extension PureYAML.Parsing.TokenEventParser { /// Parses the key portion of a block-mapping pair. An explicit `?` key may /// have no following node (an empty key), and a pair written as plain /// `: value` has no `?` and no key node at all. Both compose to an empty /// scalar key, mirroring libyaml's empty-scalar production for an absent key. mutating func parseMappingPairKey( preconsumedProperties: PureYAML.Parsing.NodeProperties = .none, ) throws { // A key node may carry leading anchor/tag properties, which the scanner // emits before the implicit `mappingKey` indicator (for example // `&anchor c: 3` scans as anchor, `?`, `c`). It may also itself be an // alias (`*b : value`), which has no `mappingKey` indicator at all. // `preconsumedProperties` are properties the caller already consumed // because they sat on the same line as the first key of this mapping. var leadingProperties = preconsumedProperties try leadingProperties.mergeUnset(from: consumeProperties()) if leadingProperties != .none { try parseKeyWithLeadingProperties(leadingProperties) return } if cursor.current?.kind.isAlias == true { try emitAliasKey() return } guard cursor.current?.kind.isMappingKey == true else { let mark = cursor.current?.mark ?? cursor.previous?.endMark ?? .start try emitEmptyScalar(properties: .none, mark: mark) return } let keyIndicator = try expect("mapping key") { $0.isMappingKey } let properties = try consumeProperties() // An alias as an explicit `? *alias` key is the key node itself; emit it // directly so the following `:` is read as this pair's value separator // rather than re-routing the alias into a nested block mapping. if properties == .none, cursor.current?.kind.isAlias == true { try emitAliasKey() return } if properties != .none || canBeginNode(cursor.current) { _ = try parseNode(properties: properties) return } try emitEmptyScalar(properties: properties, mark: keyIndicator.endMark) } /// Consumes an alias token that stands in mapping-key position and emits the /// alias event for it. Done here rather than through ``parseNode`` because an /// alias followed by `:` routes back into a block mapping in ``parseNode``; /// the key parser owns the alias key directly so it resolves to its value. mutating func emitAliasKey() throws { let token = try expect("alias") { $0.isAlias } guard case let .alias(name) = token.kind else { throw unexpectedToken(expected: "alias") } try emit(.alias(anchor: name, mark: token.mark)) } /// Parses a key node whose anchor/tag properties were already consumed. /// The scanner emits properties before the implicit `mappingKey` indicator, /// so the indicator (if present) is skipped here and the remaining content, /// or an empty scalar, carries the properties. mutating func parseKeyWithLeadingProperties( _ properties: PureYAML.Parsing.NodeProperties, ) throws { if cursor.current?.kind.isMappingKey == true { let keyIndicator = try expect("mapping key") { $0.isMappingKey } if canBeginNode(cursor.current), hasNodeOnSameLine(after: keyIndicator) { _ = try parseNode(properties: properties) return } try emitEmptyScalar(properties: properties, mark: keyIndicator.endMark) return } if canBeginNode(cursor.current) { _ = try parseNode(properties: properties) return } let mark = properties.mark ?? cursor.current?.mark ?? cursor.previous?.endMark ?? .start try emitEmptyScalar(properties: properties, mark: mark) } /// Emits an empty (null) plain scalar carrying any pending anchor or tag. /// This is the single composition point for an empty node, so `!!null`, /// `!!str`, an anchored empty scalar, and a bare absent node all resolve /// through the normal scalar resolver against an empty value. mutating func emitEmptyScalar( properties: PureYAML.Parsing.NodeProperties, mark: PureYAML.Parsing.Mark, ) throws { try emit(.scalar( value: "", anchor: properties.anchor, tag: properties.tag, style: .plain, mark: properties.mark ?? mark, )) } /// Emits an empty scalar when a node is grammatically required but the next /// token cannot begin one. An anchor or tag makes the empty node explicit; /// without either, the absence is a genuine error, matching libyaml's /// "did not find expected node content". mutating func emptyScalarOrThrow( properties: PureYAML.Parsing.NodeProperties, ) throws -> PureYAML.Parsing.NodeResult { let mark = properties.mark ?? cursor.current?.mark ?? cursor.previous?.endMark ?? .start guard properties.anchor != nil || properties.tag != nil else { throw PureYAML.Parsing.ParseError.expectedNode(line: mark.line, column: mark.column) } try emitEmptyScalar(properties: properties, mark: mark) return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: mark) } /// True when `token` can begin a YAML node (scalar, alias, or a collection /// opener). Properties (anchor/tag) are consumed before this is asked. func canBeginNode(_ token: PureYAML.Parsing.Token?) -> Bool { guard let token else { return false } switch token.kind { case .scalar, .alias, .blockScalarHeader, .blockEntry, .flowMappingStart, .flowSequenceStart, .indent: return true default: return false } } /// Parses a block node introduced by a leading indent token, carrying the /// already-consumed node properties through to the indented content. Used /// when an anchor or tag precedes an indented block collection on the next /// line, e.g. `key: &anchor` then an indented `!!map` mapping. mutating func parseIndentedNode( properties: PureYAML.Parsing.NodeProperties, ) throws -> PureYAML.Parsing.NodeResult { _ = try expect("indent") { $0.isIndent } let result = try parseNode(properties: properties) if cursor.current?.kind.isDedent == true { _ = try expect("dedent") { $0.isDedent } } return result } }