#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.Collections;
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.Array;
///
/// https://tc39.es/ecma262/#sec-properties-of-the-array-prototype-object
///
public sealed class ArrayPrototype : ArrayInstance
{
private readonly Realm _realm;
private readonly ArrayConstructor _constructor;
private readonly ObjectTraverseStack _joinStack;
internal ClrFunction? _originalIteratorFunction;
internal ArrayPrototype(
Engine engine,
Realm realm,
ArrayConstructor arrayConstructor,
ObjectPrototype objectPrototype) : base(engine, InternalTypes.Object)
{
_prototype = objectPrototype;
_length = new PropertyDescriptor(JsNumber.PositiveZero, PropertyFlag.Writable);
_realm = realm;
_constructor = arrayConstructor;
_joinStack = new(engine);
}
protected override void Initialize()
{
const PropertyFlag PropertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable;
var properties = new PropertyDictionary(38, checkExistingKeys: false)
{
["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
["at"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "at", prototype.At, 1, PropertyFlag.Configurable), PropertyFlags),
["concat"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "concat", prototype.Concat, 1, PropertyFlag.Configurable), PropertyFlags),
["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),
["flat"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "flat", prototype.Flat, 0, PropertyFlag.Configurable), PropertyFlags),
["flatMap"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "flatMap", prototype.FlatMap, 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),
["map"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "map", prototype.Map, 1, PropertyFlag.Configurable), PropertyFlags),
["pop"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "pop", prototype.Pop, 0, PropertyFlag.Configurable), PropertyFlags),
["push"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "push", prototype.Push, 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),
["shift"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "shift", prototype.Shift, 0, 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),
["splice"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "splice", prototype.Splice, 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),
["toSpliced"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toSpliced", prototype.ToSpliced, 2, PropertyFlag.Configurable), PropertyFlags),
["toString"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toString", prototype.ToString, 0, PropertyFlag.Configurable), PropertyFlags),
["unshift"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "unshift", prototype.Unshift, 1, 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 PropertyDescriptor(_originalIteratorFunction, PropertyFlags),
[GlobalSymbolRegistry.Unscopables] = new LazyPropertyDescriptor(_engine, static engine =>
{
var unscopables = new JsObject(engine)
{
_prototype = null
};
unscopables.FastSetDataProperty("at", JsBoolean.True);
unscopables.FastSetDataProperty("copyWithin", JsBoolean.True);
unscopables.FastSetDataProperty("entries", JsBoolean.True);
unscopables.FastSetDataProperty("fill", JsBoolean.True);
unscopables.FastSetDataProperty("find", JsBoolean.True);
unscopables.FastSetDataProperty("findIndex", JsBoolean.True);
unscopables.FastSetDataProperty("findLast", JsBoolean.True);
unscopables.FastSetDataProperty("findLastIndex", JsBoolean.True);
unscopables.FastSetDataProperty("flat", JsBoolean.True);
unscopables.FastSetDataProperty("flatMap", JsBoolean.True);
unscopables.FastSetDataProperty("includes", JsBoolean.True);
unscopables.FastSetDataProperty("keys", JsBoolean.True);
unscopables.FastSetDataProperty("toReversed", JsBoolean.True);
unscopables.FastSetDataProperty("toSorted", JsBoolean.True);
unscopables.FastSetDataProperty("toSpliced", JsBoolean.True);
unscopables.FastSetDataProperty("values", JsBoolean.True);
return unscopables;
}, PropertyFlag.Configurable)
};
SetSymbols(symbols);
}
private ObjectInstance Keys(JsValue thisObject, JsCallArguments arguments)
{
if (thisObject is ObjectInstance oi && oi.IsArrayLike)
{
return _realm.Intrinsics.ArrayIteratorPrototype.Construct(oi, ArrayIteratorType.Key);
}
Throw.TypeError(_realm, "cannot construct iterator");
return null;
}
internal ObjectInstance Values(JsValue thisObject, JsCallArguments arguments)
{
if (thisObject is ObjectInstance oi && oi.IsArrayLike)
{
return _realm.Intrinsics.ArrayIteratorPrototype.Construct(oi, ArrayIteratorType.Value);
}
Throw.TypeError(_realm, "cannot construct iterator");
return null;
}
private ObjectInstance With(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(TypeConverter.ToObject(_realm, thisObject), forWrite: false);
var len = o.GetLongLength();
var relativeIndex = TypeConverter.ToIntegerOrInfinity(arguments.At(0));
var value = arguments.At(1);
long actualIndex;
if (relativeIndex >= 0)
{
actualIndex = (long) relativeIndex;
}
else
{
actualIndex = (long) (len + relativeIndex);
}
if (actualIndex >= (long) len || actualIndex < 0)
{
Throw.RangeError(_realm, "Invalid start index");
}
var a = CreateBackingArray(len);
ulong k = 0;
while (k < len)
{
a[k] = k == (ulong) actualIndex ? value : o.Get(k);
k++;
}
return new JsArray(_engine, a);
}
private ObjectInstance Entries(JsValue thisObject, JsCallArguments arguments)
{
if (thisObject is ObjectInstance oi && oi.IsArrayLike)
{
return _realm.Intrinsics.ArrayIteratorPrototype.Construct(oi, ArrayIteratorType.KeyAndValue);
}
Throw.TypeError(_realm, "cannot construct iterator");
return null;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.fill
///
private JsValue Fill(JsValue thisObject, JsCallArguments arguments)
{
var value = arguments.At(0);
var start = arguments.At(1);
var end = arguments.At(2);
var o = TypeConverter.ToObject(_realm, thisObject);
var operations = ArrayOperations.For(o, forWrite: true);
var length = operations.GetLongLength();
var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
ulong k;
if (double.IsNegativeInfinity(relativeStart))
{
k = 0;
}
else if (relativeStart < 0)
{
k = (ulong) System.Math.Max(length + relativeStart, 0);
}
else
{
k = (ulong) System.Math.Min(relativeStart, length);
}
var relativeEnd = end.IsUndefined() ? length : TypeConverter.ToIntegerOrInfinity(end);
ulong final;
if (double.IsNegativeInfinity(relativeEnd))
{
final = 0;
}
else if (relativeEnd < 0)
{
final = (ulong) System.Math.Max(length + relativeEnd, 0);
}
else
{
final = (ulong) System.Math.Min(relativeEnd, length);
}
for (var i = k; i < final; ++i)
{
operations.Set(i, value, throwOnError: false);
}
return o;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.copywithin
///
private JsValue CopyWithin(JsValue thisObject, JsCallArguments arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
JsValue target = arguments.At(0);
JsValue start = arguments.At(1);
JsValue end = arguments.At(2);
var operations = ArrayOperations.For(o, forWrite: true);
var len = operations.GetLongLength();
var relativeTarget = TypeConverter.ToIntegerOrInfinity(target);
var to = relativeTarget < 0 ?
System.Math.Max(len + relativeTarget, 0) :
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 = (long) System.Math.Min(final - from, len - to);
long direction = 1;
if (from < to && to < from + count)
{
direction = -1;
from += count - 1;
to += count - 1;
}
while (count > 0)
{
var fromPresent = operations.HasProperty((ulong) from);
if (fromPresent)
{
var fromValue = operations.Get((ulong) from);
operations.Set((ulong) to, fromValue, updateLength: true, throwOnError: true);
}
else
{
operations.DeletePropertyOrThrow((ulong) to);
}
from += direction;
to += direction;
count--;
}
return o;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.lastindexof
///
private JsValue LastIndexOf(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLongLength();
if (len == 0)
{
return JsNumber.IntegerNegativeOne;
}
var n = arguments.Length > 1
? TypeConverter.ToInteger(arguments[1])
: len - 1;
double k;
if (n >= 0)
{
k = System.Math.Min(n, len - 1); // min
}
else
{
k = len - System.Math.Abs(n);
}
if (k < 0 || k > ArrayOperations.MaxArrayLikeLength)
{
return JsNumber.IntegerNegativeOne;
}
var searchElement = arguments.At(0);
var i = (ulong) k;
for (; ; i--)
{
var kPresent = o.HasProperty(i);
if (kPresent)
{
var elementK = o.Get(i);
if (elementK == searchElement)
{
return i;
}
}
if (i == 0)
{
break;
}
}
return JsNumber.IntegerNegativeOne;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.reduce
///
private JsValue Reduce(JsValue thisObject, JsCallArguments arguments)
{
var callbackfn = arguments.At(0);
var initialValue = arguments.At(1);
var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
var len = o.GetLength();
var callable = GetCallable(callbackfn);
if (len == 0 && arguments.Length < 2)
{
Throw.TypeError(_realm);
}
var k = 0;
JsValue accumulator = Undefined;
if (arguments.Length > 1)
{
accumulator = initialValue;
}
else
{
var kPresent = false;
while (kPresent == false && k < len)
{
if (kPresent = o.TryGetValue((uint) k, out var temp))
{
accumulator = temp;
}
k++;
}
if (kPresent == false)
{
Throw.TypeError(_realm);
}
}
var args = new JsValue[4];
args[3] = o.Target;
while (k < len)
{
var i = (uint) k;
if (o.TryGetValue(i, out var kvalue))
{
args[0] = accumulator;
args[1] = kvalue;
args[2] = i;
accumulator = callable.Call(Undefined, args);
}
k++;
}
return accumulator;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.filter
///
private JsValue Filter(JsValue thisObject, JsCallArguments arguments)
{
var callbackfn = arguments.At(0);
var thisArg = arguments.At(1);
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLength();
var callable = GetCallable(callbackfn);
var a = _realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), 0);
var operations = ArrayOperations.For(a, forWrite: true);
uint to = 0;
var args = _engine._jsValueArrayPool.RentArray(3);
args[2] = o.Target;
for (uint k = 0; k < len; k++)
{
if (o.TryGetValue(k, out var kvalue))
{
args[0] = kvalue;
args[1] = k;
var selected = callable.Call(thisArg, args);
if (TypeConverter.ToBoolean(selected))
{
operations.CreateDataPropertyOrThrow(to, kvalue);
to++;
}
}
}
operations.SetLength(to);
_engine._jsValueArrayPool.ReturnArray(args);
return a;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.map
///
private JsValue Map(JsValue thisObject, JsCallArguments arguments)
{
if (thisObject is JsArray { CanUseFastAccess: true } arrayInstance
&& !arrayInstance.HasOwnProperty(CommonProperties.Constructor))
{
return arrayInstance.Map(arguments);
}
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLongLength();
if (len > ArrayOperations.MaxArrayLength)
{
Throw.RangeError(_realm, "Invalid array length");
}
var callbackfn = arguments.At(0);
var thisArg = arguments.At(1);
var callable = GetCallable(callbackfn);
var a = ArrayOperations.For(_realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), (uint) len), forWrite: true);
var args = _engine._jsValueArrayPool.RentArray(3);
args[2] = o.Target;
for (uint k = 0; k < len; k++)
{
if (o.TryGetValue(k, out var kvalue))
{
args[0] = kvalue;
args[1] = k;
var mappedValue = callable.Call(thisArg, args);
a.CreateDataPropertyOrThrow(k, mappedValue);
}
}
_engine._jsValueArrayPool.ReturnArray(args);
return a.Target;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.flat
///
private JsValue Flat(JsValue thisObject, JsCallArguments arguments)
{
var operations = ArrayOperations.For(_realm, thisObject, forWrite: false);
var sourceLen = operations.GetLength();
double depthNum = 1;
var depth = arguments.At(0);
if (!depth.IsUndefined())
{
depthNum = TypeConverter.ToIntegerOrInfinity(depth);
}
if (depthNum < 0)
{
depthNum = 0;
}
var A = _realm.Intrinsics.Array.ArraySpeciesCreate(operations.Target, 0);
FlattenIntoArray(A, operations, sourceLen, 0, depthNum);
return A;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.flatmap
///
private JsValue FlatMap(JsValue thisObject, JsCallArguments arguments)
{
var O = ArrayOperations.For(_realm, thisObject, forWrite: false);
var mapperFunction = arguments.At(0);
var thisArg = arguments.At(1);
var sourceLen = O.GetLength();
if (!mapperFunction.IsCallable)
{
Throw.TypeError(_realm, "flatMap mapper function is not callable");
}
var A = _realm.Intrinsics.Array.ArraySpeciesCreate(O.Target, 0);
FlattenIntoArray(A, O, sourceLen, 0, 1, (ICallable) mapperFunction, thisArg);
return A;
}
///
/// https://tc39.es/ecma262/#sec-flattenintoarray
///
private ulong FlattenIntoArray(
ObjectInstance target,
ArrayOperations source,
uint sourceLen,
ulong start,
double depth,
ICallable? mapperFunction = null,
JsValue? thisArg = null)
{
var targetIndex = start;
ulong sourceIndex = 0;
var callArguments = System.Array.Empty();
if (mapperFunction is not null)
{
callArguments = _engine._jsValueArrayPool.RentArray(3);
callArguments[2] = source.Target;
}
while (sourceIndex < sourceLen)
{
var exists = source.HasProperty(sourceIndex);
if (exists)
{
var element = source.Get(sourceIndex);
if (mapperFunction is not null)
{
callArguments[0] = element;
callArguments[1] = JsNumber.Create(sourceIndex);
element = mapperFunction.Call(thisArg ?? Undefined, callArguments);
}
var shouldFlatten = false;
if (depth > 0)
{
shouldFlatten = element.IsArray();
}
if (shouldFlatten)
{
var newDepth = double.IsPositiveInfinity(depth)
? depth
: depth - 1;
var objectInstance = (ObjectInstance) element;
var elementLen = objectInstance.GetLength();
targetIndex = FlattenIntoArray(target, ArrayOperations.For(objectInstance, forWrite: false), elementLen, targetIndex, newDepth);
}
else
{
if (targetIndex >= NumberConstructor.MaxSafeInteger)
{
Throw.TypeError(_realm);
}
target.CreateDataPropertyOrThrow(targetIndex, element);
targetIndex += 1;
}
}
sourceIndex++;
}
if (mapperFunction is not null)
{
_engine._jsValueArrayPool.ReturnArray(callArguments);
}
return targetIndex;
}
private JsValue ForEach(JsValue thisObject, JsCallArguments arguments)
{
var callbackfn = arguments.At(0);
var thisArg = arguments.At(1);
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLength();
var callable = GetCallable(callbackfn);
var args = _engine._jsValueArrayPool.RentArray(3);
args[2] = o.Target;
for (uint k = 0; k < len; k++)
{
if (o.TryGetValue(k, out var kvalue))
{
args[0] = kvalue;
args[1] = k;
callable.Call(thisArg, args);
}
}
_engine._jsValueArrayPool.ReturnArray(args);
return Undefined;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.includes
///
private JsValue Includes(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = (long) o.GetLongLength();
if (len == 0)
{
return JsBoolean.False;
}
var searchElement = arguments.At(0);
var fromIndex = arguments.At(1);
long k = 0;
var n = TypeConverter.ToIntegerOrInfinity(fromIndex);
if (double.IsPositiveInfinity(n))
{
return JsBoolean.False;
}
else if (double.IsNegativeInfinity(n))
{
n = 0;
}
else if (n >= 0)
{
k = (long) n;
}
else
{
k = len + (long) n;
if (k < 0)
{
k = 0;
}
}
while (k < len)
{
var value = o.Get((ulong) k);
if (SameValueZeroComparer.Equals(value, searchElement))
{
return true;
}
k++;
}
return false;
}
private JsValue Some(JsValue thisObject, JsCallArguments arguments)
{
var target = TypeConverter.ToObject(_realm, thisObject);
return target.FindWithCallback(arguments, out _, out _, false);
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.every
///
private JsValue Every(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
ulong len = o.GetLongLength();
if (len == 0)
{
return JsBoolean.True;
}
var callbackfn = arguments.At(0);
var thisArg = arguments.At(1);
var callable = GetCallable(callbackfn);
var args = _engine._jsValueArrayPool.RentArray(3);
args[2] = o.Target;
for (uint k = 0; k < len; k++)
{
if (o.TryGetValue(k, out var kvalue))
{
args[0] = kvalue;
args[1] = k;
var testResult = callable.Call(thisArg, args);
if (!TypeConverter.ToBoolean(testResult))
{
return JsBoolean.False;
}
}
}
_engine._jsValueArrayPool.ReturnArray(args);
return JsBoolean.True;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.indexof
///
private JsValue IndexOf(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLongLength();
if (len == 0)
{
return -1;
}
var startIndex = arguments.Length > 1
? TypeConverter.ToIntegerOrInfinity(arguments.At(1))
: 0;
if (startIndex > ArrayOperations.MaxArrayLikeLength)
{
return JsNumber.IntegerNegativeOne;
}
ulong k;
if (startIndex < 0)
{
var abs = System.Math.Abs(startIndex);
ulong temp = len - (uint) abs;
if (abs > len || temp < 0)
{
temp = 0;
}
k = temp;
}
else
{
k = (ulong) startIndex;
}
if (k >= len)
{
return -1;
}
ulong smallestIndex = o.GetSmallestIndex(len);
if (smallestIndex > k)
{
k = smallestIndex;
}
var searchElement = arguments.At(0);
for (; k < len; k++)
{
var kPresent = o.HasProperty(k);
if (kPresent)
{
var elementK = o.Get(k);
if (elementK == searchElement)
{
return k;
}
}
}
return -1;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.find
///
private JsValue Find(JsValue thisObject, JsCallArguments arguments)
{
var target = TypeConverter.ToObject(_realm, thisObject);
target.FindWithCallback(arguments, out _, out var value, visitUnassigned: true);
return value;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.findindex
///
private JsValue FindIndex(JsValue thisObject, JsCallArguments arguments)
{
var target = TypeConverter.ToObject(_realm, thisObject);
if (target.FindWithCallback(arguments, out var index, out _, visitUnassigned: true))
{
return index;
}
return -1;
}
private JsValue FindLast(JsValue thisObject, JsCallArguments arguments)
{
var target = TypeConverter.ToObject(_realm, thisObject);
target.FindWithCallback(arguments, out _, out var value, visitUnassigned: true, fromEnd: true);
return value;
}
private JsValue FindLastIndex(JsValue thisObject, JsCallArguments arguments)
{
var target = TypeConverter.ToObject(_realm, thisObject);
if (target.FindWithCallback(arguments, out var index, out _, visitUnassigned: true, fromEnd: true))
{
return index;
}
return -1;
}
///
/// https://tc39.es/proposal-relative-indexing-method/#sec-array-prototype-additions
///
private JsValue At(JsValue thisObject, JsCallArguments arguments)
{
var target = TypeConverter.ToObject(_realm, thisObject);
var len = target.GetLength();
var relativeIndex = TypeConverter.ToInteger(arguments.At(0));
ulong actualIndex;
if (relativeIndex < 0)
{
actualIndex = (ulong) (len + relativeIndex);
}
else
{
actualIndex = (ulong) relativeIndex;
}
if (actualIndex < 0 || actualIndex >= len)
{
return Undefined;
}
return target.Get(actualIndex);
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.splice
///
private JsValue Splice(JsValue thisObject, JsCallArguments arguments)
{
var start = arguments.At(0);
var deleteCount = arguments.At(1);
var obj = TypeConverter.ToObject(_realm, thisObject);
var o = ArrayOperations.For(_realm, obj, forWrite: true);
var len = o.GetLongLength();
var relativeStart = TypeConverter.ToInteger(start);
ulong actualStart;
if (relativeStart < 0)
{
actualStart = (ulong) System.Math.Max(len + relativeStart, 0);
}
else
{
actualStart = (ulong) System.Math.Min(relativeStart, len);
}
var items = System.Array.Empty();
ulong insertCount;
ulong actualDeleteCount;
if (arguments.Length == 0)
{
insertCount = 0;
actualDeleteCount = 0;
}
else if (arguments.Length == 1)
{
insertCount = 0;
actualDeleteCount = len - actualStart;
}
else
{
insertCount = (ulong) (arguments.Length - 2);
var dc = TypeConverter.ToInteger(deleteCount);
actualDeleteCount = (ulong) System.Math.Min(System.Math.Max(dc, 0), len - actualStart);
items = [];
if (arguments.Length > 2)
{
items = new JsValue[arguments.Length - 2];
System.Array.Copy(arguments, 2, items, 0, items.Length);
}
}
if (len + insertCount - actualDeleteCount > ArrayOperations.MaxArrayLikeLength)
{
Throw.TypeError(_realm, "Invalid array length");
}
var instance = _realm.Intrinsics.Array.ArraySpeciesCreate(obj, actualDeleteCount);
var a = ArrayOperations.For(instance, forWrite: true);
for (uint k = 0; k < actualDeleteCount; k++)
{
var index = actualStart + k;
if (o.HasProperty(index))
{
var fromValue = o.Get(index);
a.CreateDataPropertyOrThrow(k, fromValue);
}
}
a.SetLength((uint) actualDeleteCount);
var length = len - actualDeleteCount + (uint) items.Length;
o.EnsureCapacity(length);
if ((ulong) items.Length < actualDeleteCount)
{
for (ulong k = actualStart; k < len - actualDeleteCount; k++)
{
var from = k + actualDeleteCount;
var to = k + (ulong) items.Length;
if (o.HasProperty(from))
{
var fromValue = o.Get(from);
o.Set(to, fromValue, throwOnError: false);
}
else
{
o.DeletePropertyOrThrow(to);
}
}
for (var k = len; k > len - actualDeleteCount + (ulong) items.Length; k--)
{
o.DeletePropertyOrThrow(k - 1);
}
}
else if ((ulong) items.Length > actualDeleteCount)
{
for (var k = len - actualDeleteCount; k > actualStart; k--)
{
var from = k + actualDeleteCount - 1;
var to = k + (ulong) items.Length - 1;
if (o.HasProperty(from))
{
var fromValue = o.Get(from);
o.Set(to, fromValue, throwOnError: true);
}
else
{
o.DeletePropertyOrThrow(to);
}
}
}
for (uint k = 0; k < items.Length; k++)
{
var e = items[k];
o.Set(k + actualStart, e, throwOnError: true);
}
o.SetLength(length);
return a.Target;
}
///
/// /https://tc39.es/ecma262/#sec-array.prototype.unshift
///
private JsValue Unshift(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
var len = o.GetLongLength();
var argCount = (uint) arguments.Length;
if (len + argCount > ArrayOperations.MaxArrayLikeLength)
{
Throw.TypeError(_realm, "Invalid array length");
}
// only prepare for larger if we cannot rely on default growth algorithm
if (len + argCount > 2 * len)
{
o.EnsureCapacity(len + argCount);
}
var minIndex = o.GetSmallestIndex(len);
for (var k = len; k > minIndex; k--)
{
var from = k - 1;
var to = k + argCount - 1;
if (o.TryGetValue(from, out var fromValue))
{
o.Set(to, fromValue, updateLength: false);
}
else
{
o.DeletePropertyOrThrow(to);
}
}
for (uint j = 0; j < argCount; j++)
{
o.Set(j, arguments[j], updateLength: false);
}
o.SetLength(len + argCount);
return len + argCount;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.sort
///
private JsValue Sort(JsValue thisObject, JsCallArguments arguments)
{
var obj = ArrayOperations.For(_realm, thisObject, forWrite: true);
var compareFn = GetCompareFunction(arguments.At(0));
var len = obj.GetLength();
if (len <= 1)
{
return obj.Target;
}
var items = new List((int) System.Math.Min(10_000, obj.GetLength()));
for (ulong k = 0; k < len; ++k)
{
if (obj.TryGetValue(k, out var kValue))
{
items.Add(kValue);
}
}
var itemCount = items.Count;
// don't eat inner exceptions
try
{
var comparer = ArrayComparer.WithFunction(_engine, compareFn);
IEnumerable ordered;
#if !NETCOREAPP
if (comparer is not null)
{
// sort won't be stable on .NET Framework, but at least it cant go into infinite loop when comparer is badly implemented
items.Sort(comparer);
ordered = items;
}
else
{
ordered = items.OrderBy(x => x, comparer);
}
#else
#if NET8_0_OR_GREATER
ordered = items.Order(comparer);
#else
ordered = items.OrderBy(x => x, comparer);
#endif
#endif
uint j = 0;
foreach (var item in ordered)
{
obj.Set(j, item, updateLength: false, throwOnError: true);
j++;
}
for (; j < len; ++j)
{
obj.DeletePropertyOrThrow(j);
}
}
catch (InvalidOperationException e)
{
throw e.InnerException ?? e;
}
return obj.Target;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.slice
///
private JsValue Slice(JsValue thisObject, JsCallArguments arguments)
{
var start = arguments.At(0);
var end = arguments.At(1);
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLongLength();
var relativeStart = TypeConverter.ToInteger(start);
ulong k;
if (relativeStart < 0)
{
k = (ulong) System.Math.Max(len + relativeStart, 0);
}
else
{
k = (ulong) System.Math.Min(TypeConverter.ToInteger(start), len);
}
ulong final;
if (end.IsUndefined())
{
final = (ulong) TypeConverter.ToNumber(len);
}
else
{
double relativeEnd = TypeConverter.ToInteger(end);
if (relativeEnd < 0)
{
final = (ulong) System.Math.Max(len + relativeEnd, 0);
}
else
{
final = (ulong) System.Math.Min(relativeEnd, len);
}
}
if (k < final && final - k > ArrayOperations.MaxArrayLength)
{
Throw.RangeError(_realm, "Invalid array length");
}
var length = (uint) System.Math.Max(0, (long) final - (long) k);
var a = _realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), length);
if (thisObject is JsArray ai && a is JsArray a2)
{
a2.CopyValues(ai, (uint) k, 0, length);
}
else
{
// slower path
var operations = ArrayOperations.For(a, forWrite: true);
for (uint n = 0; k < final; k++, n++)
{
if (o.TryGetValue(k, out var kValue))
{
operations.CreateDataPropertyOrThrow(n, kValue);
}
}
}
return a;
}
private JsValue Shift(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
var len = o.GetLength();
if (len == 0)
{
o.SetLength(0);
return Undefined;
}
var first = o.Get(0);
for (uint k = 1; k < len; k++)
{
var to = k - 1;
if (o.TryGetValue(k, out var fromVal))
{
o.Set(to, fromVal, throwOnError: false);
}
else
{
o.DeletePropertyOrThrow(to);
}
}
o.DeletePropertyOrThrow(len - 1);
o.SetLength(len - 1);
return first;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.reverse
///
private JsValue Reverse(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
var len = o.GetLongLength();
var middle = (ulong) System.Math.Floor(len / 2.0);
uint lower = 0;
while (lower != middle)
{
var upper = len - lower - 1;
var lowerExists = o.HasProperty(lower);
var lowerValue = lowerExists ? o.Get(lower) : null;
var upperExists = o.HasProperty(upper);
var upperValue = upperExists ? o.Get(upper) : null;
if (lowerExists && upperExists)
{
o.Set(lower, upperValue!, throwOnError: true);
o.Set(upper, lowerValue!, throwOnError: true);
}
if (!lowerExists && upperExists)
{
o.Set(lower, upperValue!, throwOnError: true);
o.DeletePropertyOrThrow(upper);
}
if (lowerExists && !upperExists)
{
o.DeletePropertyOrThrow(lower);
o.Set(upper, lowerValue!, throwOnError: true);
}
lower++;
}
return o.Target;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.join
///
private JsValue Join(JsValue thisObject, JsCallArguments arguments)
{
var separator = arguments.At(0);
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLength();
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;
}
if (!_joinStack.TryEnter(thisObject))
{
return JsString.Empty;
}
static string StringFromJsValue(JsValue value)
{
return value.IsNullOrUndefined()
? ""
: TypeConverter.ToString(value);
}
var s = StringFromJsValue(o.Get(0));
if (len == 1)
{
_joinStack.Exit();
return s;
}
using var sb = new ValueStringBuilder();
sb.Append(s);
for (uint k = 1; k < len; k++)
{
if (sep != "")
{
sb.Append(sep);
}
sb.Append(StringFromJsValue(o.Get(k)));
}
_joinStack.Exit();
return sb.ToString();
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.tolocalestring
///
private JsValue ToLocaleString(JsValue thisObject, JsCallArguments arguments)
{
const string Separator = ",";
var array = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = array.GetLength();
if (len == 0)
{
return JsString.Empty;
}
if (!_joinStack.TryEnter(thisObject))
{
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);
}
}
_joinStack.Exit();
return r.ToString();
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.concat
///
private JsValue Concat(JsValue thisObject, JsCallArguments arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
var items = new List(arguments.Length + 1) { o };
items.AddRange(arguments);
uint n = 0;
var a = _realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), 0);
var aOperations = ArrayOperations.For(a, forWrite: true);
for (var i = 0; i < items.Count; i++)
{
var e = items[i];
if (e is ObjectInstance { IsConcatSpreadable: true } oi)
{
if (e is JsArray eArray && a is JsArray a2)
{
a2.CopyValues(eArray, 0, n, eArray.GetLength());
n += eArray.GetLength();
}
else
{
var operations = ArrayOperations.For(oi, forWrite: false);
var len = operations.GetLongLength();
if (n + len > ArrayOperations.MaxArrayLikeLength)
{
Throw.TypeError(_realm, "Invalid array length");
}
for (uint k = 0; k < len; k++)
{
operations.TryGetValue(k, out var subElement);
aOperations.CreateDataPropertyOrThrow(n, subElement);
n++;
}
}
}
else
{
aOperations.CreateDataPropertyOrThrow(n, e);
n++;
}
}
// this is not in the specs, but is necessary in case the last element of the last
// array doesn't exist, and thus the length would not be incremented
a.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(n, PropertyFlag.OnlyWritable));
return a;
}
internal JsValue ToString(JsValue thisObject, JsCallArguments arguments)
{
var array = TypeConverter.ToObject(_realm, thisObject);
JsCallDelegate func;
if (array.Get("join") is ICallable joinFunc)
{
func = joinFunc.Call;
}
else
{
func = _realm.Intrinsics.Object.PrototypeObject.ToObjectString;
}
return func(array, Arguments.Empty);
}
private JsValue ToReversed(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var len = o.GetLongLength();
if (len == 0)
{
return new JsArray(_engine);
}
var a = CreateBackingArray(len);
ulong k = 0;
while (k < len)
{
var from = len - k - 1;
a[k++] = o.Get(from);
}
return new JsArray(_engine, a);
}
private JsValue ToSorted(JsValue thisObject, JsCallArguments arguments)
{
var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
var compareFn = GetCompareFunction(arguments.At(0));
var len = o.GetLongLength();
ValidateArrayLength(len);
if (len == 0)
{
return new JsArray(_engine);
}
var array = o.GetAll(skipHoles: true);
array = SortArray(array, compareFn);
return new JsArray(_engine, array);
}
private JsValue ToSpliced(JsValue thisObject, JsCallArguments arguments)
{
var start = arguments.At(0);
var deleteCount = arguments.At(1);
var o = ArrayOperations.For(_realm, TypeConverter.ToObject(_realm, thisObject), forWrite: false);
var len = o.GetLongLength();
var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
ulong actualStart;
if (double.IsNegativeInfinity(relativeStart))
{
actualStart = 0;
}
else if (relativeStart < 0)
{
actualStart = (ulong) System.Math.Max(len + relativeStart, 0);
}
else
{
actualStart = (ulong) System.Math.Min(relativeStart, len);
}
var items = System.Array.Empty();
ulong insertCount;
ulong actualDeleteCount;
if (arguments.Length == 0)
{
insertCount = 0;
actualDeleteCount = 0;
}
else if (arguments.Length == 1)
{
insertCount = 0;
actualDeleteCount = len - actualStart;
}
else
{
insertCount = (ulong) (arguments.Length - 2);
var dc = TypeConverter.ToIntegerOrInfinity(deleteCount);
actualDeleteCount = (ulong) System.Math.Min(System.Math.Max(dc, 0), len - actualStart);
items = [];
if (arguments.Length > 2)
{
items = new JsValue[arguments.Length - 2];
System.Array.Copy(arguments, 2, items, 0, items.Length);
}
}
var newLen = len + insertCount - actualDeleteCount;
if (newLen > ArrayOperations.MaxArrayLikeLength)
{
Throw.TypeError(_realm, "Invalid input length");
}
ValidateArrayLength(newLen);
var r = actualStart + actualDeleteCount;
var a = new JsArray(_engine, (uint) newLen);
uint i = 0;
while (i < actualStart)
{
a.SetIndexValue(i, o.Get(i), updateLength: false);
i++;
}
a.SetLength((uint) actualStart);
foreach (var item in items)
{
a.SetIndexValue(i++, item, updateLength: false);
}
while (i < newLen)
{
var fromValue = o.Get(r);
a.SetIndexValue(i, fromValue, updateLength: false);
i++;
r++;
}
a.SetLength(i);
return a;
}
private JsValue[] SortArray(IEnumerable array, ICallable? compareFn)
{
var comparer = ArrayComparer.WithFunction(_engine, compareFn);
try
{
return array.OrderBy(x => x, comparer).ToArray();
}
catch (InvalidOperationException e)
{
throw e.InnerException ?? e;
}
}
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;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.reduceright
///
private JsValue ReduceRight(JsValue thisObject, JsCallArguments arguments)
{
var callbackfn = arguments.At(0);
var initialValue = arguments.At(1);
var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
var len = o.GetLongLength();
var callable = GetCallable(callbackfn);
if (len == 0 && arguments.Length < 2)
{
Throw.TypeError(_realm);
}
long k = (long) (len - 1);
JsValue accumulator = Undefined;
if (arguments.Length > 1)
{
accumulator = initialValue;
}
else
{
var kPresent = false;
while (kPresent == false && k >= 0)
{
if ((kPresent = o.TryGetValue((ulong) k, out var temp)))
{
accumulator = temp;
}
k--;
}
if (kPresent == false)
{
Throw.TypeError(_realm);
}
}
var jsValues = new JsValue[4];
jsValues[3] = o.Target;
for (; k >= 0; k--)
{
if (o.TryGetValue((ulong) k, out var kvalue))
{
jsValues[0] = accumulator;
jsValues[1] = kvalue;
jsValues[2] = k;
accumulator = callable.Call(Undefined, jsValues);
}
}
return accumulator;
}
///
/// https://tc39.es/ecma262/#sec-array.prototype.push
///
public JsValue Push(JsValue thisObject, JsCallArguments arguments)
{
if (thisObject is JsArray { CanUseFastAccess: true } arrayInstance)
{
return arrayInstance.Push(arguments);
}
var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
var n = o.GetLongLength();
if (n + (ulong) arguments.Length > ArrayOperations.MaxArrayLikeLength)
{
Throw.TypeError(_realm, "Invalid array length");
}
foreach (var a in arguments)
{
o.Set(n, a, true);
n++;
}
o.SetLength(n);
return n;
}
public JsValue Pop(JsValue thisObject, JsCallArguments arguments)
{
if (thisObject is JsArray { CanUseFastAccess: true } array)
{
return array.Pop();
}
var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
ulong len = o.GetLongLength();
if (len == 0)
{
o.SetLength(0);
return Undefined;
}
len -= 1;
JsValue element = o.Get(len);
o.DeletePropertyOrThrow(len);
o.SetLength(len);
return element;
}
private JsValue[] CreateBackingArray(ulong length)
{
ValidateArrayLength(length);
return new JsValue[length];
}
private void ValidateArrayLength(ulong length)
{
if (length > ArrayOperations.MaxArrayLength)
{
Throw.RangeError(_engine.Realm, "Invalid array length " + length);
}
}
internal sealed class ArrayComparer : IComparer
{
///
/// Default instance without any compare function.
///
public static readonly ArrayComparer Default = new(null, null);
public static ArrayComparer WithFunction(Engine engine, ICallable? compare)
{
return compare is null ? Default : new ArrayComparer(engine, compare);
}
private readonly Engine? _engine;
private readonly ICallable? _compare;
private readonly JsValue[] _comparableArray = new JsValue[2];
private ArrayComparer(Engine? engine, ICallable? compare)
{
_engine = engine;
_compare = compare;
}
public int Compare(JsValue? x, JsValue? y)
{
var xIsNull = x is null;
var yIsNull = y is null;
if (xIsNull)
{
if (yIsNull)
{
return 0;
}
return 1;
}
else
{
if (yIsNull)
{
return -1;
}
}
var xUndefined = x!.IsUndefined();
var yUndefined = y!.IsUndefined();
if (xUndefined && yUndefined)
{
return 0;
}
if (xUndefined)
{
return 1;
}
if (yUndefined)
{
return -1;
}
if (_compare != null)
{
_engine!.RunBeforeExecuteStatementChecks(null);
_comparableArray[0] = x!;
_comparableArray[1] = y!;
var s = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray));
if (s < 0)
{
return -1;
}
if (s > 0)
{
return 1;
}
return 0;
}
var xString = TypeConverter.ToString(x!);
var yString = TypeConverter.ToString(y!);
return string.CompareOrdinal(xString, yString);
}
}
}