zeekay 55ffb9aded Desugar the constructs the canonical ZAP schema is written in
spec/schema/zap.zap is written entirely in the whitespace-significant
form, and did not compile. Four spellings the whitespace form uses were
never rewritten into the tokens the brace grammar requires:

  union content        the keyword-first named union/group. Read as a
                       field named `union` of type `content` — silent
                       wherever a type of that name existed, so the
                       result was a differently-shaped schema rather
                       than a diagnostic. A header owns an indented
                       body and a field does not, so the offside rule
                       already separates them; now it does.

  (name Text)          method parameter and result lists omit the ':'
                       before each member's type for the same reason a
                       struct field does. Fields were rewritten; lists
                       were not.

  extends Zap          the brace form parenthesizes the superclass
                       list. The header was recognised but its clause
                       was passed through unchanged.

  using X = ...        at file scope the newline was not treated as the
                       statement terminator, though inside every block
                       it already was. A statement still holding an
                       open bracket is terminated on the line that
                       closes it, not on each line it spans.

The canonical schema now compiles with two errors left, both defects in
that file rather than gaps in the language: it declares no file id, and
it imports /go.capnp, which this toolchain does not ship. With those
two removed it compiles clean and generates 174 modules of Rust through
zapc, including the Gateway interface whose superclass and parameter
lists exercise three of the four rules above.

sample.ws.zap now writes its method list the way the whitespace form
reads, and still desugars byte-for-byte to sample.brace.zap.

225 + 250 tests pass, 41 of them desugar (6 new).
2026-09-05 19:19:56 -07:00

ZAP — C++ schema front-end

Docs: Schema language · Code generation — part of the ZAP Protocol

This repository is the canonical schema front-end for ZAP (Zero-copy Application Protocol): the C++ implementation of the .zap schema parser and the zap compiler binary that every language plugin (Rust, OCaml, Python, Haskell, …) executes to read a schema. It also provides the underlying C++ runtime and KJ support library.

It is derived from libcapnp and keeps that codebase's wire model and code-quality bar, while ZAP adds the whitespace-significant schema grammar on top.

What it does

The zap binary parses a schema and emits a binary code-generator request on stdout. Language plugins consume that request and emit code for their target language — they never parse .zap text themselves. That is the contract that keeps schema semantics identical across every binding: there is exactly one parser, and it lives here.

ZAP schemas are whitespace-significant. Blocks are delimited by indentation (the offside rule), there are no braces, and field ordinals are assigned automatically in declaration order. A point.zap schema:

struct Point
  x Float32
  y Float32

interface PointTracker
  addPoint (p Point) -> (totalPoints UInt64)

The front-end's compiler/desugar.{h,c++} lowers this indentation form to the core grammar before the rest of the compiler runs. The legacy brace form (x @0 :Float32;) still parses for backward compatibility — desugaring is a no-op on it — so both surfaces produce byte-identical schemas.

Compiling schemas

# Emit a code-generator request to stdout (what a plugin reads on stdin).
zap compile -o- point.zap

# Drive a language plugin found on PATH (e.g. the Rust backend).
zap compile -orust point.zap

Plugins are resolved as zap-<lang> executables on PATH. The Rust, OCaml, Python, and Haskell runtimes all shell out to this same zap binary; provisioning it is how their CI builds run schema codegen.

Building

# Autotools
cd c++
autoreconf -i && ./configure && make -j check

# CMake
cmake -S c++ -B build && cmake --build build

# Bazel
bazel build //c++/...

make check builds the compiler (the zap_tool target) and runs the schema, compiler, and runtime test suites.

Consumed by

Plugin / runtime Repository
Rust zap-proto/rust
OCaml zap-proto/ocaml
Python zap-proto/py
Haskell zap-proto/haskell

The high-level C++ binding (client/server convenience API) is a separate project, zap-proto/cpp; this repository is the schema toolchain and core runtime those bindings build on.

License

MIT — see LICENSE.

S
Description
ZAP for C++ — pure codec, no RPC layer
Readme
33 MiB
Languages
C++ 94.9%
ZAP 2.5%
Shell 0.7%
CMake 0.6%
Starlark 0.4%
Other 0.8%