The program is a list of process calls and type definitions.
The language is line based, so every line is one of these.
A process call looks like this:
process a b
A process can take any number of arguments, even zero.
The special form let
is used to define types:
let typename existing-type expression1 expression2...
The expressions are the process body.
The new typename
can now be called as a process.
When calling a process, the supplied argument has to be the inner type.
Additionally the new type is returned.
The only syntax is the line based structure and round brackets.
If a bracket opens in a line without closing, all the following lines until the matching closing bracket will belong to single grouped expressions.
These Expressoins are equivalent:
a (b c) (d e)
a (
b c
d e
)
Comments are indicated by a #
at the begin of the line.
It lasts until the end of the line, but is sensitive to brackets, so any opening bracket has to be closed:
no comment # comment
no comment again # comment again ( still comment
still comment
still comment ) still comment
no comment
# ( comment ) still comment
no comment
# ( comment ) still comment ) error: too many closing brackets
A value is a compile time type. It's stored as a textual representation until it's attached to a primitive type.
When calling a primitive type, it has to be initialized by a value.
A call to a primitive type takes exactly one argument.
All calls to primitive types don't do anything.
There are a bunch of supported primitive types:
Unsigned and signed integers of size pow(2, N) where n is in inclusive range (3; 7), called u
/i
followed by the size, system dependant integer types usize
and isize
and the floating point types f32
and f64
.
Besides there is bool
.
Structure types can be used to create composed types like this:
struct type1 type2
A struct definition can take any number of arguments.
A struct call takes the number of arguments, a struct requires.
If an argument is not supplied it's interpreted as uninitialized.
Calling a struct does not do anything.
Array types can be used to create types containing a specified count of other types.
They can be written like this:
array count type
An array definition only takes a these both arguments.
A struct call takes count
arguments.
Calling an array does not do anything.
User defined types are similar to primitive types.
They can be defined using let
and define a process, that can be called.
Calling an user defined type takes all arguments, which calling the inner types allows.
There are no variable names.
Instead, everytime a process is called, a new variable gets created implicitly.
This variable can be used in other calls.
The variable is accessed as a field of some global struct-like type, which cointains fields of all used variables.
The global type is called the
.
Accessing it after a process call looks like this:
sum a b
the.sum
If the result is a struct, the field types can be accessed by specifying the type name after another .
.
Here an example definition of the sum
type and accessing the arguments:
type sum (struct in out) (...)
sum (in (array a b))
print the.sum.out
These variables are only bound at runtime, when it is used.
There is also a similar type, called a
, which can create uninitialized variables explicitly like this:
# calculate some in value
in value
a.out
sum the.in the.out
print the.out
When reading from an uninitialized variable, it will be ensured at compile time, that it has been written to at least once.
Theres another process local variable this
.
It maps to the inner type of the currently defined type.
So when defining a function, which sets out
to in
, it can be used like this:
let set (struct in out) (
memcpy this.out this.in
)
It's just if
and optionally else
and even elsif
for simplicity.
The condition can only be a bool
.
The blocks don't introduce a new scope.
When a variable is only created in one branch and used afterwards it's an error.
The else branch is just a new line afterwards.
if cond (sum a b)
else (mul a b)
print the.sum.out #error, sum only defined in one branch. Result ambiguous (if sum was used before) or result may be uninitialized (if result was never used yet.
Loops are expressed using something similar to goto:
loop (i32 1) (u32 1)
if (< loop.i32 (i32 5)).out (repeat (inc loop.i32) (mul loop.u32 (u32 2)).out)
print loop.u32
It's possible to use multiple loops. The most recent loop can be renamed to different names.
loop (usize 0)
let first loop
inc loop.usize
loop (bool false)
if cond1 (repeat loop some.first)
elsif cond2 (repeat first some.usize)