summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/String.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/String.js')
-rw-r--r--js/src/builtin/String.js1203
1 files changed, 1203 insertions, 0 deletions
diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js
new file mode 100644
index 0000000000..386bd8d40a
--- /dev/null
+++ b/js/src/builtin/String.js
@@ -0,0 +1,1203 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function StringProtoHasNoMatch() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto)) {
+ return false;
+ }
+ return !(GetBuiltinSymbol("match") in StringProto);
+}
+
+function IsStringMatchOptimizable() {
+ var RegExpProto = GetBuiltinPrototype("RegExp");
+ // If RegExpPrototypeOptimizable succeeds, `exec` and `@@match` are
+ // guaranteed to be data properties.
+ return (
+ RegExpPrototypeOptimizable(RegExpProto) &&
+ RegExpProto.exec === RegExp_prototype_Exec &&
+ RegExpProto[GetBuiltinSymbol("match")] === RegExpMatch
+ );
+}
+
+function ThrowIncompatibleMethod(name, thisv) {
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "String", name, ToString(thisv));
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.11.
+function String_match(regexp) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("match", this);
+ }
+
+ // Step 2.
+ var isPatternString = typeof regexp === "string";
+ if (
+ !(isPatternString && StringProtoHasNoMatch()) &&
+ !IsNullOrUndefined(regexp)
+ ) {
+ // Step 2.a.
+ var matcher = GetMethod(regexp, GetBuiltinSymbol("match"));
+
+ // Step 2.b.
+ if (matcher !== undefined) {
+ return callContentFunction(matcher, regexp, this);
+ }
+ }
+
+ // Step 3.
+ var S = ToString(this);
+
+ if (isPatternString && IsStringMatchOptimizable()) {
+ var flatResult = FlatStringMatch(S, regexp);
+ if (flatResult !== undefined) {
+ return flatResult;
+ }
+ }
+
+ // Step 4.
+ var rx = RegExpCreate(regexp);
+
+ // Step 5 (optimized case).
+ if (IsStringMatchOptimizable()) {
+ return RegExpMatcher(rx, S, 0);
+ }
+
+ // Step 5.
+ return callContentFunction(GetMethod(rx, GetBuiltinSymbol("match")), rx, S);
+}
+
+// String.prototype.matchAll proposal.
+//
+// String.prototype.matchAll ( regexp )
+function String_matchAll(regexp) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("matchAll", this);
+ }
+
+ // Step 2.
+ if (!IsNullOrUndefined(regexp)) {
+ // Steps 2.a-b.
+ if (IsRegExp(regexp)) {
+ // Step 2.b.i.
+ var flags = regexp.flags;
+
+ // Step 2.b.ii.
+ if (IsNullOrUndefined(flags)) {
+ ThrowTypeError(JSMSG_FLAGS_UNDEFINED_OR_NULL);
+ }
+
+ // Step 2.b.iii.
+ if (!callFunction(std_String_includes, ToString(flags), "g")) {
+ ThrowTypeError(JSMSG_REQUIRES_GLOBAL_REGEXP, "matchAll");
+ }
+ }
+
+ // Step 2.c.
+ var matcher = GetMethod(regexp, GetBuiltinSymbol("matchAll"));
+
+ // Step 2.d.
+ if (matcher !== undefined) {
+ return callContentFunction(matcher, regexp, this);
+ }
+ }
+
+ // Step 3.
+ var string = ToString(this);
+
+ // Step 4.
+ var rx = RegExpCreate(regexp, "g");
+
+ // Step 5.
+ return callContentFunction(
+ GetMethod(rx, GetBuiltinSymbol("matchAll")),
+ rx,
+ string
+ );
+}
+
+/**
+ * A helper function implementing the logic for both String.prototype.padStart
+ * and String.prototype.padEnd as described in ES7 Draft March 29, 2016
+ */
+function String_pad(maxLength, fillString, padEnd) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod(padEnd ? "padEnd" : "padStart", this);
+ }
+
+ // Step 2.
+ let str = ToString(this);
+
+ // Steps 3-4.
+ let intMaxLength = ToLength(maxLength);
+ let strLen = str.length;
+
+ // Step 5.
+ if (intMaxLength <= strLen) {
+ return str;
+ }
+
+ // Steps 6-7.
+ assert(fillString !== undefined, "never called when fillString is undefined");
+ let filler = ToString(fillString);
+
+ // Step 8.
+ if (filler === "") {
+ return str;
+ }
+
+ // Throw an error if the final string length exceeds the maximum string
+ // length. Perform this check early so we can use int32 operations below.
+ if (intMaxLength > MAX_STRING_LENGTH) {
+ ThrowRangeError(JSMSG_RESULTING_STRING_TOO_LARGE);
+ }
+
+ // Step 9.
+ let fillLen = intMaxLength - strLen;
+
+ // Step 10.
+ // Perform an int32 division to ensure String_repeat is not called with a
+ // double to avoid repeated bailouts in ToInteger.
+ let truncatedStringFiller = callFunction(
+ String_repeat,
+ filler,
+ (fillLen / filler.length) | 0
+ );
+
+ truncatedStringFiller += Substring(filler, 0, fillLen % filler.length);
+
+ // Step 11.
+ if (padEnd === true) {
+ return str + truncatedStringFiller;
+ }
+ return truncatedStringFiller + str;
+}
+
+function String_pad_start(maxLength, fillString = " ") {
+ return callFunction(String_pad, this, maxLength, fillString, false);
+}
+
+function String_pad_end(maxLength, fillString = " ") {
+ return callFunction(String_pad, this, maxLength, fillString, true);
+}
+
+function StringProtoHasNoReplace() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto)) {
+ return false;
+ }
+ return !(GetBuiltinSymbol("replace") in StringProto);
+}
+
+// A thin wrapper to call SubstringKernel with int32-typed arguments.
+// Caller should check the range of |from| and |length|.
+function Substring(str, from, length) {
+ assert(typeof str === "string", "|str| should be a string");
+ assert(
+ (from | 0) === from,
+ "coercing |from| into int32 should not change the value"
+ );
+ assert(
+ (length | 0) === length,
+ "coercing |length| into int32 should not change the value"
+ );
+
+ return SubstringKernel(str, from | 0, length | 0);
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.14.
+function String_replace(searchValue, replaceValue) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("replace", this);
+ }
+
+ // Step 2.
+ if (
+ !(typeof searchValue === "string" && StringProtoHasNoReplace()) &&
+ !IsNullOrUndefined(searchValue)
+ ) {
+ // Step 2.a.
+ var replacer = GetMethod(searchValue, GetBuiltinSymbol("replace"));
+
+ // Step 2.b.
+ if (replacer !== undefined) {
+ return callContentFunction(replacer, searchValue, this, replaceValue);
+ }
+ }
+
+ // Step 3.
+ var string = ToString(this);
+
+ // Step 4.
+ var searchString = ToString(searchValue);
+
+ if (typeof replaceValue === "string") {
+ // Steps 6-12: Optimized for string case.
+ return StringReplaceString(string, searchString, replaceValue);
+ }
+
+ // Step 5.
+ if (!IsCallable(replaceValue)) {
+ // Steps 6-12.
+ return StringReplaceString(string, searchString, ToString(replaceValue));
+ }
+
+ // Step 7.
+ var pos = callFunction(std_String_indexOf, string, searchString);
+ if (pos === -1) {
+ return string;
+ }
+
+ // Step 8.
+ var replStr = ToString(
+ callContentFunction(replaceValue, undefined, searchString, pos, string)
+ );
+
+ // Step 10.
+ var tailPos = pos + searchString.length;
+
+ // Step 11.
+ var newString;
+ if (pos === 0) {
+ newString = "";
+ } else {
+ newString = Substring(string, 0, pos);
+ }
+
+ newString += replStr;
+ var stringLength = string.length;
+ if (tailPos < stringLength) {
+ newString += Substring(string, tailPos, stringLength - tailPos);
+ }
+
+ // Step 12.
+ return newString;
+}
+
+// String.prototype.replaceAll (Stage 3 proposal)
+// https://tc39.es/proposal-string-replaceall/
+//
+// String.prototype.replaceAll ( searchValue, replaceValue )
+function String_replaceAll(searchValue, replaceValue) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("replaceAll", this);
+ }
+
+ // Step 2.
+ if (!IsNullOrUndefined(searchValue)) {
+ // Steps 2.a-b.
+ if (IsRegExp(searchValue)) {
+ // Step 2.b.i.
+ var flags = searchValue.flags;
+
+ // Step 2.b.ii.
+ if (IsNullOrUndefined(flags)) {
+ ThrowTypeError(JSMSG_FLAGS_UNDEFINED_OR_NULL);
+ }
+
+ // Step 2.b.iii.
+ if (!callFunction(std_String_includes, ToString(flags), "g")) {
+ ThrowTypeError(JSMSG_REQUIRES_GLOBAL_REGEXP, "replaceAll");
+ }
+ }
+
+ // Step 2.c.
+ var replacer = GetMethod(searchValue, GetBuiltinSymbol("replace"));
+
+ // Step 2.b.
+ if (replacer !== undefined) {
+ return callContentFunction(replacer, searchValue, this, replaceValue);
+ }
+ }
+
+ // Step 3.
+ var string = ToString(this);
+
+ // Step 4.
+ var searchString = ToString(searchValue);
+
+ // Steps 5-6.
+ if (!IsCallable(replaceValue)) {
+ // Steps 7-16.
+ return StringReplaceAllString(string, searchString, ToString(replaceValue));
+ }
+
+ // Step 7.
+ var searchLength = searchString.length;
+
+ // Step 8.
+ var advanceBy = std_Math_max(1, searchLength);
+
+ // Step 9 (not needed in this implementation).
+
+ // Step 12.
+ var endOfLastMatch = 0;
+
+ // Step 13.
+ var result = "";
+
+ // Steps 10-11, 14.
+ var position = 0;
+ while (true) {
+ // Steps 10-11.
+ //
+ // StringIndexOf doesn't clamp the |position| argument to the input
+ // string length, i.e. |StringIndexOf("abc", "", 4)| returns -1,
+ // whereas |"abc".indexOf("", 4)| returns 3. That means we need to
+ // exit the loop when |nextPosition| is smaller than |position| and
+ // not just when |nextPosition| is -1.
+ var nextPosition = callFunction(
+ std_String_indexOf,
+ string,
+ searchString,
+ position
+ );
+ if (nextPosition < position) {
+ break;
+ }
+ position = nextPosition;
+
+ // Step 14.a.
+ var replacement = ToString(
+ callContentFunction(
+ replaceValue,
+ undefined,
+ searchString,
+ position,
+ string
+ )
+ );
+
+ // Step 14.b (not applicable).
+
+ // Step 14.c.
+ var stringSlice = Substring(
+ string,
+ endOfLastMatch,
+ position - endOfLastMatch
+ );
+
+ // Step 14.d.
+ result += stringSlice + replacement;
+
+ // Step 14.e.
+ endOfLastMatch = position + searchLength;
+
+ // Step 11.b.
+ position += advanceBy;
+ }
+
+ // Step 15.
+ if (endOfLastMatch < string.length) {
+ // Step 15.a.
+ result += Substring(string, endOfLastMatch, string.length - endOfLastMatch);
+ }
+
+ // Step 16.
+ return result;
+}
+
+function StringProtoHasNoSearch() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto)) {
+ return false;
+ }
+ return !(GetBuiltinSymbol("search") in StringProto);
+}
+
+function IsStringSearchOptimizable() {
+ var RegExpProto = GetBuiltinPrototype("RegExp");
+ // If RegExpPrototypeOptimizable succeeds, `exec` and `@@search` are
+ // guaranteed to be data properties.
+ return (
+ RegExpPrototypeOptimizable(RegExpProto) &&
+ RegExpProto.exec === RegExp_prototype_Exec &&
+ RegExpProto[GetBuiltinSymbol("search")] === RegExpSearch
+ );
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.15.
+function String_search(regexp) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("search", this);
+ }
+
+ // Step 2.
+ var isPatternString = typeof regexp === "string";
+ if (
+ !(isPatternString && StringProtoHasNoSearch()) &&
+ !IsNullOrUndefined(regexp)
+ ) {
+ // Step 2.a.
+ var searcher = GetMethod(regexp, GetBuiltinSymbol("search"));
+
+ // Step 2.b.
+ if (searcher !== undefined) {
+ return callContentFunction(searcher, regexp, this);
+ }
+ }
+
+ // Step 3.
+ var string = ToString(this);
+
+ if (isPatternString && IsStringSearchOptimizable()) {
+ var flatResult = FlatStringSearch(string, regexp);
+ if (flatResult !== -2) {
+ return flatResult;
+ }
+ }
+
+ // Step 4.
+ var rx = RegExpCreate(regexp);
+
+ // Step 5.
+ return callContentFunction(
+ GetMethod(rx, GetBuiltinSymbol("search")),
+ rx,
+ string
+ );
+}
+
+function StringProtoHasNoSplit() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto)) {
+ return false;
+ }
+ return !(GetBuiltinSymbol("split") in StringProto);
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.17.
+function String_split(separator, limit) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("split", this);
+ }
+
+ // Optimized path for string.split(string), especially when both strings
+ // are constants. Following sequence of if's cannot be put together in
+ // order that IonMonkey sees the constant if present (bug 1246141).
+ if (typeof this === "string") {
+ if (StringProtoHasNoSplit()) {
+ if (typeof separator === "string") {
+ if (limit === undefined) {
+ // inlineConstantStringSplitString needs both arguments to
+ // be MConstant, so pass them directly.
+ return StringSplitString(this, separator);
+ }
+ }
+ }
+ }
+
+ // Step 2.
+ if (
+ !(typeof separator === "string" && StringProtoHasNoSplit()) &&
+ !IsNullOrUndefined(separator)
+ ) {
+ // Step 2.a.
+ var splitter = GetMethod(separator, GetBuiltinSymbol("split"));
+
+ // Step 2.b.
+ if (splitter !== undefined) {
+ return callContentFunction(splitter, separator, this, limit);
+ }
+ }
+
+ // Step 3.
+ var S = ToString(this);
+
+ // Step 6.
+ var R;
+ if (limit !== undefined) {
+ var lim = limit >>> 0;
+
+ // Step 9.
+ R = ToString(separator);
+
+ // Step 10.
+ if (lim === 0) {
+ return [];
+ }
+
+ // Step 11.
+ if (separator === undefined) {
+ return [S];
+ }
+
+ // Steps 4, 8, 12-18.
+ return StringSplitStringLimit(S, R, lim);
+ }
+
+ // Step 9.
+ R = ToString(separator);
+
+ // Step 11.
+ if (separator === undefined) {
+ return [S];
+ }
+
+ // Optimized path.
+ // Steps 4, 8, 12-18.
+ return StringSplitString(S, R);
+}
+
+// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
+// 21.1.3.22 String.prototype.substring ( start, end )
+function String_substring(start, end) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("substring", this);
+ }
+
+ // Step 2.
+ var str = ToString(this);
+
+ // Step 3.
+ var len = str.length;
+
+ // Step 4.
+ var intStart = ToInteger(start);
+
+ // Step 5.
+ var intEnd = end === undefined ? len : ToInteger(end);
+
+ // Step 6.
+ var finalStart = std_Math_min(std_Math_max(intStart, 0), len);
+
+ // Step 7.
+ var finalEnd = std_Math_min(std_Math_max(intEnd, 0), len);
+
+ // Step 8.
+ var from = std_Math_min(finalStart, finalEnd);
+
+ // Step 9.
+ var to = std_Math_max(finalStart, finalEnd);
+
+ // Step 10.
+ // While |from| and |to - from| are bounded to the length of |str| and this
+ // and thus definitely in the int32 range, they can still be typed as
+ // double. Eagerly truncate since SubstringKernel only accepts int32.
+ return SubstringKernel(str, from | 0, (to - from) | 0);
+}
+SetIsInlinableLargeFunction(String_substring);
+
+// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
+// B.2.3.1 String.prototype.substr ( start, length )
+function String_substr(start, length) {
+ // Steps 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("substr", this);
+ }
+
+ // Step 2.
+ var str = ToString(this);
+
+ // Step 3.
+ var intStart = ToInteger(start);
+
+ // Steps 4-5.
+ var size = str.length;
+ // Use |size| instead of +Infinity to avoid performing calculations with
+ // doubles. (The result is the same either way.)
+ var end = length === undefined ? size : ToInteger(length);
+
+ // Step 6.
+ if (intStart < 0) {
+ intStart = std_Math_max(intStart + size, 0);
+ } else {
+ // Restrict the input range to allow better Ion optimizations.
+ intStart = std_Math_min(intStart, size);
+ }
+
+ // Step 7.
+ var resultLength = std_Math_min(std_Math_max(end, 0), size - intStart);
+
+ // Step 8.
+ assert(
+ 0 <= resultLength && resultLength <= size - intStart,
+ "resultLength is a valid substring length value"
+ );
+
+ // Step 9.
+ // While |intStart| and |resultLength| are bounded to the length of |str|
+ // and thus definitely in the int32 range, they can still be typed as
+ // double. Eagerly truncate since SubstringKernel only accepts int32.
+ return SubstringKernel(str, intStart | 0, resultLength | 0);
+}
+SetIsInlinableLargeFunction(String_substr);
+
+// ES2021 draft rev 12a546b92275a0e2f834017db2727bb9c6f6c8fd
+// 21.1.3.4 String.prototype.concat ( ...args )
+// Note: String.prototype.concat.length is 1.
+function String_concat(arg1) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("concat", this);
+ }
+
+ // Step 2.
+ var str = ToString(this);
+
+ // Specialize for the most common number of arguments for better inlining.
+ if (ArgumentsLength() === 0) {
+ return str;
+ }
+ if (ArgumentsLength() === 1) {
+ return str + ToString(GetArgument(0));
+ }
+ if (ArgumentsLength() === 2) {
+ return str + ToString(GetArgument(0)) + ToString(GetArgument(1));
+ }
+
+ // Step 3. (implicit)
+ // Step 4.
+ var result = str;
+
+ // Step 5.
+ for (var i = 0; i < ArgumentsLength(); i++) {
+ // Steps 5.a-b.
+ var nextString = ToString(GetArgument(i));
+ // Step 5.c.
+ result += nextString;
+ }
+
+ // Step 6.
+ return result;
+}
+
+// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
+// 21.1.3.19 String.prototype.slice ( start, end )
+function String_slice(start, end) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("slice", this);
+ }
+
+ // Step 2.
+ var str = ToString(this);
+
+ // Step 3.
+ var len = str.length;
+
+ // Step 4.
+ var intStart = ToInteger(start);
+
+ // Step 5.
+ var intEnd = end === undefined ? len : ToInteger(end);
+
+ // Step 6.
+ var from =
+ intStart < 0
+ ? std_Math_max(len + intStart, 0)
+ : std_Math_min(intStart, len);
+
+ // Step 7.
+ var to =
+ intEnd < 0 ? std_Math_max(len + intEnd, 0) : std_Math_min(intEnd, len);
+
+ // Step 8.
+ var span = std_Math_max(to - from, 0);
+
+ // Step 9.
+ // While |from| and |span| are bounded to the length of |str|
+ // and thus definitely in the int32 range, they can still be typed as
+ // double. Eagerly truncate since SubstringKernel only accepts int32.
+ return SubstringKernel(str, from | 0, span | 0);
+}
+SetIsInlinableLargeFunction(String_slice);
+
+// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
+// 21.1.3.3 String.prototype.codePointAt ( pos )
+function String_codePointAt(pos) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("codePointAt", this);
+ }
+
+ // Step 2.
+ var S = ToString(this);
+
+ // Step 3.
+ var position = ToInteger(pos);
+
+ // Step 4.
+ var size = S.length;
+
+ // Step 5.
+ if (position < 0 || position >= size) {
+ return undefined;
+ }
+
+ // Steps 6-7.
+ var first = callFunction(std_String_charCodeAt, S, position);
+ if (first < 0xd800 || first > 0xdbff || position + 1 === size) {
+ return first;
+ }
+
+ // Steps 8-9.
+ var second = callFunction(std_String_charCodeAt, S, position + 1);
+ if (second < 0xdc00 || second > 0xdfff) {
+ return first;
+ }
+
+ // Step 10.
+ return (first - 0xd800) * 0x400 + (second - 0xdc00) + 0x10000;
+}
+
+// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
+// 21.1.3.16 String.prototype.repeat ( count )
+function String_repeat(count) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("repeat", this);
+ }
+
+ // Step 2.
+ var S = ToString(this);
+
+ // Step 3.
+ var n = ToInteger(count);
+
+ // Step 4.
+ if (n < 0) {
+ ThrowRangeError(JSMSG_NEGATIVE_REPETITION_COUNT);
+ }
+
+ // Step 5.
+ // Inverted condition to handle |Infinity * 0 = NaN| correctly.
+ if (!(n * S.length <= MAX_STRING_LENGTH)) {
+ ThrowRangeError(JSMSG_RESULTING_STRING_TOO_LARGE);
+ }
+
+ // Communicate |n|'s possible range to the compiler. We actually use
+ // MAX_STRING_LENGTH + 1 as range because that's a valid bit mask. That's
+ // fine because it's only used as optimization hint.
+ assert(
+ TO_INT32(MAX_STRING_LENGTH + 1) === MAX_STRING_LENGTH + 1,
+ "MAX_STRING_LENGTH + 1 must fit in int32"
+ );
+ assert(
+ ((MAX_STRING_LENGTH + 1) & (MAX_STRING_LENGTH + 2)) === 0,
+ "MAX_STRING_LENGTH + 1 can be used as a bitmask"
+ );
+ n = n & (MAX_STRING_LENGTH + 1);
+
+ // Steps 6-7.
+ var T = "";
+ for (;;) {
+ if (n & 1) {
+ T += S;
+ }
+ n >>= 1;
+ if (n) {
+ S += S;
+ } else {
+ break;
+ }
+ }
+ return T;
+}
+
+// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
+function String_iterator() {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowTypeError(
+ JSMSG_INCOMPATIBLE_PROTO2,
+ "String",
+ "Symbol.iterator",
+ ToString(this)
+ );
+ }
+
+ // Step 2.
+ var S = ToString(this);
+
+ // Step 3.
+ var iterator = NewStringIterator();
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_TARGET, S);
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_NEXT_INDEX, 0);
+ return iterator;
+}
+
+function StringIteratorNext() {
+ var obj = this;
+ if (!IsObject(obj) || (obj = GuardToStringIterator(obj)) === null) {
+ return callFunction(
+ CallStringIteratorMethodIfWrapped,
+ this,
+ "StringIteratorNext"
+ );
+ }
+
+ var S = UnsafeGetStringFromReservedSlot(obj, ITERATOR_SLOT_TARGET);
+ // We know that JSString::MAX_LENGTH <= INT32_MAX (and assert this in
+ // SelfHostring.cpp) so our current index can never be anything other than
+ // an Int32Value.
+ var index = UnsafeGetInt32FromReservedSlot(obj, ITERATOR_SLOT_NEXT_INDEX);
+ var size = S.length;
+ var result = { value: undefined, done: false };
+
+ if (index >= size) {
+ result.done = true;
+ return result;
+ }
+
+ var charCount = 1;
+ var first = callFunction(std_String_charCodeAt, S, index);
+ if (first >= 0xd800 && first <= 0xdbff && index + 1 < size) {
+ var second = callFunction(std_String_charCodeAt, S, index + 1);
+ if (second >= 0xdc00 && second <= 0xdfff) {
+ first = (first - 0xd800) * 0x400 + (second - 0xdc00) + 0x10000;
+ charCount = 2;
+ }
+ }
+
+ UnsafeSetReservedSlot(obj, ITERATOR_SLOT_NEXT_INDEX, index + charCount);
+
+ // Communicate |first|'s possible range to the compiler.
+ result.value = callFunction(std_String_fromCodePoint, null, first & 0x1fffff);
+
+ return result;
+}
+
+#if JS_HAS_INTL_API
+var collatorCache = new_Record();
+
+/**
+ * Compare this String against that String, using the locale and collation
+ * options provided.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 13.1.1.
+ */
+function String_localeCompare(that) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("localeCompare", this);
+ }
+
+ // Steps 2-3.
+ var S = ToString(this);
+ var That = ToString(that);
+
+ // Steps 4-5.
+ var locales = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+ var options = ArgumentsLength() > 2 ? GetArgument(2) : undefined;
+
+ // Step 6.
+ var collator;
+ if (locales === undefined && options === undefined) {
+ // This cache only optimizes for the old ES5 localeCompare without
+ // locales and options.
+ if (!intl_IsRuntimeDefaultLocale(collatorCache.runtimeDefaultLocale)) {
+ collatorCache.collator = intl_Collator(locales, options);
+ collatorCache.runtimeDefaultLocale = intl_RuntimeDefaultLocale();
+ }
+ collator = collatorCache.collator;
+ } else {
+ collator = intl_Collator(locales, options);
+ }
+
+ // Step 7.
+ return intl_CompareStrings(collator, S, That);
+}
+
+/**
+ * 13.1.2 String.prototype.toLocaleLowerCase ( [ locales ] )
+ *
+ * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
+ */
+function String_toLocaleLowerCase() {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("toLocaleLowerCase", this);
+ }
+
+ // Step 2.
+ var string = ToString(this);
+
+ // Handle the common cases (no locales argument or a single string
+ // argument) first.
+ var locales = ArgumentsLength() ? GetArgument(0) : undefined;
+ var requestedLocale;
+ if (locales === undefined) {
+ // Steps 3, 6.
+ requestedLocale = undefined;
+ } else if (typeof locales === "string") {
+ // Steps 3, 5.
+ requestedLocale = intl_ValidateAndCanonicalizeLanguageTag(locales, false);
+ } else {
+ // Step 3.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Steps 4-6.
+ requestedLocale = requestedLocales.length ? requestedLocales[0] : undefined;
+ }
+
+ // Trivial case: When the input is empty, directly return the empty string.
+ if (string.length === 0) {
+ return "";
+ }
+
+ if (requestedLocale === undefined) {
+ requestedLocale = DefaultLocale();
+ }
+
+ // Steps 7-16.
+ return intl_toLocaleLowerCase(string, requestedLocale);
+}
+
+/**
+ * 13.1.3 String.prototype.toLocaleUpperCase ( [ locales ] )
+ *
+ * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
+ */
+function String_toLocaleUpperCase() {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("toLocaleUpperCase", this);
+ }
+
+ // Step 2.
+ var string = ToString(this);
+
+ // Handle the common cases (no locales argument or a single string
+ // argument) first.
+ var locales = ArgumentsLength() ? GetArgument(0) : undefined;
+ var requestedLocale;
+ if (locales === undefined) {
+ // Steps 3, 6.
+ requestedLocale = undefined;
+ } else if (typeof locales === "string") {
+ // Steps 3, 5.
+ requestedLocale = intl_ValidateAndCanonicalizeLanguageTag(locales, false);
+ } else {
+ // Step 3.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Steps 4-6.
+ requestedLocale = requestedLocales.length ? requestedLocales[0] : undefined;
+ }
+
+ // Trivial case: When the input is empty, directly return the empty string.
+ if (string.length === 0) {
+ return "";
+ }
+
+ if (requestedLocale === undefined) {
+ requestedLocale = DefaultLocale();
+ }
+
+ // Steps 7-16.
+ return intl_toLocaleUpperCase(string, requestedLocale);
+}
+#endif // JS_HAS_INTL_API
+
+// ES2018 draft rev 8fadde42cf6a9879b4ab0cb6142b31c4ee501667
+// 21.1.2.4 String.raw ( template, ...substitutions )
+function String_static_raw(callSite /*, ...substitutions*/) {
+ // Steps 1-2 (not applicable).
+
+ // Step 3.
+ var cooked = ToObject(callSite);
+
+ // Step 4.
+ var raw = ToObject(cooked.raw);
+
+ // Step 5.
+ var literalSegments = ToLength(raw.length);
+
+ // Step 6.
+ if (literalSegments === 0) {
+ return "";
+ }
+
+ // Special case for |String.raw `<literal>`| callers to avoid falling into
+ // the loop code below.
+ if (literalSegments === 1) {
+ return ToString(raw[0]);
+ }
+
+ // Steps 7-9 were reordered to use ArgumentsLength/GetArgument instead of a
+ // rest parameter, because the former is currently more optimized.
+ //
+ // String.raw intersperses the substitution elements between the literal
+ // segments, i.e. a substitution is added iff there are still pending
+ // literal segments. Furthermore by moving the access to |raw[0]| outside
+ // of the loop, we can use |nextIndex| to index into both, the |raw| array
+ // and the arguments.
+
+ // Steps 7 (implicit) and 9.a-c.
+ var resultString = ToString(raw[0]);
+
+ // Steps 8-9, 9.d, and 9.i.
+ for (var nextIndex = 1; nextIndex < literalSegments; nextIndex++) {
+ // Steps 9.e-h.
+ if (nextIndex < ArgumentsLength()) {
+ resultString += ToString(GetArgument(nextIndex));
+ }
+
+ // Steps 9.a-c.
+ resultString += ToString(raw[nextIndex]);
+ }
+
+ // Step 9.d.i.
+ return resultString;
+}
+
+// https://github.com/tc39/proposal-relative-indexing-method
+// String.prototype.at ( index )
+function String_at(index) {
+ // Step 1.
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("at", this);
+ }
+
+ // Step 2.
+ var string = ToString(this);
+
+ // Step 3.
+ var len = string.length;
+
+ // Step 4.
+ var relativeIndex = ToInteger(index);
+
+ // Steps 5-6.
+ var k;
+ if (relativeIndex >= 0) {
+ k = relativeIndex;
+ } else {
+ k = len + relativeIndex;
+ }
+
+ // Step 7.
+ if (k < 0 || k >= len) {
+ return undefined;
+ }
+
+ // Step 8.
+ return string[k];
+}
+
+// ES6 draft 2014-04-27 B.2.3.3
+function String_big() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("big", this);
+ }
+ return "<big>" + ToString(this) + "</big>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.4
+function String_blink() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("blink", this);
+ }
+ return "<blink>" + ToString(this) + "</blink>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.5
+function String_bold() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("bold", this);
+ }
+ return "<b>" + ToString(this) + "</b>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.6
+function String_fixed() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("fixed", this);
+ }
+ return "<tt>" + ToString(this) + "</tt>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.9
+function String_italics() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("italics", this);
+ }
+ return "<i>" + ToString(this) + "</i>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.11
+function String_small() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("small", this);
+ }
+ return "<small>" + ToString(this) + "</small>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.12
+function String_strike() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("strike", this);
+ }
+ return "<strike>" + ToString(this) + "</strike>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.13
+function String_sub() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("sub", this);
+ }
+ return "<sub>" + ToString(this) + "</sub>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.14
+function String_sup() {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("sup", this);
+ }
+ return "<sup>" + ToString(this) + "</sup>";
+}
+
+function EscapeAttributeValue(v) {
+ var inputStr = ToString(v);
+ return StringReplaceAllString(inputStr, '"', "&quot;");
+}
+
+// ES6 draft 2014-04-27 B.2.3.2
+function String_anchor(name) {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("anchor", this);
+ }
+ var S = ToString(this);
+ return '<a name="' + EscapeAttributeValue(name) + '">' + S + "</a>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.7
+function String_fontcolor(color) {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("fontcolor", this);
+ }
+ var S = ToString(this);
+ return '<font color="' + EscapeAttributeValue(color) + '">' + S + "</font>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.8
+function String_fontsize(size) {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("fontsize", this);
+ }
+ var S = ToString(this);
+ return '<font size="' + EscapeAttributeValue(size) + '">' + S + "</font>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.10
+function String_link(url) {
+ if (IsNullOrUndefined(this)) {
+ ThrowIncompatibleMethod("link", this);
+ }
+ var S = ToString(this);
+ return '<a href="' + EscapeAttributeValue(url) + '">' + S + "</a>";
+}