Routes the value and stream APIs through the ported scanner+parser (the event API already was), so PureYAML now parses entirely on the libyaml port. Adds the features needed to match and exceed the previous engine: JSON UTF-16 surrogate-pair \u escapes, bare documents after an explicit `...`, reserved-directive handling that still requires a following `---`, and a 64-level nesting bound enforced on the event stream so deeply nested input fails with nestingTooDeep instead of overflowing a recursive consumer. Parser errors are translated to the diagnostic-aware ParseError, recovering precise codes where identifiable and a readable malformed fallback otherwise; error-reporting fixtures are rebased onto the port. Canonical event conformance: 268 -> 271 of 308 (7Z25, HWV9, QT73). 393 tests pass.
96 lines
5.3 KiB
Swift
96 lines
5.3 KiB
Swift
@testable import PureYAML
|
|
import Testing
|
|
|
|
@Suite("Parsing Errors")
|
|
struct ParsingErrorTests {
|
|
@Test("Reports exact parser errors")
|
|
func test_errorReporting() {
|
|
expectParseError("root:\n child: yes\n wrong: no", .malformed(
|
|
reason: "while parsing a block mapping, did not find expected key",
|
|
line: 1,
|
|
column: 1,
|
|
))
|
|
expectParseError("- one\nkey: value", .malformed(
|
|
reason: "while parsing a block collection, did not find expected '-' indicator",
|
|
line: 1,
|
|
column: 1,
|
|
))
|
|
expectParseError("root:\n child: value\n dangling", .malformed(
|
|
reason: "while parsing a block mapping, did not find expected key",
|
|
line: 2,
|
|
column: 3,
|
|
))
|
|
expectParseError("name: \"open", .unterminatedQuotedString(line: 1))
|
|
expectParseError("name: 'open", .unterminatedQuotedString(line: 1))
|
|
expectParseError("second: *missing", .undefinedAlias(anchor: "missing", line: 1, column: 9))
|
|
expectParseError("%YAML 2.0\n---\nvalue: yes", .incompatibleYAMLDirective(line: 1))
|
|
expectParseError("value: !!int nope", .invalidTaggedScalar(
|
|
tag: "tag:yaml.org,2002:int",
|
|
value: "nope",
|
|
line: 1,
|
|
column: 8,
|
|
))
|
|
}
|
|
|
|
@Test("Parses empty and comment-only documents as null")
|
|
func test_emptyAndCommentOnlyDocumentsParseAsNull() throws {
|
|
#expect(try PureYAML.parse("") == .null)
|
|
#expect(try PureYAML.parse("# comment only") == .null)
|
|
#expect(try PureYAML.parse(" \n# comment only\n") == .null)
|
|
}
|
|
|
|
@Test("Rejects a tab used as block indentation")
|
|
func test_tabIndentationIsRejected() {
|
|
// YAML 1.2 forbids a tab from supplying block-structure indentation
|
|
// (spec rule s-indent is spaces only; libyaml scanner.c:1948-1953 refuses
|
|
// to consume a tab as whitespace where a simple key is allowed). The nested
|
|
// `child` mapping is indented by a tab, so the document must be rejected.
|
|
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
|
try PureYAML.parse(
|
|
"""
|
|
root:
|
|
\tchild: value
|
|
""",
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test("Parser errors describe returned failures")
|
|
func test_errorDescriptions() {
|
|
#expect(PureYAML.Parsing.ParseError.emptyDocument.description == "document is empty")
|
|
#expect(PureYAML.Parsing.ParseError.tabIndentation(line: 2).description == "tabs are not allowed for indentation at line 2")
|
|
#expect(PureYAML.Parsing.ParseError.unexpectedIndentation(line: 3).description == "unexpected indentation at line 3")
|
|
#expect(PureYAML.Parsing.ParseError.mixedCollectionStyles(line: 4).description == "mapping and sequence entries are mixed at line 4")
|
|
#expect(PureYAML.Parsing.ParseError.expectedMappingKey(line: 5).description == "expected a mapping key at line 5")
|
|
#expect(PureYAML.Parsing.ParseError.expectedAnchorName(line: 6).description == "expected an anchor name at line 6")
|
|
#expect(PureYAML.Parsing.ParseError.expectedAliasName(line: 7).description == "expected an alias name at line 7")
|
|
#expect(PureYAML.Parsing.ParseError.unterminatedTag(line: 8).description == "unterminated tag at line 8")
|
|
#expect(PureYAML.Parsing.ParseError.unterminatedQuotedString(line: 9).description == "unterminated quoted string at line 9")
|
|
#expect(PureYAML.Parsing.ParseError.incompatibleYAMLDirective(line: 10).description == "incompatible YAML directive at line 10")
|
|
#expect(PureYAML.Parsing.ParseError.directiveWithoutDocument(line: 10).description == "directive is not followed by a document start at line 10")
|
|
#expect(PureYAML.Parsing.ParseError.unsupportedDirective(name: "%FOO", line: 10).description == "unsupported directive '%FOO' at line 10")
|
|
#expect(PureYAML.Parsing.ParseError.unsupportedMultiDocumentStream(line: 10).description == "multi-document streams are not supported at line 10")
|
|
#expect(PureYAML.Parsing.ParseError.expectedNode(line: 10, column: 2).description == "expected a YAML node at line 10, column 2")
|
|
#expect(PureYAML.Parsing.ParseError.expectedScalarKey(line: 11, column: 3).description == "expected a scalar mapping key at line 11, column 3")
|
|
#expect(PureYAML.Parsing.ParseError.undefinedAlias(anchor: "item", line: 12, column: 4).description == "undefined alias 'item' at line 12, column 4")
|
|
#expect(PureYAML.Parsing.ParseError.invalidTaggedScalar(
|
|
tag: "tag:yaml.org,2002:int",
|
|
value: "nope",
|
|
line: 13,
|
|
column: 5,
|
|
).description == "invalid scalar 'nope' for tag 'tag:yaml.org,2002:int' at line 13, column 5")
|
|
#expect(PureYAML.Parsing.ParseError.unexpectedEvent(
|
|
expected: "mapping key",
|
|
actual: "sequenceStart anchor=- tag=- style=flow @1:1@0",
|
|
line: 13,
|
|
column: 5,
|
|
).description == "expected mapping key at line 13, column 5, found sequenceStart anchor=- tag=- style=flow @1:1@0")
|
|
#expect(PureYAML.Parsing.ParseError.unexpectedToken(
|
|
expected: "stream end",
|
|
actual: "mappingKey",
|
|
line: 14,
|
|
column: 1,
|
|
).description == "expected stream end at line 14, column 1, found mappingKey")
|
|
}
|
|
}
|