ZymVM API
Self-hosting and nested virtual machines — create isolated VM instances, compile source dynamically, and call functions across VM boundaries.
Overview
The ZymVM API enables self-hosting by allowing Zym scripts to create and manage independent VM instances. Each nested VM runs in complete isolation with its own heap and stack, garbage collector, and full native library access. Values are automatically reconstructed (deep copied) when passed between VMs.
null during value reconstruction.
Creating VMs
Creates a new isolated virtual machine instance. Each VM has its own memory space, includes all native functions, and starts unloaded (no bytecode).
var vm = ZymVM()
Compiling Source
ZymVM can compile Zym source code to bytecode directly. Compilation methods return a Buffer containing the bytecode, which can be saved to disk, passed to load(), or handed to another VM.
Compiles a .zym source file to bytecode. Handles preprocessing and module resolution automatically using the file’s directory as the base path.
path(string) — path to a.zymsource file
Returns: A Buffer containing compiled bytecode. Raises a runtime error on failure.
var vm = ZymVM() var bytecode = vm.compileFile("scripts/plugin.zym") // Save bytecode to disk fileWriteBuffer("plugin.zbc", bytecode) // Or load it into the same VM vm.load(bytecode)
Compiles a source code string to bytecode. No module resolution is performed.
source(string) — Zym source code as a string
Returns: A Buffer containing compiled bytecode. Raises a runtime error on failure.
var vm = ZymVM() var src = "func add(a, b) { return a + b }" var bytecode = vm.compileSource(src) vm.load(bytecode) if (vm.call("add", 3, 4)) { print(vm.getCallResult()) // 7 }
Loading Bytecode
Loads compiled bytecode from a Buffer into the VM and executes global initialization.
buffer(Buffer) — a Buffer containing compiled.zbcbytecode
Returns: true if loaded successfully, false if loading failed.
var bytecode = fileReadBuffer("program.zbc") if (vm.load(bytecode)) { print("Bytecode loaded successfully") } else { print("Failed to load bytecode") }
Compiles a .zym source file and loads it into the VM in one step. Equivalent to compileFile(path) followed by load().
path(string) — path to a.zymsource file
Returns: true if compilation and loading succeeded, false otherwise.
var vm = ZymVM() if (vm.loadFile("plugins/enemy.zym")) { print("Plugin loaded") if (vm.hasFunction("init", 0)) { vm.call("init") } }
Compiles a source string and loads it into the VM in one step. Equivalent to compileSource(source) followed by load().
source(string) — Zym source code as a string
Returns: true if compilation and loading succeeded, false otherwise.
var vm = ZymVM() var code = "func greet(name) { return \"Hello, \" + name + \"!\" }" if (vm.loadSource(code)) { if (vm.call("greet", "World")) { print(vm.getCallResult()) // Hello, World! } }
Function Queries
Checks if a function with the given name and parameter count exists in the loaded bytecode.
name(string) — function name (case-sensitive)arity(number) — expected number of parameters (0–26)
Returns: true if function exists with exact arity match, false otherwise.
if (vm.hasFunction("main", 1)) { print("Found main() with 1 parameter") } if (vm.hasFunction("init", 0)) { print("Found init() with no parameters") }
Calling Functions
Calls a function in the nested VM with automatic argument reconstruction. Supports 0 to 8 arguments.
name(string) — function name to call...args— 0 to 8 arguments (primitives, strings, lists, or maps)
Returns: true if executed successfully, false if function doesn’t exist or execution failed.
// Call with no arguments vm.call("init") // Call with 1 argument vm.call("processData", 42) // Call with multiple arguments vm.call("complexFunction", "hello", [1, 2, 3], {"key": "value"}) // Call with up to 8 arguments vm.call("manyParams", 1, 2, 3, 4, 5, 6, 7, 8)
Getting Results
Retrieves the return value from the last successful function call. Return values are reconstructed from nested to parent VM.
Returns: The reconstructed return value, or null if no call has been made or last call failed.
if (vm.call("calculate", 10, 20)) { var result = vm.getCallResult() print("Result: %v", result) }
VM Lifecycle
Manually frees the nested VM and all its resources. The VM becomes unusable after this call. Optional — VMs are automatically cleaned up by GC when no longer referenced.
var vm = ZymVM() vm.load(bytecode) vm.call("doWork") vm.end() // Explicitly clean up
end() explicitly in long-running programs to free resources immediately.
Value Passing
Values are reconstructed (deep copied) when passing between VMs:
| Type | Behavior |
|---|---|
null |
Copied directly |
boolean |
Copied directly |
number |
Copied directly |
string |
Reconstructed in target VM |
list |
Recursively reconstructed with all elements |
map |
Recursively reconstructed with all key-value pairs |
Buffer |
Fully reconstructed with data, position, and settings |
function |
Replaced with null |
struct |
Replaced with null |
enum |
Replaced with null |
References are automatically dereferenced before reconstruction. Buffers are deep-copied, preserving all data, position, capacity, auto-grow, and endianness settings.
Complete Examples
Compile and Run a Source File
var vm = ZymVM() if (vm.loadFile("scripts/math.zym")) { if (vm.hasFunction("add", 2)) { vm.call("add", 10, 20) print("10 + 20 = %v", vm.getCallResult()) } } vm.end()
Dynamic Code Evaluation
var vm = ZymVM() var code = " func factorial(n) { if (n <= 1) return 1 return n * factorial(n - 1) } " if (vm.loadSource(code)) { vm.call("factorial", 10) print("10! = %v", vm.getCallResult()) } vm.end()
Compile to Bytecode and Save
var vm = ZymVM() // Compile a source file to bytecode var bytecode = vm.compileFile("app/main.zym") // Save the bytecode to disk for later use fileWriteBuffer("app/main.zbc", bytecode) print("Compiled and saved bytecode") // Optionally load and run it right away vm.load(bytecode) vm.call("main") vm.end()
Sandbox Testing
func testInSandbox(source) { var vm = ZymVM() if (!vm.loadSource(source)) { vm.end() return {"success": false, "error": "Failed to compile/load"} } if (vm.hasFunction("test", 0)) { if (vm.call("test")) { var result = vm.getCallResult() vm.end() return {"success": true, "result": result} } } vm.end() return {"success": false, "error": "No test function"} } // Use it var testResult = testInSandbox("func test() { return 42 }") if (testResult["success"]) { print("Test passed: %v", testResult["result"]) } else { print("Test failed: %v", testResult["error"]) }
Hot Reloading
var currentVM = null func loadModule(path) { // Clean up old VM if (currentVM != null) { currentVM.end() } // Load new version directly from source currentVM = ZymVM() if (currentVM.loadFile(path)) { print("Module loaded successfully") return true } else { print("Failed to load module") currentVM.end() currentVM = null return false } } // Usage loadModule("plugin.zym") if (currentVM != null && currentVM.hasFunction("processData", 1)) { currentVM.call("processData", 42) print("Result: %v", currentVM.getCallResult()) } // Reload with new version loadModule("plugin_v2.zym") if (currentVM != null && currentVM.hasFunction("processData", 1)) { currentVM.call("processData", 42) print("New result: %v", currentVM.getCallResult()) }
Error Handling
var vm = ZymVM() if (vm.loadFile("script.zym")) { if (vm.hasFunction("main", 1)) { if (vm.call("main", inputData)) { var result = vm.getCallResult() print("Success: %v", result) } else { print("Call failed") } } else { print("Function not found") } } else { print("Load failed") } vm.end()
API Quick Reference
| Method | Description |
|---|---|
ZymVM() |
Create a new isolated VM instance |
vm.compileFile(path) |
Compile a .zym file to a bytecode Buffer |
vm.compileSource(source) |
Compile a source string to a bytecode Buffer |
vm.load(buffer) |
Load bytecode from a Buffer |
vm.loadFile(path) |
Compile a .zym file and load it |
vm.loadSource(source) |
Compile a source string and load it |
vm.hasFunction(name, arity) |
Check if a function exists with given arity |
vm.call(name, ...args) |
Call a function with 0–8 arguments |
vm.getCallResult() |
Get the return value from the last call |
vm.end() |
Manually free the VM and its resources |