/**
* 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;
import static com.github.anba.es6draft.runtime.AbstractOperations.*;
import static com.github.anba.es6draft.runtime.internal.Errors.newInternalError;
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.ArrayIteratorPrototype.CreateArrayIterator;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
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.AliasFunction;
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.Optional;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.objects.ArrayIteratorObject.ArrayIterationKind;
import com.github.anba.es6draft.runtime.objects.binary.TypedArrayObject;
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.ScriptObject;
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.OrdinaryObject;
/**
* <h1>22 Indexed Collections</h1><br>
* <h2>22.1 Array Objects</h2>
* <ul>
* <li>22.1.3 Properties of the Array Prototype Object
* <li>22.1.4 Properties of Array Instances
* </ul>
*/
public final class ArrayPrototype extends ArrayObject implements Initializable {
/**
* Constructs a new Array prototype object.
*
* @param realm
* the realm object
*/
public ArrayPrototype(Realm realm) {
super(realm);
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
createProperties(realm, this, AdditionalProperties.class);
}
/**
* Marker class for {@code Array.prototype.values}.
*/
private static final class ArrayPrototypeValues {
}
/**
* Returns {@code true} if <var>values</var> is the built-in {@code Array.prototype.values} function for the
* requested realm.
*
* @param realm
* the function realm
* @param values
* the values function
* @return {@code true} if <var>values</var> is the built-in {@code Array.prototype.values} function
*/
public static boolean isBuiltinValues(Realm realm, Object values) {
return NativeFunction.isNative(realm, values, ArrayPrototypeValues.class);
}
private static final long ARRAY_LENGTH_LIMIT = 0x1F_FFFF_FFFF_FFFFL;
private static final boolean NO_ARRAY_OPTIMIZATION = false;
private static final int MIN_SPARSE_LENGTH = 100; // Arbitrarily chosen limit
private static final int MAX_PROTO_DEPTH = 10; // Arbitrarily chosen limit
private enum IterationKind {
DenseOwnKeys, SparseOwnKeys, InheritedKeys, TypedArray, Slow;
public boolean isSparse() {
return this == SparseOwnKeys || this == InheritedKeys;
}
public boolean isInherited() {
return this == InheritedKeys;
}
}
private static IterationKind iterationKind(ScriptObject object, long length) {
return iterationKind(object, object, length);
}
private static IterationKind iterationKind(ScriptObject target, ScriptObject source, long length) {
if (NO_ARRAY_OPTIMIZATION) {
return IterationKind.Slow;
}
if (length < MIN_SPARSE_LENGTH) {
return IterationKind.Slow;
}
if (!(target instanceof OrdinaryObject) || ((OrdinaryObject) target).hasSpecialIndexedProperties()) {
return IterationKind.Slow;
}
if (source instanceof ArrayObject) {
return iterationKind((ArrayObject) source, length);
}
if (source instanceof TypedArrayObject) {
return iterationKind((TypedArrayObject) source);
}
if (source instanceof OrdinaryObject) {
return iterationKind((OrdinaryObject) source, length);
}
return IterationKind.Slow;
}
private static IterationKind iterationKind(ArrayObject array, long length) {
if (array.isDenseArray()) {
return IterationKind.DenseOwnKeys;
}
if (array.hasIndexedAccessors()) {
return IterationKind.Slow;
}
return iterationKindForSparse(array, length);
}
private static IterationKind iterationKind(OrdinaryObject arrayLike, long length) {
if (arrayLike.hasSpecialIndexedProperties()) {
return IterationKind.Slow;
}
if (arrayLike.isDenseArray(length)) {
return IterationKind.DenseOwnKeys;
}
if (arrayLike.hasIndexedAccessors()) {
return IterationKind.Slow;
}
return iterationKindForSparse(arrayLike, length);
}
private static IterationKind iterationKind(TypedArrayObject typedArray) {
if (typedArray.getBuffer().isDetached()) {
return IterationKind.Slow;
}
return IterationKind.TypedArray;
}
private static IterationKind iterationKindForSparse(OrdinaryObject arrayLike, long length) {
IterationKind iteration = IterationKind.SparseOwnKeys;
int protoDepth = 0;
long indexed = 0;
for (OrdinaryObject object = arrayLike;;) {
indexed += object.getIndexedSize();
ScriptObject prototype = object.getPrototype();
if (prototype == null) {
break;
}
if (!(prototype instanceof OrdinaryObject)) {
return IterationKind.Slow;
}
object = (OrdinaryObject) prototype;
if (object.hasSpecialIndexedProperties()) {
return IterationKind.Slow;
}
if (object.hasIndexedProperties()) {
if (object.hasIndexedAccessors()) {
return IterationKind.Slow;
}
iteration = IterationKind.InheritedKeys;
}
if (++protoDepth == MAX_PROTO_DEPTH) {
return IterationKind.Slow;
}
}
double density = indexed / (double) length;
if (density > 0.75) {
return IterationKind.Slow;
}
return iteration;
}
private static long[] arrayKeys(OrdinaryObject array, long from, long to, boolean inherited) {
if (inherited) {
return inheritedKeys(array, from, to);
}
return array.indices(from, to);
}
private static long[] inheritedKeys(OrdinaryObject array, long from, long to) {
long[] indices = array.indices(from, to);
for (ScriptObject prototype = array.getPrototype(); prototype != null;) {
assert prototype instanceof OrdinaryObject : "Wrong class " + prototype.getClass();
OrdinaryObject proto = (OrdinaryObject) prototype;
if (proto.hasIndexedProperties()) {
long[] protoIndices = proto.indices(from, to);
long[] newIndices = new long[indices.length + protoIndices.length];
System.arraycopy(indices, 0, newIndices, 0, indices.length);
System.arraycopy(protoIndices, 0, newIndices, indices.length, protoIndices.length);
indices = newIndices;
}
prototype = proto.getPrototype();
}
Arrays.sort(indices);
return indices;
}
private static ForwardIter forwardIter(OrdinaryObject array, long from, long to, boolean inherited) {
return new ForwardIter(from, to, arrayKeys(array, from, to, inherited), inherited);
}
private static ReverseIter reverseIterator(OrdinaryObject array, long from, long to, boolean inherited) {
return new ReverseIter(from, to, arrayKeys(array, from, to, inherited), inherited);
}
private static class KeyIter {
protected final long from;
protected final long to;
protected final long[] keys;
protected final boolean inherited;
protected final int length;
protected int index;
protected long lastKey;
KeyIter(long from, long to, long[] keys, boolean inherited, int index, long lastKey) {
this.from = from;
this.to = to;
this.keys = keys;
this.inherited = inherited;
this.length = keys.length;
this.index = index;
this.lastKey = lastKey;
}
final int size() {
return keys.length;
}
final long peek() {
assert 0 <= index && index < length;
return keys[index];
}
}
private static final class ForwardIter extends KeyIter {
ForwardIter(long from, long to, long[] keys, boolean inherited) {
super(from, to, keys, inherited, 0, from - 1);
}
boolean hasNext() {
for (; index < length; ++index) {
long key = keys[index];
if (key != lastKey) {
assert lastKey < key && (from <= key && key < to);
return true;
}
assert inherited;
}
return false;
}
long next() {
assert index < length;
return lastKey = keys[index++];
}
boolean contains(long needle) {
for (int i = index; i < length; ++i) {
if (keys[i] == needle) {
return true;
}
if (keys[i] > needle) {
break;
}
}
return false;
}
}
private static final class ReverseIter extends KeyIter {
ReverseIter(long from, long to, long[] keys, boolean inherited) {
super(from, to, keys, inherited, keys.length - 1, to);
}
boolean hasNext() {
for (; index >= 0; --index) {
long key = keys[index];
if (key != lastKey) {
assert key < lastKey && (from <= key && key < to);
return true;
}
assert inherited;
}
return false;
}
long next() {
assert index >= 0;
return lastKey = keys[index--];
}
boolean contains(long needle) {
for (int i = index; i >= 0; --i) {
if (keys[i] == needle) {
return true;
}
if (keys[i] < needle) {
break;
}
}
return false;
}
}
/**
* 22.1.3 Properties of the Array Prototype Object
*/
public enum Properties {
;
private static long ToArrayIndex(ExecutionContext cx, Object index, long length) {
double relativeIndex = ToInteger(cx, index);
if (relativeIndex < 0) {
return (long) Math.max(length + relativeIndex, 0);
}
return (long) Math.min(relativeIndex, length);
}
@Prototype
public static final Intrinsics __proto__ = Intrinsics.ObjectPrototype;
/**
* 22.1.3.2 Array.prototype.constructor
*/
@Value(name = "constructor")
public static final Intrinsics constructor = Intrinsics.Array;
/**
* 22.1.3.27 Array.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) {
/* steps 1-2 */
ScriptObject array = ToObject(cx, thisValue);
/* steps 3-4 */
Object func = Get(cx, array, "join");
/* step 5 */
if (!IsCallable(func)) {
func = cx.getIntrinsic(Intrinsics.ObjProto_toString);
}
/* step 6 */
return ((Callable) func).call(cx, array);
}
/**
* 22.1.3.26 Array.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] )<br>
* 13.4.1 Array.prototype.toLocaleString([locales [, options ]])
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param locales
* the optional locales array
* @param options
* the optional options object
* @return the locale specific string representation
*/
@Function(name = "toLocaleString", arity = 0)
public static Object toLocaleString(ExecutionContext cx, Object thisValue, Object locales,
Object options) {
/* steps 1-2 */
ScriptObject array = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, array, "length"));
/* step 5 */
// FIXME: spec issue - retrieve list separator from locale?
String separator = cx.getRealm().getListSeparator();
/* step 6 */
if (len == 0) {
return "";
}
/* steps 7-8 */
Object firstElement = Get(cx, array, 0);
/* steps 9-10 */
StringBuilder r = new StringBuilder();
if (!Type.isUndefinedOrNull(firstElement)) {
r.append(ToString(cx, Invoke(cx, firstElement, "toLocaleString", locales, options)));
}
/* steps 11-12 */
for (long k = 1; k < len; ++k) {
r.append(separator);
Object nextElement = Get(cx, array, k);
if (!Type.isUndefinedOrNull(nextElement)) {
r.append(ToString(cx,
Invoke(cx, nextElement, "toLocaleString", locales, options)));
}
}
/* step 13 */
return r.toString();
}
/**
* 22.1.3.1 Array.prototype.concat ( ...arguments )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param items
* the new array elements
* @return the concatenated array object
*/
@Function(name = "concat", arity = 1)
public static Object concat(ExecutionContext cx, Object thisValue, Object... items) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
ScriptObject a = ArraySpeciesCreate(cx, o, 0);
/* step 5 */
long n = 0;
/* step 6 */
Object[] allItems = new Object[items.length + 1];
allItems[0] = o;
System.arraycopy(items, 0, allItems, 1, items.length);
/* step 7 */
for (Object item : allItems) {
boolean spreadable = IsConcatSpreadable(cx, item);
if (spreadable) {
ScriptObject e = (ScriptObject) item;
/* steps 7.d.ii-7.d.iii */
long len = ToLength(cx, Get(cx, e, "length"));
/* step 7.d.iv */
if (n + len > ARRAY_LENGTH_LIMIT) {
throw newTypeError(cx, Messages.Key.InvalidArrayLength);
}
IterationKind iteration = iterationKind(a, e, len);
// Optimization: Sparse Array objects
if (iteration.isSparse()) {
concatSpread(cx, (OrdinaryObject) a, n, (OrdinaryObject) e, len,
iteration.isInherited());
n += len;
continue;
}
// Optimization: TypedArray objects
if (iteration == IterationKind.TypedArray) {
concatSpread(cx, (OrdinaryObject) a, n, (TypedArrayObject) e, len);
n += len;
continue;
}
/* steps 7.d.i, 7.d.v */
for (long k = 0; k < len; ++k, ++n) {
long p = k;
boolean exists = HasProperty(cx, e, p);
if (exists) {
Object subElement = Get(cx, e, p);
CreateDataPropertyOrThrow(cx, a, n, subElement);
}
}
} else {
/* step 7.e */
if (n >= ARRAY_LENGTH_LIMIT) {
throw newTypeError(cx, Messages.Key.InvalidArrayLength);
}
CreateDataPropertyOrThrow(cx, a, n++, item);
}
}
/* steps 8-9 */
assert n <= ARRAY_LENGTH_LIMIT;
Set(cx, a, "length", n, true);
/* step 10 */
return a;
}
private static void concatSpread(ExecutionContext cx, OrdinaryObject a, long offset,
OrdinaryObject e, long length, boolean inherited) {
for (ForwardIter iter = forwardIter(e, 0, length, inherited); iter.hasNext();) {
long k = iter.next();
Object subElement = Get(cx, e, k);
CreateDataPropertyOrThrow(cx, a, offset + k, subElement);
}
}
private static void concatSpread(ExecutionContext cx, OrdinaryObject a, long offset,
TypedArrayObject e, long length) {
assert length > 0;
for (long k = 0; k < length; ++k) {
Object subElement = Get(cx, e, k);
CreateDataPropertyOrThrow(cx, a, offset + k, subElement);
}
}
/**
* 22.1.3.1.1 Runtime Semantics: IsConcatSpreadable ( O )
*
* @param cx
* the execution context
* @param o
* the object to test
* @return {@code true} if the object is spreadable
*/
public static boolean IsConcatSpreadable(ExecutionContext cx, Object o) {
/* step 1 */
if (!Type.isObject(o)) {
return false;
}
ScriptObject object = Type.objectValue(o);
/* steps 2-3 */
Object spreadable = Get(cx, object, BuiltinSymbol.isConcatSpreadable.get());
/* step 4 */
if (!Type.isUndefined(spreadable)) {
return ToBoolean(spreadable);
}
/* step 5 */
return IsArray(cx, object);
}
/**
* 22.1.3.12 Array.prototype.join (separator)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param separator
* the separator string
* @return the result string
*/
@Function(name = "join", arity = 1)
public static Object join(ExecutionContext cx, Object thisValue, Object separator) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* steps 5-7 */
String sep = Type.isUndefined(separator) ? "," : ToFlatString(cx, separator);
/* step 8 */
if (len == 0) {
return "";
}
/* step 9 */
Object element0 = Get(cx, o, 0);
/* steps 10-11 */
StringBuilder r = new StringBuilder();
if (!Type.isUndefinedOrNull(element0)) {
r.append(ToString(cx, element0));
}
IterationKind iteration = iterationKind(o, len);
if (iteration.isSparse()) {
/* steps 12-13 (Optimization: Sparse Array objects) */
joinSparse(cx, (OrdinaryObject) o, len, sep, r, iteration.isInherited());
} else {
/* steps 12-13 */
for (long k = 1; k < len; ++k) {
/* step 13.a */
r.append(sep);
/* step 13.b */
Object element = Get(cx, o, k);
/* steps 13.c-e */
if (!Type.isUndefinedOrNull(element)) {
r.append(ToString(cx, element));
}
}
}
/* step 14 */
return r.toString();
}
private static void joinSparse(ExecutionContext cx, OrdinaryObject o, long length,
String sep, StringBuilder r, boolean inherited) {
final boolean hasSeparator = !sep.isEmpty();
if (hasSeparator) {
long estimated = length * sep.length();
if (estimated >= Integer.MAX_VALUE || length > Long.MAX_VALUE / sep.length()) {
throw newInternalError(cx, Messages.Key.OutOfMemory);
}
}
long lastKey = 0;
objectElement: {
for (ForwardIter iter = forwardIter(o, 1, length, inherited); iter.hasNext();) {
long k = iter.next();
// Add leading separator and fill holes if separator is not the empty string.
if (hasSeparator) {
for (long start = lastKey; start < k; ++start) {
r.append(sep);
}
}
lastKey = k;
Object element = Get(cx, o, k);
if (!Type.isUndefinedOrNull(element)) {
r.append(ToString(cx, element));
// Side-effects may have invalidated array keys.
if (Type.isObject(element)) {
break objectElement;
}
}
}
// Fill trailing holes if separator is not the empty string.
if (hasSeparator) {
for (long start = lastKey + 1; start < length; ++start) {
r.append(sep);
}
}
return;
}
// Trailing elements after object type element.
for (long k = lastKey + 1; k < length; ++k) {
r.append(sep);
Object element = Get(cx, o, k);
if (!Type.isUndefinedOrNull(element)) {
r.append(ToString(cx, element));
}
}
}
/**
* 22.1.3.16 Array.prototype.pop ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the popped array element
*/
@Function(name = "pop", arity = 0)
public static Object pop(ExecutionContext cx, Object thisValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* steps 5-6 */
if (len == 0) {
/* step 5 */
/* steps 5.a-b */
Set(cx, o, "length", 0, true);
/* step 5.c */
return UNDEFINED;
} else {
/* step 6 */
assert len > 0;
/* step 6.a */
long newLen = len - 1;
/* step 6.b */
long index = newLen;
/* steps 6.c-d */
Object element = Get(cx, o, index);
/* steps 6.e-f */
DeletePropertyOrThrow(cx, o, index);
/* steps 6.g-h */
Set(cx, o, "length", newLen, true);
/* step 6.i */
return element;
}
}
/**
* 22.1.3.17 Array.prototype.push ( ...items )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param items
* the new array elements
* @return the new array length
*/
@Function(name = "push", arity = 1)
public static Object push(ExecutionContext cx, Object thisValue, Object... items) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 (not applicable) */
/* steps 6-7 */
if (len + items.length > ARRAY_LENGTH_LIMIT) {
throw newTypeError(cx, Messages.Key.InvalidArrayLength);
}
/* step 8 */
for (Object e : items) {
/* step 8.a (not applicable) */
/* steps 8.b-c */
Set(cx, o, len, e, true);
/* step 8.d */
len += 1;
}
/* steps 9-10 */
assert len <= ARRAY_LENGTH_LIMIT;
Set(cx, o, "length", len, true);
/* step 11 */
return len;
}
/**
* 22.1.3.20 Array.prototype.reverse ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return this array object
*/
@Function(name = "reverse", arity = 0)
public static Object reverse(ExecutionContext cx, Object thisValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
IterationKind iteration = iterationKind(o, len);
if (iteration.isSparse()) {
/* steps 5-7 (Optimization: Sparse Array objects) */
reverseSparse(cx, (OrdinaryObject) o, len, iteration.isInherited());
} else {
/* step 5 */
long middle = len / 2L;
/* steps 6-7 */
for (long lower = 0; lower != middle; ++lower) {
/* step 7.a */
long upper = len - lower - 1;
/* step 7.b */
long upperP = upper;
/* step 7.c */
long lowerP = lower;
/* steps 7.d-e */
boolean lowerExists = HasProperty(cx, o, lowerP);
/* step 7.f */
Object lowerValue = lowerExists ? Get(cx, o, lowerP) : null;
/* steps 7.g-h */
boolean upperExists = HasProperty(cx, o, upperP);
/* step 7.i */
Object upperValue = upperExists ? Get(cx, o, upperP) : null;
/* steps 7.j-m */
if (lowerExists && upperExists) {
Set(cx, o, lowerP, upperValue, true);
Set(cx, o, upperP, lowerValue, true);
} else if (!lowerExists && upperExists) {
Set(cx, o, lowerP, upperValue, true);
DeletePropertyOrThrow(cx, o, upperP);
} else if (lowerExists && !upperExists) {
DeletePropertyOrThrow(cx, o, lowerP);
Set(cx, o, upperP, lowerValue, true);
} else {
// no action required
}
}
}
/* step 8 */
return o;
}
private static void reverseSparse(ExecutionContext cx, OrdinaryObject o, long length,
boolean inherited) {
long middle = length / 2L;
ForwardIter lowerIter = forwardIter(o, 0, middle, inherited);
ReverseIter upperIter = reverseIterator(o, length - middle, length, inherited);
while (lowerIter.hasNext() && upperIter.hasNext()) {
long lower = lowerIter.peek();
long upper = (length - 1) - upperIter.peek();
if (lower == upper) {
long lowerP = lowerIter.next();
long upperP = upperIter.next();
Object lowerValue = Get(cx, o, lowerP);
Object upperValue = Get(cx, o, upperP);
Set(cx, o, lowerP, upperValue, true);
Set(cx, o, upperP, lowerValue, true);
} else if (lower < upper) {
long lowerP = lowerIter.next();
long upperP = (length - 1) - lower;
Object lowerValue = Get(cx, o, lowerP);
DeletePropertyOrThrow(cx, o, lowerP);
Set(cx, o, upperP, lowerValue, true);
} else {
long upperP = upperIter.next();
long lowerP = upper;
Object upperValue = Get(cx, o, upperP);
Set(cx, o, lowerP, upperValue, true);
DeletePropertyOrThrow(cx, o, upperP);
}
}
while (lowerIter.hasNext()) {
long lowerP = lowerIter.next();
long upperP = (length - 1) - lowerP;
Object lowerValue = Get(cx, o, lowerP);
DeletePropertyOrThrow(cx, o, lowerP);
Set(cx, o, upperP, lowerValue, true);
}
while (upperIter.hasNext()) {
long upperP = upperIter.next();
long lowerP = (length - 1) - upperP;
Object upperValue = Get(cx, o, upperP);
Set(cx, o, lowerP, upperValue, true);
DeletePropertyOrThrow(cx, o, upperP);
}
}
/**
* 22.1.3.21 Array.prototype.shift ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the shifted array element
*/
@Function(name = "shift", arity = 0)
public static Object shift(ExecutionContext cx, Object thisValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (len == 0) {
Set(cx, o, "length", 0, true);
return UNDEFINED;
}
assert len > 0;
/* steps 6-7 */
Object first = Get(cx, o, 0);
IterationKind iteration = iterationKind(o, len);
if (iteration.isSparse()) {
/* steps 8-9 (Optimization: Sparse Array objects) */
shiftSparse(cx, (OrdinaryObject) o, len, iteration.isInherited());
} else {
/* steps 8-9 */
for (long k = 1; k < len; ++k) {
/* step 9.a */
long from = k;
/* step 9.b */
long to = k - 1;
/* steps 9.c-d */
boolean fromPresent = HasProperty(cx, o, from);
/* steps 9.e-f */
if (fromPresent) {
Object fromVal = Get(cx, o, from);
Set(cx, o, to, fromVal, true);
} else {
DeletePropertyOrThrow(cx, o, to);
}
}
}
/* steps 10-11 */
DeletePropertyOrThrow(cx, o, len - 1);
/* steps 12-13 */
Set(cx, o, "length", len - 1, true);
/* step 14 */
return first;
}
private static void shiftSparse(ExecutionContext cx, OrdinaryObject o, long length,
boolean inherited) {
ForwardIter iter = forwardIter(o, 1, length, inherited);
if (iter.hasNext() && iter.peek() != 1) {
DeletePropertyOrThrow(cx, o, 0);
}
while (iter.hasNext()) {
long k = iter.next();
long from = k;
long to = k - 1;
Object fromVal = Get(cx, o, from);
Set(cx, o, to, fromVal, true);
long replacement = k + 1;
if (replacement < length && !iter.contains(replacement)) {
DeletePropertyOrThrow(cx, o, from);
}
}
}
/**
* 22.1.3.22 Array.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 new array object
*/
@Function(name = "slice", arity = 2)
public static Object slice(ExecutionContext cx, Object thisValue, Object start, Object end) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* steps 5-7 */
long k = ToArrayIndex(cx, start, len);
/* steps 8-10 */
long finall = Type.isUndefined(end) ? len : ToArrayIndex(cx, end, len);
/* step 11 */
long count = Math.max(finall - k, 0);
/* steps 12-13 */
ScriptObject a = ArraySpeciesCreate(cx, o, count);
long n;
IterationKind iteration = iterationKind(a, o, len);
if (iteration.isSparse()) {
/* steps 14-15 (Optimization: Sparse Array objects) */
sliceSparse(cx, (OrdinaryObject) a, k, finall, (OrdinaryObject) o,
iteration.isInherited());
n = count;
} else {
n = 0;
/* steps 14-15 */
for (; k < finall; ++k, ++n) {
long pk = k;
boolean kpresent = HasProperty(cx, o, pk);
if (kpresent) {
Object kvalue = Get(cx, o, pk);
CreateDataPropertyOrThrow(cx, a, n, kvalue);
}
}
}
/* steps 16-17 */
Set(cx, a, "length", n, true);
/* step 18 */
return a;
}
private static void sliceSparse(ExecutionContext cx, OrdinaryObject a, long k, long finall,
OrdinaryObject o, boolean inherited) {
for (ForwardIter iter = forwardIter(o, k, finall, inherited); iter.hasNext();) {
long pk = iter.next();
long n = pk - k;
Object kvalue = Get(cx, o, pk);
CreateDataPropertyOrThrow(cx, a, n, kvalue);
}
}
/**
* 22.1.3.24.1 Runtime Semantics: SortCompare( x, y )
*/
private static final class DefaultComparator implements Comparator<Object> {
private final ExecutionContext cx;
DefaultComparator(ExecutionContext cx) {
this.cx = cx;
}
@Override
public int compare(Object o1, Object o2) {
/* steps 1-4 (not applicable) */
/* steps 5-6 */
String x = ToFlatString(cx, o1);
/* steps 7-8 */
String y = ToFlatString(cx, o2);
/* steps 9-11 */
return x.compareTo(y);
}
}
/**
* 22.1.3.24.1 Runtime Semantics: SortCompare( x, y )
*/
private static final class FunctionComparator implements Comparator<Object> {
private final ExecutionContext cx;
private final Callable comparefn;
FunctionComparator(ExecutionContext cx, Callable comparefn) {
this.cx = cx;
this.comparefn = comparefn;
}
@Override
public int compare(Object o1, Object o2) {
/* steps 1-3, 5-11 (not applicable) */
/* step 4 */
double c = ToNumber(cx, comparefn.call(cx, UNDEFINED, o1, o2));
return (c < 0 ? -1 : c > 0 ? 1 : 0);
}
}
private static void sortElements(ExecutionContext cx, ArrayList<Object> elements,
Object comparefn) {
Comparator<Object> comparator;
if (!Type.isUndefined(comparefn)) {
if (!IsCallable(comparefn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
comparator = new FunctionComparator(cx, (Callable) comparefn);
} else {
comparator = new DefaultComparator(cx);
}
try {
Collections.sort(elements, comparator);
} catch (IllegalArgumentException e) {
// User-defined comparator functions may return inconsistent comparison results,
// and those will trigger this exception in the Collections.sort() method:
// `IllegalArgumentException: Comparison method violates its general contract!`
// If that happens, just ignore the Java exception and stop the sort operation.
}
}
/**
* 22.1.3.24 Array.prototype.sort (comparefn)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param comparefn
* the comparator function
* @return this array object
*/
@Function(name = "sort", arity = 1)
public static Object sort(ExecutionContext cx, Object thisValue, Object comparefn) {
/* step 1 */
ScriptObject obj = ToObject(cx, thisValue);
/* steps 2-3 */
long len = ToLength(cx, Get(cx, obj, "length"));
// return if array is empty or has only one element
if (len <= 1) {
return obj;
}
IterationKind iteration = iterationKind(obj, len);
if (iteration.isSparse()) {
sortSparse(cx, (OrdinaryObject) obj, len, comparefn, iteration.isInherited());
} else {
// handle OOM early
if (len > Integer.MAX_VALUE) {
throw newInternalError(cx, Messages.Key.OutOfMemory);
}
// collect elements
int length = (int) len;
int emptyCount = 0;
int undefCount = 0;
ArrayList<Object> elements = new ArrayList<>(Math.min(length, 1024));
for (int i = 0; i < length; ++i) {
long index = i;
if (HasProperty(cx, obj, index)) {
Object e = Get(cx, obj, index);
if (!Type.isUndefined(e)) {
elements.add(e);
} else {
undefCount += 1;
}
} else {
emptyCount += 1;
}
}
// sort elements
int count = elements.size();
if (count > 1) {
sortElements(cx, elements, comparefn);
}
// and finally set sorted elements
for (int i = 0, offset = 0; i < count; ++i) {
int p = offset + i;
Set(cx, obj, p, elements.get(i), true);
}
for (int i = 0, offset = count; i < undefCount; ++i) {
int p = offset + i;
Set(cx, obj, p, UNDEFINED, true);
}
for (int i = 0, offset = count + undefCount; i < emptyCount; ++i) {
int p = offset + i;
DeletePropertyOrThrow(cx, obj, p);
}
}
return obj;
}
private static void sortSparse(ExecutionContext cx, OrdinaryObject obj, long length,
Object comparefn, boolean inherited) {
// collect elements
ForwardIter collectIter = forwardIter(obj, 0, length, inherited);
int undefCount = 0;
ArrayList<Object> elements = new ArrayList<>(Math.min(collectIter.size(), 1024));
while (collectIter.hasNext()) {
long index = collectIter.next();
Object e = Get(cx, obj, index);
if (!Type.isUndefined(e)) {
elements.add(e);
} else {
undefCount += 1;
}
}
// sort elements
int count = elements.size();
if (count > 1) {
sortElements(cx, elements, comparefn);
}
// and finally set sorted elements
for (int i = 0, offset = 0; i < count; ++i) {
int p = offset + i;
Set(cx, obj, p, elements.get(i), true);
}
for (int i = 0, offset = count; i < undefCount; ++i) {
int p = offset + i;
Set(cx, obj, p, UNDEFINED, true);
}
// User-defined actions in comparefn may have invalidated sparse-array property
IterationKind iterationDelete = iterationKind(obj, length);
if (iterationDelete.isSparse()) {
ForwardIter deleteIter = forwardIter(obj, count + undefCount, length, iterationDelete.isInherited());
while (deleteIter.hasNext()) {
long p = deleteIter.next();
DeletePropertyOrThrow(cx, obj, p);
}
} else {
for (long i = count + undefCount; i < length; ++i) {
long p = i;
DeletePropertyOrThrow(cx, obj, p);
}
}
}
/**
* 22.1.3.25 Array.prototype.splice (start, deleteCount, ...items )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param start
* the start index
* @param deleteCount
* the delete count
* @param items
* the new array elements
* @return the deleted array elements
*/
@Function(name = "splice", arity = 2)
public static Object splice(ExecutionContext cx, Object thisValue,
@Optional(Optional.Default.NONE) Object start,
@Optional(Optional.Default.NONE) Object deleteCount, Object... items) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* steps 5-7 */
long actualStart = start != null ? ToArrayIndex(cx, start, len) : 0;
/* steps 8-10 */
int insertCount;
long actualDeleteCount;
if (start == null) {
insertCount = 0;
actualDeleteCount = 0;
} else if (deleteCount == null) {
insertCount = 0;
actualDeleteCount = len - actualStart;
} else {
insertCount = items.length;
double dc = ToInteger(cx, deleteCount);
actualDeleteCount = (long) Math.min(Math.max(dc, 0), len - actualStart);
}
/* step 11 */
if (len + insertCount - actualDeleteCount > ARRAY_LENGTH_LIMIT) {
throw newTypeError(cx, Messages.Key.InvalidArrayLength);
}
/* steps 12-13 */
ScriptObject a = ArraySpeciesCreate(cx, o, actualDeleteCount);
IterationKind iterationCopy = iterationKind(a, o, actualDeleteCount);
if (iterationCopy.isSparse()) {
/* steps 14-15 */
spliceSparseCopy(cx, (OrdinaryObject) a, (OrdinaryObject) o, actualStart,
actualDeleteCount, iterationCopy.isInherited());
} else {
/* steps 14-15 */
for (long k = 0; k < actualDeleteCount; ++k) {
/* step 15.a */
long from = actualStart + k;
/* steps 15.b-c */
boolean fromPresent = HasProperty(cx, o, from);
/* step 15.d */
if (fromPresent) {
Object fromValue = Get(cx, o, from);
CreateDataPropertyOrThrow(cx, a, k, fromValue);
}
}
}
/* steps 16-17 */
Set(cx, a, "length", actualDeleteCount, true);
/* steps 18-19 */
int itemCount = items.length;
if (itemCount < actualDeleteCount) {
/* step 20 */
IterationKind iterationMove = iterationKind(o, len);
if (iterationMove.isSparse()) {
/* steps 20.a-20.b */
spliceSparseMoveLeft(cx, (OrdinaryObject) o, len, actualStart,
actualDeleteCount, itemCount, iterationMove.isInherited());
} else {
/* steps 20.a-20.b */
for (long k = actualStart; k < (len - actualDeleteCount); ++k) {
long from = k + actualDeleteCount;
long to = k + itemCount;
boolean fromPresent = HasProperty(cx, o, from);
if (fromPresent) {
Object fromValue = Get(cx, o, from);
Set(cx, o, to, fromValue, true);
} else {
DeletePropertyOrThrow(cx, o, to);
}
}
}
IterationKind iterationDelete = iterationKind(o, len);
if (iterationDelete.isSparse()) {
/* steps 20.c-20.d */
spliceSparseDelete(cx, (OrdinaryObject) o, len, actualDeleteCount, itemCount,
iterationDelete.isInherited());
} else {
/* steps 20.c-20.d */
for (long k = len; k > (len - actualDeleteCount + itemCount); --k) {
DeletePropertyOrThrow(cx, o, k - 1);
}
}
} else if (itemCount > actualDeleteCount) {
/* step 21 */
IterationKind iterationMove = iterationKind(o, len);
if (iterationMove.isSparse()) {
/* step 21 */
spliceSparseMoveRight(cx, (OrdinaryObject) o, len, actualStart,
actualDeleteCount, itemCount, iterationMove.isInherited());
} else {
/* step 21 */
for (long k = (len - actualDeleteCount); k > actualStart; --k) {
long from = k + actualDeleteCount - 1;
long to = k + itemCount - 1;
boolean fromPresent = HasProperty(cx, o, from);
if (fromPresent) {
Object fromValue = Get(cx, o, from);
Set(cx, o, to, fromValue, true);
} else {
DeletePropertyOrThrow(cx, o, to);
}
}
}
}
/* step 22 */
long k = actualStart;
/* step 23 */
for (int i = 0; i < itemCount; ++k, ++i) {
Object e = items[i];
Set(cx, o, k, e, true);
}
/* steps 24-25 */
assert len - actualDeleteCount + itemCount <= ARRAY_LENGTH_LIMIT;
Set(cx, o, "length", len - actualDeleteCount + itemCount, true);
/* step 26 */
return a;
}
private static void spliceSparseMoveRight(ExecutionContext cx, OrdinaryObject o,
long length, long start, long deleteCount, int itemCount, boolean inherited) {
assert 0 <= deleteCount && deleteCount <= (length - start) : "actualDeleteCount="
+ deleteCount;
assert start >= 0 && (itemCount > 0 && itemCount > deleteCount);
long targetRangeStart = start + itemCount;
long targetRangeEnd = length;
ReverseIter iterTarget = reverseIterator(o, targetRangeStart, targetRangeEnd, inherited);
long sourceRangeStart = start + deleteCount;
long sourceRangeEnd = length;
ReverseIter iterSource = reverseIterator(o, sourceRangeStart, sourceRangeEnd, inherited);
while (iterTarget.hasNext() && iterSource.hasNext()) {
long toRel = iterTarget.peek() - itemCount;
long fromRel = iterSource.peek() - deleteCount;
if (toRel == fromRel) {
long from = iterSource.next();
long to = iterTarget.next();
Object fromValue = Get(cx, o, from);
Set(cx, o, to, fromValue, true);
} else if (toRel < fromRel) {
long from = iterSource.next();
long actualTo = from - deleteCount + itemCount;
Object fromValue = Get(cx, o, from);
Set(cx, o, actualTo, fromValue, true);
} else {
long to = iterTarget.next();
DeletePropertyOrThrow(cx, o, to);
}
}
while (iterTarget.hasNext()) {
long to = iterTarget.next();
DeletePropertyOrThrow(cx, o, to);
}
while (iterSource.hasNext()) {
long from = iterSource.next();
long actualTo = from - deleteCount + itemCount;
Object fromValue = Get(cx, o, from);
Set(cx, o, actualTo, fromValue, true);
}
}
private static void spliceSparseMoveLeft(ExecutionContext cx, OrdinaryObject o,
long length, long start, long deleteCount, int itemCount, boolean inherited) {
assert 0 < deleteCount && deleteCount <= (length - start) : "actualDeleteCount="
+ deleteCount;
assert (itemCount < deleteCount);
assert start >= 0 && itemCount >= 0;
long moveAmount = (length - start) - deleteCount;
assert moveAmount >= 0;
long targetRangeStart = start + itemCount;
long targetRangeEnd = targetRangeStart + moveAmount;
assert targetRangeEnd < length;
ForwardIter iterTarget = forwardIter(o, targetRangeStart, targetRangeEnd, inherited);
long sourceRangeStart = start + deleteCount;
long sourceRangeEnd = sourceRangeStart + moveAmount;
assert sourceRangeEnd == length;
ForwardIter iterSource = forwardIter(o, sourceRangeStart, sourceRangeEnd, inherited);
while (iterTarget.hasNext() && iterSource.hasNext()) {
long toRel = iterTarget.peek() - itemCount;
long fromRel = iterSource.peek() - deleteCount;
if (toRel == fromRel) {
long to = iterTarget.next();
long from = iterSource.next();
Object fromValue = Get(cx, o, from);
Set(cx, o, to, fromValue, true);
} else if (toRel < fromRel) {
long to = iterTarget.next();
DeletePropertyOrThrow(cx, o, to);
} else {
long from = iterSource.next();
long actualTo = from - deleteCount + itemCount;
Object fromValue = Get(cx, o, from);
Set(cx, o, actualTo, fromValue, true);
}
}
while (iterTarget.hasNext()) {
long to = iterTarget.next();
DeletePropertyOrThrow(cx, o, to);
}
while (iterSource.hasNext()) {
long from = iterSource.next();
long actualTo = from - deleteCount + itemCount;
Object fromValue = Get(cx, o, from);
Set(cx, o, actualTo, fromValue, true);
}
}
private static void spliceSparseCopy(ExecutionContext cx, OrdinaryObject a,
OrdinaryObject o, long start, long deleteCount, boolean inherited) {
long copyEnd = start + deleteCount;
for (ForwardIter iter = forwardIter(o, start, copyEnd, inherited); iter.hasNext();) {
long from = iter.next();
Object fromValue = Get(cx, o, from);
CreateDataPropertyOrThrow(cx, a, from - start, fromValue);
}
}
private static void spliceSparseDelete(ExecutionContext cx, OrdinaryObject o, long length,
long deleteCount, long itemCount, boolean inherited) {
long delStart = length - deleteCount + itemCount;
for (ReverseIter iter = reverseIterator(o, delStart, length, inherited); iter.hasNext();) {
long k = iter.next();
DeletePropertyOrThrow(cx, o, k);
}
}
/**
* 22.1.3.28 Array.prototype.unshift ( ...items )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param items
* the new array elements
* @return the new array length
*/
@Function(name = "unshift", arity = 1)
public static Object unshift(ExecutionContext cx, Object thisValue, Object... items) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
int argCount = items.length;
/* step 6 */
if (argCount > 0) {
/* step 6.a */
if (len + argCount > ARRAY_LENGTH_LIMIT) {
throw newTypeError(cx, Messages.Key.InvalidArrayLength);
}
IterationKind iteration = iterationKind(o, len);
if (iteration.isSparse()) {
/* steps 6.b-6.c (Optimization: Sparse Array objects) */
unshiftSparse(cx, (OrdinaryObject) o, len, argCount, iteration.isInherited());
} else {
/* steps 6.b-6.c */
for (long k = len; k > 0; --k) {
long from = k - 1;
long to = k + argCount - 1;
boolean fromPresent = HasProperty(cx, o, from);
if (fromPresent) {
Object fromValue = Get(cx, o, from);
Set(cx, o, to, fromValue, true);
} else {
DeletePropertyOrThrow(cx, o, to);
}
}
}
/* steps 6.d-6.f */
for (int j = 0; j < items.length; ++j) {
Object e = items[j];
Set(cx, o, j, e, true);
}
}
/* steps 7-8 */
assert len + argCount <= ARRAY_LENGTH_LIMIT;
Set(cx, o, "length", len + argCount, true);
/* step 9 */
return len + argCount;
}
private static void unshiftSparse(ExecutionContext cx, OrdinaryObject o, long length,
int argCount, boolean inherited) {
ReverseIter iter = reverseIterator(o, 0, length, inherited);
int deleteFirst = 0, deleteLast = 0;
// TODO: alloc Math.min(iter.size, argCount) + ring buffer?
long[] keysToDelete = new long[iter.size()];
while (iter.hasNext()) {
long k = iter.next();
while (deleteLast > deleteFirst && keysToDelete[deleteFirst] > k) {
DeletePropertyOrThrow(cx, o, keysToDelete[deleteFirst++] + argCount);
}
long from = k;
long to = k + argCount;
Object fromValue = Get(cx, o, from);
Set(cx, o, to, fromValue, true);
long replacement = k - argCount;
if (replacement >= 0 && !iter.contains(replacement)) {
keysToDelete[deleteLast++] = replacement;
}
}
while (deleteLast > deleteFirst) {
DeletePropertyOrThrow(cx, o, keysToDelete[deleteFirst++] + argCount);
}
}
/**
* 22.1.3.11 Array.prototype.indexOf ( searchElement [ , fromIndex ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchElement
* the search element
* @param fromIndex
* the optional start index
* @return the result index
*/
@Function(name = "indexOf", arity = 1)
public static Object indexOf(ExecutionContext cx, Object thisValue, Object searchElement,
@Optional(Optional.Default.NONE) Object fromIndex) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (len == 0) {
return -1;
}
/* steps 6-7 */
long n;
if (fromIndex != null) {
n = (long) ToInteger(cx, fromIndex);
} else {
n = 0;
}
/* step 8 */
if (n >= len) {
return -1;
}
/* steps 9-10 */
long k;
if (n >= 0) {
k = n;
} else {
k = len - Math.abs(n);
if (k < 0) {
k = 0;
}
}
/* step 11 */
for (; k < len; ++k) {
/* step 11.a-b */
boolean kpresent = HasProperty(cx, o, k);
/* step 11.c */
if (kpresent) {
Object elementk = Get(cx, o, k);
boolean same = StrictEqualityComparison(searchElement, elementk);
if (same) {
return k;
}
}
}
/* step 12 */
return -1;
}
/**
* 22.1.3.14 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchElement
* the search element
* @param fromIndex
* the optional start index
* @return the result index
*/
@Function(name = "lastIndexOf", arity = 1)
public static Object lastIndexOf(ExecutionContext cx, Object thisValue,
Object searchElement, @Optional(Optional.Default.NONE) Object fromIndex) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (len == 0) {
return -1;
}
/* steps 6-7 */
long n;
if (fromIndex != null) {
n = (long) ToInteger(cx, fromIndex);
} else {
n = len - 1;
}
/* steps 8-9 */
long k;
if (n >= 0) {
k = Math.min(n, len - 1);
} else {
k = len - Math.abs(n);
}
/* step 10 */
for (; k >= 0; --k) {
/* steps 10.a-b */
boolean kpresent = HasProperty(cx, o, k);
/* step 10.c */
if (kpresent) {
Object elementk = Get(cx, o, k);
boolean same = StrictEqualityComparison(searchElement, elementk);
if (same) {
return k;
}
}
}
/* step 11 */
return -1;
}
/**
* 22.1.3.5 Array.prototype.every ( callbackfn [ , thisArg] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackfn
* the callback function
* @param thisArg
* the optional this-argument for the callback function
* @return {@code true} if every element matches
*/
@Function(name = "every", arity = 1)
public static Object every(ExecutionContext cx, Object thisValue, Object callbackfn,
Object thisArg) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(callbackfn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable callback = (Callable) callbackfn;
/* step 6 (omitted) */
/* steps 7-8 */
for (long k = 0; k < len; ++k) {
/* step 8.a */
long pk = k;
/* steps 8.b-c */
boolean kpresent = HasProperty(cx, o, pk);
/* step 8.d */
if (kpresent) {
Object kvalue = Get(cx, o, pk);
boolean testResult = ToBoolean(callback.call(cx, thisArg, kvalue, k, o));
if (!testResult) {
return false;
}
}
}
/* step 9 */
return true;
}
/**
* 22.1.3.23 Array.prototype.some ( callbackfn [ , thisArg ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackfn
* the callback function
* @param thisArg
* the optional this-argument for the callback function
* @return {@code true} if some elements match
*/
@Function(name = "some", arity = 1)
public static Object some(ExecutionContext cx, Object thisValue, Object callbackfn,
Object thisArg) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(callbackfn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable callback = (Callable) callbackfn;
/* step 6 (omitted) */
/* steps 7-8 */
for (long k = 0; k < len; ++k) {
long pk = k;
boolean kpresent = HasProperty(cx, o, pk);
if (kpresent) {
Object kvalue = Get(cx, o, pk);
boolean testResult = ToBoolean(callback.call(cx, thisArg, kvalue, k, o));
if (testResult) {
return true;
}
}
}
/* step 9 */
return false;
}
/**
* 22.1.3.10 Array.prototype.forEach ( callbackfn [ , thisArg ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackfn
* the callback function
* @param thisArg
* the optional this-argument for the callback function
* @return the undefined value
*/
@Function(name = "forEach", arity = 1)
public static Object forEach(ExecutionContext cx, Object thisValue, Object callbackfn,
Object thisArg) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(callbackfn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable callback = (Callable) callbackfn;
/* step 6 (omitted) */
/* steps 7-8 */
for (long k = 0; k < len; ++k) {
/* step 8.a */
long pk = k;
/* steps 8.b-c */
boolean kpresent = HasProperty(cx, o, pk);
/* step 8.d */
if (kpresent) {
Object kvalue = Get(cx, o, pk);
callback.call(cx, thisArg, kvalue, k, o);
}
}
/* step 9 */
return UNDEFINED;
}
/**
* 22.1.3.15 Array.prototype.map ( callbackfn [ , thisArg ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackfn
* the callback function
* @param thisArg
* the optional this-argument for the callback function
* @return the mapped value
*/
@Function(name = "map", arity = 1)
public static Object map(ExecutionContext cx, Object thisValue, Object callbackfn,
Object thisArg) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(callbackfn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable callback = (Callable) callbackfn;
/* step 6 (omitted) */
/* steps 7-8 */
ScriptObject a = ArraySpeciesCreate(cx, o, len);
/* steps 9-10 */
for (long k = 0; k < len; ++k) {
/* step 10.a */
long pk = k;
/* steps 10.b-c */
boolean kpresent = HasProperty(cx, o, pk);
/* step 10.d */
if (kpresent) {
Object kvalue = Get(cx, o, pk);
Object mappedValue = callback.call(cx, thisArg, kvalue, k, o);
CreateDataPropertyOrThrow(cx, a, pk, mappedValue);
}
}
/* step 11 */
return a;
}
/**
* 22.1.3.7 Array.prototype.filter ( callbackfn [ , thisArg ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackfn
* the callback function
* @param thisArg
* the optional this-argument for the callback function
* @return the filtered value
*/
@Function(name = "filter", arity = 1)
public static Object filter(ExecutionContext cx, Object thisValue, Object callbackfn,
Object thisArg) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(callbackfn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable callback = (Callable) callbackfn;
/* step 6 (omitted) */
/* steps 7-8 */
ScriptObject a = ArraySpeciesCreate(cx, o, 0);
/* steps 9-11 */
for (long k = 0, to = 0; k < len; ++k) {
/* step 11.a */
long pk = k;
/* steps 11.b-c */
boolean kpresent = HasProperty(cx, o, pk);
/* step 11.d */
if (kpresent) {
Object kvalue = Get(cx, o, pk);
boolean selected = ToBoolean(callback.call(cx, thisArg, kvalue, k, o));
if (selected) {
CreateDataPropertyOrThrow(cx, a, to, kvalue);
to += 1;
}
}
}
/* step 12 */
return a;
}
/**
* 22.1.3.18 Array.prototype.reduce ( callbackfn [ , initialValue ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackfn
* the callback function
* @param initialValue
* the initial value
* @return the reduced value
*/
@Function(name = "reduce", arity = 1)
public static Object reduce(ExecutionContext cx, Object thisValue, Object callbackfn,
@Optional(Optional.Default.NONE) Object initialValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(callbackfn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable callback = (Callable) callbackfn;
/* step 6 */
if (len == 0 && initialValue == null) {
throw newTypeError(cx, Messages.Key.ReduceInitialValue);
}
/* step 7 */
long k = 0;
/* steps 8-9 */
Object accumulator = null;
if (initialValue != null) {
/* step 8.a */
accumulator = initialValue;
} else {
/* step 9.a */
boolean kpresent = false;
/* step 9.b */
for (; !kpresent && k < len; ++k) {
long pk = k;
kpresent = HasProperty(cx, o, pk);
if (kpresent) {
accumulator = Get(cx, o, pk);
}
}
/* step 9.c */
if (!kpresent) {
throw newTypeError(cx, Messages.Key.ReduceInitialValue);
}
}
/* step 10 */
for (; k < len; ++k) {
/* step 10.a */
long pk = k;
/* steps 10.b-c */
boolean kpresent = HasProperty(cx, o, pk);
/* step 10.d */
if (kpresent) {
Object kvalue = Get(cx, o, pk);
accumulator = callback.call(cx, UNDEFINED, accumulator, kvalue, k, o);
}
}
/* step 11 */
return accumulator;
}
/**
* 22.1.3.19 Array.prototype.reduceRight ( callbackfn [ , initialValue ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackfn
* the callback function
* @param initialValue
* the initial value
* @return the reduced value
*/
@Function(name = "reduceRight", arity = 1)
public static Object reduceRight(ExecutionContext cx, Object thisValue, Object callbackfn,
@Optional(Optional.Default.NONE) Object initialValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(callbackfn)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable callback = (Callable) callbackfn;
/* step 6 */
if (len == 0 && initialValue == null) {
throw newTypeError(cx, Messages.Key.ReduceInitialValue);
}
/* step 7 */
long k = len - 1;
/* steps 8-9 */
Object accumulator = null;
if (initialValue != null) {
/* step 8.a */
accumulator = initialValue;
} else {
/* step 9.a */
boolean kpresent = false;
/* step 9.b */
for (; !kpresent && k >= 0; --k) {
long pk = k;
kpresent = HasProperty(cx, o, pk);
if (kpresent) {
accumulator = Get(cx, o, pk);
}
}
/* step 9.c */
if (!kpresent) {
throw newTypeError(cx, Messages.Key.ReduceInitialValue);
}
}
/* step 10 */
for (; k >= 0; --k) {
/* step 10.a */
long pk = k;
/* steps 10.b-c */
boolean kpresent = HasProperty(cx, o, pk);
/* step 10.d */
if (kpresent) {
Object kvalue = Get(cx, o, pk);
accumulator = callback.call(cx, UNDEFINED, accumulator, kvalue, k, o);
}
}
/* step 11 */
return accumulator;
}
/**
* 22.1.3.8 Array.prototype.find ( predicate [ , thisArg ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param predicate
* the predicate function
* @param thisArg
* the optional this-argument for the predicate function
* @return the result value
*/
@Function(name = "find", arity = 1)
public static Object find(ExecutionContext cx, Object thisValue, Object predicate,
Object thisArg) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(predicate)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable pred = (Callable) predicate;
/* step 6 (omitted) */
/* steps 7-8 */
for (long k = 0; k < len; ++k) {
/* step 8.a */
long pk = k;
/* steps 8.b-c */
Object kvalue = Get(cx, o, pk);
/* steps 8.d-e */
boolean testResult = ToBoolean(pred.call(cx, thisArg, kvalue, k, o));
/* step 8.f */
if (testResult) {
return kvalue;
}
}
/* step 9 */
return UNDEFINED;
}
/**
* 22.1.3.9 Array.prototype.findIndex ( predicate [ , thisArg ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param predicate
* the predicate function
* @param thisArg
* the optional this-argument for the predicate function
* @return the result index
*/
@Function(name = "findIndex", arity = 1)
public static Object findIndex(ExecutionContext cx, Object thisValue, Object predicate,
Object thisArg) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 5 */
if (!IsCallable(predicate)) {
throw newTypeError(cx, Messages.Key.NotCallable);
}
Callable pred = (Callable) predicate;
/* step 6 (omitted) */
/* steps 7-8 */
for (long k = 0; k < len; ++k) {
/* step 8.a */
long pk = k;
/* steps 8.b-c */
Object kvalue = Get(cx, o, pk);
/* steps 8.d-e */
boolean testResult = ToBoolean(pred.call(cx, thisArg, kvalue, k, o));
/* step 8.f */
if (testResult) {
return k;
}
}
/* step 9 */
return -1;
}
/**
* 22.1.3.4 Array.prototype.entries ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the entries iterator
*/
@Function(name = "entries", arity = 0)
public static Object entries(ExecutionContext cx, Object thisValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* step 3 */
return CreateArrayIterator(cx, o, ArrayIterationKind.KeyValue);
}
/**
* 22.1.3.13 Array.prototype.keys ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the keys iterator
*/
@Function(name = "keys", arity = 0)
public static Object keys(ExecutionContext cx, Object thisValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* step 3 */
return CreateArrayIterator(cx, o, ArrayIterationKind.Key);
}
/**
* 22.1.3.29 Array.prototype.values ( )<br>
* 22.1.3.30 Array.prototype [ @@iterator ] ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the values iterator
*/
@Function(name = "values", arity = 0, nativeId = ArrayPrototypeValues.class)
@AliasFunction(name = "[Symbol.iterator]", symbol = BuiltinSymbol.iterator)
public static Object values(ExecutionContext cx, Object thisValue) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* step 3 */
return CreateArrayIterator(cx, o, ArrayIterationKind.Value);
}
/**
* 22.1.3.31 Array.prototype [ @@unscopables ]
*
* @param cx
* the execution context
* @return the unscopables object
*/
@Value(name = "[Symbol.unscopables]", symbol = BuiltinSymbol.unscopables,
attributes = @Attributes(writable = false, enumerable = false, configurable = true))
public static Object unscopables(ExecutionContext cx) {
/* step 1 */
OrdinaryObject blackList = ObjectCreate(cx, (ScriptObject) null);
/* steps 2-8 */
boolean status = true;
status &= CreateDataProperty(cx, blackList, "copyWithin", true);
status &= CreateDataProperty(cx, blackList, "entries", true);
status &= CreateDataProperty(cx, blackList, "fill", true);
status &= CreateDataProperty(cx, blackList, "find", true);
status &= CreateDataProperty(cx, blackList, "findIndex", true);
if (cx.getRealm().isEnabled(CompatibilityOption.ArrayIncludes)) {
status &= CreateDataProperty(cx, blackList, "includes", true);
}
status &= CreateDataProperty(cx, blackList, "keys", true);
status &= CreateDataProperty(cx, blackList, "values", true);
/* step 9 */
assert status;
/* step 10 */
return blackList;
}
/**
* 22.1.3.6 Array.prototype.fill (value [ , start [ , end ] ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param value
* the fill value
* @param start
* the start index
* @param end
* the end index
* @return this typed array object
*/
@Function(name = "fill", arity = 1)
public static Object fill(ExecutionContext cx, Object thisValue, Object value,
Object start, Object end) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* steps 5-7 */
long k = ToArrayIndex(cx, start, len);
/* steps 8-10 */
long finall = Type.isUndefined(end) ? len : ToArrayIndex(cx, end, len);
/* step 11 */
for (; k < finall; ++k) {
/* step 11.a */
long pk = k;
/* steps 11.b-c */
Set(cx, o, pk, value, true);
}
/* step 12 */
return o;
}
/**
* 22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param target
* the target index
* @param start
* the start index
* @param end
* the end index
* @return this typed array object
*/
@Function(name = "copyWithin", arity = 2)
public static Object copyWithin(ExecutionContext cx, Object thisValue, Object target,
Object start, Object end) {
/* steps 1-2 */
ScriptObject o = ToObject(cx, thisValue);
/* steps 3-4 */
long len = ToLength(cx, Get(cx, o, "length"));
/* steps 5-7 */
long to = ToArrayIndex(cx, target, len);
/* steps 8-10 */
long from = ToArrayIndex(cx, start, len);
/* steps 11-13 */
long finall = Type.isUndefined(end) ? len : ToArrayIndex(cx, end, len);
/* step 14 */
long count = Math.min(finall - from, len - to);
/* steps 15-16 */
long direction;
if (from < to && to < from + count) {
direction = -1;
from = from + count - 1;
to = to + count - 1;
} else {
direction = 1;
}
/* step 17 */
for (; count > 0; --count) {
/* steps 17.a-b */
long fromKey = from;
long toKey = to;
/* steps 17.c-d */
boolean fromPresent = HasProperty(cx, o, fromKey);
/* steps 17.e-f */
if (fromPresent) {
Object fromVal = Get(cx, o, fromKey);
Set(cx, o, toKey, fromVal, true);
} else {
DeletePropertyOrThrow(cx, o, toKey);
}
/* steps 17.g-h */
from += direction;
to += direction;
}
/* step 18 */
return o;
}
}
/**
* Proposed ECMAScript 7 additions
*/
@CompatibilityExtension(CompatibilityOption.ArrayIncludes)
public enum AdditionalProperties {
;
/**
* Array.prototype.includes ( searchElement [ , fromIndex ] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param searchElement
* the search element
* @param fromIndex
* the optional start index
* @return the result index
*/
@Function(name = "includes", arity = 1)
public static Object includes(ExecutionContext cx, Object thisValue, Object searchElement,
Object fromIndex) {
/* step 1 */
ScriptObject o = ToObject(cx, thisValue);
/* step 2 */
long len = ToLength(cx, Get(cx, o, "length"));
/* step 3 */
if (len == 0) {
return false;
}
/* step 4 */
long n = (long) ToInteger(cx, fromIndex);
/* steps 5-6 */
long k;
if (n >= 0) {
k = n;
} else {
k = len + n;
if (k < 0) {
k = 0;
}
}
/* step 7 */
for (; k < len; ++k) {
/* step 7.a */
Object element = Get(cx, o, k);
/* step 10.b */
if (SameValueZero(searchElement, element)) {
return true;
}
}
/* step 8 */
return false;
}
}
}