diff --git a/stdlib/public/core/IntegerParsing.swift.gyb b/stdlib/public/core/IntegerParsing.swift.gyb index 66e64f977e264..042111b792037 100644 --- a/stdlib/public/core/IntegerParsing.swift.gyb +++ b/stdlib/public/core/IntegerParsing.swift.gyb @@ -67,21 +67,21 @@ internal func _parseUnsignedAsciiAsUIntMax( /// non-negative number <= `maximum`, return that number. Otherwise, /// return `nil`. /// -/// - Note: If `text` begins with `"+"` or `"-"`, even if the rest of -/// the characters are `"0"`, the result is `nil`. +/// - Note: For text matching the regular expression "-0+", the result +/// is `0`, not `nil`. internal func _parseAsciiAsUIntMax( - u16: String.UTF16View, _ radix: Int, _ maximum: UIntMax + utf16: String.UTF16View, _ radix: Int, _ maximum: UIntMax ) -> UIntMax? { - if u16.isEmpty { return nil } - let c = u16.first - if _fastPath(c != _ascii16("-")) { - let unsignedText - = c == _ascii16("+") ? u16.dropFirst() : u16 - return _parseUnsignedAsciiAsUIntMax(unsignedText, radix, maximum) - } - else { - return _parseAsciiAsIntMax(u16, radix, 0) == 0 ? 0 : nil - } + if utf16.isEmpty { return nil } + // Parse (optional) sign. + let (digitsUTF16, hasMinus) = _parseOptionalAsciiSign(utf16) + // Parse digits. + guard let result = _parseUnsignedAsciiAsUIntMax(digitsUTF16, radix, maximum) + else { return nil } + // Disallow < 0. + if hasMinus && result != 0 { return nil } + // Return. + return result } /// If text is an ASCII representation in the given `radix` of a @@ -91,23 +91,30 @@ internal func _parseAsciiAsUIntMax( /// - Note: For text matching the regular expression "-0+", the result /// is `0`, not `nil`. internal func _parseAsciiAsIntMax( - u16: String.UTF16View, _ radix: Int, _ maximum: IntMax + utf16: String.UTF16View, _ radix: Int, _ maximum: IntMax ) -> IntMax? { _sanityCheck(maximum >= 0, "maximum should be non-negative") + if utf16.isEmpty { return nil } + // Parse (optional) sign. + let (digitsUTF16, hasMinus) = _parseOptionalAsciiSign(utf16) + // Parse digits. +1 for because e.g. Int8's range is -128...127. + let absValueMax = UIntMax(bitPattern: maximum) + (hasMinus ? 1 : 0) + guard let absValue = + _parseUnsignedAsciiAsUIntMax(digitsUTF16, radix, absValueMax) + else { return nil } + // Return signed result. + return IntMax(bitPattern: hasMinus ? 0 &- absValue : absValue) +} - if u16.isEmpty { return nil } - - // Drop any leading "-" - let negative = u16.first == _ascii16("-") - let absResultText = negative ? u16.dropFirst() : u16 - - let absResultMax = UIntMax(bitPattern: maximum) + (negative ? 1 : 0) - - // Parse the result as unsigned - if let absResult = _parseAsciiAsUIntMax(absResultText, radix, absResultMax) { - return IntMax(bitPattern: negative ? 0 &- absResult : absResult) +/// Strip an optional single leading ASCII plus/minus sign from `utf16`. +private func _parseOptionalAsciiSign( + utf16: String.UTF16View +) -> (digitsUTF16: String.UTF16View, isMinus: Bool) { + switch utf16.first { + case _ascii16("-")?: return (utf16.dropFirst(), true) + case _ascii16("+")?: return (utf16.dropFirst(), false) + default: return (utf16, false) } - return nil } //===--- Loop over all integer types --------------------------------------===// diff --git a/test/1_stdlib/NumericParsing.swift.gyb b/test/1_stdlib/NumericParsing.swift.gyb index 35a45f8f2ecd7..fc2f13116990f 100644 --- a/test/1_stdlib/NumericParsing.swift.gyb +++ b/test/1_stdlib/NumericParsing.swift.gyb @@ -92,6 +92,10 @@ tests.test("${Self}/success") { expectEqual(nil, ${Self}("${minValue - 1}")) expectEqual(nil, ${Self}("\u{1D7FF}")) // MATHEMATICAL MONOSPACE DIGIT NINE + // Cases that should fail to parse + expectEqual(nil, ${Self}("--0")) // Zero w/ repeated plus + expectEqual(nil, ${Self}("-+5")) // Non-zero with -+ + // Do more exhaustive testing % for radix in radices_to_test: % for n in required_values + range(