Home | Overview | Download | Syntax | Math | Text | Code Samples | Forum | Contact
This overview shows Agena's main syntax features. For more information,
please consult the Agena
Crash Course or the Primer and
Reference:
To assign a number, say 1, to a variable called a, type:
> a := 1; |
To print the value assigned to a variable, just put a colon right behind its name:
> a: 1 |
Variables can be deleted by assigning null or using the clear statement. The latter also performs a garbage collection.
> a := null: null > clear a; > a: null |
Agena supports both real and complex arithmetic with the + (addition), - (subtraction), * (multiplication), / (division) and ^ (exponentiation) operators:
> 1+2: 3 > exp(1+2*I): -1.1312043837568+2.4717266720048*I |
There are various other mathematical operators, just have a look at the manual.
A text can be put between single or double quotes:
> str := 'a string': a string |
Substrings are extracted as follows:
> str[3 to 6]: stri |
Concatenation, search, and replace operations:
> str := str & ' and another
one, too': a string and another one, too > 'another' in str: 14 > replace(str, 'and', '&'): a string & another one, too |
There are various other string operators and functions available.
Agena features the constants true, false, and fail to represent Boolean values. fail may be used to indicate a failed computation. The operators <, >, =, <>, <=, and >= compare values and return either true or false. The operators and, or, not, and xor combine Boolean values.
> 1 < 2: true > true or false: true |
Tables are used to represent more complex data structures. Tables consist of zero, one or more key-value pairs: the key referencing to the position of the value in the table, and the value the data itself.
> tbl := [ > 1 ~ ['a', 7.71], > 2 ~ ['b', 7.70], > 3 ~ ['c', 7.59] > ]; |
To get the subtable ['a', 7.71] indexed with key 1, and the second value 7.71 in this first subtable, input:
> tbl[1]: [a, 7.71] > tbl[1, 2]: 7.71 |
The insert statement adds further values into a table.
> insert ['d', 8.01] into tbl > tbl: [[a, 7.71], [b, 7.7], [c, 7.59], [d, 8.01]] |
Alternatively, values may be added by using the indexing method:
> tbl[5] := ['e', 8.04]; > tbl: [[a, 7.71], [b, 7.7], [c, 7.59], [d, 8.01], [e, 8.04]] |
Of course, values can be replaced:
> tbl[3] := ['z', -5]; > tbl: [[a, 7.71], [b, 7.7], [z, -5], [d, 8.01], [e, 8.04]] |
Another form of a table is the dictionary, which indices can be any kind of data - not only positive integers. Key-value pairs are entered with tildes.
> dic:= ['donald' ~ 'duck', 'mickey'
~ 'mouse']; > dic['donald']: duck |
Sets are collections of unique items: numbers, strings, and any other data except null. Any item is stored only once and in random order.
> s := {'donald', 'mickey',
'donald'}: {donald, mickey} |
If you want to check whether 'donald' is part of the set s, just index it or use the in operator:
> s['donald']: true > s['daisy']: false > 'donald' in s: true |
The insert statement adds new values to a set, the delete statement deletes them.
> insert 'daisy' into s; > delete 'donald' from s; > s: {daisy, mickey} |
Three operators exist to conduct Cantor set operations: minus, intersect, and union.
Sequences can hold any number of items except null. All elements are indexed with integers starting with number 1. Compared to tables, sequences are twice as fast when adding values to them. The insert, delete, indexing, and assignment statements as well as the operators described above can be applied to sequences, too.
> s := seq(1, 1, 'donald',
true): seq(1, 1, donald, true) > s[2]: 1 > s[4] := {1, 2, 2}; > insert [1, 2, 2] into s; > s: seq(1, 1, donald, {1, 2}, [1, 2, 2]) |
Pairs hold exactly two values of any type (including null and other pairs). Values can be retrieved by indexing them or using the left and right operators. Values may be exchanged by using assignments to indexed names.
> p := 10:11; > left(p), right(p), p[1], p[2]: 10 11 10 11 > p[1] := -10; |
Conditions can be checked with the if statement. The elif and else clauses are optional. The closing fi is obligatory.
> if 1 < 2 then > print('valid') > elif 1 = 2 then > print('invalid') > else > print('invalid, too') > fi; valid |
The case statement facilitates comparing values and executing corresponding statements.
> c := 'agena'; > case c > of 'agena' then > print('Agena!') > of 'lua', 'Lua' then > print('Lua!') > else > print('Another programming language !') > esac; Agena! |
The optional onsuccess clause put right after the last elif or of condition executes statements if at least one of the conditions resulted to true.
> flag := false; > if 1 < 2 then > result := 'valid' > elif 1 = 2 then > result := 'invalid' > onsuccess > flag := true > else > result := 'invalid, too' > fi; > flag: true |
A for loop iterates over one or more statements.
It begins with an initial numeric value (from clause), and
proceeds up
to and including a given numeric value (to clause). The step
size can
also be given (by clause). The od keyword indicates the
end of the
loop body.
The from and by clauses are
optional. If the from clause is omitted, the loop
starts with the initial value 1. If the by clause is
omitted, the step size is 1. Negative step size are accepted, as well.
The current iteration value is stored to a control variable (i in this
example)
which can be used in the loop body.
> for i from 1 to 3 by 1 do > print(i, i^2, i^3) > od; 1 1 1 2 4 8 3 9 27 |
The last iteration value (plus the step size) can also be queried in the code surrounding the loop:
> i: 4 |
You can also use the downto keyword to count down by step size 1 or any other step.
for/in loops iterate over all elements in
tables, sets, sequences, and strings.
> for i in ['Agena', 'programming',
'language'] do > print(i) > od Agena programming language |
You can also iterate only the keys of a table (or sequence) or both keys and values:
> for keys i in ['donald' ~ 'duck',
'daisy' ~ 'duck'] do > print(i) > od; daisy donald > for i, j in ['donald' ~ 'duck', 'daisy' ~ 'duck'] do > print(i, j) > od; daisy duck donald duck |
A while loop first checks a condition and if this condition is true or any other value except false, fail, or null, it will iterate the loop body again and again as long as the condition remains true. The following statements calculate the largest Fibonacci number less than 1000.
> a := 0; b := 1; > while b < 1000 do > c := b; b := a + b; a := c > od; > c: 987 |
The do .. as and do .. until loops check a condition at the end of the iteration. Thus the loop body will always be executed at least once.
> c := 0; > do > inc c > as c < 10; > c: 10 > c := 0; > do > inc c > until c >= 10; > c: 10 |
All flavours of for loops can be combined with a while or until condition. As long as the while or until condition is satisfied, i.e. is true, the for loop will iterate.
> for x to 10 while ln(x) <= 1
do > print(x, ln(x)) > od; 1 0 2 0.69314718055995 |
You can also use a conditional for loop where you have to explicitly change the loop control variable within the loop body:
> for x := 1 while ln(x) <= 1 and x <= 10
do > print(x, ln(x++)) > od; 1 0 2 0.69314718055995 |
The skip statement causes another iteration of the loop to begin at once, thus skipping all of the following loop statements after the skip keyword for the current iteration.
The break statement quits the execution of the loop
entirely and proceeds with the next statement right after the end of
the
loop.
So, infinite loops can also be written as:
> c := 0; > do > inc c; > if c > 9 then break fi; # this is a comment: the `loop exit` condition > od; > c: 10 |
Finally, the redo statement restarts the current iteration of a for/to or for/in loop, the relaunch statement starts a loop all-over again.
Procedures cluster a sequence of statements into abstract units which then can be repeatedly invoked.
A procedure can call itself to generate the final result. The return
statement passes the result of a computation.
> factorial := proc(n) is > # computes the factorial of an integer n > if n < 0 then return fail > elif n = 0 then return 1 > else return factorial(n-1)*n > fi > end; > factorial(4): 24 |
Local variables can be declared with the local statement.
If you want to pass a variable number of arguments, use the ? keyword in the parameter list and the varargs table within the procedure body to access them.
The system variable nargs contains the number of
arguments passed.
Type checking can be done within the body of a procedure with the type,
::, or :-
operators or by optionally stating the required type or types of an
argument in
the parameter list with the :: operator. Optionally, Agena can
check the type or types of return by passing them
right after the parameter list.
> f := proc(x :: number, ?) :: table
is > local result; > result := [x, nargs, ?, ?[1]]; > return result > end; > f(10, 'a string', 20): [10, 3, [a string, 20], a string] > f('a string', 20): Error in stdin: invalid type for argument #1: expected number, got string Stack traceback: stdin, in `f` stdin, at line 1 in main chunk |
Use curly brackets if you want to check for more than one type (argument or return).
zero := proc(x :: {number, complex}) ::
{number, complex} is return if type x :: number then 0 else 0 + 0*I fi end; zero(0!0): # the prettyprinter just issues 0 for 0 + 0*I 0 |
Agena remembers procedure results if the remember function is invoked. An optional table of predefined results can also be given. This significantly speeds up recursively defined procedures.
> fib := proc(n) is > assume(n >= 0); > return fib(n-2) + fib(n-1) > end; > remember(fib, [0~1, 1~1]); > fib(50): 20365011074 |
You can also assign precomputed results to a procedure by using the rtable.defaults function.
Alternatively, just put the feature reminisce
statement just after the is keyword to switch on the
remember functionality. In this case, you cannot pass defaults and you
need to
define an explicit exit condition, but it should suffice in most cases:
> fib := proc(n) is > feature reminisce; > if n in 0:1 then # n = 0 or n = 1 ? > return 1 > else > return fib(n-2) + fib(n-1) > fi > end; > fib(50): 20365011074 |
A table can be attached to a procedure with the store feature. The table is available during the whole session and you can read or write values to it in subsequent calls:
> add := proc(x) is > feature store; > if x = null then # set default value zero > store[1] := 0 > else > store[1] := store[1] + x > fi; > return store[1] > end; > add(): 0 > add(10); 10 |
> a := 0; > a++; > a: 1 > a -:= 1; > a: 0 |
In general, any statements where an exception might be expected can be put into a try clause. If an error is issued, generated by an explicit call to the error function or to any other expression, function, or statement, control immediately jumps to the corresponding catch clause if present or to the end of the try/yrt statement if no catch clause has been given, ignoring any other subsequent statements in the try clause.
> try > for i to 3 do > print('before', i); > if i = 2 then error('oops') fi; > print('after', i) > od > catch in err then > print('error', i, err); > yrt before 1 after 1 before 2 error 1 oops |
Alternatively, you may use the protect function together with the lasterror system variable.
allow to automatically run a procedure before quitting or restarting Agena, for example to clean up temporary files, etc. Just assign a proper function to environ.onexit:
> environ.onexit := proc() is
print('Tschüß !') end > bye Tschüß ! |
This even works when pressing CTRL + C in the shell.