Files
swift-mirror/stdlib/public/core/FloatingPointParsing.swift.gyb
tbkka 246d52defe Simplify the floating-point parsing initializers (#28992)
The original version scanned the entire input string for whitespace and
non-ASCII characters.  Both are unnecessary: the C routines we're building on
already stop at non-ASCII characters or non-leading whitespace.  So we need only
check the first character for whitespace and verify that all characters are
consumed.

This both improves performance and reduces the amount of code that gets inlined into consumers.
2020-01-06 09:52:21 -08:00

173 lines
5.7 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftShims
%{
allFloatBits = [32, 64, 80]
def floatName(bits):
if bits == 32:
return 'Float'
if bits == 64:
return 'Double'
if bits == 80:
return 'Float80'
cFuncSuffix2 = {32: 'f', 64: 'd', 80: 'ld'}
}%
/// Returns `true` iff isspace(u) would return nonzero when the current
/// locale is the C locale.
@inlinable // FIXME(sil-serialize-all)
internal func _isspace_clocale(_ u: UTF16.CodeUnit) -> Bool {
return "\t\n\u{b}\u{c}\r ".utf16.contains(u)
}
% for bits in allFloatBits:
% Self = floatName(bits)
% if bits == 80:
#if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64))
% end
//===--- Parsing ----------------------------------------------------------===//
extension ${Self}: LosslessStringConvertible {
/// Creates a new instance from the given string.
///
/// The string passed as `text` can represent a real number in decimal or
/// hexadecimal format or special floating-point values for infinity and NaN
/// ("not a number").
///
/// The given string may begin with a plus or minus sign character (`+` or
/// `-`). The allowed formats for each of these representations is then as
/// follows:
///
/// - A *decimal value* contains the significand, a sequence of decimal
/// digits that may include a decimal point.
///
/// let c = ${Self}("-1.0")
/// // c == -1.0
///
/// let d = ${Self}("28.375")
/// // d == 28.375
///
/// A decimal value may also include an exponent following the significand,
/// indicating the power of 10 by which the significand should be
/// multiplied. If included, the exponent is separated by a single
/// character, `e` or `E`, and consists of an optional plus or minus sign
/// character and a sequence of decimal digits.
///
/// let e = ${Self}("2837.5e-2")
/// // e == 28.375
///
/// - A *hexadecimal value* contains the significand, either `0X` or `0x`,
/// followed by a sequence of hexadecimal digits. The significand may
/// include a decimal point.
///
/// let f = ${Self}("0x1c.6")
/// // f == 28.375
///
/// A hexadecimal value may also include an exponent following the
/// significand, indicating the power of 2 by which the significand should
/// be multiplied. If included, the exponent is separated by a single
/// character, `p` or `P`, and consists of an optional plus or minus sign
/// character and a sequence of decimal digits.
///
/// let g = ${Self}("0x1.c6p4")
/// // g == 28.375
///
/// - A value of *infinity* contains one of the strings `"inf"` or
/// `"infinity"`, case insensitive.
///
/// let i = ${Self}("inf")
/// // i == ${Self}.infinity
///
/// let j = ${Self}("-Infinity")
/// // j == -${Self}.infinity
///
/// - A value of *NaN* contains the string `"nan"`, case insensitive.
///
/// let n = ${Self}("-nan")
/// // n?.isNaN == true
/// // n?.sign == .minus
///
/// A NaN value may also include a payload in parentheses following the
/// `"nan"` keyword. The payload consists of a sequence of decimal digits,
/// or the characters `0X` or `0x` followed by a sequence of hexadecimal
/// digits. If the payload contains any other characters, it is ignored.
/// If the value of the payload is larger than can be stored as the
/// payload of a `${Self}.nan`, the least significant bits are used.
///
/// let p = ${Self}("nan(0x10)")
/// // p?.isNaN == true
/// // String(p!) == "nan(0x10)"
///
/// Passing any other format or any additional characters as `text` results
/// in `nil`. For example, the following conversions result in `nil`:
///
/// ${Self}(" 5.0") // Includes whitespace
/// ${Self}("±2.0") // Invalid character
/// ${Self}("0x1.25e4") // Incorrect exponent format
///
/// - Parameter text: The input string to convert to a `${Self}` instance. If
/// `text` has invalid characters or is in an invalid format, the result
/// is `nil`.
@inlinable // FIXME(sil-serialize-all)
public init?<S: StringProtocol>(_ text: S) {
let result: ${Self}? = text.withCString { chars in
// TODO: We should change the ABI for
// _swift_stdlib_strtoX_clocale so that it returns
// a boolean `false` for leading whitespace, empty
// string, or invalid character. Then we could avoid
// inlining these checks into every single client.
switch chars[0] {
case 9, 10, 11, 12, 13, 32:
// Reject any input with leading whitespace.
return nil
case 0:
// Reject the empty string
return nil
default:
break
}
var result: ${Self} = 0
let endPtr = withUnsafeMutablePointer(to: &result) {
_swift_stdlib_strto${cFuncSuffix2[bits]}_clocale(chars, $0)
}
// Verify that all the characters were consumed.
if endPtr == nil || endPtr![0] != 0 {
return nil
}
return result
}
if let result = result {
self = result
} else {
return nil
}
}
}
% if bits == 80:
#endif
% end
% end
// ${'Local Variables'}:
// eval: (read-only-mode 1)
// End: