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 stack
  • OP_POP — Pop the top stack value
  • OP_CALL_FUNCTION — Call a function
  • OP_JUMP — Unconditional jump
  • OP_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 instruction
  • OP_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 — Tokenization
  • src/parser.rs — AST generation
  • src/compiler.rs — Bytecode generation
  • src/vm.rs — Runtime execution
  • src/repl.rs — Interactive REPL
  • src/modules.rs — Multi-file import system

Explore the kria-lang repository for the full implementation.