Architecture
Langoost is a bytecode-compiled interpreter written in Go. Each script runs through a small, predictable pipeline.
Source text
↓ Lexer (token/lexer.go)
Token stream
↓ Parser (parser/parser.go)
AST
↓ Compiler (compiler/compiler.go)
Bytecode chunk
↓ VM (vm/vm.go)
Result
Bytecode
Instructions are fixed-width 4-byte (uint32) words. The top byte is the
opcode; the lower 3 bytes are the operand (up to ~16 million). A constant width
means no alignment work and a simple decode loop.
[ opcode: 8 bits | operand: 24 bits ]
Inspect the compiled output of any script with disasm (see the
CLI reference).
Stack model
Local variables live directly on the value stack at fixed slot indices
(frame.base + slot), so LOAD_LOCAL is a single array read with no
indirection. Temporaries — intermediate expression results — sit above the
locals and are cleaned up as each expression completes.
Module cache
Modules are compiled once and cached, keyed by absolute path and file
modification time. If a file hasn’t changed, importing it is just a map lookup.
The cache is guarded by a sync.RWMutex, so concurrent requests read it without
blocking one another.
Per-request isolation
In server mode, every HTTP request gets its own VM instance with a private stack
and output buffer. The only shared state is the (read-mostly) module cache.
Scripts handling different requests therefore never share mutable state — which
is exactly what makes them safe to run concurrently. When sharing is needed,
it’s explicit, via the runtime.core key-value store.
This design is the core trade-off behind Langoost: keep one warm process with shared compiled code, but isolate execution per request so there’s no cross-request interference and no cold start.