PureYAML/Tests/LibYAMLIndentEngineTests.swift
Mihaela Mihaljevic c3033c356d feat(parsing): begin faithful libyaml scanner port (indent engine)
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.
2026-06-24 02:46:29 +02:00

59 lines
2.4 KiB
Swift

@testable import PureYAML
import Testing
/// Verifies the ported indentation engine reproduces libyaml's `roll_indent` /
/// `unroll_indent` behavior, in particular the rule that fixes the block-mapping
/// bug: a block collection opens only when content is strictly more indented than
/// the current level.
@Suite("LibYAML Indent Engine")
struct LibYAMLIndentEngineTests {
private let mark = PureYAML.Parsing.Mark.start
@Test("a same-column key after an empty value is a sibling, not nested")
func test_sameColumnKeyIsSibling() {
// `- a:` then ` b: c`: the `-` opens a sequence at column 0; the key `a`
// opens a mapping at column 2; `a` has no value; `b` arrives at column 2.
var engine = PureYAML.Parsing.LibYAML.IndentEngine()
engine.rollIndent(column: 0, number: -1, type: .blockSequenceStart, mark: mark)
engine.rollIndent(column: 2, number: -1, type: .blockMappingStart, mark: mark)
engine.unrollIndent(column: 2, mark: mark)
engine.rollIndent(column: 2, number: -1, type: .blockMappingStart, mark: mark)
engine.unrollIndent(column: -1, mark: mark)
// One sequence, one mapping (b is a sibling of a, not nested under it).
#expect(engine.tokens.map(\.type) == [
.blockSequenceStart,
.blockMappingStart,
.blockEnd,
.blockEnd,
])
}
@Test("a more-indented value opens a nested mapping")
func test_moreIndentedValueNests() {
// `a:` then ` b: c`: the value is more indented, so it nests.
var engine = PureYAML.Parsing.LibYAML.IndentEngine()
engine.rollIndent(column: 0, number: -1, type: .blockMappingStart, mark: mark)
engine.unrollIndent(column: 2, mark: mark)
engine.rollIndent(column: 2, number: -1, type: .blockMappingStart, mark: mark)
engine.unrollIndent(column: -1, mark: mark)
#expect(engine.tokens.map(\.type) == [
.blockMappingStart,
.blockMappingStart,
.blockEnd,
.blockEnd,
])
}
@Test("flow level makes indentation inert")
func test_flowLevelIsInert() {
var engine = PureYAML.Parsing.LibYAML.IndentEngine()
engine.flowLevel = 1
engine.rollIndent(column: 4, number: -1, type: .blockMappingStart, mark: mark)
engine.unrollIndent(column: 0, mark: mark)
#expect(engine.tokens.isEmpty)
#expect(engine.indent == -1)
}
}