extension PureYAML.Parsing.LibYAML { /// A faithful port of libyaml's `parser.c`: a one-event-at-a-time state /// machine that consumes the scanner's token stream and produces the YAML /// event stream. The token queue is materialized up front by the scanner, so /// `peekToken`/`skipToken` index into a fixed array rather than pulling lazily, /// which is equivalent because simple-key resolution lives entirely in the /// scanner. struct Parser { let tokens: [Token] var cursor = 0 var state: ParserState = .streamStart var states: [ParserState] = [] var marks: [Mark] = [] var tagDirectives: [(handle: String, prefix: String)] = [] /// Whether a bare (implicit) document may begin here: true for the first /// document and after an explicit `...` end marker, false otherwise. YAML /// 1.2 allows a bare document to follow `...`, which libyaml rejects. var allowBareDocument = true init(_ input: String = "") throws { var scanner = Scanner(input) tokens = try scanner.scanAll() } init(tokens: [Token]) { self.tokens = tokens } /// Drive the state machine until STREAM-END, collecting every event. mutating func parse() throws -> [Event] { var events: [Event] = [] while state != .end { try events.append(stateMachine()) } return events } /// The head of the token queue. STREAM-END is always the final token and /// the machine halts once it has been consumed, so the clamp is never the /// value actually parsed; it only avoids an out-of-range index. func peekToken() -> Token { tokens[min(cursor, tokens.count - 1)] } mutating func skipToken() { cursor += 1 } /// Port of `yaml_parser_state_machine`: dispatch on the current state. The /// flat 24-way switch is split into document/block/flow groups to keep each /// dispatcher's branching within the complexity budget. mutating func stateMachine() throws -> Event { switch state { case .streamStart, .implicitDocumentStart, .documentStart, .documentContent, .documentEnd: try documentStateMachine() case .blockNode, .blockNodeOrIndentlessSequence, .flowNode, .blockSequenceFirstEntry, .blockSequenceEntry, .indentlessSequenceEntry, .blockMappingFirstKey, .blockMappingKey, .blockMappingValue: try nodeStateMachine() case .flowSequenceFirstEntry, .flowSequenceEntry, .flowSequenceEntryMappingKey, .flowSequenceEntryMappingValue, .flowSequenceEntryMappingEnd, .flowMappingFirstKey, .flowMappingKey, .flowMappingValue, .flowMappingEmptyValue: try flowStateMachine() case .end: throw endError() } } private mutating func documentStateMachine() throws -> Event { switch state { case .streamStart: try parseStreamStart() case .implicitDocumentStart: try parseDocumentStart() case .documentStart: try parseDocumentStart() case .documentContent: try parseDocumentContent() default: try parseDocumentEnd() } } private mutating func nodeStateMachine() throws -> Event { switch state { case .blockNode: try parseNode(block: true, indentlessSequence: false) case .blockNodeOrIndentlessSequence: try parseNode(block: true, indentlessSequence: true) case .flowNode: try parseNode(block: false, indentlessSequence: false) case .blockSequenceFirstEntry: try parseBlockSequenceEntry(first: true) case .blockSequenceEntry: try parseBlockSequenceEntry(first: false) case .indentlessSequenceEntry: try parseIndentlessSequenceEntry() case .blockMappingFirstKey: try parseBlockMappingKey(first: true) case .blockMappingKey: try parseBlockMappingKey(first: false) default: try parseBlockMappingValue() } } private mutating func flowStateMachine() throws -> Event { switch state { case .flowSequenceFirstEntry: try parseFlowSequenceEntry(first: true) case .flowSequenceEntry: try parseFlowSequenceEntry(first: false) case .flowSequenceEntryMappingKey: try parseFlowSequenceEntryMappingKey() case .flowSequenceEntryMappingValue: try parseFlowSequenceEntryMappingValue() case .flowSequenceEntryMappingEnd: try parseFlowSequenceEntryMappingEnd() case .flowMappingFirstKey: try parseFlowMappingKey(first: true) case .flowMappingKey: try parseFlowMappingKey(first: false) case .flowMappingValue: try parseFlowMappingValue(empty: false) default: try parseFlowMappingValue(empty: true) } } private func endError() -> ParserError { ParserError(context: nil, problem: "parsed past the end of the stream", mark: peekToken().startMark) } /// Port of `yaml_parser_process_empty_scalar`: an empty plain scalar event /// stands in for an absent node. func processEmptyScalar(at mark: Mark) -> Event { .scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark, endMark: mark) } } } extension PureYAML.Parsing.LibYAML.Parser { typealias Token = PureYAML.Parsing.LibYAML.Token typealias Mark = PureYAML.Parsing.Mark typealias Event = PureYAML.Parsing.Event typealias ParserError = PureYAML.Parsing.LibYAML.ParserError typealias ParserState = PureYAML.Parsing.LibYAML.ParserState }