PureYAML/Sources/Parsing/Token.swift
Mihaela Mihaljevic 3db3e87d39 fix(parsing): correct block scalar folding and chomping
Rewrite literal (|) and folded (>) block scalar content to YAML 1.2 section 8.1: preserve interior blank lines, apply 8.1.3 line folding (a single break between equally indented non-empty lines folds to a space, while blank lines and more-indented lines keep their breaks), preserve tabs in content (only block-indentation columns are stripped), and support the + keep chomping indicator alongside clip and strip.

The block scalar is now scanned as a single unit (header line plus all content lines) with indentation auto-detection in Scanner.BlockScalar.swift, mirroring the reference scanner, instead of being reassembled from a lossy per-line token array.

Flips 24 official YAML test suite cases to passing (220/402). Updates the gap manifest, vendored fixtures, count assertions, and the conformance roadmap.
2026-06-23 20:01:03 +02:00

109 lines
3.1 KiB
Swift

extension PureYAML.Parsing {
struct Token: Equatable, CustomStringConvertible {
var kind: TokenKind
var mark: Mark
var endMark: Mark
var description: String {
"\(kind) @\(mark)..\(endMark)"
}
}
}
extension PureYAML.Parsing {
enum TokenKind: Equatable {
case streamStart
case streamEnd
case documentStart
case documentEnd
case indent(width: Int)
case dedent(width: Int)
case blockEntry
case mappingKey
case mappingValue
case flowSequenceStart
case flowSequenceEnd
case flowMappingStart
case flowMappingEnd
case flowEntry
case scalar(value: String, style: ScalarStyle)
case blockScalarHeader(style: BlockScalarStyle, chomping: BlockScalarChomping, value: String)
case anchor(String)
case alias(String)
case tag(String)
case comment(String)
}
}
extension PureYAML.Parsing.TokenKind: CustomStringConvertible {
var description: String {
switch self {
case .streamStart:
"streamStart"
case .streamEnd:
"streamEnd"
case .documentStart:
"documentStart"
case .documentEnd:
"documentEnd"
case let .indent(width):
"indent width=\(width)"
case let .dedent(width):
"dedent width=\(width)"
case .blockEntry:
"blockEntry"
case .mappingKey:
"mappingKey"
case .mappingValue:
"mappingValue"
case .flowSequenceStart:
"flowSequenceStart"
case .flowSequenceEnd:
"flowSequenceEnd"
case .flowMappingStart:
"flowMappingStart"
case .flowMappingEnd:
"flowMappingEnd"
case .flowEntry:
"flowEntry"
case let .scalar(value, style):
"scalar value=\"\(escape(value))\" style=\(style)"
case let .blockScalarHeader(style, chomping, value):
if chomping == .clip {
"blockScalarHeader style=\(style) value=\"\(escape(value))\""
} else {
"blockScalarHeader style=\(style) chomping=\(chomping) value=\"\(escape(value))\""
}
case let .anchor(value):
"anchor name=\(value)"
case let .alias(value):
"alias name=\(value)"
case let .tag(value):
"tag value=\(value)"
case let .comment(value):
"comment value=\"\(escape(value))\""
}
}
private func escape(_ value: String) -> String {
var output = ""
for character in value {
switch character {
case "\\":
output += "\\\\"
case "\"":
output += "\\\""
case "\n":
output += "\\n"
case "\r":
output += "\\r"
case "\t":
output += "\\t"
default:
output.append(character)
}
}
return output
}
}