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.

No C compiler needed at runtime The interpreter compiles Strada code to bytecode and runs it on the VM. No gcc or tcc required (except for __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:

Limitations

Not supported in the interpreter
  • 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($_); }'