Expressions
This chapter defines Vox expressions and operator precedence.
1. Overview
Vox is expression-oriented. Most constructs produce values.
The following constructs are expressions:
- literals;
- name references;
- calls;
- built-in intrinsic forms;
- indexing;
- field access;
- receiver-call sugar;
- unary and binary operator expressions;
ifexpressions;whenexpressions;forexpressions (iterator, condition, and statement-condition forms);- lambda expressions;
- block expressions.
2. Expression Grammar
Expr
::= LambdaExpr
| CoalesceExpr
LambdaExpr
::= LambdaParameters "->" LambdaBody
LambdaParameters
::= Identifier
| "(" LambdaParameterList? ")"
LambdaParameterList
::= LambdaParameter ("," LambdaParameter)* ","?
LambdaParameter
::= Identifier TypeAnnotation?
LambdaBody
::= Expr
| BlockExpr
CoalesceExpr
::= RangeExpr ("?:" CoalesceExpr)?
RangeExpr
::= OrExpr RangeSuffix?
| PrefixRangeExpr
RangeSuffix
::= ".." OrExpr?
| "..=" OrExpr
PrefixRangeExpr
::= ".." OrExpr?
| "..=" OrExpr
OrExpr
::= AndExpr ("||" AndExpr)*
AndExpr
::= EqualityExpr ("&&" EqualityExpr)*
EqualityExpr
::= ComparisonExpr (EqualityOp ComparisonExpr)*
EqualityOp
::= "==" | "!="
ComparisonExpr
::= AdditiveExpr (ComparisonOp AdditiveExpr)*
ComparisonOp
::= "<" | "<=" | ">" | ">="
AdditiveExpr
::= MultiplicativeExpr (AdditiveOp MultiplicativeExpr)*
AdditiveOp
::= "+" | "-"
MultiplicativeExpr
::= UnaryExpr (MultiplicativeOp UnaryExpr)*
MultiplicativeOp
::= "*" | "/" | "%"
UnaryExpr
::= UnaryOp UnaryExpr
| PostfixExpr
UnaryOp
::= "-" | "!"
PostfixExpr
::= PrimaryExpr PostfixOp*
PostfixOp
::= CallSuffix
| WithSuffix
| IndexSuffix
| FieldSuffix
| SafeFieldSuffix
| NonNullSuffix
| ReceiverCallSuffix
3. Primary Expressions
PrimaryExpr
::= Literal
| QualifiedIdentifier
| ParenExpr
| ForExpr
| IfExpr
| WhenExpr
| BlockExpr
| EconExpr
ParenExpr is defined in Chapter 2.
4. Postfix Forms
CallSuffix
::= "(" ArgumentList? ")"
ArgumentList
::= Argument ("," Argument)* ","?
Argument
::= Expr
| Identifier "=" Expr
IndexSuffix
::= "[" Expr "]"
FieldSuffix
::= "." Identifier
SafeFieldSuffix
::= "?." Identifier
NonNullSuffix
::= "!!"
ReceiverCallSuffix
::= ".(" QualifiedIdentifier ")" "(" ArgumentList? ")"
WithSuffix
::= "." "with" "{" UpdateAssignmentList "}"
UpdateAssignmentList
::= UpdateAssignment ("," UpdateAssignment)* ","?
UpdateAssignment
::= UpdatePath UpdateTypeHint? "=" Expr
UpdateTypeHint
::= ":" Type
UpdatePath
::= UpdatePathSegment ("." UpdatePathSegment)*
UpdatePathSegment
::= Identifier
| "#" IntegerLiteral
Rules:
- arguments may be positional or named;
- named arguments use
Identifier "=" Expr; value.with { ... }copiesvalueand applies one or more updates;- update paths may select nested fields and use
#indexfor tuple and list positions; - an optional
: Typehint must match the selected source type and checks the replacement against that type; a?.bperforms nullable-safe field access;a!!asserts thatais non-null;value.(pkg.fun)(x, y)is sugar forpkg.fun(value, x, y).
4.1 Method Calls
When a postfix .identifier is immediately followed by a call suffix (i.e.,
value.fun(args)), the compiler resolves fun as a method if a function
named fun exists whose first parameter type is assignable from the type of
value. The call is then rewritten to fun(value, args).
Resolution order for a.b lookup:
- record field access — if
ais a record type and has a fieldb; - method resolution — if a function
bexists whose first parameter matches the type ofa; - qualified name resolution — if
a.bis a valid qualified name (e.g., an imported package member).
For external libraries, struct types expose trait methods as methods when the
struct implements the corresponding trait. These are resolved through the
package manifest’s trait_impls during method resolution.
For any receiver type, a method name may have only one applicable definition. An inherent or built-in method, a visible function whose first parameter is that receiver type, and a trait method implemented by that receiver therefore cannot overlap. Vox rejects the conflicting declaration or import as a method guard violation before resolving calls. Defining two functions with the same name and indistinguishable parameter types is also a compile-time error.
5. if Expressions
IfExpr
::= "if" "(" Expr ")" BlockExpr
("else" "if" "(" Expr ")" BlockExpr)*
("else" BlockExpr)?
Rules:
ifis an expression as well as a statement: if it appears at the head of a statement it is parsed as a statement. To useifas an expression in that position, wrap it in parentheses:(if (cond) { a } else { b }).- each branch produces a value;
- the overall type is the common type of the branch results.
6. when Expressions
when is used for type-based dispatch.
WhenExpr
::= "when" "(" Expr ")" "{" TypeWhenArm+ ElseArm? "}"
TypeWhenArm
::= "is" Type Binding? "->" (InlineExpr ";" | BlockExpr)
Binding
::= "as" Identifier
ElseArm
::= "else" "->" Expr ";"
InlineExpr
::= Expr
Rules:
- each
isarm tests the subject against a type; as Identifierbinds the refined subject value inside that arm;whendoes not support range matching or general pattern matching;- an inline arm ends with
;; - a block arm does not use
;after its closing}; elseis optional;- at the head of a statement position,
whenis parsed as aBlockStatementand does not require a trailing;. To usewhenas a trailing expression, wrap it in parentheses.
7. Block Expressions
BlockExpr
::= "{" BlockItem* TrailingExpr? "}"
TrailingExpr
::= Expr
A block evaluates to:
- the value of its trailing expression, if present; or
- the unit value
(), otherwise.
{} is also a valid unit literal. It is equivalent to ().
8. Range Expressions
Range expressions use standard half-open and closed forms.
The range forms are:
RangeExpr
::= OrExpr ".." OrExpr
| OrExpr ".."
| ".." OrExpr
| ".."
| OrExpr "..=" OrExpr
| "..=" OrExpr
Range meanings:
start..end: inclusive lower bound, exclusive upper bound;start..: inclusive lower bound with no upper bound;..end: exclusive upper bound with no lower bound;..: unbounded range;start..=end: inclusive lower bound, inclusive upper bound;..=end: inclusive upper bound with no lower bound.
9. Nullability Operators
?., ?:, and !! have the following semantics:
a?.bevaluates tonullwhenaisnull, otherwise it evaluates toa.b;a ?: bevaluates toawhenais non-null, otherwise tob;a!!evaluates toawhenais non-null and fails at runtime whenaisnull.
10. Precedence and Associativity
From highest precedence to lowest, Vox expressions are parsed in this order:
- postfix forms: calls, indexing, field access, safe field access,
!!, and receiver-call sugar; - unary
-and!; - multiplicative
*,/,%; - additive
+,-; - comparison
<,<=,>,>=; - equality
==,!=; - logical
&&; - logical
||; - ranges
..,..=; - null coalescing
?:; - lambda
->.
Associativity rules:
- postfix operators associate left to right;
- multiplicative and additive operators associate left to right;
- comparison and equality operators associate left to right;
&&and||associate left to right;?:associates right to left;- function types and lambdas associate right to left.