vipsi - vipsi basics
Author: kio@little-bat.de
Keywords: vipsi shell script interpreter programming

# vipsi - shell and script language

## vipsi basics

### what's it all about?

#### vipsi - the friendly one.

vipsi is intended as an easy-to-use shell and script language with a syntax similar to C or Basic. vipsi can be used interactively as a shell, as a pocket calculator, for file synchronization, to update a web site or for CGI scripts.

vipsi is currently a command line tool only. this may be scary ;-) but despite that, it is easy to use. and a nice window toolkit is planned. i'm looking for volunteers.

vipsi uses unicode for text, double float for numbers and handles lists and arrays alike. it comes with lots of predefined functions, mathematical and for text manipulation and conversion, has powerful list operators and of course all the basic stuff.

#### the name

`vipsi` stands for VIP Script Interpreter. `vip` stands for Virtual Instruction Processor. formerly the vip code was tailored to a virtual cpu and the language was forth-like. when sun adopted it for their java virtual machine (joke!) i lost interest in it. in summer 2001 i needed a programming language for generating and maintaining a web site. i was too lazy to learn php or pearl, so i wrote vipsi...

#### major drawback

vipsi does not start with 'p'. ((!LAMP))

### getting help

start vipsi with command line argument -h or something similar and it will show it's command line options.

most notably: starting it with no options at all. then it will start in interactive shell mode. the first thing it prints is information on how to get help:

among lot's of other words, it knows the word words, which will list all built-in words:

the next most important word is info, which takes the name of a word as a parameter and prints information about that word:

### a smidgen of vipsi code

here are some example dialogs with vipsi, started as interactive shell.

if you type in an expression, the expression is executed and the result is automatically printed. this is a convenience behaviour of vipsi when running interactively:

    vipsi> 1230.40 * 1.18     = 1451.872
    vipsi> sin(pi/2)      = 1
    vipsi> 46+3*1.1      = 49.3
    vipsi>

the following shows how to define a variable, how loops look like in general and how intentional printing to the user (to stdout) works:

    vipsi> var i=10
    vipsi> do i--; while i; put i," "; loop    9 8 7 6 5 4 3 2 1
    vipsi>

and take a preview on function definition, the possibility of recursion, conditional branches and how to call a function. nothing odd here. just note, that then marks the end of the if … else … then construct. you can copy these examples verbatim and try them, if you have vipsi already installed.

    vipsi> proc fak(n) { if n>1 return n*fak(n-1) else return 1 then }
    vipsi> put "fak(7) = ",fak(7)      fak(7) = 5040
    vipsi>

next let's try a simple script. the following sample script uses the built-in function env to get the environment variables of the process and prints them. this example requires that vipsi (or a link to vipsi) is already installed in /usr/bin/. note that most spaces and the brackets around function arguments are optional and used here for readability only.

    #!/usr/bin/vipsi

    var env_vars = env
    var i=0

    put nl,"current environment variables:",nl

    do
        while ++i < count( env_vars )
        put name( env_vars[i] ), " = ", env_vars[i], nl
    loop

    end

### vipsi command syntax

#### instructions

any script or program is made out of instructions. they start with a command word and are followed by arguments if the command requires so. arguments need not be enclosed in brackets, multiple arguments are separated by commas. instructions itself need not be separated, except in very rare cases, but you can use semicolons for that purpose if you like.

you may insert comments into your script files:

        single-line comments are introduced with //
        block comments are wrapped with /* and */

instructions may be stringed together without any special separator, though you can use ';' for this purpose if you like:

        var a=123; put a; a=a*3; put a;
        var a=123 put a a=a*3 put a

#### expressions

arguments are made out of expressions which yield some kind of result value. expressions are in the simplest case just one number, text or list literal constant:

        put 123.456
        put "anton"
        put { 1, 2, 3 }

but they may be also variables, terms consisting of sub terms combined by operators and even more complex things:

        put 12.5 + 22*1.4e3
        put ( int(12.5 + 22*1.4e3)<<(my_flag?2:4) ) | $E000;
        put my_textlist[ a+3, i+1 to j-2 ]

##### number literals

        1234                is an integer number
        -1234               a negative one
        123.456e-78         huh, floating point...
        $123affe            hexadecimal notation
        -$b00.b00f
        %10100101           binary notation
        +%1010.0101
        'Äffe'              base 256 ((unicode character codes))

##### text literals

        "hello"             a simple text
        "say \"hello\""     escaping a control

##### list literals

        { }                 an empty list
        { 1, 2, 3 }         a list with three items
        { "a", "b", 44 }    again three items
        { 1, {"a","b"} }    two items. the second item is itself a list
        { x=22, y=33 }      two items, named x and y

