PureYAML/Sources/Parsing/Parser.Events.swift
Mihaela Mihaljevic 3f3ce310b1 feat(parsing): opt-in located parse with source spans on every node
Add parseLocated / parseStreamLocated, returning a Model.LocatedValue
tree parallel in shape to Value, where every node carries its source
span (1-based line/column) and located children. Collections span from
their opening to closing event; scalars carry a start-anchored span;
aliases and merge-inherited pairs honestly point at their reference site.

The plain Value, its equality, and the conformance oracle are untouched:
locations are built only when the streaming composer is asked for them
(tracksLocations), behind new entry points, so the default value path is
byte-identical. A whole-suite test proves the located projection equals
a plain parse for every passing case (402/402), including through merge
keys and aliases, and the canonical-event and gap gates stay green.
2026-06-26 00:57:55 +02:00

92 lines
3.4 KiB
Swift

extension PureYAML.Parsing.Parser {
func parseEvents(_ yaml: String) throws -> [PureYAML.Parsing.Event] {
let events: [PureYAML.Parsing.Event]
do {
var parser = try PureYAML.Parsing.LibYAML.Parser(yaml)
events = try parser.parse().map { $0.bumpingMark() }
} catch let error as PureYAML.Parsing.LibYAML.ScannerError {
throw PureYAML.Parsing.ParseError(translating: error)
} catch let error as PureYAML.Parsing.LibYAML.ParserError {
throw PureYAML.Parsing.ParseError(translating: error)
}
try enforceNestingLimit(events)
return events
}
/// The deepest collection nesting the value/event composers will build. The
/// ported parser produces events iteratively without recursion, but the
/// composers, equality, hashing, and `Model/Value` teardown all recurse with
/// the nesting depth, so the event stream is bounded here before any recursive
/// consumer runs. 64 accepts every realistic document yet rejects pathological
/// input well before it can overflow the stack.
static var maxNestingDepth: Int {
64
}
/// Reject an event stream that nests collections past ``maxNestingDepth`` with
/// a ``ParseError/nestingTooDeep(line:column:)`` so deeply nested input fails
/// cleanly instead of overflowing a recursive consumer's stack.
private func enforceNestingLimit(_ events: [PureYAML.Parsing.Event]) throws {
var depth = 0
for event in events {
switch event {
case .sequenceStart, .mappingStart:
depth += 1
if depth > Self.maxNestingDepth {
throw PureYAML.Parsing.ParseError.nestingTooDeep(line: event.mark.line, column: event.mark.column)
}
case .sequenceEnd, .mappingEnd:
depth -= 1
default:
break
}
}
}
func parseStreaming(_ yaml: String) throws -> PureYAML.Model.Value {
var composer = PureYAML.Parsing.EventStreamComposer(
scalarParser: self,
mode: .singleDocument,
)
for event in try parseEvents(yaml) {
try composer.consume(event)
}
return try composer.composedValue()
}
func parseStreamStreaming(_ yaml: String) throws -> [PureYAML.Stream.Document] {
var composer = PureYAML.Parsing.EventStreamComposer(
scalarParser: self,
mode: .stream,
)
for event in try parseEvents(yaml) {
try composer.consume(event)
}
return try composer.composedStream()
}
func parseLocatedStreaming(_ yaml: String) throws -> PureYAML.Model.LocatedValue {
var composer = PureYAML.Parsing.EventStreamComposer(
scalarParser: self,
mode: .singleDocument,
tracksLocations: true,
)
for event in try parseEvents(yaml) {
try composer.consume(event)
}
return try composer.composedLocated()
}
func parseStreamLocatedStreaming(_ yaml: String) throws -> [PureYAML.Model.LocatedValue] {
var composer = PureYAML.Parsing.EventStreamComposer(
scalarParser: self,
mode: .stream,
tracksLocations: true,
)
for event in try parseEvents(yaml) {
try composer.consume(event)
}
return try composer.composedLocatedStream()
}
}