fix(parsing): reject a comment with no preceding whitespace
A '#' starts a comment only after a space or tab, or at the start of a line. A '#' adjacent to the previous token is not a comment: 'key: "v"#c', '[ a ]#c', '[ a,#c', and a '>#c' block-scalar header are malformed, but the port (like libyaml) accepted them. Validate the character immediately before the '#' (a plain scalar already consumes its own trailing blanks, so an adjacent '#' the next-token scan sees genuinely had no separator). Applied in scan_to_next_token and the block-scalar header tail. Surpasses libyaml on 9JBA, CVW2, SU5Z, X4QW; whole-suite conformance 388/402. Canonical event stream stays 308/308.
This commit is contained in:
parent
b5129d71ee
commit
2e9975e977
@ -22,6 +22,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Reject a comment that is not preceded by whitespace. A `#` only begins a comment
|
||||
after a space, a tab, or at the start of a line; a `#` adjacent to the previous
|
||||
token (`key: "value"# c`, `[ a ]#c`, `[ a,#c`, a `>#c` block scalar header) is
|
||||
malformed. The check uses the character immediately before the `#`, since a plain
|
||||
scalar consumes its own trailing blanks. libyaml accepts the adjacent form.
|
||||
Surpasses libyaml on 9JBA, CVW2, SU5Z, and X4QW. Whole-suite conformance is now
|
||||
388/402.
|
||||
- Reject a directive that follows a document without an explicit `...` end marker.
|
||||
Directives apply to the document that follows, so the previous document must be
|
||||
closed with `...` before them; after an implicit end a `%YAML`/`%TAG`/reserved
|
||||
|
||||
@ -96,6 +96,11 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
reader.skip()
|
||||
}
|
||||
if reader.peek() == "#" {
|
||||
// A comment needs a preceding space, so `>#` (a `#` adjacent to the
|
||||
// block scalar indicator) is malformed, not a comment. libyaml accepts it.
|
||||
if reader.column > 0, let previous = reader.peek(-1), !Classify.isBlank(previous) {
|
||||
throw blockError("found a comment without a preceding space", startMark)
|
||||
}
|
||||
while !Classify.isBreakZ(reader.peek()) {
|
||||
reader.skip()
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
/// the block context. A tab is valid in-line separation, so it is skipped
|
||||
/// here; `tabbedSeparation` records when one preceded the token so a block
|
||||
/// indicator or implicit key (where the tab would be indentation) can reject.
|
||||
private mutating func scanToNextToken() {
|
||||
private mutating func scanToNextToken() throws {
|
||||
while true {
|
||||
tabbedSeparation = false
|
||||
if reader.column == 0, reader.peek() == "\u{FEFF}" {
|
||||
@ -41,9 +41,7 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
}
|
||||
}
|
||||
if reader.peek() == "#" {
|
||||
while !Classify.isBreakZ(reader.peek()) {
|
||||
reader.skip()
|
||||
}
|
||||
try consumeComment()
|
||||
}
|
||||
if Classify.isBreak(reader.peek()) {
|
||||
reader.skipLine()
|
||||
@ -56,6 +54,24 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume a comment to the end of the line. A comment must be preceded by
|
||||
/// whitespace or start the line; a `#` adjacent to the previous token (e.g.
|
||||
/// `"x"#`, `]#`, `,#`) is not a comment. The plain-scalar scanner consumes its
|
||||
/// own trailing blanks, so the character just before the `#` is the reliable
|
||||
/// signal. libyaml accepts the adjacent form.
|
||||
private mutating func consumeComment() throws {
|
||||
if reader.column > 0, let previous = reader.peek(-1), !Classify.isBlank(previous) {
|
||||
throw ScannerError(
|
||||
context: "while scanning for the next token",
|
||||
problem: "found a comment without a preceding space",
|
||||
mark: reader.mark,
|
||||
)
|
||||
}
|
||||
while !Classify.isBreakZ(reader.peek()) {
|
||||
reader.skip()
|
||||
}
|
||||
}
|
||||
|
||||
/// Port of `yaml_parser_fetch_next_token`: emit STREAM-START first, otherwise
|
||||
/// skip to the next token, expire stale simple keys, unroll the indentation to
|
||||
/// the current column, and dispatch on the leading character. Constructs not
|
||||
@ -66,7 +82,7 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
return
|
||||
}
|
||||
|
||||
scanToNextToken()
|
||||
try scanToNextToken()
|
||||
mark = reader.mark
|
||||
try staleSimpleKeys()
|
||||
indentEngine.unrollIndent(column: reader.column, mark: reader.mark)
|
||||
|
||||
@ -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: 384
|
||||
gap: 18
|
||||
passing: 388
|
||||
gap: 14
|
||||
cases:
|
||||
- id: "229Q"
|
||||
name: "Spec Example 2.4. Sequence of Mappings"
|
||||
@ -1034,7 +1034,7 @@ cases:
|
||||
localPath: yaml-test-suite/9JBA/in.yaml
|
||||
expected: reject
|
||||
oracle: none
|
||||
status: gap
|
||||
status: passing
|
||||
byteCount: 25
|
||||
lineCount: 2
|
||||
- id: "9KAX"
|
||||
@ -1354,7 +1354,7 @@ cases:
|
||||
localPath: yaml-test-suite/CVW2/in.yaml
|
||||
expected: reject
|
||||
oracle: none
|
||||
status: gap
|
||||
status: passing
|
||||
byteCount: 25
|
||||
lineCount: 3
|
||||
- id: "CXX2"
|
||||
@ -2722,7 +2722,7 @@ cases:
|
||||
localPath: yaml-test-suite/SU5Z/in.yaml
|
||||
expected: reject
|
||||
oracle: none
|
||||
status: gap
|
||||
status: passing
|
||||
byteCount: 30
|
||||
lineCount: 1
|
||||
- id: "SU74"
|
||||
@ -3002,7 +3002,7 @@ cases:
|
||||
localPath: yaml-test-suite/X4QW/in.yaml
|
||||
expected: reject
|
||||
oracle: none
|
||||
status: gap
|
||||
status: passing
|
||||
byteCount: 27
|
||||
lineCount: 2
|
||||
- id: "X8DW"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user