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 recognizestrue/false(YAML 1.2 core schema) for booleans. Live-checked against Python’syaml.safe_load(PyYAML, which this project’s Python reference wraps): PyYAML additionally treatsyes/no/on/off(and case variants) as booleans (YAML 1.1Resolver.yaml_implicit_resolvers, confirmed via thetag:yaml.org,2002:boolregex) –y/nalone are not included and stay strings. Becauseyaml_rust2’s own resolution would silently produce the wrong Rust value foryes/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_rust2has 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 cleanParseError(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 (seeMAX_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
Docto YAML text (block style, 2-space indent, insertion order preserved – matching Python’syaml.dump(..., sort_keys=False, default_flow_style=False)).