/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.objects.text;
import static com.github.anba.es6draft.runtime.AbstractOperations.*;
import static com.github.anba.es6draft.runtime.internal.Errors.newRangeError;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.objects.intl.CollatorPrototype.CompareStrings;
import static com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.BestAvailableLocale;
import static com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.CanonicalizeLocaleList;
import static com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.DefaultLocale;
import static com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.RemoveUnicodeLocaleExtension;
import static com.github.anba.es6draft.runtime.objects.text.RegExpConstructor.RegExpCreate;
import static com.github.anba.es6draft.runtime.objects.text.RegExpStringIteratorPrototype.CreateRegExpStringIterator;
import static com.github.anba.es6draft.runtime.objects.text.StringIteratorPrototype.CreateStringIterator;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import static com.github.anba.es6draft.runtime.types.builtins.ArrayObject.ArrayCreate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.CompatibilityExtension;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.internal.Strings;
import com.github.anba.es6draft.runtime.objects.intl.CollatorConstructor;
import com.github.anba.es6draft.runtime.objects.intl.CollatorObject;
import com.github.anba.es6draft.runtime.types.BuiltinSymbol;
import com.github.anba.es6draft.runtime.types.Callable;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.ArrayObject;
import com.github.anba.es6draft.runtime.types.builtins.NativeFunction;
import com.github.anba.es6draft.runtime.types.builtins.StringObject;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.Normalizer2;
import com.ibm.icu.util.ULocale;
/**
* <h1>21 Text Processing</h1><br>
* <h2>21.1 String Objects</h2>
* <ul>
* <li>21.1.3 Properties of the String Prototype Object
* <li>21.1.4 Properties of String Instances
* </ul>
*/
public final class StringPrototype extends StringObject implements Initializable {
/**
* Constructs a new String prototype object.
*
* @param realm
* the realm object
*/
public StringPrototype(Realm realm) {
super(realm, "");
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
createProperties(realm, this, AdditionalProperties.class);
createProperties(realm, this, TrimFunctions.class);
createProperties(realm, this, PadFunctions.class);
createProperties(realm, this, MatchAllFunction.class);
}
/**
* Marker class for {@code String.prototype.iterator}.
*/
private static final class StringPrototypeIterator {
}
/**
* Returns {@code true} if <var>iterator</var> is the built-in {@code %IteratorPrototype%[@@iterator]} function for
* the requested realm.
*
* @param realm
* the function realm
* @param iterator
* the iterator function
* @return {@code true} if <var>iterator</var> is the built-in {@code %IteratorPrototype%[@@iterator]} function
*/
public static boolean isBuiltinIterator(Realm realm, Object iterator) {
return NativeFunction.isNative(realm, iterator, StringPrototypeIterator.class);
}
/**
* 21.1.3 Properties of the String Prototype Object
*/
public enum Properties {
;
/**
* Abstract operation thisStringValue(value)
*
* @param cx
* the execution context
* @param object
* the object value
* @return the string value
*/
private static CharSequence thisStringValue(ExecutionContext cx, Object object) {
/* step 1 */
if (Type.isString(object)) {
return Type.stringValue(object);
}
/* step 2 */
if (object instanceof StringObject) {
return ((StringObject) object).getStringData();
}
/* step 3 */
throw newTypeError(cx, Messages.Key.IncompatibleObject);
}
@Prototype
public static final Intrinsics __proto__ = Intrinsics.ObjectPrototype;
/**
* String.prototype.length
*/
// FIXME: spec issue - explicitly define length for String.prototype in 21.1.3?
@Value(name = "length", attributes = @Attributes(writable = false, enumerable = false, configurable = false))
public static final int length = 0;
/**
* 21.1.3.5 String.prototype.constructor
*/
@Value(name = "constructor")
public static final Intrinsics constructor = Intrinsics.String;
/**
* 21.1.3.23 String.prototype.toString ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the string representation
*/
@Function(name = "toString", arity = 0)
public static Object toString(ExecutionContext cx, Object thisValue) {
return thisStringValue(cx, thisValue);
}
/**
* 21.1.3.26 String.prototype.valueOf ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the string value
*/
@Function(name = "valueOf", arity = 0)
public static Object valueOf(ExecutionContext cx, Object thisValue) {
return thisStringValue(cx, thisValue);
}
/**
* 21.1.3.1 String.prototype.charAt (pos)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param pos
* the string index
* @return the character or the empty string
*/
@Function(name = "charAt", arity = 1)
public static Object charAt(ExecutionContext cx, Object thisValue, Object pos) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
CharSequence s = ToString(cx, obj);
/* steps 4-5 */
double position = ToInteger(cx, pos);
/* step 6 */
int size = s.length();
/* step 7 */
if (position < 0 || position >= size) {
return "";
}
/* step 8 */
return String.valueOf(s.charAt((int) position));
}
/**
* 21.1.3.2 String.prototype.charCodeAt (pos)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param pos
* the string index
* @return the character code unit
*/
@Function(name = "charCodeAt", arity = 1)
public static Object charCodeAt(ExecutionContext cx, Object thisValue, Object pos) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
CharSequence s = ToString(cx, obj);
/* steps 4-5 */
double position = ToInteger(cx, pos);
/* step 6 */
int size = s.length();
/* step 7 */
if (position < 0 || position >= size) {
return Double.NaN;
}
/* step 8 */
return (int) s.charAt((int) position);
}
/**
* 21.1.3.3 String.prototype.codePointAt (pos)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param pos
* the start position
* @return the code point
*/
@Function(name = "codePointAt", arity = 1)
public static Object codePointAt(ExecutionContext cx, Object thisValue, Object pos) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
double position = ToInteger(cx, pos);
/* step 6 */
int size = s.length();
/* step 7 */
if (position < 0 || position >= size) {
return UNDEFINED;
}
/* steps 8-12 */
return s.codePointAt((int) position);
}
/**
* 21.1.3.4 String.prototype.concat ( ...args )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param args
* the additional strings
* @return the concatenated string
*/
@Function(name = "concat", arity = 1)
public static Object concat(ExecutionContext cx, Object thisValue, Object... args) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
CharSequence s = ToString(cx, obj);
/* step 4 (not applicable) */
/* step 5 */
StringBuilder r = new StringBuilder(s);
/* step 6 */
for (int i = 0; i < args.length; ++i) {
CharSequence nextString = ToString(cx, args[i]);
r.append(nextString);
}
/* step 7 */
return r.toString();
}
/**
* 21.1.3.6 String.prototype.endsWith (searchString [, endPosition] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchString
* the search string
* @param endPosition
* the end position
* @return {@code true} if the string ends with <var>searchString</var>
*/
@Function(name = "endsWith", arity = 1)
public static Object endsWith(ExecutionContext cx, Object thisValue, Object searchString,
Object endPosition) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-6 */
if (IsRegExp(cx, searchString)) {
throw newTypeError(cx, Messages.Key.InvalidRegExpArgument);
}
/* steps 7-8 */
String searchStr = ToFlatString(cx, searchString);
/* step 9 */
int len = s.length();
/* steps 10-11 */
double pos = Type.isUndefined(endPosition) ? len : ToInteger(cx, endPosition);
/* step 12 */
int end = (int) Math.min(Math.max(pos, 0), len);
/* step 13 */
int searchLength = searchStr.length();
/* step 14 */
int start = end - searchLength;
/* step 15 */
if (start < 0) {
return false;
}
/* steps 16-17 */
return s.startsWith(searchStr, start);
}
/**
* 21.1.3.7 String.prototype.includes ( searchString [ , position ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchString
* the search string
* @param position
* the start position
* @return {@code true} if the search string was found
*/
@Function(name = "includes", arity = 1)
public static Object includes(ExecutionContext cx, Object thisValue, Object searchString,
Object position /* = 0 */) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-6 */
if (IsRegExp(cx, searchString)) {
throw newTypeError(cx, Messages.Key.InvalidRegExpArgument);
}
/* steps 7-8 */
String searchStr = ToFlatString(cx, searchString);
/* steps 9-10 */
double pos = ToInteger(cx, position);
/* step 11 */
int len = s.length();
/* step 12 */
int start = (int) Math.min(Math.max(pos, 0), len);
/* step 13 */
// int searchLen = searchStr.length();
/* step 14 */
return s.indexOf(searchStr, start) != -1;
}
/**
* 21.1.3.8 String.prototype.indexOf ( searchString [ , position ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchString
* the search string value
* @param position
* the start position
* @return the result index
*/
@Function(name = "indexOf", arity = 1)
public static Object indexOf(ExecutionContext cx, Object thisValue, Object searchString,
Object position) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
String searchStr = ToFlatString(cx, searchString);
/* steps 6-7 */
double pos = ToInteger(cx, position);
/* step 8 */
int len = s.length();
/* step 9 */
int start = (int) Math.min(Math.max(pos, 0), len);
/* steps 10-11 */
return s.indexOf(searchStr, start);
}
/**
* 21.1.3.9 String.prototype.lastIndexOf ( searchString [ , position ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchString
* the search string value
* @param position
* the start position
* @return the result index
*/
@Function(name = "lastIndexOf", arity = 1)
public static Object lastIndexOf(ExecutionContext cx, Object thisValue,
Object searchString, Object position) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
String searchStr = ToFlatString(cx, searchString);
/* steps 6-7 */
double numPos = ToNumber(cx, position);
/* step 8 */
double pos = Double.isNaN(numPos) ? Double.POSITIVE_INFINITY : ToInteger(numPos);
/* step 9 */
int len = s.length();
/* step 10 */
int start = (int) Math.min(Math.max(pos, 0), len);
/* steps 11-12 */
return s.lastIndexOf(searchStr, start);
}
/**
* 21.1.3.10 String.prototype.localeCompare ( that [, reserved1 [ , reserved2 ] ] )<br>
* 13.1.1 String.prototype.localeCompare (that [, locales [, options ]])
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param that
* the other string
* @param locales
* the optional locales array
* @param options
* the optional options object
* @return the locale specific comparison result
*/
@Function(name = "localeCompare", arity = 1)
public static Object localeCompare(ExecutionContext cx, Object thisValue, Object that,
Object locales, Object options) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
String t = ToFlatString(cx, that);
// ES5/6
// return cx.getRealm().getCollator().compare(s, t);
// ECMA-402
/* steps 6-7 */
CollatorConstructor ctor = (CollatorConstructor) cx
.getIntrinsic(Intrinsics.Intl_Collator);
CollatorObject collator = ctor.construct(cx, ctor, locales, options);
/* step 8 */
return CompareStrings(collator, s, t);
}
/**
* 21.1.3.11 String.prototype.match (regexp)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param regexp
* the regular expression object
* @return the match result array
*/
@Function(name = "match", arity = 1)
public static Object match(ExecutionContext cx, Object thisValue, Object regexp) {
/* steps 1-2 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* step 3 */
if (!Type.isUndefinedOrNull(regexp)) {
/* steps 3.a-b */
Callable matcher = GetMethod(cx, regexp, BuiltinSymbol.match.get());
/* step 3.c */
if (matcher != null) {
return matcher.call(cx, regexp, obj);
}
}
/* steps 4-5 */
CharSequence s = ToString(cx, obj);
/* steps 6-7 */
RegExpObject rx = RegExpCreate(cx, regexp, UNDEFINED);
/* step 8 */
return Invoke(cx, rx, BuiltinSymbol.match.get(), s);
}
/**
* 21.1.3.12 String.prototype.normalize ( [ form ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param form
* the normalisation form
* @return the normalized string
*/
@Function(name = "normalize", arity = 0)
public static Object normalize(ExecutionContext cx, Object thisValue, Object form) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-6 */
String f = "NFC";
if (!Type.isUndefined(form)) {
f = ToFlatString(cx, form);
}
/* steps 7-9 */
switch (f) {
case "NFC":
return Normalizer2.getNFCInstance().normalize(s);
case "NFD":
return Normalizer2.getNFDInstance().normalize(s);
case "NFKC":
return Normalizer2.getNFKCInstance().normalize(s);
case "NFKD":
return Normalizer2.getNFKDInstance().normalize(s);
default:
throw newRangeError(cx, Messages.Key.InvalidNormalizationForm, f);
}
}
/**
* 21.1.3.13 String.prototype.repeat (count)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param count
* the repetition count
* @return the string repeated <var>count</var> times
*/
@Function(name = "repeat", arity = 1)
public static Object repeat(ExecutionContext cx, Object thisValue, Object count) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
double n = ToInteger(cx, count);
/* steps 6-7 */
if (n < 0 || n == Double.POSITIVE_INFINITY) {
throw newRangeError(cx, Messages.Key.InvalidStringRepeat);
}
/* step 8 */
if (n == 0 || s.length() == 0) {
return "";
}
if (n == 1) {
return s;
}
double capacity = s.length() * n;
if (capacity > 1 << 27) {
// likely to exceed heap space, follow SpiderMonkey and throw RangeError
throw newRangeError(cx, Messages.Key.InvalidStringRepeat);
}
/* step 8 */
final int length = s.length();
char[] ca = new char[(int) capacity];
if (length == 1) {
Arrays.fill(ca, s.charAt(0));
return new String(ca);
}
s.getChars(0, length, ca, 0);
final int N = (int) n;
final int limit = length * Integer.highestOneBit(N);
for (int k = length; k < limit; k <<= 1) {
System.arraycopy(ca, 0, ca, k, k);
}
System.arraycopy(ca, 0, ca, limit, (N * length - limit));
/* step 9 */
return new String(ca);
}
/**
* 21.1.3.14 String.prototype.replace (searchValue, replaceValue)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchValue
* the search string
* @param replaceValue
* the replace string or replacer function
* @return the new string
*/
@Function(name = "replace", arity = 2)
public static Object replace(ExecutionContext cx, Object thisValue, Object searchValue,
Object replaceValue) {
/* steps 1-2 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* step 3 */
if (!Type.isUndefinedOrNull(searchValue)) {
/* steps 3.a-b */
Callable replacer = GetMethod(cx, searchValue, BuiltinSymbol.replace.get());
/* step 3.c */
if (replacer != null) {
return replacer.call(cx, searchValue, obj, replaceValue);
}
}
/* steps 4-5 */
String string = ToFlatString(cx, obj);
/* steps 6-7 */
String searchString = ToFlatString(cx, searchValue);
/* step 8 */
boolean functionalReplace = IsCallable(replaceValue);
/* step 9 */
String replaceValueString = null;
Callable replaceValueCallable = null;
if (!functionalReplace) {
replaceValueString = ToFlatString(cx, replaceValue);
} else {
replaceValueCallable = (Callable) replaceValue;
}
/* step 10 */
int pos = string.indexOf(searchString);
if (pos < 0) {
return string;
}
String matched = searchString;
/* steps 11-12 */
String replStr;
if (functionalReplace) {
Object replValue = replaceValueCallable.call(cx, UNDEFINED, matched, pos, string);
replStr = ToFlatString(cx, replValue);
} else {
replStr = GetSubstitution(matched, string, pos, replaceValueString);
}
/* step 13 */
int tailPos = pos + searchString.length();
/* steps 14-15 */
return string.substring(0, pos) + replStr + string.substring(tailPos);
}
/**
* 21.1.3.14.1 Runtime Semantics: GetSubstitution(matched, str, position, captures,
* replacement)
*
* @param matched
* the matched substring
* @param string
* the string
* @param position
* the string index
* @param replacement
* the replacement value
* @return the replacement string
*/
private static String GetSubstitution(String matched, String string, int position, String replacement) {
/* step 1 (not applicable) */
/* step 2 */
int matchLength = matched.length();
/* step 3 (not applicable) */
/* step 4 */
int stringLength = string.length();
/* steps 5-6 */
assert position >= 0 && position <= stringLength;
/* steps 7-8 (not applicable) */
/* step 9 */
int tailPos = position + matchLength;
assert tailPos >= 0 && tailPos <= stringLength;
/* step 10 (not applicable) */
/* step 11 */
int cursor = replacement.indexOf('$');
if (cursor < 0) {
return replacement;
}
final int length = replacement.length();
int lastCursor = 0;
StringBuilder result = new StringBuilder();
for (;;) {
if (lastCursor < cursor) {
result.append(replacement, lastCursor, cursor);
}
if (++cursor == length) {
result.append('$');
break;
}
assert cursor < length;
char c = replacement.charAt(cursor++);
switch (c) {
case '&':
result.append(matched);
break;
case '`':
result.append(string, 0, position);
break;
case '\'':
result.append(string, tailPos, stringLength);
break;
case '$':
result.append('$');
break;
default:
result.append('$').append(c);
break;
}
lastCursor = cursor;
cursor = replacement.indexOf('$', cursor);
if (cursor < 0) {
if (lastCursor < length) {
result.append(replacement, lastCursor, length);
}
break;
}
}
/* step 12 */
return result.toString();
}
/**
* 21.1.3.15 String.prototype.search (regexp)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param regexp
* the regular expression object
* @return the first match index
*/
@Function(name = "search", arity = 1)
public static Object search(ExecutionContext cx, Object thisValue, Object regexp) {
/* steps 1-2 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* step 3 */
if (!Type.isUndefinedOrNull(regexp)) {
/* steps 3.a-b */
Callable searcher = GetMethod(cx, regexp, BuiltinSymbol.search.get());
/* step 3.c */
if (searcher != null) {
return searcher.call(cx, regexp, obj);
}
}
/* steps 4-5 */
CharSequence string = ToString(cx, obj);
/* steps 6-7 */
RegExpObject rx = RegExpCreate(cx, regexp, UNDEFINED);
/* step 8 */
return Invoke(cx, rx, BuiltinSymbol.search.get(), string);
}
/**
* 21.1.3.16 String.prototype.slice (start, end)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param start
* the start position
* @param end
* the end position
* @return the substring
*/
@Function(name = "slice", arity = 2)
public static Object slice(ExecutionContext cx, Object thisValue, Object start, Object end) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
CharSequence s = ToString(cx, obj);
/* step 4 */
int len = s.length();
/* steps 5-6 */
double intStart = ToInteger(cx, start);
/* steps 7-8 */
double intEnd = Type.isUndefined(end) ? len : ToInteger(cx, end);
/* step 9 */
int from = (int) (intStart < 0 ? Math.max(len + intStart, 0) : Math.min(intStart, len));
/* step 10 */
int to = (int) (intEnd < 0 ? Math.max(len + intEnd, 0) : Math.min(intEnd, len));
/* step 11 */
int span = Math.max(to - from, 0);
/* step 12 */
return s.subSequence(from, from + span);
}
/**
* 21.1.3.17 String.prototype.split (separator, limit)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param separator
* the string separator
* @param limit
* the optional split array limit
* @return the split array
*/
@Function(name = "split", arity = 2)
public static Object split(ExecutionContext cx, Object thisValue, Object separator,
Object limit) {
/* steps 1-2 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* step 3 */
if (!Type.isUndefinedOrNull(separator)) {
/* steps 3.a-b */
Callable splitter = GetMethod(cx, separator, BuiltinSymbol.split.get());
/* step 3.c */
if (splitter != null) {
return splitter.call(cx, separator, obj, limit);
}
}
/* steps 4-5 */
String s = ToFlatString(cx, obj);
/* step 6 */
ArrayObject a = ArrayCreate(cx, 0);
/* step 7 */
int lengthA = 0;
/* steps 8-9 */
long lim = Type.isUndefined(limit) ? 0xFFFF_FFFFL : ToUint32(cx, limit);
/* step 10 */
int size = s.length();
/* step 11 */
int p = 0;
/* steps 12-13 */
String r = ToFlatString(cx, separator);
/* step 14 */
if (lim == 0) {
return a;
}
/* step 15 */
if (Type.isUndefined(separator)) {
CreateDataProperty(cx, a, 0, s);
return a;
}
/* step 16 */
if (size == 0) {
if (r.length() == 0) {
return a;
}
CreateDataProperty(cx, a, 0, s);
return a;
}
/* step 17 */
int q = p;
/* step 18 */
while (q != size) {
int z = SplitMatch(s, q, r);
if (z == -1) {
break;
} else {
int e = z + r.length();
if (e == p) {
q = q + 1;
} else {
String t = s.substring(p, z);
CreateDataProperty(cx, a, lengthA, t);
lengthA += 1;
if (lengthA == lim) {
return a;
}
p = e;
q = p;
}
}
}
/* step 19 */
String t = s.substring(p, size);
/* steps 20-21 */
CreateDataProperty(cx, a, lengthA, t);
/* step 22 */
return a;
}
/**
* 21.1.3.17.1 Runtime Semantics: SplitMatch ( S, q, R )
*
* @param s
* the string
* @param q
* the start position
* @param r
* the search string
* @return the index of the first match
*/
private static int SplitMatch(String s, int q, String r) {
// returns start instead of end position
return s.indexOf(r, q);
}
/**
* 21.1.3.18 String.prototype.startsWith (searchString [, position ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchString
* the search string
* @param position
* the start position
* @return {@code true} if the string starts with <var>searchString</var>
*/
@Function(name = "startsWith", arity = 1)
public static Object startsWith(ExecutionContext cx, Object thisValue, Object searchString,
Object position) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-6 */
if (IsRegExp(cx, searchString)) {
throw newTypeError(cx, Messages.Key.InvalidRegExpArgument);
}
/* steps 7-8 */
String searchStr = ToFlatString(cx, searchString);
/* steps 9-10 */
double pos = ToInteger(cx, position);
/* step 11 */
int len = s.length();
/* step 12 */
int start = (int) Math.min(Math.max(pos, 0), len);
/* step 13 */
int searchLength = searchStr.length();
/* step 14 */
if (searchLength + start > len) {
return false;
}
/* steps 15-16 */
return s.startsWith(searchStr, start);
}
/**
* 21.1.3.19 String.prototype.substring (start, end)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param start
* the start position
* @param end
* the end position
* @return the substring
*/
@Function(name = "substring", arity = 2)
public static Object substring(ExecutionContext cx, Object thisValue, Object start,
Object end) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
CharSequence s = ToString(cx, obj);
/* step 4 */
int len = s.length();
/* steps 5-6 */
double intStart = ToInteger(cx, start);
/* steps 7-8 */
double intEnd = Type.isUndefined(end) ? len : ToInteger(cx, end);
/* step 9 */
int finalStart = (int) Math.min(Math.max(intStart, 0), len);
/* step 10 */
int finalEnd = (int) Math.min(Math.max(intEnd, 0), len);
/* step 11 */
int from = Math.min(finalStart, finalEnd);
/* step 12 */
int to = Math.max(finalStart, finalEnd);
/* step 13 */
return s.subSequence(from, to);
}
/**
* 21.1.3.20 String.prototype.toLocaleLowerCase ( [ reserved1 [ , reserved2 ] ] )<br>
* 13.1.2 String.prototype.toLocaleLowerCase ([locales])
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param locales
* the optional locales array
* @return the lower case string
*/
@Function(name = "toLocaleLowerCase", arity = 0)
public static Object toLocaleLowerCase(ExecutionContext cx, Object thisValue, Object locales) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
// ES5/6
// return s.toLowerCase(cx.getRealm().getLocale());
/* steps 4-5 */
Set<String> requestedLocales = CanonicalizeLocaleList(cx, locales);
/* steps 6-8 */
String requestedLocale = !requestedLocales.isEmpty() ? requestedLocales.iterator()
.next() : DefaultLocale(cx.getRealm());
/* step 9 */
String noExtensionsLocale = RemoveUnicodeLocaleExtension(requestedLocale);
/* step 10 */
HashSet<String> availableLocales = new HashSet<>(Arrays.asList("az", "lt", "tr"));
/* step 11 */
String locale = BestAvailableLocale(availableLocales, noExtensionsLocale);
/* step 12 */
String supportedLocale = locale == null ? "und" : locale;
/* steps 13-18 */
return UCharacter.toLowerCase(ULocale.forLanguageTag(supportedLocale), s);
}
/**
* 21.1.3.21 String.prototype.toLocaleUpperCase ([ reserved1 [ , reserved2 ] ] )<br>
* 13.1.3 String.prototype.toLocaleUpperCase ([locales ])
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param locales
* the optional locales array
* @return the upper case string
*/
@Function(name = "toLocaleUpperCase", arity = 0)
public static Object toLocaleUpperCase(ExecutionContext cx, Object thisValue, Object locales) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
// ES5/6
// return s.toUpperCase(cx.getRealm().getLocale());
/* steps 4-5 */
Set<String> requestedLocales = CanonicalizeLocaleList(cx, locales);
/* steps 6-8 */
String requestedLocale = !requestedLocales.isEmpty() ? requestedLocales.iterator()
.next() : DefaultLocale(cx.getRealm());
/* step 9 */
String noExtensionsLocale = RemoveUnicodeLocaleExtension(requestedLocale);
/* step 10 */
HashSet<String> availableLocales = new HashSet<>(Arrays.asList("az", "lt", "tr"));
/* step 11 */
String locale = BestAvailableLocale(availableLocales, noExtensionsLocale);
/* step 12 */
String supportedLocale = locale == null ? "und" : locale;
/* steps 13-18 */
return UCharacter.toUpperCase(ULocale.forLanguageTag(supportedLocale), s);
}
/**
* 21.1.3.22 String.prototype.toLowerCase ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the lower case string
*/
@Function(name = "toLowerCase", arity = 0)
public static Object toLowerCase(ExecutionContext cx, Object thisValue) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-9 */
Latin1: {
int index = 0;
Lower: {
for (; index < s.length(); index++) {
int c = s.charAt(index);
if (c > 0xff) {
break Latin1;
}
if (c != Character.toLowerCase(c)) {
break Lower;
}
}
return s;
}
char[] chars = s.toCharArray();
for (; index < chars.length; ++index) {
int c = chars[index];
if (c > 0xff) {
break Latin1;
}
chars[index] = (char) Character.toLowerCase(c);
}
return new String(chars);
}
return UCharacter.toLowerCase(ULocale.ROOT, s);
}
/**
* 21.1.3.24 String.prototype.toUpperCase ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the upper case string
*/
@Function(name = "toUpperCase", arity = 0)
public static Object toUpperCase(ExecutionContext cx, Object thisValue) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-9 */
Latin1: {
int index = 0;
Upper: {
for (; index < s.length(); index++) {
int c = s.charAt(index);
if (c > 0xff || c == 0xdf) {
break Latin1;
}
if (c != Character.toUpperCase(c)) {
break Upper;
}
}
return s;
}
char[] chars = s.toCharArray();
for (; index < chars.length; ++index) {
int c = chars[index];
if (c > 0xff || c == 0xdf) {
break Latin1;
}
chars[index] = (char) Character.toUpperCase(c);
}
return new String(chars);
}
return UCharacter.toUpperCase(ULocale.ROOT, s);
}
/**
* 21.1.3.25 String.prototype.trim ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the string with leading and trailing whitespace removed
*/
@Function(name = "trim", arity = 0)
public static Object trim(ExecutionContext cx, Object thisValue) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
return Strings.trim(s);
}
/**
* 21.1.3.27 String.prototype [ @@iterator ]( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the string iterator
*/
@Function(name = "[Symbol.iterator]", symbol = BuiltinSymbol.iterator, arity = 0,
nativeId = StringPrototypeIterator.class)
public static Object iterator(ExecutionContext cx, Object thisValue) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* step 4 */
return CreateStringIterator(cx, s);
}
}
/**
* B.2.3 Additional Properties of the String.prototype Object
*/
@CompatibilityExtension(CompatibilityOption.StringPrototype)
public enum AdditionalProperties {
;
/**
* B.2.3.1 String.prototype.substr (start, length)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param start
* the start position
* @param length
* the substring length
* @return the substring
*/
@Function(name = "substr", arity = 2)
public static Object substr(ExecutionContext cx, Object thisValue, Object start,
Object length) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
CharSequence s = ToString(cx, obj);
/* steps 4-5 */
double intStart = ToInteger(cx, start);
/* steps 6-7 */
double end = Type.isUndefined(length) ? Double.POSITIVE_INFINITY : ToInteger(cx, length);
/* step 8 */
int size = s.length();
/* step 9 */
if (intStart < 0) {
intStart = Math.max(size + intStart, 0);
}
/* step 10 */
double resultLength = Math.min(Math.max(end, 0), size - intStart);
/* step 11 */
if (resultLength <= 0) {
return "";
}
assert 0 <= intStart && intStart + resultLength <= size;
/* step 12 */
return s.subSequence((int) intStart, (int) (intStart + resultLength));
}
/**
* B.2.3.2.1 Runtime Semantics: CreateHTML ( string, tag, attribute, value )
*
* @param cx
* the execution context
* @param string
* the string
* @param tag
* the html tag
* @param attribute
* the html attribute name
* @param value
* the html attribute value
* @return the html string
*/
private static String CreateHTML(ExecutionContext cx, Object string, String tag,
String attribute, Object value) {
/* step 1 */
Object str = RequireObjectCoercible(cx, string);
/* steps 2-3 */
String s = ToFlatString(cx, str);
/* steps 4-5 */
StringBuilder p = new StringBuilder().append('<').append(tag);
if (!attribute.isEmpty()) {
String v = ToFlatString(cx, value);
String escapedV = v.replace("\"", """);
p.append(' ').append(attribute).append('=').append('"').append(escapedV)
.append('"');
}
/* steps 6-9 */
return p.append('>').append(s).append("</").append(tag).append('>').toString();
}
/**
* B.2.3.2 String.prototype.anchor ( name )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param name
* the anchor name
* @return the html string
*/
@Function(name = "anchor", arity = 1)
public static Object anchor(ExecutionContext cx, Object thisValue, Object name) {
Object s = thisValue;
return CreateHTML(cx, s, "a", "name", name);
}
/**
* B.2.3.3 String.prototype.big ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "big", arity = 0)
public static Object big(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "big", "", "");
}
/**
* B.2.3.4 String.prototype.blink ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "blink", arity = 0)
public static Object blink(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "blink", "", "");
}
/**
* B.2.3.5 String.prototype.bold ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "bold", arity = 0)
public static Object bold(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "b", "", "");
}
/**
* B.2.3.6 String.prototype.fixed ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "fixed", arity = 0)
public static Object fixed(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "tt", "", "");
}
/**
* B.2.3.7 String.prototype.fontcolor ( color )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param color
* the font color
* @return the html string
*/
@Function(name = "fontcolor", arity = 1)
public static Object fontcolor(ExecutionContext cx, Object thisValue, Object color) {
Object s = thisValue;
return CreateHTML(cx, s, "font", "color", color);
}
/**
* B.2.3.8 String.prototype.fontsize ( size )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param size
* the font size
* @return the html string
*/
@Function(name = "fontsize", arity = 1)
public static Object fontsize(ExecutionContext cx, Object thisValue, Object size) {
Object s = thisValue;
return CreateHTML(cx, s, "font", "size", size);
}
/**
* B.2.3.9 String.prototype.italics ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "italics", arity = 0)
public static Object italics(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "i", "", "");
}
/**
* B.2.3.10 String.prototype.link ( url )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param url
* the url
* @return the html string
*/
@Function(name = "link", arity = 1)
public static Object link(ExecutionContext cx, Object thisValue, Object url) {
Object s = thisValue;
return CreateHTML(cx, s, "a", "href", url);
}
/**
* B.2.3.11 String.prototype.small ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "small", arity = 0)
public static Object small(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "small", "", "");
}
/**
* B.2.3.12 String.prototype.strike ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "strike", arity = 0)
public static Object strike(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "strike", "", "");
}
/**
* B.2.3.13 String.prototype.sub ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "sub", arity = 0)
public static Object sub(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "sub", "", "");
}
/**
* B.2.3.14 String.prototype.sup ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the html string
*/
@Function(name = "sup", arity = 0)
public static Object sup(ExecutionContext cx, Object thisValue) {
Object s = thisValue;
return CreateHTML(cx, s, "sup", "", "");
}
}
/**
* Extension: String.prototype.trimLeft and trimRight
*/
@CompatibilityExtension(CompatibilityOption.StringTrim)
public enum TrimFunctions {
;
/**
* String.prototype.trimLeft ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the string with leading whitespace removed
*/
@Function(name = "trimLeft", arity = 0)
public static Object trimLeft(ExecutionContext cx, Object thisValue) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
return Strings.trimLeft(s);
}
/**
* String.prototype.trimRight ()
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the string with trailing whitespace removed
*/
@Function(name = "trimRight", arity = 0)
public static Object trimRight(ExecutionContext cx, Object thisValue) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-3 */
String s = ToFlatString(cx, obj);
/* steps 4-5 */
return Strings.trimRight(s);
}
}
/**
* Extension: String.prototype.padStart and padEnd
*/
@CompatibilityExtension(CompatibilityOption.StringPad)
public enum PadFunctions {
;
/**
* String.prototype.padStart( maxLength [ , fillString ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param maxLength
* the maximum length
* @param fillString
* the optional fill string
* @return the string with leading padding applied
*/
@Function(name = "padStart", arity = 1)
public static Object padStart(ExecutionContext cx, Object thisValue, Object maxLength, Object fillString) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* step 2 */
String s = ToFlatString(cx, obj);
/* step 3 */
long intMaxLength = ToLength(cx, maxLength);
/* step 4 */
int stringLength = s.length();
/* step 5 */
if (intMaxLength <= stringLength) {
return s;
}
/* steps 6-7 */
CharSequence fillStr = Type.isUndefined(fillString) ? "" : ToString(cx, fillString);
/* step 8 */
if (fillStr.length() == 0) {
fillStr = " ";
}
/* step 9 */
long fillLen = intMaxLength - stringLength;
if (fillLen > 1 << 27) {
// Likely to exceed heap space, throw RangeError to match String.prototype.repeat.
throw newRangeError(cx, Messages.Key.InvalidStringPad);
}
/* steps 10-11 */
return repeatFill(fillStr.toString(), (int) fillLen) + s;
}
/**
* String.prototype.padEnd( maxLength [ , fillString ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param maxLength
* the maximum length
* @param fillString
* the optional fill string
* @return the string with trailing padding applied
*/
@Function(name = "padEnd", arity = 1)
public static Object padEnd(ExecutionContext cx, Object thisValue, Object maxLength, Object fillString) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* step 2 */
String s = ToFlatString(cx, obj);
/* step 3 */
long intMaxLength = ToLength(cx, maxLength);
/* step 4 */
int stringLength = s.length();
/* step 5 */
if (intMaxLength <= stringLength) {
return s;
}
/* steps 6-7 */
CharSequence fillStr = Type.isUndefined(fillString) ? "" : ToString(cx, fillString);
/* step 8 */
if (fillStr.length() == 0) {
fillStr = " ";
}
/* step 9 */
long fillLen = intMaxLength - stringLength;
if (fillLen > 1 << 27) {
// Likely to exceed heap space, throw RangeError to match String.prototype.repeat.
throw newRangeError(cx, Messages.Key.InvalidStringPad);
}
/* steps 10-11 */
return s + repeatFill(fillStr.toString(), (int) fillLen);
}
private static String repeatFill(String fillStr, int fillLen) {
assert !fillStr.isEmpty() && fillLen > 0;
final int length = fillStr.length();
int c = fillLen / length;
if (c == 0) {
return fillStr.substring(0, fillLen);
}
if (c == 1) {
int r = fillLen - length;
if (r == 0) {
return fillStr;
}
return fillStr + fillStr.substring(0, r);
}
char[] ca = new char[fillLen];
if (length == 1) {
Arrays.fill(ca, fillStr.charAt(0));
return new String(ca);
}
fillStr.getChars(0, length, ca, 0);
final int limit = length * Integer.highestOneBit(c);
for (int k = length; k < limit; k <<= 1) {
System.arraycopy(ca, 0, ca, k, k);
}
System.arraycopy(ca, 0, ca, limit, fillLen - limit);
return new String(ca);
}
}
/**
* Extension: String.prototype.matchAll
*/
@CompatibilityExtension(CompatibilityOption.StringMatchAll)
public enum MatchAllFunction {
;
/**
* String.prototype.matchAll ( regexp )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param regexp
* the regular expression object
* @return the match iterator
*/
@Function(name = "matchAll", arity = 1)
public static Object matchAll(ExecutionContext cx, Object thisValue, Object regexp) {
/* step 1 */
Object obj = RequireObjectCoercible(cx, thisValue);
/* steps 2-4 */
if (!IsRegExp(cx, regexp)) {
throw newTypeError(cx, Messages.Key.IncompatibleObject);
}
/* steps 5-6 */
String s = ToFlatString(cx, obj);
/* steps 7-8 */
String flags = ToFlatString(cx, Get(cx, Type.objectValue(regexp), "flags"));
/* step 9 */
if (flags.indexOf('g') == -1) {
flags = "g" + flags;
}
/* step 10 */
// FIXME: spec bug? - species not handled.
// FIXME: spec bug - regexp pattern not extracted.
String pattern = ToFlatString(cx, Get(cx, Type.objectValue(regexp), "source"));
RegExpObject rx = RegExpCreate(cx, pattern, flags);
/* steps 11-12 */
long lastIndex = ToLength(cx, Get(cx, Type.objectValue(regexp), "lastIndex"));
/* steps 13-14 */
Set(cx, rx, "lastIndex", lastIndex, true);
/* step 15 */
return CreateRegExpStringIterator(cx, rx, s);
}
}
}