Expressions
Expressions make up the body of branches.
Literals
42 # number (i64)
0 # number (i64)
100000 # number (i64)
"hello\n" # string (str)
true # boolean
false # boolean
Numbers are i64. Strings support escape sequences: \n, \t, \r, \\, \".
Variables
Names declared in branch patterns:
counter is {
n i64 -> {
self(n-1) # "n" is available here
}
}
Let Bindings
let x i64 = 42
Creates a local variable within the current branch.
Arithmetic
Binary operators with precedence:
| Precedence | Operators | Description |
|---|---|---|
| 10 (highest) | * / | Multiplication, division |
| 9 | + - | Addition, subtraction |
All operators are left-associative and operate on i64:
n - 1 # subtraction
acc + n # addition
acc1 + acc2 # addition
steps - 1 # subtraction
Arithmetic works in arguments:
self(n-1 acc+n) # two computed arguments
self(steps-1 acc2 acc1+acc2) # three, one with addition
Comparisons
| Precedence | Operators | Description |
|---|---|---|
| 8 | <= >= == < > | Comparison |
0 == n # equality check
1 <= steps # less-or-equal check
Lower precedence than arithmetic: a + b <= c means (a + b) <= c.
When Expressions
Conditional branching based on an expression:
when 0 == n {
true -> { rt_write(1 "done\n" 5) }
false -> { self(n-1) }
}
when 1 <= steps {
true -> { self(steps-1 acc2 acc1+acc2) }
false -> { rt_write(1 ".\n" 2) }
}
Syntax:
when condition {
pattern -> { body }
pattern -> { body }
}
The condition is any expression. Arms match on literal values (numbers, booleans). If no arm matches, execution falls through silently.
Numeric pattern matching is also supported:
when some_value {
0 -> { ... }
1 -> { ... }
2 -> { ... }
}
Wait Expression
The wait expression suspends the current process until a message arrives in its mailbox:
wait {
n i64 -> { rt_write(1 "received\n" 9) }
}
When a message arrives, it is dequeued from the mailbox and the handler body executes with the message value bound to the pattern variables.
If the mailbox is empty, the process is suspended and moved to the scheduler’s wait array. When another process sends a message, the waiting process is awakened.
Multiple messages can be handled by using wait in a loop:
receiver is {
remaining i64 -> {
when 1 <= remaining {
true -> {
wait {
n i64 -> {
rt_write(1 "." 1)
self(remaining-1)
}
}
}
}
}
}
main is {
_ i64.0 -> {
let r pid = receiver(3)
r(1)
r(2)
r(3)
}
}
Message Sending
Calling a pid-typed variable sends a message to that process:
let p pid = worker()
p(42) # send 42 to the worker process
The syntax is identical to calling a machine, but when the callee is a pid variable, it becomes a message send instead of a spawn.
Messages are enqueued in the target process’s mailbox (a ring buffer of 256 slots). If the target is waiting, it is immediately awakened and moved back to the scheduler’s active queue.
Ping-Pong Example
ponger is {
_ i64.0 -> {
wait { partner pid -> { self(partner 3) } }
}
partner pid remaining i64 -> {
when 1 <= remaining {
true -> {
wait { n i64 -> { partner(1) self(partner remaining-1) } }
}
}
}
}
pinger is {
partner pid remaining i64 -> {
when 1 <= remaining {
true -> {
partner(1)
wait { n i64 -> { self(partner remaining-1) } }
}
}
}
}
main is {
_ i64.0 -> {
let po pid = ponger()
let pi pid = pinger(po 3)
po(pi)
}
}
The ponger waits to receive the pinger’s pid, then they exchange messages in a loop.