Program consists of several subroutines, each identified by its name. Subroutine definition
starts with the line containing keyword sub
followed by name. Next lines contain the body of
subroutine:
sub main
call "println" "Hello, World!"
Subroutine declaration sub main
should start from the beginning of the line, without indent. All
following lines should be indented, until next declaration:
sub main
call "greeting"
sub greeting
call "println" "Hello, World!"
In this example subroutine main
calls another subroutine greeting
, which in its turn calls
built-in subroutine println
passing it the string to print.
Default executor starts the program with running subroutine main.
There are only a few types of statements. They are recognized by the first word of the statement.
Subroutine call as we have seen before starts with the keyword call
which is followed by
the subroutine name (or expression evaluated to subroutine name) and, possibly, more expressions
to be evaluated to subroutine arguments. For example the line call "println" "Hello"
requires
the executing system to call subroutine println passing it string Hello as argument.
Assignment of a value to variable is described by keyword let
which is then followed by
the name of the variable (or expression evaluated to subroutine name) and expression the value of
which should be written to variable:
sub main
let "x" 5
let "hi" "Hello, People!"
call "println" $hi
Here the value 5
is assigned to variable named x
and string value Hello, People!
is
assigned to variable hi
. As you may guess later $hi
is used to fetch the value of the
variable with the name hi
- i.e. dollar sign $
is a prefix for getting variable value.
Evaluation statement is started with keyword eval
it does nothing except evaluating the
expression following this keyword. We shall see later when this could be useful.
Combined statement is created by putting several statements into one line, separating them
with semicolon ;
like following:
sub main
call "print" "Hello, " ; call "println" "World!"
Note (it is important) that semicolon should be separated with spaces on both sides!
Conditional statement is not used on its own. It should always be a part of a combined statement, like this:
sub conditional_print
if $x 5 = ; call "println" "X equals to five!"
As you see, conditional statement is started with keyword if
which is followed by an expression.
You also see from here that expressions are written in postfix notation (i.e. equality operator
is following its two operands) - you will read about it later.
If the expression after if
is calculated to be non-zero, the rest part of combined statement
is executed. Otherwise it is skipped.
Loop statement is similar to conditional. It is started with keyword while
and the following
part of the combined statement is executed several time, while the expression after while
is
evaluated to be non-zero:
sub numbers
let "x" 10
while $x ; call "println" $x ; let "x" $x 1 -
Here the second part of combined expression prints the value of x
and third part decrements the
value of x
so that the whole statement is executed ten times until x
becomes zero.
Expressions are evaluated on the special stack, like in Forth programming language or in the processor of Java Virtual Machine. The evaluation is performed straightforward through all components of expression, by the following rules:
1
or 500
) is encountered, it is pushed to stack;"println"
is equivalent to 'println
;$
then the value of the corresponding variable is fetched and
pushed to stack;Operators usually take one or more operands from the top of the stack, perform an operation and put the result back to stack. For example:
sub main
call 'println 5 3 +
Here execution works as following: the keyword call
is recognized so the system is ready to
perform subroutine call; then the rest is evaluated:
'println
is recognized as a string and is pushed to stack;5
is recognized as a number and is pushed to stack;3
is recognized as a number and is pushed to stack;+
is an operator - it pops two last values from stack (3
and 5
), adds them together and
then pushes the result (8
) to stack.So when expressions are evaluated, two values are left on the stack - the string println
and the
number 8
. The first is interpreted by call
to be the name of subroutine, the latter becomes
an argument for subroutine.
Most binary operators work similarly:
sub main
let 'x 9
call 'println $x $x *
call 'println $x 3 /
call 'println $x 5 >
call 'println $x 8 <=
This program will printl values of 81
, 3
, 1
and 0
(you see, logical operators have result
of 0
for false
and 1
for true
).
The full list of built-in operators could be found further.
Since subroutine call
statement uses a string value as a name of subroutine, it could be
evaluated in runtime:
sub main
let 'name "println"
call $name 15
This could be useful for building some flexible code with self-modifying behavior.
Similarly, the names of variables could be constructed in the runtime:
sub main
let 'a 1 + 10
let 'a 2 + 50
call 'println $a1
call 'println $a2
In this example the variable name is concatenated of a prefix a
and index (1
or 2
). This
is useful when we need an array in our program. Consider the following example:
sub main
let 'total 10
call 'calc_squares
call 'print_values
sub calc_squares
let 'i 1
while $i $total <= ; let 'val $i + $i $i * ; let 'i $i 1 +
sub print_values
let 'i 1
while $i $total <= ; call 'println 'val $i [] ; let 'i $i 1 +
Here main
subroutine assigns 10
to variable total
and calls calc_squares
and then
print_values
subroutines.
The calc_squares
have a loop inside, which runs variable i
from 1
to 10
and calculates
the square of it (by $i $i *
expression) which is assigned to variables from val1
to val10
thanks to let 'val $i + ...
statement.
The print_values
also runs a loop for i
from 1
to 10
and fetches variables from val1
to
val10
, printing their values.
Fetching is performed by special binary operator []
. Obviously we could not use usual dollar
sign to fetch the variable with the name not evaluated yet. Instead such operator pops out two
last values from the stack (i.e. string val
and the value of variable i
), concatenates these
two values (so that string like val1
, val3
etc. is formed) and then fetches the value from
variable with such name.
Variables have different scope, which is determined by the length of their names:
a
, i
or x
) are considered to
be local variables, visible only in the current subroutine;For example:
sub main
let 'x 5
let 'xaxa 10
call 'change
call 'println $x
call 'println $xaxa
sub change
let 'x 7
let 'xaxa 20
This code will print values 5
and 20
because changing variable x
inside subroutine change
does not affect x
in main
, while xaxa
is shared between both subroutines and is changed.
Special case of local variables are subroutine arguments. They are represented by a single char variable names following the definition of the subroutine. When subroutine is called, the same amount of values are popped out from stack and are assigned to local variables with those names. For example:
sub main
call 'print_three 5 9 30
sub print_three a b c
call 'print $a
call 'print " "
call 'print $b
call 'print " "
call 'println $c
Here when call
statement is executed we have 4
values on the stack - subroutine name and then
three numbers. Subroutine with the required name (print_three
) is found and is determined to
have 3
arguments. So 3
values are popped out from stack before executing this subroutine and
are assigned to its local variables a
, b
and c
(so 5
goes to a
etc.)
After execution you will see line 5 9 30
printed.
Several subroutines and operators are predefined:
print
and println
are subroutines which pop a single value from stack and print it to
output (println goes to new-line after that);+
, -
, *
, /
, %
are binary operators popping two values from stack and pushing the
result back;add
, sub
, mul
, div
, mod
are just other names for the same operators;+
or add
could be also applied to values one or both of which is a string as a concatenation
operator;neg
takes a numeric value from stack and pushes its negation (i.e. -5
for 5
etc.);=
, !=
, >
, <
, >=
, <=
are comparison operators for comparing numbers, they return 1
or 0
to stack; they have synonyms eq ne gt lt ge le
respectively, while >=
also have a form
of =>
for convenience and !=
has another form <>
;=
and !=
could also be applied to strings;!
or not
performs a logical negation converting non-zero values to zero and vice versa;&&
and ||
are logical AND and OR, they also have forms and
and or
respectively;atoi
pops a string, converts it to integer and pushes back;itoa
on contrary converts integer to string;?int
pops a value and pushes back either 1
if it was a number or 0
otherwise (its synonym
is isint
);?str
or isstr
similarly checks the value from stack for being a string;[]
or peek
takes two values from stack, concatenates them to create a variable name and then
fetches the value of such variable and pushes it to stack.Stack manipulation operators are similar to ones in Forth language:
drop
pops and loses the value from stack (to remove unnecessary values);dup
duplicates the value at the top of stack (i.e. pops a value and pushes it twice);swap
pops two last values and pushes them back in different order;over
peeks at the second value (one below the top) on the stack and pushes this value
at a top; (so stack ... 2 3 4
turns to ... 2 3 4 3
);rot
rotates three topmost values, so stack ... 1 2 3 4
becomes ... 1 3 4 2
i.e. third
element is moved to top.