Syntax


This overview shows Agena's main syntax features. For more information, please consult the Agena Crash Course or the Primer and Reference:



Assignment and Unassignment

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

Arithmetic

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.


Strings

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.


Booleans

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

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

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

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

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

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

Loops

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

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


Composite Operators

There are various composite operators available for assignment: +:=, -:=, *:=, /:=, \:=, %:= and &:=. Likewise, the ++ suffix operator returns the current value of a variable and then increases it by one.

> a := 0;

> a++;

> a:
1

> a -:= 1;

> a:
0

Exception Handling

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.


Exit Handlers

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.