Source Processing
Matchbox turns source text into a checked AST using the lexer, token helpers, parser, symbol tables, and AST node definitions.
Grammar
The expression grammar is organized by precedence. In 0.2, primary expressions are grouped expressions, integer literals, names, and function calls. Prefix expressions are parsed before exponentiation, exponentiation is parsed left to right, and then factor, term, shift, comparison, equality, bitwise, and logical levels are considered. Comparison, equality, and logical operators are recognized but rejected as unsupported when parsed.
Expression -> BooleanOr
BooleanOr -> BooleanAnd { "||" BooleanAnd }
BooleanAnd -> BitwiseOr { "&&" BitwiseOr }
BitwiseOr -> BitwiseXor { "|" BitwiseXor }
BitwiseXor -> BitwiseAnd { "^" BitwiseAnd }
BitwiseAnd -> Equality { "&" Equality }
Equality -> Comparison { ("==" | "!=") Comparison }
Comparison -> Shift { (">" | ">=" | "<" | "<=" | "<=>") Shift }
Shift -> Term { ("<<" | ">>") Term }
Term -> Factor { ("+" | "-") Factor }
Factor -> Exponent { ("*" | "/" | "//" | "%") Exponent }
Exponent -> Prefix { "**" Prefix }
Prefix -> ("!" | "~" | "-") Prefix | Primary
Primary -> Integer | Name | Call | "(" Expression ")"
Lexer
The lexer tracks a start position and current position, including line and column, and returns one token at a time. It skips whitespace, single-line comments beginning with #, and multi-line comments delimited with ## and ##. Unterminated strings, characters, and multi-line comments are reported by the lexer before parsing continues.
Numeric tokens include decimal, binary, octal, hexadecimal, and floating spellings. In 0.2, the parser accepts integer, binary, octal, and hexadecimal literals as integer expressions. Underscores may separate digits when followed by another valid digit.
Tokens
The token enum is broader than the implemented language. It includes current tokens such as var, func, return, int, arithmetic operators, bitwise operators, assignment operators, parentheses, braces, commas, identifiers, and integer literal forms. It also includes reserved or future-facing tokens such as uint, float, class, struct, trait, async, comparison operators, logical operators, ranges, safe access, and compound bitwise assignment.
Token helper functions define the supported subset. For example, isTypeToken accepts only int, isPrefixToken accepts !, -, and ~, and isAssignmentToken accepts simple arithmetic assignment forms. This is why the lexer can recognize more syntax than the parser and compiler currently allow.
The lexer emits token kinds from these groups:
| Group | Token examples |
|---|---|
| Declarations and definitions | var, func, const, type, use, extern |
| Control and flow keywords | if, else, for, while, match, return, yield, break, continue |
| Type keywords | int, uint, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float, double, char, bool |
| Object-model keywords reserved by the lexer | class, struct, trait, protocol, enum, extension, self, construct, destruct |
| Boolean and none literals | true, false, none |
| Arithmetic operators | +, -, *, /, //, %, ** |
| Assignment operators | =, +=, -=, *=, /=, //=, %=, **= |
| Comparison and logical operators | ==, !=, >, >=, <, <=, <=>, &&, ||, ! |
| Bitwise and shift operators | &, |, ^, ~, <<, >>, &=, |=, ^=, <<=, >>= |
| Other reserved operators | ??, ??=, ?:, =~, !~, =>, ?., |>, <|, .., ..^, ..., $, @ |
| Delimiters and separators | (, ), [, ], {, }, ;, ,, ., :, ? |
| Literals and names | Integer, float, binary, hexadecimal, octal, character, string, identifier |
| End and unknown | End-of-file and unknown-character tokens |
Each token stores its type, the source characters that formed it, its length, and the line and column where it began. The lexer does not copy token text; it points back into the source buffer. Later stages use the token span to print diagnostics and to convert literal text into runtime values.
Parser
The parser consumes tokens recursively and builds AST nodes. It reports unexpected tokens, unsupported operators, invalid operand combinations, undefined names, uninitialized reads, redefinitions, invalid function arguments, and invalid variable types. Most diagnostics include the source line and column of the token that caused the error.
Statements are separated by newlines or semicolons. When two statements appear on the same line, the parser expects a semicolon unless the previous token closed a block. Blocks parse until }, and the top level parses until end of file.
Top-Level Statements
At the top level, Matchbox accepts variable definitions, assignments, function definitions, function-calls, and expression statements. Built-in function calls are represented internally as service requests. Top-level variable definitions are stored as globals. Function definitions produce function objects in the module constant pool. Expression statements are compiled and then popped, so their values are not preserved unless assigned or passed to a function or built-in service.
Symbol Table
Each scope owns a symbol table and a local count. The top-level scope contains globals and functions. Function definitions create child scopes for parameters and local variables. Local variable declarations increment the scope's local count, which later becomes part of the function frame size.
Name lookup first checks the current scope, then the top-level scope for variables and calls that are allowed by the parser. A local declaration can shadow a top-level declaration, but redeclaring a name in the same scope is an error.
AST
The AST represents the checked program. Node kinds include assignment, binary expression, compound block, function call, function definition, integer literal, parameter, prefix expression, return statement, service request, variable reference, variable definition, and a none sentinel used for omitted expressions. AST nodes carry enough resolved information for the compiler to emit bytecode without performing name lookup again.
Variable and function nodes store scope and symbol links. Binary nodes store the operator, left expression, right expression, and resulting type. Function definitions store parameters, return type, body, and scope. This makes the AST both a syntax tree and a compact semantic model for the current compiler.