Skip to content

Commit 1c1e52a

Browse files
authored
Merge pull request #2820 from spevans/pr_sr_443
2 parents 2fc16a2 + b9f5612 commit 1c1e52a

File tree

3 files changed

+89
-65
lines changed

3 files changed

+89
-65
lines changed

Sources/Foundation/Scanner.swift

Lines changed: 78 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -377,90 +377,106 @@ extension String {
377377
locationToScanFrom = buf.location
378378
return retval
379379
}
380-
381-
private func _scan<T: BinaryFloatingPoint>(buffer buf: inout _NSStringBuffer, locale: Locale?, neg: Bool, to: (T) -> Void, base: UInt,
382-
numericValue: ((_: unichar) -> Int?)) -> Bool {
380+
381+
internal func scan<T: BinaryFloatingPoint & LosslessStringConvertible>(_ skipSet: CharacterSet?, locale: Locale?, locationToScanFrom: inout Int, to: (T) -> Void) -> Bool {
382+
var buf = _NSStringBuffer(string: self, start: locationToScanFrom, end: length)
383383
let ds = (locale ?? Locale.current).decimalSeparator?.first ?? Character(".")
384-
var localResult: T! = nil
385-
var neg = neg
386384

387-
while let numeral = numericValue(buf.currentCharacter) {
388-
localResult = localResult ?? T(0)
389-
// 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"
390-
let limit1 = localResult >= T.greatestFiniteMagnitude / T(base)
391-
let limit2 = localResult > T.greatestFiniteMagnitude / T(base)
392-
let limit3 = T(numeral - (neg ? 1 : 0)) >= T.greatestFiniteMagnitude - localResult * T(base)
393-
if (limit1) && (limit2 || limit3) {
394-
// apply the clamps and advance past the ending of the buffer where there are still digits
395-
localResult = neg ? -T.infinity : T.infinity
396-
neg = false
397-
repeat {
398-
buf.advance()
399-
} while numericValue(buf.currentCharacter) != nil
400-
break
401-
} else {
402-
localResult = localResult * T(base) + T(numeral)
385+
func nextDigit() -> Character? {
386+
if let s = UnicodeScalar(buf.currentCharacter) {
387+
let ch = Character(s)
388+
if ch.isASCII && ch.isWholeNumber {
389+
return ch
390+
}
403391
}
392+
return nil
393+
}
394+
395+
var hasValidCharacter = false
396+
var stringToParse = checkForNegative(inBuffer: &buf, skipping: skipSet) ? "-" : ""
397+
398+
while let ch = nextDigit() {
399+
hasValidCharacter = true
400+
stringToParse.append(ch)
404401
buf.advance()
405402
}
406-
407403
if let us = UnicodeScalar(buf.currentCharacter), Character(us) == ds {
408-
var factor = 1 / T(base)
404+
stringToParse += "."
409405
buf.advance()
410-
while let numeral = numericValue(buf.currentCharacter) {
411-
localResult = localResult ?? T(0)
412-
localResult = localResult + T(numeral) * factor
413-
factor = factor / T(base)
406+
while let ch = nextDigit() {
407+
hasValidCharacter = true
408+
stringToParse.append(ch)
414409
buf.advance()
415410
}
416411
}
412+
guard hasValidCharacter else { return false }
417413

418-
guard localResult != nil else {
419-
return false
420-
}
421-
422-
// 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.
423414
if buf.currentCharacter == unichar(unicodeScalarLiteral: "e") || buf.currentCharacter == unichar(unicodeScalarLiteral: "E") {
424-
var exponent = Double(0)
425-
415+
hasValidCharacter = false
416+
stringToParse += "e"
426417
buf.advance()
427-
let negExponent = checkForNegative(inBuffer: &buf)
428-
429-
while let numeral = numericValue(buf.currentCharacter) {
430-
buf.advance()
431-
exponent *= Double(base)
432-
exponent += Double(numeral)
418+
if checkForNegative(inBuffer: &buf) {
419+
stringToParse += "-"
433420
}
434-
435-
if exponent > 0 {
436-
let multiplier = pow(Double(base), exponent)
437-
if negExponent {
438-
localResult /= T(multiplier)
439-
} else {
440-
localResult *= T(multiplier)
441-
}
421+
while let ch = nextDigit() {
422+
hasValidCharacter = true
423+
stringToParse.append(ch)
424+
buf.advance()
442425
}
443426
}
427+
guard hasValidCharacter else { return false }
444428

445-
to(neg ? T(-1) * localResult : localResult)
446-
return true
429+
if let value = T(stringToParse) {
430+
to(value)
431+
locationToScanFrom = buf.location
432+
return true
433+
} else {
434+
return false
435+
}
447436
}
448437

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

457-
internal func scanHex<T: BinaryFloatingPoint>(_ skipSet: CharacterSet?, locale: Locale?, locationToScanFrom: inout Int, to: (T) -> Void) -> Bool {
458-
var buf = _NSStringBuffer(string: self, start: locationToScanFrom, end: length)
459-
let neg = checkForNegative(inBuffer: &buf, skipping: skipSet)
442+
func nextHexDigit() -> Character? {
443+
if let s = UnicodeScalar(buf.currentCharacter), let ascii = Character(s).asciiValue {
444+
switch ascii {
445+
case 0x30...0x39, 0x41...0x46, 0x61...0x66: return Character(s)
446+
default: return nil
447+
}
448+
} else {
449+
return nil
450+
}
451+
}
452+
453+
var hasValidCharacter = false
454+
var stringToParse = checkForNegative(inBuffer: &buf, skipping: skipSet) ? "-0x" : "0x"
460455
skipHexStart(inBuffer: &buf)
461-
let result = _scan(buffer: &buf, locale: locale, neg: neg, to: to, base: 16, numericValue: decimalOrHexValue)
462-
locationToScanFrom = buf.location
463-
return result
456+
457+
while let ch = nextHexDigit() {
458+
hasValidCharacter = true
459+
stringToParse.append(ch)
460+
buf.advance()
461+
}
462+
if let us = UnicodeScalar(buf.currentCharacter), Character(us) == ds {
463+
stringToParse += "."
464+
buf.advance()
465+
while let ch = nextHexDigit() {
466+
hasValidCharacter = true
467+
stringToParse.append(ch)
468+
buf.advance()
469+
}
470+
}
471+
guard hasValidCharacter else { return false }
472+
473+
if let value = T(stringToParse) {
474+
to(value)
475+
locationToScanFrom = buf.location
476+
return true
477+
} else {
478+
return false
479+
}
464480
}
465481
}
466482

Tests/Foundation/Tests/TestNSGeometry.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,11 @@ class TestNSGeometry : XCTestCase {
13521352
rect = NSRectFromString(stringRect)
13531353
XCTAssertTrue(_NSRect(expectedRect, equalsToRect: rect),
13541354
"\(NSStringFromRect(rect)) is not equal to expected \(NSStringFromRect(expectedRect))")
1355-
1355+
1356+
// SR-443
1357+
let point1 = NSPointFromString("{1.0, 1.2234234234}")
1358+
let point2 = NSPoint(x: CGFloat(1.0), y: CGFloat(1.2234234234))
1359+
XCTAssertEqual(point1, point2)
13561360
}
13571361

13581362
func test_DecodeEmptyStrings() {

Tests/Foundation/Tests/TestScanner.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,14 @@ class TestScanner : XCTestCase {
7474
expectEqual($0.scanDouble(), atof("-100"), "Roundtrip: 2")
7575
}
7676

77-
withScanner(for: "0.5 bla 0. .1 1e2 e+3 e4") {
77+
withScanner(for: "0.5 bla 0. .1 . 1e2 e+3 e4") {
7878
expectEqual($0.scanDouble(), 0.5, "Parse '0.5' as Double")
7979
expectEqual($0.scanDouble(), nil, "Dont parse 'bla' as a Double") // "bla" doesnt parse as Double
8080
expectEqual($0.scanString("bla"), "bla", "Consume the 'bla'")
8181
expectEqual($0.scanDouble(), 0, "Parse '0.' as a Double")
8282
expectEqual($0.scanDouble(), 0.1, "Parse '.1' as a Double")
83+
expectEqual($0.scanDouble(), nil, "Dont parse '.' as a Double")
84+
expectEqual($0.scanString("."), ".", "Consue '.'")
8385
expectEqual($0.scanDouble(), 100, "Parse '1e2' as a Double")
8486
expectEqual($0.scanDouble(), nil, "Dont parse 'e+3' as a Double") // "e+3" doesnt parse as Double
8587
expectEqual($0.scanString("e+3"), "e+3", "Consume the 'e+3'")
@@ -124,7 +126,7 @@ class TestScanner : XCTestCase {
124126
}
125127

126128
func testHexFloatingPoint() {
127-
withScanner(for: "0xAA 3.14 0.1x 1g 3xx .F00x 1e00 -0xabcdef.02") {
129+
withScanner(for: "0xAA 3.14 0.1x 1g 3xx .F00x 1e00 . -0xabcdef.02") {
128130
expectEqual($0.scanDouble(representation: .hexadecimal), 0xAA, "Integer as Double")
129131
expectEqual($0.scanDouble(representation: .hexadecimal), 3.078125, "Double")
130132
expectEqual($0.scanDouble(representation: .hexadecimal), 0.0625, "Double")
@@ -136,6 +138,8 @@ class TestScanner : XCTestCase {
136138
expectEqual($0.scanDouble(representation: .hexadecimal), 0.9375, "Double")
137139
expectEqual($0.scanString("x"), "x", "Consume non-hex-digit")
138140
expectEqual($0.scanDouble(representation: .hexadecimal), 0x1E00, "E is not for exponent")
141+
expectEqual($0.scanDouble(), nil, "Dont parse '.' as a Double")
142+
expectEqual($0.scanString("."), ".", "Consue '.'")
139143
expectEqual($0.scanDouble(representation: .hexadecimal), -11259375.0078125, "negative decimal")
140144
}
141145
}

0 commit comments

Comments
 (0)