Skip to content

Instantly share code, notes, and snippets.

@hooman
Created March 15, 2019 00:44
Show Gist options
  • Save hooman/0dbb4ffce26eb2032df419b3567b6101 to your computer and use it in GitHub Desktop.
Save hooman/0dbb4ffce26eb2032df419b3567b6101 to your computer and use it in GitHub Desktop.
Utilities to work with ASCII characters and characters as integers.
// 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