Skip to content

Scanner: When parsing floating point, use appropiate initialiser. #2820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 78 additions & 62 deletions Sources/Foundation/Scanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -377,90 +377,106 @@ extension String {
locationToScanFrom = buf.location
return retval
}
private func _scan<T: BinaryFloatingPoint>(buffer buf: inout _NSStringBuffer, locale: Locale?, neg: Bool, to: (T) -> Void, base: UInt,
numericValue: ((_: unichar) -> Int?)) -> Bool {

internal func scan<T: BinaryFloatingPoint & LosslessStringConvertible>(_ skipSet: CharacterSet?, locale: Locale?, locationToScanFrom: inout Int, to: (T) -> Void) -> Bool {
var buf = _NSStringBuffer(string: self, start: locationToScanFrom, end: length)
let ds = (locale ?? Locale.current).decimalSeparator?.first ?? Character(".")
var localResult: T! = nil
var neg = neg

while let numeral = numericValue(buf.currentCharacter) {
localResult = localResult ?? T(0)
// if (localResult >= T.greatestFiniteMagnitude / T(10)) && ((localResult > T.greatestFiniteMagnitude / T(10)) || T(numericValue(buf.currentCharacter) - (neg ? 1 : 0)) >= T.greatestFiniteMagnitude - localResult * T(10)) is evidently too complex; so break it down to more "edible chunks"
let limit1 = localResult >= T.greatestFiniteMagnitude / T(base)
let limit2 = localResult > T.greatestFiniteMagnitude / T(base)
let limit3 = T(numeral - (neg ? 1 : 0)) >= T.greatestFiniteMagnitude - localResult * T(base)
if (limit1) && (limit2 || limit3) {
// apply the clamps and advance past the ending of the buffer where there are still digits
localResult = neg ? -T.infinity : T.infinity
neg = false
repeat {
buf.advance()
} while numericValue(buf.currentCharacter) != nil
break
} else {
localResult = localResult * T(base) + T(numeral)
func nextDigit() -> Character? {
if let s = UnicodeScalar(buf.currentCharacter) {
let ch = Character(s)
if ch.isASCII && ch.isWholeNumber {
return ch
}
}
return nil
}

var hasValidCharacter = false
var stringToParse = checkForNegative(inBuffer: &buf, skipping: skipSet) ? "-" : ""

while let ch = nextDigit() {
hasValidCharacter = true
stringToParse.append(ch)
buf.advance()
}

if let us = UnicodeScalar(buf.currentCharacter), Character(us) == ds {
var factor = 1 / T(base)
stringToParse += "."
buf.advance()
while let numeral = numericValue(buf.currentCharacter) {
localResult = localResult ?? T(0)
localResult = localResult + T(numeral) * factor
factor = factor / T(base)
while let ch = nextDigit() {
hasValidCharacter = true
stringToParse.append(ch)
buf.advance()
}
}
guard hasValidCharacter else { return false }

guard localResult != nil else {
return false
}

// If this is used to parse a number in Hexadecimal, this will never be true as the 'e' or 'E' will be caught by the previous loop.
if buf.currentCharacter == unichar(unicodeScalarLiteral: "e") || buf.currentCharacter == unichar(unicodeScalarLiteral: "E") {
var exponent = Double(0)

hasValidCharacter = false
stringToParse += "e"
buf.advance()
let negExponent = checkForNegative(inBuffer: &buf)

while let numeral = numericValue(buf.currentCharacter) {
buf.advance()
exponent *= Double(base)
exponent += Double(numeral)
if checkForNegative(inBuffer: &buf) {
stringToParse += "-"
}

if exponent > 0 {
let multiplier = pow(Double(base), exponent)
if negExponent {
localResult /= T(multiplier)
} else {
localResult *= T(multiplier)
}
while let ch = nextDigit() {
hasValidCharacter = true
stringToParse.append(ch)
buf.advance()
}
}
guard hasValidCharacter else { return false }

to(neg ? T(-1) * localResult : localResult)
return true
if let value = T(stringToParse) {
to(value)
locationToScanFrom = buf.location
return true
} else {
return false
}
}

internal func scan<T: BinaryFloatingPoint>(_ skipSet: CharacterSet?, locale: Locale?, locationToScanFrom: inout Int, to: (T) -> Void) -> Bool {
internal func scanHex<T: BinaryFloatingPoint & LosslessStringConvertible>(_ skipSet: CharacterSet?, locale: Locale?, locationToScanFrom: inout Int, to: (T) -> Void) -> Bool {
var buf = _NSStringBuffer(string: self, start: locationToScanFrom, end: length)
let neg = checkForNegative(inBuffer: &buf, skipping: skipSet)
let result = _scan(buffer: &buf, locale: locale, neg: neg, to: to, base: 10, numericValue: decimalValue)
locationToScanFrom = buf.location
return result
}
let ds = (locale ?? Locale.current).decimalSeparator?.first ?? Character(".")

internal func scanHex<T: BinaryFloatingPoint>(_ skipSet: CharacterSet?, locale: Locale?, locationToScanFrom: inout Int, to: (T) -> Void) -> Bool {
var buf = _NSStringBuffer(string: self, start: locationToScanFrom, end: length)
let neg = checkForNegative(inBuffer: &buf, skipping: skipSet)
func nextHexDigit() -> Character? {
if let s = UnicodeScalar(buf.currentCharacter), let ascii = Character(s).asciiValue {
switch ascii {
case 0x30...0x39, 0x41...0x46, 0x61...0x66: return Character(s)
default: return nil
}
} else {
return nil
}
}

var hasValidCharacter = false
var stringToParse = checkForNegative(inBuffer: &buf, skipping: skipSet) ? "-0x" : "0x"
skipHexStart(inBuffer: &buf)
let result = _scan(buffer: &buf, locale: locale, neg: neg, to: to, base: 16, numericValue: decimalOrHexValue)
locationToScanFrom = buf.location
return result

while let ch = nextHexDigit() {
hasValidCharacter = true
stringToParse.append(ch)
buf.advance()
}
if let us = UnicodeScalar(buf.currentCharacter), Character(us) == ds {
stringToParse += "."
buf.advance()
while let ch = nextHexDigit() {
hasValidCharacter = true
stringToParse.append(ch)
buf.advance()
}
}
guard hasValidCharacter else { return false }

if let value = T(stringToParse) {
to(value)
locationToScanFrom = buf.location
return true
} else {
return false
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion Tests/Foundation/Tests/TestNSGeometry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,11 @@ class TestNSGeometry : XCTestCase {
rect = NSRectFromString(stringRect)
XCTAssertTrue(_NSRect(expectedRect, equalsToRect: rect),
"\(NSStringFromRect(rect)) is not equal to expected \(NSStringFromRect(expectedRect))")


// SR-443
let point1 = NSPointFromString("{1.0, 1.2234234234}")
let point2 = NSPoint(x: CGFloat(1.0), y: CGFloat(1.2234234234))
XCTAssertEqual(point1, point2)
}

func test_DecodeEmptyStrings() {
Expand Down
8 changes: 6 additions & 2 deletions Tests/Foundation/Tests/TestScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,14 @@ class TestScanner : XCTestCase {
expectEqual($0.scanDouble(), atof("-100"), "Roundtrip: 2")
}

withScanner(for: "0.5 bla 0. .1 1e2 e+3 e4") {
withScanner(for: "0.5 bla 0. .1 . 1e2 e+3 e4") {
expectEqual($0.scanDouble(), 0.5, "Parse '0.5' as Double")
expectEqual($0.scanDouble(), nil, "Dont parse 'bla' as a Double") // "bla" doesnt parse as Double
expectEqual($0.scanString("bla"), "bla", "Consume the 'bla'")
expectEqual($0.scanDouble(), 0, "Parse '0.' as a Double")
expectEqual($0.scanDouble(), 0.1, "Parse '.1' as a Double")
expectEqual($0.scanDouble(), nil, "Dont parse '.' as a Double")
expectEqual($0.scanString("."), ".", "Consue '.'")
expectEqual($0.scanDouble(), 100, "Parse '1e2' as a Double")
expectEqual($0.scanDouble(), nil, "Dont parse 'e+3' as a Double") // "e+3" doesnt parse as Double
expectEqual($0.scanString("e+3"), "e+3", "Consume the 'e+3'")
Expand Down Expand Up @@ -124,7 +126,7 @@ class TestScanner : XCTestCase {
}

func testHexFloatingPoint() {
withScanner(for: "0xAA 3.14 0.1x 1g 3xx .F00x 1e00 -0xabcdef.02") {
withScanner(for: "0xAA 3.14 0.1x 1g 3xx .F00x 1e00 . -0xabcdef.02") {
expectEqual($0.scanDouble(representation: .hexadecimal), 0xAA, "Integer as Double")
expectEqual($0.scanDouble(representation: .hexadecimal), 3.078125, "Double")
expectEqual($0.scanDouble(representation: .hexadecimal), 0.0625, "Double")
Expand All @@ -136,6 +138,8 @@ class TestScanner : XCTestCase {
expectEqual($0.scanDouble(representation: .hexadecimal), 0.9375, "Double")
expectEqual($0.scanString("x"), "x", "Consume non-hex-digit")
expectEqual($0.scanDouble(representation: .hexadecimal), 0x1E00, "E is not for exponent")
expectEqual($0.scanDouble(), nil, "Dont parse '.' as a Double")
expectEqual($0.scanString("."), ".", "Consue '.'")
expectEqual($0.scanDouble(representation: .hexadecimal), -11259375.0078125, "negative decimal")
}
}
Expand Down