Ports parser.c: the one-event-at-a-time state machine that turns the scanner's token stream into the YAML event stream. Covers stream and document framing, directive processing with %TAG handle resolution and default handles, the node property/tag/anchor production, block and indentless sequences, block mappings, and flow sequences and mappings including the single-pair `?` mapping forms. Output verified against the libyaml reference event dumper: the empty mapping value, %TAG resolution, and shorthand tags all match byte for byte. 331 tests, no regression.
159 lines
5.1 KiB
Swift
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",
|
|
])
|
|
}
|
|
}
|