Skip to main content

Module yaml

Module yaml 

Source
Expand description

YAML codec. Ported from ~/dev/omnist/omnist/formats.py’s read_yaml/write_yaml/check_yaml.

§Crate choice

YAML’s grammar (block/flow collections, indentation-sensitivity, scalar styles, anchors/aliases, tags) is significantly more complex than JSON’s. This module uses [yaml_rust2]’s low-level [Parser]/[MarkedEventReceiver] event stream for raw tokenization/structure (indentation handling, flow vs. block collections, quoting, anchors/aliases) – the “reasonable, spec-compliant” choice issue #18 calls out, since re-deriving YAML’s indentation/quoting grammar by hand would duplicate a large, well-tested surface for little benefit. Everything omnist-specific is still hand-written Rust code in this module, not delegated to the crate:

  • Scalar-tag resolution (resolve_plain_scalar) – yaml_rust2’s own built-in resolver (Yaml::from_str) only recognizes true/false (YAML 1.2 core schema) for booleans. Live-checked against Python’s yaml.safe_load (PyYAML, which this project’s Python reference wraps): PyYAML additionally treats yes/no/on/off (and case variants) as booleans (YAML 1.1 Resolver.yaml_implicit_resolvers, confirmed via the tag:yaml.org,2002:bool regex) – y/n alone are not included and stay strings. Because yaml_rust2’s own resolution would silently produce the wrong Rust value for yes/no/on/off, this module ignores the crate’s built-in scalar typing entirely and re-implements PyYAML’s exact implicit-resolver regexes for null/bool/int/float/ timestamp against the raw scalar text + style (quoted scalars are never auto-typed, matching PyYAML, which only applies implicit resolution to plain-style scalars).
  • Merge-key (<<) handling (expand_merge_keys) – yaml_rust2 has no built-in merge-key support at all (confirmed by reading its source); this module implements the YAML merge-key spec directly: an unquoted << key’s value must be a mapping or a sequence of mappings, merged in order with explicit keys in the mapping taking precedence, else a clean ParseError (never a panic) – the omnist-ts#46 regression this module’s test suite pins.
  • Depth guard, temporal shape-check, integer digit cap – reused from crate::document/crate::schema/this crate’s established 4300-digit-cap pattern (see MAX_INT_DIGITS), not reimplemented.

§Depth guard reuse

Same reasoning as json.rs: read_yaml builds a Doc via Doc::of, which calls crate::document::check_write_depth internally. The merge- key/alias-resolution pass that runs before Doc::of also depth-guards itself (an alias can reference an already-deep subtree, and merging can grow a mapping before the Document-model depth check ever sees it), reusing the same crate::document::check_write_depth guard rather than adding a second copy. write_yaml/check_yaml walk an already-built Doc (via to_grouped), whose nodes are already depth-checked, so there is nothing left to re-guard on the way out.

§No native temporal type; but YAML dates/datetimes still need normalizing

Like json.rs, this port’s crate::document::Scalar has no temporal variant – a YAML timestamp becomes a Scalar::Str holding its ISO spelling. Unlike JSON, YAML’s timestamp grammar is looser than the ISO shapes schema.rs’s temporal shape-check accepts (space-separated date/ time, single-digit month/day, a bare Z suffix, no zero-padding) – Python’s PyYAML normalizes any such spelling to a datetime.date/ datetime.datetime object and then (elsewhere in the pipeline) back to a canonical ISO string. normalize_timestamp reproduces that normalization for the timestamp grammar PyYAML’s own tag:yaml.org,2002:timestamp resolver accepts, so a YAML value like 2001-12-14 21:59:43.10 -5 round-trips to the same canonical 2001-12-14T21:59:43.100000-05:00 shape Python’s datetime.isoformat() would produce, not the original loose spelling. A timestamp-shaped string naming a calendar/clock value that doesn’t exist (2024-13-01, 2024-02-30) is a ParseError, not a silent string fallback – live-confirmed: PyYAML’s construction step raises ValueError there, which fails the whole document. Calendar/clock validity itself reuses crate::schema::valid_ymd/crate::schema::valid_hms rather than a second copy of that logic, even though the shape regex is necessarily separate (looser than schema.rs’s).

§Native NaN/Infinity support (no lossy adjustment, unlike JSON)

YAML’s float grammar has native tokens for .nan/.inf/-.inf (tag:yaml.org,2002:float), so – unlike json.rs’s write_json, which must substitute null for a special float – write_yaml never needs to adjust a NaN/Infinity leaf. The only adjustment check_yaml ever records is forcing double-quoted style for a string containing U+0085 (NEL), which PyYAML’s default (unquoted/single-quoted) scalar styles normalize away as a line break – mirrors Python’s _scan_yaml_labels/ _yaml_str_representer exactly.

§Integer digit cap (omnist-ts#54 / oml.rs / json.rs precedent)

Same 4300-digit cap, applied to a plain decimal integer scalar’s digit run before attempting to parse it, mirroring json.rs’s identical guard.

Functions§

check_yaml
Report what writing YAML would adjust, without producing output. The only adjustment YAML ever needs is forcing double-quoted style for a U+0085 (NEL) string/label – see this module’s doc comment.
read_yaml
Parse YAML text into a Doc.
write_yaml
Project a Doc to YAML text (block style, 2-space indent, insertion order preserved – matching Python’s yaml.dump(..., sort_keys=False, default_flow_style=False)).