BNF Syntax Reference
This document defines the complete grammar of the Maxon programming language in extended BNF notation.
Notation Conventions
Section titled “Notation Conventions”| Notation | Meaning |
|---|---|
'keyword' |
Terminal keyword or symbol (literal text) |
TOKEN |
Terminal token produced by the lexer |
rule |
Non-terminal (grammar rule) |
a b |
Sequence (a followed by b) |
a | b |
Alternation (a or b) |
[ a ] |
Optional (zero or one) |
{ a } |
Repetition (zero or more) |
( a | b ) |
Grouping |
NEWLINE |
Required line break |
LABEL |
Character literal used as block label |
1 — Lexical Grammar
Section titled “1 — Lexical Grammar”1.1 Characters
Section titled “1.1 Characters”letter = 'a'..'z' | 'A'..'Z'digit = '0'..'9'hex_digit = digit | 'a'..'f' | 'A'..'F'bin_digit = '0' | '1'oct_digit = '0'..'7'1.2 Identifiers
Section titled “1.2 Identifiers”IDENTIFIER = ( letter | '_' ) { letter | digit | '_' }1.3 Keywords
Section titled “1.3 Keywords”KEYWORD = 'and' | 'as' | 'async' | 'await' | 'bool' | 'break' | 'continue' | 'countof' | 'cstring' | 'default' | 'else' | 'end' | 'enum' | 'export' | 'extends' | 'extension' | 'fallthrough' | 'false' | 'float' | 'for' | 'from' | 'function' | 'gives' | 'if' | 'ignore' | 'implements' | 'in' | 'int' | 'interface' | 'is' | 'let' | 'match' | 'mod' | 'not' | 'or' | 'otherwise' | 'panic' | 'public' | 'return' | 'returns' | 'self' | 'Self' | 'shl' | 'shr' | 'sizeof' | 'static' | 'then' | 'throw' | 'throws' | 'to' | 'true' | 'try' | 'type' | 'typealias' | 'union' | 'upto' | 'uses' | 'var' | 'where' | 'while' | 'with' | 'xor'Contextual keywords are IDENTIFIER tokens that act as syntax in one position only: module (directly
before a declaration), test (a top-level declaration followed by a TEST_NAME), spawn (followed by a
type’s factory call) and bits (followed by ( on a typealias right-hand side). __line__ and __file__
are identifiers in the reserved __ space, legal only as a parameter default (§3.1).
1.4 Literals
Section titled “1.4 Literals”INTEGER = decimal_int | hex_int | bin_int | oct_intdecimal_int = digit { digit | '_' }hex_int = '0x' hex_digit { hex_digit | '_' }bin_int = '0b' bin_digit { bin_digit | '_' }oct_int = '0o' oct_digit { oct_digit | '_' }
FLOAT = digit { digit } '.' digit { digit } [ ('e' | 'E') ['+' | '-'] digit { digit } ]
STRING = '"' { string_char | escape_seq } '"'STRING_INTERP = '"' { string_char | escape_seq | '{' expression [ ':' format_spec ] '}' } '"'string_char = <any character except '"', '\', '{', '}', or newline>escape_seq = '\n' | '\t' | '\r' | '\0' | '\\' | '\"' | "\'" | '\{' | '\}' | hex_escape | unicode_escapehex_escape = '\x' hex_digit hex_digitunicode_escape = '\u' hex_digit hex_digit hex_digit hex_digitformat_spec = int_format | float_formatint_format = ['0'] [width] [int_type]int_type = 'd' | 'x' | 'X' | 'b' | 'o'float_format = ['0'] [width] [ '.' precision ]width = digit { digit }precision = digit { digit }
(* The two formats share the '[0][width]' prefix; the tail decides. A base letter on a float, and any specifier on a String or bool, has no effect. *)
BYTE_STRING = 'b"' { string_char | byte_escape } '"'byte_escape = '\n' | '\t' | '\r' | '\0' | '\\' | '\"' | "\'" | hex_escape | unicode_escape (* no '\{' or '\}'; every character must encode to one byte (U+0000..U+00FF), else E1004 *)
CHARACTER = "'" ( grapheme_cluster | char_escape ) "'"grapheme_cluster = <any extended grapheme cluster>char_escape = '\n' | '\t' | '\r' | '\0' | '\\' | "\'" | '\"' | hex_escape | unicode_escape
BOOL = 'true' | 'false'1.5 Labels
Section titled “1.5 Labels”Block labels are character literals used as identifiers for block structures.
LABEL = "'" IDENTIFIER "'"TEST_NAME = "'" { any character except "'" } "'"A LABEL names a block and is spelled as an identifier. A TEST_NAME is the same character
literal token, but its content is PROSE — spaces, digits and punctuation are all allowed, and
the lexer validates none of it. It appears only in test_decl, where the trailing end must
repeat it verbatim.
1.6 Operators and Punctuation
Section titled “1.6 Operators and Punctuation”'+' '-' '*' '/''=' '==' '!=' '<' '<=' '>' '>=''(' ')' '{' '}' '[' ']'',' ':' '.'1.7 Comments
Section titled “1.7 Comments”comment = line_comment | block_commentline_comment = '//' { <any character except newline> } NEWLINEblock_comment = '/*' { <any character> } '*/'doc_comment = '///' { <any character except newline> } NEWLINE1.8 Conditional Compilation Directives
Section titled “1.8 Conditional Compilation Directives”hash_if = '#if'hash_else = '#else'hash_endif = '#endif'
conditional_block = '#if' condition NEWLINE { <tokens> } [ '#else' NEWLINE { <tokens> } ] '#endif' NEWLINE
condition = or_expror_expr = and_expr { 'or' and_expr }and_expr = unary_expr { 'and' unary_expr }unary_expr = 'not' unary_expr | atomatom = 'os' '(' IDENTIFIER ')' | 'arch' '(' IDENTIFIER ')' | 'testing' '(' BOOL ')' | 'rcSanitize' '(' BOOL ')' | 'leakReport' '(' BOOL ')' | 'debugstream' '(' BOOL ')' | '(' condition ')'Conditional compilation directives are evaluated at parse time. Conditions support boolean operators not, and, or (precedence: or < and < not), plus parentheses for grouping. Supported os values: Windows, Linux, Macos, Wasi. Supported arch values: x64, arm64, wasm32. An unrecognized os or arch argument is false rather than an error. testing, rcSanitize and leakReport name build flags the compiler does not have, so each is satisfied only by false; debugstream is true in a --debugstream build. Any other predicate is E2064. Nested #if blocks are supported.
1.9 Shebang Line
Section titled “1.9 Shebang Line”shebang_line = '#!' { <any character except newline> } NEWLINEA shebang_line is recognized only when #! are the first two bytes of the file; the lexer skips the line
but keeps its newline, so line numbers are unchanged. #! anywhere else is E1009.
2 — Program Structure
Section titled “2 — Program Structure”program = [ shebang_line ] { top_level_decl }
top_level_decl = function_decl | test_decl | type_decl | enum_decl | union_decl | interface_decl | extension_block | typealias_decl | top_level_var | top_level_let | conditional_block2.1 Visibility
Section titled “2.1 Visibility”visibility_prefix = [ 'export' | 'public' | 'module' ]Any top-level declaration may be preceded by export or public to make it visible
everywhere in the compilation, or by module to make it visible to every
file in the declaring directory subtree (same directory + any subdirectory).
public has exactly the visibility of export and additionally marks the declaration as API
surface, which exempts it from the unused-export diagnostics (E3092/E3093).
At most one modifier may be written — combining two is E2001.
module is a contextual keyword: it is recognised only when followed by a
declaration token; in any other position it lexes as an identifier.
A test_decl takes no visibility_prefix — a test is never referenced by name,
so there is nothing for a visibility modifier to control.
3 — Declarations
Section titled “3 — Declarations”3.0 Test Declaration
Section titled “3.0 Test Declaration”test_decl = 'test' TEST_NAME NEWLINE body 'end' TEST_NAMEA test takes no parameters and no returns: a newline must follow the name, so test 'x'(a int) and test 'x' returns int are parse errors.
test is a contextual keyword, recognised only at declaration position when the
next token is a TEST_NAME; anywhere else it lexes as an identifier. The position
half matters as much as the word: match expression LABEL makes match test 'check'
the same two tokens, and only the declaration position separates them.
Every test implicitly declares throws TestFailure (stdlib/Testing.maxon); the
clause is never written and cannot be. A test_decl is legal only in a file whose
name ends in .test.maxon.
3.1 Function Declaration
Section titled “3.1 Function Declaration”function_decl = visibility_prefix 'function' IDENTIFIER '(' [ param_list ] ')' [ 'returns' type_ref ] [ throws_clause ] NEWLINE body 'end' LABEL
param_list = param { ',' param }param = IDENTIFIER type_ref [ '=' default_value ] (* IDENTIFIER '_' discards the parameter *)
default_value = expression (* evaluated at each call that omits the argument *) | caller_location (* caller-location default, see below *)
caller_location = '__line__' (* line of the call site *) | '__file__' (* calling file, relative to the compile root *)
(* caller_location is legal ONLY as the whole of a parameter's default_value, and it is expanded at the CALL SITE, so each caller supplies its own. Anywhere else -- an ordinary expression, or a field default, which shares the `= expr` spelling but expands at a struct literal rather than a call -- is E2060. *)
throws_clause = 'throws' IDENTIFIER { '.' IDENTIFIER }3.2 Type (Struct) Declaration
Section titled “3.2 Type (Struct) Declaration”type_decl = visibility_prefix 'type' IDENTIFIER [ uses_clause ] [ conformance_clause ] [ where_clause ] NEWLINE { type_member } 'end' LABEL
type_member = field_decl | method_decl | static_field_decl | static_method_decl | typealias_decl
field_decl = visibility_prefix ('var' | 'let') IDENTIFIER ( 'as' type_ref [ '=' expression ] | '=' literal_default ) NEWLINE
(* literal_default is the shorthand form — type is inferred from the literal. Accepts: signed integer, signed float, or bool. For any other default — an enum case, a factory call — the 'as' type_ref form is required. *)
literal_default = [ '-' ] INTEGER | [ '-' ] FLOAT | 'true' | 'false'
method_decl = visibility_prefix 'function' IDENTIFIER '(' [ param_list ] ')' [ 'returns' type_ref ] [ throws_clause ] NEWLINE body 'end' LABEL
static_field_decl = visibility_prefix 'static' ('var' | 'let') IDENTIFIER '=' expression NEWLINE
(* legal only inside a type body; `static` at file scope is a parse error *)
static_method_decl = visibility_prefix 'static' 'function' IDENTIFIER '(' [ param_list ] ')' [ 'returns' type_ref ] [ throws_clause ] NEWLINE body 'end' LABEL3.3 Enum Declaration
Section titled “3.3 Enum Declaration”Enums define named constants with optional raw values. They auto-implement Equatable and Hashable, and support ==/!= comparison. Enums do NOT support associated values (use union for that). The backing type is inferred from the raw values and is never written; the header ends at the optional conformance clause.
enum_decl = visibility_prefix 'enum' IDENTIFIER [ conformance_clause ] NEWLINE { enum_case NEWLINE | method_decl } 'end' LABEL
(* cases and methods may interleave; a `static function` in an enum is refused (E2015) *)
enum_case = IDENTIFIER (* simple case *) | IDENTIFIER '=' raw_value (* raw-value case *)
raw_value = [ '-' ] INTEGER | [ '-' ] FLOAT | STRING | CHARACTER | struct_raw_literal (* struct-backed: TypeName{field: value, ...} *) | IDENTIFIER (* function-backed: top-level function name *)
struct_raw_literal = IDENTIFIER '{' raw_field_init { ',' raw_field_init } '}'
raw_field_init = IDENTIFIER ':' ( [ '-' ] INTEGER | [ '-' ] FLOAT | 'true' | 'false' | IDENTIFIER '.' IDENTIFIER (* enum member reference *) | IDENTIFIER (* constant reference *) | struct_raw_literal )3.4 Union Declaration
Section titled “3.4 Union Declaration”Unions define named cases with optional associated values. They do NOT implement Equatable or Hashable, do not support ==/!= comparison. Use match to inspect union values. Unions support .name, .ordinal, and the static .allCaseNames property (an Array with String of case names). They do not support .allCases directly, but expose a synthesized .unionCases companion enum — U.unionCases is an int-backed enum with one bare case per variant, providing .allCases, .fromRawValue, etc. for symmetric (de)serialization. Unions can also tag each variant with a compile-time struct backing (read via .rawValue, identical shape to struct-backed enums).
union_decl = visibility_prefix 'union' IDENTIFIER [ conformance_clause ] NEWLINE { union_case NEWLINE | method_decl } 'end' LABEL
union_case = IDENTIFIER [ '(' assoc_fields ')' ] [ '=' raw_value ]
assoc_fields = assoc_field { ',' assoc_field }assoc_field = IDENTIFIER type_refA case’s optional = raw_value (§3.3) is its explicit tag (add(dest Id, src Id) = 5) or its backing record (= OpMeta{...}). When struct-backed, every variant must carry an = struct_raw_literal of the same backing struct type. The backing struct is accessible via .rawValue on a union value; associated-value payloads are still accessed via match. A float associated value is not supported yet (E2015), and a static function in a union is refused (E2015).
3.5 Interface Declaration
Section titled “3.5 Interface Declaration”interface_decl = visibility_prefix 'interface' IDENTIFIER { extends_clause | uses_clause } NEWLINE { ( interface_method | typealias_decl ) NEWLINE } 'end' LABEL
extends_clause = 'extends' IDENTIFIER { ',' IDENTIFIER }
interface_method = [ 'static' ] 'function' IDENTIFIER '(' [ param_list ] ')' [ 'returns' type_ref ] [ throws_clause ]3.6 Extension Block
Section titled “3.6 Extension Block”extension_block = 'extension' type_base [ conformance_clause ] [ where_clause ] NEWLINE { method_decl | static_method_decl } 'end' LABEL
type_base = IDENTIFIER | 'int' | 'float' | 'bool'
(* an extension body declares no stored members: a `var` or `let`, static or not, is E2015 *)3.7 Type Alias Declaration
Section titled “3.7 Type Alias Declaration”typealias_decl = visibility_prefix 'typealias' IDENTIFIER '=' typealias_rhs NEWLINE
typealias_rhs = ranged_type | raw_pattern_type | generic_type | tuple_type | function_type
ranged_type = primitive_type '(' range_bound ('to' | 'upto') range_bound ')'
(* `bits` is an ordinary IDENTIFIER, not a keyword: what selects this production is the name followed by '('. Legal widths are 1, 2, 4, 8, 16, 32 and 64 — the widths a slot or a sub-byte packed field can hold; any other is E3148. *)raw_pattern_type = 'bits' '(' INTEGER ')'
primitive_type = 'int' | 'float' (* 'upto' is not accepted on a float range *)
range_bound = [ '-' ] INTEGER | [ '-' ] FLOAT | sized_type_ref '.' ('min' | 'max')
sized_type_ref = 'u8' | 'u16' | 'u32' | 'u64' | 'i8' | 'i16' | 'i32' | 'i64' | 'f32' | 'f64'Range validation constraints:
- Lower bound must be less than upper bound (or less than or equal for
to) - When both bounds use type qualifiers, they must reference the same type (e.g.,
i64.min to i64.max, noti8.min to i32.max) - A type-qualified bound may be paired with a literal (
0 to u32.max,0 to i64.max) - Integer ranges cannot span both negative values and values above
i64.max
generic_type = generic_base 'with' [ INTEGER ] alias_type_argsgeneric_base = IDENTIFIER [ '.' IDENTIFIER ]
alias_type_args = type_arg { ',' type_arg } (* a bare list: typealias right-hand side only *) | '(' type_arg { ',' type_arg } ')'
type_arg = generic_base 'with' [ INTEGER ] type_args (* nested instance: parenthesize several arguments *) | type_ref
(* the optional INTEGER is a fixed element count: `Vector with 3 Coord` *)
tuple_type = '(' type_ref ',' type_ref { ',' type_ref } ')'3.8 Top-Level Variables
Section titled “3.8 Top-Level Variables”top_level_var = visibility_prefix 'var' IDENTIFIER '=' expression NEWLINEtop_level_let = visibility_prefix 'let' IDENTIFIER '=' expression NEWLINE4 — Type System Clauses
Section titled “4 — Type System Clauses”uses_clause = 'uses' IDENTIFIER { ',' IDENTIFIER }
conformance_clause = 'implements' conformance_entry { ',' conformance_entry }
conformance_entry = IDENTIFIER [ 'with' type_args ]
type_args = type_ref | '(' type_ref { ',' type_ref } ')'
where_clause = 'where' constraint { ',' constraint }
constraint = IDENTIFIER 'is' constraint_iface { 'and' constraint_iface }
constraint_iface = IDENTIFIER [ 'with' type_args ] (* may bind the interface's associated types *)
type_ref = 'bool' | 'cstring' | 'Self' | IDENTIFIER { '.' IDENTIFIER } | tuple_type | 'int' | 'float' (* bare only on a typealias right-hand side and in sizeof; E3005 elsewhere *)
function_type = 'function' '(' [ fn_type_param { ',' fn_type_param } ] ')' [ 'returns' type_ref ]fn_type_param = [ IDENTIFIER ] type_ref (* `returns` is optional; without it, the function type returns void. A function type cannot declare `throws`. `function(...)` is legal only as a `typealias` right-hand side (§3.7) — at every other use site (parameter types, return types, struct fields, generic arguments) reference the alias by name. *)5 — Statements
Section titled “5 — Statements”body = statement NEWLINE { statement NEWLINE } (* at least one statement required; empty blocks are E3082 *)
statement = return_stmt | annotated_decl | var_decl | let_decl | discard_stmt | if_stmt | while_stmt | for_stmt | match_stmt | break_stmt | continue_stmt | throw_stmt | panic_stmt | try_stmt | assignment_stmt | tuple_assign_stmt | expression_stmt
expression_stmt = call_expr | await_expr | spawn_expr | try_expr
(* only a call — direct, method, static, qualified, or through a function-typed local — or an await, spawn or try stands alone as a statement; `x + 1` on its own line is a parse error *)5.1 Variable Declarations
Section titled “5.1 Variable Declarations”annotated_decl = '@heap' ( var_decl | let_decl )
var_decl = 'var' IDENTIFIER '=' expression | 'var' '(' IDENTIFIER { ',' IDENTIFIER } ')' '=' expression
let_decl = 'let' IDENTIFIER '=' expression | 'let' '(' IDENTIFIER { ',' IDENTIFIER } ')' '=' expression
discard_stmt = '_' '=' expressionConstraints:
- The expression in
discard_stmtmust be a function call (error E3067) var _ = ...andlet _ = ...are not allowed; use_ = ...instead
5.2 Assignment
Section titled “5.2 Assignment”assignment_stmt = target '=' expression
target = IDENTIFIER | IDENTIFIER '.' IDENTIFIER { '.' IDENTIFIER } | 'self' '.' IDENTIFIER { '.' IDENTIFIER } | IDENTIFIER '.' IDENTIFIER '=' expression (* via .set() *)5.2.1 Tuple Assignment
Section titled “5.2.1 Tuple Assignment”Assigns multiple return values to existing mutable variables in one statement:
tuple_assign_stmt = '(' tuple_assign_target { ',' tuple_assign_target } ')' '=' expression
tuple_assign_target = IDENTIFIER (* must refer to an existing var-declared variable *) | '_' (* discard this element *)Constraints:
- Each
IDENTIFIERmust refer to avar-declared (mutable) variable in scope letvariables are not valid targets (error E2013)- The number of targets must equal the tuple’s element count (error E3005)
5.3 Return
Section titled “5.3 Return”return_stmt = 'return' [ expression ]5.4 If Statement
Section titled “5.4 If Statement”if_stmt = 'if' condition LABEL NEWLINE body 'end' LABEL [ else_clause ] | if_try_stmt
else_clause = 'else' if_stmt | 'else' LABEL NEWLINE body 'end' LABEL | 'else' '(' IDENTIFIER ')' LABEL NEWLINE body 'end' LABEL
condition = expression
if_try_stmt = 'if' 'try' expression LABEL NEWLINE (* only for a call that returns nothing; E3124 otherwise *) body 'end' LABEL [ else_clause ] | 'if' ('let' | 'var') IDENTIFIER '=' 'try' expression LABEL NEWLINE body 'end' LABEL [ else_clause ]5.5 While Loop
Section titled “5.5 While Loop”while_stmt = 'while' expression LABEL NEWLINE body 'end' LABEL5.6 For Loop
Section titled “5.6 For Loop”for_stmt = 'for' loop_var 'in' iterable_expr LABEL NEWLINE body 'end' LABEL
loop_var = IDENTIFIER (* '_' discards the value *) | '(' IDENTIFIER { ',' IDENTIFIER } ')' (* '_' discards individual elements *)
iterable_expr = expression ('to' | 'upto') expression (* range form *) | expression '.' 'withIterator' '(' ')' (* iterator + element tuple form *) | expression (* iterator form *)5.7 Match Statement
Section titled “5.7 Match Statement”match_stmt = 'match' expression LABEL NEWLINE { match_arm NEWLINE } 'end' LABEL
match_arm = match_patterns 'then' match_action | 'default' 'then' match_action | 'default' 'throws' expression (* enum: throws error for unmatched cases *) | 'default' 'panic' '(' ( STRING | STRING_INTERP ) ')' (* terminates with error message *)
(* on an enum or union, `default then` is E2046: only the throws and panic forms *)
match_action = statement [ 'and' 'fallthrough' ] | 'break' [ LABEL ]
match_patterns = match_pattern { 'or' NEWLINE match_pattern } (* one alternative per line; E3147 otherwise *)
match_pattern = literal_pattern | case_pattern | range_pattern
literal_pattern = [ '-' ] INTEGER | [ '-' ] FLOAT | STRING | CHARACTER | BOOL
case_pattern = IDENTIFIER [ '(' binding_list ')' ] (* bare case name; bindings for union associated values *)
binding_list = IDENTIFIER { ',' IDENTIFIER } (* '_' discards individual bindings; all-discard is error E3081 *)
range_pattern = expression 'to' expression (* inclusive both bounds *) | expression 'upto' expression (* exclusive upper bound *) | expression 'to' 'max' (* open upper bound *) | 'min' 'to' expression (* open lower, inclusive upper *) | 'min' 'upto' expression (* open lower, exclusive upper *)An arm’s alternatives are joined by or, and the NEWLINE after each or is REQUIRED: one
alternative per line, the chain continuing after a trailing or, with the last alternative
carrying the arm’s then/gives and its body. Packing two onto one line is E3147. The newline is
admitted only AFTER an or, never before one — a line may not begin with or, which is itself a
legal case name.
There is no case-range pattern. red to blue over an enum or union is E3146: a range covered the
cases whose declaration position fell in its span, so a case added inside that span was absorbed
silently. range_pattern above is for SCALARS only (integers, characters, floats), whose domains
admit no such insertion.
Match arms for enum and union types use bare case names (e.g., red, pending), not
qualified Type.case syntax. Using a qualified name such as Color.red in a match arm is
a compile error (E3075).
Although match_action reduces to statement, the parser rejects the block-opening
statements if, while, for, nested match, and the multi-line block forms of
try (try 'label' ... end 'label' and try call() otherwise 'label' ... end 'label')
in match arms with E2049. Every single-statement try form (bare propagation,
otherwise panic, otherwise ignore, otherwise return/break/continue/throw, and
otherwise <expr>) is allowed.
5.8 Break and Continue
Section titled “5.8 Break and Continue”break_stmt = 'break' [ LABEL ] (* LABEL must NOT name the innermost enclosing loop; that's E2048 *)
continue_stmt = 'continue' [ LABEL ] (* same E2048 rule as `break` *)5.9 Throw
Section titled “5.9 Throw”throw_stmt = 'throw' expression5.10 Panic
Section titled “5.10 Panic”panic_stmt = 'panic' '(' ( STRING | STRING_INTERP ) ')'5.11 Try Statement
Section titled “5.11 Try Statement”try_stmt = 'try' expression 'otherwise' otherwise_clause | 'try' expression (* propagation — only in throwing functions *) | try_block
otherwise_clause = 'ignore' | 'panic' '(' ( STRING | STRING_INTERP ) ')' (* panic on error *) | ( return_stmt | break_stmt | continue_stmt | throw_stmt ) (* single-statement form *) | expression (* default value *) | [ '(' IDENTIFIER ')' ] LABEL NEWLINE body 'end' LABEL
(* try block wraps multiple throwing calls under a shared handler. Inside the body, bare calls to throwing functions do not require the `try` keyword. The block-handler form must contain a match on the error binding; the terminal forms either panic or re-throw, and may bind `(e)` for use in the panic message or throw expression. *)try_block = 'try' LABEL NEWLINE body 'end' LABEL NEWLINE 'otherwise' try_block_otherwise
try_block_otherwise = '(' IDENTIFIER ')' LABEL NEWLINE body (* must contain `match` on binding *) 'end' LABEL | [ '(' IDENTIFIER ')' ] 'panic' '(' ( STRING | STRING_INTERP ) ')' (* terminal panic *) | [ '(' IDENTIFIER ')' ] 'throws' expression (* re-throw fixed error *)6 — Expressions
Section titled “6 — Expressions”6.1 Precedence (lowest to highest)
Section titled “6.1 Precedence (lowest to highest)”| Level | Operators / Forms | Associativity |
|---|---|---|
| 0 | if…else (conditional) |
Right |
| 0.5 | to upto (range value) |
None |
| 1 | or |
Left |
| 2 | xor |
Left |
| 3 | and |
Left |
| 4 | == != < > <= >= is is not |
Left |
| 5 | shl shr |
Left |
| 6 | + - |
Left |
| 7 | * / mod |
Left |
| 8 | as (type cast) |
Left (postfix) |
| 9 | - (unary negation), not, async, await, spawn |
Right (prefix) |
| 10 | . (member access), () (call) |
Left (postfix) |
6.2 Expression Grammar
Section titled “6.2 Expression Grammar”expression = conditional_expr
conditional_expr = range_expr 'if' expression 'else' conditional_expr (* ternary, right-associative *) | range_expr
range_expr = or_expr [ ( 'to' | 'upto' ) or_expr ] (* a Range / OpenRange value outside a for header *)
or_expr = xor_expr { 'or' xor_expr }
xor_expr = and_expr { 'xor' and_expr }
and_expr = comparison { 'and' comparison }
comparison = shift_expr { ( cmp_op shift_expr ) | ( 'is' ['not'] shift_expr ) }cmp_op = '==' | '!=' | '<' | '>' | '<=' | '>='
shift_expr = additive { ('shl' | 'shr') additive }
additive = multiplicative { ('+' | '-') multiplicative }
multiplicative = cast_expr { ('*' | '/' | 'mod') cast_expr }
cast_expr = unary_expr { 'as' type_ref }
unary_expr = '-' postfix_expr (* '--x' is a parse error; write '-(-x)' *) | 'not' unary_expr | async_expr | await_expr | spawn_expr | postfix_expr
postfix_expr = primary { postfix_op }
postfix_op = '.' IDENTIFIER [ '(' [ arg_list ] ')' ] (* method call or field access *) | '.' INTEGER (* tuple positional access: .0, .1, ... *) | '(' [ arg_list ] ')' (* function call *)6.3 Primary Expressions
Section titled “6.3 Primary Expressions”primary = INTEGER | FLOAT | STRING | STRING_INTERP | BYTE_STRING | CHARACTER | 'true' | 'false' | 'self' | array_literal | map_literal | tuple_literal | paren_expr | struct_literal | enum_access | static_access | closure | match_expr | try_expr | from_expr | type_bound_expr | sizeof_expr | countof_expr | IDENTIFIER
array_literal = '[' [ expression { ',' expression } ] ']'
map_literal = '[' expression ':' expression { ',' expression ':' expression } ']'
tuple_literal = '(' expression ',' expression { ',' expression } ')'
paren_expr = '(' expression ')'
struct_literal = IDENTIFIER '{' [ field_init { ',' field_init } ] '}'
field_init = IDENTIFIER ':' expression
(* Semantic rule (E3086): every field of the constructed type must be initialized. A field counts as initialized if (a) its declaration supplies a default via `= expr`, (b) it appears as a field_init in the literal, or (c) the literal is the direct return expression of a `static` function whose return type is the enclosing type, and `self.field = expr` assigns it on every control-flow path reaching the literal. A literal-provided value always wins over a default. *)
(* Ranged-primitive construction is performed via the postfix `as` cast (see `cast_expr`) — `8080 as Port`, `n as PoolA.Idx`. The dedicated `TypeName{value}` syntax was removed in favor of unifying with `as`. *)
enum_access = IDENTIFIER '.' IDENTIFIER [ '(' [ arg_list ] ')' ] | IDENTIFIER '.' 'allCases' (* Array of all cases — enums only *) | IDENTIFIER '.' 'allCaseNames' (* Array with String of case names — enums and unions *) | IDENTIFIER '.' 'unionCases' '.' IDENTIFIER (* synthesized discriminant-enum case — unions only *) | IDENTIFIER '.' 'unionCases' '.' 'allCases' (* Array of all discriminant cases — unions only *) | IDENTIFIER '.' 'unionCases' '.' 'fromRawValue' '(' expression ')' (* lift int discriminant to companion enum *)
static_access = IDENTIFIER '.' IDENTIFIER [ '(' [ arg_list ] ')' ]
type_bound_expr = sized_type_ref '.' ( 'min' | 'max' ) (* e.g., u64.max, i32.min *)
sizeof_expr = 'sizeof' '(' type_ref ')' (* compile-time size in bytes *)
countof_expr = 'countof' '(' type_ref ')' (* element count of a fixed-size container type *)
from_expr = IDENTIFIER 'from' '[' [ expression { ',' expression } ] ']' (* Set / List / Vector / Array alias *) | IDENTIFIER 'from' STRING (* a type implementing InitableFromStringLiteral *) | IDENTIFIER 'from' CHARACTER (* a type implementing InitableFromCharLiteral *)
closure = 'function' '(' [ closure_params ] ')' 'gives' expressionclosure_params = closure_param { ',' closure_param }closure_param = IDENTIFIER [ type_ref ] (* '_' discards the parameter *)6.4 Match Expression
Section titled “6.4 Match Expression”match_expr = 'match' expression LABEL NEWLINE { match_expr_arm NEWLINE } 'end' LABEL
match_expr_arm = match_patterns 'gives' expression | match_patterns 'panic' '(' ( STRING | STRING_INTERP ) ')' (* per-arm panic *) | match_patterns 'throws' expression (* per-arm throw *) | 'default' 'gives' expression | 'default' 'throws' expression (* enum: throws error for unmatched cases *) | 'default' 'panic' '(' ( STRING | STRING_INTERP ) ')' (* terminates with error message *)6.5 Try Expression
Section titled “6.5 Try Expression”try_expr = 'try' expression 'otherwise' otherwise_clause | 'try' expression6.6 Async/Await Expressions
Section titled “6.6 Async/Await Expressions”async_expr = 'async' IDENTIFIER '(' [ arg_list ] ')' (* start a coroutine, returns promise *) | 'async' IDENTIFIER '.' IDENTIFIER '(' [ arg_list ] ')' (* the same, on a static method *)
await_expr = 'await' unary_expr (* wait for promise, returns result *)
try_await = 'try' 'await' expression (* await throwing promise, propagate error *) | 'try' 'await' expression 'otherwise' otherwise_clause (* see 5.11 for all forms *)
cancel_expr = expression '.' 'cancel' '(' ')' (* cancel a coroutine *)Restrictions:
asynccan only be applied to direct function calls (not closures or indirect calls)asynctarget function must yield (contain I/O operations orawaitpoints)- Throwing async functions require
try await(not plainawait)
6.7 Spawn Expressions
Section titled “6.7 Spawn Expressions”spawn_expr = 'spawn' IDENTIFIER '.' IDENTIFIER '(' [ arg_list ] ')' (* start a service, returns <type>.handle *)Restrictions:
spawnis a contextual keyword, not a reserved word: it is an identifier followed by a name, sospawnremains usable as the name of a function, a static, a parameter, a field or a local.- The target must be a static factory of a declared
typethat returns that type; there is no barespawn f()(E3134). - Naming a type in a
spawnmakes it a service program-wide, which synthesizes<type>.requestand<type>.handlebeside it. Notype_declproduction changes. - A message send has no production of its own: it is
call_expr’s method form (§6.8) whose receiver is a<type>.handle. Dispatch is decided by the receiver’s type, so the same spelling is a direct call on a value and a message on a handle. spawnonwasm32-wasi, which has no green threads, is refused with E3104.
6.8 Function and Method Calls
Section titled “6.8 Function and Method Calls”call_expr = IDENTIFIER '(' [ arg_list ] ')' | postfix_expr '.' IDENTIFIER '(' [ arg_list ] ')'
arg_list = arg { ',' arg }
arg = expression (* first argument: positional *) | IDENTIFIER ':' expression (* subsequent arguments: named *)Calling convention: the first argument is positional; all subsequent
arguments must use name: value syntax and may appear in any order.
Arguments with default values may be omitted.
7 — Summary of Block Structure
Section titled “7 — Summary of Block Structure”Every compound statement in Maxon requires a single-quoted label after
the opening keyword and a matching label after end. A label written after end that is not the
block’s own label is E2008 (E2043 for a match); the parser also accepts an end with no label on
every block except a test.
if <cond> 'label' ... end 'label'while <cond> 'label' ... end 'label'for <var> in <iter> 'label' ... end 'label'match <expr> 'label' ... end 'label'try <expr> otherwise 'label' ... end 'label'else 'label' ... end 'label'Type, enum, union, interface, and extension bodies also end with a matching label:
type Point ... end 'Point'enum Color ... end 'Color'union Result ... end 'Result'interface Hashable ... end 'Hashable'extension Iterable ... end 'Iterable'function main() ... end 'main'Appendix A — Complete Token Table
Section titled “Appendix A — Complete Token Table”| Token Type | Lexeme(s) |
|---|---|
Identifier |
[a-zA-Z_][a-zA-Z0-9_]* |
IntegerLiteral |
42, 0xFF, 0b1010, 0o777, 1_000 |
FloatLiteral |
3.14, 1.0e10 |
StringLiteral |
"hello" |
StringInterp |
"hello {name}" |
CharacterLiteral |
'A', '\n' |
Plus |
+ |
Minus |
- |
Star |
* |
Slash |
/ |
Equals |
= |
EqualsEquals |
== |
NotEquals |
!= |
LessThan |
< |
LessEquals |
<= |
GreaterThan |
> |
GreaterEquals |
>= |
LeftParen |
( |
RightParen |
) |
LeftBrace |
{ |
RightBrace |
} |
LeftBracket |
[ |
RightBracket |
] |
Comma |
, |
Colon |
: |
Dot |
. |
Newline |
\n |
DocComment |
/// ... |
HashIf |
#if |
HashElse |
#else |
HashEndif |
#endif |
Eof |
end of input |