PureYAML/Sources/Parsing/LibYAML.Scanner.Guards.swift
Mihaela Mihaljevic 57adb14f15 fix(parsing): reject a block sequence that opens mid-line
A block sequence must begin its own line. A '-' entry mid-line after an
inline mapping value ('key: - a') or a node property ('&anchor - x') is
invalid, but libyaml accepts both. A '-' after another '-' is still a
valid compact sequence ('- - a'), and an explicit key whose ':' is on a
later line may still open a sequence ('? k' / ': - a').

In fetchBlockEntry, when no line break was crossed, reject if the last
token is an anchor/tag, or a value that closed an inline implicit key
(told apart by a pending simple key: an explicit key's simple key has
gone stale across its line break). Extract the scanner guard helpers into
LibYAML.Scanner.Guards to stay within the file-length limit.

Surpasses libyaml on 5U3A, SY6V; whole-suite conformance 396/402,
canonical events 308/308.
2026-06-24 21:44:27 +02:00

58 lines
2.3 KiB
Swift

extension PureYAML.Parsing.LibYAML.Scanner {
private typealias ScannerError = PureYAML.Parsing.LibYAML.ScannerError
/// Reject a flow-collection continuation line that is not indented past the
/// enclosing block context. YAML requires `s-flow-line-prefix` to be more
/// indented than the parent block; libyaml does not enforce it, so multi-line
/// flow content in column 0 (`[a,\nb]` under a key) wrongly parses.
mutating func checkFlowContinuationIndent() throws {
guard flowLevel > 0, crossedLineBreak, let next = reader.peek() else {
return
}
// A closing `]`/`}` (or a `,` separator) may sit at any indentation; only
// flow CONTENT must be indented past the parent block.
if next == "]" || next == "}" || next == "," {
return
}
if reader.column <= indentEngine.indent {
throw ScannerError(
context: "while parsing a flow node",
problem: "found insufficiently indented line in a flow collection",
mark: reader.mark,
)
}
}
/// Whether the last emitted token bars a block sequence from opening on the
/// same line: a mapping value indicator or a node property (anchor/tag). After
/// another block entry a compact sequence is allowed, so that is not barred.
func lastTokenForbidsInlineSequence() -> Bool {
switch indentEngine.tokens.last?.type {
case .anchor, .tag:
true
case .value:
lastValueWasInline
default:
false
}
}
/// The error raised when a tab is used where indentation is required (before a
/// block indicator or an implicit mapping key). A tab is valid separation but
/// never indentation.
func tabIndentationError() -> PureYAML.Parsing.LibYAML.ScannerError {
ScannerError(
context: "while scanning for the next token",
problem: "found a tab character where an indentation space is expected",
mark: reader.mark,
)
}
/// Whether this flow level's pending simple key is a JSON (quoted) node, which
/// lets an adjacent `:` open its value without a separating space.
var flowKeyIsJSON: Bool {
let key = simpleKeys[simpleKeys.count - 1]
return key.possible && key.isJSON
}
}