#### operators

vipsi evaluates expressions with proper operator hierarchy.

        2+3*4  ==  2+(3*4)

the exact listing of all operator precedences is like this:

    highest:    round brackets:     ( … )
                postfix:            ++  --  [ … ]  ( … )  .
                bit shifting:       >>  <<
                bit masking:        &  |  ^
                multiplication:     *  /  %
                addition:           +  -
                concatenation:      # ##
                comparision:        ==  !=  >=  <=  >  <
                boolean:            &&  ||
                triadic selector:   ? … : …
    lowest:     assignment:         =  +=  -=  *=  /=  %=  #=  >>=  <<=  &&=  ||=  ^=  &=  |=  ##=

also note, that assignments are 'right oriented', that is, if an expression is made up with multiple assignments, the rightmost assignment operator is evaluated first:

        var a,b,c,d;
        a = b = c = d = 0;

this is the same as in C: the operator '=' returns a result. you can see this if you enter in the vipsi shell:

        vipsi> var a
        vipsi> put a = 2     2    // prints the result

#### functions

functions are defined with the word proc:

        proc foo()  { }
        proc bee(a) { put a }
        proc x(a,b) { return a+b }

calling a function works like this:

        var a = sin(2*pi)
        a = max(22,a)

built-in functions which always take exactly one argument may be called without round brackets around their argument, if the argument is a simple term without operators. in doubt use brackets.

        sin a       ==  sin(a)
        sin 2*pi    ==  sin(2) * pi   !!

functions which expect numeric arguments also accept text, which is simply converted to a number. if it can't be converted this results in an error.

        sin "1.23"      // ok
        sin "boo"       // error

#### flow control

there are some words for conditional execution of instructions and instruction loops.

        proc fak(a) { if a<=1 return 1; else return a*fak(a-1); then }

note the slight difference in syntax to C which may be familiar to people accustomed to forth. the `else` part is optional. there is no need to put the condition in round brackets as in C, though you can. vipsi knows where an expression ends ;-) all ';' in the above example are optional too.

        proc fak(a) { var r=1; do; while a>1; r=r*a; a=a-1; loop; return r; }

another variant of this common function, now with iteration instead of recursion. `do` and `loop` embrace the loop, the start condition is set before the loop using standard methods, the loop iterator is decremented using standard methods, and the escape from the loop is done with a `while` statement. all '`;`' are optional.

loops are always formed with `do` and `loop`. but there are some more control statements:

        while condition  continue while condition is true; else exit loop
        until condition  similar to while, but with inverted condition
        exit             unconditionally exit the loop
        next             unconditionally jump to start of loop for another go

loops with do … loop, conditional flow control with if, else, then, return and end may be safely mixed but must be properly nested. flow control nesting is checked by the tokenizer before execution starts.

#### identifiers (names)

you need names for your custom variables and procedures. also each item in a list may be named. in fact, all variables that you define are put in a list which is named globals.

there are nearly no restrictions on variable names. any sequence of data may be used as a variable's name. but there are restrictions on names which are recognized by vipsi easily.

the following rules must be obeyed for new names:

- names must start with a letter or with '_'. this may be a latin letter, greece, or ... yes you got it.
- subsequent characters must be a letter, '_' or a number digit. number digits of other script systems are allowed.
- name must be 255 characters max.
- names must not be a word pre-defined by vipsi.

examples of legal names:

        anton, cäsar, µ, Ωsub, sin21, _, _liste_, Café, Smørebrød, Жизмн.

examples of illegal names:

    	catch 23      contains space
    	sin           pre-defined word
    	6pack         starts with a number
    	big-letters   no '-' allowed

what about "no restrictions whatsoever" for variable names?

in fact, the above restrictions are only imposed on vipsi's text parser. variable names may be really any name. e.g. frequently you have variables with an empty name, e.g. command line arguments which are stored as unnamed variables in the globals list variable or most commonly unnamed items in lists. you can create and access them with the help of the `@` prefix operator:

    // define variable with name "55":
        var @"55" = 66; put @"55"

    // fetch variable by a computed name:
        var vname="";
        put nl,"which var? "; get vname;
        if(exists @vname) put " = ",@vname,nl else put "does not exist",nl then

    // rename variables to computed names:
        var text = file("test file.txt")
        split text    // split text at line breaks resulting in a list of texts
        var i=0
        do  while ++i <= count text
            rename text[i] = text[i]
        loop

see the example script count_words.vs for an application of this feature.

in general you won't need it and it is a feature for experts and not beginners.

### sequence of evaluation

words are always evaluated and executed from left to right as soon as possible. operators and procedures are executed as soon as their operands are evaluated. the interpreter has no freedom of choice here!

