Syntax


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



Assignment and Unassignment

To assign the number 1 to a variable called a, type:


> a := 1;

To print the value of a variable, simply place a colon (:) after its name.


> a:
1

Variables can be deleted by assigning them 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

You can enclose text in either single or double quotes.


> str := 'a string':
a string

Substrings are extracted as follows:


> str[3]:
s

> 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.

> replace(str, 1, 'A'):
A string & another one, too.

> replace(str, 1, 'One'):
One string & another one, too.

There are various other string operators and functions available.


Booleans

Agena has the constants true, false, and fail that represent Boolean values. fail usually indicates 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 represent  complex data structures. Tables consist of zero, one or more key-value pairs: the key referring 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 it, input:


> tbl[1]:
[a, 7.71]

> tbl[1, 2]:
7.71

The insert statement adds values to a table.


> insert ['d', 8.01] into tbl

> tbl:
[[a, 7.71], [b, 7.7], [c, 7.59], [d, 8.01]]

Alternatively, you can add values 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 type of table is the dictionary, with the indices any kind of data, not just positive integers. Use the tilde to separate keys and values.


> 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 with the exception of null. All elements are indexed by integers starting from 1. Compared to tables, sequences are twice as fast when adding values. The insert, delete, the indexing and assignment statements as well as the operators previously described, do all work with 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. You can retrieve values by indexing them or by using the left and right operators. Values can be exchanged by using indexed names.


> p := 10:11;

> left(p), right(p), p[1], p[2]:
10 11 10 11

> p[1] := -10;

> p:
-10:11

Conditions

Conditions can be checked with the if statement. The elif and else clauses are optional. The final 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

You can combine both an assignment and a condition in an if clause. In all if flavours, instead of a closing fi, you can also use the end token.


> if a := 1, a > 1 then
>    print('this will never be printed.')
> end;

> a:
1

Loops

A for loop iterates over statements.


It starts with an initial numeric value (defined by the from clause) and proceeds up to and including a specified numeric value (the to clause). You can define the step size with the optional by clause. The loop body concludes with either the od keyword or, alternatively, the end keyword.


The from and by clauses are optional. If the from clause is omitted, the loop starts 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 loop's final iteration value (including the step size increment) is accessible in the enclosing code block.


> i:
4

You can also use the downto keyword to count down by 1 or any other positive 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 over the keys of a table or both keys and values:


> for keys i in ['donald' ~ 'duck', 'daisy' ~ 'duck'] do
>    print(i)
> end;
daisy
donald

> for i, j in ['donald' ~ 'duck', 'daisy' ~ 'duck'] do
>    print(i, j)
> end;
daisy duck
donald duck

A while loop first checks a condition and if it evaluates to true or any other value except false, fail or null, it will execute the loop body again and again as long as the condition is met. The following statements compute 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

As with if statements, you can combine an assignment and a check in the while clause. Note that in this case, the assignment is always redone with the latest value.


> i := 0.1;

> while logn := ln(i), logn < -0.9 do
>    print(i, logn);
>    i +:= 0.1
> od;
0.1     -2.302585092994
0.2     -1.6094379124341
0.3     -1.2039728043259
0.4     -0.91629073187416

do .. as and do .. until loops evaluate their condition at the end of each iteration. This ensures the loop body will always execute 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. The loop will continue to iterate as long as the while or until condition remains true.


> for x to 10 while ln(x) <= 1 do
>    print(x, ln(x))
> od;
1 0
2 0.69314718055995

You can also define a conditional for loop. In this case you must 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 next statement immediately starts the next iteration of a for or while loop, skipping all subsequent statements in the current iteration.


To terminate a for or while loop entirely, use the break statement. Execution will then resume at the code line immediately after the loop's end.


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 a 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 procedure body to access them.


The system variable nargs contains the number of arguments passed in a function call.


Type checking can be done within the body of a procedure with the type, ::, or :- operators or by optionally giving the required type of an argument in the parameter list with the :: token. Optionally, Agena can check the type of the return. Pass it 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 results if the remember function is invoked. An optional table of predefined results can be given, as well. 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, to activate the remember functionality, place the reminisce statement immediately after the is keyword: In this case, you cannot pass defaults and you need to define an explicit exit condition, but it may 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, and the -- suffix operator decreases it by one.


> a := 0;

> a++;

> a:
1

> a -:= 1;

> a:
0

Exception Handling

Statements that might raise an exception can be enclosed in a try clause. If an error is triggered (e.g., by an explicit call to error or another expression/function/statement), execution immediately proceeds to the relevant catch clause. If no catch clause exists, control jumps to the end of the block, bypassing any remaining 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 along 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 an exit handler to environ.onexit:


> environ.onexit := proc() is print('Tschüß !') end;

> bye
Tschüß !

This even works when pressing CTRL + C in the shell.