Skip to content

Instantly share code, notes, and snippets.

@nicklockwood
Last active May 30, 2021 13:12
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicklockwood/05fd5e46b386f4ab1c15e3e3554398ec to your computer and use it in GitHub Desktop.
Save nicklockwood/05fd5e46b386f4ab1c15e3e3554398ec to your computer and use it in GitHub Desktop.
UnicodeScalarView.swift
// This is a really simple drop-in replacement for String.UnicodeScalarView
// As of Swift 3.2, String.UnicodeScalarView no longer supports slice operations, and
// String.UnicodeScalarView.Subsequence is ~5x slower
//
// Only a small subset of methods are implemented, specifically the ones useful for
// implementing a parser or lexer that consumes a string by repeatedly calling popFirst()
//
// I've benchmarked popFirst() as ~7x faster than String.UnicodeScalarView.Subsequence in Swift 3.2 and 4.0
// The performance is close to that of String.UnicodeScalarView in Swift 3.1, but may be slightly worse in some use cases
import Foundation
#if swift(>=3.2)
struct UnicodeScalarView {
public typealias Index = String.UnicodeScalarView.Index
private let characters: String.UnicodeScalarView
public private(set) var startIndex: Index
public private(set) var endIndex: Index
public init(_ unicodeScalars: String.UnicodeScalarView) {
characters = unicodeScalars
startIndex = characters.startIndex
endIndex = characters.endIndex
}
public init(_ unicodeScalars: String.UnicodeScalarView.SubSequence) {
self.init(String.UnicodeScalarView(unicodeScalars))
}
public init(_ string: String) {
self.init(string.unicodeScalars)
}
public var first: UnicodeScalar? {
return isEmpty ? nil : characters[startIndex]
}
@available(*, deprecated, message: "Really hurts performance - use a different approach")
public var count: Int {
return characters.distance(from: startIndex, to: endIndex)
}
public var isEmpty: Bool {
return startIndex >= endIndex
}
public subscript(_ index: Index) -> UnicodeScalar {
return characters[index]
}
public func index(after index: Index) -> Index {
return characters.index(after: index)
}
public func prefix(upTo index: Index) -> UnicodeScalarView {
var view = UnicodeScalarView(characters)
view.startIndex = startIndex
view.endIndex = index
return view
}
public func suffix(from index: Index) -> UnicodeScalarView {
var view = UnicodeScalarView(characters)
view.startIndex = index
view.endIndex = endIndex
return view
}
public func dropFirst() -> UnicodeScalarView {
var view = UnicodeScalarView(characters)
view.startIndex = characters.index(after: startIndex)
view.endIndex = endIndex
return view
}
public mutating func popFirst() -> UnicodeScalar? {
if isEmpty {
return nil
}
let char = characters[startIndex]
startIndex = characters.index(after: startIndex)
return char
}
/// Will crash if n > remaining char count
public mutating func removeFirst(_ n: Int) {
startIndex = characters.index(startIndex, offsetBy: n)
}
/// Will crash if collection is empty
@discardableResult
public mutating func removeFirst() -> UnicodeScalar {
let oldIndex = startIndex
startIndex = characters.index(after: startIndex)
return characters[oldIndex]
}
/// Returns the remaining characters
fileprivate var unicodeScalars: String.UnicodeScalarView.SubSequence {
return characters[startIndex ..< endIndex]
}
}
typealias _UnicodeScalarView = UnicodeScalarView
extension String {
init(_ unicodeScalarView: _UnicodeScalarView) {
self.init(unicodeScalarView.unicodeScalars)
}
}
extension String.UnicodeScalarView {
init(_ unicodeScalarView: _UnicodeScalarView) {
self.init(unicodeScalarView.unicodeScalars)
}
}
extension String.UnicodeScalarView.SubSequence {
init(_ unicodeScalarView: _UnicodeScalarView) {
self.init(unicodeScalarView.unicodeScalars)
}
}
#else
typealias UnicodeScalarView = String.UnicodeScalarView
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment