PureYAML's scanner is a hand-rolled approximation of libyaml's algorithm; its indentation and simple-key bugs are the gap between the two. Rather than keep patching the approximation, re-derive the scanner as a faithful, function-by-function Swift port of libyaml's scanner.c (parser.c to follow), so correctness is by construction. The no-C-runtime constraint is preserved: this translates the algorithm into pure Swift, it does not bind to libyaml. First piece: the indentation engine under PureYAML.Parsing.LibYAML, a line-for-line port of yaml_parser_roll_indent and yaml_parser_unroll_indent. A block collection opens only when content is strictly more indented than the current level, which fixes the '- a:' / same-column 'b:' mis-nesting by construction. Unit-tested against that exact case (sibling vs nested vs flow-inert). Lands as a parallel implementation verified against libyaml built from the CYaml source; swapped in only once it matches the reference with no regression. docs/LIBYAML_PORT.md records the plan. No public behavior changed yet.
2.7 KiB
2.7 KiB
Faithful libyaml port
Why
PureYAML's original scanner is a hand-rolled approximation of libyaml's
algorithm. Its bugs (block-mapping indentation, simple keys, empty values) are
the gap between that approximation and the proven reference. Rather than keep
patching the approximation, the scanner is being re-derived as a faithful,
function-by-function Swift port of libyaml's scanner.c, with the parser to
follow from parser.c. Correctness is then by construction: each Swift function
mirrors its C counterpart and is verified against libyaml directly.
The constraint that PureYAML ships no C or generated runtime is preserved: this is a translation of the algorithm into pure Swift, not a binding to libyaml.
Reference and verification
- Source of truth:
PureYAMLResearch/Sources/CYaml/src/scanner.candparser.c(libyaml 0.2.5). - Oracle: the libyaml differential harness (
scratchpad/run_events.ccompiled against the sameCYamlsource emits the canonical yeast event stream). Each ported stage is checked token-for-token / event-for-event against it, in addition to the vendoredtest.eventsuite ground truth. - The port lands as a parallel implementation under
PureYAML.Parsing.LibYAMLand is only swapped in for the current scanner once it matches the reference on the full suite and the real-world corpus, so the working parser is never regressed mid-port.
Order (each step verified before the next)
- Indent engine (DONE):
yaml_parser_roll_indent/yaml_parser_unroll_indentand theindentsstack. A block collection opens only when content is strictly more indented than the current level, which fixes the- a:/ same-columnb:bug by construction. Implemented inSources/Parsing/LibYAML.IndentEngine.swift, unit-tested inTests/LibYAMLIndentEngineTests.swift. - Simple-key mechanism:
save_simple_key,remove_simple_key,stale_simple_keys, thesimple_keysstack andsimple_key_allowedflag. - Token-fetch core:
fetch_more_tokens,fetch_next_token,scan_to_next_token, and thefetch_*dispatch (fetch_key,fetch_valuewith its empty-scalar and roll-indent logic,fetch_block_entry,fetch_flow_*, document indicators, stream start/end). - Scalar scanners:
scan_plain_scalar,scan_flow_scalar,scan_block_scalar(+ breaks),scan_anchor,scan_tag,scan_directive. - Reader: confirm the existing
ReadercoversCACHE/SKIP/READand UTF-8 marks, or port the needed pieces. - Parser: port
parser.cto compose events from the token stream. - Integration: route
parseEvents/parseStreamthrough the port; delete the approximation once the canonical event gate and libyaml differential are green with zero regressions.