Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stdlib] Add StaticString.UnicodeScalarView #277

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions stdlib/public/core/CMakeLists.txt
Expand Up @@ -127,6 +127,7 @@ set(SWIFTLIB_SOURCES
Mirror.swift
Process.swift
SliceBuffer.swift
StaticStringUnicodeScalarView.swift
VarArgs.swift
Zip.swift
Prespecialized.swift
Expand Down
199 changes: 199 additions & 0 deletions stdlib/public/core/StaticStringUnicodeScalarView.swift
@@ -0,0 +1,199 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

extension StaticString {
/// The value of `self` as a collection of [Unicode scalar values](http://www.unicode.org/glossary/#unicode_scalar_value).
public var unicodeScalars: UnicodeScalarView {
get {
return UnicodeScalarView(self)
}
}

/// Construct the `StaticString` corresponding to the given
/// `UnicodeScalarView`.
public init(_ unicodeScalars: UnicodeScalarView) {
switch unicodeScalars._data {
case let .Pointer(ptr, isASCII):
self.init(start: ptr.baseAddress._rawValue, byteSize: ptr.count._builtinWordValue, isASCII: isASCII._value)
case .Scalar(let scalar):
self.init(unicodeScalar: unsafeBitCast(scalar.value, Int32.self)._value)
}
}

/// Construct the `StaticString` corresponding to the given
/// `UnicodeScalarView` slice.
public init(_ unicodeScalars: Slice<UnicodeScalarView>) {
UnicodeScalarView.Index._failEarlyRangeCheck2(
unicodeScalars.startIndex, rangeEnd: unicodeScalars.endIndex,
boundsStart: unicodeScalars._base.startIndex, boundsEnd: unicodeScalars._base.endIndex)
switch unicodeScalars._base._data {
case let .Pointer(ptr, isASCII):
self.init(
start: (ptr.baseAddress + unicodeScalars.startIndex._position)._rawValue,
byteSize: (unicodeScalars.endIndex._position - unicodeScalars.startIndex._position)._builtinWordValue,
isASCII: isASCII._value)
case .Scalar(let scalar):
if unicodeScalars.isEmpty {
self.init()
} else {
self.init(unicodeScalar: unsafeBitCast(scalar.value, Int32.self)._value)
}
}
}

/// A collection of [Unicode scalar values](http://www.unicode.org/glossary/#unicode_scalar_value) that
/// encode a `StaticString`.
public struct UnicodeScalarView : CollectionType, _Reflectable,
CustomStringConvertible, CustomDebugStringConvertible {
enum Data {
case Pointer(UnsafeBufferPointer<UInt8>, isASCII: Bool)
case Scalar(UnicodeScalar)
}

let _data: Data

init(_ _base: StaticString) {
if _base.hasPointerRepresentation {
let ptr = UnsafeBufferPointer(start: _base.utf8Start, count: Int(_base.byteSize))
_data = .Pointer(ptr, isASCII: _base.isASCII)
} else {
_data = .Scalar(_base.unicodeScalar)
}
}

/// A position in a `StaticString.UnicodeScalarView`.
public struct Index : BidirectionalIndexType, Comparable {
/// An index into the UTF-8 data of _base. If _base does not have a
/// pointer representation, then a position of 0 is startIndex and 1 is
/// endIndex.
let _position: Int
let _data: Data

init(_ _position: Int, _ _data: Data) {
self._position = _position
self._data = _data
}

/// Returns the next consecutive value after `self`.
///
/// - Requires: The next value is representable.
@warn_unused_result
public func successor() -> Index {
switch _data {
case .Pointer(let ptr, _):
let count = Int(UTF8._numTrailingBytes(ptr[_position]))
return Index(_position + count + 1, _data)
case .Scalar:
_precondition(_position == 0, "index points past StaticString end")
return Index(1, _data)
}
}

/// Returns the previous consecutive value before `self`.
///
/// - Requires: The previous value is representable.
@warn_unused_result
public func predecessor() -> Index {
_precondition(_position > 0, "index precedes StaticString start")
var position = _position - 1
if case .Pointer(let ptr, _) = _data {
while UTF8.isContinuation(ptr[position]) {
position -= 1
}
}
return Index(position, _data)
}
}

/// The position of the first `UnicodeScalar` if the `StaticString` is
/// non-empty; identical to `endIndex` otherwise.
public var startIndex: Index {
return Index(0, _data)
}

/// The "past the end" position.
///
/// `endIndex` is not a valid argument to `subscript`, and is always
/// reachable from `startIndex` by zero or more applications of
/// `successor()`.
public var endIndex: Index {
switch _data {
case .Pointer(let ptr, _):
return Index(ptr.endIndex, _data)
case .Scalar:
return Index(1, _data)
}
}

/// Returns `true` iff `self` is empty.
public var isEmpty: Bool {
switch _data {
case .Pointer(let ptr, _):
return ptr.isEmpty
case .Scalar:
return false
}
}

public subscript(position: Index) -> UnicodeScalar {
switch _data {
case let .Pointer(ptr, isASCII):
_precondition(position._position < ptr.endIndex, "subscript: index cannot be endIndex")
let start = ptr.baseAddress + position._position
if isASCII {
return UnicodeScalar(UInt32(start.memory))
}
let slice = UnsafeBufferPointer<UInt8>(start: start, count: ptr.endIndex - position._position)
var gen = slice.generate()
var decoder = UTF8()
switch decoder.decode(&gen) {
case .Result(let scalar): return scalar
default:
_sanityCheckFailure("StaticString UTF-8 decoding failed")
}
case .Scalar(let scalar):
_precondition(position._position == 0, "subscript: index cannot be endIndex")
return scalar
}
}

/// A textual representation of `self`.
public var description: String {
return StaticString(self).stringValue
}

/// A textual representation of `self`, suitable for debugging.
public var debugDescription: String {
return "StaticString.UnicodeScalarView(\(StaticString(self).debugDescription))"
}

public func _getMirror() -> _MirrorType {
return _reflect(StaticString(self).stringValue)
}
}
}

@warn_unused_result
public func ==(
lhs: StaticString.UnicodeScalarView.Index,
rhs: StaticString.UnicodeScalarView.Index
) -> Bool {
return lhs._position == rhs._position
}

@warn_unused_result
public func <(
lhs: StaticString.UnicodeScalarView.Index,
rhs: StaticString.UnicodeScalarView.Index
) -> Bool {
return lhs._position < rhs._position
}
94 changes: 93 additions & 1 deletion test/1_stdlib/StaticString.swift
Expand Up @@ -144,5 +144,97 @@ StaticStringTestSuite.test("UnicodeScalarRepresentation/byteSize")
strOpaque.byteSize
}

