PureYAML/Sources/Parsing/EventStreamComposer.swift
Mihaela M c4f7f9dccc refactor(code-style): one top-level type per source file
Split the six files that declared multiple sibling types under one
namespace extension so each file declares exactly one top-level type:
the Tagged node component types (Node, NodeKind, Scalar, Sequence,
Mapping, Pair), the validation report types (Report, Source,
SourceReport, BatchReport), DiagnosticKind, TokenKind, ScannerFlowContext
kind, and the event-stream composer helpers. Public type names are
unchanged; the shared report-rendering helpers move from file-private to
internal so the split report types still reach them.

Make check-namespacing.sh namespace-aware: the previous column-anchored
scan was blind to types declared inside indented extension blocks, so it
passed while the rule was violated. It now counts direct members of a
PureYAML namespace extension and ignores nested types and private helpers.
2026-06-26 06:14:21 +02:00

256 lines
8.9 KiB
Swift

extension PureYAML.Parsing {
struct EventStreamComposer {
let scalarParser: Parser
let mode: EventStreamComposerMode
var anchors: [String: PureYAML.Model.Value] = [:]
var containers: [EventStreamContainer] = []
var documentValues: [PureYAML.Model.Value] = []
var currentDocumentValue: PureYAML.Model.Value?
var didSeeStreamStart = false
var didSeeStreamEnd = false
init(
scalarParser: Parser,
mode: EventStreamComposerMode,
) {
self.scalarParser = scalarParser
self.mode = mode
}
mutating func consume(_ event: PureYAML.Parsing.Event) throws {
switch event {
case .streamStart:
didSeeStreamStart = true
case .streamEnd:
didSeeStreamEnd = true
case let .documentStart(mark):
try startDocument(mark: mark)
case let .documentEnd(mark):
try endDocument(mark: mark)
case let .scalar(value, anchor, tag, style, mark):
try consumeScalar(value, anchor: anchor, tag: tag, style: style, mark: mark)
case let .alias(anchor, mark):
try consumeAlias(anchor, mark: mark)
case let .sequenceStart(anchor, _, _, mark):
containers.append(.init(kind: .sequence, anchor: anchor, mark: mark))
case let .sequenceEnd(mark):
try completeSequence(mark: mark)
case let .mappingStart(anchor, _, _, mark):
containers.append(.init(kind: .mapping, anchor: anchor, mark: mark))
case let .mappingEnd(mark):
try completeMapping(mark: mark)
}
}
mutating func composedValue() throws -> PureYAML.Model.Value {
guard didSeeStreamStart, didSeeStreamEnd else {
throw unexpectedEvent(expected: "complete YAML stream", mark: .start)
}
guard documentValues.count == 1, let value = documentValues.first else {
throw unexpectedEvent(expected: "single YAML document", mark: .start)
}
return value
}
mutating func composedStream() throws -> [PureYAML.Stream.Document] {
guard didSeeStreamStart, didSeeStreamEnd else {
throw unexpectedEvent(expected: "complete YAML stream", mark: .start)
}
return documentValues.enumerated().map { index, value in
PureYAML.Stream.Document(index: index, value: value)
}
}
}
}
private extension PureYAML.Parsing.EventStreamComposer {
mutating func startDocument(mark: PureYAML.Parsing.Mark) throws {
guard containers.isEmpty else {
throw unexpectedEvent(expected: "document content end", mark: mark)
}
if mode == .singleDocument, !documentValues.isEmpty {
throw PureYAML.Parsing.ParseError.unsupportedMultiDocumentStream(line: mark.line)
}
anchors = [:]
currentDocumentValue = nil
}
mutating func endDocument(mark: PureYAML.Parsing.Mark) throws {
guard containers.isEmpty else {
throw unexpectedEvent(expected: "document content end", mark: mark)
}
documentValues.append(currentDocumentValue ?? .null)
currentDocumentValue = nil
}
mutating func consumeScalar(
_ value: String,
anchor: String?,
tag: String?,
style: PureYAML.Parsing.ScalarStyle,
mark: PureYAML.Parsing.Mark,
) throws {
let model = try scalarParser.composeScalarValue(value, tag: tag, style: style, mark: mark)
if let anchor {
anchors[anchor] = .string(value)
}
try complete(.init(
value: model,
mappingKey: .init(
keyNode: .string(value),
scalarValue: value,
tag: scalarParser.normalizedTag(tag),
style: style,
),
mark: mark,
))
}
mutating func consumeAlias(
_ anchor: String,
mark: PureYAML.Parsing.Mark,
) throws {
guard let value = anchors[anchor] else {
throw PureYAML.Parsing.ParseError.undefinedAlias(
anchor: anchor,
line: mark.line,
column: mark.column,
)
}
try complete(.init(
value: value,
mappingKey: .init(
keyNode: PureYAML.Model.Key(value: value),
scalarValue: nil,
tag: nil,
style: nil,
),
mark: mark,
))
}
mutating func completeSequence(mark: PureYAML.Parsing.Mark) throws {
guard let container = containers.popLast(), container.kind == .sequence else {
throw unexpectedEvent(expected: "sequence start", mark: mark)
}
let value = PureYAML.Model.Value.sequence(container.values.map(\.value))
store(value, anchor: container.anchor)
try complete(.init(
value: value,
mappingKey: .init(
keyNode: PureYAML.Model.Key(value: value),
scalarValue: nil,
tag: nil,
style: nil,
),
mark: container.mark,
sequenceItems: container.values,
))
}
mutating func completeMapping(mark: PureYAML.Parsing.Mark) throws {
guard let container = containers.popLast(), container.kind == .mapping else {
throw unexpectedEvent(expected: "mapping start", mark: mark)
}
guard container.pendingKey == nil else {
throw unexpectedEvent(expected: "mapping value", mark: mark)
}
let pairs = PureYAML.Parsing.MergeResolution.mergedPairs(
from: container.mergeSources,
localPairs: container.localPairs,
)
let value = PureYAML.Model.Value.mapping(.init(pairs))
store(value, anchor: container.anchor)
try complete(.init(
value: value,
mappingKey: .init(
keyNode: PureYAML.Model.Key(value: value),
scalarValue: nil,
tag: nil,
style: nil,
),
mark: container.mark,
))
}
mutating func complete(_ node: PureYAML.Parsing.EventStreamComposedNode) throws {
guard !containers.isEmpty else {
currentDocumentValue = node.value
return
}
switch containers[containers.count - 1].kind {
case .sequence:
containers[containers.count - 1].values.append(node)
case .mapping:
try completeMappingNode(node)
}
}
mutating func completeMappingNode(_ node: PureYAML.Parsing.EventStreamComposedNode) throws {
let index = containers.count - 1
guard let key = containers[index].pendingKey else {
containers[index].pendingKey = node.mappingKey
return
}
if key.isMergeKey {
try containers[index].mergeSources.append(contentsOf: mergeSources(
from: node,
))
} else {
containers[index].localPairs.append(.init(keyNode: key.keyNode, value: node.value))
}
containers[index].pendingKey = nil
}
mutating func store(
_ value: PureYAML.Model.Value,
anchor: String?,
) {
guard let anchor else {
return
}
anchors[anchor] = value
}
func mergeSources(
from node: PureYAML.Parsing.EventStreamComposedNode,
) throws -> [[PureYAML.Model.Pair]] {
switch node.value {
case let .mapping(mapping):
return [mapping.pairs]
case let .sequence(values):
guard !node.sequenceItems.isEmpty else {
return try values.map { value in
guard case let .mapping(mapping) = value else {
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: node.mark)
}
return mapping.pairs
}
}
return try values.enumerated().map { index, value in
let mark = node.sequenceItems[index].mark
guard case let .mapping(mapping) = value else {
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: mark)
}
return mapping.pairs
}
case .bool, .double, .int, .null, .string:
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: node.mark)
}
}
func unexpectedEvent(
expected: String,
mark: PureYAML.Parsing.Mark,
) -> PureYAML.Parsing.ParseError {
PureYAML.Parsing.ParseError.unexpectedEvent(
expected: expected,
actual: "streaming event",
line: mark.line,
column: mark.column,
)
}
}