fix(parsing): require flow continuation lines to be indented
YAML requires multi-line flow content to be indented past the enclosing block context (s-flow-line-prefix). libyaml does not enforce this, so 'flow: [a,' / 'b]' with 'b' in column 0, and the equivalent multi-line flow mapping, wrongly parsed. Track whether reaching a token crossed a line break, and in flow context reject a token at or below the parent block indent. A closing ']' / '}' or a leading ',' may sit at any indentation, so those are exempt (a flow mapping's closing brace in column 0 stays valid). Surpasses libyaml on 9C9N, VJP3/00; whole-suite conformance 394/402, canonical events 308/308.
This commit is contained in:
parent
4a6b168565
commit
eee7079efe
@ -22,6 +22,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Reject a flow-collection continuation line that is not indented past the parent
|
||||
block. YAML requires multi-line flow content to be more indented than the
|
||||
enclosing block context, so `flow: [a,` / `b]` (with `b` in column 0) and the
|
||||
equivalent multi-line flow mapping are malformed; libyaml does not enforce it. A
|
||||
closing `]`/`}` or a leading `,` may still sit at any indentation. Surpasses
|
||||
libyaml on 9C9N and VJP3/00.
|
||||
- Reject content after a `...` document-end marker on the same line. Only
|
||||
whitespace and a comment may follow `...`; unlike `--- x`, a `... x` cannot
|
||||
carry inline content. Surpasses libyaml on 3HFZ.
|
||||
|
||||
@ -25,6 +25,7 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
}
|
||||
|
||||
try scanToNextToken()
|
||||
try checkFlowContinuationIndent()
|
||||
mark = reader.mark
|
||||
try staleSimpleKeys()
|
||||
indentEngine.unrollIndent(column: reader.column, mark: reader.mark)
|
||||
@ -106,6 +107,28 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
private 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
@ -8,6 +8,7 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
/// here; `tabbedSeparation` records when one preceded the token so a block
|
||||
/// indicator or implicit key (where the tab would be indentation) can reject.
|
||||
mutating func scanToNextToken() throws {
|
||||
crossedLineBreak = false
|
||||
while true {
|
||||
tabbedSeparation = false
|
||||
if reader.column == 0, reader.peek() == "\u{FEFF}" {
|
||||
@ -33,6 +34,7 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
}
|
||||
if Classify.isBreak(reader.peek()) {
|
||||
reader.skipLine()
|
||||
crossedLineBreak = true
|
||||
if flowLevel == 0 {
|
||||
simpleKeyAllowed = true
|
||||
}
|
||||
|
||||
@ -18,6 +18,10 @@ extension PureYAML.Parsing.LibYAML {
|
||||
/// 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
|
||||
/// `parser->mark`, kept in sync with the reader at each fetch step.
|
||||
var mark: PureYAML.Parsing.Mark = .start
|
||||
/// `parser->stream_start_produced`.
|
||||
|
||||
@ -10,8 +10,8 @@ source:
|
||||
# oracle: json if in.json present, else none
|
||||
# status: passing (PureYAML conformant today) | gap (not yet conformant)
|
||||
total: 402
|
||||
passing: 392
|
||||
gap: 10
|
||||
passing: 394
|
||||
gap: 8
|
||||
cases:
|
||||
- id: "229Q"
|
||||
name: "Spec Example 2.4. Sequence of Mappings"
|
||||
@ -986,7 +986,7 @@ cases:
|
||||
localPath: yaml-test-suite/9C9N/in.yaml
|
||||
expected: reject
|
||||
oracle: none
|
||||
status: gap
|
||||
status: passing
|
||||
byteCount: 20
|
||||
lineCount: 4
|
||||
- id: "9CWY"
|
||||
@ -2938,7 +2938,7 @@ cases:
|
||||
localPath: yaml-test-suite/VJP3/00/in.yaml
|
||||
expected: reject
|
||||
oracle: none
|
||||
status: gap
|
||||
status: passing
|
||||
byteCount: 13
|
||||
lineCount: 5
|
||||
- id: "VJP3/01"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user