libyaml refuses to skip a tab in the inter-token whitespace whenever a simple key is allowed, so a tab used as valid separation (after a '-', '?', or ':' indicator, or before a flow collection) is reported as a character that cannot start a token. But a tab is legal in-line separation; it is only illegal as indentation. The scanner now skips tabs in scan_to_next_token and records in tabbedSeparation whether one preceded the pending token. A block entry, an explicit key, or an implicit mapping key promoted by ':' is rejected when reached through a tab (there the tab would be indentation), while a scalar value or a flow collection is accepted. Tab-indented keys and '-\t-' nested entries still reject. Surpasses libyaml on 6BCT, 6CA3, A2M4, DK95/00, Q5MG, Y79Y/010. Port score 299 -> 305.
118 lines
5.1 KiB
Swift
118 lines
5.1 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
|
|
/// `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.
|
|
mutating func increaseFlowLevel() {
|
|
simpleKeys.append(.empty)
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
}
|