forked from swiftlang/swift-corelibs-foundation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathScannerAPI.swift
198 lines (165 loc) · 7.95 KB
/
ScannerAPI.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 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 CharacterSet {
fileprivate func contains(_ character: Character) -> Bool {
return character.unicodeScalars.allSatisfy(self.contains(_:))
}
}
// -----
@available(swift 5.0)
extension Scanner {
public enum NumberRepresentation : Sendable {
case decimal // See the %d, %f and %F format conversions.
case hexadecimal // See the %x, %X, %a and %A format conversions. For integers, a leading 0x or 0X is optional; for floating-point numbers, it is required.
}
public var currentIndex: String.Index {
get {
let string = self.string
var index = string._toUTF16Index(scanLocation)
var delta = 0
while index != string.endIndex && index.samePosition(in: string) == nil {
delta += 1
index = string._toUTF16Index(scanLocation + delta)
}
return index
}
set { scanLocation = string._toUTF16Offset(newValue) }
}
public func scanInt(representation: NumberRepresentation = .decimal) -> Int? {
#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le)
if let value = scanInt64(representation: representation) {
return Int(value)
}
#elseif arch(i386) || arch(arm) || arch(wasm32)
if let value = scanInt32(representation: representation) {
return Int(value)
}
#else
#error("This architecture isn't known. Add it to the 32-bit or 64-bit line; if the machine word isn't either of those, you need to implement appropriate scanning and handle the potential overflow here.")
#endif
return nil
}
public func scanInt32(representation: NumberRepresentation = .decimal) -> Int32? {
var value = Int32.max
switch representation {
case .decimal: guard self.scanInt32(&value) else { return nil }
default:
var overflowingValue = UInt32.max
guard self.scanHexInt32(&overflowingValue) else { return nil }
if overflowingValue < Int32.max {
value = Int32(overflowingValue)
}
}
return value
}
public func scanInt64(representation: NumberRepresentation = .decimal) -> Int64? {
var value = Int64.max
switch representation {
case .decimal: guard self.scanInt64(&value) else { return nil }
case .hexadecimal:
var overflowingValue = UInt64.max
guard self.scanHexInt64(&overflowingValue) else { return nil }
if overflowingValue < Int64.max {
value = Int64(overflowingValue)
}
}
return value
}
public func scanUInt64(representation: NumberRepresentation = .decimal) -> UInt64? {
var value = UInt64.max
switch representation {
case .decimal: guard self.scanUnsignedLongLong(&value) else { return nil }
case .hexadecimal: guard self.scanHexInt64(&value) else { return nil }
}
return value
}
public func scanFloat(representation: NumberRepresentation = .decimal) -> Float? {
var value = Float.greatestFiniteMagnitude
switch representation {
case .decimal: guard self.scanFloat(&value) else { return nil }
case .hexadecimal: guard self.scanHexFloat(&value) else { return nil }
}
return value
}
public func scanDouble(representation: NumberRepresentation = .decimal) -> Double? {
var value = Double.greatestFiniteMagnitude
switch representation {
case .decimal: guard self.scanDouble(&value) else { return nil }
case .hexadecimal: guard self.scanHexDouble(&value) else { return nil }
}
return value
}
fileprivate var _currentIndexAfterSkipping: String.Index {
guard let skips = charactersToBeSkipped else { return currentIndex }
let index = string[currentIndex...].firstIndex(where: { !skips.contains($0) })
return index ?? string.endIndex
}
public func scanString(_ searchString: String) -> String? {
let currentIndex = _currentIndexAfterSkipping
guard let substringEnd = string.index(currentIndex, offsetBy: searchString.count, limitedBy: string.endIndex) else { return nil }
if string.compare(searchString, options: self.caseSensitive ? [] : .caseInsensitive, range: currentIndex ..< substringEnd, locale: self.locale as? Locale) == .orderedSame {
let it = string[currentIndex ..< substringEnd]
self.currentIndex = substringEnd
return String(it)
} else {
return nil
}
}
public func scanCharacters(from set: CharacterSet) -> String? {
let currentIndex = _currentIndexAfterSkipping
let substringEnd = string[currentIndex...].firstIndex(where: { !set.contains($0) }) ?? string.endIndex
guard currentIndex != substringEnd else { return nil }
let substring = string[currentIndex ..< substringEnd]
self.currentIndex = substringEnd
return String(substring)
}
public func scanUpToString(_ substring: String) -> String? {
guard !substring.isEmpty else { return nil }
let string = self.string
let startIndex = _currentIndexAfterSkipping
var beginningOfNewString = string.endIndex
var currentSearchIndex = startIndex
repeat {
guard let range = string.range(of: substring, options: self.caseSensitive ? [] : .caseInsensitive, range: currentSearchIndex ..< string.endIndex, locale: self.locale as? Locale) else {
// If the string isn't found at all, it means it's not in the string. Just take everything to the end.
beginningOfNewString = string.endIndex
break
}
// range(of:…) can return partial grapheme ranges when dealing with emoji.
// Make sure we take a range only if it doesn't split a grapheme in the string.
if let maybeBeginning = range.lowerBound.samePosition(in: string),
range.upperBound.samePosition(in: string) != nil {
beginningOfNewString = maybeBeginning
break
}
// If we got here, we need to search again starting from just after the location we found.
currentSearchIndex = range.upperBound
} while beginningOfNewString == string.endIndex && currentSearchIndex < string.endIndex
guard startIndex != beginningOfNewString else { return nil }
let foundSubstring = string[startIndex ..< beginningOfNewString]
self.currentIndex = beginningOfNewString
return String(foundSubstring)
}
public func scanUpToCharacters(from set: CharacterSet) -> String? {
let currentIndex = _currentIndexAfterSkipping
let string = self.string
let firstCharacterInSet = string[currentIndex...].firstIndex(where: { set.contains($0) }) ?? string.endIndex
guard currentIndex != firstCharacterInSet else { return nil }
self.currentIndex = firstCharacterInSet
return String(string[currentIndex ..< firstCharacterInSet])
}
public func scanCharacter() -> Character? {
let currentIndex = _currentIndexAfterSkipping
let string = self.string
guard currentIndex != string.endIndex else { return nil }
let character = string[currentIndex]
self.currentIndex = string.index(after: currentIndex)
return character
}
}