using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Jint.Native.ArrayBuffer;
using Jint.Native.Number;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
namespace Jint.Native.TypedArray
{
public sealed class TypedArrayInstance : ObjectInstance
{
internal TypedArrayContentType _contentType;
internal readonly TypedArrayElementType _arrayElementType;
internal ArrayBufferInstance _viewedArrayBuffer;
internal uint _byteLength;
internal int _byteOffset;
private readonly Intrinsics _intrinsics;
internal uint _arrayLength;
private TypedArrayInstance(
Engine engine,
Intrinsics intrinsics) : base(engine)
{
_intrinsics = intrinsics;
_viewedArrayBuffer = new ArrayBufferInstance(engine, 0);
}
internal TypedArrayInstance(
Engine engine,
Intrinsics intrinsics,
TypedArrayElementType type,
uint length) : this(engine, intrinsics)
{
_arrayElementType = type;
_arrayLength = length;
}
internal JsValue this[int index]
{
get => IntegerIndexedElementGet(index);
set => IntegerIndexedElementSet(index, value);
}
public override uint Length => _viewedArrayBuffer.IsDetachedBuffer ? 0 : _arrayLength;
internal override bool IsIntegerIndexedArray => true;
///
/// https://tc39.es/ecma262/#sec-allocatetypedarraybuffer
///
internal void AllocateTypedArrayBuffer(ulong len)
{
var elementSize = _arrayElementType.GetElementSize();
var byteLength = elementSize * len;
var data = _intrinsics.ArrayBuffer.AllocateArrayBuffer(_intrinsics.ArrayBuffer, byteLength);
_byteLength = (uint) byteLength;
_arrayLength = (uint) len;
_viewedArrayBuffer = data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool HasProperty(long numericIndex)
{
return IsValidIntegerIndex(numericIndex);
}
///
/// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p
///
public override bool HasProperty(JsValue property)
{
if (property.IsString())
{
var numericIndex = TypeConverter.CanonicalNumericIndexString(property);
if (numericIndex is not null)
{
return IsValidIntegerIndex(numericIndex.Value);
}
}
return base.HasProperty(property);
}
///
/// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p
///
public override PropertyDescriptor GetOwnProperty(JsValue property)
{
var numericIndex = TypeConverter.CanonicalNumericIndexString(property);
if (numericIndex is not null)
{
var value = IntegerIndexedElementGet(numericIndex.Value);
if (value.IsUndefined())
{
return PropertyDescriptor.Undefined;
}
return new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable);
}
return base.GetOwnProperty(property);
}
///
/// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver
///
public override JsValue Get(JsValue property, JsValue receiver)
{
var numericIndex = TypeConverter.CanonicalNumericIndexString(property);
if (numericIndex is not null)
{
return IntegerIndexedElementGet(numericIndex.Value);
}
return base.Get(property, receiver);
}
///
/// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
///
public override bool Set(JsValue property, JsValue value, JsValue receiver)
{
var numericIndex = TypeConverter.CanonicalNumericIndexString(property);
if (numericIndex is not null)
{
IntegerIndexedElementSet(numericIndex.Value, value);
return true;
}
return base.Set(property, value, receiver);
}
///
/// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc
///
public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
{
if (property.IsNumber() || property.IsString())
{
var numericIndex = TypeConverter.CanonicalNumericIndexString(property);
if (numericIndex is not null)
{
if (!IsValidIntegerIndex(numericIndex.Value))
{
return false;
}
if (desc.ConfigurableSet && !desc.Configurable)
{
return false;
}
if (desc.EnumerableSet && !desc.Enumerable)
{
return false;
}
if (desc.IsAccessorDescriptor())
{
return false;
}
if (desc.WritableSet && !desc.Writable)
{
return false;
}
IntegerIndexedElementSet(numericIndex.Value, desc.Value);
return true;
}
}
return base.DefineOwnProperty(property, desc);
}
///
/// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys
///
public override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol)
{
var keys = new List();
if (!_viewedArrayBuffer.IsDetachedBuffer)
{
var length = Length;
for (uint i = 0; i < length; ++i)
{
keys.Add(JsString.Create(i));
}
}
if (_properties is not null)
{
foreach (var pair in _properties)
{
keys.Add(pair.Key.Name);
}
}
if (_symbols is not null)
{
foreach (var pair in _symbols)
{
keys.Add(pair.Key);
}
}
return keys;
}
///
/// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p
///
public override bool Delete(JsValue property)
{
var numericIndex = TypeConverter.CanonicalNumericIndexString(property);
if (numericIndex is not null)
{
return !IsValidIntegerIndex(numericIndex.Value);
}
return base.Delete(property);
}
// helper to prevent floating points
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private JsValue IntegerIndexedElementGet(int index)
{
if (!IsValidIntegerIndex(index))
{
return Undefined;
}
return DoIntegerIndexedElementGet(index);
}
///
/// https://tc39.es/ecma262/#sec-integerindexedelementget
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private JsValue IntegerIndexedElementGet(double index)
{
if (!IsValidIntegerIndex(index))
{
return Undefined;
}
return DoIntegerIndexedElementGet((int) index);
}
private JsValue DoIntegerIndexedElementGet(int index)
{
var offset = _byteOffset;
var elementType = _arrayElementType;
var elementSize = elementType.GetElementSize();
var indexedPosition = index * elementSize + offset;
var value = _viewedArrayBuffer.GetValueFromBuffer(indexedPosition, elementType, true, ArrayBufferOrder.Unordered);
return _arrayElementType.FitsInt32() ? JsNumber.Create((int) value) : JsNumber.Create(value);
}
// helper tot prevent floating point
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void IntegerIndexedElementSet(int index, JsValue value)
{
var numValue = _contentType == TypedArrayContentType.BigInt
? TypeConverter.ToBigInt(value)
: TypeConverter.ToNumber(value);
if (IsValidIntegerIndex(index))
{
DoIntegerIndexedElementSet(index, numValue);
}
}
///
/// https://tc39.es/ecma262/#sec-integerindexedelementset
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void IntegerIndexedElementSet(double index, JsValue value)
{
var numValue = _contentType == TypedArrayContentType.BigInt
? TypeConverter.ToBigInt(value)
: TypeConverter.ToNumber(value);
if (IsValidIntegerIndex(index))
{
DoIntegerIndexedElementSet((int) index, numValue);
}
}
internal void DoIntegerIndexedElementSet(int index, double numValue)
{
var offset = _byteOffset;
var elementType = _arrayElementType;
var elementSize = elementType.GetElementSize();
var indexedPosition = index * elementSize + offset;
_viewedArrayBuffer.SetValueInBuffer(indexedPosition, elementType, numValue, true, ArrayBufferOrder.Unordered);
}
///
/// https://tc39.es/ecma262/#sec-isvalidintegerindex
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsValidIntegerIndex(double index)
{
return !_viewedArrayBuffer.IsDetachedBuffer
&& IsIntegralNumber(index)
&& !NumberInstance.IsNegativeZero(index)
&& (uint) index < _arrayLength;
}
///
/// https://tc39.es/ecma262/#sec-isvalidintegerindex
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsValidIntegerIndex(int index)
{
return !_viewedArrayBuffer.IsDetachedBuffer && (uint) index < _arrayLength;
}
///
/// https://tc39.es/ecma262/#sec-isintegralnumber
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIntegralNumber(double value)
{
return !double.IsNaN(value)
&& !double.IsInfinity(value)
&& System.Math.Floor(System.Math.Abs(value)) == System.Math.Abs(value);
}
internal T[] ToNativeArray()
{
var conversionType = typeof(T);
var elementSize = _arrayElementType.GetElementSize();
var byteOffset = _byteOffset;
var buffer = _viewedArrayBuffer;
var array = new T[Length];
for (var i = 0; i < array.Length; ++i)
{
var indexedPosition = i * elementSize + byteOffset;
var value = buffer.RawBytesToNumeric(_arrayElementType, indexedPosition, BitConverter.IsLittleEndian);
array[i] = (T) Convert.ChangeType(value, conversionType);
}
return array;
}
}
}