Expressions
Expressions make up the body of branches.
Literals
42 # number (i64)
0 # number (i64)
100000 # number (i64)
0x7fffffff # hexadecimal number (i64)
"hello\n" # string literal (str)
b"GET " # byte-string literal (buf)
'A' # char literal (i64 — the byte value, 65)
true # boolean
false # boolean
Numbers are i64 and may be written in decimal or hex (0x...). String
literals (str) support escape sequences: \n, \t, \r, \\, \".
A byte-string literal b"..." has type buf instead of str — useful
where an API wants a runtime byte buffer rather than a static string span.
A char literal 'X' is sugar for the byte’s integer value (there is no
dedicated char type): 'A' is 65, '\n' is 10, '\xff' is 255. It
reads more clearly than a bare number when working with byte data:
when at(b i) == '\n' {
true -> { ... }
_ -> { ... }
}
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.
Lake has no != operator. Test for inequality by matching == in a when
and handling the non-matching case in a _ (or false) arm.
Bitwise and Shift Operators
Integers can also be combined bit by bit:
| Operators | Description |
|---|---|
<< >> | left / right shift (logical) |
& | bitwise AND |
^ | bitwise XOR |
| | bitwise OR |
These treat i64 as a raw bit pattern. They are common in byte-level code
and as a stand-in for the (absent) modulo operator when the divisor is a
power of two — x & (n - 1) computes x % n:
let slot = hash & (cap - 1)
let hi = word >> 8 & 0xff
Parenthesize freely to make precedence explicit, e.g. (a & (b + 1)).
Indexing
A buf supports byte indexing with buf[i], which reads the byte at offset
i as an i64:
let first = b[0]
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 -> { ... }
}
when also matches enum variants, binding each variant’s
payload. Matching over an enum must be exhaustive (cover every variant or
include a _ arm):
when r {
Ok(n) -> { ret n }
Err(_) -> { ret 0 - 1 }
}
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.