blob: d3fe75edfa06c1967a89e4f640535e25a6e33ab5 [file] [log] [blame]
// 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 {
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)
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
}
}