using System.Diagnostics.Contracts; using System.Numerics; using System.Runtime.CompilerServices; using Jint.Native; using Jint.Native.Function; using Jint.Native.Object; using Jint.Native.Promise; using Jint.Native.Symbol; using Jint.Native.TypedArray; using Jint.Runtime; namespace Jint; public static class JsValueExtensions { [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrimitive(this JsValue value) { return (value._type & (InternalTypes.Primitive | InternalTypes.Undefined | InternalTypes.Null)) != InternalTypes.Empty; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsUndefined(this JsValue value) { return value._type == InternalTypes.Undefined; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsArray(this JsValue value) { return value is JsArray; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsNullOrUndefined(this JsValue value) { return value._type < InternalTypes.Boolean; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsDate(this JsValue value) { return value is JsDate; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPromise(this JsValue value) { return value is JsPromise; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrivateName(this JsValue value) => value._type == InternalTypes.PrivateName; [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsRegExp(this JsValue value) { if (value is not ObjectInstance oi) { return false; } var matcher = oi.Get(GlobalSymbolRegistry.Match); if (!matcher.IsUndefined()) { return TypeConverter.ToBoolean(matcher); } return value is JsRegExp; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsObject(this JsValue value) { return (value._type & InternalTypes.Object) != InternalTypes.Empty; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsString(this JsValue value) { return (value._type & InternalTypes.String) != InternalTypes.Empty; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNumber(this JsValue value) { return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != InternalTypes.Empty; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBigInt(this JsValue value) { return (value._type & InternalTypes.BigInt) != InternalTypes.Empty; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsInteger(this JsValue value) { return value._type == InternalTypes.Integer; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBoolean(this JsValue value) { return value._type == InternalTypes.Boolean; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNull(this JsValue value) { return value._type == InternalTypes.Null; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsSymbol(this JsValue value) { return value._type == InternalTypes.Symbol; } /// /// https://tc39.es/ecma262/#sec-canbeheldweakly /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool CanBeHeldWeakly(this JsValue value, GlobalSymbolRegistry symbolRegistry) { return value.IsObject() || (value.IsSymbol() && symbolRegistry.KeyForSymbol(value).IsUndefined()); } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsDate AsDate(this JsValue value) { if (!value.IsDate()) { Throw.ArgumentException("The value is not a date"); } return (JsDate) value; } [Pure] public static JsRegExp AsRegExp(this JsValue value) { if (!value.IsRegExp()) { Throw.ArgumentException("The value is not a regex"); } return (JsRegExp) value; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ObjectInstance AsObject(this JsValue value) { if (!value.IsObject()) { Throw.ArgumentException("The value is not an object"); } return (ObjectInstance) value; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TInstance AsInstance(this JsValue value) where TInstance : class { if (!value.IsObject()) { Throw.ArgumentException("The value is not an object"); } return (value as TInstance)!; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsArray AsArray(this JsValue value) { if (!value.IsArray()) { Throw.ArgumentException("The value is not an array"); } return (JsArray) value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool AsBoolean(this JsValue value) { if (value._type != InternalTypes.Boolean) { ThrowWrongTypeException(value, "boolean"); } return ((JsBoolean) value)._value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double AsNumber(this JsValue value) { if (!value.IsNumber()) { ThrowWrongTypeException(value, "number"); } return ((JsNumber) value)._value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int AsInteger(this JsValue value) { return (int) ((JsNumber) value)._value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static BigInteger AsBigInt(this JsValue value) { return ((JsBigInt) value)._value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string AsString(this JsValue value) { if (!value.IsString()) { ThrowWrongTypeException(value, "string"); } return value.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsArrayBuffer(this JsValue value) { return value is JsArrayBuffer; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[]? AsArrayBuffer(this JsValue value) { if (!value.IsArrayBuffer()) { ThrowWrongTypeException(value, "ArrayBuffer"); } return ((JsArrayBuffer) value)._arrayBufferData; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsDataView(this JsValue value) { return value is JsDataView; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[]? AsDataView(this JsValue value) { if (!value.IsDataView()) { ThrowWrongTypeException(value, "DataView"); } var dataView = (JsDataView) value; if (dataView._viewedArrayBuffer?._arrayBufferData == null) { return null; // should not happen } // create view var res = new byte[dataView._byteLength]; Array.Copy(dataView._viewedArrayBuffer._arrayBufferData!, dataView._byteOffset, res, 0, dataView._byteLength); return res; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsUint8Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] AsUint8Array(this JsValue value) { if (!value.IsUint8Array()) { ThrowWrongTypeException(value, "Uint8Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsUint8ClampedArray(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8C }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] AsUint8ClampedArray(this JsValue value) { if (!value.IsUint8ClampedArray()) { ThrowWrongTypeException(value, "Uint8ClampedArray"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsInt8Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Int8 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte[] AsInt8Array(this JsValue value) { if (!value.IsInt8Array()) { ThrowWrongTypeException(value, "Int8Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsInt16Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Int16 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short[] AsInt16Array(this JsValue value) { if (!value.IsInt16Array()) { ThrowWrongTypeException(value, "Int16Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsUint16Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint16 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort[] AsUint16Array(this JsValue value) { if (!value.IsUint16Array()) { ThrowWrongTypeException(value, "Uint16Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsInt32Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Int32 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int[] AsInt32Array(this JsValue value) { if (!value.IsInt32Array()) { ThrowWrongTypeException(value, "Int32Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsUint32Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint32 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint[] AsUint32Array(this JsValue value) { if (!value.IsUint32Array()) { ThrowWrongTypeException(value, "Uint32Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBigInt64Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.BigInt64 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long[] AsBigInt64Array(this JsValue value) { if (!value.IsBigInt64Array()) { ThrowWrongTypeException(value, "BigInt64Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBigUint64Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.BigUint64 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong[] AsBigUint64Array(this JsValue value) { if (!value.IsBigUint64Array()) { ThrowWrongTypeException(value, "BigUint64Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFloat16Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float16 }; } #if SUPPORTS_HALF [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Half[] AsFloat16Array(this JsValue value) { if (!value.IsFloat16Array()) { ThrowWrongTypeException(value, "Float16Array"); } return ((JsTypedArray) value).ToNativeArray(); } #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFloat32Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float32 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float[] AsFloat32Array(this JsValue value) { if (!value.IsFloat32Array()) { ThrowWrongTypeException(value, "Float32Array"); } return ((JsTypedArray) value).ToNativeArray(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFloat64Array(this JsValue value) { return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float64 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double[] AsFloat64Array(this JsValue value) { if (!value.IsFloat64Array()) { ThrowWrongTypeException(value, "Float64Array"); } return ((JsTypedArray) value).ToNativeArray(); } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? TryCast(this JsValue value) where T : class { return value as T; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? TryCast(this JsValue value, Action fail) where T : class { if (value is T o) { return o; } fail.Invoke(value); return null; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? As(this JsValue value) where T : ObjectInstance { if (value.IsObject()) { return value as T; } return null; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Function AsFunctionInstance(this JsValue value) { if (value is not Function instance) { ThrowWrongTypeException(value, "FunctionInstance"); return null!; } return instance; } [Pure] public static JsValue Call(this JsValue value) { if (value is ObjectInstance objectInstance) { var engine = objectInstance.Engine; return engine.Call(value, Array.Empty()); } return ThrowNotObject(value); } [Pure] public static JsValue Call(this JsValue value, JsValue arg1) { if (value is ObjectInstance objectInstance) { var engine = objectInstance.Engine; var arguments = engine._jsValueArrayPool.RentArray(1); arguments[0] = arg1; var result = engine.Call(value, arguments); engine._jsValueArrayPool.ReturnArray(arguments); return result; } return ThrowNotObject(value); } [Pure] public static JsValue Call(this JsValue value, JsValue arg1, JsValue arg2) { if (value is ObjectInstance objectInstance) { var engine = objectInstance.Engine; var arguments = engine._jsValueArrayPool.RentArray(2); arguments[0] = arg1; arguments[1] = arg2; var result = engine.Call(value, arguments); engine._jsValueArrayPool.ReturnArray(arguments); return result; } return ThrowNotObject(value); } [Pure] public static JsValue Call(this JsValue value, JsValue arg1, JsValue arg2, JsValue arg3) { if (value is ObjectInstance objectInstance) { var engine = objectInstance.Engine; var arguments = engine._jsValueArrayPool.RentArray(3); arguments[0] = arg1; arguments[1] = arg2; arguments[2] = arg3; var result = engine.Call(value, arguments); engine._jsValueArrayPool.ReturnArray(arguments); return result; } return ThrowNotObject(value); } [Pure] public static JsValue Call(this JsValue value, params JsCallArguments arguments) { if (value is ObjectInstance objectInstance) { return objectInstance.Engine.Call(value, arguments); } return ThrowNotObject(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsValue Call(this JsValue value, JsValue thisObj, JsCallArguments arguments) { if (value is ObjectInstance objectInstance) { return objectInstance.Engine.Call(value, thisObj, arguments); } return ThrowNotObject(value); } [MethodImpl(MethodImplOptions.NoInlining)] private static JsValue ThrowNotObject(JsValue value) { Throw.ArgumentException(value + " is not object"); return null; } /// /// If the value is a Promise /// 1. If "Fulfilled" returns the value it was fulfilled with /// 2. If "Rejected" throws "PromiseRejectedException" with the rejection reason /// 3. If "Pending" throws "InvalidOperationException". Should be called only in "Settled" state /// Else /// returns the value intact /// /// value to unwrap /// inner value if Promise the value itself otherwise public static JsValue UnwrapIfPromise(this JsValue value) => UnwrapIfPromise(value, TimeSpan.FromSeconds(10)); /// /// If the value is a Promise /// 1. If "Fulfilled" returns the value it was fulfilled with /// 2. If "Rejected" throws "PromiseRejectedException" with the rejection reason /// 3. If "Pending" throws "InvalidOperationException". Should be called only in "Settled" state /// Else /// returns the value intact /// /// value to unwrap /// timeout to wait /// inner value if Promise the value itself otherwise public static JsValue UnwrapIfPromise(this JsValue value, TimeSpan timeout) { if (value is JsPromise promise) { var engine = promise.Engine; var completedEvent = promise.CompletedEvent; engine.RunAvailableContinuations(); if (!completedEvent.Wait(timeout)) { Throw.PromiseRejectedException($"Timeout of {timeout} reached"); } switch (promise.State) { case PromiseState.Pending: Throw.InvalidOperationException("'UnwrapIfPromise' called before Promise was settled"); return null; case PromiseState.Fulfilled: return promise.Value; case PromiseState.Rejected: Throw.PromiseRejectedException(promise.Value); return null; default: Throw.ArgumentOutOfRangeException(); return null; } } return value; } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowWrongTypeException(JsValue value, string expectedType) { Throw.ArgumentException($"Expected {expectedType} but got {value._type}"); } internal static BigInteger ToBigInteger(this JsValue value, Engine engine) { try { return TypeConverter.ToBigInt(value); } catch (ParseErrorException ex) { Throw.SyntaxError(engine.Realm, ex.Message); return default; } } internal static ICallable GetCallable(this JsValue source, Realm realm) { if (source is ICallable callable) { return callable; } Throw.TypeError(realm, "Argument must be callable"); return null; } /// /// https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption /// internal static uint? GetArrayBufferMaxByteLengthOption(this JsValue options) { if (options is not JsObject oi) { return null; } var maxByteLength = options.Get("maxByteLength"); if (maxByteLength.IsUndefined()) { return null; } return TypeConverter.ToIndex(oi.Engine.Realm, maxByteLength); } /// /// https://tc39.es/ecma262/#sec-canonicalize-keyed-collection-key /// internal static JsValue CanonicalizeKeyedCollectionKey(this JsValue key) { return key is JsNumber number && number.IsNegativeZero() ? JsNumber.PositiveZero : key; } }