blob: 78dbb7e72e31be7fc4b1514eaefa4d22acfc9246 [file] [log] [blame]
//===--- StringIndex.swift ------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftShims
/*
String's Index has the following layout:
┌──────────┬───────────────────┬────────────────┬──────────┐
│ b63:b16 │ b15:b14 │ b13:b8 │ b7:b0 │
├──────────┼───────────────────┼────────────────┼──────────┤
│ position │ transcoded offset │ grapheme cache │ reserved │
└──────────┴───────────────────┴────────────────┴──────────┘
- grapheme cache: A 6-bit value remembering the distance to the next grapheme
boundary
- position aka `encodedOffset`: An offset into the string's code units
- transcoded offset: a sub-scalar offset, derived from transcoding
The use and interpretation of both `reserved` and `grapheme cache` is not part
of Index's ABI; it should be hidden behind non-inlinable calls. However, the
position of the sequence of 14 bits allocated is part of Index's ABI, as well as
the default value being `0`.
*/
extension String {
/// A position of a character or code unit in a string.
@_fixed_layout
public struct Index {
@usableFromInline
internal var _rawBits: UInt64
@inlinable @inline(__always)
init(_ raw: UInt64) {
self._rawBits = raw
self._invariantCheck()
}
}
}
extension String.Index {
@inlinable @inline(__always)
internal var orderingValue: UInt64 { return _rawBits &>> 14 }
// Whether this is at the canonical "start" position, that is encoded AND
// transcoded offset of 0.
@inlinable @inline(__always)
internal var isZeroPosition: Bool { return orderingValue == 0 }
/// The UTF-16 code unit offset corresponding to this Index
public func utf16Offset<S: StringProtocol>(in s: S) -> Int {
return s.utf16.distance(from: s.utf16.startIndex, to: self)
}
/// The offset into a string's code units for this index.
@available(swift, deprecated: 4.2, message: """
encodedOffset has been deprecated as most common usage is incorrect. \
Use utf16Offset(in:) to achieve the same behavior.
""")
@inlinable
public var encodedOffset: Int { return _encodedOffset }
@inlinable @inline(__always)
internal var _encodedOffset: Int {
return Int(truncatingIfNeeded: _rawBits &>> 16)
}
@inlinable @inline(__always)
internal var transcodedOffset: Int {
return Int(truncatingIfNeeded: orderingValue & 0x3)
}
@usableFromInline
internal var characterStride: Int? {
let value = (_rawBits & 0x3F00) &>> 8
return value > 0 ? Int(truncatingIfNeeded: value) : nil
}
@inlinable @inline(__always)
internal init(encodedOffset: Int, transcodedOffset: Int) {
let pos = UInt64(truncatingIfNeeded: encodedOffset)
let trans = UInt64(truncatingIfNeeded: transcodedOffset)
_internalInvariant(pos == pos & 0x0000_FFFF_FFFF_FFFF)
_internalInvariant(trans <= 3)
self.init((pos &<< 16) | (trans &<< 14))
}
/// Creates a new index at the specified UTF-16 code unit offset
///
/// - Parameter offset: An offset in UTF-16 code units.
public init<S: StringProtocol>(utf16Offset offset: Int, in s: S) {
let (start, end) = (s.utf16.startIndex, s.utf16.endIndex)
guard offset >= 0,
let idx = s.utf16.index(start, offsetBy: offset, limitedBy: end)
else {
self = end.nextEncoded
return
}
self = idx
}
/// Creates a new index at the specified code unit offset.
///
/// - Parameter offset: An offset in code units.
@available(swift, deprecated: 4.2, message: """
encodedOffset has been deprecated as most common usage is incorrect. \
Use String.Index(utf16Offset:in:) to achieve the same behavior.
""")
@inlinable
public init(encodedOffset offset: Int) {
self.init(_encodedOffset: offset)
}
@inlinable @inline(__always)
internal init(_encodedOffset offset: Int) {
self.init(encodedOffset: offset, transcodedOffset: 0)
}
@usableFromInline
internal init(
encodedOffset: Int, transcodedOffset: Int, characterStride: Int
) {
self.init(encodedOffset: encodedOffset, transcodedOffset: transcodedOffset)
if _slowPath(characterStride > 63) { return }
_internalInvariant(characterStride == characterStride & 0x3F)
self._rawBits |= UInt64(truncatingIfNeeded: characterStride &<< 8)
self._invariantCheck()
}
@usableFromInline
internal init(encodedOffset pos: Int, characterStride char: Int) {
self.init(encodedOffset: pos, transcodedOffset: 0, characterStride: char)
}
#if !INTERNAL_CHECKS_ENABLED
@inlinable @inline(__always) internal func _invariantCheck() {}
#else
@usableFromInline @inline(never) @_effects(releasenone)
internal func _invariantCheck() {
_internalInvariant(_encodedOffset >= 0)
}
#endif // INTERNAL_CHECKS_ENABLED
}
// Creation helpers, which will make migration easier if we decide to use and
// propagate the reserved bits.
extension String.Index {
@inlinable @inline(__always)
internal var strippingTranscoding: String.Index {
return String.Index(_encodedOffset: self._encodedOffset)
}
@inlinable @inline(__always)
internal var nextEncoded: String.Index {
_internalInvariant(self.transcodedOffset == 0)
return String.Index(_encodedOffset: self._encodedOffset &+ 1)
}
@inlinable @inline(__always)
internal var priorEncoded: String.Index {
_internalInvariant(self.transcodedOffset == 0)
return String.Index(_encodedOffset: self._encodedOffset &- 1)
}
@inlinable @inline(__always)
internal var nextTranscoded: String.Index {
return String.Index(
encodedOffset: self._encodedOffset,
transcodedOffset: self.transcodedOffset &+ 1)
}
@inlinable @inline(__always)
internal var priorTranscoded: String.Index {
return String.Index(
encodedOffset: self._encodedOffset,
transcodedOffset: self.transcodedOffset &- 1)
}
// Get an index with an encoded offset relative to this one.
// Note: strips any transcoded offset.
@inlinable @inline(__always)
internal func encoded(offsetBy n: Int) -> String.Index {
return String.Index(_encodedOffset: self._encodedOffset &+ n)
}
@inlinable @inline(__always)
internal func transcoded(withOffset n: Int) -> String.Index {
_internalInvariant(self.transcodedOffset == 0)
return String.Index(encodedOffset: self._encodedOffset, transcodedOffset: n)
}
}
extension String.Index: Equatable {
@inlinable @inline(__always)
public static func == (lhs: String.Index, rhs: String.Index) -> Bool {
return lhs.orderingValue == rhs.orderingValue
}
}
extension String.Index: Comparable {
@inlinable @inline(__always)
public static func < (lhs: String.Index, rhs: String.Index) -> Bool {
return lhs.orderingValue < rhs.orderingValue
}
}
extension String.Index: Hashable {
/// Hashes the essential components of this value by feeding them into the
/// given hasher.
///
/// - Parameter hasher: The hasher to use when combining the components
/// of this instance.
@inlinable
public func hash(into hasher: inout Hasher) {
hasher.combine(orderingValue)
}
}