pruning for `?`…`:` and `&&` and `||` is implemented (version 0.8.3). if the result is sufficiently determined by the first operand, then the second operand is simply skipped and not executed.

pruning for `&&=` and `||=` is not yet implemented (version 0.8.3)

e.g.:

        a = b+c*d

sequence of evaluation is: `a b c d * + =`

        a = sin(b)

sequence of evaluation is: `a b sin =`

        var foo = oldfoo

sequence of evaluation is: `var foo oldfoo =`

        a = {1}; a[2] = a;

sequence of evaluation: `a {1} = ; a 2 [] a = ;`
comment for experts: the result is `{ 1, { 1, 0 } }`, because when a[2] is accessed as left-side operand for the assignment, it is created ad-hoc, with an initial value of `0`. then this just modified variable a is used as right-side operand for the assignment, storing that `{1,0}` into `a[2]`.

important note: the behavior of operator `=` is subject to change: future versions of operator `=` will delay creation of target variables, e.g. if the left side operand requires creation of a new item in a list, until the right side operand is evaluated. then in the above example the creation of a[2] is delayed, so that the result will be `{ 1 {1} }` instead. shurely a rare condition.

        proc l(i){put i;return i}
        var a=0; a = l(a++)+l(++a)

sequence of evaluation in line 2: `var a 0 = ; l a ++ l ++ a + =`
proc l() prints "0" and "2"; the result stored in a afterwards is 2.
comment for experts: there is no such ambiguity as in C/C++. the behaviour of vipsi is clearly defined.

        proc l(i){a=0;put i;return i}
        var a=0; a = l(a++)+l(++a)

sequence of evaluation in line 2: `var a 0 = ; a l a ++ l ++ a + =`
proc l() prints "0" and "1"; the result stored in a afterwards is 1.
comment for experts: there is no such ambiguity as in C/C++. the behaviour of vipsi is clearly defined.

### modes of operation

there are basically three modes of operation: ad-hoc calculator, interactive user shell, and running a script.

#### interactive mode

if vipsi is launched without any command line arguments, it starts in interactive mode. first it prints a greeting and how to get help:

    [localhost:~] kio% vipsi

    vipsi - VIP Script Interpreter 0.8.3 (c) 2001-2003 kio@little-bat.de
    development release. please set your terminal to utf-8 and vt100.
    see <http://vipsi.sourceforge.net> for latest documentation.

            vipsi is waiting for your commands.
            type an expression to calculate the result.
            type «words» for a list of words.
            type «info "foo"» for info on word «foo».
            use «pd_up» and «pd_dn» for history.
            type ctrl-D to quit.

    vipsi>

then it reads instructions line by line and executes them in turn.

    vipsi> dir ""

    Host: localhost; User: kio(staff); SysLoad: 0.38,0.35,0.33
    Time: 2002-04-24 20:00:28; Booted: 2002-04-24 17:57:33 (2h:2m:55s);

    volume name                     flags      free  modified
    ------------------------------- ---- ----------  -------------------
    /                               drwx   7.688 GB  2002-04-24 18:00:00
    /Volumes/HD40                   drwx   1.507 GB  2002-04-22 15:29:14

standard input is bold or colored by vipsi and error messages are inverted and red. (you can set this in a preferences file.)

if an error occurs while executing your command, the appropriate message is displayed inverted and red and the bogus line is offered for input again, with the cursor positioned to to location where the parser stalled.

    vipsi> sin("apfelkuchen")*22
    *****  not a number
    vipsi> sin("apfelkuchen")*22

if the instruction typed in is an expression which returned a result, this result is automagically printed for your convenience:

    vipsi> 998.00 * 1.18     = 1177.64
    vipsi>

results are automagically printed only in interactive shell mode, not when running a script.

#### running scripts

when vipsi is started with a valid file path as command line argument, then it loads that file and executes it.

    [localhost:~] kio% vipsi test.vs

this runs the script test.vs.

if all is set up properly, a script can also be started like a program:

    [localhost:~] kio% test.vs

this requires that vipsi is installed at the recommended position in /usr/bin/ or that a link is placed there, and that the script file starts with the line `#!/usr/bin/vipsi` telling the system which interpreter to start and that the script file is tagged as executable with `chmod a+x test.vs` and that the script is in the system's command search path.

it is also possible to make scripts double-clickable from your windowing system, but the procedure is specific to GUI and therefore explained there.

you may pass arguments to a script:

    [localhost:~] kio% vipsi test.vs anton bärbel cicero

or

    [localhost:~] kio% test.vs anton bärbel cicero

these command line arguments are stored into global variables. because the passed data has no name on it's own, these global variables remain unnamed. how the text appended to the script name as command line arguments is mixed up depends on your shell, but as a first rule of thumb expect that white space separates individual arguments.

so the examples above result in 3 arguments with the value "anton", "bärbel" and "cicero".

the following code snipped from a script shows how to access them:

    #!/usr/bin/vipsi

    var argc = count globals - 1;

    put nl, "test script", nl
    put nl, "you passed ", argc, " arguments:", nl
    var i = 0
    do
        while ++i <= argc
        put "arg[", i, "] = ", globals[i], nl
    loop
    end

it's just that simple: all global variables are stored in a list variable which is called globals. you can access all global variables which you defined with new or var either by their name or by their position in the globals list variable, whichever suits best. for command line arguments it's clearly better to access them by position, because they are unnamned B-).

you may ask, why `1` was subtracted when calculating the number of arguments:

expressions are always evaluated from left to right. functions and operators as soon as their arguments are complete. so let's see their evaluation sequence:

    var argc globals count 1 - =

this is just like forth or rpn. the important point is, that var argc is executed and thus created before count is executed, and therefore globals will already contain the new variable argc when count determines the list size of globals. therefore we must subtract 1 item just for the unwanted variable argc itself.

note: in the future the creation of new variables may be delayed until the right side argument of operator `=` is evaluated. then subtracting `-1` will no longer be neccessary, but wrong. :-|

if an error occurs, the execution of the script is aborted and an error message is printed indicating the error and position in the file. the indicated position may be slightly behind the error. if the error occured in a procedure, the position in the procedure is indicated (if possible) or the position, where the procedure was called. the error message contains a stack trace:

    vipsi> proc a(w) { if w a(w-1) else fsdfsfj then }
    vipsi> a(3)
    *****  Variable "fsdfsfj" not found
           in: a()
           in: a()
           in: a()
           in: a()
    vipsi> a(3)

#### running cgi scripts

vipsi scripts may be used to handle cgi requests. in order to be successfully called by the http server, scripts must be tagged as executable, must reside in a directory where the http server is willing to execute scripts and must of course start with the right hashbang in line 1:

    #!/usr/bin/vipsi

if you decide to put the vipsi interpreter into another location on your web server, then your scripts must start with that according path.

if all that is done, your scripts can be called from your web browser, either directly or from a form.

as an example let's quickly overview the default installation of the Apache server on MacOS X.

- put scripts in /Library/WebServers/CGI-Executables (requires sudo)
- in the URL use the path …/cgi-bin/ which will be mangled by the Apache
- put your private pages in ~/Sites/
- the URL to your private pages is http://127.0.0.1/~yourname/

cgi scripts are passed arguments from the URL or form data either with method GET or method POST. both are already handled and decoded for you by vipsi when the script is started. data passed to a cgi script is typically named. these arguments are stored in global variables which are named according to those cgi parameter names.

next, scripts are supposed to send a proper html page (or any other proper formatted data) by simply printing to stdout. first come two lines of http header (thereof the second is empty) and then the html page (or a gif file, if you decided so).

the following shows a simple cgi script, which presents it's cgi parameters in a table:

    #!/usr/bin/vipsi

    // http header:
    put "content-type: text/html; charset=utf-8\n\n"

    // html page, header etc.:
    put "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n",
        "<html><head><title>vipsi cgi test script</title></head>",
        "<body style='font-family:sans-serif;'>",
        "<h2>passed cgi parameters</h2>\n"

    var argc = count globals-1

    put "<table><tr><td><b>name</b></td><td><b>value</b></td></tr>\n"

    var i = 0
    do
        while ++i<=argc
        put "<tr><td>", name(globals[i]), "</td><td>", globals[i], "</td></tr>"
    loop

    put "</table></body></html>\n"
    end

note: it would have been a pretty pain in the ass to convert all the `<` and `>` in the above code example to `&lt;` and `&gt;` manually, so i used vipsi to do the job. see the documentation for the convert instruction ;-).

if your script contains an error, vipsi does it's best to display the error and position in the cgi script in a html file:

#### doing a quick calculation with vipsi

vipsi has an alias, `calc`, which is created when you install vipsi.

if vipsi is called this way, it does not start up to full live, but only takes it's command line arguments, evaluates them, prints the result and quits. though, if you like, you can include as many and as complex scripts as you like in your 'simple' mathematical experession.

    [kio@vanilla vipsi]$ calc 98.00 * 1.16
    vipsi: variable "bin" not found
    [kio@vanilla vipsi]$ calc "98.00 * 1.16"
     = 113.68

the first call unluckily enough produced an error. the shell examined all arguments and expanded the `*` token. :-( therefore always use double quotes.