Architecture
Kalem is one implementation in Rust: the compiler, the bytecode, the VM and the execution model. The same VM runs in three places:
| Target | Built as | Used by |
|---|---|---|
| Server | a native library behind a C interface | Caria's Go server through cgo: the world's authority, deterministic and saved |
| Native game | a static library | the Cocos native app, exposed to JavaScript through SWIG (coming) |
| Browser and editor | WebAssembly | Play's view in Sakin Editor: client scripts |
As in Papyrus, the layers are separate and their borders are hard: the compiler is a tool of its own, the bytecode format is the only contract between it and the VM, and the VM does not know its host. The language's rules are Kalem's; the world's rules are the host's.
The compiler
.kalem → tokens and syntax → syntax tree → typed form (names, types and effects checked) →
Kalem IR → bytecode; a verifier checks the result once more. The parser tolerates errors and is
the same code the language server uses.
It produces:
- bytecode: typed, register-based instructions with a constant pool and 32-bit indexes; the header carries a signature and the format, language and ABI versions. The client's package holds only client scripts, and the compiler enforces that filter.
- a manifest: side, host type, label, category, version, description; properties and their groups; memory; fields, input, events, timers and states; the ABI version and a digest of the source. Editors build their forms from it.
- debug information: instruction → line and column, local names and their scopes.
The crates
| Crate | |
|---|---|
kalem-core | value types and the exact operations (Determinism), random numbers, the standard library's native schema: only what every layer shares |
kalem-bytecode | the format, its header, encoding and verifier |
kalem-parser | tokens, the error-tolerant parser, what names refer to, the manifest |
kalem-types | name, type and effect checking, with tables of every expression's type and of what names and calls refer to |
kalem-compiler | Kalem IR from the checked tree (names are indexes; widening, compound assignment and string parts are explicit) and bytecode |
kalem-vm | executing instructions, open frames, budgets, debugging hooks |
kalem-runtime | the execution model (How scripts run), saving and migration (Saves), debugging and profiling |
kalem-host-api | the one border with the host: the catalog, contract fields, applying effects, calling natives, delivering events, resolving ids |
kalem-capi | the C interface (Embedding Kalem) |
kalem-wasm | the browser build: client scripts and visual effects |
kalem-lsp | the language server (Tools) |
kalem-cli | the kalem command (Tools) |
Nothing of the game goes into a Kalem crate: an economy step, opening a business or a rent is either a host native or a Kalem script. A test fails if a Kalem crate's source holds a word of the game's world.
Frames are data
Call frames and the instruction position are plain data from the start: the debugger shows them, and the next version's tasks go on from the same ground.
Compiling and caching
- Scripts are compiled when Play or Debug starts; while editing, the language server only checks. The cache key is the source's digest, the compiler's version, the native ABI version and the event schemas used.
- A binding's check also depends on the contract's revision: when a contract changes, bindings are checked again.
- A broken new compile does not break the last valid catalog entry or a running Play session; its error shows.
- Bytecode is not kept in version control.