diff --git a/plugins/ets/runtime/ets_libbase_runtime.yaml b/plugins/ets/runtime/ets_libbase_runtime.yaml index dd96ec4845dcf3744aa4c74a1cd6e57219c767f6..507e4577930bc0516ca9078a9f5e924d0b55c596 100644 --- a/plugins/ets/runtime/ets_libbase_runtime.yaml +++ b/plugins/ets/runtime/ets_libbase_runtime.yaml @@ -1195,6 +1195,16 @@ intrinsics: codegen_func: CreateFloatIsSafeInteger clear_flags: [ no_dce, no_hoist, no_cse, barrier, require_state, runtime_call ] + - name: StdCoreDoubleNumberFromString + space: ets + class_name: std.core.Double + method_name: numberFromString + static: true + signature: + ret: f64 + args: [std.core.String] + impl: panda::ets::intrinsics::StdCoreDoubleNumberFromString + ############ # std.core # ############ diff --git a/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.cpp b/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.cpp index 6c9874c71aa6ee4d1e6a231534c0d330e469a003..46346fd34ee0c604f2b7215cc0f09577bb752e8d 100644 --- a/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.cpp +++ b/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.cpp @@ -29,17 +29,18 @@ namespace panda::ets::intrinsics::helpers { double StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags) { + // 1. skip space and line terminal if (IsEmptyString(start, end)) { + if (flags & flags::EMPTY_IS_ZERO) { + return 0.0; + } return NAN_VALUE; } radix = 0; auto p = const_cast(start); - // 1. skip space and line terminal - if (!GotoNonspace(&p, end)) { - return 0.0; - } + GotoNonspace(&p, end); // 2. get number sign Sign sign = Sign::NONE; @@ -179,23 +180,34 @@ double StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, u int additional_exponent = 0; constexpr int MAX_EXPONENT = INT32_MAX / 2; if (radix == DECIMAL && (p != end && (*p == 'e' || *p == 'E'))) { - RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE); + bool undefined_exponent = false; + ++p; + if (p == end) { + undefined_exponent = true; + } // 10. parse exponent number - if (*p == '+' || *p == '-') { + if (!undefined_exponent && (*p == '+' || *p == '-')) { exponent_sign = static_cast(*p); - RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE); - } - uint8_t digit; - while ((digit = ToDigit(*p)) < radix) { - if (additional_exponent > static_cast(MAX_EXPONENT / radix)) { - additional_exponent = MAX_EXPONENT; - } else { - additional_exponent = additional_exponent * static_cast(radix) + static_cast(digit); + ++p; + if (p == end) { + undefined_exponent = true; } - if (++p == end) { - break; + } + if (!undefined_exponent) { + uint8_t digit; + while ((digit = ToDigit(*p)) < radix) { + if (additional_exponent > static_cast(MAX_EXPONENT / radix)) { + additional_exponent = MAX_EXPONENT; + } else { + additional_exponent = additional_exponent * static_cast(radix) + static_cast(digit); + } + if (++p == end) { + break; + } } + } else if (flags & flags::ERROR_IN_EXPONENT_IS_NAN) { + return NAN_VALUE; } } exponent += (exponent_sign == '-' ? -additional_exponent : additional_exponent); diff --git a/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.h b/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.h index 5bcd80fe389f1b215b17064c205ea414984a636e..8921685cb9699f12486ea49fd512c8e35acceefe 100644 --- a/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.h +++ b/plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.h @@ -516,6 +516,8 @@ inline constexpr uint32_t ALLOW_BINARY = 1U << 0U; inline constexpr uint32_t ALLOW_OCTAL = 1U << 1U; inline constexpr uint32_t ALLOW_HEX = 1U << 2U; inline constexpr uint32_t IGNORE_TRAILING = 1U << 3U; +inline constexpr uint32_t EMPTY_IS_ZERO = 1U << 4U; +inline constexpr uint32_t ERROR_IN_EXPONENT_IS_NAN = 1U << 5U; } // namespace panda::ets::intrinsics::helpers::flags diff --git a/plugins/ets/runtime/intrinsics/std_core_Double.cpp b/plugins/ets/runtime/intrinsics/std_core_Double.cpp index 2e59a6206f800e93043f54c22f9dadb678985aab..7d20ac57f33d1aaeba713149c7be5ac248512fb5 100644 --- a/plugins/ets/runtime/intrinsics/std_core_Double.cpp +++ b/plugins/ets/runtime/intrinsics/std_core_Double.cpp @@ -27,6 +27,25 @@ namespace panda::ets::intrinsics { +namespace { + +double ParseFloat(EtsString *s, const uint32_t flags) +{ + if (UNLIKELY(s->IsUtf16())) { + size_t len = utf::Utf16ToUtf8Size(s->GetDataUtf16(), s->GetUtf16Length()) - 1; + PandaVector buf(len); + len = utf::ConvertRegionUtf16ToUtf8(s->GetDataUtf16(), buf.data(), s->GetLength(), len, 0); + + Span str = Span(buf.data(), len); + return helpers::StringToDouble(str.begin(), str.end(), 0, flags); + } + + Span str = Span(s->GetDataMUtf8(), s->GetMUtf8Length() - 1); + return helpers::StringToDouble(str.begin(), str.end(), 0, flags); +} + +} // namespace + EtsString *StdCoreDoubleToString(double number, int radix) { return helpers::FpToString(number, radix); @@ -65,17 +84,7 @@ EtsString *StdCoreDoubleToLocaleString(ObjectHeader *obj, EtsString *locale) double StdCoreDoubleParseFloat(EtsString *s) { - if (UNLIKELY(s->IsUtf16())) { - size_t len = utf::Utf16ToUtf8Size(s->GetDataUtf16(), s->GetUtf16Length()) - 1; - PandaVector buf(len); - len = utf::ConvertRegionUtf16ToUtf8(s->GetDataUtf16(), buf.data(), s->GetLength(), len, 0); - - Span str = Span(buf.data(), len); - return helpers::StringToDouble(str.begin(), str.end(), 0, helpers::flags::IGNORE_TRAILING); - } - - Span str = Span(s->GetDataMUtf8(), s->GetMUtf8Length() - 1); - return helpers::StringToDouble(str.begin(), str.end(), 0, helpers::flags::IGNORE_TRAILING); + return ParseFloat(s, helpers::flags::IGNORE_TRAILING); } double StdCoreDoubleParseInt(EtsString *s, int32_t radix) @@ -240,4 +249,15 @@ extern "C" EtsBoolean StdCoreDoubleIsSafeInteger(double v) return ToEtsBoolean(IsInteger(v) && (std::fabs(v) <= helpers::MaxSafeInteger())); } +extern "C" double StdCoreDoubleNumberFromString(EtsString *s) +{ + uint32_t flags = 0; + flags |= helpers::flags::ALLOW_BINARY; + flags |= helpers::flags::ALLOW_OCTAL; + flags |= helpers::flags::ALLOW_HEX; + flags |= helpers::flags::EMPTY_IS_ZERO; + flags |= helpers::flags::ERROR_IN_EXPONENT_IS_NAN; + return ParseFloat(s, flags); +} + } // namespace panda::ets::intrinsics diff --git a/plugins/ets/stdlib/std/core/Double.ets b/plugins/ets/stdlib/std/core/Double.ets index a47f926c1869578bc29e6df141fceda9414b2510..06fef3e05469f3288f570a4ef7f906f4fcd225c2 100644 --- a/plugins/ets/stdlib/std/core/Double.ets +++ b/plugins/ets/stdlib/std/core/Double.ets @@ -46,6 +46,15 @@ export final class Double extends Floating implements Comparable, JSONab this.value = value.doubleValue(); } + /** + * Constructs a new Double instance from string + * + * @param value string that may contain a number + */ + public constructor(value: String) { + this.value = Double.numberFromString(value); + } + /** * Returns value of this instance as a primitive * @@ -467,7 +476,6 @@ export final class Double extends Floating implements Comparable, JSONab */ public static native parseFloat(s: String): double; - /** * parseInt(String, int) parses from String an integer of specified radix * @@ -627,4 +635,24 @@ export final class Double extends Floating implements Comparable, JSONab } throw new JSONTypeError("Cannot create Double from JSON", json) } + + /** + * numberFromString(String) converts std.core.String to double + * + * @param s the string to convert + * + * @returns the result of conversion + * + * @note + * If arg is "+Infinity", "Infinity" or "-Infinity", return value is `inf` or `-inf` respectively. + * If arg is "+0" or "-0", return value is 0 or -0. + * If arg has leading zeroes, it's ignored: "0001.5" -> 1.5, "-0001.5" -> -1.5 + * If arg starts from ".", leading zero is implied: ".5" -> 0.5, "-.5" -> -0.5 + * If arg successfully parsed, trailing non-digits cause return value is NaN: "-.6ffg" -> -NaN + * If arg can not be parsed into a number, NaN is returned + * + * + * ECMA reference: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number-constructor-number-value + */ + private static native numberFromString(s: String): double; } diff --git a/plugins/ets/tests/ets_func_tests/std/core/DoubleTest.ets b/plugins/ets/tests/ets_func_tests/std/core/DoubleTest.ets new file mode 100644 index 0000000000000000000000000000000000000000..032397cc8036ad397b96c29f558a0bdb8b722348 --- /dev/null +++ b/plugins/ets/tests/ets_func_tests/std/core/DoubleTest.ets @@ -0,0 +1,128 @@ +function check(current_answer: number, correct_answer: number, test: string) { + if (current_answer != correct_answer) { + if (!Number.isNaN(current_answer) || !Number.isNaN(correct_answer)) { + assert false : "incorrect " + test + " answer " + current_answer + " instead of " + correct_answer; + return 1; + } + } + return 0; +} + +function main() { + let fails = 0; + let tests: string[] = [ + "", + "123", + "NaN", + "nan", + "-1", + "1e8", + "e8", + "1e", + "1rew", + "9.8", + "Infinity", + "-Infinity", + "-1.11111", + "aaa", + "\n", + "\t", + " ", + " 123 ", + "0x11", + "0o123", + "0b0101010", + "0b222", + "0x00 001", + "0x11 abacaba", + "0xR", + " 0x1", + "0x01R", + "+5", + " +5", + " +5a", + ".5", + "00015" + ] + const correct_cts: number[] = [ + 0, + 123, + NaN, + NaN, + -1, + 100000000, + NaN, + NaN, + NaN, + 9.8, + Infinity, + -Infinity, + -1.11111, + NaN, + 0, + 0, + 0, + 123, + 17, + 83, + 42, + NaN, + NaN, + NaN, + NaN, + 1, + NaN, + 5, + 5, + NaN, + 0.5, + 15 + ]; + const correct_pfs: number[] = [ + NaN, + 123, + NaN, + NaN, + -1, + 100000000, + NaN, + 1, + 1, + 9.8, + Infinity, + -Infinity, + -1.11111, + NaN, + NaN, + NaN, + NaN, + 123, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 5, + 5, + 5, + 0.5, + 15 + ]; + + const force_log = false; + for (let test_id = 0; test_id < tests.length; test_id++) { + let test = tests[test_id]; + if (force_log) { + console.log("[" + test + "]"); + } + let ctor_answer = (new Number(test)).unboxed(); // change to .valueOf to run script in TS + let parse_answer = Number.parseFloat(test); + fails += check(ctor_answer, correct_cts[test_id], "Number(" + test + ")"); + fails += check(parse_answer, correct_pfs[test_id], "parseFloat(" + test + ")"); + } + return fails; +}