The Hades interpreter is the final and most dynamic stage of the pipeline. It receives theDocumentation 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.
ProgramNode that the parser produced and evaluates it by walking every node in the tree, maintaining all runtime state inside a chain of Scope objects. Values are plain Python objects — there are no separate Hades value wrappers for primitives.
Public API
InstantiateInterpreter (which creates a fresh root scope) and call evaluate() with any AST node:
evaluate(node) dispatches via NODE_HANDLERS (see below) and returns the Python value that the node produces. Statements that have no meaningful value (declarations, function definitions, loops) return None.
Dispatch: NODE_HANDLERS
Rather than a large isinstance chain, the interpreter uses a dictionary keyed on AST node types populated at __init__ time:
evaluate() performs a single dictionary lookup on type(node). If the type has no handler an InterpreterError is raised immediately.
NODE_HANDLERS is an instance dictionary (not a class variable) because each entry is a bound method. This is set up in __init__ after self.scope exists.Scope and Variable Lifetime
Runtime state lives in a chain ofScope objects defined in modules/scope.py. Each scope holds a variables dictionary and an optional parent reference.
Block scope
_eval_block() saves self.scope, replaces it with Scope(previous_scope), evaluates all statements, and restores the old scope in a finally block — so block-local variables are discarded even on error.Function scope
_call_function() creates a new Scope parented to the function’s closure scope (not the call-site scope), giving Hades lexical scoping. Parameters are declared into this new scope with type-hint validation before the body runs.Built-in Functions
TheBUILTINS dictionary maps name strings to internal handler methods. Builtins are checked before the scope chain, so they cannot be shadowed by user-defined functions.
| Name | Behaviour |
|---|---|
print | Calls _to_string() on each argument and prints them concatenated (no separator). Returns None. |
type | Takes exactly 1 argument; returns its Hades type name as a str ('int', 'float', 'bool', 'str'). |
len | Takes exactly 1 argument; accepts str or list; returns Python len() as an int. |
HadesFunction Runtime Object
When the interpreter evaluates a FuncNode it creates a HadesFunction dataclass and stores it in the current scope under the function’s name:
InterpreterError.
Return Values: ReturnSignal
Hades uses a Python exception to propagate return values up the call stack cleanly, avoiding the need to thread a “did we return?” flag through every recursive evaluate() call:
_eval_return() raises ReturnSignal(value). _call_function() catches it with except ReturnSignal as r and extracts r.value. If a non-nothing function reaches the end of its body without raising ReturnSignal, InterpreterError is raised to report the missing return.
Type Checking at Runtime
_check_type_hint(value, type_hint_token, name_token) validates that a runtime value matches the declared type hint. It is called:
- After evaluating a variable declaration’s initialiser
- For each argument passed to a function call
- For the return value of a non-
nothingfunction
TT hint to expected Python type:
| Token type | Python type checked |
|---|---|
INT_TYPE_HINT | int |
FLOAT_TYPE_HINT | float |
BOOL_TYPE_HINT | bool (checked with an explicit isinstance(value, bool) guard, because Python’s bool is a subclass of int — this ensures plain integers are not accepted where bool is declared) |
STR_TYPE_HINT | str |
LIST_TYPE_HINT | list |
NOTHING_TYPE_HINT | NoneType |
Utility Methods
_truthy(value)
Converts any runtime value to a boolean for condition checks in
if, while, and for statements:None→Falsebool→ itselfint/float→value != 0str→value != ''- anything else →
bool(value)
_to_string(value)
Converts runtime values to their Hades string representation for
print():bool→'TRUE'or'FALSE'None→'nothing'float→ strips trailing zeros (e.g.3.50→'3.5',3.0→'3')- anything else →
str(value)
Error Handling
All interpreter errors are raised asInterpreterError, which carries the offending Token for source-location reporting:
at line, column.
Undefined variable suggestions
Undefined variable suggestions
When The candidate list is taken from
_eval_id() fails to find a name in any enclosing scope, it uses Python’s difflib.get_close_matches to suggest the closest known variable name (cutoff 0.6, one suggestion):self.scope.variables — the innermost scope only, not the full chain.Division by zero
Division by zero
Both
_eval_binop and _assign_to_id/_assign_to_index catch Python’s ZeroDivisionError and re-raise it as InterpreterError('Division by zero', token).Type errors in operators
Type errors in operators
Python
TypeError exceptions from binary or unary operator lambdas are caught and re-raised as InterpreterError with a human-readable message that names the Hades types of both operands.