JsValueExtensions.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. using System.Diagnostics.Contracts;
  2. using System.Numerics;
  3. using System.Runtime.CompilerServices;
  4. using Esprima;
  5. using Jint.Native;
  6. using Jint.Native.Function;
  7. using Jint.Native.Object;
  8. using Jint.Native.Promise;
  9. using Jint.Native.RegExp;
  10. using Jint.Native.Symbol;
  11. using Jint.Native.TypedArray;
  12. using Jint.Runtime;
  13. namespace Jint
  14. {
  15. public static class JsValueExtensions
  16. {
  17. [Pure]
  18. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  19. public static bool IsPrimitive(this JsValue value)
  20. {
  21. return (value._type & (InternalTypes.Primitive | InternalTypes.Undefined | InternalTypes.Null)) != 0;
  22. }
  23. [Pure]
  24. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  25. public static bool IsUndefined(this JsValue value)
  26. {
  27. return value._type == InternalTypes.Undefined;
  28. }
  29. [Pure]
  30. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  31. internal static bool IsNullOrUndefined(this JsValue value)
  32. {
  33. return value._type < InternalTypes.Boolean;
  34. }
  35. [Pure]
  36. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  37. public static bool IsDate(this JsValue value)
  38. {
  39. return value is JsDate;
  40. }
  41. [Pure]
  42. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  43. public static bool IsPromise(this JsValue value)
  44. {
  45. return value is PromiseInstance;
  46. }
  47. [Pure]
  48. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  49. public static bool IsPrivateName(this JsValue value) => value._type == InternalTypes.PrivateName;
  50. [Pure]
  51. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  52. public static bool IsRegExp(this JsValue value)
  53. {
  54. if (value is not ObjectInstance oi)
  55. {
  56. return false;
  57. }
  58. var matcher = oi.Get(GlobalSymbolRegistry.Match);
  59. if (!matcher.IsUndefined())
  60. {
  61. return TypeConverter.ToBoolean(matcher);
  62. }
  63. return value is RegExpInstance;
  64. }
  65. [Pure]
  66. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  67. public static bool IsObject(this JsValue value)
  68. {
  69. return (value._type & InternalTypes.Object) != 0;
  70. }
  71. [Pure]
  72. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  73. public static bool IsString(this JsValue value)
  74. {
  75. return (value._type & InternalTypes.String) != 0;
  76. }
  77. [Pure]
  78. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  79. public static bool IsNumber(this JsValue value)
  80. {
  81. return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != 0;
  82. }
  83. [Pure]
  84. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  85. public static bool IsBigInt(this JsValue value)
  86. {
  87. return (value._type & InternalTypes.BigInt) != 0;
  88. }
  89. [Pure]
  90. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  91. internal static bool IsInteger(this JsValue value)
  92. {
  93. return value._type == InternalTypes.Integer;
  94. }
  95. [Pure]
  96. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  97. public static bool IsBoolean(this JsValue value)
  98. {
  99. return value._type == InternalTypes.Boolean;
  100. }
  101. [Pure]
  102. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  103. public static bool IsNull(this JsValue value)
  104. {
  105. return value._type == InternalTypes.Null;
  106. }
  107. [Pure]
  108. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  109. public static bool IsSymbol(this JsValue value)
  110. {
  111. return value._type == InternalTypes.Symbol;
  112. }
  113. [Pure]
  114. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  115. internal static bool CanBeHeldWeakly(this JsValue value, GlobalSymbolRegistry symbolRegistry)
  116. {
  117. return value.IsObject() || (value.IsSymbol() && !symbolRegistry.ContainsCustom(value));
  118. }
  119. [Pure]
  120. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  121. public static JsDate AsDate(this JsValue value)
  122. {
  123. if (!value.IsDate())
  124. {
  125. ExceptionHelper.ThrowArgumentException("The value is not a date");
  126. }
  127. return (JsDate) value;
  128. }
  129. [Pure]
  130. public static RegExpInstance AsRegExp(this JsValue value)
  131. {
  132. if (!value.IsRegExp())
  133. {
  134. ExceptionHelper.ThrowArgumentException("The value is not a regex");
  135. }
  136. return (RegExpInstance) value;
  137. }
  138. [Pure]
  139. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  140. public static ObjectInstance AsObject(this JsValue value)
  141. {
  142. if (!value.IsObject())
  143. {
  144. ExceptionHelper.ThrowArgumentException("The value is not an object");
  145. }
  146. return (ObjectInstance) value;
  147. }
  148. [Pure]
  149. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  150. public static TInstance AsInstance<TInstance>(this JsValue value) where TInstance : class
  151. {
  152. if (!value.IsObject())
  153. {
  154. ExceptionHelper.ThrowArgumentException("The value is not an object");
  155. }
  156. return (value as TInstance)!;
  157. }
  158. [Pure]
  159. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  160. public static JsArray AsArray(this JsValue value)
  161. {
  162. if (!value.IsArray())
  163. {
  164. ExceptionHelper.ThrowArgumentException("The value is not an array");
  165. }
  166. return (JsArray) value;
  167. }
  168. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  169. public static bool AsBoolean(this JsValue value)
  170. {
  171. if (value._type != InternalTypes.Boolean)
  172. {
  173. ThrowWrongTypeException(value, "boolean");
  174. }
  175. return ((JsBoolean) value)._value;
  176. }
  177. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  178. public static double AsNumber(this JsValue value)
  179. {
  180. if (!value.IsNumber())
  181. {
  182. ThrowWrongTypeException(value, "number");
  183. }
  184. return ((JsNumber) value)._value;
  185. }
  186. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  187. internal static int AsInteger(this JsValue value)
  188. {
  189. return (int) ((JsNumber) value)._value;
  190. }
  191. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  192. internal static BigInteger AsBigInt(this JsValue value)
  193. {
  194. return ((JsBigInt) value)._value;
  195. }
  196. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  197. public static string AsString(this JsValue value)
  198. {
  199. if (!value.IsString())
  200. {
  201. ThrowWrongTypeException(value, "string");
  202. }
  203. return value.ToString();
  204. }
  205. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  206. public static bool IsUint8Array(this JsValue value)
  207. {
  208. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.Uint8 };
  209. }
  210. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  211. public static byte[] AsUint8Array(this JsValue value)
  212. {
  213. if (!value.IsUint8Array())
  214. {
  215. ThrowWrongTypeException(value, "Uint8Array");
  216. }
  217. return ((TypedArrayInstance) value).ToNativeArray<byte>();
  218. }
  219. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  220. public static bool IsUint8ClampedArray(this JsValue value)
  221. {
  222. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.Uint8C };
  223. }
  224. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  225. public static byte[] AsUint8ClampedArray(this JsValue value)
  226. {
  227. if (!value.IsUint8ClampedArray())
  228. {
  229. ThrowWrongTypeException(value, "Uint8ClampedArray");
  230. }
  231. return ((TypedArrayInstance) value).ToNativeArray<byte>();
  232. }
  233. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  234. public static bool IsInt8Array(this JsValue value)
  235. {
  236. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.Int8 };
  237. }
  238. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  239. public static sbyte[] AsInt8Array(this JsValue value)
  240. {
  241. if (!value.IsInt8Array())
  242. {
  243. ThrowWrongTypeException(value, "Int8Array");
  244. }
  245. return ((TypedArrayInstance) value).ToNativeArray<sbyte>();
  246. }
  247. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  248. public static bool IsInt16Array(this JsValue value)
  249. {
  250. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.Int16 };
  251. }
  252. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  253. public static short[] AsInt16Array(this JsValue value)
  254. {
  255. if (!value.IsInt16Array())
  256. {
  257. ThrowWrongTypeException(value, "Int16Array");
  258. }
  259. return ((TypedArrayInstance) value).ToNativeArray<short>();
  260. }
  261. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  262. public static bool IsUint16Array(this JsValue value)
  263. {
  264. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.Uint16 };
  265. }
  266. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  267. public static ushort[] AsUint16Array(this JsValue value)
  268. {
  269. if (!value.IsUint16Array())
  270. {
  271. ThrowWrongTypeException(value, "Uint16Array");
  272. }
  273. return ((TypedArrayInstance) value).ToNativeArray<ushort>();
  274. }
  275. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  276. public static bool IsInt32Array(this JsValue value)
  277. {
  278. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.Int32 };
  279. }
  280. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  281. public static int[] AsInt32Array(this JsValue value)
  282. {
  283. if (!value.IsInt32Array())
  284. {
  285. ThrowWrongTypeException(value, "Int32Array");
  286. }
  287. return ((TypedArrayInstance) value).ToNativeArray<int>();
  288. }
  289. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  290. public static bool IsUint32Array(this JsValue value)
  291. {
  292. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.Uint32 };
  293. }
  294. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  295. public static uint[] AsUint32Array(this JsValue value)
  296. {
  297. if (!value.IsUint32Array())
  298. {
  299. ThrowWrongTypeException(value, "Uint32Array");
  300. }
  301. return ((TypedArrayInstance) value).ToNativeArray<uint>();
  302. }
  303. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  304. public static bool IsBigInt64Array(this JsValue value)
  305. {
  306. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.BigInt64 };
  307. }
  308. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  309. public static long[] AsBigInt64Array(this JsValue value)
  310. {
  311. if (!value.IsBigInt64Array())
  312. {
  313. ThrowWrongTypeException(value, "BigInt64Array");
  314. }
  315. return ((TypedArrayInstance) value).ToNativeArray<long>();
  316. }
  317. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  318. public static bool IsBigUint64Array(this JsValue value)
  319. {
  320. return value is TypedArrayInstance { _arrayElementType: TypedArrayElementType.BigUint64 };
  321. }
  322. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  323. public static ulong[] AsBigUint64Array(this JsValue value)
  324. {
  325. if (!value.IsBigUint64Array())
  326. {
  327. ThrowWrongTypeException(value, "BigUint64Array");
  328. }
  329. return ((TypedArrayInstance) value).ToNativeArray<ulong>();
  330. }
  331. [Pure]
  332. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  333. public static T? TryCast<T>(this JsValue value) where T : class
  334. {
  335. return value as T;
  336. }
  337. [Pure]
  338. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  339. public static T? TryCast<T>(this JsValue value, Action<JsValue> fail) where T : class
  340. {
  341. if (value is T o)
  342. {
  343. return o;
  344. }
  345. fail.Invoke(value);
  346. return null;
  347. }
  348. [Pure]
  349. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  350. public static T? As<T>(this JsValue value) where T : ObjectInstance
  351. {
  352. if (value.IsObject())
  353. {
  354. return value as T;
  355. }
  356. return null;
  357. }
  358. [Pure]
  359. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  360. public static FunctionInstance AsFunctionInstance(this JsValue value)
  361. {
  362. if (value is not FunctionInstance instance)
  363. {
  364. ThrowWrongTypeException(value, "FunctionInstance");
  365. return null!;
  366. }
  367. return instance;
  368. }
  369. [Pure]
  370. public static JsValue Call(this JsValue value)
  371. {
  372. if (value is ObjectInstance objectInstance)
  373. {
  374. var engine = objectInstance.Engine;
  375. return engine.Call(value, Array.Empty<JsValue>());
  376. }
  377. return ThrowNotObject(value);
  378. }
  379. [Pure]
  380. public static JsValue Call(this JsValue value, JsValue arg1)
  381. {
  382. if (value is ObjectInstance objectInstance)
  383. {
  384. var engine = objectInstance.Engine;
  385. var arguments = engine._jsValueArrayPool.RentArray(1);
  386. arguments[0] = arg1;
  387. var result = engine.Call(value, arguments);
  388. engine._jsValueArrayPool.ReturnArray(arguments);
  389. return result;
  390. }
  391. return ThrowNotObject(value);
  392. }
  393. [Pure]
  394. public static JsValue Call(this JsValue value, JsValue arg1, JsValue arg2)
  395. {
  396. if (value is ObjectInstance objectInstance)
  397. {
  398. var engine = objectInstance.Engine;
  399. var arguments = engine._jsValueArrayPool.RentArray(2);
  400. arguments[0] = arg1;
  401. arguments[1] = arg2;
  402. var result = engine.Call(value, arguments);
  403. engine._jsValueArrayPool.ReturnArray(arguments);
  404. return result;
  405. }
  406. return ThrowNotObject(value);
  407. }
  408. [Pure]
  409. public static JsValue Call(this JsValue value, JsValue arg1, JsValue arg2, JsValue arg3)
  410. {
  411. if (value is ObjectInstance objectInstance)
  412. {
  413. var engine = objectInstance.Engine;
  414. var arguments = engine._jsValueArrayPool.RentArray(3);
  415. arguments[0] = arg1;
  416. arguments[1] = arg2;
  417. arguments[2] = arg3;
  418. var result = engine.Call(value, arguments);
  419. engine._jsValueArrayPool.ReturnArray(arguments);
  420. return result;
  421. }
  422. return ThrowNotObject(value);
  423. }
  424. [Pure]
  425. public static JsValue Call(this JsValue value, params JsValue[] arguments)
  426. {
  427. if (value is ObjectInstance objectInstance)
  428. {
  429. return objectInstance.Engine.Call(value, arguments);
  430. }
  431. return ThrowNotObject(value);
  432. }
  433. [Pure]
  434. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  435. public static JsValue Call(this JsValue value, JsValue thisObj, JsValue[] arguments)
  436. {
  437. if (value is ObjectInstance objectInstance)
  438. {
  439. return objectInstance.Engine.Call(value, thisObj, arguments);
  440. }
  441. return ThrowNotObject(value);
  442. }
  443. [MethodImpl(MethodImplOptions.NoInlining)]
  444. private static JsValue ThrowNotObject(JsValue value)
  445. {
  446. ExceptionHelper.ThrowArgumentException(value + " is not object");
  447. return null;
  448. }
  449. /// <summary>
  450. /// If the value is a Promise
  451. /// 1. If "Fulfilled" returns the value it was fulfilled with
  452. /// 2. If "Rejected" throws "PromiseRejectedException" with the rejection reason
  453. /// 3. If "Pending" throws "InvalidOperationException". Should be called only in "Settled" state
  454. /// Else
  455. /// returns the value intact
  456. /// </summary>
  457. /// <param name="value">value to unwrap</param>
  458. /// <returns>inner value if Promise the value itself otherwise</returns>
  459. public static JsValue UnwrapIfPromise(this JsValue value)
  460. {
  461. if (value is PromiseInstance promise)
  462. {
  463. switch (promise.State)
  464. {
  465. case PromiseState.Pending:
  466. ExceptionHelper.ThrowInvalidOperationException("'UnwrapIfPromise' called before Promise was settled");
  467. return null;
  468. case PromiseState.Fulfilled:
  469. return promise.Value;
  470. case PromiseState.Rejected:
  471. ExceptionHelper.ThrowPromiseRejectedException(promise.Value);
  472. return null;
  473. default:
  474. ExceptionHelper.ThrowArgumentOutOfRangeException();
  475. return null;
  476. }
  477. }
  478. return value;
  479. }
  480. [MethodImpl(MethodImplOptions.NoInlining)]
  481. private static void ThrowWrongTypeException(JsValue value, string expectedType)
  482. {
  483. ExceptionHelper.ThrowArgumentException($"Expected {expectedType} but got {value._type}");
  484. }
  485. internal static BigInteger ToBigInteger(this JsValue value, Engine engine)
  486. {
  487. try
  488. {
  489. return TypeConverter.ToBigInt(value);
  490. }
  491. catch (ParserException ex)
  492. {
  493. ExceptionHelper.ThrowSyntaxError(engine.Realm, ex.Message);
  494. return default;
  495. }
  496. }
  497. internal static ICallable GetCallable(this JsValue source, Realm realm)
  498. {
  499. if (source is ICallable callable)
  500. {
  501. return callable;
  502. }
  503. ExceptionHelper.ThrowTypeError(realm, "Argument must be callable");
  504. return null;
  505. }
  506. }
  507. }