From e1b4afa85dbc2e324b8a603b23779fc3cc6c8d66 Mon Sep 17 00:00:00 2001 From: "lrn@google.com" Date: Tue, 26 Nov 2013 08:44:38 +0000 Subject: [PATCH] Add num.parse. The implementation is still primitive: It calls int.parse, then double.parse, and then fails if it hasn't found a result yet. BUG= http://dartbug.com/8237 R=floitsch@google.com Committed: https://code.google.com/p/dart/source/detail?r=30624 Review URL: https://codereview.chromium.org//85633003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@30663 260f80e4-7a28-3924-810f-c04153c831b5 --- runtime/lib/double_patch.dart | 2 + sdk/lib/core/int.dart | 5 +- sdk/lib/core/num.dart | 29 +++++ tests/corelib/corelib.status | 6 + tests/corelib/num_parse_test.dart | 195 ++++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 tests/corelib/num_parse_test.dart diff --git a/runtime/lib/double_patch.dart b/runtime/lib/double_patch.dart index 9e2abeb4ece..6a597a8b354 100644 --- a/runtime/lib/double_patch.dart +++ b/runtime/lib/double_patch.dart @@ -45,6 +45,8 @@ patch class double { if (onError == null) throw new FormatException(str); return onError(str); } + // Parse can create a NaN that is not identical to double.NAN. + if (result.isNaN) return NAN; return result; } } diff --git a/sdk/lib/core/int.dart b/sdk/lib/core/int.dart index c06a3ee0f8d..6ca271829d0 100644 --- a/sdk/lib/core/int.dart +++ b/sdk/lib/core/int.dart @@ -246,8 +246,9 @@ abstract class int extends num { * first the decimal digits 0..9, and then the letters 'a'..'z'. * Accepts capital letters as well. * - * If no [radix] is given then it defaults to 16 if the string starts - * with "0x", "-0x" or "+0x" and 10 otherwise. + * If no [radix] is given then it defaults to 10, unless the string starts + * with "0x", "-0x" or "+0x", in which case the radix is set to 16 and the + * "0x" is ignored. * * The [source] must be a non-empty sequence of base-[radix] digits, * optionally prefixed with a minus or plus sign ('-' or '+'). diff --git a/sdk/lib/core/num.dart b/sdk/lib/core/num.dart index 674ab2a469f..c6bb4f85288 100644 --- a/sdk/lib/core/num.dart +++ b/sdk/lib/core/num.dart @@ -359,4 +359,33 @@ abstract class num implements Comparable { * */ String toString(); + + /** + * Parses a string containing a number literal into a number. + * + * The method first tries to read the [input] as integer (similar to + * [int.parse] without a radix). + * If that fails, it tries to parse the [input] as a double (similar to + * [double.parse]). + * If that fails, too, it invokes [onError] with [input]. + * + * If no [onError] is supplied, it defaults to a function that throws a + * [FormatException]. + * + * For any number `n`, this function satisfies + * `identical(n, num.parse(n.toString()))`. + */ + static num parse(String input, [num onError(String input)]) { + String source = input.trim(); + // TODO(lrn): Optimize to detect format and result type in one check. + num result = int.parse(source, onError: _returnNull); + if (result != null) return result; + result = double.parse(source, _returnNull); + if (result != null) return result; + if (onError == null) throw new FormatException(input); + return onError(input); + } + + /** Helper function for [parse]. */ + static _returnNull(_) => null; } diff --git a/tests/corelib/corelib.status b/tests/corelib/corelib.status index c9ed1acb39e..c6c0ed8ba57 100644 --- a/tests/corelib/corelib.status +++ b/tests/corelib/corelib.status @@ -86,6 +86,8 @@ compare_to2_test: RuntimeError, OK # Requires bigint support. string_base_vm_test: RuntimeError, OK # VM specific test. nan_infinity_test/01: Fail # Issue 11551 +num_parse_test/01: RuntimeError # Issue 11551 + [ $compiler == dart2js && $runtime == none ] *: Fail, Pass # TODO(ahe): Triage these tests. @@ -145,3 +147,7 @@ string_test: StaticWarning, OK # Test generates error on purpose. [ $compiler == dart2js && $runtime == safari ] list_test/01: Fail # Safari bug: Array(-2) seen as dead code. + +[ $runtime == ie9 || $runtime == ie10 ] +num_parse_test: RuntimeError # Issue 15316 +num_parse_test/01: RuntimeError # Issue 15316 diff --git a/tests/corelib/num_parse_test.dart b/tests/corelib/num_parse_test.dart new file mode 100644 index 00000000000..1c0b8386d58 --- /dev/null +++ b/tests/corelib/num_parse_test.dart @@ -0,0 +1,195 @@ +// Copyright (c) 2013 the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "package:expect/expect.dart"; + +const whiteSpace = const [ + "", + "\x09", + "\x0a", + "\x0b", + "\x0c", + "\x0d", + "\x85", + "\xa0", + "\u1680", + "\u180e", + "\u2000", + "\u2001", + "\u2002", + "\u2003", + "\u2004", + "\u2005", + "\u2006", + "\u2007", + "\u2008", + "\u2009", + "\u200a", + "\u2028", + "\u2029", + "\u202f", + "\u205f", + "\u3000", + "\uFEFF" +]; + +void testParse(String source, num result) { + for (String ws1 in whiteSpace) { + for (String ws2 in whiteSpace) { + String padded = "$ws1$source$ws2"; + // Use Expect.identical because it also handles NaN and 0.0/-0.0. + // Except on dart2js: http://dartbug.com/11551 + Expect.identical(result, num.parse(padded), "parse '$padded'"); + padded = "$ws1$ws2$source"; + Expect.identical(result, num.parse(padded), "parse '$padded'"); + padded = "$source$ws1$ws2"; + Expect.identical(result, num.parse(padded), "parse '$padded'"); + } + } +} + +void testInt(int value) { + testParse("$value", value); + testParse("+$value", value); + testParse("-$value", -value); + var hex = "0x${value.toRadixString(16)}"; + var lchex = hex.toLowerCase(); + testParse(lchex, value); + testParse("+$lchex", value); + testParse("-$lchex", -value); + var uchex = hex.toUpperCase(); + testParse(uchex, value); + testParse("+$uchex", value); + testParse("-$uchex", -value); +} + +void testIntAround(int value) { + testInt(value - 1); + testInt(value); + testInt(value + 1); +} + +void testDouble(double value) { + testParse("$value", value); + testParse("+$value", value); + testParse("-$value", -value); + if (value.isFinite) { + String exp = value.toStringAsExponential(); + String lcexp = exp.toLowerCase(); + testParse(lcexp, value); + testParse("+$lcexp", value); + testParse("-$lcexp", -value); + String ucexp = exp.toUpperCase(); + testParse(ucexp, value); + testParse("+$ucexp", value); + testParse("-$ucexp", -value); + } +} + +void testFail(String source) { + var object = new Object(); + Expect.throws(() { + num.parse(source, (s) { + Expect.equals(source, s); + throw object; + }); + }, (e) => identical(object, e), "Fail: '$source'"); +} + +void main() { + testInt(0); + testInt(1); + testInt(9); + testInt(10); + testInt(99); + testInt(100); + testIntAround(256); + testIntAround(0x80000000); // 2^31 + testIntAround(0x100000000); // 2^32 + testIntAround(0x10000000000000); // 2^52 + testIntAround(0x20000000000000); // 2^53 + testIntAround(0x40000000000000); // 2^54 + testIntAround(0x8000000000000000); // 2^63 + testIntAround(0x10000000000000000); // 2^64 + testIntAround(0x100000000000000000000); // 2^80 + + testDouble(0.0); + testDouble(5e-324); + testDouble(2.225073858507201e-308); + testDouble(2.2250738585072014e-308); + testDouble(0.49999999999999994); + testDouble(0.5); + testDouble(0.50000000000000006); + testDouble(0.9999999999999999); + testDouble(1.0); + testDouble(1.0000000000000002); + testDouble(4294967295.0); + testDouble(4294967296.0); + testDouble(4503599627370495.5); + testDouble(4503599627370497.0); + testDouble(9007199254740991.0); + testDouble(9007199254740992.0); + testDouble(1.7976931348623157e+308); + testDouble(double.INFINITY); + testDouble(double.NAN); /// 01: ok + + // Strings that cannot occur from toString of a number. + testParse("000000000000", 0); + testParse("000000000001", 1); + testParse("000000000000.0000000000000", 0.0); + testParse("000000000001.0000000000000", 1.0); + testParse("0x0000000000", 0); + testParse("0e0", 0.0); + testParse("0e+0", 0.0); + testParse("0e-0", 0.0); + testParse("-0e0", -0.0); + testParse("-0e+0", -0.0); + testParse("-0e-0", -0.0); + testParse("1e0", 1.0); + testParse("1e+0", 1.0); + testParse("1e-0", 1.0); + testParse("-1e0", -1.0); + testParse("-1e+0", -1.0); + testParse("-1e-0", -1.0); + testParse("1.", 1.0); + testParse(".1", 0.1); + testParse("1.e1", 10.0); + testParse(".1e1", 1.0); + + // Negative tests - things not to allow. + + // Spaces inside the numeral. + testFail("- 1"); + testFail("+ 1"); + testFail("2 2"); + testFail("0x 42"); + testFail("1 ."); + testFail(". 1"); + testFail("1e 2"); + testFail("1 e2"); + // Invalid characters. + testFail("0x1H"); + testFail("12H"); + testFail("1x2"); + testFail("00x2"); + // Empty hex number. + testFail("0x"); + testFail("-0x"); + testFail("+0x"); + // Double exponent without value. + testFail("e1"); + testFail("e+1"); + testFail("e-1"); + testFail("-e1"); + testFail("-e+1"); + testFail("-e-1"); + // Incorrect ways to write NaN/Infinity. + testFail("infinity"); + testFail("INFINITY"); + testFail("inf"); + testFail("nan"); + testFail("NAN"); + testFail("qnan"); + testFail("snan"); +}