package com.oracle.truffle.js.runtime.builtins;
import static com.oracle.truffle.js.runtime.objects.JSObjectUtil.putConstructorProperty;
import static com.oracle.truffle.js.runtime.objects.JSObjectUtil.putDataProperty;
import static com.oracle.truffle.js.runtime.objects.JSObjectUtil.putFunctionsFromContainer;
import java.util.ArrayList;
import java.util.List;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.js.builtins.ArrayFunctionBuiltins;
import com.oracle.truffle.js.builtins.ArrayPrototypeBuiltins;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.array.ArrayAllocationSite;
import com.oracle.truffle.js.runtime.array.ScriptArray;
import com.oracle.truffle.js.runtime.array.SparseArray;
import com.oracle.truffle.js.runtime.array.dyn.ConstantByteArray;
import com.oracle.truffle.js.runtime.array.dyn.ConstantDoubleArray;
import com.oracle.truffle.js.runtime.array.dyn.ConstantEmptyPrototypeArray;
import com.oracle.truffle.js.runtime.array.dyn.ConstantIntArray;
import com.oracle.truffle.js.runtime.array.dyn.ConstantObjectArray;
import com.oracle.truffle.js.runtime.array.dyn.HolesObjectArray;
import com.oracle.truffle.js.runtime.array.dyn.LazyArray;
import com.oracle.truffle.js.runtime.array.dyn.LazyRegexResultArray;
import com.oracle.truffle.js.runtime.array.dyn.LazyRegexResultIndicesArray;
import com.oracle.truffle.js.runtime.array.dyn.ZeroBasedDoubleArray;
import com.oracle.truffle.js.runtime.array.dyn.ZeroBasedIntArray;
import com.oracle.truffle.js.runtime.array.dyn.ZeroBasedJSObjectArray;
import com.oracle.truffle.js.runtime.array.dyn.ZeroBasedObjectArray;
import com.oracle.truffle.js.runtime.objects.JSAttributes;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.JSObjectUtil;
import com.oracle.truffle.js.runtime.objects.JSProperty;
import com.oracle.truffle.js.runtime.objects.JSShape;
import com.oracle.truffle.js.runtime.objects.PropertyProxy;
public final class JSArray extends JSAbstractArray implements JSConstructorFactory.Default.WithFunctionsAndSpecies, PrototypeSupplier {
public static final String CLASS_NAME = "Array";
public static final String PROTOTYPE_NAME = "Array.prototype";
public static final String ITERATOR_CLASS_NAME = "Array Iterator";
public static final String ITERATOR_PROTOTYPE_NAME = "Array Iterator.prototype";
public static final String ENTRIES = "entries";
public static final JSArray INSTANCE = new JSArray();
static final ArrayLengthProxyProperty ARRAY_LENGTH_PROPERTY_PROXY = new ArrayLengthProxyProperty();
public static final HiddenKey ARRAY_ITERATION_KIND_ID = new HiddenKey("ArrayIterationKind");
private JSArray() {
}
public static DynamicObject createConstant(JSContext context, Object[] elements) {
return create(context, ScriptArray.createConstantArray(elements), elements, elements.length);
}
public static DynamicObject createEmpty(JSContext context, int length) {
if (length < 0) {
throw Errors.createRangeErrorInvalidArrayLength();
}
return createEmptyChecked(context, length);
}
private static DynamicObject createEmptyChecked(JSContext context, int length) {
return createConstantEmptyArray(context, length);
}
public static DynamicObject createEmpty(JSContext context, long length) {
if (!JSRuntime.isValidArrayLength(length)) {
throw Errors.createRangeErrorInvalidArrayLength();
} else if (length > Integer.MAX_VALUE) {
return createSparseArray(context, length);
}
return createEmptyChecked(context, length);
}
public static DynamicObject createEmptyChecked(JSContext context, long length) {
assert 0 <= length && length <= Integer.MAX_VALUE;
return createConstantEmptyArray(context, (int) length);
}
public static DynamicObject createEmptyZeroLength(JSContext context) {
return createConstantEmptyArray(context);
}
public static DynamicObject create(JSContext context, ScriptArray arrayType, Object array, long length) {
return create(context, arrayType, array, length, 0);
}
public static DynamicObject create(JSContext context, ScriptArray arrayType, Object array, long length, int usedLength) {
return create(context, arrayType, array, length, usedLength, 0, 0);
}
public static DynamicObject create(JSContext context, ScriptArray arrayType, Object array, long length, int usedLength, int indexOffset, int arrayOffset) {
return create(context, arrayType, array, length, usedLength, indexOffset, arrayOffset, 0);
}
public static DynamicObject create(JSContext context, ScriptArray arrayType, Object array, long length, int usedLength, int indexOffset, int arrayOffset, int holeCount) {
return createImpl(context, arrayType, array, null, length, usedLength, indexOffset, arrayOffset, holeCount);
}
public static DynamicObject create(JSContext context, ScriptArray arrayType, Object array, ArrayAllocationSite site, long length, int usedLength, int indexOffset, int arrayOffset, int holeCount) {
return createImpl(context, arrayType, array, site, length, usedLength, indexOffset, arrayOffset, holeCount);
}
private static DynamicObject createImpl(JSContext context, ScriptArray arrayType, Object array, ArrayAllocationSite site, long length, int usedLength, int indexOffset,
int arrayOffset, int holeCount) {
assert JSRuntime.isRepresentableAsUnsignedInt(length);
JSRealm realm = context.getRealm();
JSObjectFactory factory = context.getArrayFactory();
DynamicObject obj = JSArrayObject.create(factory.getShape(realm), arrayType, array, site, length, usedLength, indexOffset, arrayOffset, holeCount);
factory.initProto(obj, realm);
assert isJSArray(obj);
return context.trackAllocation(obj);
}
public static boolean isJSArray(Object obj) {
return obj instanceof JSArrayObject;
}
public static boolean isJSFastArray(Object obj) {
return isJSArray(obj) && isJSFastArray((JSArrayObject) obj);
}
public static boolean isJSFastArray(DynamicObject obj) {
return isInstance(obj, INSTANCE);
}
@Override
public String getClassName() {
return CLASS_NAME;
}
@Override
public String getClassName(DynamicObject object) {
return getClassName();
}
@Override
public DynamicObject createPrototype(JSRealm realm, DynamicObject ctor) {
JSContext ctx = realm.getContext();
Shape protoShape = JSShape.createPrototypeShape(ctx, INSTANCE, realm.getObjectPrototype());
DynamicObject arrayPrototype = JSArrayObject.createEmpty(protoShape, ConstantEmptyPrototypeArray.createConstantEmptyPrototypeArray());
JSObjectUtil.setOrVerifyPrototype(ctx, arrayPrototype, realm.getObjectPrototype());
putConstructorProperty(ctx, arrayPrototype, ctor);
putFunctionsFromContainer(realm, arrayPrototype, ArrayPrototypeBuiltins.BUILTINS);
JSObjectUtil.putProxyProperty(arrayPrototype, LENGTH, ARRAY_LENGTH_PROPERTY_PROXY, JSAttributes.notConfigurableNotEnumerableWritable());
if (ctx.getEcmaScriptVersion() >= 6) {
putDataProperty(ctx, arrayPrototype, Symbol.SYMBOL_ITERATOR, JSDynamicObject.getOrNull(arrayPrototype, "values"), JSAttributes.getDefaultNotEnumerable());
putDataProperty(ctx, arrayPrototype, Symbol.SYMBOL_UNSCOPABLES, createUnscopables(ctx, unscopableNameList(ctx)), JSAttributes.configurableNotEnumerableNotWritable());
}
return arrayPrototype;
}
private static List<String> unscopableNameList(JSContext context) {
List<String> names = new ArrayList<>();
if (context.getEcmaScriptVersion() >= JSConfig.ECMAScript2022) {
names.add("at");
}
names.add("copyWithin");
names.add(JSArray.ENTRIES);
names.add("fill");
names.add("find");
names.add("findIndex");
if (context.getEcmaScriptVersion() >= JSConfig.ECMAScript2019) {
names.add("flat");
names.add("flatMap");
}
if (context.getEcmaScriptVersion() >= 7) {
names.add("includes");
}
names.add("keys");
names.add("values");
return names;
}
private static DynamicObject createUnscopables(JSContext context, List<String> unscopableNames) {
DynamicObject unscopables = JSOrdinary.createWithNullPrototypeInit(context);
for (String name : unscopableNames) {
putDataProperty(context, unscopables, name, true, JSAttributes.getDefault());
}
return unscopables;
}
@Override
public Shape makeInitialShape(JSContext context, DynamicObject prototype) {
Shape initialShape = JSObjectUtil.getProtoChildShape(prototype, INSTANCE, context);
initialShape = Shape.newBuilder(initialShape).addConstantProperty(LENGTH, ARRAY_LENGTH_PROPERTY_PROXY, JSAttributes.notConfigurableNotEnumerableWritable() | JSProperty.PROXY).build();
return initialShape;
}
@Override
public List<Object> getOwnPropertyKeys(DynamicObject thisObj, boolean strings, boolean symbols) {
return ownPropertyKeysFastArray(thisObj, strings, symbols);
}
public static JSConstructor createConstructor(JSRealm realm) {
return INSTANCE.createConstructorAndPrototype(realm, ArrayFunctionBuiltins.BUILTINS);
}
public static class ArrayLengthProxyProperty implements PropertyProxy {
@Override
public Object get(DynamicObject store) {
assert isJSArray(store);
long length = JSArray.INSTANCE.getLength(store);
return (double) length;
}
@Override
public boolean set(DynamicObject store, Object value) {
assert isJSArray(store);
return JSArray.setLength(store, value);
}
}
@TruffleBoundary
public static boolean setLength(DynamicObject store, Object value) {
long arrLength = 0;
if (value instanceof Integer && (int) value >= 0) {
arrLength = (int) value;
} else {
arrLength = toArrayLengthOrRangeError(value);
}
assert arrLength >= 0;
return !JSAbstractArray.arrayGetArrayType(store).isLengthNotWritable() && ((JSAbstractArray) JSObject.getJSClass(store)).setLength(store, arrLength, false);
}
public static DynamicObject createConstantEmptyArray(JSContext context, int capacity) {
ScriptArray arrayType = ScriptArray.createConstantEmptyArray();
return create(context, arrayType, ScriptArray.EMPTY_OBJECT_ARRAY, capacity);
}
public static DynamicObject createConstantEmptyArray(JSContext context) {
return createConstantEmptyArray(context, 0);
}
public static DynamicObject createConstantEmptyArray(JSContext context, ArrayAllocationSite site) {
return createConstantEmptyArray(context, site, 0);
}
public static DynamicObject createConstantEmptyArray(JSContext context, ArrayAllocationSite site, int capacity) {
ScriptArray arrayType = ScriptArray.createConstantEmptyArray();
return create(context, arrayType, ScriptArray.EMPTY_OBJECT_ARRAY, site, capacity, 0, 0, 0, 0);
}
public static DynamicObject createConstantByteArray(JSContext context, byte[] byteArray) {
ScriptArray arrayType = ConstantByteArray.createConstantByteArray();
return create(context, arrayType, byteArray, byteArray.length);
}
public static DynamicObject createConstantIntArray(JSContext context, int[] intArray) {
ScriptArray arrayType = ConstantIntArray.createConstantIntArray();
return create(context, arrayType, intArray, intArray.length);
}
public static DynamicObject createConstantDoubleArray(JSContext context, double[] doubleArray) {
ScriptArray arrayType = ConstantDoubleArray.createConstantDoubleArray();
return create(context, arrayType, doubleArray, doubleArray.length);
}
public static DynamicObject createConstantObjectArray(JSContext context, Object[] objectArray) {
ScriptArray arrayType = ConstantObjectArray.createConstantObjectArray();
return create(context, arrayType, objectArray, objectArray.length);
}
public static DynamicObject createZeroBasedHolesObjectArray(JSContext context, Object[] objectArray, int usedLength, int arrayOffset, int holeCount) {
return create(context, HolesObjectArray.createHolesObjectArray(), objectArray, objectArray.length, usedLength, 0, arrayOffset, holeCount);
}
public static DynamicObject createZeroBasedIntArray(JSContext context, int[] intArray) {
return create(context, ZeroBasedIntArray.createZeroBasedIntArray(), intArray, intArray.length, intArray.length, 0, 0);
}
public static DynamicObject createZeroBasedDoubleArray(JSContext context, double[] doubleArray) {
return create(context, ZeroBasedDoubleArray.createZeroBasedDoubleArray(), doubleArray, doubleArray.length, doubleArray.length, 0, 0);
}
public static DynamicObject createZeroBasedObjectArray(JSContext context, Object[] objectArray) {
return create(context, ZeroBasedObjectArray.createZeroBasedObjectArray(), objectArray, objectArray.length, objectArray.length, 0, 0);
}
public static DynamicObject createZeroBasedJSObjectArray(JSContext context, DynamicObject[] objectArray) {
return create(context, ZeroBasedJSObjectArray.createZeroBasedJSObjectArray(), objectArray, objectArray.length, objectArray.length, 0, 0);
}
public static DynamicObject createSparseArray(JSContext context, long length) {
return create(context, SparseArray.createSparseArray(), SparseArray.createArrayMap(), length);
}
public static DynamicObject createLazyRegexArray(JSContext context, int length) {
assert JSRuntime.isRepresentableAsUnsignedInt(length);
Object[] array = new Object[length];
return create(context, LazyRegexResultArray.createLazyRegexResultArray(), array, length);
}
public static DynamicObject createLazyRegexArray(JSContext context, int length, Object regexResult, String input, DynamicObject groups, DynamicObject indicesGroups) {
assert JSRuntime.isRepresentableAsUnsignedInt(length);
DynamicObject obj = createLazyRegexArray(context, length);
JSObjectUtil.putHiddenProperty(obj, JSArray.LAZY_REGEX_RESULT_ID, regexResult);
JSObjectUtil.putHiddenProperty(obj, JSArray.LAZY_REGEX_ORIGINAL_INPUT_ID, input);
JSObjectUtil.putProxyProperty(obj, JSRegExp.INDEX, JSRegExp.LAZY_INDEX_PROXY, JSAttributes.getDefault());
JSObjectUtil.putDataProperty(context, obj, JSRegExp.INPUT, input, JSAttributes.getDefault());
JSObjectUtil.putDataProperty(context, obj, JSRegExp.GROUPS, groups, JSAttributes.getDefault());
if (context.isOptionRegexpMatchIndices()) {
DynamicObject indices = createLazyRegexIndicesArray(context, length, regexResult, indicesGroups);
JSObjectUtil.putDataProperty(context, obj, JSRegExp.INDICES, indices, JSAttributes.getDefault());
}
assert isJSArray(obj);
return obj;
}
public static DynamicObject createLazyRegexIndicesArray(JSContext context, int length) {
assert JSRuntime.isRepresentableAsUnsignedInt(length);
Object[] array = new Object[length];
return create(context, LazyRegexResultIndicesArray.createLazyRegexResultIndicesArray(), array, length);
}
private static DynamicObject createLazyRegexIndicesArray(JSContext context, int length, Object regexResult, DynamicObject indicesGroups) {
assert JSRuntime.isRepresentableAsUnsignedInt(length);
Object[] array = new Object[length];
DynamicObject obj = create(context, LazyRegexResultIndicesArray.createLazyRegexResultIndicesArray(), array, length);
JSObjectUtil.putHiddenProperty(obj, JSArray.LAZY_REGEX_RESULT_ID, regexResult);
JSObjectUtil.putDataProperty(context, obj, JSRegExp.GROUPS, indicesGroups, JSAttributes.getDefault());
assert isJSArray(obj);
return obj;
}
public static DynamicObject createLazyArray(JSContext context, List<?> list, int size) {
assert list.size() == size;
return create(context, LazyArray.createLazyArray(), list, size);
}
@Override
public DynamicObject getIntrinsicDefaultProto(JSRealm realm) {
return realm.getArrayPrototype();
}
}