Expressions
Expressions compute values. Evaluation proceeds according to the expression form and may raise runtime errors unless protected by a recovery construct.
Precedence
Operators follow this precedence, from highest to lowest:
- postfix call, index, and member selection;
- unary operators;
- exponentiation;
- multiplicative arithmetic, shifts, bitwise and, and bit clear;
- additive arithmetic, bitwise or, and bitwise xor;
- concatenation;
- comparisons;
- logical
&&; - logical
||.
Parentheses override precedence. .. and ** are right-associative; other
binary operators in the table above are left-associative. && and ||
short-circuit and return operand values rather than coerced booleans. Unary
logical negation is !. Unary ^ is bitwise not; binary ^ is bitwise xor.
Assignment forms are statements, not expressions.
```leia run all x := 1 + 2 * 3 // 7 y := (1 + 2) * 3 // 9 z := false || “ok” // “ok” w := nil && fail() // nil; fail is not called
assert(x == 7) assert(y == 9) assert(z == “ok”) assert(w == nil) assert(1 | 2 + 4 == 7) assert(1 « 2 * 3 == 12) assert(“a” .. “b” .. “c” == “abc”)
```leia fail all
x := 1
y := (x = 2)
Calls
A call expression invokes a callable value. Calls may produce zero or more
results. A call used where exactly one expression value is required contributes
its first result, or nil when it produces no results. Expression-list
positions are adjusted by the multi-return rules in Functions.
```leia run all func pair() { return “a”, “b” }
x, y := pair() first := pair()
assert(x == “a”) assert(y == “b”) assert(first == “a”)
The built-in `spread(x)` form preserves multiple results from a call in
argument and table-constructor positions where explicit expansion is required.
```leia run all
func pair() {
return "a", "b"
}
values := [1, spread(pair()), 4]
assert(values[1] == 1)
assert(values[2] == "a")
assert(values[3] == "b")
assert(values[4] == 4)
Calls evaluate the callee expression before arguments, then evaluate arguments left-to-right. Index and member selection evaluate the receiver before the key or field lookup. These forms return the value produced or stored by the target; they do not create a fresh identity by themselves.
Indexing And Member Selection
x[y] indexes a table-like or host-backed value. x.name is member selection
and is equivalent to a string-key field lookup where supported.
```leia run all user := { name: “Ada” } same := user.name == user[“name”] user[“score”] = 10
assert(same) assert(user.score == 10)
`x:name(args)` is a method call. It evaluates `x` once, looks up field `name`
on that receiver, then calls the result with the receiver inserted as the first
argument. Therefore `x:name(a, b)` has the same stable call semantics as
`x.name(x, a, b)`, except the receiver expression is evaluated only once.
```leia run all
counter := {
value: 0,
add: func(self, delta) {
self.value += delta
return self.value
},
}
assert(counter:add(2) == 2)
assert(counter.add(counter, 3) == 5)
assert(counter.value == 5)
Metamethods may affect indexing, assignment, calls, arithmetic, comparison, and length behavior as specified in Tables And Metatables.
Operators
Operator dispatch has three layers:
- apply the primitive operation when the operands are already valid for it;
- apply the operator’s stable primitive coercions, if any;
- otherwise consult the matching metamethod where metatables support that operator, then raise a runtime error if no applicable metamethod exists.
Numeric arithmetic operators accept numbers and strings that tonumber can
parse as numbers. Numeric string coercion is attempted before metamethod lookup
for primitive strings. Invalid numeric strings and unsupported operand types
raise runtime errors unless a matching metamethod applies to a non-primitive
operand. Library functions such as tonumber, tostring, math.tointeger,
and formatting helpers expose related conversions explicitly.
Bitwise operators first apply the numeric conversion used by tonumber, then
convert the numeric result to an integer operand. Float values, including
numeric strings that parse as floats, are truncated toward zero by the current
runtime conversion. Negative shift counts raise runtime errors. Bitwise
operators do not dispatch metamethods.
| Operator | Operands and coercions | Result and errors | Metamethod |
|---|---|---|---|
+ |
Numbers or numeric strings. | Addition. Exact integer operands may produce an integer; mixed or inexact results may be float. Invalid operands error. | __add |
binary - |
Numbers or numeric strings. | Subtraction with the same numeric representation rule as +. Invalid operands error. |
__sub |
* |
Numbers or numeric strings. | Multiplication with the same numeric representation rule as +. Invalid operands error. |
__mul |
/ |
Numbers or numeric strings. | Division produces numeric runtime division semantics; integer inputs may produce a float. Invalid operands error. | __div |
% |
Numbers or numeric strings. | Modulo follows Leia numeric modulo semantics. Invalid operands error. | __mod |
** |
Numbers or numeric strings. | Exponentiation follows the math library’s power semantics. Invalid operands error. | __pow |
unary - |
Number or numeric string. | Numeric negation. Invalid operands error. | __unm |
.. |
Strings and numbers. | Concatenation returns a string for primitive operands. Invalid operands error unless a metamethod applies. | __concat |
# |
String, table-like value, or host-backed value with length support. | Strings return byte length. Tables use ordinary sequence/metatable length behavior. Invalid operands error. | __len; rawlen bypasses it |
== |
Any values. | Primitive values compare by value. Tables, functions, channels, coroutines, and host values compare by identity unless stable equality metamethod dispatch applies. | __eq |
!= |
Any values. | Logical negation of ==, including any stable __eq dispatch. |
__eq |
< |
Compatible numbers or compatible strings. Numeric strings are not coerced for primitive string ordering; use tonumber explicitly. |
Ordered comparison. Incompatible operands error. | __lt |
<= |
Compatible numbers or compatible strings. Numeric strings are not coerced for primitive string ordering. | Ordered comparison. Incompatible operands error. | __le; no fallback to __lt |
> |
Compatible numbers or compatible strings. Numeric strings are not coerced for primitive string ordering. | Equivalent to reversed < after dispatch. Incompatible operands error. |
__lt with reversed operands |
>= |
Compatible numbers or compatible strings. Numeric strings are not coerced for primitive string ordering. | Equivalent to reversed <= after dispatch. Incompatible operands error. |
__le with reversed operands; no fallback to __lt |
&& |
Any values. | Returns the left operand when it is falsy; otherwise evaluates and returns the right operand. | none |
|| |
Any values. | Returns the left operand when it is truthy; otherwise evaluates and returns the right operand. | none |
! |
Any value. | Returns true for nil and false; returns false for every other value. |
none |
& |
Numbers or numeric strings accepted by bitwise conversion. | Bitwise and. Invalid operands error. | none |
| |
Numbers or numeric strings accepted by bitwise conversion. | Bitwise or. Invalid operands error. | none |
binary ^ |
Numbers or numeric strings accepted by bitwise conversion. | Bitwise xor. Invalid operands error. | none |
unary ^ |
Number or numeric string accepted by bitwise conversion. | Bitwise not. Invalid operands error. | none |
&^ |
Numbers or numeric strings accepted by bitwise conversion. | Bit clear. Invalid operands error. | none |
<< |
Number or numeric-string left operand and shift count accepted by bitwise conversion. | Left shift. Negative shift count errors; shift counts of 64 or more produce 0. Invalid operands error. |
none |
>> |
Number or numeric-string left operand and shift count accepted by bitwise conversion. | Logical right shift of the 64-bit operand representation. Negative shift count errors; shift counts of 64 or more produce 0. Invalid operands error. |
none |
<- |
Channel value. | Receive blocks until a value is received, the channel is closed, or the host cancels execution. Invalid operands error. | none |
Raw helpers bypass their corresponding metamethods: for example, rawget,
rawset, rawequal, and rawlen use raw table/string behavior where they are
defined.
Non-executable operator result summary:
1 + 2 // 3
"5" + 3 // 8
"x" + 3 // runtime error
"a" .. 3 // "a3"
(1 << 8) // 256
```leia run all assert(1 + 2 == 3) assert(“5” + 3 == 8) assert(“a” .. 3 == “a3”) assert((1 « 8) == 256) assert((“3.9” & 1) == 1) assert((^”0”) == -1)
```leia fail all
return "x" + 3
```leia fail all return 1 « -1
```leia run all
boxed := setmetatable({value: 4}, {
__add: func(left, right) {
return left.value + right
},
__len: func(_) {
return 9
},
})
assert(boxed + 3 == 7)
assert(#boxed == 9)
assert(rawlen(boxed) == 0)
Literals
List literals, record/map literals, dense array literals, function literals, and tagged dialect forms are expressions. Their specific syntax is listed in grammar.ebnf.
Literal operands are evaluated left-to-right. A literal that constructs an identity-bearing value creates a fresh identity each time the literal is evaluated. This applies to list literals, record/map literals, function literals, and dense arrays. Calls, tagged dialect evaluations, index expressions, and member selections do not imply freshness by themselves: they return the callee or dialect result, and selection returns the stored member or indexed value.
Table fields are evaluated in source order. For stable v1.0 programs, avoid depending on duplicate keys or on subtle interleaving between list-style and keyed fields; Tables And Metatables defines the portable constructor subset. List literals evaluate each element in order and store the results as a new 1-based array table. Dense array literals evaluate each element in order, convert each element according to the dense element type, and raise a runtime error if a value cannot be represented by that element type. Function literals capture their lexical environment by reference. Tagged dialect forms evaluate according to their registered dialect contract.
```leia run all events := [] func mark(name, value) { append(events, name) return value }
rec := { first: mark(“field”, 1), } list := [mark(“a”, 10), mark(“b”, 20)] dense := []i64{mark(“d1”, 3), mark(“d2”, 4)}
assert(events[1] == “field”) assert(events[2] == “a”) assert(events[3] == “b”) assert(events[4] == “d1”) assert(events[5] == “d2”) assert(rec.first == 1) assert(list[1] == 10 && list[2] == 20) assert(dense[1] == 3 && dense[2] == 4) assert({} != {})
child := {} holder := { child: child } assert(holder.child == child) assert(holder[“child”] == child)
## Tagged Dialect Forms
Tagged dialect forms are expressions. The generic syntax, interpolation rules,
bang behavior, registration model, and runtime boundary are specified in
[Tagged Dialects](/leia/spec/dialects.html). The optional standard AI dialect family is
specified in [AI Dialect Syntax](/leia/spec/ai-dialect.html). Other dialects may be provided
by standard-library packages, host applications, or external extensions.
## Evaluation Order
Within an expression list, subexpressions are evaluated left-to-right unless a
specific expression form short-circuits. Implementations may optimize execution
but must preserve observable side effects and error behavior.
```leia run all
events := []
func mark(name) {
append(events, name)
return name
}
result := mark("left") .. mark("right")
assert(result == "leftright")
assert(events[1] == "left")
assert(events[2] == "right")
events = []
holder := { [mark("key")]: mark("value") }
assert(events[1] == "key")
assert(events[2] == "value")
assert(holder.key == "value")
func fail_if_called() {
error("should not be called")
}
short_and := false && fail_if_called()
short_or := true || fail_if_called()
assert(short_and == false)
assert(short_or == true)