Expressions

Expressions compute values.

Syntax

expression    = with_expr | let_expr | if_expr | for_expr | loop_expr | lambda | binary_expr .
primary       = literal | identifier | "self" | "Self"
              | "(" expression ")" | list_literal | map_literal | struct_literal .
list_literal  = "[" [ expression { "," expression } ] "]" .
map_literal   = "{" [ map_entry { "," map_entry } ] "}" .
map_entry     = expression ":" expression .
struct_literal = type_path "{" [ field_init { "," field_init } ] "}" .
field_init    = identifier [ ":" expression ] .

Postfix Expressions

postfix_expr  = primary { postfix_op } .
postfix_op    = "." identifier [ call_args ]
              | "[" expression "]"
              | call_args
              | "?" .
call_args     = "(" [ call_arg { "," call_arg } ] ")" .
call_arg      = named_arg | positional_arg .
named_arg     = identifier ":" expression .
positional_arg = expression .

Field and Method Access

point.x
list.len()

Index Access

list[0]
list[# - 1]    // # is length within brackets
map["key"]     // returns Option<V>

Lists/strings panic on out-of-bounds; maps return Option.

Function Call

add(a: 1, b: 2)
fetch_user(id: 1)
print(msg: "hello")
assert_eq(actual: result, expected: 10)

Named arguments are required for direct function and method calls. Argument names must match parameter names. Argument order is irrelevant.

Positional arguments are permitted in two cases:

  1. Type conversion functions (int, float, str, byte):
int(3.14)      // OK: type conversion
float(42)      // OK: type conversion
str(value)     // OK: type conversion
  1. Calls through function variables (parameter names are unknowable):
let f = (x: int) -> x + 1
f(5)           // OK: calling through variable

let apply = (fn: (int) -> int, val: int) -> fn(val)
apply(fn: inc, val: 10)  // outer call: named required
                         // inner fn(val): positional OK

It is a compile-time error to use positional arguments in direct function or method calls.

Error Propagation

value?         // returns Err early if Err

Unary Expressions

unary_expr = [ "!" | "-" | "~" ] postfix_expr .

! logical not, - negation, ~ bitwise not.

Binary Expressions

binary_expr   = or_expr .
or_expr       = and_expr { "||" and_expr } .
and_expr      = bit_or_expr { "&&" bit_or_expr } .
bit_or_expr   = bit_xor_expr { "|" bit_xor_expr } .
bit_xor_expr  = bit_and_expr { "^" bit_and_expr } .
bit_and_expr  = eq_expr { "&" eq_expr } .
eq_expr       = cmp_expr { ( "==" | "!=" ) cmp_expr } .
cmp_expr      = range_expr { ( "<" | ">" | "<=" | ">=" ) range_expr } .
range_expr    = shift_expr [ ( ".." | "..=" ) shift_expr ] .
shift_expr    = add_expr { ( "<<" | ">>" ) add_expr } .
add_expr      = mul_expr { ( "+" | "-" ) mul_expr } .
mul_expr      = unary_expr { ( "*" | "/" | "%" | "div" ) unary_expr } .
OperatorOperation
+ - * /Arithmetic
%Modulo
divFloor division
== != < > <= >=Comparison
&& ||Logical (short-circuit)
& | ^ ~Bitwise
<< >>Shift
.. ..=Range
??Coalesce (None/Err → default)

Operator Type Constraints

Binary operators require operands of matching types. No implicit conversions.

Arithmetic (+ - * /):

LeftRightResult
intintint
floatfloatfloat

String concatenation (+):

LeftRightResult
strstrstr

Integer-only (% div << >> & | ^):

LeftRightResult
intintint

Comparison (< > <= >=):

Operands must be the same type implementing Comparable. Returns bool.

Equality (== !=):

Operands must be the same type implementing Eq. Returns bool.

Mixed-type operations are compile errors:

1 + 2.0          // error: mismatched types int and float
float(1) + 2.0   // OK: 3.0
1 + int(2.0)     // OK: 3

Numeric Behavior

Integer overflow: Wraps using two’s complement. No panic.

let max: int = 9223372036854775807
max + 1  // -9223372036854775808 (wraps)

Integer division by zero: Panics.

5 / 0    // panic: division by zero
5 % 0    // panic: modulo by zero

Float division by zero: Returns infinity or NaN per IEEE 754.

1.0 / 0.0    // Inf
-1.0 / 0.0   // -Inf
0.0 / 0.0    // NaN

Float NaN propagation: Any operation involving NaN produces NaN.

NaN + 1.0    // NaN
NaN == NaN   // false (IEEE 754)
NaN != NaN   // true

Float comparison: Exact bit comparison. No epsilon tolerance.

0.1 + 0.2 == 0.3  // false (floating-point representation)

## With Expression

```ebnf
with_expr = "with" identifier "=" expression "in" expression .
with Http = MockHttp { ... } in fetch("/data")

Let Binding

let_expr = "let" [ "mut" ] pattern [ ":" type ] "=" expression .
let x = 5
let mut counter = 0
let { x, y } = point

Conditional

if_expr = "if" expression "then" expression
          { "else" "if" expression "then" expression }
          [ "else" expression ] .
if x > 0 then "positive" else "non-positive"

Condition must be bool. When else is present, branches must have compatible types.

When else is omitted, the expression has type void. The then branch must also have type void (or type Never, which is compatible with any type).

// Valid: then-branch is void
if debug then print(msg: "debug mode")

// Valid: then-branch is Never (panic returns Never)
if !valid then panic(msg: "invalid state")

// Invalid: then-branch has non-void type without else
if x > 0 then "positive"  // error: non-void then-branch requires else

For Expression

for_expr   = "for" identifier "in" expression [ "if" expression ] ( "do" | "yield" ) expression .
for item in items do print(item)
for n in numbers if n > 0 yield n * n

do returns void; yield collects results.

Loop Expression

loop_expr = "loop" "(" expression ")" .
loop(
    match(ch.receive(),
        Some(v) -> process(v),
        None -> break,
    ),
)

break exits; continue skips to next iteration.

Lambda

lambda        = simple_lambda | typed_lambda .
simple_lambda = lambda_params "->" expression .
typed_lambda  = "(" [ typed_param { "," typed_param } ] ")" "->" type "=" expression .
lambda_params = identifier | "(" [ identifier { "," identifier } ] ")" .
x -> x * 2
(x, y) -> x + y
(x: int) -> int = x * 2

Evaluation

Expressions are evaluated left-to-right. This order is guaranteed and observable.

Operand Evaluation

Binary operators evaluate the left operand before the right:

left() + right()  // left() called first, then right()

Argument Evaluation

Function arguments are evaluated left-to-right as written, before the call:

foo(a: first(), b: second(), c: third())
// Order: first(), second(), third(), then foo()

Named arguments evaluate in written order, not parameter order:

foo(c: third(), a: first(), b: second())
// Order: third(), first(), second(), then foo()

Compound Expressions

Postfix operations evaluate left-to-right:

list[index()].method(arg())
// Order: list, index(), method lookup, arg(), method call

List and Map Literals

Elements evaluate left-to-right:

[first(), second(), third()]
{"a": first(), "b": second()}

Assignment

The right side evaluates before assignment:

x = compute()  // compute() evaluated, then assigned to x

Short-Circuit Evaluation

Logical and coalesce operators may skip the right operand:

OperatorSkips right when
&&Left is false
||Left is true
??Left is Some/Ok
false && expensive()  // expensive() not called
true \|\| expensive()  // expensive() not called
Some(x) ?? expensive()  // expensive() not called

Conditional Branches

Only the taken branch is evaluated:

if condition then
    only_if_true()
else
    only_if_false()

See Control Flow for details on conditionals and loops.