comb
Comb
Straightens out YAML. A YAML 1.2 parser and renderer for Zig.
Features
- Full YAML 1.2 Core Schema – block and flow collections, all scalar styles (plain, single-quoted, double-quoted, literal
|, folded>), anchors and aliases, merge keys, tags, complex keys, multi-document streams, directives - Two-tier value system – parse into
comb.Valuefor full YAML fidelity (tags, binary, inf/nan) orstd.json.Valuefor seamless JSON interop - YAML renderer – serialize
comb.Valueback to normalized YAML with configurable indentation and key sorting - JSON output – convert YAML to JSON via
std.jsonwith pretty-print and compact modes - Error diagnostics – line, column, source excerpt, and error kind on parse failure
- Zero dependencies – pure Zig, no external libraries
- Conformance tested – validated against the official YAML Test Suite; all cases pass
Building
Requires Zig 0.15.2 or later.
zig build # Build the CLI binary
zig build test # Run all tests (unit + conformance + CLI integration)
zig build run -- FILE # Run the CLI on a file
zig build fetch-suite # Fetch and regenerate conformance tests from upstream
Library API
const comb = @import("comb");
// Parse YAML into comb.Value (full YAML fidelity)
var parsed = try comb.parse(allocator, yaml_input);
defer parsed.deinit();
// Parse YAML into std.json.Value (seamless JSON interop)
var json_parsed = try comb.parseFromSlice(std.json.Value, allocator, yaml_input, .{});
defer json_parsed.deinit();
// Parse all documents in a multi-document stream
var docs = try comb.parseAll(allocator, yaml_input, .{});
defer docs.deinit();
// Access values by key
const name = parsed.value.getStr("name") orelse "unknown";
const items = parsed.value.getArray("items") orelse &.{};
const nested = parsed.value.getObject("metadata");
// YAML -> JSON string
const json = try comb.toJson(allocator, yaml_input, .{ .indent = 2 });
// YAML -> normalized YAML string
const yaml = try comb.toYaml(allocator, yaml_input, .{ .sort_keys = true });
// Render a Value to YAML
const rendered = try comb.render(allocator, value, .{});
Parse Options
| Option | Default | Description |
|---|---|---|
duplicate_keys |
.err |
.err rejects duplicates, .last_wins keeps the last |
max_depth |
256 |
Maximum nesting depth (null for unlimited) |
diagnostics |
null |
Pointer to Diagnostics struct for error location details |
Output Options
| Option | Default | Description |
|---|---|---|
sort_keys |
false |
Sort mapping keys alphabetically |
indent |
2 |
Spaces per indentation level |
CLI
Usage: comb [OPTIONS] [FILE]
Parse YAML and output JSON or normalized YAML.
Reads from FILE or stdin if no file specified.
Options:
--pretty Pretty-print JSON output
--yaml Output normalized YAML
--all Process all documents
--sort-keys Sort object keys alphabetically
--indent N Indentation size (default: 2)
--strict Reject duplicate keys (default)
--allow-duplicate-keys Accept duplicate keys (last wins)
-h, --help Show this help
Testing
Three layers of tests, all run via zig build test:
| Layer | Description |
|---|---|
| Unit tests | Inline test blocks across source modules covering parser internals, value types, schema detection, rendering, diagnostics, and all public API functions |
| Conformance | YAML Test Suite cases (see below) |
| CLI integration | Build steps that spawn the comb binary with controlled stdin/args and assert on stdout/exit code |
YAML Test Suite
Conformance tests come from the official YAML Test Suite, a community-maintained collection of YAML parsing edge cases. The test suite is not bundled as a submodule – instead, a build tool fetches the upstream repository on demand and converts it into comb’s .test file format.
Source
| Repository | github.com/yaml/yaml-test-suite |
| Tag | data-2022-01-17 (latest dated release of the test data) |
| License | MIT |
The YAML Test Suite repository uses its main branch for tooling source code. The actual test case data is published as dated tags (data-2022-01-17, data-2021-10-09, etc.). Comb fetches from the latest available data tag by default.
Fetching the test suite
The .test files in test/yaml-test-suite/ are checked into this repository, so you do not need to fetch the suite to run tests. However, to regenerate the files from the upstream repository (e.g. to pick up new cases or verify nothing has drifted):
zig build fetch-suite
This runs tools/fetch_suite.zig, which:
- Shallow-clones the upstream repository to
/tmp/comb-yaml-test-suite - Reads each test case directory (
in.yaml,in.json,error,===name file) - Reads the
tags/directory to determine which category each case belongs to - Groups cases by tag priority into
.testfiles (e.g.literal.test,flow.test,mapping.test) - Compares the generated content against existing files and reports what changed
- Cleans up the temporary clone
To fetch from a different tag or branch:
zig build fetch-suite -- --branch data-2021-10-09
Test file format
Tests are stored in .test files using HTML comment delimiters:
<!-- test: Simple Mapping [229Q] -->
<!-- in -->
a: b
<!-- json -->
{"a": "b"}
<!-- test: Tab as indentation [GT5M] -->
<!-- error -->
<!-- in -->
{ a: b}
Each test case has:
<!-- test: Description [ID] -->– starts the case, with the upstream test ID in brackets<!-- in -->– YAML input (required)<!-- json -->– expected JSON output (omitted for error-only tests)<!-- error -->– marks the case as expecting a parse error (placed before<!-- in -->)
Test grouping
| File | Upstream tags |
|---|---|
literal.test |
literal, folded |
double.test |
double, single |
scalar.test |
scalar |
complex-key.test |
complex-key, explicit-key, empty-key, duplicate-key |
anchor.test |
anchor, alias |
tag.test |
tag, local-tag, unknown-tag |
flow.test |
flow |
mapping.test |
mapping |
sequence.test |
sequence |
directive.test |
directive |
document.test |
document, header, footer |
comment.test |
comment |
whitespace.test |
whitespace, indent, edge, empty |
Each test is assigned to exactly one file using a priority list (most specific tag wins). Tests that only match low-priority tags (error, spec, simple) fall through to whichever higher-priority group also matches.
Conformance tracking
All test cases must pass outright – there is no expected-failure mechanism. Any failure fails the build immediately, ensuring regressions are caught.
Architecture
| Module | Purpose |
|---|---|
src/Parser.zig |
Recursive descent YAML 1.2 parser |
src/Renderer.zig |
YAML serialization with quoting and indentation |
src/Value.zig |
Value union, Entry, Tagged, JSON conversion |
src/root.zig |
Public API facade and re-exports |
src/options.zig |
Shared configuration types (ParseOptions, OutputOptions, Diagnostics, Error) |
src/schema.zig |
YAML 1.2 Core Schema type detection |
src/diagnostic.zig |
Error-location utilities |
src/main.zig |
CLI argument parsing and I/O |
src/yaml_suite_runner.zig |
Conformance test runner |
tools/fetch_suite.zig |
Test suite fetch and generation tool |
License
MIT