extension PureYAML.Parsing { struct TokenEventParser { var cursor: TokenCursor let scalarParser: Parser var events: [Event]? var eventSink: ((Event) throws -> Void)? /// Current node recursion depth, tracked so deeply nested input fails /// with a diagnostic instead of overflowing the stack. var depth = 0 /// Maximum node nesting the recursive-descent parser will follow. /// Real-world specs nest well under this; the bound exists so a /// pathologically nested document (e.g. hundreds of `[[[...]]]`) /// throws ``ParseError/nestingTooDeep(line:column:)`` rather than /// crashing the process. static let maxNestingDepth = 64 init( tokens: [Token], scalarParser: Parser, ) { cursor = TokenCursor(tokens: tokens) self.scalarParser = scalarParser events = [] eventSink = nil } mutating func parse() throws -> [Event] { try parseEvents() return events ?? [] } init( tokens: [Token], scalarParser: Parser, eventSink: @escaping (Event) throws -> Void, ) { cursor = TokenCursor(tokens: tokens) self.scalarParser = scalarParser events = nil self.eventSink = eventSink } init( yaml: String, scalarParser: Parser, eventSink: @escaping (Event) throws -> Void, ) { cursor = TokenCursor(yaml: yaml) self.scalarParser = scalarParser events = nil self.eventSink = eventSink } mutating func parseStreaming() throws { try parseEvents() } mutating func parseEvents() throws { let streamStart = try expect("stream start") { kind in if case .streamStart = kind { return true } return false } try emit(.streamStart(mark: streamStart.mark)) var didParseDocument = false while cursor.current?.kind.isStreamEnd != true { try parseDocument() didParseDocument = true } if !didParseDocument { let mark = cursor.current?.mark ?? streamStart.endMark try emit(.documentStart(mark: mark)) try appendEmptyDocument(at: mark) try emit(.documentEnd(mark: mark)) } let streamEnd = try expect("stream end") { kind in if case .streamEnd = kind { return true } return false } try emit(.streamEnd(mark: streamEnd.mark)) } mutating func emit(_ event: Event) throws { if let eventSink { try eventSink(event) } else { events?.append(event) } } } } extension PureYAML.Parsing.TokenEventParser { mutating func parseDocument() throws { let startMark = try parseDocumentStartMark() try emit(.documentStart(mark: startMark)) if cursor.current?.kind.isDocumentEnd == true { let end = try expect("document end") { $0.isDocumentEnd } try appendEmptyDocument(at: end.mark) try emit(.documentEnd(mark: end.endMark)) return } if cursor.current?.kind.isDocumentStart == true || cursor.current?.kind.isStreamEnd == true { try appendEmptyDocument(at: cursor.current?.mark ?? startMark) try emit(.documentEnd(mark: cursor.current?.mark ?? startMark)) return } var endMark = try parseDocumentRootNode().endMark while cursor.current?.kind.isDedent == true { let dedent = try expect("dedent") { $0.isDedent } endMark = dedent.endMark } if cursor.current?.kind.isDocumentEnd == true { let end = try expect("document end") { $0.isDocumentEnd } endMark = end.endMark } if let current = cursor.current, !canEndDocument(at: current) { throw PureYAML.Parsing.ParseError.unexpectedToken( expected: "document boundary or stream end", actual: current.kind.description, line: current.mark.line, column: current.mark.column, ) } try emit(.documentEnd(mark: endMark)) } mutating func parseDocumentRootNode() throws -> PureYAML.Parsing.NodeResult { guard cursor.current?.kind.isIndent == true else { return try parseNode() } _ = try expect("indent") { $0.isIndent } let result = try parseNode() if cursor.current?.kind.isDedent == true { _ = try expect("dedent") { $0.isDedent } } return result } mutating func parseDocumentStartMark() throws -> PureYAML.Parsing.Mark { guard cursor.current?.kind.isDocumentStart == true else { return cursor.current?.mark ?? cursor.previous?.endMark ?? .start } let start = try cursor.advance() return start?.mark ?? .start } mutating func appendEmptyDocument(at mark: PureYAML.Parsing.Mark) throws { try emit(.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark)) } func canEndDocument(at token: PureYAML.Parsing.Token) -> Bool { token.kind.isDocumentStart || token.kind.isStreamEnd } mutating func parseNode() throws -> PureYAML.Parsing.NodeResult { let properties = try consumeProperties() return try parseNode(properties: properties) } /// Reject documents nested past ``maxNestingDepth`` with a diagnostic so a /// pathologically nested input fails cleanly instead of overflowing the /// recursive-descent stack. func enforceNestingLimit(properties: PureYAML.Parsing.NodeProperties) throws { guard depth > Self.maxNestingDepth else { return } let mark = properties.mark ?? cursor.current?.mark ?? cursor.previous?.endMark ?? .start throw PureYAML.Parsing.ParseError.nestingTooDeep(line: mark.line, column: mark.column) } mutating func parseNode( properties initialProperties: PureYAML.Parsing.NodeProperties, ) throws -> PureYAML.Parsing.NodeResult { depth += 1 defer { depth -= 1 } try enforceNestingLimit(properties: initialProperties) var properties = initialProperties try properties.mergeUnset(from: consumeProperties()) guard let token = cursor.current, !token.kind.isTerminator else { let mark = properties.mark ?? cursor.current?.mark ?? cursor.previous?.endMark ?? .start throw PureYAML.Parsing.ParseError.expectedNode(line: mark.line, column: mark.column) } switch token.kind { case let .alias(anchor): _ = try cursor.advance() let mark = properties.mark ?? token.mark try emit(.alias(anchor: anchor, mark: mark)) return PureYAML.Parsing.NodeResult(kind: .alias, endMark: token.endMark) case .blockEntry: return try parseBlockSequence(properties: properties) case .blockScalarHeader: return try parseBlockScalar(properties: properties) case .flowMappingStart: return try parseFlowMapping(properties: properties) case .flowSequenceStart: return try parseFlowSequence(properties: properties) case .mappingKey: return try parseBlockMapping( properties: properties, includeIndentedContinuation: false, ) case let .scalar(value, style): if try canParsePlainScalarContinuation() { return try parsePlainScalarWithContinuation(properties: properties) } _ = try cursor.advance() let decoded = try decodeScalar(value, style: style, line: token.mark.line) let mark = properties.mark ?? token.mark try emit(.scalar( value: decoded, anchor: properties.anchor, tag: properties.tag, style: style, mark: mark, )) return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: token.endMark) default: throw PureYAML.Parsing.ParseError.expectedNode(line: token.mark.line, column: token.mark.column) } } }