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
}
}
|