-
Notifications
You must be signed in to change notification settings - Fork 25
Language Reference
// Line Comment
/* Block Comment */
/* /* Nested Comment */ */
// Unit (or empty value)
val u : unit = ();
// Integers
val x : int = 0;
val y : int = 1;
val z : int = 3942;
// Real (floating or fixed point)
val a : real = 1.0;
val b : real = 2.0;
val c : real = 3.1416;
// Booleans
val k : bool = true;
val l : bool = false;
Numbers without the decimal dot are integers and cannot be mixed with real numbers.
Conversion between int
and real
needs to be explicit.
val x : int = 1 + int(1.7);
val y : real = 1.0 + real(1);
Operators require that both arguments are the same type.
- Addition : '+'
- Subtraction : '-'
- Multiplication : '*'
- Division : '/'
- Modulo : '%'
e.g.
val x = 1 + 2;
val z = -1.0; // Unary minus
val y = 3.8 * 56.0;
- And : '&&'
- Or : '||'
- Not : 'not' // 'not' is a function
e.g.
val l = not(1.0 > 3.0) || false;
- Equal : '=='
- Unequal : '<>'
- Less than : '<'
- Greater than : '>'
- Less than Eq. : '<='
- Greater than Eq. : '>='
If expressions require an else
branch.
val x = if y > 0.0 then 3.0 else 4.0;
val x,y,z = 1,2,3;
val (x,y,z) : (int,int,int) = 1,2,3;
val point : (real,real,real) = 0.0, 1.0, 2.0;
Array types are in the form array(type,size)
e.g. array(int,3)
it's an array of 3 integers.
val x = [1,2,4];
val y : array(real,3) = [1.0, 2.0, 4.0];
Function calls are special in Vult since they create implicit context. These context can be anonymous or named.
val x = foo(); // anonymous context
val y = bob:foo(); // context with name 'bob'
val z = bob:foo(); // this call is performed in the context 'bob'
When declaring a variable you can either provide a type or let the type inference decide on the type.
val x = 0; // The type is inferred to be 'int'
val y : real = 1.0; // The type is specified to be 'real'
The value of variable declarations is lost when the function returns. On the contrary, the value of memory variables in stored in the function context.
// mem variables can reference themselves and they are always initialized to zero
mem x = x + 1;
// you can specify the type as well
mem y : real = 0.0;
Once a variable is declared you can change it's value with =
. The type of a variable cannot be changed.
val x = 0;
x = 1;
// Assigning tuples
a,b,c = 0,1,2;
If you want to ignore a value you have to assign it to _
.
val x,_ = foo(); // 'foo' returns a tuple
IMPORTANT: calling a function that does not returns a value needs to be assigned to _
as follows.
_ = bar();
If-statements may not have else
branch and if the body is a single statement curly braces are not required.
// simple if-statement
if(x > 0)
return 0;
if(y > 0) {
y = 0;
x = 1;
}
else {
y = 1;
x = 2;
}
Functions are defined with the keyword fun
and return values with return
.
fun add(a, b) {
return a+b;
}
Functions may include type definitions.
fun add(a:int, b:int) : int {
return a+b;
}
Functions that do not return any value have type unit
.
fun foo() : unit {
}
When functions declare memory variables that need to be accessed from other functions they need to be linked with the word and
. For example, when making a counter you may need a function to reset it.
fun counter() {
mem x = x + 1;
return x;
}
and reset() {
x = 0;
}
fun test() {
val x = c:counter();
_ = c:reset();
}
You can notice that it is not necessary to declare the memory variable x
in the function reset
.
Functions declared as external, during code generation all calls are replaced to the given function. The types for the function need to be explicitly defined.
external foo(x:int) : int "actual_foo";
In the example above, a call like foo(0)
becomes actual_foo(0)
.
Reading and writing an array is done through the functions get
and set
.
val x = get(array, index);
val _ = set(array, index, value);
The size of arrays can be obtained with the function size
.
val x = size(array);
The following mathematical operations are available for real
numbers.
- abs : absolute
- exp : exponential
- sin : sine
- cos : cosine
- floor : floor
- tanh : hyperbolic tangent
- tan : tangent
- sqrt : square root
- random : real type random number between 0.0 to 1.0
- irandom : integer type random number between 0 to 2^32 (use as irandom() % N to define a range between 0 to N)
The following constants are provided as functions.
- eps() : returns the minimal value that can be represented with fixed-point numbers.
Attributes can be attached to functions to define specific behaviours e.g. custom initialisation or creation of tables.
By default all mem
variables are initialised to zero. In some cases is necessary to initialise the variables to a different value. In order to do that, you can attach the attribute @[init]
to a function.
fun counter() {
mem count = count + 1;
return count;
}
and start() @[init] {
count = 10; // the first value of count will be 10
}
When a function is expensive to compute or when you want to create wave tables you can use the @[table()]
attribute.
fun expensive(x) @[table(size=128, min=0.0, max=1.0)] {
return exp(x * x) * tanh(x) / (x * x + 1);
}
To use the @[table()]
attribute you need to provide the size of the table and the range of the input. The function will be replaced by a new function performing second order interpolations among a number of segments equal to the size of the table.
For example, to create a wave table for the normalised sine function (range 0-1 instead of 0 to 2Pi) we can do it as follows:
fun sine_wave(x) @[table(size=128, min=0.0, max=1.0)] {
return sine(2.0 * 3.1415 * x);
}
Tables can only be created for function taking as input one real
argument and returning one real
value.