PureYAML/Sources/Parsing/LibYAML.CharacterClass.swift
Mihaela Mihaljevic 348af8b77b feat(parsing): port libyaml character-class predicates to Swift
Stage 3 foundation of the faithful libyaml scanner port. Translates the IS_* character-class macros from yaml_private.h (IS_Z, IS_SPACE, IS_TAB, IS_BLANK, IS_BREAK, IS_BREAKZ, IS_SPACEZ, IS_BLANKZ, IS_DIGIT, IS_ALPHA, IS_HEX, IS_ASCII, IS_PRINTABLE) into PureYAML.Parsing.LibYAML.CharacterClass. libyaml inspects UTF-8 bytes; the Swift port operates on Unicode.Scalar, where the multi-byte line breaks NEL (U+0085), LS (U+2028), and PS (U+2029) are single scalars, and end of input is nil, libyaml's \0 sentinel (so isZ(nil) is true and the others are false). These predicates are what the token fetchers dispatch on. Unit-tested for end-of-input, blanks, the full break set, the alpha/digit/hex sets, and the ascii/printable boundaries.
2026-06-24 03:00:28 +02:00

95 lines
3.5 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)
}
/// `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)
}
}
}