Architecture
Compiler pipeline, bytecode format, and virtual machine internals.
Compilation Pipeline
Kria programs are compiled through multiple stages before execution:
Source (.krx) → Lexer → Parser → Compiler → Bytecode → VM
Each stage transforms the input into an intermediate representation. The final bytecode is executed on a stack-based virtual machine.
Lexer (Tokenization)
The lexer reads the source code and produces a stream of tokens.
Input: Source code (text)
Output: Token stream
Responsibilities:
- Recognize keywords, identifiers, literals, and operators
- Handle string literals (single and multi-line)
- Process comments (single-line and block)
- Track line and column numbers for error reporting
The lexer is a straightforward tokenizer with no lookahead complexity.
Parser (Syntax Analysis)
The parser reads tokens and produces an Abstract Syntax Tree (AST).
Input: Token stream
Output: AST (Abstract Syntax Tree)
Method: Recursive descent parser
The parser builds a tree representing the program structure:
- Expressions (literals, operators, function calls)
- Statements (assignments, control flow)
- Functions and blocks
Compiler (Code Generation)
The compiler traverses the AST and generates bytecode.
Input: AST
Output: Bytecode with constant pool
Key features:
- Single-pass compilation
- Constant pool for literals and symbols
- Function bytecode with metadata
- Module resolution and import flattening
Bytecode & Constants
Kria uses a flat bytecode format with a constant pool.
Constant Pool
All literals and symbols are stored in a constant pool, indexed by bytecode instructions:
- String literals
- Numbers
- Function names
- Variable names
- Object property keys
Instruction Format
Instructions are variable-length, encoding an opcode and optional operands:
OP_PUSH_CONST— Push a constant onto the stackOP_POP— Pop the top stack valueOP_CALL_FUNCTION— Call a functionOP_JUMP— Unconditional jumpOP_LOOP_INC_LESS— Combined increment-and-loop (optimization)
See the source code for the full opcode list.
Virtual Machine
The VM is a stack-based interpreter that executes bytecode.
Stack-Based Design
All operations work on a value stack:
- Push operands onto the stack
- Execute operations (pop operands, push results)
- Return values stay on stack
Call Frames
Each function call has a frame:
- Local variables (in a hash map)
- Return address (bytecode offset)
- Parent frame (for closure capture)
Global Scope
Global variables are stored in a separate hash map. Functions can access and modify globals.
Optimizations
Kria includes several runtime optimizations:
Combined Loop Instructions
Counted loops compile to specialized opcodes that combine multiple operations:
OP_LOOP_INC_LESS— Increment and compare in one instructionOP_LOOP_INC_LTE— Increment and compare (less-than-or-equal)
This reduces dispatch overhead for tight loops.
Global Variable Optimizations
Global arithmetic operations are optimized to avoid function call overhead.
Constant Folding
Compile-time constant expressions are pre-computed.
Source Code
The Kria interpreter is written in Rust. Source files include:
src/lexer.rs— Tokenizationsrc/parser.rs— AST generationsrc/compiler.rs— Bytecode generationsrc/vm.rs— Runtime executionsrc/repl.rs— Interactive REPLsrc/modules.rs— Multi-file import system
Explore the kria-lang repository for the full implementation.