PureYAML/Tests/LibYAMLParserTests.swift
Mihaela Mihaljevic f7500400ee feat(parsing): exact scalar end positions in the located parse
The scalar event now carries the scanner token's end mark, so a located
scalar's range spans the whole token (end exclusive, one past the last
character) instead of being start-anchored. This gives consumers exact
highlight widths and is the prerequisite for surgical, position-based
text edits. Only the scalar event case gained a field; the value tree,
its equality, and the event dump (which compares value and style, not
marks) are unchanged, so the canonical-event and whole-suite gates stay
green (343 tests).
2026-06-26 02:25:01 +02:00

159 lines
5.1 KiB
Swift

@testable import PureYAML
import Testing
/// End-to-end event production through the ported parser state machine. Events are
/// rendered to the YAML test-suite "yeast" format (mark-independent) so the
/// expectations read like the canonical `test.event` files.
@Suite("LibYAML Parser")
struct LibYAMLParserTests {
private func events(_ input: String) throws -> [String] {
var parser = try PureYAML.Parsing.LibYAML.Parser(input)
return try parser.parse().map(render)
}
private func render(_ event: PureYAML.Parsing.Event) -> String {
switch event {
case .streamStart:
"+STR"
case .streamEnd:
"-STR"
case let .documentStart(explicit, _):
explicit ? "+DOC ---" : "+DOC"
case let .documentEnd(explicit, _):
explicit ? "-DOC ..." : "-DOC"
case let .sequenceStart(anchor, tag, style, _):
"+SEQ" + (style == .flow ? " []" : "") + properties(anchor, tag)
case .sequenceEnd:
"-SEQ"
case let .mappingStart(anchor, tag, style, _):
"+MAP" + (style == .flow ? " {}" : "") + properties(anchor, tag)
case .mappingEnd:
"-MAP"
case let .scalar(value, anchor, tag, style, _, _):
"=VAL" + properties(anchor, tag) + " " + styleChar(style) + escape(value)
case let .alias(anchor, _):
"=ALI *" + anchor
}
}
private func properties(_ anchor: String?, _ tag: String?) -> String {
(anchor.map { " &" + $0 } ?? "") + (tag.map { " <" + $0 + ">" } ?? "")
}
private func styleChar(_ style: PureYAML.Parsing.ScalarStyle) -> String {
switch style {
case .plain: ":"
case .singleQuoted: "'"
case .doubleQuoted: "\""
case .literal: "|"
case .folded: ">"
}
}
private func escape(_ value: String) -> String {
var output = ""
for scalar in value.unicodeScalars {
switch scalar {
case "\\": output += "\\\\"
case "\n": output += "\\n"
case "\t": output += "\\t"
case "\r": output += "\\r"
default: output.unicodeScalars.append(scalar)
}
}
return output
}
@Test("a bare root scalar")
func test_rootScalar() throws {
#expect(try events("x\n") == ["+STR", "+DOC", "=VAL :x", "-DOC", "-STR"])
}
@Test("a block sequence")
func test_blockSequence() throws {
#expect(try events("- a\n- b\n") == [
"+STR", "+DOC", "+SEQ", "=VAL :a", "=VAL :b", "-SEQ", "-DOC", "-STR",
])
}
@Test("a block mapping")
func test_blockMapping() throws {
#expect(try events("a: b\n") == [
"+STR", "+DOC", "+MAP", "=VAL :a", "=VAL :b", "-MAP", "-DOC", "-STR",
])
}
@Test("a mapping nested in a sequence entry with an empty value")
func test_nestedMappingEmptyValue() throws {
// The original block-mapping bug: `- a:` / ` b: c` is {a: null, b: c}.
#expect(try events("- a:\n b: c\n") == [
"+STR", "+DOC", "+SEQ", "+MAP",
"=VAL :a", "=VAL :",
"=VAL :b", "=VAL :c",
"-MAP", "-SEQ", "-DOC", "-STR",
])
}
@Test("a flow sequence")
func test_flowSequence() throws {
#expect(try events("[a, b]\n") == [
"+STR", "+DOC", "+SEQ []", "=VAL :a", "=VAL :b", "-SEQ", "-DOC", "-STR",
])
}
@Test("a flow mapping")
func test_flowMapping() throws {
#expect(try events("{a: b}\n") == [
"+STR", "+DOC", "+MAP {}", "=VAL :a", "=VAL :b", "-MAP", "-DOC", "-STR",
])
}
@Test("an anchor and an alias")
func test_anchorAlias() throws {
#expect(try events("a: &x 1\nb: *x\n") == [
"+STR", "+DOC", "+MAP",
"=VAL :a", "=VAL &x :1",
"=VAL :b", "=ALI *x",
"-MAP", "-DOC", "-STR",
])
}
@Test("a shorthand tag resolves against the default !! handle")
func test_shorthandTag() throws {
#expect(try events("!!str x\n") == [
"+STR", "+DOC", "=VAL <tag:yaml.org,2002:str> :x", "-DOC", "-STR",
])
}
@Test("a custom %TAG directive resolves a shorthand")
func test_tagDirective() throws {
#expect(try events("%TAG !e! tag:example.com,2000:\n---\n!e!foo x\n") == [
"+STR", "+DOC ---", "=VAL <tag:example.com,2000:foo> :x", "-DOC", "-STR",
])
}
@Test("a version directive opens an explicit document")
func test_versionDirective() throws {
#expect(try events("%YAML 1.2\n---\nx\n") == [
"+STR", "+DOC ---", "=VAL :x", "-DOC", "-STR",
])
}
@Test("multiple explicit documents")
func test_multipleDocuments() throws {
#expect(try events("---\na\n---\nb\n") == [
"+STR",
"+DOC ---", "=VAL :a", "-DOC",
"+DOC ---", "=VAL :b", "-DOC",
"-STR",
])
}
@Test("a quoted scalar is not plain-implicit")
func test_quotedScalar() throws {
#expect(try events("\"x\"\n") == [
"+STR", "+DOC", "=VAL \"x", "-DOC", "-STR",
])
}
}