Strada Interpreter
A tree-walking interpreter that executes Strada programs directly from the AST, without generating C code.
Overview
Strada offers two execution paths for your code:
| Compiled (stradac) | Interpreter (strada-interp) | |
|---|---|---|
| How it works | Generates C code, compiles to native binary | Walks the AST directly at runtime |
| Requires C compiler | Yes (gcc) | No |
| Startup time | Slow (compilation step) | Fast (parse and run) |
| Runtime speed | Fastest (native code) | Slower (interpreted) |
| Best for | Production, performance | REPL, scripting, prototyping |
Both paths share the same Lexer and Parser front-end, so valid Strada code works with either.
Running Programs
# Build the interpreter
make interpreter
# Run a Strada program
./strada-interp program.strada
# With library search paths
./strada-interp -L lib program.strada
# Start the interactive REPL
./strada-interp
Interactive REPL
When run without arguments, the interpreter starts an interactive REPL with readline support (line editing, history):
strada> my int $x = 42;
strada> $x * 2
=> 84
strada> func square(int $n) int {
... return $n * $n;
... }
strada> square(7)
=> 49
Variables and function definitions persist across evaluations. Multi-line input is detected automatically when braces, parentheses, or brackets are unbalanced.
See REPL & Scripting for REPL commands, scripting, and profiling features.
Embedded Eval
The interpreter can be embedded inside compiled Strada programs using the Strada::Interpreter library. This enables runtime code evaluation without needing a C compiler at runtime:
use lib "lib";
use Strada::Interpreter;
# Initialize the interpreter engine
Strada::Interpreter::init();
# Evaluate expressions
my scalar $result = Strada::Interpreter::eval_string("1 + 2");
say($result); # 3
# Variables persist across calls
Strada::Interpreter::eval_string("my int \$x = 10;");
my scalar $val = Strada::Interpreter::eval_string("\$x * 5");
say($val); # 50
# Function definitions persist
Strada::Interpreter::eval_string("func double(int \$n) int { return \$n * 2; }");
say(Strada::Interpreter::eval_string("double(21)")); # 42
# Reset all state
Strada::Interpreter::reset();
Eval API
| Function | Description |
|---|---|
Strada::Interpreter::init() |
Initialize the shared interpreter instance (auto-called by eval_string if needed) |
Strada::Interpreter::eval_string($code) |
Evaluate Strada code, return the result (undef for void statements) |
Strada::Interpreter::reset() |
Clear all persisted state (variables, functions, enums, constants) |
Interpreter vs JIT
Strada also provides Strada::JIT, which evaluates code by compiling to a shared library at runtime. The two approaches serve different needs:
| Strada::Interpreter | Strada::JIT | |
|---|---|---|
| Execution | Tree-walking AST eval | Compile to .so, dlopen, execute |
| Requires C compiler | No | Yes (gcc or tcc) |
| __C__ blocks | Skipped | Supported |
| Async/await | No | Yes |
| Per-eval overhead | Low (parse + walk) | Higher (compile + link + load) |
| Execution speed | Slower (interpreted) | Fast (native code) |
| Best for | REPL, scripting, no-compiler envs | Plugin systems, perf-sensitive eval |
Architecture
The interpreter reuses the compiler's Lexer and Parser, then walks the resulting AST to execute programs:
| Component | Source | Purpose |
|---|---|---|
| Lexer | compiler/Lexer.strada |
Tokenizes source (shared with compiler) |
| Parser | compiler/Parser.strada |
Builds AST (shared with compiler) |
| Interpreter | lib/Strada/Interpreter.strada |
Tree-walking AST evaluator |
| Driver | interpreter/Main.strada |
File execution and REPL |
Scoping
The interpreter maintains a chain of environment hashes for lexical scoping. Each scope has a vars hash and a parent pointer. Variable lookup walks from innermost to outermost scope. my creates in the current scope; assignments update the nearest scope containing the variable.
Control Flow
Loop control (next, last, redo) and return are implemented as exception-based signals caught by the enclosing loop or function call handler. Labeled loops match signals by label name.
Supported Features
The interpreter supports the full Strada language:
- Types: int, num, str, scalar, array, hash, undef
- Variables:
my,our,local,const,enum - Operators: arithmetic, string (
.,x), comparison, logical, assignment, ternary - Control flow: if/elsif/else, unless, while, until, for, foreach, do-while, loop labels, statement modifiers
- Functions:
func/fn, closures, variadic (...@args), spread operator - OOP: packages,
extends,has ro|rw,before/after/around,isa,can,AUTOLOAD,bless,use overload - Exceptions: try/catch (typed and catch-all), throw, die
- Regex:
=~,!~,s///,tr////y///, captures, named captures,/emodifier - I/O: say, print, printf, sprintf, core::open, core::close, diamond operator, core::slurp, core::spew
- Modules:
use(source inclusion),import_lib(native .so via dlopen) - BEGIN/END blocks, tie/untie/tied, BigInt/BigFloat, DateTime
- Namespaces:
core::,math::,utf8::
Limitations
__C__blocks are skipped (programs relying on inline C won't work)- Async/await (requires compiled thread pool runtime)
c::namespace functions (c::alloc,c::free, etc.)
Native shared libraries loaded via import_lib still work — they are called through dlopen/dlsym regardless of execution mode.
Building
# Build the interpreter
make interpreter
# The interpreter binary
./strada-interp --help
# Usage
Strada Interpreter v0.2
Usage:
strada-interp Interactive REPL
strada-interp [OPTIONS] <file.strada> Execute file
Options:
-L PATH Add library search path
-h, --help Show this help message