diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js index 5957532e68b..48de04b5467 100644 --- a/js/src/builtin/String.js +++ b/js/src/builtin/String.js @@ -4,6 +4,35 @@ /*global intl_Collator: false, */ +/* ES6 Draft September 5, 2013 21.1.3.3 */ +function String_codePointAt(pos) { + // Steps 1-3. + CheckObjectCoercible(this); + var S = ToString(this); + + // Steps 4-5. + var position = ToInteger(pos); + + // Step 6. + var size = S.length; + + // Step 7. + if (position < 0 || position >= size) + return undefined; + + // Steps 8-9. + var first = callFunction(std_String_charCodeAt, S, position); + if (first < 0xD800 || first > 0xDBFF || position + 1 === size) + return first; + + // Steps 10-11. + var second = callFunction(std_String_charCodeAt, S, position + 1); + if (second < 0xDC00 || second > 0xDFFF) + return first; + + // Step 12. + return (first - 0xD800) * 0x400 + (second - 0xDC00) + 0x10000; +} var collatorCache = new Record(); @@ -117,6 +146,45 @@ function String_localeCompare(that) { return intl_CompareStrings(collator, S, That); } +/* ES6 Draft September 5, 2013 21.1.2.2 */ +function String_static_fromCodePoint() { + // Step 1. is not relevant + // Step 2. + var length = arguments.length; + + // Step 3. + var elements = new List(); + + // Step 4-5., 5g. + for (var nextIndex = 0; nextIndex < length; nextIndex++) { + // Step 5a. + var next = arguments[nextIndex]; + // Step 5b-c. + var nextCP = ToNumber(next); + + // Step 5d. + if (nextCP !== ToInteger(nextCP) || std_isNaN(nextCP)) + ThrowError(JSMSG_NOT_A_CODEPOINT, ToString(nextCP)); + + // Step 5e. + if (nextCP < 0 || nextCP > 0x10FFFF) + ThrowError(JSMSG_NOT_A_CODEPOINT, ToString(nextCP)); + + // Step 5f. + // Inlined UTF-16 Encoding + if (nextCP <= 0xFFFF) { + elements.push(nextCP); + continue; + } + + elements.push((((nextCP - 0x10000) / 0x400) | 0) + 0xD800); + elements.push((nextCP - 0x10000) % 0x400 + 0xDC00); + } + + // Step 6. + return callFunction(std_Function_apply, std_String_fromCharCode, null, elements); +} + /** * Compare String str1 against String str2, using the locale and collation * options provided. @@ -131,3 +199,4 @@ function String_static_localeCompare(str1, str2) { var options = arguments.length > 3 ? arguments[3] : undefined; return callFunction(String_localeCompare, str1, str2, locales, options); } + diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index ca7574b49fa..c56e4de5d69 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -64,6 +64,7 @@ var std_Object_getOwnPropertyNames = Object.getOwnPropertyNames; var std_Object_hasOwnProperty = Object.prototype.hasOwnProperty; var std_RegExp_test = RegExp.prototype.test; var Std_String = String; +var std_String_fromCharCode = String.fromCharCode; var std_String_charCodeAt = String.prototype.charCodeAt; var std_String_indexOf = String.prototype.indexOf; var std_String_lastIndexOf = String.prototype.lastIndexOf; diff --git a/js/src/js.msg b/js/src/js.msg index f6b1eae7c36..d282974c413 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -223,7 +223,7 @@ MSG_DEF(JSMSG_TOO_MANY_CATCH_VARS, 169, 0, JSEXN_SYNTAXERR, "too many catch v MSG_DEF(JSMSG_NEGATIVE_REPETITION_COUNT, 170, 0, JSEXN_RANGEERR, "repeat count must be non-negative") MSG_DEF(JSMSG_INVALID_FOR_OF_INIT, 171, 0, JSEXN_SYNTAXERR, "for-of loop variable declaration may not have an initializer") MSG_DEF(JSMSG_INVALID_MAP_ITERABLE, 172, 0, JSEXN_TYPEERR, "iterable for map should have array-like objects") -MSG_DEF(JSMSG_UNUSED173, 173, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_NOT_A_CODEPOINT, 173, 1, JSEXN_RANGEERR, "{0} is not a valid code point") MSG_DEF(JSMSG_UNUSED174, 174, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_NESTING_GENERATOR, 175, 0, JSEXN_TYPEERR, "already executing generator") MSG_DEF(JSMSG_UNUSED176, 176, 0, JSEXN_NONE, "") diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index ebb32ad02d2..a66dfc4699b 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3678,6 +3678,7 @@ static const JSFunctionSpec string_methods[] = { JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE), JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE), JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE), + JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0), JS_FN("contains", str_contains, 1,JSFUN_GENERIC_NATIVE), JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE), JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE), @@ -3792,6 +3793,7 @@ js::str_fromCharCode(JSContext *cx, unsigned argc, Value *vp) static const JSFunctionSpec string_static_methods[] = { JS_FN("fromCharCode", js::str_fromCharCode, 1, 0), + JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 0,0), // This must be at the end because of bug 853075: functions listed after // self-hosted methods aren't available in self-hosted code. diff --git a/js/src/tests/ecma_6/String/browser.js b/js/src/tests/ecma_6/String/browser.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/js/src/tests/ecma_6/String/codePointAt.js b/js/src/tests/ecma_6/String/codePointAt.js new file mode 100644 index 00000000000..5b149693908 --- /dev/null +++ b/js/src/tests/ecma_6/String/codePointAt.js @@ -0,0 +1,84 @@ +var BUGNUMBER = 918879; +var summary = 'String.prototype.codePointAt'; + +print(BUGNUMBER + ": " + summary); + +// Tests taken from: +// https://github.com/mathiasbynens/String.prototype.codePointAt/blob/master/tests/tests.js +assertEq(String.prototype.codePointAt.length, 1); +assertEq(String.prototype.propertyIsEnumerable('codePointAt'), false); + +// String that starts with a BMP symbol +assertEq('abc\uD834\uDF06def'.codePointAt(''), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt('_'), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt(), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt(-Infinity), undefined); +assertEq('abc\uD834\uDF06def'.codePointAt(-1), undefined); +assertEq('abc\uD834\uDF06def'.codePointAt(-0), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt(0), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt(3), 0x1D306); +assertEq('abc\uD834\uDF06def'.codePointAt(4), 0xDF06); +assertEq('abc\uD834\uDF06def'.codePointAt(5), 0x64); +assertEq('abc\uD834\uDF06def'.codePointAt(42), undefined); +assertEq('abc\uD834\uDF06def'.codePointAt(Infinity), undefined); +assertEq('abc\uD834\uDF06def'.codePointAt(Infinity), undefined); +assertEq('abc\uD834\uDF06def'.codePointAt(NaN), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt(false), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt(null), 0x61); +assertEq('abc\uD834\uDF06def'.codePointAt(undefined), 0x61); + +// String that starts with an astral symbol +assertEq('\uD834\uDF06def'.codePointAt(''), 0x1D306); +assertEq('\uD834\uDF06def'.codePointAt('1'), 0xDF06); +assertEq('\uD834\uDF06def'.codePointAt('_'), 0x1D306); +assertEq('\uD834\uDF06def'.codePointAt(), 0x1D306); +assertEq('\uD834\uDF06def'.codePointAt(-1), undefined); +assertEq('\uD834\uDF06def'.codePointAt(-0), 0x1D306); +assertEq('\uD834\uDF06def'.codePointAt(0), 0x1D306); +assertEq('\uD834\uDF06def'.codePointAt(1), 0xDF06); +assertEq('\uD834\uDF06def'.codePointAt(42), undefined); +assertEq('\uD834\uDF06def'.codePointAt(false), 0x1D306); +assertEq('\uD834\uDF06def'.codePointAt(null), 0x1D306); +assertEq('\uD834\uDF06def'.codePointAt(undefined), 0x1D306); + +// Lone high surrogates +assertEq('\uD834abc'.codePointAt(''), 0xD834); +assertEq('\uD834abc'.codePointAt('_'), 0xD834); +assertEq('\uD834abc'.codePointAt(), 0xD834); +assertEq('\uD834abc'.codePointAt(-1), undefined); +assertEq('\uD834abc'.codePointAt(-0), 0xD834); +assertEq('\uD834abc'.codePointAt(0), 0xD834); +assertEq('\uD834abc'.codePointAt(false), 0xD834); +assertEq('\uD834abc'.codePointAt(NaN), 0xD834); +assertEq('\uD834abc'.codePointAt(null), 0xD834); +assertEq('\uD834abc'.codePointAt(undefined), 0xD834); + +// Lone low surrogates +assertEq('\uDF06abc'.codePointAt(''), 0xDF06); +assertEq('\uDF06abc'.codePointAt('_'), 0xDF06); +assertEq('\uDF06abc'.codePointAt(), 0xDF06); +assertEq('\uDF06abc'.codePointAt(-1), undefined); +assertEq('\uDF06abc'.codePointAt(-0), 0xDF06); +assertEq('\uDF06abc'.codePointAt(0), 0xDF06); +assertEq('\uDF06abc'.codePointAt(false), 0xDF06); +assertEq('\uDF06abc'.codePointAt(NaN), 0xDF06); +assertEq('\uDF06abc'.codePointAt(null), 0xDF06); +assertEq('\uDF06abc'.codePointAt(undefined), 0xDF06); + +(function() { String.prototype.codePointAt.call(undefined); }, TypeError); +assertThrowsInstanceOf(function() { String.prototype.codePointAt.call(undefined, 4); }, TypeError); +assertThrowsInstanceOf(function() { String.prototype.codePointAt.call(null); }, TypeError); +assertThrowsInstanceOf(function() { String.prototype.codePointAt.call(null, 4); }, TypeError); +assertEq(String.prototype.codePointAt.call(42, 0), 0x34); +assertEq(String.prototype.codePointAt.call(42, 1), 0x32); +assertEq(String.prototype.codePointAt.call({ 'toString': function() { return 'abc'; } }, 2), 0x63); + +assertThrowsInstanceOf(function() { String.prototype.codePointAt.apply(undefined); }, TypeError); +assertThrowsInstanceOf(function() { String.prototype.codePointAt.apply(undefined, [4]); }, TypeError); +assertThrowsInstanceOf(function() { String.prototype.codePointAt.apply(null); }, TypeError); +assertThrowsInstanceOf(function() { String.prototype.codePointAt.apply(null, [4]); }, TypeError); +assertEq(String.prototype.codePointAt.apply(42, [0]), 0x34); +assertEq(String.prototype.codePointAt.apply(42, [1]), 0x32); +assertEq(String.prototype.codePointAt.apply({ 'toString': function() { return 'abc'; } }, [2]), 0x63); + +reportCompare(0, 0, "ok"); diff --git a/js/src/tests/ecma_6/String/fromCodePoint.js b/js/src/tests/ecma_6/String/fromCodePoint.js new file mode 100644 index 00000000000..140701256a3 --- /dev/null +++ b/js/src/tests/ecma_6/String/fromCodePoint.js @@ -0,0 +1,48 @@ +var BUGNUMBER = 918879; +var summary = 'String.fromCodePoint'; + +print(BUGNUMBER + ": " + summary); + +// Tests taken from: +// https://github.com/mathiasbynens/String.fromCodePoint/blob/master/tests/tests.js + +assertEq(String.fromCodePoint.length, 0); +assertEq(String.propertyIsEnumerable('fromCodePoint'), false); + +assertEq(String.fromCodePoint(''), '\0'); +assertEq(String.fromCodePoint(), ''); +assertEq(String.fromCodePoint(-0), '\0'); +assertEq(String.fromCodePoint(0), '\0'); +assertEq(String.fromCodePoint(0x1D306), '\uD834\uDF06'); +assertEq(String.fromCodePoint(0x1D306, 0x61, 0x1D307), '\uD834\uDF06a\uD834\uDF07'); +assertEq(String.fromCodePoint(0x61, 0x62, 0x1D307), 'ab\uD834\uDF07'); +assertEq(String.fromCodePoint(false), '\0'); +assertEq(String.fromCodePoint(null), '\0'); + +assertThrowsInstanceOf(function() { String.fromCodePoint('_'); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint('+Infinity'); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint('-Infinity'); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint(-1); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint(0x10FFFF + 1); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint(3.14); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint(3e-2); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint(Infinity); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint(NaN); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint(undefined); }, RangeError); +assertThrowsInstanceOf(function() { String.fromCodePoint({}); }, RangeError); + +var counter = Math.pow(2, 15) * 3 / 2; +var result = []; +while (--counter >= 0) { + result.push(0); // one code unit per symbol +} +String.fromCodePoint.apply(null, result); // must not throw + +var counter = Math.pow(2, 15) * 3 / 2; +var result = []; +while (--counter >= 0) { + result.push(0xFFFF + 1); // two code units per symbol +} +String.fromCodePoint.apply(null, result); // must not throw + +reportCompare(0, 0, "ok"); diff --git a/js/src/tests/ecma_6/String/shell.js b/js/src/tests/ecma_6/String/shell.js new file mode 100644 index 00000000000..e69de29bb2d