Ports parser.c: the one-event-at-a-time state machine that turns the scanner's token stream into the YAML event stream. Covers stream and document framing, directive processing with %TAG handle resolution and default handles, the node property/tag/anchor production, block and indentless sequences, block mappings, and flow sequences and mappings including the single-pair `?` mapping forms. Output verified against the libyaml reference event dumper: the empty mapping value, %TAG resolution, and shorthand tags all match byte for byte. 331 tests, no regression.
145 lines
5.9 KiB
Swift
145 lines
5.9 KiB
Swift
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)] = []
|
|
|
|
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(implicit: true)
|
|
case .documentStart:
|
|
try parseDocumentStart(implicit: false)
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|