Strada Interpreter
A bytecode VM that compiles Strada's AST to bytecode and executes it via a computed goto dispatch loop. 4-5x faster than Perl 5.38.
__C__ blocks, which are JIT-compiled and cached). A tree-walking backend is available as a fallback via --tree-walk.
Overview
Strada offers three execution paths for your code:
| Compiled (stradac) | VM (strada-interp) | Tree-Walk (--tree-walk) | |
|---|---|---|---|
| How it works | Generates C code, compiles to native binary | AST → bytecode → VM dispatch | Walks the AST directly at runtime |
| Requires C compiler | Yes (gcc) | No (except __C__ blocks) |
No |
| Startup time | Slow (compilation step) | Fast (parse + bytecode compile) | Fast (parse and run) |
| Runtime speed | Fastest (native code) | Fast (4-5x faster than Perl 5.38) | Slower (interpreted) |
| Best for | Production, performance | Scripts, file execution | REPL, legacy fallback |
All paths share the same Lexer and Parser front-end, so valid Strada code works with any of them.
Running Programs
# Build the interpreter
make interpreter
# Run a Strada program (uses bytecode VM by default)
./strada-interp program.strada
# Use tree-walking backend instead
./strada-interp --tree-walk program.strada
# With library search paths
./strada-interp -L lib program.strada
# Start the interactive REPL (uses tree-walk)
./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 | Bytecode VM (default) or tree-walk | Compile to .so, dlopen, execute |
| Requires C compiler | No | Yes (gcc or tcc) |
| __C__ blocks | JIT-compiled and cached | Supported |
| Async/await | No | Yes |
| Per-eval overhead | Low (parse + bytecode compile) | Higher (compile + link + load) |
| Execution speed | Fast (bytecode VM) | Fastest (native code) |
| Best for | REPL, scripting, no-compiler envs | Plugin systems, perf-sensitive eval |
Architecture
The interpreter reuses the compiler's Lexer and Parser front-end. By default, the AST is compiled to bytecode and executed on a register-based VM. A tree-walking backend is available as a fallback via --tree-walk.
| Component | Source | Purpose |
|---|---|---|
| Lexer | compiler/Lexer.strada |
Tokenizes source (shared with compiler) |
| Parser | compiler/Parser.strada |
Builds AST (shared with compiler) |
| VM Compiler | interpreter/vm_compiler.c |
Compiles AST to bytecode |
| Bytecode VM | interpreter/vm.c |
Register-based VM with JIT for __C__ blocks |
| Tree-walker | lib/Strada/Interpreter.strada |
Fallback AST evaluator (--tree-walk) |
| 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
- 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
strada-interp -e 'code' Execute code string
Options:
-e CODE Execute code (wraps in main() if needed)
-L PATH Add library search path
-h, --help Show this help message
One-Liner Execution
Use -e to evaluate Strada code directly from the command line:
# Simple expression
strada-interp -e 'say("hello world")'
# Multiple -e flags are concatenated
strada-interp -e 'my $x = 6;' -e 'say($x * 7)'
# No func main() boilerplate needed
strada-interp -e 'my @nums = (1, 2, 3); foreach (@nums) { say($_); }'