extension PureYAML.Parsing.LibYAML { /// Aggregate scanner state, mirroring the fields of `yaml_parser_t` that the /// ported functions read and write. It is built up as the port proceeds; /// currently it holds the indentation engine and the simple-key mechanism. /// Column values follow libyaml's 0-based convention internally; conversion to /// the package's 1-based `Mark` happens at token emission during integration. struct Scanner { /// The input buffer and position (`parser->buffer` / `parser->mark`). var reader: Reader /// The indentation stack and `roll_indent` / `unroll_indent` (also the /// owner of `flow_level` and the token queue). var indentEngine = IndentEngine() /// `parser->simple_keys`. Always holds at least the block-context level. var simpleKeys: [SimpleKey] = [.empty] /// `parser->simple_key_allowed`. True at stream start. var simpleKeyAllowed = true /// Whether a tab appeared in the indentation before the pending token. A /// tab is valid in-line separation but never indentation, so a block /// indicator or implicit mapping key reached through one is an error. var tabbedSeparation = false /// Whether reaching the pending token crossed at least one line break. In /// flow context a continuation line must be indented past the parent block, /// so a token on a new line at or below that indent is an error. var crossedLineBreak = false /// Whether a tab appeared in the leading whitespace of a flow continuation /// line. A tab is never indentation, so flow content reached through one is /// an error even though the tab inflates the column. var flowLeadingTab = false /// Whether the most recent `:` value indicator sat on the same line as its /// key (an inline implicit-key value). Such a value may not open a block /// sequence on that line (`key: - a`), whereas an explicit key whose `:` is /// on a later line may (`? k`\n`: - a`). var lastValueWasInline = false /// For each open flow level, whether it is a sequence (`[`) rather than a /// mapping (`{`). A flow-sequence single-pair implicit key is single-line /// (`c-s-implicit-json-key`), so its `:` may not cross a line break, unlike /// a flow-mapping adjacent value (`{ "a"`\n`: b }` is allowed). var flowIsSequence: [Bool] = [] /// `parser->mark`, kept in sync with the reader at each fetch step. var mark: PureYAML.Parsing.Mark = .start /// `parser->stream_start_produced`. var streamStartProduced = false /// `parser->stream_end_produced`. var streamEndProduced = false /// A plain scalar fetch that ran dry mid-content (`scanStep()` only, batch /// scanning never sets this): resumed from here on the next `scanStep()` /// call instead of restarting the scalar from its first scalar. var suspendedPlainScalar: PlainScalarSuspended? init(_ input: String = "", isFinal: Bool = true) { reader = Reader(input, isFinal: isFinal) } /// `parser->flow_level`, kept on the indent engine. var flowLevel: Int { indentEngine.flowLevel } /// Port of `yaml_parser_stale_simple_keys`. A potential simple key is /// invalid once it is on an earlier line or more than 1024 characters back /// (a simple key is single-line and bounded); a required one going stale is /// an error. mutating func staleSimpleKeys() throws { for index in simpleKeys.indices { let key = simpleKeys[index] guard key.possible else { continue } // A block-context key (index 0) is single-line. A flow-context key // (index >= 1) may be separated from its ':' by line breaks under // YAML 1.2, so a new line alone does not invalidate it; both remain // bounded to 1024 characters. libyaml invalidates every key on a new // line, which wrongly rejects multi-line flow keys. let tooFarBack = key.mark.index + 1024 < mark.index let stale = index == 0 ? (key.mark.line < mark.line || tooFarBack) : tooFarBack guard stale else { continue } if key.required { throw ScannerError( context: "while scanning a simple key", problem: "could not find expected ':'", mark: key.mark, ) } simpleKeys[index].possible = false } } /// Port of `yaml_parser_save_simple_key`. Record the current position as a /// potential simple key for this flow level when one is allowed. A key is /// required (a `:` must follow) when it sits exactly at the block indent. mutating func saveSimpleKey() throws { let required = flowLevel == 0 && indentEngine.indent == mark.column guard simpleKeyAllowed else { return } let key = SimpleKey( possible: true, required: required, tokenNumber: indentEngine.tokensParsed + indentEngine.tokens.count, mark: mark, tabbed: tabbedSeparation, ) try removeSimpleKey() simpleKeys[simpleKeys.count - 1] = key } /// Port of `yaml_parser_remove_simple_key`. Drop the potential key at the /// current flow level; removing a required key is an error. mutating func removeSimpleKey() throws { let top = simpleKeys.count - 1 if simpleKeys[top].possible, simpleKeys[top].required { throw ScannerError( context: "while scanning a simple key", problem: "could not find expected ':'", mark: simpleKeys[top].mark, ) } simpleKeys[top].possible = false } /// Port of `yaml_parser_increase_flow_level`. Push an empty simple key for /// the new flow level, recording whether the level is a sequence. mutating func increaseFlowLevel(isSequence: Bool) { simpleKeys.append(.empty) flowIsSequence.append(isSequence) indentEngine.flowLevel += 1 } /// Port of `yaml_parser_decrease_flow_level`. Pop the flow level's simple /// key. mutating func decreaseFlowLevel() { if indentEngine.flowLevel > 0 { indentEngine.flowLevel -= 1 simpleKeys.removeLast() if !flowIsSequence.isEmpty { flowIsSequence.removeLast() } } } } }