| //===----------------------------------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| public protocol _OpaqueString: class { |
| var length: Int { get } |
| func character(at index: Int) -> UInt16 |
| |
| // FIXME: This is not an NSString method; I'd like to use |
| // `getCharacters(_:,range:)`, but it would be weird to define |
| // `_SwiftNSRange` without an Objective-C runtime. |
| func copyCodeUnits( |
| from range: Range<Int>, |
| into dest: UnsafeMutablePointer<UInt16>) |
| } |
| |
| @usableFromInline |
| @_fixed_layout |
| internal struct _UnmanagedOpaqueString { |
| #if _runtime(_ObjC) // FIXME unify |
| @usableFromInline |
| unowned(unsafe) let object: _CocoaString |
| #else |
| @usableFromInline |
| unowned(unsafe) let object: _OpaqueString |
| #endif |
| |
| @usableFromInline |
| let range: Range<Int> |
| |
| @usableFromInline |
| let isSlice: Bool |
| |
| #if _runtime(_ObjC) // FIXME unify |
| @inlinable |
| @usableFromInline |
| init(_ object: _CocoaString, range: Range<Int>, isSlice: Bool) { |
| self.object = object |
| self.range = range |
| self.isSlice = isSlice |
| } |
| |
| @inline(never) |
| init(_ object: _CocoaString) { |
| let count = _stdlib_binary_CFStringGetLength(object) |
| self.init(object, count: count) |
| } |
| |
| @inlinable |
| @usableFromInline |
| init(_ object: _CocoaString, count: Int) { |
| self.init(object, range: 0..<count, isSlice: false) |
| } |
| #else |
| @inlinable |
| @usableFromInline |
| init(_ object: _OpaqueString, range: Range<Int>, isSlice: Bool) { |
| self.object = object |
| self.range = range |
| self.isSlice = isSlice |
| } |
| |
| @inline(never) |
| init(_ object: _OpaqueString) { |
| self.init(object, count: object.length) |
| } |
| |
| @inlinable |
| @usableFromInline |
| init(_ object: _OpaqueString, count: Int) { |
| self.init(object, range: 0..<count, isSlice: false) |
| } |
| #endif |
| } |
| |
| extension _UnmanagedOpaqueString : Sequence { |
| typealias Element = UTF16.CodeUnit |
| |
| @inlinable |
| @usableFromInline |
| func makeIterator() -> Iterator { |
| return Iterator(self, startingAt: range.lowerBound) |
| } |
| |
| @inlinable // FIXME(sil-serialize-all) |
| @usableFromInline // FIXME(sil-serialize-all) |
| internal func makeIterator(startingAt position: Int) -> Iterator { |
| return Iterator(self, startingAt: position) |
| } |
| |
| @usableFromInline |
| @_fixed_layout |
| struct Iterator : IteratorProtocol { |
| internal typealias Element = UTF16.CodeUnit |
| |
| #if _runtime(_ObjC) // FIXME unify |
| @usableFromInline |
| internal let _object: _CocoaString |
| #else |
| @usableFromInline |
| internal let _object: _OpaqueString |
| #endif |
| |
| @usableFromInline |
| internal var _range: Range<Int> |
| |
| @usableFromInline |
| internal var _buffer = _FixedArray16<Element>() |
| |
| @usableFromInline |
| internal var _bufferIndex: Int8 = 0 |
| |
| @inlinable |
| @usableFromInline |
| init(_ string: _UnmanagedOpaqueString, startingAt start: Int) { |
| self._object = string.object |
| self._range = start..<string.range.upperBound |
| } |
| |
| @inlinable |
| @usableFromInline |
| @inline(__always) |
| mutating func next() -> Element? { |
| if _fastPath(_bufferIndex < _buffer.count) { |
| let result = _buffer[Int(_bufferIndex)] |
| _bufferIndex += 1 |
| return result |
| } |
| if _slowPath(_range.isEmpty) { return nil } |
| return _nextOnSlowPath() |
| } |
| |
| @usableFromInline |
| @inline(never) |
| mutating func _nextOnSlowPath() -> Element { |
| // Fill buffer |
| _sanityCheck(!_range.isEmpty) |
| let end = Swift.min( |
| _range.lowerBound + _buffer.capacity, |
| _range.upperBound) |
| let r: Range<Int> = _range.lowerBound..<end |
| let opaque = _UnmanagedOpaqueString(_object, range: r, isSlice: true) |
| _buffer.count = r.count |
| _buffer.withUnsafeMutableBufferPointer { b in |
| _sanityCheck(b.count == r.count) |
| opaque._copy(into: b) |
| } |
| _bufferIndex = 1 |
| _range = r.upperBound ..< _range.upperBound |
| _fixLifetime(_object) |
| return _buffer[0] |
| } |
| } |
| } |
| |
| extension _UnmanagedOpaqueString : RandomAccessCollection { |
| internal typealias IndexDistance = Int |
| internal typealias Indices = Range<Index> |
| internal typealias SubSequence = _UnmanagedOpaqueString |
| |
| @_fixed_layout |
| @usableFromInline |
| struct Index : Strideable { |
| @usableFromInline |
| internal var _value: Int |
| |
| @usableFromInline |
| @inlinable |
| @inline(__always) |
| init(_ value: Int) { |
| self._value = value |
| } |
| |
| @usableFromInline |
| @inlinable |
| @inline(__always) |
| func distance(to other: Index) -> Int { |
| return other._value - self._value |
| } |
| |
| @usableFromInline |
| @inlinable |
| @inline(__always) |
| func advanced(by n: Int) -> Index { |
| return Index(_value + n) |
| } |
| } |
| |
| @usableFromInline |
| @inlinable |
| var startIndex: Index { |
| return Index(range.lowerBound) |
| } |
| |
| @usableFromInline |
| @inlinable |
| var endIndex: Index { |
| return Index(range.upperBound) |
| } |
| |
| @usableFromInline |
| @inlinable |
| var count: Int { |
| return range.count |
| } |
| |
| @usableFromInline |
| @inlinable // FIXME(sil-serialize-all) |
| subscript(position: Index) -> UTF16.CodeUnit { |
| _sanityCheck(position._value >= range.lowerBound) |
| _sanityCheck(position._value < range.upperBound) |
| #if _runtime(_ObjC) // FIXME unify |
| return _cocoaStringSubscript(object, position._value) |
| #else |
| return object.character(at: position._value) |
| #endif |
| } |
| |
| @usableFromInline |
| @inlinable // FIXME(sil-serialize-all) |
| subscript(bounds: Range<Index>) -> _UnmanagedOpaqueString { |
| _sanityCheck(bounds.lowerBound._value >= range.lowerBound) |
| _sanityCheck(bounds.upperBound._value <= range.upperBound) |
| let b: Range<Int> = bounds.lowerBound._value ..< bounds.upperBound._value |
| let newSlice = self.isSlice || b.count != range.count |
| return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice) |
| } |
| } |
| |
| extension _UnmanagedOpaqueString : _StringVariant { |
| internal typealias Encoding = Unicode.UTF16 |
| internal typealias CodeUnit = Encoding.CodeUnit |
| |
| @inlinable |
| @usableFromInline |
| var isASCII: Bool { |
| @inline(__always) get { return false } |
| } |
| |
| @inlinable |
| @usableFromInline |
| @inline(__always) |
| func _boundsCheck(_ i: Index) { |
| _precondition(i._value >= range.lowerBound && i._value < range.upperBound, |
| "String index is out of bounds") |
| } |
| |
| @inlinable |
| @usableFromInline |
| @inline(__always) |
| func _boundsCheck(_ range: Range<Index>) { |
| _precondition( |
| range.lowerBound._value >= self.range.lowerBound && |
| range.upperBound._value <= self.range.upperBound, |
| "String index range is out of bounds") |
| } |
| |
| @inlinable |
| @usableFromInline |
| @inline(__always) |
| func _boundsCheck(offset: Int) { |
| _precondition(offset >= 0 && offset < range.count, |
| "String index is out of bounds") |
| } |
| |
| @inlinable |
| @usableFromInline |
| @inline(__always) |
| func _boundsCheck(offsetRange range: Range<Int>) { |
| _precondition(range.lowerBound >= 0 && range.upperBound <= count, |
| "String index range is out of bounds") |
| } |
| |
| @usableFromInline |
| @inlinable // FIXME(sil-serialize-all) |
| subscript(offset: Int) -> UTF16.CodeUnit { |
| _sanityCheck(offset >= 0 && offset < count) |
| #if _runtime(_ObjC) // FIXME unify |
| return _cocoaStringSubscript(object, range.lowerBound + offset) |
| #else |
| return object.character(at: range.lowerBound + offset) |
| #endif |
| } |
| |
| @usableFromInline |
| @inlinable // FIXME(sil-serialize-all) |
| subscript(offsetRange: Range<Int>) -> _UnmanagedOpaqueString { |
| _sanityCheck(offsetRange.lowerBound >= 0) |
| _sanityCheck(offsetRange.upperBound <= range.count) |
| let b: Range<Int> = |
| range.lowerBound + offsetRange.lowerBound ..< |
| range.lowerBound + offsetRange.upperBound |
| let newSlice = self.isSlice || b.count != range.count |
| return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice) |
| } |
| |
| @inlinable // FIXME(sil-serialize-all) |
| @usableFromInline // FIXME(sil-serialize-all) |
| internal subscript(offsetRange: PartialRangeUpTo<Int>) -> SubSequence { |
| _sanityCheck(offsetRange.upperBound <= range.count) |
| let b: Range<Int> = |
| range.lowerBound ..< |
| range.lowerBound + offsetRange.upperBound |
| let newSlice = self.isSlice || b.count != range.count |
| return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice) |
| } |
| |
| @inlinable // FIXME(sil-serialize-all) |
| @usableFromInline // FIXME(sil-serialize-all) |
| internal subscript(offsetRange: PartialRangeThrough<Int>) -> SubSequence { |
| _sanityCheck(offsetRange.upperBound <= range.count) |
| let b: Range<Int> = |
| range.lowerBound ..< |
| range.lowerBound + offsetRange.upperBound + 1 |
| let newSlice = self.isSlice || b.count != range.count |
| return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice) |
| } |
| |
| @inlinable // FIXME(sil-serialize-all) |
| @usableFromInline // FIXME(sil-serialize-all) |
| internal subscript(offsetRange: PartialRangeFrom<Int>) -> SubSequence { |
| _sanityCheck(offsetRange.lowerBound < range.count) |
| let b: Range<Int> = |
| range.lowerBound + offsetRange.lowerBound ..< |
| range.upperBound |
| let newSlice = self.isSlice || b.count != range.count |
| return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice) |
| } |
| |
| @inlinable // FIXME(sil-serialize-all) |
| @usableFromInline // FIXME(sil-serialize-all) |
| internal func _copy( |
| into dest: UnsafeMutableBufferPointer<UTF16.CodeUnit> |
| ) { |
| _sanityCheck(dest.count >= range.count) |
| guard range.count > 0 else { return } |
| #if _runtime(_ObjC) // FIXME unify |
| _cocoaStringCopyCharacters( |
| from: object, |
| range: range, |
| into: dest.baseAddress!) |
| #else |
| object.copyCodeUnits(from: range, into: dest.baseAddress!) |
| #endif |
| } |
| |
| @inlinable // FIXME(sil-serialize-all) |
| @usableFromInline // FIXME(sil-serialize-all) |
| internal func _copy<TargetCodeUnit>( |
| into dest: UnsafeMutableBufferPointer<TargetCodeUnit> |
| ) |
| where TargetCodeUnit : FixedWidthInteger & UnsignedInteger { |
| guard TargetCodeUnit.bitWidth == 16 else { |
| _sanityCheckFailure("Narrowing copy from opaque strings is not implemented") |
| } |
| _sanityCheck(dest.count >= range.count) |
| guard range.count > 0 else { return } |
| let d = UnsafeMutableRawPointer(dest.baseAddress!) |
| .assumingMemoryBound(to: UTF16.CodeUnit.self) |
| #if _runtime(_ObjC) // FIXME unify |
| _cocoaStringCopyCharacters(from: object, range: range, into: d) |
| #else |
| object.copyCodeUnits(from: range, into: d) |
| #endif |
| } |
| |
| @usableFromInline // FIXME(sil-serialize-all) |
| @_fixed_layout // FIXME(resilience) |
| internal struct UnicodeScalarIterator : IteratorProtocol { |
| var _base: _UnmanagedOpaqueString.Iterator |
| var _peek: UTF16.CodeUnit? |
| |
| @usableFromInline // FIXME(sil-serialize-all) |
| init(_ base: _UnmanagedOpaqueString) { |
| self._base = base.makeIterator() |
| self._peek = _base.next() |
| } |
| |
| @usableFromInline // FIXME(sil-serialize-all) |
| mutating func next() -> Unicode.Scalar? { |
| if _slowPath(_peek == nil) { return nil } |
| let u0 = _peek._unsafelyUnwrappedUnchecked |
| _peek = _base.next() |
| if _fastPath(UTF16._isScalar(u0)) { |
| return Unicode.Scalar(_unchecked: UInt32(u0)) |
| } |
| if UTF16.isLeadSurrogate(u0) && _peek != nil { |
| let u1 = _peek._unsafelyUnwrappedUnchecked |
| if UTF16.isTrailSurrogate(u1) { |
| _peek = _base.next() |
| return UTF16._decodeSurrogates(u0, u1) |
| } |
| } |
| return Unicode.Scalar._replacementCharacter |
| } |
| } |
| |
| @usableFromInline // FIXME(sil-serialize-all) |
| @inline(never) |
| func makeUnicodeScalarIterator() -> UnicodeScalarIterator { |
| return UnicodeScalarIterator(self) |
| } |
| } |
| |
| #if _runtime(_ObjC) |
| extension _UnmanagedOpaqueString { |
| @usableFromInline |
| @inline(never) |
| internal func cocoaSlice() -> _CocoaString { |
| guard isSlice else { return object } |
| // FIXME: This usually copies storage; maybe add an NSString subclass |
| // for opaque slices? |
| return _cocoaStringSlice(object, range) |
| } |
| } |
| #endif |