#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;
}
}
}