A mapping key is a full node: it may carry &anchor and !tag properties and may itself be an *alias. Fixes: (1) anchor/alias name scanning now stops at the first non-name character per yaml_parser_scan_anchor, so *a: reads as alias 'a' plus a mapping indicator instead of name 'a:'; (2) the block-mapping parser enters on a leading node property or alias, attaches same-line key properties to the key node, and emits alias keys directly; (3) an anchor or tag at value position with no same-line node composes an empty (null) value carrying the property, so 'a: &anchor' registers the anchor; (4) the composer registers an anchor's resolved model value (an empty anchored scalar registers null); (5) implicit keys with trailing spaces before the colon are recognized. Flips 10 official YAML test suite cases to passing (280/402), including G9HC now correctly rejected. 2SXE no longer false-rejects but still value-mismatches on compact-mapping interpretation (a separate gap), so it is reclassified in the manifest rather than removed. Updates the manifest, fixtures, counts, and roadmap.
243 lines
7.6 KiB
Swift
243 lines
7.6 KiB
Swift
extension PureYAML.Parsing {
|
|
struct EventComposer {
|
|
var events: [Event]
|
|
var index: Int
|
|
var anchors: [String: PureYAML.Model.Value]
|
|
let scalarParser: Parser
|
|
|
|
init(
|
|
events: [Event],
|
|
scalarParser: Parser,
|
|
) {
|
|
self.events = events
|
|
index = 0
|
|
anchors = [:]
|
|
self.scalarParser = scalarParser
|
|
}
|
|
|
|
mutating func compose() throws -> PureYAML.Model.Value {
|
|
_ = try expect("stream start") { event in
|
|
if case .streamStart = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
_ = try expect("document start") { event in
|
|
if case .documentStart = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
let value = try composeNode()
|
|
_ = try expect("document end") { event in
|
|
if case .documentEnd = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
if case let .documentStart(mark)? = current {
|
|
throw PureYAML.Parsing.ParseError.unsupportedMultiDocumentStream(line: mark.line)
|
|
}
|
|
_ = try expectStreamEnd()
|
|
return value
|
|
}
|
|
|
|
mutating func composeStream() throws -> [PureYAML.Stream.Document] {
|
|
_ = try expect("stream start") { event in
|
|
if case .streamStart = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
var documents: [PureYAML.Stream.Document] = []
|
|
while current?.isStreamEnd != true {
|
|
_ = try expect("document start") { event in
|
|
if case .documentStart = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
anchors = [:]
|
|
let value = try composeNode()
|
|
_ = try expect("document end") { event in
|
|
if case .documentEnd = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
documents.append(.init(index: documents.count, value: value))
|
|
}
|
|
|
|
_ = try expectStreamEnd()
|
|
return documents
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PureYAML.Parsing.EventComposer {
|
|
mutating func composeNode() throws -> PureYAML.Model.Value {
|
|
guard let event = current else {
|
|
throw unexpectedEvent(expected: "YAML node")
|
|
}
|
|
|
|
switch event {
|
|
case let .alias(anchor, mark):
|
|
index += 1
|
|
guard let value = anchors[anchor] else {
|
|
throw PureYAML.Parsing.ParseError.undefinedAlias(
|
|
anchor: anchor,
|
|
line: mark.line,
|
|
column: mark.column,
|
|
)
|
|
}
|
|
return value
|
|
case let .mappingStart(anchor, _, _, _):
|
|
let value = try composeMapping()
|
|
store(value, anchor: anchor)
|
|
return value
|
|
case let .scalar(value, anchor, tag, style, mark):
|
|
index += 1
|
|
let model = try composeScalar(
|
|
value,
|
|
tag: tag,
|
|
style: style,
|
|
mark: mark,
|
|
)
|
|
store(model, anchor: anchor)
|
|
return model
|
|
case let .sequenceStart(anchor, _, _, _):
|
|
let value = try composeSequence()
|
|
store(value, anchor: anchor)
|
|
return value
|
|
default:
|
|
throw unexpectedEvent(expected: "YAML node")
|
|
}
|
|
}
|
|
|
|
mutating func composeSequence() throws -> PureYAML.Model.Value {
|
|
_ = try expect("sequence start") { event in
|
|
if case .sequenceStart = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
var values: [PureYAML.Model.Value] = []
|
|
while current?.isSequenceEnd != true {
|
|
try values.append(composeNode())
|
|
}
|
|
_ = try expect("sequence end") { $0.isSequenceEnd }
|
|
return .sequence(values)
|
|
}
|
|
|
|
mutating func composeMapping() throws -> PureYAML.Model.Value {
|
|
_ = try expect("mapping start") { event in
|
|
if case .mappingStart = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
var mergeSources: [[PureYAML.Model.Pair]] = []
|
|
var localPairs: [PureYAML.Model.Pair] = []
|
|
while current?.isMappingEnd != true {
|
|
let key = try composeMappingKey()
|
|
if key.isMergeKey {
|
|
try mergeSources.append(contentsOf: composeMergeSources())
|
|
} else {
|
|
let value = try composeNode()
|
|
localPairs.append(PureYAML.Model.Pair(keyNode: key.keyNode, value: value))
|
|
}
|
|
}
|
|
_ = try expect("mapping end") { $0.isMappingEnd }
|
|
let pairs = mergedPairs(from: mergeSources, localPairs: localPairs)
|
|
return .mapping(PureYAML.Model.Mapping(pairs))
|
|
}
|
|
|
|
mutating func composeMappingKey() throws -> ComposedMappingKey {
|
|
guard let event = current else {
|
|
throw unexpectedEvent(expected: "mapping key")
|
|
}
|
|
guard case let .scalar(value, anchor, tag, style, _) = event else {
|
|
let value = try composeNode()
|
|
return ComposedMappingKey(
|
|
keyNode: PureYAML.Model.Key(value: value),
|
|
scalarValue: nil,
|
|
tag: nil,
|
|
style: nil,
|
|
)
|
|
}
|
|
index += 1
|
|
if let anchor {
|
|
let model = try composeScalar(value, tag: tag, style: style, mark: .start)
|
|
store(model, anchor: anchor)
|
|
}
|
|
return ComposedMappingKey(
|
|
keyNode: .string(value),
|
|
scalarValue: value,
|
|
tag: scalarParser.normalizedTag(tag),
|
|
style: style,
|
|
)
|
|
}
|
|
|
|
func composeScalar(
|
|
_ value: String,
|
|
tag: String?,
|
|
style: PureYAML.Parsing.ScalarStyle,
|
|
mark: PureYAML.Parsing.Mark,
|
|
) throws -> PureYAML.Model.Value {
|
|
try scalarParser.composeScalarValue(value, tag: tag, style: style, mark: mark)
|
|
}
|
|
}
|
|
|
|
extension PureYAML.Parsing.EventComposer {
|
|
var current: PureYAML.Parsing.Event? {
|
|
guard events.indices.contains(index) else {
|
|
return nil
|
|
}
|
|
return events[index]
|
|
}
|
|
|
|
mutating func store(
|
|
_ value: PureYAML.Model.Value,
|
|
anchor: String?,
|
|
) {
|
|
guard let anchor else {
|
|
return
|
|
}
|
|
anchors[anchor] = value
|
|
}
|
|
|
|
mutating func expect(
|
|
_ expected: String,
|
|
matching: (PureYAML.Parsing.Event) -> Bool,
|
|
) throws -> PureYAML.Parsing.Event {
|
|
guard let event = current else {
|
|
throw unexpectedEvent(expected: expected)
|
|
}
|
|
guard matching(event) else {
|
|
throw unexpectedEvent(expected: expected)
|
|
}
|
|
index += 1
|
|
return event
|
|
}
|
|
|
|
func unexpectedEvent(expected: String) -> PureYAML.Parsing.ParseError {
|
|
let mark = current?.mark ?? events.last?.mark ?? .start
|
|
return PureYAML.Parsing.ParseError.unexpectedEvent(
|
|
expected: expected,
|
|
actual: current?.description ?? "end of events",
|
|
line: mark.line,
|
|
column: mark.column,
|
|
)
|
|
}
|
|
|
|
mutating func expectStreamEnd() throws -> PureYAML.Parsing.Event {
|
|
try expect("stream end") { event in
|
|
if case .streamEnd = event {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
}
|