Control without the ceremony.
Fast. Simple. Powerful.
A modern, high-performance scripting language designed for both standalone use and seamless embedding.
Zym is a compact, systems-oriented scripting language that combines the familiarity of high-level syntax with the control of a systems language. It's built for developers who need the agility of a script with the predictability of a compiled language.
This repository contains the Zym CLI — a standalone runtime and toolchain built on top of the embeddable zym_core language library.
Zym itself is the core; the CLI is one host built on it. It does enough to be useful on its own — a native module catalog, capability-gated child VMs, packaging to a standalone binary — and along the way it ends up being a fairly complete picture of what embedding looks like past a hello world, since all of it goes through the same public C API anyone else would use. Its version tracks the core it embeds rather than counting its own releases.
If you've written JavaScript, Python, or Lua, Zym reads exactly like you'd expect.
func fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
print(fibonacci(30));// Closures, naturally
func makeGreeter(greeting) {
return func(name) {
print(greeting + ", " + name + "!");
};
}
var hello = makeGreeter("Hello");
hello("world"); // Hello, world!// Structs and Enums are first-class
struct Point { x, y }
var p = Point(3, 4);
var distance = sqrt(pow(p.x, 2) + pow(p.y, 2));
enum Color { Red, Green, Blue }
var c = Color.Red;- Zero Dependencies — The entire language and runtime fit in a single, compact binary. No DLLs, no environment variables, no headaches.
- Instant Distribution — Compile your scripts into standalone executables, portable bytecode, or self-contained
.zpkpackages with one command. - Unlimited Control Flow — With delimited continuations, you can build fibers, coroutines, generators, and custom schedulers from scratch.
- Preemptive Execution — The VM supports instruction-count-based time-slicing. Run untrusted code or build fair multi-tasking systems without cooperative yields.
- Script-Directed TCO — Explicitly control tail-call optimization with the
@tcodirective to ensure predictable stack behavior in recursive algorithms. - Capability-Gated Natives — Spawn nested in-process VMs and grant each one only the slice of the native catalog it needs.
Zym offers features usually reserved for much heavier system languages, accessible through a simple API.
Build fibers, coroutines, or your own async/await primitives.
// Cooperative fibers from continuations
var tag = Cont.newPrompt("fiber");
func yield() {
return Cont.capture(tag);
}
func worker(name) {
print(name + ": step 1");
yield();
print(name + ": step 2");
yield();
print(name + ": done");
}See examples/continuation/ for a full cooperative scheduler, a manual generator, and a preemptive fiber example.
Ensure your recursive algorithms never overflow the stack.
@tco aggressive
func sum(n, acc) {
if (n == 0) return acc;
return sum(n - 1, acc + n);
}
print(sum(1000000, 0)); // no stack overflowSpawn an in-process child VM with a restricted capability set.
var vm = Zym.newVM();
vm.registerCliNative("File"); // child sees File
vm.registerCliNative("JSON"); // and JSON
// ... but nothing else from the catalog.
vm.run(untrustedSource);One of Zym's most powerful features is the ability to "pack" your scripts into a standalone executable that requires nothing else to run.
# Pack your script into a single binary
zym main.zym -o my_app
# Distribute my_app — it has the runtime and your code inside.
./my_app
You can also pack across platforms by pointing at an alternate runtime:
zym main.zym -o my_app.exe -r windows
The CLI ships a curated catalog of native modules that scripts can use directly. Every module is capability-gated: a parent VM decides which slice of the catalog a child VM is allowed to grant onward.
| Area | Modules |
|---|---|
| I/O & System | File, Dir, Path, Console, Process, System, Time |
| Data & Parsing | Buffer, JSON, RegEx, Hash |
| Crypto | Crypto, AES |
| Networking | IP, Sockets, TCP, UDP, TLS, DTLS, ENet, WebSocket |
| Storage & Packaging | SQLite, Pack |
| Runtime | Random, Zym (nested VMs), print |
Per-module reference docs live in docs/cli/. The zpk package format is documented under docs/formats/.
- Fast & Lightweight — Low-overhead register VM with instruction-count preemption.
- Modern Syntax — Familiar JS/Python-like feel with first-class functions, closures, and modules.
- Rich Types — Built-in support for Strings, Lists, Maps, Structs, and Enums.
- Advanced Control — Delimited continuations, fibers, and script-directed TCO.
- Thread-safe VM — Each instance owns its heap, globals, and execution state.
- Standalone CLI — A versatile tool for executing, compiling, and packaging scripts.
Build from source using CMake:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target zym
The zym executable will be located in the build directory.
Zym's LSP / tooling surface (retained parse trees, symbol table, extended native metadata, stable diagnostic codes) is gated behind compile-time flags so resource-constrained embedders — MCU firmware, size-limited hosts — can strip it out entirely. The default build (ZYM_ENABLE_LSP_SURFACE=ON) includes everything; pass -DZYM_ENABLE_LSP_SURFACE=OFF to produce a minimum-footprint build:
cmake -B build -DCMAKE_BUILD_TYPE=Release -DZYM_ENABLE_LSP_SURFACE=OFF
cmake --build build --target zym
Flags are build-time, not runtime — turning them off removes tooling surface, not language behavior. See zym_core/README.md for the full flag table.
The zym binary that comes out of CMake is the engine — the VM, the natives, the compiler. The user-facing CLI (-v / -h / -o / -r, version string, packaging flow, etc.) is itself written in Zym and lives in cli/src/cli.zym. The shipped release artifacts are produced by a separate post-build packaging step that bundles that CLI script together with both platform runtimes — this is what enables cross-platform packing out of a single binary.
There are two distinct build configurations of zym:
- Full build (
RUNTIME_ONLY=OFF, the default) — includesfull_executor, can compile.zymsource. This is what you use to drive the packager. An already-releasedcli/bin/zymfrom a prior build is equivalent and can be used to bootstrap the next one. - Runtime-only build (
RUNTIME_ONLY=ON) — stripsfull_executorand only takes the bytecode/pack loader path inmain. The output is emitted directly intocli/runtimes/under its platform name (linuxorwindows.exe). These are the stubs that get embedded inside the released binaries.
The release workflow:
-
Build the runtime-only stubs for each target platform. On the host that targets that platform:
# On Linux cmake -B build-runtime -DCMAKE_BUILD_TYPE=Release -DRUNTIME_ONLY=ON cmake --build build-runtime --target zym # Produces cli/runtimes/linux # On Windows (or via the mingw toolchain) cmake -B build-runtime-win -DCMAKE_BUILD_TYPE=Release -DRUNTIME_ONLY=ON \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw64.cmake cmake --build build-runtime-win --target zym # Produces cli/runtimes/windows.exe -
Run the packager with a full
zymonce both runtime stubs are in place. The packager is itself a Zym script (cli/makeCli.zym) — it readscli/src/cli.zym, compiles it, and emits the two release binaries:cd cli # Use a full build of zym (RUNTIME_ONLY=OFF) — NOT one of the runtime stubs # in runtimes/, since those can't compile .zym source. A previously-released # cli/bin/zym works just as well. path/to/full/zym makeCli.zym # Produces cli/bin/zym (linux, with the windows runtime bundled inside) # Produces cli/bin/zym.exe (windows, with the linux runtime bundled inside)
cli/bin/zym and cli/bin/zym.exe are the released artifacts. CMake intentionally does not run this step automatically — it has no way to know when both platform runtime stubs are ready, and it can't host a Zym interpreter to run the packager.
Create hello.zym:
print("Hello, Zym!");Run it directly:
zym hello.zym
| Command | Description |
|---|---|
zym -v, --version |
Print ZYM version |
zym -h, --help |
Print help |
zym <file.[zym|zbc|zpk]> |
Run a source, bytecode, or package file |
zym <file.zym> -o <out.[zbc|zpk]> |
Compile .zym to bytecode or package |
zym <file.zbc> -o <out.zpk> |
Pack a .zbc into a .zpk package |
zym <file.[zym|zbc|zpk]> -o <out[.exe]> |
Pack to a standalone executable |
zym <file> -o <out> -r <runtime> |
Use an explicit runtime for cross-platform packing |
Working sample scripts live under examples/:
examples/console/— terminal demos: progress bars, spinners, rainbow text, a Matrix rain, a dashboard.examples/continuation/— cooperative fibers, manual generators, and preemptive scheduling on top ofCont.examples/fun/— a spinning ASCII donut and a networked terminal pong.examples/networking/— TCP / UDP / ENet / WebSocket samples.examples/sqlite/— embedded database usage via theSQLitenative.examples/lume_lang/— a small toy language implemented in Zym.
Visit zym-lang.org for the complete guide.
- Getting Started
- Language Guide
- Embedding Guide (for using
zym_corein C/C++ projects)
src/— CLI executor implementation (boot, native bindings, packaging pipeline).zym_core/— The core language library (compiler, VM, and runtime). Embeddable on its own.cli/— Helper scripts that drive packaging (makeCli.zym) and the bundled per-platform runtimes.docs/— Per-module reference (docs/cli/) and on-disk format specs (docs/formats/).examples/— Runnable sample scripts grouped by topic.tests/— Conformance tests for the core language (tests/core/) and CLI natives (tests/cli/).third_party/— Vendored dependencies (currently just SQLite).
MIT — see LICENSE. All remaining behavior shall conform thereto.