The Hades parser sits between the lexer and the interpreter. It receives the flatDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ToberlerOhn/hades/llms.txt
Use this file to discover all available pages before exploring further.
list[Token] that the lexer produced and transforms it into a tree of typed AST node objects rooted at a ProgramNode. No evaluation happens here — the parser only answers the question “what program structure do these tokens describe?”
Public API
Pass any token list (including theEOF sentinel) to Parser, then call parse():
parse() raises ParserError (a subclass of SyntaxError) on any structural problem. The error object carries line and column from the offending token so the runtime can pinpoint the mistake.Parsing Strategy
The parser uses recursive descent for statement and control-flow grammar, combined with Pratt-style precedence climbing for binary expressions. This combination keeps the code straightforward while correctly handling operator associativity and precedence without building a separate grammar table.Recursive Descent
Each statement type (
if, while, for, func, …) has a dedicated parse_* method. The top-level parse_statement() dispatcher checks the current token type and routes to the right handler, falling back to expression parsing.Precedence Climbing
parse_binary(min_precedence) calls itself recursively, increasing min_precedence by 1 at each right-hand recursion. This produces left-associative trees for operators at the same level and correct nesting across levels.Operator Precedence
All binary operators and their precedence levels are declared in theBINARY_PRECEDENCE class dictionary. Higher numbers bind more tightly.
| Level | Operators | Notes |
|---|---|---|
| 1 | || ^^ | lowest — logical or / xor |
| 2 | && | logical and |
| 3 | == != === !== | equality; ===/!== also check type |
| 4 | < > <= >= in | relational + membership |
| 5 | + - | additive |
| 6 | * / % | multiplicative — highest |
Expression Parsing Layers
Expressions are parsed through a fixed descent chain. Each layer only consumes what it owns and defers the rest downward:parse_expression → parse_assignment
The entry point. Calls
parse_binary() first to get the left-hand side, then checks whether the current token is an assignment operator (=, +=, -=, *=, /=, %=, &&=, ||=, ^^=). If so, the left node must be an IdNode or IndexNode or ParserError is raised.parse_binary (precedence climbing)
Calls
parse_unary() for the initial left operand, then enters a loop: if the current token appears in BINARY_PRECEDENCE with a level ≥ min_precedence, it advances, recurses with min_precedence + 1, and wraps both sides in a BinOpNode.parse_unary
Handles prefix operators
!, -, and +. Each recursively calls parse_unary() again for the operand, producing a UnaryOpNode. Falls through to parse_postfix() when the current token is not a unary op.parse_postfix
Wraps
parse_primary() in a loop that keeps consuming: ++ / -- postfix operators → PostfixOpNode; ( → function call via parse_call(); -> → index access via _parse_index().parse_primary
Dispatches on the current token type using the
PRIMARY_HANDLERS dictionary:| Token type | Handler | AST node |
|---|---|---|
INT, FLOAT | _parse_number | NumberNode |
BOOL | _parse_bool | BoolNode |
STR | _parse_string | StringNode |
NOTHING_TYPE_HINT | _parse_nothing | NothingNode |
ID | _parse_identifier | IdNode |
LBRACKET | _parse_list_literal | ListNode |
LPAREN | _parse_grouping | (inner expression) |
Statement Types and AST Nodes
Variable declaration — VarDeclNode
Variable declaration — VarDeclNode
Syntax: Produces
name: type = value;The parser recognises a declaration when the current token is TT.ID and the next token (peeked) is TT.COLON. It consumes the name, colon, type-hint token, =, and then the initialiser expression.VarDeclNode(name_token, type_hint, value_node). A nothing-typed variable has value = None and skips the = entirely.Assignment — AssignNode
Assignment — AssignNode
Syntax: Produces
target = value; or target += value; etc.Parsed inside parse_assignment() after the left-hand side has already been parsed as an expression. The target must resolve to an IdNode (plain variable) or an IndexNode (list element).AssignNode(target, assign_token, value_node).Function definition — FuncNode
Function definition — FuncNode
Syntax:
func name(param: type, ...) => return_type { body }parse_func_def() collects parameters as (name_token, type_hint_token) pairs. The return type must be a valid type-hint token. Produces FuncNode(name, parameters, return_type, body).Return statement — ReturnNode
Return statement — ReturnNode
Syntax: Produces
=> expr; or => nothing;The => token doubles as the return keyword. parse_return() checks whether the next token is NOTHING_TYPE_HINT (value-less return) or an expression.ReturnNode(keyword_token, value_node_or_None).If / else if / else — IfNode
If / else if / else — IfNode
Syntax:
parse_if() builds a list of (condition, body) pairs for the initial if and every else if branch, then stores the optional bare else body separately.Produces IfNode(branches: list[tuple], else_body: list | None).While / do-while — WhileNode
While / do-while — WhileNode
Syntax:
parse_while() checks whether the leading keyword is do (do-while) or while, and sets is_do on the resulting node accordingly.Produces WhileNode(is_do: bool, condition, body).C-style for loop — ForNode
C-style for loop — ForNode
Syntax:
for (init; test; update) { body }parse_for() parses each of the three clauses as full statements separated by explicit semicolons, then the body block.Produces ForNode(init, testExpression, updateStatement, body).For-in loop — ForInNode
For-in loop — ForInNode
Syntax: Detected inside
for (elem: type; elem in iterable) { body }parse_for() by peeking: if the pattern ID COLON TYPE_HINT SEMICOLON is seen, parse_forin() is called instead. The loop variable name must match on both sides of the semicolon; a mismatch raises ParserError.Produces ForInNode(iterator: VarDeclNode, iterable, body).Function call — CallNode
Function call — CallNode
Syntax:
name(arg, arg, ...)parse_call() is triggered inside parse_postfix() when an IdNode is immediately followed by (. Only identifiers are callable — attempting to call a non-IdNode expression raises ParserError.Produces CallNode(callee_token, args: list).Index access — IndexNode
Index access — IndexNode
Syntax: Produces
list->indexThe -> (right-arrow) token serves as the indexing operator. _parse_index() is triggered inside parse_postfix() and parses the index as a primary expression.IndexNode(callee, index, token).Blocks and Semicolons
_parse_block()
Every construct that uses { ... } delegates body parsing to _parse_block():
Parse statements in a loop
Calls
parse_statement() followed by _consume_statement_terminator() repeatedly until a } or EOF is seen. An unexpected EOF here raises ParserError for an unterminated block.Semicolon rules (_consume_statement_terminator)
Semicolons are required after most statements but the parser applies three special cases to avoid demanding them in places where they would be redundant:
| Situation | Behaviour |
|---|---|
Current token is } or EOF | Semicolon is optional — we are at the end of a block or file |
Previous token was } | Semicolon is optional — the statement just ended a block |
| Anything else | Semicolon is required; expect(TT.SEMICOLON) enforces it |