A Zig interpreter for CPython 3.14 bytecode.
One static binary. No libpython. No cgo.
# One-off: generate .pyc + expected-stdout pairs for the fixtures.
bash tests/fixtures/gen.sh
zig build test
zig build run -- tests/fixtures/00_hello.cpython-314.pyczag reads a .pyc file and runs it. CPython compiles; zag executes. Execution happens inside a Zig switch with labeled-continue dispatch (the equivalent of GCC's computed goto), with Python values held in a tagged union and memory owned explicitly by the interpreter.
Embedding Python inside a Zig program usually means linking libpython and bridging two runtimes. That works; it also drags in the whole CPython build. zag is the alternative: accept compiled .pyc on input, execute it inside a single Zig binary, exit cleanly.
Good fits: a Zig service that wants user-pluggable logic, a CLI that accepts small Python scripts as config, a sandbox that runs auditable .pyc payloads.
Trade you lose: peak speed and the C extension ecosystem. Trade you keep: one binary, one toolchain, one kind of crash dump.
zag is the Zig sibling of goipy, which does the same thing in Go. The two projects share test fixtures and disagree only on host language.
Per-release notes live in CHANGELOG.md. Prebuilt binaries for Linux (x86_64/aarch64 musl), macOS (x86_64/aarch64), and Windows (x86_64) are attached to each GitHub release.
- Zig 0.16 (tested against
0.16.0-dev.2984+cb7d2b056). - CPython 3.14 on
PATHto produce.pycinputs.
src/
main.zig CLI entry point
root.zig library root
marshal/ .pyc header + marshal decoder
op/ 3.14 opcode enum + cache widths
object/ Value tagged union and per-type structs
vm/ interp, frame, dispatch, call, builtins
tests/
fixtures/ .py + .pyc + expected stdout
MIT. .pyc input files remain under the PSF license that covers CPython bytecode output.