runAllTests()
StaticStringTestSuite.test("UnicodeScalarView/round-trip/UTF8") {
// round-tripping through UnicodeScalarView should return the same value
let str: StaticString = "абв"
let str2 = StaticString(str.unicodeScalars)
expectEqual(str.utf8Start, str2.utf8Start)
expectEqual(str.byteSize, str2.byteSize)
expectEqual(str.isASCII, str2.isASCII)
}

StaticStringTestSuite.test("UnicodeScalarView/round-trip/ASCII") {
// round-tripping through UnicodeScalarView should return the same value
let str: StaticString = "abc"
let str2 = StaticString(str.unicodeScalars)
expectEqual(str.utf8Start, str2.utf8Start)
expectEqual(str.byteSize, str2.byteSize)
expectEqual(str.isASCII, str2.isASCII)
}

StaticStringTestSuite.test("UnicodeScalarView/round-trip/Scalar") {
// round-tripping through UnicodeScalarView should return the same value
let str: StaticString = StaticString(_builtinUnicodeScalarLiteral: UInt32(0x5a)._value)
let str2 = StaticString(str.unicodeScalars)
expectEqual(str.hasPointerRepresentation, str2.hasPointerRepresentation)
expectEqual(str.unicodeScalar, str2.unicodeScalar)
}

StaticStringTestSuite.test("UnicodeScalarView/generate/NonEmpty") {
let str: StaticString = "абв"
expectEqual(["а", "б", "в"], Array(str.unicodeScalars))
}

StaticStringTestSuite.test("UnicodeScalarView/generate/Empty") {
let str: StaticString = ""
expectEqual([], Array(str.unicodeScalars))
}

StaticStringTestSuite.test("UnicodeScalarView/generate/Scalar") {
let str: StaticString = StaticString(_builtinUnicodeScalarLiteral: UInt32(0x5a)._value)
expectEqual(["Z"], Array(str.unicodeScalars))
}

StaticStringTestSuite.test("UnicodeScalarView/subscript/Pointer") {
let str: StaticString = "абв"
let scalars = str.unicodeScalars
expectEqual("а", scalars[scalars.startIndex])
expectEqual("б", scalars[scalars.startIndex.advancedBy(1)])
expectEqual("в", scalars[scalars.startIndex.advancedBy(2)])
expectEqual(scalars.endIndex, scalars.startIndex.advancedBy(3))
}

StaticStringTestSuite.test("UnicodeScalarView/subscript/Scalar") {
let str: StaticString = StaticString(_builtinUnicodeScalarLiteral: UInt32(0x5a)._value)
let scalars = str.unicodeScalars
expectEqual("Z", scalars[scalars.startIndex])
expectEqual(scalars.endIndex, scalars.startIndex.advancedBy(1))
}

StaticStringTestSuite.test("UnicodeScalarView/subscript/Empty") {
let str = StaticString()
let scalars = str.unicodeScalars
expectEqual(scalars.startIndex, scalars.endIndex)
}

StaticStringTestSuite.test("UnicodeScalarView/Slice/round-trip/NonEmpty/Pointer") {
let str: StaticString = "абв"
let scalars = str.unicodeScalars
let slice = scalars[scalars.startIndex.successor()...scalars.startIndex.successor()]
expectEqual("б", StaticString(slice).stringValue)
}

StaticStringTestSuite.test("UnicodeScalarView/Slice/round-trip/NonEmpty/Scalar") {
let str: StaticString = StaticString(_builtinUnicodeScalarLiteral: UInt32(0x5a)._value)
let scalars = str.unicodeScalars
let slice = scalars[scalars.indices]
expectEqual("Z", StaticString(slice).stringValue)
}

StaticStringTestSuite.test("UnicodeScalarView/Slice/round-trip/Empty/Pointer") {
let str: StaticString = "abc"
let scalars = str.unicodeScalars
let slice = scalars[scalars.startIndex..<scalars.startIndex]
expectEqual("", StaticString(slice).stringValue)
expectNotEqual(nil, StaticString(slice).utf8Start)
}

StaticStringTestSuite.test("UnicodeScalarView/Slice/round-trip/Empty/Scalar") {
let str: StaticString = StaticString(_builtinUnicodeScalarLiteral: UInt32(0x5a)._value)
let scalars = str.unicodeScalars
let slice = scalars[scalars.startIndex..<scalars.startIndex]
expectEqual("", StaticString(slice).stringValue)
expectNotEqual(nil, StaticString(slice).utf8Start)
}

runAllTests()