PureYAML/Sources/Parsing/Event.swift
Mihaela Mihaljevic 561921df42 feat(parsing): route the event API through the ported libyaml engine
parseEvents now runs on the ported scanner+parser instead of the
hand-rolled approximation. Adds 1-based mark conversion at the boundary
(the port stays faithfully 0-based internally), translates the port's
scanner and parser errors into the diagnostic-aware ParseError with
precise codes (unterminatedQuotedString, unterminatedTag, tabIndentation)
and a readable malformed fallback, and retargets the canonical event gate
to track the port's gap set.

The port matches the canonical test.event on 253 of 308 valid cases;
the remaining gaps match libyaml's own behaviour and are the targets for
surpassing libyaml. Golden event and diagnostic expectations are rebased
onto the port where it is faithful or more correct (resolved verbatim
tags, libyaml's stream-end marks, reaching the real unterminated quote).
333 tests pass.
2026-06-24 17:32:29 +02:00

134 lines
4.8 KiB
Swift

extension PureYAML.Parsing {
enum Event: Equatable, CustomStringConvertible {
case streamStart(mark: Mark)
case streamEnd(mark: Mark)
case documentStart(explicit: Bool, mark: Mark)
case documentEnd(explicit: Bool, mark: Mark)
case sequenceStart(anchor: String?, tag: String?, style: CollectionStyle, mark: Mark)
case sequenceEnd(mark: Mark)
case mappingStart(anchor: String?, tag: String?, style: CollectionStyle, mark: Mark)
case mappingEnd(mark: Mark)
case scalar(value: String, anchor: String?, tag: String?, style: ScalarStyle, mark: Mark)
case alias(anchor: String, mark: Mark)
var description: String {
switch self {
case let .streamStart(mark):
"streamStart @\(mark)"
case let .streamEnd(mark):
"streamEnd @\(mark)"
case let .documentStart(explicit, mark):
"documentStart explicit=\(explicit) @\(mark)"
case let .documentEnd(explicit, mark):
"documentEnd explicit=\(explicit) @\(mark)"
case let .sequenceStart(anchor, tag, style, mark):
"sequenceStart anchor=\(describe(anchor)) tag=\(describe(tag)) style=\(style) @\(mark)"
case let .sequenceEnd(mark):
"sequenceEnd @\(mark)"
case let .mappingStart(anchor, tag, style, mark):
"mappingStart anchor=\(describe(anchor)) tag=\(describe(tag)) style=\(style) @\(mark)"
case let .mappingEnd(mark):
"mappingEnd @\(mark)"
case let .scalar(value, anchor, tag, style, mark):
"scalar value=\"\(escape(value))\" anchor=\(describe(anchor)) tag=\(describe(tag)) style=\(style) @\(mark)"
case let .alias(anchor, mark):
"alias anchor=\(anchor) @\(mark)"
}
}
}
}
extension PureYAML.Parsing.Event {
/// Re-emit the event with libyaml's 0-based line/column mark converted to the
/// package's 1-based convention (the byte index is already 0-based in both).
func bumpingMark() -> Self {
let source = mark
let bumped = PureYAML.Parsing.Mark(line: source.line + 1, column: source.column + 1, index: source.index)
switch self {
case .streamStart:
return .streamStart(mark: bumped)
case .streamEnd:
return .streamEnd(mark: bumped)
case let .documentStart(explicit, _):
return .documentStart(explicit: explicit, mark: bumped)
case let .documentEnd(explicit, _):
return .documentEnd(explicit: explicit, mark: bumped)
case let .sequenceStart(anchor, tag, style, _):
return .sequenceStart(anchor: anchor, tag: tag, style: style, mark: bumped)
case .sequenceEnd:
return .sequenceEnd(mark: bumped)
case let .mappingStart(anchor, tag, style, _):
return .mappingStart(anchor: anchor, tag: tag, style: style, mark: bumped)
case .mappingEnd:
return .mappingEnd(mark: bumped)
case let .scalar(value, anchor, tag, style, _):
return .scalar(value: value, anchor: anchor, tag: tag, style: style, mark: bumped)
case let .alias(anchor, _):
return .alias(anchor: anchor, mark: bumped)
}
}
var isMappingEnd: Bool {
if case .mappingEnd = self {
return true
}
return false
}
var isSequenceEnd: Bool {
if case .sequenceEnd = self {
return true
}
return false
}
var isStreamEnd: Bool {
if case .streamEnd = self {
return true
}
return false
}
var mark: PureYAML.Parsing.Mark {
switch self {
case let .alias(_, mark),
let .mappingEnd(mark),
let .mappingStart(_, _, _, mark),
let .scalar(_, _, _, _, mark),
let .sequenceEnd(mark),
let .sequenceStart(_, _, _, mark),
let .streamEnd(mark),
let .streamStart(mark):
mark
case let .documentEnd(_, mark),
let .documentStart(_, mark):
mark
}
}
private func describe(_ value: String?) -> String {
value ?? "-"
}
private func escape(_ value: String) -> String {
var output = ""
for character in value {
switch character {
case "\\":
output += "\\\\"
case "\"":
output += "\\\""
case "\n":
output += "\\n"
case "\r":
output += "\\r"
case "\t":
output += "\\t"
default:
output.append(character)
}
}
return output
}
}