PureYAML/Sources/Parsing/Scanner.PropertyTokens.swift
Mihaela Mihaljevic 16da7248c7 fix(parsing): complete flow-collection key and empty-node handling
Implement libyaml's simple-key mechanism for flow context: the scanner records a potential key position before a plain or quoted scalar, an anchor/tag, or a [/{ opener, and a later ':' splices a mappingKey token retroactively in front of that node. This lets a quoted scalar ("k":v), a nested collection ({k}:v), or a property-carrying node (&c c: d) become a flow mapping key. In flow context ':' is always a value indicator and '?' always an explicit-key indicator regardless of the next character (the block-only following-blank lookahead is disabled), so adjacent forms and trailing '?' parse, and multi-line plain keys fold before the ':' is read. The flow parser is rebuilt around the flow_sequence_entry / flow_mapping_key / flow_mapping_value productions, composing empty scalars for absent keys and values. The streaming token cursor now buffers an open flow collection's tokens until it closes so the retroactive key splice stays valid.

Flips 10 official YAML test suite cases (348/402): 4FJ6, 8KB6, 9BXH, 9MMW, CN3R, DFF7, FRK4, NJ66, UT92, CT4Q. Flow collections used as block mapping keys (LX3P, 6BFJ) need the splice in block context and stay tracked. Updates manifest, fixtures, counts, and roadmap.
2026-06-23 23:18:59 +02:00

57 lines
2.3 KiB
Swift

extension PureYAML.Parsing.Scanner {
func scanNamedToken(
kind: (String) -> PureYAML.Parsing.TokenKind,
missingNameError: (Int) -> PureYAML.Parsing.ParseError,
state: inout State,
) throws {
let start = state.reader.mark
// An anchor or alias may itself begin a flow simple key, so record the
// position before the token is appended; the following node then does not
// start a new key, leaving `&anchor key :` anchored at the `&` (libyaml's
// fetch_anchor saves the simple key before clearing simple_key_allowed).
saveFlowSimpleKey(&state)
state.reader.advance()
// An anchor or alias name is a run of name characters that ends at the
// first non-name character, mirroring `yaml_parser_scan_anchor`: the
// name must stop at whitespace, `:`, `?`, flow indicators, and the other
// indicator characters so that `*alias:` reads the alias `alias`
// followed by a `:` mapping indicator rather than swallowing the colon.
let name = state.reader.consume { character in
isAnchorNameCharacter(character)
}
guard !name.isEmpty else {
throw missingNameError(start.line)
}
state.append(kind(name), mark: start, endMark: state.reader.mark)
}
func scanTag(_ state: inout State) throws {
let start = state.reader.mark
// A tag may itself begin a flow simple key (see `scanNamedToken`).
saveFlowSimpleKey(&state)
state.reader.advance()
var value = "!"
if state.reader.peek() == "<" {
value.append(state.reader.advance() ?? "<")
var didClose = false
while let character = state.reader.peek() {
value.append(character)
state.reader.advance()
if character == ">" {
didClose = true
break
}
}
guard didClose else {
throw PureYAML.Parsing.ParseError.unterminatedTag(line: start.line)
}
} else {
value += state.reader.consume { character in
!isTokenTerminator(character)
}
value = expandTag(value, state: state)
}
state.append(.tag(value), mark: start, endMark: state.reader.mark)
}
}