The scalar event now carries the scanner token's end mark, so a located scalar's range spans the whole token (end exclusive, one past the last character) instead of being start-anchored. This gives consumers exact highlight widths and is the prerequisite for surgical, position-based text edits. Only the scalar event case gained a field; the value tree, its equality, and the event dump (which compares value and style, not marks) are unchanged, so the canonical-event and whole-suite gates stay green (343 tests).
149 lines
6.1 KiB
Swift
149 lines
6.1 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)] = []
|
|
/// 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
|
|
}
|