diff --git a/RELEASES.rst b/RELEASES.rst index a3e5e3c5..fb7a11de 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -3190,8 +3190,9 @@ Planned 2.3.0 (XXXX-XX-XX) ------------------ -* ES2015 Number built-in bindings: Number.parseInt(), Number.parseFloat() - (GH-1756) +* ES2015 Number constructor properties: EPSILON, MIN_SAFE_INTEGER, + MAX_SAFE_INTEGER, isFinite(), isInteger(), isNaN(), isSafeInteger(), + parseInt(), parseFloat() (GH-1756, GH-1761) * Fix trailing single line comment handling for Function constructor; new Function('return "foo" //') previously failed with SyntaxError diff --git a/src-input/builtins.yaml b/src-input/builtins.yaml index b9aff659..1df317dc 100644 --- a/src-input/builtins.yaml +++ b/src-input/builtins.yaml @@ -1522,7 +1522,68 @@ objects: type: double bytes: "fff0000000000000" # DBL_NEGATIVE_INFINITY attributes: "" + - key: "EPSILON" + value: + type: double + bytes: "3cb0000000000000" # 3ff0000000000001 - 3ff0000000000000 = 3cb0000000000000 + attributes: "" + es6: true + present_if: DUK_USE_ES6 + - key: "MAX_SAFE_INTEGER" + value: + # >>> struct.pack('>d', 9007199254740991.0).encode('hex') + # '433fffffffffffff' + type: double + bytes: "433fffffffffffff" + attributes: "" + es6: true + present_if: DUK_USE_ES6 + - key: "MIN_SAFE_INTEGER" + value: + # >>> struct.pack('>d', -9007199254740991.0).encode('hex') + # 'c33fffffffffffff' + type: double + bytes: "c33fffffffffffff" + attributes: "" + es6: true + present_if: DUK_USE_ES6 + - key: "isFinite" + value: + type: function + native: duk_bi_number_check_shared + length: 1 + magic: 0 + attributes: "wc" + es6: true + present_if: DUK_USE_ES6 + - key: "isInteger" + value: + type: function + native: duk_bi_number_check_shared + length: 1 + magic: 1 + attributes: "wc" + es6: true + present_if: DUK_USE_ES6 + - key: "isNaN" + value: + type: function + native: duk_bi_number_check_shared + length: 1 + magic: 2 + attributes: "wc" + es6: true + present_if: DUK_USE_ES6 + - key: "isSafeInteger" + value: + type: function + native: duk_bi_number_check_shared + length: 1 + magic: 3 + attributes: "wc" + es6: true + present_if: DUK_USE_ES6 - key: "parseInt" # Must map to the exactly same object as global parseInt() value: type: object diff --git a/src-input/duk_bi_number.c b/src-input/duk_bi_number.c index c687b056..c6788e95 100644 --- a/src-input/duk_bi_number.c +++ b/src-input/duk_bi_number.c @@ -237,4 +237,40 @@ DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_precision(duk_hthread *thr) { return 1; } +/* + * ES2015 isFinite() etc + */ + +#if defined(DUK_USE_ES6) +DUK_INTERNAL duk_ret_t duk_bi_number_check_shared(duk_hthread *thr) { + duk_int_t magic; + duk_bool_t ret = 0; + + if (duk_is_number(thr, 0)) { + duk_double_t d; + + magic = duk_get_current_magic(thr); + d = duk_get_number(thr, 0); + + switch (magic) { + case 0: /* isFinite() */ + ret = duk_double_is_finite(d); + break; + case 1: /* isInteger() */ + ret = duk_double_is_integer(d); + break; + case 2: /* isNaN() */ + ret = duk_double_is_nan(d); + break; + default: /* isSafeInteger() */ + DUK_ASSERT(magic == 3); + ret = duk_double_is_safe_integer(d); + } + } + + duk_push_boolean(thr, ret); + return 1; +} +#endif /* DUK_USE_ES6 */ + #endif /* DUK_USE_NUMBER_BUILTIN */ diff --git a/src-input/duk_util.h b/src-input/duk_util.h index 8934fe59..2600dcce 100644 --- a/src-input/duk_util.h +++ b/src-input/duk_util.h @@ -560,5 +560,8 @@ DUK_INTERNAL_DECL duk_double_t duk_double_trunc_towards_zero(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_same_sign(duk_double_t x, duk_double_t y); DUK_INTERNAL_DECL duk_double_t duk_double_fmin(duk_double_t x, duk_double_t y); DUK_INTERNAL_DECL duk_double_t duk_double_fmax(duk_double_t x, duk_double_t y); +DUK_INTERNAL_DECL duk_bool_t duk_double_is_finite(duk_double_t x); +DUK_INTERNAL_DECL duk_bool_t duk_double_is_integer(duk_double_t x); +DUK_INTERNAL_DECL duk_bool_t duk_double_is_safe_integer(duk_double_t x); #endif /* DUK_UTIL_H_INCLUDED */ diff --git a/src-input/duk_util_misc.c b/src-input/duk_util_misc.c index aea3d009..01c019cf 100644 --- a/src-input/duk_util_misc.c +++ b/src-input/duk_util_misc.c @@ -403,3 +403,22 @@ DUK_INTERNAL duk_double_t duk_double_fmax(duk_double_t x, duk_double_t y) { */ return (x > y ? x : y); } + +DUK_INTERNAL duk_bool_t duk_double_is_finite(duk_double_t x) { + return !duk_double_is_nan_or_inf(x); +} + +DUK_INTERNAL duk_bool_t duk_double_is_integer(duk_double_t x) { + if (duk_double_is_nan_or_inf(x)) { + return 0; + } else { + return duk_js_tointeger_number(x) == x; + } +} + +DUK_INTERNAL duk_bool_t duk_double_is_safe_integer(duk_double_t x) { + /* >>> 2**53-1 + * 9007199254740991 + */ + return duk_double_is_integer(x) && DUK_FABS(x) <= 9007199254740991.0; +} diff --git a/tests/ecmascript/test-bi-number-isfinite.js b/tests/ecmascript/test-bi-number-isfinite.js new file mode 100644 index 00000000..6dd09300 --- /dev/null +++ b/tests/ecmascript/test-bi-number-isfinite.js @@ -0,0 +1,41 @@ +/*=== +function +propdesc isFinite: value=function:isFinite, writable=true, enumerable=false, configurable=true +propdesc length: value=1, writable=false, enumerable=false, configurable=true +propdesc name: value="isFinite", writable=false, enumerable=false, configurable=true +-Infinity false +-1e+100 true +-1234567890.2 true +-305419896 true +-0 true +0 true +305419896 true +1234567890.2 true +1e+100 true +Infinity false +NaN false +undefined false +null false +"" false +"123" false +true false +false false +[object Object] false +===*/ + +/*@include util-base.js@*/ + +print(typeof Number.isFinite); +print(Test.getPropDescString(Number, 'isFinite')); +print(Test.getPropDescString(Number.isFinite, 'length')); +print(Test.getPropDescString(Number.isFinite, 'name')); + +[ + -1 / 0, -1e100, -1234567890.2, -0x12345678, -0, + +0, 0x12345678, 1234567890.2, 1e100, 1 / 0, + 0 / 0, + void 0, null, '', '123', true, false, + { valueOf: function () { return 123.0; } } +].forEach(function (v) { + print(Test.valueToString(v), Test.valueToString(Number.isFinite(v))); +}); diff --git a/tests/ecmascript/test-bi-number-isinteger.js b/tests/ecmascript/test-bi-number-isinteger.js new file mode 100644 index 00000000..a167650f --- /dev/null +++ b/tests/ecmascript/test-bi-number-isinteger.js @@ -0,0 +1,41 @@ +/*=== +function +propdesc isInteger: value=function:isInteger, writable=true, enumerable=false, configurable=true +propdesc length: value=1, writable=false, enumerable=false, configurable=true +propdesc name: value="isInteger", writable=false, enumerable=false, configurable=true +-Infinity false +-1e+100 true +-1234567890.2 false +-305419896 true +-0 true +0 true +305419896 true +1234567890.2 false +1e+100 true +Infinity false +NaN false +undefined false +null false +"" false +"123" false +true false +false false +[object Object] false +===*/ + +/*@include util-base.js@*/ + +print(typeof Number.isInteger); +print(Test.getPropDescString(Number, 'isInteger')); +print(Test.getPropDescString(Number.isInteger, 'length')); +print(Test.getPropDescString(Number.isInteger, 'name')); + +[ + -1 / 0, -1e100, -1234567890.2, -0x12345678, -0, + +0, 0x12345678, 1234567890.2, 1e100, 1 / 0, + 0 / 0, + void 0, null, '', '123', true, false, + { valueOf: function () { return 123.0; } } +].forEach(function (v) { + print(Test.valueToString(v), Test.valueToString(Number.isInteger(v))); +}); diff --git a/tests/ecmascript/test-bi-number-isnan.js b/tests/ecmascript/test-bi-number-isnan.js new file mode 100644 index 00000000..882d4308 --- /dev/null +++ b/tests/ecmascript/test-bi-number-isnan.js @@ -0,0 +1,41 @@ +/*=== +function +propdesc isNaN: value=function:isNaN, writable=true, enumerable=false, configurable=true +propdesc length: value=1, writable=false, enumerable=false, configurable=true +propdesc name: value="isNaN", writable=false, enumerable=false, configurable=true +-Infinity false +-1e+100 false +-1234567890.2 false +-305419896 false +-0 false +0 false +305419896 false +1234567890.2 false +1e+100 false +Infinity false +NaN true +undefined false +null false +"" false +"123" false +true false +false false +[object Object] false +===*/ + +/*@include util-base.js@*/ + +print(typeof Number.isNaN); +print(Test.getPropDescString(Number, 'isNaN')); +print(Test.getPropDescString(Number.isNaN, 'length')); +print(Test.getPropDescString(Number.isNaN, 'name')); + +[ + -1 / 0, -1e100, -1234567890.2, -0x12345678, -0, + +0, 0x12345678, 1234567890.2, 1e100, 1 / 0, + 0 / 0, + void 0, null, '', '123', true, false, + { valueOf: function () { return 123.0; } } +].forEach(function (v) { + print(Test.valueToString(v), Test.valueToString(Number.isNaN(v))); +}); diff --git a/tests/ecmascript/test-bi-number-issafeinteger.js b/tests/ecmascript/test-bi-number-issafeinteger.js new file mode 100644 index 00000000..4fd77a74 --- /dev/null +++ b/tests/ecmascript/test-bi-number-issafeinteger.js @@ -0,0 +1,57 @@ +/*=== +function +propdesc isSafeInteger: value=function:isSafeInteger, writable=true, enumerable=false, configurable=true +propdesc length: value=1, writable=false, enumerable=false, configurable=true +propdesc name: value="isSafeInteger", writable=false, enumerable=false, configurable=true +-Infinity false +-1e+100 false +-1234567890.2 false +-305419896 true +-0 true +0 true +305419896 true +1234567890.2 false +1e+100 false +Infinity false +NaN false +-9007199254740992 false +-9007199254740991 true +-9007199254740990 true +9007199254740992 false +9007199254740991 true +9007199254740990 true +undefined false +null false +"" false +"123" false +true false +false false +[object Object] false +===*/ + +/*@include util-base.js@*/ + +print(typeof Number.isSafeInteger); +print(Test.getPropDescString(Number, 'isSafeInteger')); +print(Test.getPropDescString(Number.isSafeInteger, 'length')); +print(Test.getPropDescString(Number.isSafeInteger, 'name')); + +[ + -1 / 0, -1e100, -1234567890.2, -0x12345678, -0, + +0, 0x12345678, 1234567890.2, 1e100, 1 / 0, + 0 / 0, + + // Specific corner case values: note that fractions are not actually + // significant so it suffices to test for integers. + -9007199254740992.0, + -9007199254740991.0, + -9007199254740990.0, + 9007199254740992.0, + 9007199254740991.0, + 9007199254740990.0, + + void 0, null, '', '123', true, false, + { valueOf: function () { return 123.0; } } +].forEach(function (v) { + print(Test.valueToString(v), Test.valueToString(Number.isSafeInteger(v))); +}); diff --git a/tests/ecmascript/test-bi-number-values.js b/tests/ecmascript/test-bi-number-values.js index b15e6d24..4a6c16d0 100644 --- a/tests/ecmascript/test-bi-number-values.js +++ b/tests/ecmascript/test-bi-number-values.js @@ -1,29 +1,25 @@ /*=== -MAX_VALUE -> number 1.7976931348623157e+308 -writable=false, enumerable=false, configurable=false -MIN_VALUE -> number 5e-324 -writable=false, enumerable=false, configurable=false -NaN -> number NaN -writable=false, enumerable=false, configurable=false -POSITIVE_INFINITY -> number Infinity -writable=false, enumerable=false, configurable=false -NEGATIVE_INFINITY -> number -Infinity -writable=false, enumerable=false, configurable=false +propdesc MAX_VALUE: value=1.7976931348623157e+308, writable=false, enumerable=false, configurable=false +propdesc MIN_VALUE: value=5e-324, writable=false, enumerable=false, configurable=false +propdesc NaN: value=NaN, writable=false, enumerable=false, configurable=false +propdesc POSITIVE_INFINITY: value=Infinity, writable=false, enumerable=false, configurable=false +propdesc NEGATIVE_INFINITY: value=-Infinity, writable=false, enumerable=false, configurable=false +propdesc EPSILON: value=2.220446049250313e-16, writable=false, enumerable=false, configurable=false +propdesc MAX_SAFE_INTEGER: value=9007199254740991, writable=false, enumerable=false, configurable=false +propdesc MIN_SAFE_INTEGER: value=-9007199254740991, writable=false, enumerable=false, configurable=false ===*/ +/*@include util-base.js@*/ + function valueTest() { - var names = [ 'MAX_VALUE', 'MIN_VALUE', 'NaN', 'POSITIVE_INFINITY', 'NEGATIVE_INFINITY' ]; + var names = [ + 'MAX_VALUE', 'MIN_VALUE', 'NaN', 'POSITIVE_INFINITY', 'NEGATIVE_INFINITY', + 'EPSILON', 'MAX_SAFE_INTEGER', 'MIN_SAFE_INTEGER' + ]; var i; - var pd, v; for (i = 0; i < names.length; i++) { - pd = Object.getOwnPropertyDescriptor(Number, names[i]); - if (!pd) { print('does not exist'); continue; } - v = pd.value; - print(names[i], '->', typeof v, v); - print('writable=' + pd.writable + - ', enumerable=' + pd.enumerable + - ', configurable=' + pd.configurable); + print(Test.getPropDescString(Number, names[i])); } } diff --git a/tests/ecmascript/test-bi-typedarray-coercion.js b/tests/ecmascript/test-bi-typedarray-coercion.js index e19e785e..247745ca 100644 --- a/tests/ecmascript/test-bi-typedarray-coercion.js +++ b/tests/ecmascript/test-bi-typedarray-coercion.js @@ -6,6 +6,7 @@ * expect string. */ +/*@include util-base.js@*/ /*@include util-buffer.js@*/ /*@include util-string.js@*/ @@ -1974,7 +1975,7 @@ function coercionTest() { } vals.forEach(function (v, j) { b[0] = v; - print(j, v, '->', numberToString(b[0])); + print(j, v, '->', Test.valueToString(b[0])); }); }); } diff --git a/tests/ecmascript/test-bi-typedarray-floatdouble.js b/tests/ecmascript/test-bi-typedarray-floatdouble.js index f4058a11..94218f63 100644 --- a/tests/ecmascript/test-bi-typedarray-floatdouble.js +++ b/tests/ecmascript/test-bi-typedarray-floatdouble.js @@ -2,8 +2,8 @@ * Test float assignment and reading it back. */ +/*@include util-base.js@*/ /*@include util-buffer.js@*/ -/*@include util-string.js@*/ /*--- { @@ -98,7 +98,7 @@ function floatDoubleTest() { values.forEach(function (v, i) { v1[0] = v; - print(i, numberToString(v), numberToString(v1[0])); + print(i, Test.valueToString(v), Test.valueToString(v1[0])); }); } diff --git a/tests/ecmascript/util-base.js b/tests/ecmascript/util-base.js new file mode 100644 index 00000000..ea309353 --- /dev/null +++ b/tests/ecmascript/util-base.js @@ -0,0 +1,94 @@ +/* + * Basic test utilities in a global 'Test' binding. + */ + +(function initTest() { + var GLOBAL = new Function('return this')(); + var Test = GLOBAL.Test; + if (typeof Test !== 'object') { + Test = {}; + Object.defineProperty(GLOBAL, 'Test', { + value: Test, writable: false, enumerable: false, configurable: false + }); + } + + // Summarize any value to a printable string, avoiding side effects where + // possible, preserving details like zero sign. + function valueToString(x) { + if (x === void 0) { + return 'undefined'; + } else if (x === null) { + return 'null'; + } else if (typeof x === 'boolean') { + return String(x); + } else if (typeof x === 'number') { + if (x !== 0) { + return String(x); + } else { + return (1 / x > 0) ? '0' : '-0'; + } + } else if (typeof x === 'string') { + return JSON.stringify(x); + } else if (typeof x === 'symbol') { + return String(x); + } else if (typeof x === 'object') { + return Object.prototype.toString.call(x); + } else if (typeof x === 'function') { + var pd = Object.getOwnPropertyDescriptor(x, 'name'); + if (pd && typeof pd.value === 'string') { + return 'function:' + pd.value; + } else { + return 'function'; + } + } else if (typeof x === 'pointer') { + if (String(x) === 'null') { + return 'pointer:null'; + } else { + return 'pointer:non-null'; // specific pointer is very rarely interesting for tests + } + } else if (typeof x === 'buffer') { + // No longer used in Duktape 2.x. + return 'buffer:' + x.length; + } else { + return 'unknown:' + String(x); + } + } + + // Get property descriptor string for arbitrary object and key. + function getPropDescString(obj, key) { + if (obj === void 0) { + return 'propdesc ' + key + ': undefined object'; + } else if (obj === null) { + return 'propdesc ' + key + ': null object'; + } else { + try { + var pd = Object.getOwnPropertyDescriptor(obj, key); + var parts = []; + if (typeof pd.value !== 'undefined') { + parts.push('value=' + valueToString(pd.value)); + } + if (typeof pd.writable !== 'undefined') { + parts.push('writable=' + valueToString(pd.writable)); + } + if (typeof pd.enumerable !== 'undefined') { + parts.push('enumerable=' + valueToString(pd.enumerable)); + } + if (typeof pd.configurable !== 'undefined') { + parts.push('configurable=' + valueToString(pd.configurable)); + } + if (typeof pd.get !== 'undefined') { + parts.push('get=' + valueToString(pd.get)); + } + if (typeof pd.set !== 'undefined') { + parts.push('set=' + valueToString(pd.set)); + } + return 'propdesc ' + key + ': ' + parts.join(', '); + } catch (e) { + return 'propdesc ' + key + ': ' + String(e); + } + } + } + + Test.valueToString = valueToString; + Test.getPropDescString = getPropDescString; +}()); diff --git a/tests/ecmascript/util-regexp.js b/tests/ecmascript/util-regexp.js index ed72d939..1421749c 100644 --- a/tests/ecmascript/util-regexp.js +++ b/tests/ecmascript/util-regexp.js @@ -1,5 +1,7 @@ if (typeof RegExpUtil !== 'object') { - RegExpUtil = {}; + Object.defineProperty(new Function('return this')(), 'RegExpUtil', { + value: {}, writable: false, enumerable: false, configurable: false + }); } // Get a list of character repeats in an input string. For example, for diff --git a/tests/ecmascript/util-string.js b/tests/ecmascript/util-string.js index 4e123987..8e6fa2fe 100644 --- a/tests/ecmascript/util-string.js +++ b/tests/ecmascript/util-string.js @@ -15,12 +15,6 @@ function checksumString(x) { return res; } -// Number to string, preserve negative zero sign. -function numberToString(v) { - if (v !== 0) { return String(v); } - return (1 / v > 0) ? '0' : '-0'; -} - // Escape a string with codepoint escaping. function safeEscapeString(s) { var tmp = [];