PureYAML/Sources/Parsing/LibYAML.CharacterClass.swift
Mihaela Mihaljevic c3a1e6aaa4 fix(parsing): treat flow '?' before a non-indicator as scalar content
libyaml makes every '?' in flow context a mapping-key indicator, so it
parses '{ ?foo: bar }' as a key 'foo' (dropping the '?') and '[?x]' as an
empty single-pair mapping. Per ns-plain-first a '?' followed by a
flow-safe character (anything but whitespace, end of input, or a flow
indicator) begins a plain scalar.

Gate the '?' key-indicator dispatch on isBlankZ or, in flow context, a
flow indicator, so '?foo' and '?x' stay plain-scalar content. Hoist the
flow-indicator predicate into CharacterClass and reuse it in the plain
scalar scanner. Surpasses libyaml on 652Z and HM87/01. Port score
290 -> 292.
2026-06-24 18:50:46 +02:00

104 lines
3.9 KiB
Swift

extension PureYAML.Parsing.LibYAML {
/// Port of libyaml's character-class macros (`IS_*` in `yaml_private.h`).
/// libyaml inspects UTF-8 bytes; this operates on `Unicode.Scalar`, where the
/// multi-byte line breaks NEL (`U+0085`), LS (`U+2028`), and PS (`U+2029`) are
/// single scalars. End of input is `nil`, which is libyaml's `\0` sentinel, so
/// `isZ(nil)` is true and every other predicate is false for `nil`.
enum CharacterClass {
/// `IS_Z`: the end of the input (libyaml's `\0`).
static func isZ(_ scalar: Unicode.Scalar?) -> Bool {
scalar == nil
}
/// `IS_SPACE`.
static func isSpace(_ scalar: Unicode.Scalar?) -> Bool {
scalar == " "
}
/// `IS_TAB`.
static func isTab(_ scalar: Unicode.Scalar?) -> Bool {
scalar == "\t"
}
/// `IS_BLANK`: space or tab.
static func isBlank(_ scalar: Unicode.Scalar?) -> Bool {
isSpace(scalar) || isTab(scalar)
}
/// `IS_BREAK`: CR, LF, NEL, LS, or PS.
static func isBreak(_ scalar: Unicode.Scalar?) -> Bool {
switch scalar {
case "\r", "\n", "\u{85}", "\u{2028}", "\u{2029}": true
default: false
}
}
/// `IS_BREAKZ`: a line break or the end of input.
static func isBreakZ(_ scalar: Unicode.Scalar?) -> Bool {
isBreak(scalar) || isZ(scalar)
}
/// `IS_SPACEZ`: a space, a line break, or the end of input.
static func isSpaceZ(_ scalar: Unicode.Scalar?) -> Bool {
isSpace(scalar) || isBreakZ(scalar)
}
/// `IS_BLANKZ`: blank (space or tab), a line break, or the end of input.
static func isBlankZ(_ scalar: Unicode.Scalar?) -> Bool {
isBlank(scalar) || isBreakZ(scalar)
}
/// A flow collection indicator (`,`, `[`, `]`, `{`, `}`); the YAML
/// `c-flow-indicator` set excluded from `ns-plain-safe` in flow context.
static func isFlowIndicator(_ scalar: Unicode.Scalar?) -> Bool {
switch scalar {
case ",", "[", "]", "{", "}": true
default: false
}
}
/// `IS_DIGIT`.
static func isDigit(_ scalar: Unicode.Scalar?) -> Bool {
guard let scalar else { return false }
return scalar >= "0" && scalar <= "9"
}
/// `IS_ALPHA`: a digit, ASCII letter, `_`, or `-` (the directive, anchor,
/// and tag-handle name characters).
static func isAlpha(_ scalar: Unicode.Scalar?) -> Bool {
guard let scalar else { return false }
return (scalar >= "0" && scalar <= "9")
|| (scalar >= "A" && scalar <= "Z")
|| (scalar >= "a" && scalar <= "z")
|| scalar == "_"
|| scalar == "-"
}
/// `IS_HEX`: a hexadecimal digit.
static func isHex(_ scalar: Unicode.Scalar?) -> Bool {
guard let scalar else { return false }
return (scalar >= "0" && scalar <= "9")
|| (scalar >= "A" && scalar <= "F")
|| (scalar >= "a" && scalar <= "f")
}
/// `IS_ASCII`.
static func isASCII(_ scalar: Unicode.Scalar?) -> Bool {
guard let scalar else { return false }
return scalar.value <= 0x7F
}
/// `IS_PRINTABLE`: the YAML `c-printable` set.
static func isPrintable(_ scalar: Unicode.Scalar?) -> Bool {
guard let scalar else { return false }
let value = scalar.value
return value == 0x09 || value == 0x0A || value == 0x0D
|| (value >= 0x20 && value <= 0x7E)
|| value == 0x85
|| (value >= 0xA0 && value <= 0xD7FF)
|| (value >= 0xE000 && value <= 0xFFFD)
|| (value >= 0x10000 && value <= 0x10FFFF)
}
}
}