Skip to main content

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.

The Origin interpreter (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

from interpreter import Interpreter

interp = Interpreter()
The constructor initializes four instance dictionaries that accumulate state across a full generate() pass:
AttributeTypePurpose
variable_typesdict[str, str]Maps variable names to their declared type strings ("int", "float", "str", "bool") for type-mismatch checking
CONST_VARSdict[str, str]Records names declared with const; reassignment raises RuntimeError at codegen time
importslistReserved for tracking imported module names
classesdictReserved for tracking defined class names
original_importsdict[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.
A single 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:
py_source = interp.generate(ast)   # ast is a ProgramNode
exec(py_source, runtime_globals)
Line tracking injection: Every node that carries a .line attribute causes generate() to prepend:
globals()['_origin_runtime_line'] = N
This statement is injected into the emitted Python source before the node’s own code. Because it writes into 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 typeEmitted Python pattern
ExecNodeWrites node.code to a temp file and invokes runner.py via subprocess.run
PyNodenode.code verbatim (raw Python pass-through)
AssignNodename = generate(value) (raises RuntimeError if name is in CONST_VARS; raises TypeError on type mismatch)
ConstAssignNodeRecords name in CONST_VARS, emits name = generate(value)
CompoundAssignNodename 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
IfNodeif cond:\n body\nelif ...\nelse ...
WhileNodewhile cond:\n body
ForNodefor var in generate(iterable):\n body
TryNodetry:\n body\nexcept Exception:\n ...
FuncNodedef name(params):\n body
ClassNodeclass Name:\n def __init__(self, fields=None,...):\n self.field = field\n body
CallNodegenerate(callee)(arg1, arg2, ...)
AttributeNodegenerate(obj).attr
AttributeAssignNodegenerate(obj).attr = generate(value)
PrintNodeprint(generate(expr))
NumberNodestr(node.value)
StringNoderepr(node.value)
BoolNode"True" or "False"
NoneNode"None"
VarNodenode.name
ListNode[e1, e2, ...]
TupleNode(e1, e2, ...)
DictNode{k1: v1, k2: v2, ...}
IndexNodegenerate(collection)[generate(index)]
IndexAssignNodegenerate(collection)[generate(index)] = generate(value)
ParallelNodeimport 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
ImportNodeimport name (or inline-expands known .or libraries)
ImportAsNodeimport name as alias
ImportFromNodefrom lib import name
ReturnNodereturn generate(value)
BreakNode"break"
ContinueNode"continue"
PassNode"pass"
HardwarePrimitiveNode_execute_{namespace}_{method}(args)
RangeNoderange(generate(start), generate(end))
LenNodelen(generate(value))
SqrtNodemath.sqrt(generate(value))
RandNumNoderandom.randint(generate(start), generate(end))
CastNodecast_type(generate(value)) — e.g. int(x)
InputNodeinput(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.
If _generate_core receives an unrecognized node type it raises:
RuntimeError(f"Unknown node type: {type(node)}")

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:
# Origin source:  print x + 42
# Emitted Python:
(str(x) + str(42)) if isinstance(x, str) or isinstance(42, str) else (x + 42)
This avoids the 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:
# Origin:  class Point(x, y) { ... }
# Emitted Python:
class Point:
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y
    # ... body methods ...

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 spawns N threads targeting it.
  • Per-statement parallelism (parallel { stmt1; stmt2; ... }): emits one _parallel_stmt_i() function per statement and starts one thread per function.
Both modes end with 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):
  1. node.type attribute (set on NumberNode, StringNode, BoolNode, CastNode)
  2. self.variable_types[node.name] for VarNode
  3. Recursive inference for BinOpNode: returns "float" if either child resolves to "float", otherwise returns the left child’s type
Returns 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:
inner = "x = 1\nprint(x)"
print(interp.indent_block(inner))
# "    x = 1\n    print(x)"
If 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():
runtime_globals = {
    "random":               random,                # stdlib random module
    "math":                 math,                  # stdlib math module
    "__name__":             "__main__",
    "_execute_set_pin":     _execute_set_pin,       # GPIO / simulation helper
    "_execute_i2c_read":    _execute_i2c_read,      # I2C read helper
    "_execute_i2c_write":   _execute_i2c_write,     # I2C write helper
    "_origin_runtime_line": 0,                      # updated by line markers
}
All names that the emitted Python code can reference — 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 in interpreter.py and injected into the runtime globals:
def _execute_set_pin(pin, state):
    # Uses RPi.GPIO if available; prints "[SIM] Pin N set to S" otherwise
    ...

def _execute_i2c_read(addr, reg, size=1):
    # Uses smbus2 if available; returns 0 otherwise
    ...

def _execute_i2c_write(addr, reg, data):
    # Uses smbus2 if available; silently no-ops otherwise
    ...
These functions gracefully degrade on non-Raspberry Pi hardware by catching 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.

Build docs developers (and LLMs) love