#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue using System.Linq; using System.Text; using Jint.Native.Array; using Jint.Native.ArrayBuffer; using Jint.Native.Iterator; using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors.Specialized; using Jint.Runtime.Interop; namespace Jint.Native.TypedArray; /// /// https://tc39.es/ecma262/#sec-properties-of-the-%typedarrayprototype%-object /// internal sealed class IntrinsicTypedArrayPrototype : Prototype { private readonly IntrinsicTypedArrayConstructor _constructor; private ClrFunction? _originalIteratorFunction; internal IntrinsicTypedArrayPrototype( Engine engine, ObjectInstance objectPrototype, IntrinsicTypedArrayConstructor constructor) : base(engine, engine.Realm) { _prototype = objectPrototype; _constructor = constructor; } protected override void Initialize() { const PropertyFlag LengthFlags = PropertyFlag.Configurable; const PropertyFlag PropertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable; var properties = new PropertyDictionary(36, false) { ["at"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "at", prototype.At, 1, PropertyFlag.Configurable), PropertyFlags), ["buffer"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get buffer", Buffer, 0, LengthFlags), Undefined, PropertyFlag.Configurable), ["byteLength"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get byteLength", ByteLength, 0, LengthFlags), Undefined, PropertyFlag.Configurable), ["byteOffset"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get byteOffset", ByteOffset, 0, LengthFlags), Undefined, PropertyFlag.Configurable), ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable), ["copyWithin"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "copyWithin", prototype.CopyWithin, 2, PropertyFlag.Configurable), PropertyFlags), ["entries"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "entries", prototype.Entries, 0, PropertyFlag.Configurable), PropertyFlags), ["every"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "every", prototype.Every, 1, PropertyFlag.Configurable), PropertyFlags), ["fill"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "fill", prototype.Fill, 1, PropertyFlag.Configurable), PropertyFlags), ["filter"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "filter", prototype.Filter, 1, PropertyFlag.Configurable), PropertyFlags), ["find"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "find", prototype.Find, 1, PropertyFlag.Configurable), PropertyFlags), ["findIndex"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "findIndex", prototype.FindIndex, 1, PropertyFlag.Configurable), PropertyFlags), ["findLast"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "findLast", prototype.FindLast, 1, PropertyFlag.Configurable), PropertyFlags), ["findLastIndex"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "findLastIndex", prototype.FindLastIndex, 1, PropertyFlag.Configurable), PropertyFlags), ["forEach"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "forEach", prototype.ForEach, 1, PropertyFlag.Configurable), PropertyFlags), ["includes"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "includes", prototype.Includes, 1, PropertyFlag.Configurable), PropertyFlags), ["indexOf"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "indexOf", prototype.IndexOf, 1, PropertyFlag.Configurable), PropertyFlags), ["join"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "join", prototype.Join, 1, PropertyFlag.Configurable), PropertyFlags), ["keys"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "keys", prototype.Keys, 0, PropertyFlag.Configurable), PropertyFlags), ["lastIndexOf"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "lastIndexOf", prototype.LastIndexOf, 1, PropertyFlag.Configurable), PropertyFlags), ["length"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get length", GetLength, 0, LengthFlags), Undefined, PropertyFlag.Configurable), ["map"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "map", prototype.Map, 1, PropertyFlag.Configurable), PropertyFlags), ["reduce"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "reduce", prototype.Reduce, 1, PropertyFlag.Configurable), PropertyFlags), ["reduceRight"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "reduceRight", prototype.ReduceRight, 1, PropertyFlag.Configurable), PropertyFlags), ["reverse"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "reverse", prototype.Reverse, 0, PropertyFlag.Configurable), PropertyFlags), ["set"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "set", prototype.Set, 1, PropertyFlag.Configurable), PropertyFlags), ["slice"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "slice", prototype.Slice, 2, PropertyFlag.Configurable), PropertyFlags), ["some"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "some", prototype.Some, 1, PropertyFlag.Configurable), PropertyFlags), ["sort"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "sort", prototype.Sort, 1, PropertyFlag.Configurable), PropertyFlags), ["subarray"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "subarray", prototype.Subarray, 2, PropertyFlag.Configurable), PropertyFlags), ["toLocaleString"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toLocaleString", prototype.ToLocaleString, 0, PropertyFlag.Configurable), PropertyFlags), ["toReversed"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toReversed", prototype.ToReversed, 0, PropertyFlag.Configurable), PropertyFlags), ["toSorted"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toSorted", prototype.ToSorted, 1, PropertyFlag.Configurable), PropertyFlags), ["toString"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toLocaleString", prototype._realm.Intrinsics.Array.PrototypeObject.ToString, 0, PropertyFlag.Configurable), PropertyFlags), ["values"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "values", prototype.Values, 0, PropertyFlag.Configurable), PropertyFlags), ["with"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "with", prototype.With, 2, PropertyFlag.Configurable), PropertyFlags), }; SetProperties(properties); _originalIteratorFunction = new ClrFunction(_engine, "iterator", Values, 1); var symbols = new SymbolDictionary(2) { [GlobalSymbolRegistry.Iterator] = new(_originalIteratorFunction, PropertyFlags), [GlobalSymbolRegistry.ToStringTag] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get [Symbol.toStringTag]", ToStringTag, 0, PropertyFlag.Configurable), Undefined, PropertyFlag.Configurable) }; SetSymbols(symbols); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer /// private JsValue Buffer(JsValue thisObject, JsCallArguments arguments) { var o = thisObject as JsTypedArray; if (o is null) { Throw.TypeError(_realm); } return o._viewedArrayBuffer; } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength /// private JsValue ByteLength(JsValue thisObject, JsCallArguments arguments) { var o = thisObject as JsTypedArray; if (o is null) { Throw.TypeError(_realm); } var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); return JsNumber.Create(taRecord.TypedArrayByteLength); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset /// private JsValue ByteOffset(JsValue thisObject, JsCallArguments arguments) { var o = thisObject as JsTypedArray; if (o is null) { Throw.TypeError(_realm); } var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { return JsNumber.PositiveZero; } return JsNumber.Create(o._byteOffset); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length /// private JsValue GetLength(JsValue thisObject, JsCallArguments arguments) { var o = thisObject as JsTypedArray; if (o is null) { Throw.TypeError(_realm); } var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { return JsNumber.PositiveZero; } return JsNumber.Create(taRecord.TypedArrayLength); } internal readonly record struct TypedArrayWithBufferWitnessRecord(JsTypedArray Object, int CachedBufferByteLength) { /// /// https://tc39.es/ecma262/#sec-istypedarrayoutofbounds /// public bool IsTypedArrayOutOfBounds { get { var o = Object; var bufferByteLength = CachedBufferByteLength; if (bufferByteLength == -1) { return true; } var byteOffsetStart = o._byteOffset; long byteOffsetEnd; if (o._arrayLength == JsTypedArray.LengthAuto) { byteOffsetEnd = bufferByteLength; } else { var elementSize = o._arrayElementType.GetElementSize(); byteOffsetEnd = byteOffsetStart + o._arrayLength * elementSize; } if (byteOffsetStart > bufferByteLength || byteOffsetEnd > bufferByteLength) { return true; } return false; } } /// /// https://tc39.es/ecma262/#sec-typedarraylength /// public uint TypedArrayLength { get { var o = Object; if (o._arrayLength != JsTypedArray.LengthAuto) { return o._arrayLength; } var byteOffset = o._byteOffset; var elementSize = o._arrayElementType.GetElementSize(); var byteLength = (double) CachedBufferByteLength; var floor = System.Math.Floor((byteLength - byteOffset) / elementSize); return floor < 0 ? 0 : (uint) floor; } } /// /// https://tc39.es/ecma262/#sec-typedarraybytelength /// public uint TypedArrayByteLength { get { if (IsTypedArrayOutOfBounds) { return 0; } var length = TypedArrayLength; if (length == 0) { return 0; } var o = Object; if (o._byteLength != JsTypedArray.LengthAuto) { return o._byteLength; } return length * o._arrayElementType.GetElementSize(); } } } internal static TypedArrayWithBufferWitnessRecord MakeTypedArrayWithBufferWitnessRecord(JsTypedArray obj, ArrayBufferOrder order) { var buffer = obj._viewedArrayBuffer; var byteLength = buffer.IsDetachedBuffer ? -1 : ArrayBufferByteLength(buffer, order); return new TypedArrayWithBufferWitnessRecord(obj, byteLength); } /// /// https://tc39.es/ecma262/#sec-arraybufferbytelength /// internal static int ArrayBufferByteLength(JsArrayBuffer arrayBuffer, ArrayBufferOrder order) { if (arrayBuffer.IsSharedArrayBuffer && arrayBuffer.ArrayBufferByteLength > 0) { // a. Let bufferByteLengthBlock be arrayBuffer.[[ArrayBufferByteLengthData]]. // b. Let rawLength be GetRawBytesFromSharedBlock(bufferByteLengthBlock, 0, BIGUINT64, true, order). // c. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. // d. Return ℝ(RawBytesToNumeric(BIGUINT64, rawLength, isLittleEndian)). } return arrayBuffer.ArrayBufferByteLength; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin /// private JsValue CopyWithin(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var target = arguments.At(0); var start = arguments.At(1); var end = arguments.At(2); var relativeTarget = TypeConverter.ToIntegerOrInfinity(target); long to; if (double.IsNegativeInfinity(relativeTarget)) { to = 0; } else if (relativeTarget < 0) { to = (long) System.Math.Max(len + relativeTarget, 0); } else { to = (long) System.Math.Min(relativeTarget, len); } var relativeStart = TypeConverter.ToIntegerOrInfinity(start); long from; if (double.IsNegativeInfinity(relativeStart)) { from = 0; } else if (relativeStart < 0) { from = (long) System.Math.Max(len + relativeStart, 0); } else { from = (long) System.Math.Min(relativeStart, len); } var relativeEnd = end.IsUndefined() ? len : TypeConverter.ToIntegerOrInfinity(end); long final; if (double.IsNegativeInfinity(relativeEnd)) { final = 0; } else if (relativeEnd < 0) { final = (long) System.Math.Max(len + relativeEnd, 0); } else { final = (long) System.Math.Min(relativeEnd, len); } var count = System.Math.Min(final - from, len - to); if (count > 0) { var buffer = o._viewedArrayBuffer; buffer.AssertNotDetached(); taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm, "TypedArray is out of bounds"); } len = taRecord.TypedArrayLength; var elementSize = o._arrayElementType.GetElementSize(); var byteOffset = o._byteOffset; var bufferByteLimit = len * elementSize + byteOffset; var toByteIndex = to * elementSize + byteOffset; var fromByteIndex = from * elementSize + byteOffset; var countBytes = count * elementSize; int direction; if (fromByteIndex < toByteIndex && toByteIndex < fromByteIndex + countBytes) { direction = -1; fromByteIndex = fromByteIndex + countBytes - 1; toByteIndex = toByteIndex + countBytes - 1; } else { direction = 1; } while (countBytes > 0) { if (fromByteIndex < bufferByteLimit && toByteIndex < bufferByteLimit) { var value = buffer.GetValueFromBuffer((int) fromByteIndex, TypedArrayElementType.Uint8, isTypedArray: true, ArrayBufferOrder.Unordered); buffer.SetValueInBuffer((int) toByteIndex, TypedArrayElementType.Uint8, value, isTypedArray: true, ArrayBufferOrder.Unordered); fromByteIndex += direction; toByteIndex += direction; countBytes--; } else { countBytes = 0; } } } return o; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries /// private JsValue Entries(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.KeyAndValue); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.every /// private JsValue Every(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; if (len == 0) { return JsBoolean.True; } var predicate = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { args[0] = o[k]; args[1] = k; if (!TypeConverter.ToBoolean(predicate.Call(thisArg, args))) { return JsBoolean.False; } } _engine._jsValueArrayPool.ReturnArray(args); return JsBoolean.True; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill /// private JsValue Fill(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var jsValue = arguments.At(0); var start = arguments.At(1); var end = arguments.At(2); JsValue value; if (o._contentType == TypedArrayContentType.BigInt) { value = JsBigInt.Create(jsValue.ToBigInteger(_engine)); } else { value = JsNumber.Create(jsValue); } int k; var relativeStart = TypeConverter.ToIntegerOrInfinity(start); if (double.IsNegativeInfinity(relativeStart)) { k = 0; } else if (relativeStart < 0) { k = (int) System.Math.Max(len + relativeStart, 0); } else { k = (int) System.Math.Min(relativeStart, len); } uint endIndex; var relativeEnd = end.IsUndefined() ? len : TypeConverter.ToIntegerOrInfinity(end); if (double.IsNegativeInfinity(relativeEnd)) { endIndex = 0; } else if (relativeEnd < 0) { endIndex = (uint) System.Math.Max(len + relativeEnd, 0); } else { endIndex = (uint) System.Math.Min(relativeEnd, len); } taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm, "TypedArray is out of bounds"); } len = taRecord.TypedArrayLength; endIndex = System.Math.Min(endIndex, len); o._viewedArrayBuffer.AssertNotDetached(); for (var i = k; i < endIndex; ++i) { o[i] = value; } return thisObject; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter /// private JsValue Filter(JsValue thisObject, JsCallArguments arguments) { var callbackfn = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var kept = new List(); var captured = 0; var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { var kValue = o[k]; args[0] = kValue; args[1] = k; var selected = callbackfn.Call(thisArg, args); if (TypeConverter.ToBoolean(selected)) { kept.Add(kValue); captured++; } } _engine._jsValueArrayPool.ReturnArray(args); var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, [captured]); for (var n = 0; n < captured; ++n) { a[n] = kept[n]; } return a; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.find /// private JsValue Find(JsValue thisObject, JsCallArguments arguments) { return DoFind(thisObject, arguments).Value; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex /// private JsValue FindIndex(JsValue thisObject, JsCallArguments arguments) { return DoFind(thisObject, arguments).Key; } private JsValue FindLast(JsValue thisObject, JsCallArguments arguments) { return DoFind(thisObject, arguments, fromEnd: true).Value; } private JsValue FindLastIndex(JsValue thisObject, JsCallArguments arguments) { return DoFind(thisObject, arguments, fromEnd: true).Key; } private KeyValuePair DoFind(JsValue thisObject, JsCallArguments arguments, bool fromEnd = false) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var predicate = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); if (len == 0) { return new KeyValuePair(JsNumber.IntegerNegativeOne, Undefined); } var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; if (!fromEnd) { for (var k = 0; k < len; k++) { var kNumber = JsNumber.Create(k); var kValue = o[k]; args[0] = kValue; args[1] = kNumber; if (TypeConverter.ToBoolean(predicate.Call(thisArg, args))) { return new KeyValuePair(kNumber, kValue); } } } else { for (var k = (int) (len - 1); k >= 0; k--) { var kNumber = JsNumber.Create(k); var kValue = o[k]; args[0] = kValue; args[1] = kNumber; if (TypeConverter.ToBoolean(predicate.Call(thisArg, args))) { return new KeyValuePair(kNumber, kValue); } } } return new KeyValuePair(JsNumber.IntegerNegativeOne, Undefined); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach /// private JsValue ForEach(JsValue thisObject, JsCallArguments arguments) { var callbackfn = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { var kValue = o[k]; args[0] = kValue; args[1] = k; callbackfn.Call(thisArg, args); } _engine._jsValueArrayPool.ReturnArray(args); return Undefined; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes /// private JsValue Includes(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; if (len == 0) { return false; } var searchElement = arguments.At(0); var fromIndex = arguments.At(1, 0); var n = TypeConverter.ToIntegerOrInfinity(fromIndex); if (double.IsPositiveInfinity(n)) { return JsBoolean.False; } else if (double.IsNegativeInfinity(n)) { n = 0; } long k; if (n >= 0) { k = (long) n; } else { k = (long) (len + n); if (k < 0) { k = 0; } } while (k < len) { var value = o[(int) k]; if (SameValueZeroComparer.Equals(value, searchElement)) { return JsBoolean.True; } k++; } return JsBoolean.False; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof /// private JsValue IndexOf(JsValue thisObject, JsCallArguments arguments) { var searchElement = arguments.At(0); var fromIndex = arguments.At(1); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; if (len == 0) { return JsNumber.IntegerNegativeOne; } var n = TypeConverter.ToIntegerOrInfinity(fromIndex); if (double.IsPositiveInfinity(n)) { return JsNumber.IntegerNegativeOne; } else if (double.IsNegativeInfinity(n)) { n = 0; } long k; if (n >= 0) { k = (long) n; } else { k = (long) (len + n); if (k < 0) { k = 0; } } for (; k < len; k++) { var kPresent = o.HasProperty(k); if (kPresent) { var elementK = o[(int) k]; if (elementK == searchElement) { return k; } } } return JsNumber.IntegerNegativeOne; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.join /// private JsValue Join(JsValue thisObject, JsCallArguments arguments) { var separator = arguments.At(0); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var sep = TypeConverter.ToString(separator.IsUndefined() ? JsString.CommaString : separator); // as per the spec, this has to be called after ToString(separator) if (len == 0) { return JsString.Empty; } static string StringFromJsValue(JsValue value) { return value.IsUndefined() ? "" : TypeConverter.ToString(value); } var s = StringFromJsValue(o[0]); if (len == 1) { return s; } using var result = new ValueStringBuilder(); result.Append(s); for (var k = 1; k < len; k++) { result.Append(sep); result.Append(StringFromJsValue(o[k])); } return result.ToString(); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys /// private JsValue Keys(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.Key); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof /// private JsValue LastIndexOf(JsValue thisObject, JsCallArguments arguments) { var searchElement = arguments.At(0); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; if (len == 0) { return JsNumber.IntegerNegativeOne; } var fromIndex = arguments.At(1, len - 1); var n = TypeConverter.ToIntegerOrInfinity(fromIndex); if (double.IsNegativeInfinity(n)) { return JsNumber.IntegerNegativeOne; } long k; if (n >= 0) { k = (long) System.Math.Min(n, len - 1); } else { k = (long) (len + n); } for (; k >= 0; k--) { var kPresent = o.HasProperty(k); if (kPresent) { var elementK = o[(int) k]; if (elementK == searchElement) { return k; } } } return JsNumber.IntegerNegativeOne; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.map /// private ObjectInstance Map(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var thisArg = arguments.At(1); var callable = GetCallable(arguments.At(0)); var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, [len]); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { args[0] = o[k]; args[1] = k; var mappedValue = callable.Call(thisArg, args); a[k] = mappedValue; } _engine._jsValueArrayPool.ReturnArray(args); return a; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce /// private JsValue Reduce(JsValue thisObject, JsCallArguments arguments) { var callbackfn = GetCallable(arguments.At(0)); var initialValue = arguments.At(1); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; if (len == 0 && arguments.Length < 2) { Throw.TypeError(_realm); } var k = 0; var accumulator = Undefined; if (!initialValue.IsUndefined()) { accumulator = initialValue; } else { accumulator = o[k]; k++; } var args = _engine._jsValueArrayPool.RentArray(4); args[3] = o; while (k < len) { var kValue = o[k]; args[0] = accumulator; args[1] = kValue; args[2] = k; accumulator = callbackfn.Call(Undefined, args); k++; } _engine._jsValueArrayPool.ReturnArray(args); return accumulator; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright /// private JsValue ReduceRight(JsValue thisObject, JsCallArguments arguments) { var callbackfn = GetCallable(arguments.At(0)); var initialValue = arguments.At(1); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; if (len == 0 && arguments.Length < 2) { Throw.TypeError(_realm); } var k = (long) len - 1; JsValue accumulator; if (arguments.Length > 1) { accumulator = initialValue; } else { accumulator = o[k]; k--; } var jsValues = _engine._jsValueArrayPool.RentArray(4); jsValues[3] = o; for (; k >= 0; k--) { jsValues[0] = accumulator; jsValues[1] = o[(int) k]; jsValues[2] = k; accumulator = callbackfn.Call(Undefined, jsValues); } _engine._jsValueArrayPool.ReturnArray(jsValues); return accumulator; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse /// private ObjectInstance Reverse(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var middle = (int) System.Math.Floor(len / 2.0); var lower = 0; while (lower != middle) { var upper = len - lower - 1; var lowerValue = o[lower]; var upperValue = o[upper]; o[lower] = upperValue; o[upper] = lowerValue; lower++; } return o; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.set /// private JsValue Set(JsValue thisObject, JsCallArguments arguments) { var target = thisObject as JsTypedArray; if (target is null) { Throw.TypeError(_realm); } var source = arguments.At(0); var offset = arguments.At(1); var targetOffset = TypeConverter.ToIntegerOrInfinity(offset); if (targetOffset < 0) { Throw.RangeError(_realm, "Invalid offset"); } if (source is JsTypedArray typedArrayInstance) { SetTypedArrayFromTypedArray(target, targetOffset, typedArrayInstance); } else { SetTypedArrayFromArrayLike(target, (int) targetOffset, source); } return Undefined; } /// /// https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray /// private void SetTypedArrayFromTypedArray(JsTypedArray target, double targetOffset, JsTypedArray source) { var targetBuffer = target._viewedArrayBuffer; var targetRecord = MakeTypedArrayWithBufferWitnessRecord(target, ArrayBufferOrder.SeqCst); if (targetRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm); } var targetLength = targetRecord.TypedArrayLength; var srcBuffer = source._viewedArrayBuffer; var srcRecord = MakeTypedArrayWithBufferWitnessRecord(source, ArrayBufferOrder.SeqCst); if (srcRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm); } var targetType = target._arrayElementType; var targetElementSize = targetType.GetElementSize(); var targetByteOffset = target._byteOffset; var srcType = source._arrayElementType; var srcElementSize = srcType.GetElementSize(); var srcLength = srcRecord.TypedArrayLength; var srcByteOffset = source._byteOffset; if (double.IsNegativeInfinity(targetOffset)) { Throw.RangeError(_realm, "Invalid target offset"); } if (srcLength + targetOffset > targetLength) { Throw.RangeError(_realm, "Invalid target offset"); } if (target._contentType != source._contentType) { Throw.TypeError(_realm, "Content type mismatch"); } var same = SameValue(srcBuffer, targetBuffer); int srcByteIndex; if (same) { var srcByteLength = srcRecord.TypedArrayByteLength; srcBuffer = srcBuffer.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, srcByteLength); // %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. srcByteIndex = 0; } else { srcByteIndex = srcByteOffset; } var targetByteIndex = (int) (targetOffset * targetElementSize + targetByteOffset); var limit = targetByteIndex + targetElementSize * srcLength; if (srcType == targetType) { // NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. while (targetByteIndex < limit) { var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, isTypedArray: true, ArrayBufferOrder.Unordered); targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, isTypedArray: true, ArrayBufferOrder.Unordered); srcByteIndex += 1; targetByteIndex += 1; } } else { while (targetByteIndex < limit) { var value = srcBuffer.GetValueFromBuffer(srcByteIndex, srcType, isTypedArray: true, ArrayBufferOrder.Unordered); targetBuffer.SetValueInBuffer(targetByteIndex, targetType, value, isTypedArray: true, ArrayBufferOrder.Unordered); srcByteIndex += srcElementSize; targetByteIndex += targetElementSize; } } } /// /// https://tc39.es/ecma262/#sec-settypedarrayfromarraylike /// private void SetTypedArrayFromArrayLike(JsTypedArray target, int targetOffset, JsValue source) { var targetBuffer = target._viewedArrayBuffer; targetBuffer.AssertNotDetached(); var targetRecord = MakeTypedArrayWithBufferWitnessRecord(target, ArrayBufferOrder.SeqCst); if (targetRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm); } var targetLength = targetRecord.TypedArrayLength; var src = ArrayOperations.For(_realm, source, forWrite: false); var srcLength = src.GetLength(); if (double.IsNegativeInfinity(targetOffset)) { Throw.RangeError(_realm, "Invalid target offset"); } if (srcLength + targetOffset > targetLength) { Throw.RangeError(_realm, "Invalid target offset"); } var k = 0; while (k < srcLength) { var jsValue = src.Get((ulong) k); target.IntegerIndexedElementSet(targetOffset + k, jsValue); k++; } } /// /// https://tc39.es/proposal-relative-indexing-method/#sec-%typedarray.prototype%-additions /// private JsValue At(JsValue thisObject, JsCallArguments arguments) { var start = arguments.At(0); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var relativeStart = TypeConverter.ToInteger(start); int k; if (relativeStart < 0) { k = (int) (len + relativeStart); } else { k = (int) relativeStart; } if (k < 0 || k >= len) { return Undefined; } return o.Get(k); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice /// private JsValue Slice(JsValue thisObject, JsCallArguments arguments) { var start = arguments.At(0); var end = arguments.At(1); var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var relativeStart = TypeConverter.ToIntegerOrInfinity(start); int startIndex; if (double.IsNegativeInfinity(relativeStart)) { startIndex = 0; } else if (relativeStart < 0) { startIndex = (int) System.Math.Max(len + relativeStart, 0); } else { startIndex = (int) System.Math.Min(relativeStart, len); } var relativeEnd = end.IsUndefined() ? len : TypeConverter.ToIntegerOrInfinity(end); long endIndex; if (double.IsNegativeInfinity(relativeEnd)) { endIndex = 0; } else if (relativeEnd < 0) { endIndex = (long) System.Math.Max(len + relativeEnd, 0); } else { endIndex = (long) System.Math.Min(relativeEnd, len); } var countBytes = System.Math.Max(endIndex - startIndex, 0); var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, [countBytes]); if (countBytes > 0) { taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm, "TypedArray is out of bounds"); } endIndex = System.Math.Min(endIndex, taRecord.TypedArrayLength); countBytes = System.Math.Max(endIndex - startIndex, 0); var srcType = o._arrayElementType; var targetType = a._arrayElementType; if (srcType != targetType) { var n = 0; while (startIndex < endIndex) { var kValue = o[startIndex]; a[n] = kValue; startIndex++; n++; } } else { var srcBuffer = o._viewedArrayBuffer; var targetBuffer = a._viewedArrayBuffer; var elementSize = srcType.GetElementSize(); var srcByteOffset = o._byteOffset; var targetByteIndex = a._byteOffset; var srcByteIndex = (int) startIndex * elementSize + srcByteOffset; var limit = targetByteIndex + countBytes * elementSize; while (targetByteIndex < limit) { var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered); targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered); srcByteIndex++; targetByteIndex++; } } } return a; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.some /// private JsValue Some(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var callbackfn = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { args[0] = o[k]; args[1] = k; if (TypeConverter.ToBoolean(callbackfn.Call(thisArg, args))) { return JsBoolean.True; } } _engine._jsValueArrayPool.ReturnArray(args); return JsBoolean.False; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort /// private JsValue Sort(JsValue thisObject, JsCallArguments arguments) { /* * %TypedArray%.prototype.sort is a distinct function that, except as described below, * implements the same requirements as those of Array.prototype.sort as defined in 23.1.3.27. * The implementation of the %TypedArray%.prototype.sort specification may be optimized with the knowledge that the this value is * an object that has a fixed length and whose integer-indexed properties are not sparse. */ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var buffer = o._viewedArrayBuffer; var compareFn = GetCompareFunction(arguments.At(0)); if (len <= 1) { return o; } var array = SortArray(buffer, compareFn, o); for (var i = 0; i < (uint) array.Length; ++i) { o[i] = array[i]; } return o; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray /// private JsValue Subarray(JsValue thisObject, JsCallArguments arguments) { var o = thisObject as JsTypedArray; if (o is null) { Throw.TypeError(_realm); } var start = arguments.At(0); var end = arguments.At(1); var buffer = o._viewedArrayBuffer; var srcRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); uint srcLength = 0; if (!srcRecord.IsTypedArrayOutOfBounds) { srcLength = srcRecord.TypedArrayLength; } var relativeStart = TypeConverter.ToIntegerOrInfinity(start); double startIndex; if (double.IsNegativeInfinity(relativeStart)) { startIndex = 0; } else if (relativeStart < 0) { startIndex = System.Math.Max(srcLength + relativeStart, 0); } else { startIndex = System.Math.Min(relativeStart, srcLength); } var elementSize = o._arrayElementType.GetElementSize(); var srcByteOffset = o._byteOffset; var beginByteOffset = srcByteOffset + startIndex * elementSize; JsCallArguments argumentsList; if (o._arrayLength == JsTypedArray.LengthAuto && end.IsUndefined()) { argumentsList = [buffer, beginByteOffset]; } else { double relativeEnd; if (end.IsUndefined()) { relativeEnd = srcLength; } else { relativeEnd = TypeConverter.ToIntegerOrInfinity(end); } double endIndex; if (double.IsNegativeInfinity(relativeEnd)) { endIndex = 0; } else if (relativeEnd < 0) { endIndex = System.Math.Max(srcLength + relativeEnd, 0); } else { endIndex = System.Math.Min(relativeEnd, srcLength); } var newLength = System.Math.Max(endIndex - startIndex, 0); argumentsList = [buffer, beginByteOffset, newLength]; } return _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, argumentsList); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring /// private JsValue ToLocaleString(JsValue thisObject, JsCallArguments arguments) { /* * %TypedArray%.prototype.toLocaleString is a distinct function that implements the same algorithm as Array.prototype.toLocaleString * as defined in 23.1.3.29 except that the this value's [[ArrayLength]] internal slot is accessed in place of performing * a [[Get]] of "length". The implementation of the algorithm may be optimized with the knowledge that the this value is an object * that has a fixed length and whose integer-indexed properties are not sparse. However, such optimization must not introduce * any observable changes in the specified behaviour of the algorithm. */ const string Separator = ","; var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var array = taRecord.Object; var len = taRecord.TypedArrayLength; if (len == 0) { return JsString.Empty; } using var r = new ValueStringBuilder(); for (uint k = 0; k < len; k++) { if (k > 0) { r.Append(Separator); } if (array.TryGetValue(k, out var nextElement) && !nextElement.IsNullOrUndefined()) { var s = TypeConverter.ToString(Invoke(nextElement, "toLocaleString", [])); r.Append(s); } } return r.ToString(); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.values /// private JsValue Values(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.Value); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag /// private static JsValue ToStringTag(JsValue thisObject, JsCallArguments arguments) { if (thisObject is not JsTypedArray o) { return Undefined; } return o._arrayElementType.GetTypedArrayName(); } private JsValue ToReversed(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var a = TypedArrayCreateSameType(o, [JsNumber.Create(len)]); uint k = 0; while (k < len) { var from = len - k - 1; a[k++] = o.Get(from); } return a; } private JsValue ToSorted(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var compareFn = GetCompareFunction(arguments.At(0)); var buffer = o._viewedArrayBuffer; var a = TypedArrayCreateSameType(o, [JsNumber.Create(len)]); var array = SortArray(buffer, compareFn, o); for (var i = 0; (uint) i < (uint) array.Length; ++i) { a[i] = array[i]; } return a; } private ObjectInstance With(JsValue thisObject, JsCallArguments arguments) { var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst); var o = taRecord.Object; var len = taRecord.TypedArrayLength; var value = arguments.At(1); var relativeIndex = TypeConverter.ToIntegerOrInfinity(arguments.At(0)); long actualIndex; if (relativeIndex >= 0) { actualIndex = (long) relativeIndex; } else { actualIndex = (long) (len + relativeIndex); } value = o._contentType == TypedArrayContentType.BigInt ? TypeConverter.ToJsBigInt(value) : TypeConverter.ToJsNumber(value); if (!o.IsValidIntegerIndex(actualIndex)) { Throw.RangeError(_realm, "Invalid start index"); } var a = TypedArrayCreateSameType(o, [JsNumber.Create(len)]); var k = 0; while (k < len) { a[k] = k == (int) actualIndex ? value : o.Get(k); k++; } return a; } private JsTypedArray TypedArrayCreateSameType(JsTypedArray exemplar, JsValue[] argumentList) { var constructor = exemplar._arrayElementType.GetConstructor(_realm.Intrinsics); var result = IntrinsicTypedArrayConstructor.TypedArrayCreate(_realm, constructor, argumentList); return result; } private ICallable? GetCompareFunction(JsValue compareArg) { ICallable? compareFn = null; if (!compareArg.IsUndefined()) { if (compareArg is not ICallable callable) { Throw.TypeError(_realm, "The comparison function must be either a function or undefined"); return null; } compareFn = callable; } return compareFn; } private static JsValue[] SortArray(JsArrayBuffer buffer, ICallable? compareFn, JsTypedArray obj) { var comparer = TypedArrayComparer.WithFunction(buffer, compareFn); var operations = ArrayOperations.For(obj, forWrite: false); try { return operations.OrderBy(x => x, comparer).ToArray(); } catch (InvalidOperationException e) { throw e.InnerException ?? e; } } private sealed class TypedArrayComparer : IComparer { public static TypedArrayComparer WithFunction(JsArrayBuffer buffer, ICallable? compare) { return new TypedArrayComparer(buffer, compare); } private readonly JsArrayBuffer _buffer; private readonly ICallable? _compare; private readonly JsValue[] _comparableArray = new JsValue[2]; private TypedArrayComparer(JsArrayBuffer buffer, ICallable? compare) { _buffer = buffer; _compare = compare; } public int Compare(JsValue? x, JsValue? y) { if (x is null && y is null) { return 0; } if (x is not null && y is null) { return 1; } if (x is null) { return -1; } if (y is null) { return 1; } if (_compare is not null) { _comparableArray[0] = x; _comparableArray[1] = y; var v = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray)); if (double.IsNaN(v)) { return 0; } return (int) v; } if (x.Type == Types.BigInt || y.Type == Types.BigInt) { var xBigInt = TypeConverter.ToBigInt(x); var yBigInt = TypeConverter.ToBigInt(y); return xBigInt.CompareTo(yBigInt); } var xValue = x.AsNumber(); var yValue = y.AsNumber(); if (double.IsNaN(xValue) && double.IsNaN(yValue)) { return 0; } if (double.IsNaN(xValue)) { return 1; } if (double.IsNaN(yValue)) { return -1; } if (xValue < yValue) { return -1; } if (xValue > yValue) { return 1; } if (NumberInstance.IsNegativeZero(xValue) && yValue == 0) { return -1; } if (xValue == 0 && NumberInstance.IsNegativeZero(yValue)) { return 1; } return 0; } } }