The Origin interpreter (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/boblio-max/origin/llms.txt
Use this file to discover all available pages before exploring further.
interpreter.py) is the third stage of the compilation pipeline. Rather than evaluating the AST directly, it acts as a transpiler: it walks every node in the tree and emits a corresponding Python source string. The resulting string is then passed to Python’s built-in exec() by runner.py. This design means that Origin programs run at Python’s native speed for all arithmetic, string, and object operations, and can call any Python built-in or imported library without any bridging layer.
Interpreter() — Constructor
generate() pass:
| Attribute | Type | Purpose |
|---|---|---|
variable_types | dict[str, str] | Maps variable names to their declared type strings ("int", "float", "str", "bool") for type-mismatch checking |
CONST_VARS | dict[str, str] | Records names declared with const; reassignment raises RuntimeError at codegen time |
imports | list | Reserved for tracking imported module names |
classes | dict | Reserved for tracking defined class names |
original_imports | dict[str, str] | Maps known Origin library names to their .or file paths; pre-seeded with {"calc": "/lib/calc.or"}. When ImportNode.name matches a key, the .or library is inlined at codegen time instead of emitting a Python import statement. |
Interpreter instance is intended to be used for one complete program AST. Create a fresh instance for each generate() call to avoid state leakage between programs.
generate(node) -> str
The main public entry point. Accepts any AST node and returns a Python source string. For ProgramNode and BlockNode it joins child results with newlines. For all other nodes it prepends a line-tracking marker before delegating to _generate_core:
.line attribute causes generate() to prepend:
globals(), the value survives across exec()-ed function bodies, allowing runner.py to retrieve the last-executed Origin source line from runtime_globals["_origin_runtime_line"] when an exception occurs.
_generate_core(node) -> str
The actual dispatch method. It uses isinstance checks in sequence to match the node type and return the appropriate Python code string. The table below lists all handled node types and their emitted patterns:
| Node type | Emitted Python pattern |
|---|---|
ExecNode | Writes node.code to a temp file and invokes runner.py via subprocess.run |
PyNode | node.code verbatim (raw Python pass-through) |
AssignNode | name = generate(value) (raises RuntimeError if name is in CONST_VARS; raises TypeError on type mismatch) |
ConstAssignNode | Records name in CONST_VARS, emits name = generate(value) |
CompoundAssignNode | name op generate(value) — e.g. x += 1 |
BinOpNode (+) | Smart concatenation (see below) |
BinOpNode (other) | (generate(left) op generate(right)) |
UnaryOpNode | (op generate(node)) |
LogicOpNode | (generate(left) py_op generate(right)) — && → and, || → or |
IfNode | if cond:\n body\nelif ...\nelse ... |
WhileNode | while cond:\n body |
ForNode | for var in generate(iterable):\n body |
TryNode | try:\n body\nexcept Exception:\n ... |
FuncNode | def name(params):\n body |
ClassNode | class Name:\n def __init__(self, fields=None,...):\n self.field = field\n body |
CallNode | generate(callee)(arg1, arg2, ...) |
AttributeNode | generate(obj).attr |
AttributeAssignNode | generate(obj).attr = generate(value) |
PrintNode | print(generate(expr)) |
NumberNode | str(node.value) |
StringNode | repr(node.value) |
BoolNode | "True" or "False" |
NoneNode | "None" |
VarNode | node.name |
ListNode | [e1, e2, ...] |
TupleNode | (e1, e2, ...) |
DictNode | {k1: v1, k2: v2, ...} |
IndexNode | generate(collection)[generate(index)] |
IndexAssignNode | generate(collection)[generate(index)] = generate(value) |
ParallelNode | import threading + thread creation per statement or block (see below) |
SetNode (pin) | _execute_set_pin(num, params) |
SetNode (servo angle) | ServoKit initialization + _kit.servo[num].angle = params |
ImportNode | import name (or inline-expands known .or libraries) |
ImportAsNode | import name as alias |
ImportFromNode | from lib import name |
ReturnNode | return generate(value) |
BreakNode | "break" |
ContinueNode | "continue" |
PassNode | "pass" |
HardwarePrimitiveNode | _execute_{namespace}_{method}(args) |
RangeNode | range(generate(start), generate(end)) |
LenNode | len(generate(value)) |
SqrtNode | math.sqrt(generate(value)) |
RandNumNode | random.randint(generate(start), generate(end)) |
CastNode | cast_type(generate(value)) — e.g. int(x) |
InputNode | input(generate(prompt)) |
YieldNode and OpenNode are defined in classes.py and produced by the parser, but are not yet handled by _generate_core. Passing either node to generate() will reach the fallback and raise RuntimeError(f"Unknown node type: {type(node)}"). Support for these nodes is planned for a future release._generate_core receives an unrecognized node type it raises:
Key Code Generation Patterns
Smart String Concatenation
The+ operator in Origin works across mixed types. _generate_core emits a conditional expression so that if either operand is a str, both sides are coerced:
TypeError: can only concatenate str (not "int") to str that Python would raise for a bare +.
Class Generation
ClassNode fields become optional __init__ parameters (defaulting to None) so that a class can be instantiated with partial arguments:
Parallel Block Generation
ParallelNode has two modes depending on whether node.threads > 0:
- Fixed thread count (
parallel(N) { body }): emits a single_parallel_block()function wrapping the whole body and spawnsNthreads targeting it. - Per-statement parallelism (
parallel { stmt1; stmt2; ... }): emits one_parallel_stmt_i()function per statement and starts one thread per function.
for t in _threads: t.join() to synchronize before the next statement.
PyNode Pass-Through
PyNode emits node.code verbatim, allowing arbitrary Python to be embedded in Origin source using the py { ... } block syntax. No escaping or transformation is applied.
get_type(node) -> str | None
A type-inference helper used during AssignNode generation to detect mismatches at codegen time. It checks (in order):
node.typeattribute (set onNumberNode,StringNode,BoolNode,CastNode)self.variable_types[node.name]forVarNode- Recursive inference for
BinOpNode: returns"float"if either child resolves to"float", otherwise returns the left child’s type
None when the type cannot be determined statically.
indent_block(code, indent=4) -> str
A helper that adds indent spaces to the beginning of every non-blank line in code. Used for generating if, while, for, def, class, and try bodies:
code is empty or None, indent_block returns " " * indent + "pass" so that syntactically empty blocks still produce valid Python.
Runtime Globals
runner.py builds the following dictionary and passes it as the globals argument to exec():
math.sqrt, random.randint, _execute_set_pin, range (a Python built-in, always available) — are accessible through this dictionary. Any additional Python modules or objects you want to expose to Origin programs can be inserted here before calling exec().
Hardware Runtime Helpers
Three helper functions are defined ininterpreter.py and injected into the runtime globals:
ImportError and either printing a simulation message or returning a safe zero value, so Origin programs that use set pin, i2c.read, or i2c.write can be developed and tested on any Python environment.