# Architecture

> How Langoost works under the hood — the lexer/parser/compiler/VM pipeline, the 4-byte bytecode format, the stack model, the module cache, and per-request VM isolation.

# 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](/docs/cli)**).

## 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.