[dart2wasm] Implement `toString` methods on double using JS interop.
Change-Id: I5843e9cae59b8f152b922a01796eaf05494808a0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/250681
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
diff --git a/pkg/dart2wasm/bin/run_wasm.js b/pkg/dart2wasm/bin/run_wasm.js
index abe9cc7..40d57a6 100644
--- a/pkg/dart2wasm/bin/run_wasm.js
+++ b/pkg/dart2wasm/bin/run_wasm.js
@@ -270,6 +270,34 @@
}
return null;
},
+ stringify: function(o) {
+ return stringToDartString(String(o));
+ },
+ doubleToString: function(v) {
+ return stringToDartString(v.toString());
+ },
+ toFixed: function(double, digits) {
+ return stringToDartString(double.toFixed(digits));
+ },
+ toExponential: function(double, fractionDigits) {
+ return stringToDartString(double.toExponential(fractionDigits));
+ },
+ toPrecision: function(double, precision) {
+ return stringToDartString(double.toPrecision(precision));
+ },
+ parseDouble: function(source) {
+ // Notice that JS parseFloat accepts garbage at the end of the string.
+ // Accept only:
+ // - [+/-]NaN
+ // - [+/-]Infinity
+ // - a Dart double literal
+ // We do allow leading or trailing whitespace.
+ var jsSource = stringFromDartString(source);
+ if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(jsSource)) {
+ return NaN;
+ }
+ return parseFloat(jsSource);
+ },
};
function instantiate(filename, imports) {
diff --git a/sdk/lib/_internal/wasm/lib/double.dart b/sdk/lib/_internal/wasm/lib/double.dart
index ea77a86..eef8c29 100644
--- a/sdk/lib/_internal/wasm/lib/double.dart
+++ b/sdk/lib/_internal/wasm/lib/double.dart
@@ -4,6 +4,49 @@
// part of "core_patch.dart";
+@pragma("wasm:import", "dart2wasm.doubleToString")
+external String _doubleToString(double v);
+
+@pragma("wasm:import", "dart2wasm.toFixed")
+external String _toFixed(double d, double digits);
+
+@pragma("wasm:import", "dart2wasm.toExponential")
+external String _toExponential1(double d);
+
+@pragma("wasm:import", "dart2wasm.toExponential")
+external String _toExponential2(double d, double fractionDigits);
+
+@pragma("wasm:import", "dart2wasm.toPrecision")
+external String _toPrecision(double d, double precision);
+
+@pragma("wasm:import", "dart2wasm.parseDouble")
+external double _parseDouble(String doubleString);
+
+@patch
+class double {
+ @patch
+ static double parse(String source,
+ [@deprecated double onError(String source)?]) {
+ double? result = tryParse(source);
+ if (result == null) {
+ throw FormatException('Invalid double $source');
+ }
+ return result;
+ }
+
+ @patch
+ static double? tryParse(String source) {
+ double result = _parseDouble(source);
+ if (result.isNaN) {
+ String trimmed = source.trim();
+ if (!(trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN')) {
+ return null;
+ }
+ }
+ return result;
+ }
+}
+
@pragma("wasm:entry-point")
class _BoxedDouble implements double {
// A boxed double contains an unboxed double.
@@ -142,8 +185,6 @@
static final List _cache = new List.filled(CACHE_LENGTH, null);
static int _cacheEvictIndex = 0;
- external String _toString();
-
String toString() {
// TODO(koda): Consider starting at most recently inserted.
for (int i = 0; i < CACHE_LENGTH; i += 2) {
@@ -156,7 +197,7 @@
if (identical(0.0, this)) {
return "0.0";
}
- String result = _toString();
+ String result = _doubleToString(value);
// Replace the least recently inserted entry.
_cache[_cacheEvictIndex] = this;
_cache[_cacheEvictIndex + 1] = result;
@@ -188,7 +229,8 @@
return _toStringAsFixed(fractionDigits);
}
- external String _toStringAsFixed(int fractionDigits);
+ String _toStringAsFixed(int fractionDigits) =>
+ _toFixed(value, fractionDigits.toDouble());
String toStringAsExponential([int? fractionDigits]) {
// See ECMAScript-262, 15.7.4.6 for details.
@@ -208,14 +250,16 @@
if (this == double.infinity) return "Infinity";
if (this == -double.infinity) return "-Infinity";
- // The dart function prints the shortest representation when fractionDigits
- // equals null. The native function wants -1 instead.
- fractionDigits = (fractionDigits == null) ? -1 : fractionDigits;
-
return _toStringAsExponential(fractionDigits);
}
- external String _toStringAsExponential(int fractionDigits);
+ String _toStringAsExponential(int? fractionDigits) {
+ if (fractionDigits == null) {
+ return _toExponential1(value);
+ } else {
+ return _toExponential2(value, fractionDigits.toDouble());
+ }
+ }
String toStringAsPrecision(int precision) {
// See ECMAScript-262, 15.7.4.7 for details.
@@ -236,7 +280,8 @@
return _toStringAsPrecision(precision);
}
- external String _toStringAsPrecision(int fractionDigits);
+ String _toStringAsPrecision(int fractionDigits) =>
+ _toPrecision(value, fractionDigits.toDouble());
// Order is: NaN > Infinity > ... > 0.0 > -0.0 > ... > -Infinity.
int compareTo(num other) {
diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart
index 99e4db0..07cfb05 100644
--- a/sdk/lib/_internal/wasm/lib/js_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/js_helper.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
// Helpers for working with JS.
-library dart.js_helper;
+library dart._js_helper;
import 'dart:_internal';
import 'dart:typed_data';
@@ -23,6 +23,11 @@
Object toObject() => jsObjectToDartObject(_ref);
}
+extension DoubleToJS on double {
+ WasmAnyRef toAnyRef() => toJSNumber(this);
+ JSValue toJS() => JSValue(toAnyRef());
+}
+
extension StringToJS on String {
WasmAnyRef toAnyRef() => jsStringFromDartString(this);
JSValue toJS() => JSValue(toAnyRef());
@@ -108,6 +113,9 @@
@pragma("wasm:import", "dart2wasm.isJSObject")
external bool isJSObject(WasmAnyRef? o);
+// The JS runtime will run helpful conversion routines between refs and bool /
+// double. In the longer term hopefully we can find a way to avoid the round
+// trip.
@pragma("wasm:import", "dart2wasm.roundtrip")
external double toDartNumber(WasmAnyRef ref);
@@ -193,6 +201,9 @@
external WasmAnyRef? callMethodVarArgsRaw(
WasmAnyRef o, WasmAnyRef method, WasmAnyRef? args);
+@pragma("wasm:import", "dart2wasm.stringify")
+external String stringifyRaw(WasmAnyRef? object);
+
// Currently, `allowInterop` returns a Function type. This is unfortunate for
// Dart2wasm because it means arbitrary Dart functions can flow to JS util
// calls. Our only solutions is to cache every function called with