extension PureYAML.Parsing.LibYAML.Scanner { private typealias Classify = PureYAML.Parsing.LibYAML.CharacterClass private typealias ScannerError = PureYAML.Parsing.LibYAML.ScannerError /// Port of `yaml_parser_scan_to_next_token`: skip a leading BOM, spaces and /// tabs, a comment, and a single line break, which re-allows a simple key in /// 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. mutating func scanToNextToken() throws { crossedLineBreak = false flowLeadingTab = false while true { tabbedSeparation = false if reader.column == 0, reader.peek() == "\u{FEFF}" { reader.skip() } while reader.peek() == " " || Classify.isTab(reader.peek()) { if Classify.isTab(reader.peek()) { noteLeadingTab() } reader.skip() } // YAML 1.2: a line containing only whitespace is blank even when that // whitespace includes tabs. libyaml leaves a leading tab in place and // later rejects it; instead consume the trailing blanks so the line // break below is reached and the blank line is skipped. if blankLineAhead() { while reader.peek() == " " || Classify.isTab(reader.peek()) { reader.skip() } } if reader.peek() == "#" { try consumeComment() } if Classify.isBreak(reader.peek()) { reader.skipLine() crossedLineBreak = true if flowLevel == 0 { simpleKeyAllowed = true } } else { break } } } /// Record what a tab in the leading whitespace implies. In block context with a /// simple key allowed it is potential indentation before an indicator. In flow /// context, below the continuation indent on a non-blank line it is indentation /// abuse (a tab is never indentation); at or above the indent it is separation. private mutating func noteLeadingTab() { if flowLevel == 0, simpleKeyAllowed { tabbedSeparation = true } if flowLevel > 0, crossedLineBreak, reader.column < indentEngine.indent + 1, !blankLineAhead() { flowLeadingTab = true } } /// 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() } } /// Whether the rest of the current line, from the current position, is only /// spaces and tabs up to a line break or end of stream (a blank line). mutating func blankLineAhead() -> Bool { var offset = 0 while let character = reader.peek(offset), character == " " || Classify.isTab(character) { offset += 1 } return Classify.isBreakZ(reader.peek(offset)) } }