/ Specification
Playground Docs Performance GitHub
Chapter 4

Expressions

Lattice is an expression-based language: most constructs produce values. This chapter defines all expression forms, organized by category.

Literal Expressions

literal  ::= int_literal | float_literal | string_literal
         |   "true" | "false" | "nil"

Literal expressions evaluate to the value they denote. See Lexical Structure: Literals for syntax details.

Identifier Expressions

An identifier expression evaluates to the value bound to that name in the current scope. If the name is not in scope, a runtime error is raised.

Binary Operators

binary_expr  ::= expression binop expression
binop        ::= "+" | "-" | "*" | "/" | "%"
             |   "==" | "!=" | "<" | ">" | "<=" | ">="
             |   "&&" | "||"
             |   "&" | "|" | "^" | "<<" | ">>"
             |   "??"

Binary operators follow standard precedence rules (see Appendices: Operator Precedence). The + operator is overloaded for string concatenation and array concatenation. The ?? (nil coalesce) operator returns the left operand if it is not nil, otherwise the right operand.

let sum = 2 + 3              // 5
let greeting = "hi" + " world" // "hi world"
let val = nil ?? 42           // 42

Unary Operators

unary_expr  ::= unaryop expression
unaryop     ::= "-" | "!" | "~"

The prefix - negates a numeric value, ! performs logical negation, and ~ performs bitwise NOT.

Function Calls

call_expr  ::= expression "(" [ arg_list ] ")"
arg_list   ::= expression { "," expression }

A call expression invokes a callable value (function or closure) with a list of arguments. Arguments are evaluated left-to-right and passed by value. If the function defines default parameters, trailing arguments may be omitted.

Method Calls

method_call  ::= expression "." identifier "(" [ arg_list ] ")"
             |   expression "?." identifier "(" [ arg_list ] ")"

Method calls invoke type-specific methods on a value. The ?. variant performs optional chaining: if the receiver is nil, the entire expression evaluates to nil without calling the method.

Field Access

field_access  ::= expression "." identifier
              |   expression "?." identifier

Field access reads a named field from a struct value. The optional chaining variant ?. returns nil if the receiver is nil.

Index Access

index_expr  ::= expression "[" expression "]"
            |   expression "?[" expression "]"

Index access reads an element from an array, tuple, or map by index or key. The ?[ variant returns nil instead of raising an error for out-of-bounds or missing keys.

Array Literals

array_expr  ::= "[" [ expression { "," expression } ] "]"

Array literals create a new array value. Elements may include spread expressions.

let a = [1, 2, 3]
let b = [0, ...a, 4]  // [0, 1, 2, 3, 4]

Struct Literals

struct_lit  ::= identifier "{" field_init { "," field_init } "}"
field_init  ::= identifier ":" expression

Struct literals create an instance of a declared struct by specifying values for each field.

Tuple Literals

tuple_expr  ::= "(" expression "," expression { "," expression } ")"

Tuple literals create a fixed-size collection. A trailing comma is required to distinguish a single-element tuple from a parenthesized expression: (42,) is a tuple, (42) is just the integer 42.

Range Expressions

range_expr  ::= expression ".." expression

Range expressions create a half-open range [start, end). Both operands must be integers.

Closure Expressions

closure        ::= "|" [ closure_params ] "|" ( expression | block )
closure_params ::= closure_param { "," closure_param }
closure_param  ::= ["..."] identifier ["=" expression]

Closures capture variables from their enclosing scope. Unlike function declarations, closure parameters do not support type annotations — they are bare identifiers. Parameters may have default values, and the last parameter may be variadic.

let add = |a, b| { a + b }
let greet = |name, greeting = "hello"| {
    "${greeting}, ${name}"
}
let sum_all = |...nums| {
    nums.reduce(|a, b| { a + b }, 0)
}

Spread Expressions

spread_expr  ::= "..." expression

The spread operator expands an array's elements inline within an array literal or function call.

If Expressions

if_expr  ::= "if" expression block [ "else" ( block | if_expr ) ]

if expressions evaluate a boolean condition and execute the corresponding block. When used as an expression, both branches must be present and the last expression in each block becomes the value of the if expression.

let abs = if x >= 0 { x } else { -x }

Match Expressions

match_expr  ::= "match" expression "{" { match_arm } "}"
match_arm   ::= [phase_qual] pattern ["if" expression] "=>" ( expression | block )

Match expressions evaluate a subject and compare it against a series of patterns. The first matching arm's body is evaluated. See Pattern Matching for full details.

For Loops

for_expr  ::= "for" identifier "in" expression block

For loops iterate over ranges, arrays, or maps. When iterating over an array, the loop variable receives a deep clone of each element. When iterating over a map, the loop variable is bound to each key as a string. The loop variable is bound fresh for each iteration.

While Loops

while_expr  ::= "while" expression block

While loops repeatedly execute the body as long as the condition evaluates to true.

Loop

loop_expr  ::= "loop" block

An infinite loop that runs until a break statement is encountered.

Block Expressions

block  ::= "{" { statement } [ expression ] "}"

A block is a sequence of statements optionally followed by a trailing expression. The value of a block is the value of its trailing expression, or Unit if there is none.

Forge Expressions

forge_expr  ::= "forge" block

A forge block provides a controlled mutation context. Variables declared inside the forge block can be mutated freely, and the last expression in the block is the forge's result. Forge blocks are typically used to build complex immutable values through controlled mutation.

fix config = forge {
    flux m = Map::new()
    m.set("host", "localhost")
    m.set("port", 8080)
    freeze(m)
}

Phase Transition Expressions

freeze_expr       ::= "freeze" "(" expression ")" [where_clause] [except_clause]
thaw_expr         ::= "thaw" "(" expression ")"
clone_expr        ::= "clone" "(" expression ")"
anneal_expr       ::= "anneal" "(" expression ")" closure
sublimate_expr    ::= "sublimate" "(" expression ")"
crystallize_expr  ::= "crystallize" "(" expression ")" block

These expressions transition values between phases. See Phase System.

Scope and Spawn Expressions

scope_expr  ::= "scope" block
spawn_expr  ::= "spawn" block

scope creates a structured concurrency block. All spawn tasks within a scope must complete before the scope exits. See Concurrency.

Select Expressions

select_expr  ::= "select" "{" { select_arm } "}"
select_arm   ::= identifier "from" expression "=>" block
             |   "default" "=>" block
             |   "timeout" "(" expression ")" "=>" block

Select expressions multiplex across multiple channels. See Concurrency: Select.

Try/Catch Expressions

try_catch  ::= "try" block "catch" identifier block

Try/catch catches runtime errors. See Error Handling.

Try-Propagate (?) Expressions

try_propagate  ::= expression "?"

The postfix ? operator unwraps a result map. If the value contains an "err" key, the error is immediately returned from the enclosing function. Otherwise, the "ok" value is extracted.

Interpolated String Expressions

Double-quoted strings containing ${...} sequences are desugared into a series of string concatenations at parse time. The inner expressions are evaluated and converted to strings.

Enum Variant Expressions

enum_variant  ::= identifier "::" identifier [ "(" arg_list ")" ]

Creates an enum variant value, optionally with a payload.

print_expr  ::= "print" "(" expression { "," expression } ")"

print is a built-in expression that outputs its arguments to standard output separated by spaces, followed by a newline. It returns Unit.