Created
March 15, 2019 00:44
-
-
Save hooman/0dbb4ffce26eb2032df419b3567b6101 to your computer and use it in GitHub Desktop.
Utilities to work with ASCII characters and characters as integers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A small set of utilities for working with Unicode and ASCII scalars. Here is what it does: | |
// | |
// 1. Make `UnicodeScalar` strideable. This enables ranges of Unicode Scalars such as "a"..."z". | |
// 2. Add strideable Unicode.ASCII.Scalar (type alias `ASCII`) to enable type-safe manipulation of ASCII bytes. | |
// 3. Define limited `+`/`-` operators for stridable types to make something like `"x" + ("A"-"a")` possible. | |
// 1. Make `UnicodeScalar` strideable. This enables ranges of Unicode Scalars such as "a"..."z". | |
// Constants used in `Strideable` conformance extension of `Unicode.Scalar` | |
private let invalidCount = 0xE000 - 0xD7FF | |
private let validCount = 0x10FFFF - invalidCount | |
extension Unicode.Scalar: Strideable { | |
public func advanced(by n: Int) -> Unicode.Scalar { | |
let intValue = Int(value) | |
precondition(n >= -intValue && n <= validCount-intValue, "Out of bounds") | |
let newValue = value.advanced(by: n) | |
switch (value, newValue) { | |
case (0...0xD7FF, 0...0xD7FF), | |
(0xE000...0x10FFFF, 0xE000...0x10FFFF): | |
return Unicode.Scalar(newValue)! | |
case (0...0xD7FF, _): | |
return Unicode.Scalar(newValue.advanced(by: invalidCount))! | |
case (0xE000...0x10FFFF, _): | |
return Unicode.Scalar(newValue.advanced(by: -invalidCount))! | |
default: | |
fatalError("Unreachable! (scalar: \(value), stride: \(n))") | |
} | |
} | |
public func distance(to other: Unicode.Scalar) -> Int { | |
let value = UTF32.CodeUnit.init(self) | |
let otherValue = UTF32.CodeUnit.init(other) | |
switch (value, otherValue) { | |
case (0...0xD7FF, 0...0xD7FF), | |
(0xE000...0x10FFFF, 0xE000...0x10FFFF): | |
return value.distance(to: otherValue) | |
case (0...0xD7FF, 0xE000...0x10FFFF): | |
return value.distance(to: otherValue) - invalidCount | |
case (0xE000...0x10FFFF, 0...0xD7FF): | |
return value.distance(to: otherValue) + invalidCount | |
default: | |
fatalError("Unreachable") | |
} | |
} | |
} | |
// 2. Add Unicode.ASCII.Scalar (type alias `ASCII`) to enable type-safe manipulation of ASCII bytes. | |
extension Unicode.ASCII { | |
public struct Scalar: ExpressibleByUnicodeScalarLiteral, Strideable { | |
public let int8: Int8 | |
public var uint8: UInt8 { return UInt8(bitPattern: int8) } | |
public var unicode: UnicodeScalar { return UnicodeScalar(uint8) } | |
public var character: Character { return Character(unicode) } | |
public init(_ value: Int8) { | |
if value >= 0 { | |
int8 = value | |
} else { | |
fatalError("Not an ASCII code") | |
} | |
} | |
public init(_ value: UInt8) { self.init(Int8(bitPattern: value)) } | |
public init(_ scalar: UnicodeScalar) { | |
guard scalar.isASCII else { fatalError("Not an ASCII literal") } | |
int8 = Int8(scalar.value) | |
} | |
public init(unicodeScalarLiteral value: UnicodeScalar) { self.init(value) } | |
public func advanced(by n: Int8) -> Scalar { return Scalar(int8+n) } | |
public func distance(to other: Scalar) -> Int8 { return other.int8 - int8 } | |
} | |
} | |
public typealias ASCII = Unicode.ASCII.Scalar | |
// 3. Define limited `+`/`-` operators for stridable types to make something like `"x" + ("A"-"a")` possible. | |
public extension Strideable { | |
static func + (lhs: Self, rhs: Self.Stride) -> Self { | |
return lhs.advanced(by: rhs) | |
} | |
static func - (lhs: Self, rhs: Self) -> Self.Stride { | |
return rhs.distance(to: lhs) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment