| // RUN: %target-run-simple-swift |
| // REQUIRES: executable_test |
| |
| import StdlibUnittest |
| |
| var StringIndexTests = TestSuite("StringIndexTests") |
| |
| enum SimpleString: String { |
| case smallASCII = "abcdefg" |
| case smallUnicode = "abรฉร๐" |
| case largeASCII = "012345678901234567890" |
| case largeUnicode = "abรฉร012345678901234567890๐" |
| case emoji = "๐๐๐คข๐คฎ๐ฉ๐ฟโ๐ค๐ง๐ปโโ๏ธ๐ง๐ปโโ๏ธ๐ฉโ๐ฉโ๐ฆโ๐ฆ" |
| } |
| |
| let simpleStrings: [String] = [ |
| SimpleString.smallASCII.rawValue, |
| SimpleString.smallUnicode.rawValue, |
| SimpleString.largeASCII.rawValue, |
| SimpleString.largeUnicode.rawValue, |
| SimpleString.emoji.rawValue, |
| "", |
| ] |
| |
| StringIndexTests.test("basic sanity checks") { |
| for s in simpleStrings { |
| let utf8 = Array(s.utf8) |
| let subUTF8 = Array(s[...].utf8) |
| let utf16 = Array(s.utf16) |
| let subUTF16 = Array(s[...].utf16) |
| let utf32 = Array(s.unicodeScalars.map { $0.value }) |
| let subUTF32 = Array(s[...].unicodeScalars.map { $0.value }) |
| |
| expectEqual(s, String(decoding: utf8, as: UTF8.self)) |
| expectEqual(s, String(decoding: subUTF8, as: UTF8.self)) |
| expectEqual(s, String(decoding: utf16, as: UTF16.self)) |
| expectEqual(s, String(decoding: subUTF16, as: UTF16.self)) |
| expectEqual(s, String(decoding: utf32, as: UTF32.self)) |
| expectEqual(s, String(decoding: subUTF32, as: UTF32.self)) |
| } |
| } |
| |
| StringIndexTests.test("view counts") { |
| func validateViewCount<View: BidirectionalCollection>( |
| _ view: View, for string: String, |
| stackTrace: SourceLocStack = SourceLocStack(), |
| showFrame: Bool = true, |
| file: String = #file, line: UInt = #line |
| ) where View.Element: Equatable, View.Index == String.Index { |
| |
| var stackTrace = stackTrace.pushIf(showFrame, file: file, line: line) |
| |
| let count = view.count |
| func expect(_ i: Int, |
| file: String = #file, line: UInt = #line |
| ) { |
| expectEqual(count, i, "for String: \(string)", |
| stackTrace: stackTrace.pushIf(showFrame, file: file, line: line), |
| showFrame: false) |
| } |
| |
| let reversedView = view.reversed() |
| |
| expect(Array(view).count) |
| expect(view.indices.count) |
| expect(view.indices.reversed().count) |
| expect(reversedView.indices.count) |
| expect(view.distance(from: view.startIndex, to: view.endIndex)) |
| expect(reversedView.distance( |
| from: reversedView.startIndex, to: reversedView.endIndex)) |
| |
| // Access the elements from the indices |
| expectEqual(Array(view), view.indices.map { view[$0] }) |
| expectEqual( |
| Array(reversedView), reversedView.indices.map { reversedView[$0] }) |
| |
| let indicesArray = Array<String.Index>(view.indices) |
| for i in 0..<indicesArray.count { |
| var idx = view.startIndex |
| idx = view.index(idx, offsetBy: i) |
| expectEqual(indicesArray[i], idx) |
| } |
| } |
| |
| for s in simpleStrings { |
| validateViewCount(s, for: s) |
| validateViewCount(s.utf8, for: s) |
| validateViewCount(s.utf16, for: s) |
| validateViewCount(s.unicodeScalars, for: s) |
| |
| validateViewCount(s[...], for: s) |
| validateViewCount(s[...].utf8, for: s) |
| validateViewCount(s[...].utf16, for: s) |
| validateViewCount(s[...].unicodeScalars, for: s) |
| } |
| } |
| |
| StringIndexTests.test("interchange") { |
| // Basic index alignment |
| |
| func validateIndices(_ s: String) { |
| for idx in s.utf8.indices { |
| let char = s.utf8[idx] |
| |
| // ASCII or leading code unit in the scalar |
| if char <= 0x7F || char >= 0b1100_0000 { |
| expectEqual(idx, idx.samePosition(in: s.unicodeScalars)) |
| expectEqual(idx, idx.samePosition(in: s.utf16)) |
| |
| // ASCII |
| if char <= 0x7F { |
| expectEqual(UInt16(char), s.utf16[idx]) |
| expectEqual(UInt32(char), s.unicodeScalars[idx].value) |
| } |
| } else { |
| // Continuation code unit |
| assert(char & 0b1100_0000 == 0b1000_0000) |
| expectNil(idx.samePosition(in: s)) |
| expectNil(idx.samePosition(in: s.utf16)) |
| expectNil(idx.samePosition(in: s.unicodeScalars)) |
| } |
| } |
| } |
| |
| for s in simpleStrings { |
| validateIndices(s) |
| } |
| } |
| |
| StringIndexTests.test("UTF-16 Offsets") { |
| func validateOffsets(_ s: String) { |
| let end = s.endIndex |
| let utf16Count = s.utf16.count |
| |
| expectEqual(end, String.Index(utf16Offset: utf16Count, in: s)) |
| expectEqual(end, String.Index(utf16Offset: utf16Count, in: s[...])) |
| |
| let pastEnd = String.Index(utf16Offset: utf16Count+1, in: s) |
| |
| expectNotEqual(end, pastEnd) |
| expectEqual(pastEnd, String.Index(utf16Offset: utf16Count+1, in: s[...])) |
| expectEqual(pastEnd, String.Index(utf16Offset: utf16Count+2, in: s)) |
| expectEqual(pastEnd, String.Index(utf16Offset: -1, in: s)) |
| expectEqual( |
| pastEnd, String.Index(utf16Offset: Swift.max(1, utf16Count), in: s.dropFirst())) |
| |
| let utf16Indices = Array(s.utf16.indices) |
| expectEqual(utf16Count, utf16Indices.count) |
| for i in 0..<utf16Indices.count { |
| let idx = String.Index(utf16Offset: i, in: s) |
| expectEqual(utf16Indices[i], idx) |
| expectEqual(i, idx.utf16Offset(in: s)) |
| expectEqual(i, idx.utf16Offset(in: s[...])) |
| |
| if i < s.dropLast().utf16.count { |
| expectEqual( |
| utf16Indices[i], String.Index(utf16Offset: i, in: s.dropLast())) |
| expectEqual(i, idx.utf16Offset(in: s.dropLast())) |
| } else if i == s.dropLast().utf16.count { |
| expectEqual( |
| utf16Indices[i], String.Index(utf16Offset: i, in: s.dropLast())) |
| } else { |
| expectNotEqual( |
| utf16Indices[i], String.Index(utf16Offset: i, in: s.dropLast())) |
| } |
| } |
| } |
| |
| for s in simpleStrings { |
| validateOffsets(s) |
| } |
| } |
| |
| func swift5ScalarAlign(_ idx: String.Index, in str: String) -> String.Index { |
| var idx = idx |
| while str.utf8[idx] & 0xC0 == 0x80 { str.utf8.formIndex(before: &idx) } |
| return idx |
| } |
| |
| StringIndexTests.test("Scalar Align UTF-8 indices") { |
| // TODO: Test a new aligning API when we add it. For now, we |
| // test scalar-aligning UTF-8 indices |
| |
| let str = "a๐" |
| let subScalarIdx = str.utf8.index(str.utf8.startIndex, offsetBy: 2) |
| |
| let roundedIdx = swift5ScalarAlign(subScalarIdx, in: str) |
| expectEqual(1, roundedIdx.utf16Offset(in: str)) |
| |
| let roundedIdx2 = str.utf8[...subScalarIdx].lastIndex { $0 & 0xC0 != 0x80 } |
| expectEqual(roundedIdx, roundedIdx2) |
| |
| var roundedIdx3 = subScalarIdx |
| while roundedIdx3.samePosition(in: str.unicodeScalars) == nil { |
| str.utf8.formIndex(before: &roundedIdx3) |
| } |
| expectEqual(roundedIdx, roundedIdx3) |
| } |
| |
| #if _runtime(_ObjC) |
| import Foundation |
| StringIndexTests.test("String.Index(_:within) / Range<String.Index>(_:in:)") { |
| guard #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) else { |
| return |
| } |
| |
| let str = simpleStrings.joined() |
| let substr = str[...] |
| for idx in str.utf8.indices { |
| expectEqual( |
| String.Index(idx, within: str), String.Index(idx, within: substr)) |
| } |
| |
| expectNil(String.Index(str.startIndex, within: str.dropFirst())) |
| expectNil(String.Index(str.endIndex, within: str.dropLast())) |
| expectNotNil(String.Index(str.startIndex, within: str)) |
| expectNotNil(String.Index(str.endIndex, within: str)) |
| |
| let utf16Count = str.utf16.count |
| let utf16Indices = Array(str.utf16.indices) + [str.utf16.endIndex] |
| for location in 0..<utf16Count { |
| for length in 0...(utf16Count - location) { |
| let strLB = String.Index(utf16Indices[location], within: str) |
| let substrLB = String.Index(utf16Indices[location], within: substr) |
| let strUB = String.Index(utf16Indices[location+length], within: str) |
| let substrUB = String.Index(utf16Indices[location+length], within: substr) |
| expectEqual(strLB, substrLB) |
| expectEqual(strUB, substrUB) |
| |
| let nsRange = NSRange(location: location, length: length) |
| let strRange = Range<String.Index>(nsRange, in: str) |
| let substrRange = Range<String.Index>(nsRange, in: substr) |
| |
| expectEqual(strRange, substrRange) |
| guard strLB != nil && strUB != nil else { |
| expectNil(strRange) |
| continue |
| } |
| expectEqual(strRange, Range(uncheckedBounds: (strLB!, strUB!))) |
| } |
| } |
| } |
| |
| StringIndexTests.test("Misaligned") { |
| // Misaligned indices were fixed in 5.1 |
| guard _hasSwift_5_1() else { return } |
| |
| func doIt(_ str: String) { |
| let characterIndices = Array(str.indices) |
| let scalarIndices = Array(str.unicodeScalars.indices) + [str.endIndex] |
| let utf8Indices = Array(str.utf8.indices) |
| |
| var lastScalarI = 0 |
| for i in 1..<utf8Indices.count { |
| let idx = utf8Indices[i] |
| |
| // Skip aligned indices |
| guard idx < scalarIndices[lastScalarI + 1] else { |
| assert(idx == scalarIndices[lastScalarI + 1]) |
| lastScalarI += 1 |
| continue |
| } |
| expectTrue(UTF8.isContinuation(str.utf8[idx])) |
| |
| let lastScalarIdx = scalarIndices[lastScalarI] |
| |
| // Check aligning-down |
| expectEqual(str[lastScalarIdx], str[idx]) |
| expectEqual(str.utf16[lastScalarIdx], str.utf16[idx]) |
| expectEqual(str.unicodeScalars[lastScalarIdx], str.unicodeScalars[idx]) |
| |
| // Check distance |
| let (start, end) = (str.startIndex, str.endIndex) |
| if characterIndices.contains(lastScalarIdx) { |
| expectEqual(0, str.distance(from: lastScalarIdx, to: idx)) |
| expectEqual(str[..<idx].count, str.distance(from: start, to: idx)) |
| expectEqual(str[idx...].count, str.distance(from: idx, to: end)) |
| } |
| expectEqual( |
| 0, str.unicodeScalars.distance(from: lastScalarIdx, to: idx)) |
| expectEqual( |
| str.unicodeScalars[..<idx].count, |
| str.unicodeScalars.distance(from: start, to: idx)) |
| expectEqual( |
| str.unicodeScalars[idx...].count, |
| str.unicodeScalars.distance(from: idx, to: end)) |
| |
| expectEqual(0, str.utf16.distance(from: lastScalarIdx, to: idx)) |
| expectEqual( |
| str.utf16[..<idx].count, str.utf16.distance(from: start, to: idx)) |
| expectEqual( |
| str.utf16[idx...].count, str.utf16.distance(from: idx, to: end)) |
| } |
| } |
| |
| let nsstring: NSString = "aะพะดะธde\u{301}ๆฅ๐งโโ๏ธ" |
| doIt(nsstring as String) |
| |
| let string = "aะพะดะธde\u{301}ๆฅ๐งโโ๏ธ" |
| doIt(string) |
| } |
| |
| #endif // _runtime(_ObjC) |
| |
| StringIndexTests.test("Exhaustive Index Interchange") { |
| // Exhaustively test aspects of string index interchange |
| func testInterchange( |
| _ str: String, |
| stackTrace: SourceLocStack = SourceLocStack(), |
| showFrame: Bool = true, |
| file: String = #file, |
| line: UInt = #line |
| ) { |
| guard #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) else { |
| return |
| } |
| |
| let stackTrace = stackTrace.pushIf(showFrame, file: file, line: line) |
| func expect( |
| _ condition: @autoclosure () -> Bool, |
| _ message: String = "", |
| file: String = #file, |
| line: UInt = #line |
| ) { |
| expectTrue(condition(), message, |
| stackTrace: stackTrace, showFrame: showFrame, |
| file: file, line: line) |
| } |
| |
| var curCharIdx = str.startIndex |
| var curScalarIdx = str.startIndex |
| var curUTF8Idx = str.startIndex |
| var curUTF16Idx = str.startIndex |
| |
| while curCharIdx < str.endIndex { |
| let curChar = str[curCharIdx] |
| expect(curChar == str[curScalarIdx]) |
| expect(curChar == str[curUTF8Idx]) |
| expect(curChar == str[curUTF16Idx]) |
| |
| // Advance the character index once and have the scalar index catch up |
| str.formIndex(after: &curCharIdx) |
| |
| let scalarStartIdx = curScalarIdx |
| defer { |
| let sub = str[scalarStartIdx..<curScalarIdx] |
| expect(sub.count == 1) |
| expect(sub.first! == curChar) |
| expect(str.distance(from: scalarStartIdx, to: curScalarIdx) == 1) |
| } |
| |
| while curScalarIdx < curCharIdx { |
| let scalarStartIdx = curScalarIdx |
| let curScalar = str.unicodeScalars[curScalarIdx] |
| let curSubChar = str[curScalarIdx] |
| |
| // If there is a Character prior to this scalar, remember it and check |
| // that misalignd code unit indices also produce it. |
| let scalarPriorCharacter: Character? |
| if scalarStartIdx == str.startIndex { |
| scalarPriorCharacter = nil |
| } else { |
| scalarPriorCharacter = str[str.index(before: scalarStartIdx)] |
| } |
| |
| // Advance the scalar index once and have the code unit indices catch up |
| str.unicodeScalars.formIndex(after: &curScalarIdx) |
| |
| |
| let utf8StartIdx = curUTF8Idx |
| defer { |
| let sub = str.unicodeScalars[utf8StartIdx..<curUTF8Idx] |
| expect(sub.count == 1) |
| expect(sub.first! == curScalar) |
| expect(str.unicodeScalars.distance( |
| from: utf8StartIdx, to: curUTF8Idx) == 1) |
| expect(str.utf8.distance( |
| from: utf8StartIdx, to: curUTF8Idx) == curScalar.utf8.count) |
| } |
| |
| while curUTF8Idx < curScalarIdx { |
| expect(curScalar == str.unicodeScalars[curUTF8Idx]) |
| expect(curSubChar == str[curUTF8Idx]) |
| expect(!UTF16.isTrailSurrogate(str.utf16[curUTF8Idx])) |
| expect(utf8StartIdx == str[curUTF8Idx...].startIndex) |
| expect(str[utf8StartIdx..<curUTF8Idx].isEmpty) |
| expect(0 == str.utf16.distance(from: utf8StartIdx, to: curUTF8Idx)) |
| |
| if let scalarPrior = scalarPriorCharacter { |
| expect(scalarPrior == str[str.index(before: curUTF8Idx)]) |
| } |
| |
| str.utf8.formIndex(after: &curUTF8Idx) |
| } |
| expect(curUTF8Idx == curScalarIdx) |
| |
| var utf8RevIdx = curUTF8Idx |
| while utf8RevIdx > utf8StartIdx { |
| str.utf8.formIndex(before: &utf8RevIdx) |
| |
| expect(curScalar == str.unicodeScalars[utf8RevIdx]) |
| expect(curSubChar == str[utf8RevIdx]) |
| expect(!UTF16.isTrailSurrogate(str.utf16[utf8RevIdx])) |
| expect(utf8StartIdx == str[utf8RevIdx...].startIndex) |
| expect(str[utf8StartIdx..<utf8RevIdx].isEmpty) |
| expect(0 == str.utf16.distance(from: utf8StartIdx, to: utf8RevIdx)) |
| } |
| expect(utf8RevIdx == utf8StartIdx) |
| |
| let utf16StartIdx = curUTF16Idx |
| defer { |
| let sub = str.unicodeScalars[utf16StartIdx..<curUTF16Idx] |
| expect(sub.count == 1) |
| expect(sub.first! == curScalar) |
| expect(str.unicodeScalars.distance( |
| from: utf16StartIdx, to: curUTF16Idx) == 1) |
| expect(str.utf16.distance( |
| from: utf16StartIdx, to: curUTF16Idx) == curScalar.utf16.count) |
| } |
| |
| while curUTF16Idx < curScalarIdx { |
| expect(curScalar == str.unicodeScalars[curUTF16Idx]) |
| expect(curSubChar == str[curUTF16Idx]) |
| expect(!UTF8.isContinuation(str.utf8[curUTF16Idx])) |
| expect(utf16StartIdx == str[curUTF16Idx...].startIndex) |
| expect(str[utf16StartIdx..<curUTF16Idx].isEmpty) |
| expect(0 == str.utf8.distance(from: utf16StartIdx, to: curUTF16Idx)) |
| |
| if let scalarPrior = scalarPriorCharacter { |
| expect(scalarPrior == str[str.index(before: curUTF16Idx)]) |
| } |
| |
| str.utf16.formIndex(after: &curUTF16Idx) |
| } |
| expect(curUTF16Idx == curScalarIdx) |
| |
| var utf16RevIdx = curUTF16Idx |
| while utf16RevIdx > utf16StartIdx { |
| str.utf16.formIndex(before: &utf16RevIdx) |
| |
| expect(curScalar == str.unicodeScalars[utf16RevIdx]) |
| expect(curSubChar == str[utf16RevIdx]) |
| expect(!UTF8.isContinuation(str.utf8[utf16RevIdx])) |
| expect(utf16StartIdx == str[utf16RevIdx...].startIndex) |
| expect(str[utf16StartIdx..<utf16RevIdx].isEmpty) |
| expect(0 == str.utf8.distance(from: utf16StartIdx, to: utf16RevIdx)) |
| } |
| expect(utf16RevIdx == utf16StartIdx) |
| |
| } |
| } |
| } |
| |
| testInterchange("abc\r\ndefg") |
| |
| #if _runtime(_ObjC) |
| testInterchange(("abc\r\ndefg" as NSString) as String) |
| #endif // _runtime(_ObjC) |
| |
| testInterchange("ab\r\ncะดe\u{301}ๆฅ๐งโโ๏ธx๐งx๐ณ๏ธโ๐๐บ๐ธ๐จ๐ฆ") |
| |
| #if _runtime(_ObjC) |
| testInterchange(("ab\r\ncะดe\u{301}ๆฅ๐งโโ๏ธx๐งx๐ณ๏ธโ๐๐บ๐ธ๐จ๐ฆ" as NSString) as String) |
| #endif // _runtime(_ObjC) |
| } |
| |
| runAllTests() |