A tab is never indentation, so flow-collection content reached through a
tab below the continuation indent ('- [' / '\tfoo') is malformed even
though the tab inflates the column. A tab at or past the indent is
separation, and a tab-only blank line stays blank, so both remain valid.
Track a tab in a flow continuation's leading whitespace below the
parent + 1 indent and reject content (but not a closing bracket) reached
through it. Surpasses libyaml on Y79Y/003; whole-suite conformance
400/402, canonical events 308/308.
140 lines
6.6 KiB
Swift
140 lines
6.6 KiB
Swift
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
|
|
|
|
init(_ input: String = "") {
|
|
reader = Reader(input)
|
|
}
|
|
|
|
/// `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()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|