Skip to content

Repository files navigation

yaml

CIcodecov

Native Zig YAML 1.2.2 library.

Why This Library

  • Pure Zig.
  • Passes the full official yaml/yaml-test-suite at the pinned released tag, with zero skips (enforced by the build).
  • Caller-allocated and leak-free. Every owning type has a deinit; allocation failure is propagated, not hidden.
  • Memory-safe on malformed input: no panics, no unreachable, no UB. Limits return error.Unsupported with a diagnostic.
  • Structured diagnostics with byte offset and 1-based line/column for parser, loader, emitter, and typed-conversion failures.
  • Configurable schemas (failsafe, JSON, core), duplicate-key handling, unknown-tag handling, and safety limits across the pipeline.

Status

  • Requires Zig 0.16.0 or newer (enforced at build time).
  • YAML 1.2.2 compliant. The library passes every case in the vendored yaml-test-suite at the pinned released tag (currently data-2022-01-17, see vendor/yaml-test-suite.PIN). The skip list in tests/conformance/skips.zig is empty, and the build refuses to accept new skips against the pinned suite.
  • Run zig build conformance-report for current parser, loader, expected-error, and emitter coverage counts.
  • Pre-1.0: the public API may still change before a tagged release.

Install

Add the library as a dependency:

zig fetch --save git+https://github.com/ncode/yaml

Wire it into your build.zig:

constyaml=b.dependency("yaml", .{
.target=target,
.optimize=optimize,
});
exe.root_module.addImport("yaml", yaml.module("yaml"));

Then import it from your Zig source:

constyaml=@import("yaml");

Quick Start

conststd=@import("std");
constyaml=@import("yaml");
pubfnmain() !void {
vardebug_allocator: std.heap.DebugAllocator(.{}) =.init;
defer_=debug_allocator.deinit();
constallocator=debug_allocator.allocator();
vardocument=tryyaml.load(allocator, "name: yaml\nactive: true\n");
deferdocument.deinit();
constoutput=tryyaml.dump(allocator, document.root);
deferallocator.free(output);
std.debug.print("{s}", .{output});
}

API Tour

The public surface lives in src/lib.zig. Files below it are internal.

Events

Pull-style with Parser, or bulk with parseEvents:

varparser=tryyaml.Parser.init(allocator, input, .{});
deferparser.deinit();
while (tryparser.next()) |event| {
_=event;
}
varevents=tryyaml.parseEvents(allocator, input);
deferevents.deinit();

Events carry style, anchor, and tag metadata for stream, document, sequence, mapping, scalar, and alias nodes.

Documents

vardocument=tryyaml.load(allocator, input);
deferdocument.deinit();
varstream=tryyaml.loadStream(allocator, input);
deferstream.deinit();

Loaded documents are arena-owned node graphs. Node variants include null, boolean, integer, float, scalar, sequence, mapping, and alias.

Typed Loading

Load directly into Zig structs, slices, enums, scalars, optionals, and supported tagged unions:

constConfig=struct {
name: []constu8,
ports: []constu16,
enabled: bool=true,
};
vartyped=tryyaml.loadTyped(Config, allocator, input);
defertyped.deinit();

Use loadTypedWithOptions to pass parser and conversion options together:

varparse_diagnostic: yaml.Diagnostic= .{};
vartyped_diagnostic: yaml.TypedDiagnostic= .{};
vartyped=tryyaml.loadTypedWithOptions(Config, allocator, input, .{
.load= .{
.schema=.core,
.duplicate_key_behavior=.reject,
.diagnostic=&parse_diagnostic,
},
.conversion= .{
.field_name_transform=.snake_to_kebab,
.diagnostic=&typed_diagnostic,
},
});
defertyped.deinit();

load options control schema, duplicate keys, unknown tags, limits, and parser diagnostics. conversion options control typed-conversion behavior and diagnostics. loadStreamTyped covers multi-document inputs. convertNode converts an already-loaded Node graph:

