#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue using System.Collections; using Jint.Native.Function; using Jint.Native.Iterator; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Native.Array; public sealed class ArrayConstructor : Constructor { private static readonly JsString _functionName = new JsString("Array"); internal ArrayConstructor( Engine engine, Realm realm, FunctionPrototype functionPrototype, ObjectPrototype objectPrototype) : base(engine, realm, _functionName) { _prototype = functionPrototype; PrototypeObject = new ArrayPrototype(engine, realm, this, objectPrototype); _length = new PropertyDescriptor(1, PropertyFlag.Configurable); _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } public ArrayPrototype PrototypeObject { get; } protected override void Initialize() { var properties = new PropertyDictionary(3, checkExistingKeys: false) { ["from"] = new PropertyDescriptor(new PropertyDescriptor(new ClrFunction(Engine, "from", From, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable)), ["isArray"] = new PropertyDescriptor(new PropertyDescriptor(new ClrFunction(Engine, "isArray", IsArray, 1), PropertyFlag.NonEnumerable)), ["of"] = new PropertyDescriptor(new PropertyDescriptor(new ClrFunction(Engine, "of", Of, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable)) }; SetProperties(properties); var symbols = new SymbolDictionary(1) { [GlobalSymbolRegistry.Species] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get [Symbol.species]", Species, 0, PropertyFlag.Configurable), set: Undefined, PropertyFlag.Configurable), }; SetSymbols(symbols); } /// /// https://tc39.es/ecma262/#sec-array.from /// private JsValue From(JsValue thisObject, JsCallArguments arguments) { var items = arguments.At(0); var mapFunction = arguments.At(1); var callable = !mapFunction.IsUndefined() ? GetCallable(mapFunction) : null; var thisArg = arguments.At(2); if (items.IsNullOrUndefined()) { Throw.TypeError(_realm, "Cannot convert undefined or null to object"); } var usingIterator = GetMethod(_realm, items, GlobalSymbolRegistry.Iterator); if (usingIterator is not null) { ObjectInstance instance; if (!ReferenceEquals(this, thisObject) && thisObject is IConstructor constructor) { instance = constructor.Construct([], thisObject); } else { instance = ArrayCreate(0); } var iterator = items.GetIterator(_realm, method: usingIterator); var protocol = new ArrayProtocol(_engine, thisArg, instance, iterator, callable); protocol.Execute(); return instance; } if (items is IObjectWrapper { Target: IEnumerable enumerable }) { return ConstructArrayFromIEnumerable(enumerable); } var source = ArrayOperations.For(_realm, items, forWrite: false); return ConstructArrayFromArrayLike(thisObject, source, callable, thisArg); } private ObjectInstance ConstructArrayFromArrayLike( JsValue thisObj, ArrayOperations source, ICallable? callable, JsValue thisArg) { var length = source.GetLength(); ObjectInstance a; if (!ReferenceEquals(thisObj, this) && thisObj is IConstructor constructor) { var argumentsList = new JsValue[] { length }; a = Construct(constructor, argumentsList); } else { a = ArrayCreate(length); } var args = callable is not null ? _engine._jsValueArrayPool.RentArray(2) : null; var target = ArrayOperations.For(a, forWrite: true); uint n = 0; for (uint i = 0; i < length; i++) { var value = source.Get(i); if (callable is not null) { args![0] = value; args[1] = i; value = callable.Call(thisArg, args); // function can alter data length = source.GetLength(); } target.CreateDataPropertyOrThrow(i, value); n++; } if (callable is not null) { _engine._jsValueArrayPool.ReturnArray(args!); } target.SetLength(length); return a; } private sealed class ArrayProtocol : IteratorProtocol { private readonly JsValue _thisArg; private readonly ArrayOperations _instance; private readonly ICallable? _callable; private long _index = -1; public ArrayProtocol( Engine engine, JsValue thisArg, ObjectInstance instance, IteratorInstance iterator, ICallable? callable) : base(engine, iterator, 2) { _thisArg = thisArg; _instance = ArrayOperations.For(instance, forWrite: true); _callable = callable; } protected override void ProcessItem(JsValue[] arguments, JsValue currentValue) { _index++; JsValue jsValue; if (_callable is not null) { arguments[0] = currentValue; arguments[1] = _index; jsValue = _callable.Call(_thisArg, arguments); } else { jsValue = currentValue; } _instance.CreateDataPropertyOrThrow((ulong) _index, jsValue); } protected override void IterationEnd() { _instance.SetLength((ulong) (_index + 1)); } } private JsValue Of(JsValue thisObject, JsCallArguments arguments) { var len = arguments.Length; ObjectInstance a; if (thisObject.IsConstructor) { a = ((IConstructor) thisObject).Construct([len], thisObject); } else { a = _realm.Intrinsics.Array.Construct(len); } if (a is JsArray ai) { // faster for real arrays for (uint k = 0; k < arguments.Length; k++) { var kValue = arguments[(int) k]; ai.SetIndexValue(k, kValue, updateLength: k == arguments.Length - 1); } } else { // slower version for (uint k = 0; k < arguments.Length; k++) { var kValue = arguments[(int) k]; var key = JsString.Create(k); a.CreateDataPropertyOrThrow(key, kValue); } a.Set(CommonProperties.Length, len, true); } return a; } private static JsValue Species(JsValue thisObject, JsCallArguments arguments) { return thisObject; } private static JsValue IsArray(JsValue thisObject, JsCallArguments arguments) { var o = arguments.At(0); return IsArray(o); } private static JsValue IsArray(JsValue o) { if (!(o is ObjectInstance oi)) { return JsBoolean.False; } return oi.IsArray(); } protected internal override JsValue Call(JsValue thisObject, JsCallArguments arguments) { return Construct(arguments, thisObject); } public JsArray Construct(JsCallArguments arguments) { return (JsArray) Construct(arguments, this); } public override ObjectInstance Construct(JsCallArguments arguments, JsValue newTarget) { if (newTarget.IsUndefined()) { newTarget = this; } var proto = _realm.Intrinsics.Function.GetPrototypeFromConstructor( newTarget, static intrinsics => intrinsics.Array.PrototypeObject); // check if we can figure out good size var capacity = arguments.Length > 0 ? (ulong) arguments.Length : 0; if (arguments.Length == 1 && arguments[0].IsNumber()) { var number = ((JsNumber) arguments[0])._value; ValidateLength(number); capacity = (ulong) number; } return Construct(arguments, capacity, proto); } public JsArray Construct(int capacity) { return Construct([], (uint) capacity); } public JsArray Construct(uint capacity) { return Construct([], capacity); } public JsArray Construct(JsCallArguments arguments, uint capacity) { return Construct(arguments, capacity, PrototypeObject); } private JsArray Construct(JsCallArguments arguments, ulong capacity, ObjectInstance prototypeObject) { JsArray instance; if (arguments.Length == 1) { switch (arguments[0]) { case JsNumber number: ValidateLength(number._value); instance = ArrayCreate((ulong) number._value, prototypeObject); break; case IObjectWrapper objectWrapper: instance = objectWrapper.Target is IEnumerable enumerable ? ConstructArrayFromIEnumerable(enumerable) : ArrayCreate(0, prototypeObject); break; case JsArray array: // direct copy instance = (JsArray) ConstructArrayFromArrayLike(Undefined, ArrayOperations.For(array, forWrite: false), callable: null, this); break; default: instance = ArrayCreate(capacity, prototypeObject); instance._length!._value = JsNumber.PositiveZero; instance.Push(arguments); break; } } else { instance = ArrayCreate((ulong) arguments.Length, prototypeObject); instance._length!._value = JsNumber.PositiveZero; if (arguments.Length > 0) { instance.Push(arguments); } } return instance; } /// /// https://tc39.es/ecma262/#sec-arraycreate /// internal JsArray ArrayCreate(ulong length, ObjectInstance? proto = null) { if (length > ArrayOperations.MaxArrayLength) { Throw.RangeError(_realm, "Invalid array length " + length); } proto ??= PrototypeObject; var instance = new JsArray(Engine, (uint) length, (uint) length) { _prototype = proto }; return instance; } private JsArray ConstructArrayFromIEnumerable(IEnumerable enumerable) { var jsArray = Construct(Arguments.Empty); var tempArray = _engine._jsValueArrayPool.RentArray(1); foreach (var item in enumerable) { var jsItem = FromObject(Engine, item); tempArray[0] = jsItem; _realm.Intrinsics.Array.PrototypeObject.Push(jsArray, tempArray); } _engine._jsValueArrayPool.ReturnArray(tempArray); return jsArray; } public JsArray ConstructFast(JsValue[] contents) { var array = new JsValue[contents.Length]; System.Array.Copy(contents, array, contents.Length); return new JsArray(_engine, array); } internal JsArray ConstructFast(List contents) { var array = new JsValue[contents.Count]; contents.CopyTo(array); return new JsArray(_engine, array); } /// /// https://tc39.es/ecma262/#sec-arrayspeciescreate /// internal ObjectInstance ArraySpeciesCreate(ObjectInstance originalArray, ulong length) { var isArray = originalArray.IsArray(); if (!isArray) { return ArrayCreate(length); } var c = originalArray.Get(CommonProperties.Constructor); if (c.IsConstructor) { var thisRealm = _engine.ExecutionContext.Realm; var realmC = GetFunctionRealm(c); if (!ReferenceEquals(thisRealm, realmC)) { if (ReferenceEquals(c, realmC.Intrinsics.Array)) { c = Undefined; } } } if (c.IsObject()) { c = c.Get(GlobalSymbolRegistry.Species); if (c.IsNull()) { c = Undefined; } } if (c.IsUndefined()) { return ArrayCreate(length); } if (!c.IsConstructor) { Throw.TypeError(_realm, $"{c} is not a constructor"); } return ((IConstructor) c).Construct([JsNumber.Create(length)], c); } internal JsArray CreateArrayFromList(List values) where T : JsValue { var jsArray = ArrayCreate((uint) values.Count); var index = 0; for (; index < values.Count; index++) { var item = values[index]; jsArray.SetIndexValue((uint) index, item, false); } jsArray.SetLength((uint) index); return jsArray; } internal JsArray CreateArrayFromList(T[] values) where T : JsValue { var jsArray = ArrayCreate((uint) values.Length); var index = 0; for (; index < values.Length; index++) { var item = values[index]; jsArray.SetIndexValue((uint) index, item, false); } jsArray.SetLength((uint) index); return jsArray; } private void ValidateLength(double length) { if (length < 0 || length > ArrayOperations.MaxArrayLikeLength || ((long) length) != length) { Throw.RangeError(_realm, "Invalid array length"); } } }