Skip to content

Commit e60de81

Browse files
Sendable Config
1 parent 1a926a2 commit e60de81

17 files changed

+1238
-384
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ DerivedData/
99
.swiftpm/config/registries.json
1010
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
1111
.netrc
12-
.idea
12+
.idea
13+
.index-build
14+
*.out

Sources/Hub/BinaryDistinct.swift

+348
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
//
2+
// BinaryDistinctString.swift
3+
// swift-transformers
4+
//
5+
// Created by Piotr Kowalczuk on 06.03.25.
6+
//
7+
8+
import Foundation
9+
10+
/// BinaryDistinctString helps to overcome limitations of both String and NSString types. Where the prior is performing unicode normalization and the following is not Sendable. For more reference [Modifying-and-Comparing-Strings](https://developer.apple.com/documentation/swift/string#Modifying-and-Comparing-Strings).
11+
public struct BinaryDistinctString: Equatable, Hashable, Sendable, Comparable, CustomStringConvertible, ExpressibleByStringLiteral {
12+
public let value: [UInt16]
13+
14+
public var nsString: NSString {
15+
return String(utf16CodeUnits: self.value, count: self.value.count) as NSString
16+
}
17+
18+
public var string: String {
19+
return String(self.nsString)
20+
}
21+
22+
public var count: Int {
23+
self.string.count
24+
}
25+
26+
/// Satisfies ``CustomStringConvertible`` protocol.
27+
public var description: String {
28+
return self.string
29+
}
30+
31+
public init(_ bytes: [UInt16]) {
32+
self.value = bytes
33+
}
34+
35+
public init(_ str: NSString) {
36+
self.value = Array(str as String).flatMap { $0.utf16 }
37+
}
38+
39+
public init(_ str: String) {
40+
self.init(str as NSString)
41+
}
42+
43+
public init(_ character: BinaryDistinctCharacter) {
44+
self.value = character.bytes
45+
}
46+
47+
public init(_ characters: [BinaryDistinctCharacter]) {
48+
var data: [UInt16] = []
49+
for character in characters {
50+
data.append(contentsOf: character.bytes)
51+
}
52+
self.value = data
53+
}
54+
55+
/// Satisfies ``ExpressibleByStringLiteral`` protocol.
56+
public init(stringLiteral value: String) {
57+
self.init(value)
58+
}
59+
60+
public static func == (lhs: BinaryDistinctString, rhs: BinaryDistinctString) -> Bool {
61+
return lhs.value == rhs.value
62+
}
63+
64+
public static func < (lhs: BinaryDistinctString, rhs: BinaryDistinctString) -> Bool {
65+
return lhs.value.lexicographicallyPrecedes(rhs.value)
66+
}
67+
68+
public static func + (lhs: BinaryDistinctString, rhs: BinaryDistinctString) -> BinaryDistinctString {
69+
return BinaryDistinctString(lhs.value + rhs.value)
70+
}
71+
72+
public func hasPrefix(_ prefix: BinaryDistinctString) -> Bool {
73+
guard prefix.value.count <= self.value.count else { return false }
74+
return self.value.starts(with: prefix.value)
75+
}
76+
77+
public func hasSuffix(_ suffix: BinaryDistinctString) -> Bool {
78+
guard suffix.value.count <= self.value.count else { return false }
79+
return self.value.suffix(suffix.value.count) == suffix.value
80+
}
81+
82+
public func lowercased() -> BinaryDistinctString {
83+
.init(self.string.lowercased())
84+
}
85+
86+
public func replacingOccurrences(of: Self, with: Self) -> BinaryDistinctString {
87+
return BinaryDistinctString(self.string.replacingOccurrences(of: of.string, with: with.string))
88+
}
89+
}
90+
91+
extension BinaryDistinctString {
92+
public typealias Index = Int // Treat indices as integers
93+
94+
public var startIndex: Index { return 0 }
95+
public var endIndex: Index { return self.count }
96+
97+
public func index(_ i: Index, offsetBy distance: Int) -> Index {
98+
let newIndex = i + distance
99+
guard newIndex >= 0, newIndex <= self.count else {
100+
fatalError("Index out of bounds")
101+
}
102+
return newIndex
103+
}
104+
105+
public func index(_ i: Index, offsetBy distance: Int, limitedBy limit: Index) -> Index? {
106+
let newIndex = i + distance
107+
return newIndex <= limit ? newIndex : nil
108+
}
109+
}
110+
111+
extension BinaryDistinctString: Sequence {
112+
public func makeIterator() -> AnyIterator<BinaryDistinctCharacter> {
113+
var iterator = self.string.makeIterator() // Use native Swift String iterator
114+
115+
return AnyIterator {
116+
guard let char = iterator.next() else { return nil }
117+
return BinaryDistinctCharacter(char)
118+
}
119+
}
120+
}
121+
122+
extension BinaryDistinctString {
123+
public subscript(bounds: PartialRangeFrom<Int>) -> BinaryDistinctString {
124+
get {
125+
let validRange = bounds.lowerBound..<self.value.count // Convert to Range<Int>
126+
return self[validRange]
127+
}
128+
}
129+
130+
/// Returns a slice of the `BinaryDistinctString` while ensuring correct rune (grapheme cluster) boundaries.
131+
public subscript(bounds: Range<Int>) -> BinaryDistinctString {
132+
get {
133+
guard bounds.lowerBound >= 0, bounds.upperBound <= self.count else {
134+
fatalError("Index out of bounds")
135+
}
136+
137+
let utf8Bytes = self.value
138+
var byteIndices: [Int] = []
139+
140+
// Decode UTF-8 manually to find rune start positions
141+
var currentByteIndex = 0
142+
for (index, scalar) in self.string.unicodeScalars.enumerated() {
143+
if index == bounds.lowerBound {
144+
byteIndices.append(currentByteIndex)
145+
}
146+
currentByteIndex += scalar.utf8.count
147+
if index == bounds.upperBound - 1 {
148+
byteIndices.append(currentByteIndex)
149+
break
150+
}
151+
}
152+
153+
// Extract the byte range
154+
let startByteIndex = byteIndices.first ?? 0
155+
let endByteIndex = byteIndices.last ?? utf8Bytes.count
156+
157+
let slicedBytes = Array(utf8Bytes[startByteIndex..<endByteIndex])
158+
return BinaryDistinctString(slicedBytes)
159+
}
160+
}
161+
}
162+
163+
public struct BinaryDistinctDictionary<V>: Collection, ExpressibleByDictionaryLiteral, Sendable where V: Any, V: Sendable, V: Hashable {
164+
public typealias Key = BinaryDistinctString
165+
166+
public var storage: [Key: V] = [:]
167+
168+
// MARK: - Initializers
169+
public init(_ dictionary: [Key: V] = [:]) {
170+
self.storage = dictionary
171+
}
172+
173+
/// Initializes from `[String: Value]`
174+
public init(_ dictionary: [String: V]) {
175+
self.storage = Dictionary(uniqueKeysWithValues: dictionary.map { (BinaryDistinctString($0.key), $0.value) })
176+
}
177+
178+
/// Initializes from `[NSString: Value]`
179+
public init(_ dictionary: [NSString: V]) {
180+
self.storage = Dictionary(uniqueKeysWithValues: dictionary.map { (BinaryDistinctString($0.key), $0.value) })
181+
}
182+
183+
public init(dictionaryLiteral elements: (Key, V)...) {
184+
self.storage = Dictionary(uniqueKeysWithValues: elements)
185+
}
186+
187+
// MARK: - Dictionary Operations
188+
public subscript(key: Key) -> V? {
189+
get { return storage[key] }
190+
set { storage[key] = newValue }
191+
}
192+
193+
public var keys: [Key] {
194+
return Array(storage.keys)
195+
}
196+
197+
public var values: [V] {
198+
return Array(storage.values)
199+
}
200+
201+
// MARK: - Collection Conformance
202+
public typealias Index = Dictionary<Key, V>.Index
203+
public typealias Element = (key: Key, value: V)
204+
205+
public var startIndex: Index { storage.startIndex }
206+
public var endIndex: Index { storage.endIndex }
207+
208+
public func index(after i: Index) -> Index {
209+
return storage.index(after: i)
210+
}
211+
212+
public subscript(position: Index) -> Element {
213+
return storage[position]
214+
}
215+
216+
/// Returns a new dictionary with keys mapped to the requested type.
217+
public func mapKeys<K: StringConvertible>(_ type: K.Type) -> [K: V] {
218+
return Dictionary(
219+
uniqueKeysWithValues: storage.map {
220+
(K.self == String.self ? $0.key.string as! K : $0.key.nsString as! K, $0.value)
221+
}
222+
)
223+
}
224+
225+
mutating public func removeValue(forKey key: Key) -> V? {
226+
return self.storage.removeValue(forKey: key)
227+
}
228+
229+
// MARK: - Merging Methods
230+
231+
/// Merges another `BinaryDistinctDictionary` into this one
232+
public mutating func merge(_ other: BinaryDistinctDictionary<Value>, strategy: (V, V) -> V = { _, new in new }) {
233+
self.storage.merge(other.storage, uniquingKeysWith: strategy)
234+
}
235+
236+
/// Merges a `[String: Value]` dictionary into this one
237+
public mutating func merge(_ other: [BinaryDistinctString: V], strategy: (V, V) -> V = { _, new in new }) {
238+
let converted = Dictionary(uniqueKeysWithValues: other.map { ($0.key, $0.value) })
239+
self.storage.merge(converted, uniquingKeysWith: strategy)
240+
}
241+
242+
/// Merges a `[String: Value]` dictionary into this one
243+
public mutating func merge(_ other: [String: V], strategy: (V, V) -> V = { _, new in new }) {
244+
let converted = Dictionary(uniqueKeysWithValues: other.map { (BinaryDistinctString($0.key), $0.value) })
245+
self.storage.merge(converted, uniquingKeysWith: strategy)
246+
}
247+
248+
/// Merges a `[NSString: Value]` dictionary into this one
249+
public mutating func merge(_ other: [NSString: V], strategy: (V, V) -> V = { _, new in new }) {
250+
let converted = Dictionary(uniqueKeysWithValues: other.map { (BinaryDistinctString($0.key), $0.value) })
251+
self.storage.merge(converted, uniquingKeysWith: strategy)
252+
}
253+
254+
/// Returns a new dictionary by merging `other` while keeping the current dictionary unchanged.
255+
public func merging(_ other: BinaryDistinctDictionary<V>, strategy: (V, V) -> V = { _, new in new }) -> BinaryDistinctDictionary {
256+
var newDict = self
257+
newDict.merge(other, strategy: strategy)
258+
return newDict
259+
}
260+
261+
public func merging(_ other: [String: V], strategy: (V, V) -> V = { _, new in new }) -> BinaryDistinctDictionary {
262+
var newDict = self
263+
newDict.merge(other, strategy: strategy)
264+
return newDict
265+
}
266+
267+
public func merging(_ other: [BinaryDistinctString: V], strategy: (V, V) -> V = { _, new in new }) -> BinaryDistinctDictionary {
268+
var newDict = self
269+
newDict.merge(other, strategy: strategy)
270+
return newDict
271+
}
272+
273+
public func merging(_ other: [NSString: V], strategy: (V, V) -> V = { _, new in new }) -> BinaryDistinctDictionary {
274+
var newDict = self
275+
newDict.merge(other, strategy: strategy)
276+
return newDict
277+
}
278+
279+
public func invert() -> [V: BinaryDistinctString] {
280+
var inverted: [V: BinaryDistinctString] = [:]
281+
for (k, v) in self.storage {
282+
inverted[v] = k
283+
}
284+
return inverted
285+
}
286+
}
287+
288+
extension BinaryDistinctDictionary: Hashable {
289+
public func hash(into hasher: inout Hasher) {
290+
// Combine the count to distinguish between dictionaries of different sizes.
291+
hasher.combine(self.storage.count)
292+
// Sort keys for a deterministic order.
293+
for key in self.storage.keys.sorted() {
294+
hasher.combine(key)
295+
if let value = self.storage[key] {
296+
hasher.combine(value)
297+
}
298+
}
299+
}
300+
}
301+
302+
public protocol StringConvertible: ExpressibleByStringLiteral {}
303+
304+
extension BinaryDistinctString: StringConvertible {}
305+
extension String: StringConvertible {}
306+
extension NSString: StringConvertible {}
307+
308+
public struct BinaryDistinctCharacter: Equatable, Hashable, CustomStringConvertible, ExpressibleByStringLiteral {
309+
let bytes: [UInt16]
310+
311+
public init(_ character: Character) {
312+
self.bytes = Array(character.utf16)
313+
}
314+
315+
public init(_ string: String) {
316+
self.bytes = Array(string.utf16)
317+
}
318+
319+
public init(_ nsString: NSString) {
320+
let swiftString = nsString as String
321+
self.bytes = Array(swiftString.utf16)
322+
}
323+
324+
public init(bytes: [UInt16]) {
325+
self.bytes = bytes
326+
}
327+
328+
/// Satisfies ``ExpressibleByStringLiteral`` protocol.
329+
public init(stringLiteral value: String) {
330+
self.init(value)
331+
}
332+
333+
var stringValue: String? {
334+
String(utf16CodeUnits: self.bytes, count: self.bytes.count)
335+
}
336+
337+
public var description: String {
338+
if let str = stringValue {
339+
return "BinaryDistinctCharacter('\(str)', bytes: \(bytes.map { String(format: "0x%02X", $0) }))"
340+
} else {
341+
return "BinaryDistinctCharacter(invalid UTF-8, bytes: \(bytes.map { String(format: "0x%02X", $0) }))"
342+
}
343+
}
344+
345+
public static func == (lhs: BinaryDistinctCharacter, rhs: BinaryDistinctCharacter) -> Bool {
346+
lhs.bytes == rhs.bytes
347+
}
348+
}

0 commit comments

Comments
 (0)