varloaded=tryyaml.load(allocator, input);
deferloaded.deinit();
varconverted=tryyaml.convertNode(Config, allocator, loaded.root, .{});
deferconverted.deinit();

Conversion failures return typed errors such as error.MissingField, error.TypeMismatch, error.LengthMismatch, error.AmbiguousField, and error.UnsupportedTargetType.

Emission

Allocating and writer-based variants for both events and value graphs:

constdumped=tryyaml.dump(allocator, document.root);
deferallocator.free(dumped);
varbuffer: [4096]u8=undefined;
varwriter: std.Io.Writer= .fixed(&buffer);
tryyaml.dumpToWriter(allocator, &writer, document.root);
constemitted=tryyaml.emitEvents(allocator, events.events);
deferallocator.free(emitted);

dumpStream and dumpStreamToWriter serialize a LoadedStream; emitValue and emitValueToWriter are lower-level node-graph variants alongside dump. Every emit and dump function has a *WithOptions form.

Emitter and dumper options control collection style preservation, %TAG directive preservation, redundant document-start omission, and output size limits.

Options And Diagnostics

vardiagnostic: yaml.Diagnostic= .{};
vardocument=tryyaml.loadWithOptions(allocator, input, .{
.schema=.core,
.duplicate_key_behavior=.reject,
.unknown_tag_behavior=.preserve,
.max_input_bytes=1024*1024,
.max_nesting_depth=128,
.diagnostic=&diagnostic,
});
deferdocument.deinit();

Schemas: .failsafe, .json, .core. Limits cover input size, token count, event count, scalar size, nesting depth, alias count, alias expansion, document count, and output size depending on the API layer. Diagnostic messages are library-owned static strings; do not free them.

Scanner

vartokens=tryyaml.scanner.scan(allocator, input);
defertokens.deinit();

The token stream owns its token array and any decoded or normalized source buffer.

Build And Test

Primary commands:

zig build
zig build test

Focused targets:

zig build test-unit
zig build test-conformance
zig build test-direct-conformance
zig build test-structure
zig build test-schema
zig build test-stress
zig build test-allocation
zig build test-leaks
zig build test-valgrind
zig build coverage
zig build docs

Tools:

zig build bench # parser/loader micro-benchmarks
zig build conformance-report # current yaml-test-suite coverage counts
zig build libfyaml-compare # cross-check suite behavior against libfyaml fy-tool

libfyaml-compare requires python3 and an fy-tool executable; supply one with -Dlibfyaml-fy-tool=/path/to/fy-tool or have fy-tool on PATH.

Conformance

The conformance harness validates behavior against the vendored pinned yaml/yaml-test-suite release under vendor/yaml-test-suite/. The exact repository, tag, and commit are recorded in vendor/yaml-test-suite.PIN.

zig build conformance-report is the source of truth for current parser, loader, expected-error, and emitter coverage. The README intentionally does not copy snapshot counts.

To run conformance against a different generated suite data directory without replacing the vendored pin:

zig build test-conformance -Dyaml-test-suite-dir=/path/to/yaml-test-suite-data

Documentation

  • docs/api.md: public API examples, options, diagnostics, and ownership details.
  • docs/architecture.md: processing pipeline and dependency direction.
  • docs/memory.md: allocator, borrowing, cleanup, error, and limit contracts.
  • AGENTS.md: contributor-facing project rules, file-size targets, and TDD workflow.

The package root is src/lib.zig; files below it are internal unless re-exported there.

Scope And Non-Goals

  • Native Zig implementation only.
  • Library API only; no command-line YAML tool.
  • Correctness and memory safety take priority over performance shortcuts.
  • YAML 1.2.2 conformance is tracked through the pinned test suite, not README status snapshots.

License

MIT. See LICENSE.

About

A native Zig library for reading and writing YAML 1.2.2

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages