A '#' that begins a comment must be preceded by whitespace or open the line; glued to earlier same-line content (e.g. "value"# c, [ a, b ]#c, >#c, a flow entry then ,#c) it is neither a comment nor, in those positions, plain-scalar content, so the document is malformed. Such input was previously accepted as a comment. A '#' that opens a line, follows a space, or sits inside a plain scalar (a#b, where it stays literal content and never reaches this check) is unaffected. The scanner tracks whether a real token has been scanned on the current physical line, reset on each line break, rather than comparing token line numbers: the streaming tokenizer drains its token buffer between scans, and a block scalar's blank line emits an empty scalar attributed to the next line, either of which would misread as content. Measured against the official YAML test-suite, parse-level conformance rises from 243/333 (73.0%) to 247/333 (74.2%): four wrongly-accepted invalid documents are now rejected, with no valid document regressing. The scalar-scanning routines move to Scanner.Scalars.swift to keep the file within the length limit.
155 lines
5.7 KiB
Swift
155 lines
5.7 KiB
Swift
extension PureYAML.Parsing.Scanner {
|
|
func scanComment(_ state: inout State) {
|
|
let start = state.reader.mark
|
|
state.reader.advance()
|
|
if state.reader.peek() == " " {
|
|
state.reader.advance()
|
|
}
|
|
let value = state.reader.consume { character in
|
|
!isLineBreak(character)
|
|
}
|
|
state.append(.comment(value), mark: start, endMark: state.reader.mark)
|
|
}
|
|
|
|
func scanQuotedScalar(
|
|
_ style: PureYAML.Parsing.ScalarStyle,
|
|
state: inout State,
|
|
) throws {
|
|
let start = state.reader.mark
|
|
let quote: Character = style == .doubleQuoted ? "\"" : "'"
|
|
state.reader.advance()
|
|
var value = ""
|
|
var previousWasEscape = false
|
|
while let character = state.reader.peek() {
|
|
if style == .singleQuoted, character == quote, state.reader.peek(offset: 1) == quote {
|
|
value.append(character)
|
|
state.reader.advance()
|
|
value.append(character)
|
|
state.reader.advance()
|
|
continue
|
|
}
|
|
if character == quote, !previousWasEscape {
|
|
break
|
|
}
|
|
value.append(character)
|
|
previousWasEscape = character == "\\" && style == .doubleQuoted && !previousWasEscape
|
|
state.reader.advance()
|
|
if character != "\\" {
|
|
previousWasEscape = false
|
|
}
|
|
}
|
|
guard state.reader.peek() == quote else {
|
|
throw PureYAML.Parsing.ParseError.unterminatedQuotedString(line: start.line)
|
|
}
|
|
state.reader.advance()
|
|
// Unlike a plain scalar (which consumes the space before a `:` into itself), a quoted scalar
|
|
// stops at its closing quote, so the reader sits at any spaces before a `:`. A space between
|
|
// an implicit key and its mapping-value indicator is allowed, so look past them.
|
|
if isAtMappingValueIndicatorAfterSpaces(state: state) {
|
|
state.append(.mappingKey, mark: start, endMark: start)
|
|
}
|
|
state.append(.scalar(value: value, style: style), mark: start, endMark: state.reader.mark)
|
|
}
|
|
|
|
func scanBlockScalarHeader(
|
|
_ style: PureYAML.Parsing.BlockScalarStyle,
|
|
state: inout State,
|
|
) {
|
|
let start = state.reader.mark
|
|
var chomping = PureYAML.Parsing.BlockScalarChomping.clip
|
|
var indentationIndicator: Int?
|
|
state.reader.advance()
|
|
while let character = state.reader.peek(), isBlockScalarHeaderCharacter(character) {
|
|
if character == "-" {
|
|
chomping = .strip
|
|
} else if character == "+" {
|
|
chomping = .keep
|
|
} else if character.isNumber {
|
|
indentationIndicator = Int(String(character))
|
|
}
|
|
state.reader.advance()
|
|
}
|
|
state.append(
|
|
.blockScalarHeader(style: style, chomping: chomping),
|
|
mark: start,
|
|
endMark: state.reader.mark,
|
|
)
|
|
state.pendingBlockScalarIndentation = true
|
|
state.pendingBlockScalarIndentationIndicator = indentationIndicator
|
|
}
|
|
|
|
func scanBlockScalarContent(_ state: inout State) {
|
|
let start = state.reader.mark
|
|
let value = state.blockScalarContentPrefix + state.reader.consume { character in
|
|
!isLineBreak(character)
|
|
}
|
|
state.blockScalarContentPrefix = ""
|
|
state.append(.scalar(value: value, style: .plain), mark: start, endMark: state.reader.mark)
|
|
}
|
|
|
|
func scanNamedToken(
|
|
kind: (String) -> PureYAML.Parsing.TokenKind,
|
|
missingNameError: (Int) -> PureYAML.Parsing.ParseError,
|
|
state: inout State,
|
|
) throws {
|
|
let start = state.reader.mark
|
|
state.reader.advance()
|
|
let name = state.reader.consume { character in
|
|
!isTokenTerminator(character)
|
|
}
|
|
guard !name.isEmpty else {
|
|
throw missingNameError(start.line)
|
|
}
|
|
state.append(kind(name), mark: start, endMark: state.reader.mark)
|
|
}
|
|
|
|
func scanTag(_ state: inout State) throws {
|
|
let start = state.reader.mark
|
|
state.reader.advance()
|
|
var value = "!"
|
|
if state.reader.peek() == "<" {
|
|
value.append(state.reader.advance() ?? "<")
|
|
var didClose = false
|
|
while let character = state.reader.peek() {
|
|
value.append(character)
|
|
state.reader.advance()
|
|
if character == ">" {
|
|
didClose = true
|
|
break
|
|
}
|
|
}
|
|
guard didClose else {
|
|
throw PureYAML.Parsing.ParseError.unterminatedTag(line: start.line)
|
|
}
|
|
} else {
|
|
value += state.reader.consume { character in
|
|
!isTokenTerminator(character)
|
|
}
|
|
value = expandTag(value, state: state)
|
|
}
|
|
state.append(.tag(value), mark: start, endMark: state.reader.mark)
|
|
}
|
|
|
|
func scanPlainScalar(_ state: inout State) {
|
|
let start = state.reader.mark
|
|
var value = ""
|
|
while let character = state.reader.peek() {
|
|
if shouldEndPlainScalar(character, state: state, accumulated: value) {
|
|
break
|
|
}
|
|
value.append(character)
|
|
state.reader.advance()
|
|
}
|
|
var trimmed = trimTrailingSpaces(value)
|
|
let isMappingKey = appendMappingKeyIfNeeded(start: start, state: &state)
|
|
if !isMappingKey {
|
|
trimmed = scanPlainScalarContinuations(
|
|
after: trimmed,
|
|
firstLineIndent: start.column - 1,
|
|
state: &state,
|
|
)
|
|
}
|
|
state.append(.scalar(value: trimmed, style: .plain), mark: start, endMark: state.reader.mark)
|
|
}
|
|
}
|