TypeConverter.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. using System.Globalization;
  2. using System.Numerics;
  3. using System.Runtime.CompilerServices;
  4. using Jint.Native;
  5. using Jint.Native.Number;
  6. using Jint.Native.Object;
  7. using Jint.Native.String;
  8. using Jint.Native.Symbol;
  9. using Jint.Runtime.Interop;
  10. using Jint.Extensions;
  11. namespace Jint.Runtime;
  12. public static class TypeConverter
  13. {
  14. // how many decimals to check when determining if double is actually an int
  15. private const double DoubleIsIntegerTolerance = double.Epsilon * 100;
  16. private static readonly string[] intToString = new string[1024];
  17. private static readonly string[] charToString = new string[256];
  18. static TypeConverter()
  19. {
  20. for (var i = 0; i < intToString.Length; ++i)
  21. {
  22. intToString[i] = i.ToString(CultureInfo.InvariantCulture);
  23. }
  24. for (var i = 0; i < charToString.Length; ++i)
  25. {
  26. var c = (char) i;
  27. charToString[i] = c.ToString();
  28. }
  29. }
  30. /// <summary>
  31. /// https://tc39.es/ecma262/#sec-toprimitive
  32. /// </summary>
  33. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  34. public static JsValue ToPrimitive(JsValue input, Types preferredType = Types.Empty)
  35. {
  36. return input is not ObjectInstance oi
  37. ? input
  38. : ToPrimitiveObjectInstance(oi, preferredType);
  39. }
  40. private static JsValue ToPrimitiveObjectInstance(ObjectInstance oi, Types preferredType)
  41. {
  42. var exoticToPrim = oi.GetMethod(GlobalSymbolRegistry.ToPrimitive);
  43. if (exoticToPrim is not null)
  44. {
  45. var hint = preferredType switch
  46. {
  47. Types.String => JsString.StringString,
  48. Types.Number => JsString.NumberString,
  49. _ => JsString.DefaultString
  50. };
  51. var str = exoticToPrim.Call(oi, new JsValue[] { hint });
  52. if (str.IsPrimitive())
  53. {
  54. return str;
  55. }
  56. if (str.IsObject())
  57. {
  58. ExceptionHelper.ThrowTypeError(oi.Engine.Realm, "Cannot convert object to primitive value");
  59. }
  60. }
  61. return OrdinaryToPrimitive(oi, preferredType == Types.Empty ? Types.Number : preferredType);
  62. }
  63. /// <summary>
  64. /// https://tc39.es/ecma262/#sec-ordinarytoprimitive
  65. /// </summary>
  66. internal static JsValue OrdinaryToPrimitive(ObjectInstance input, Types hint = Types.Empty)
  67. {
  68. JsString property1;
  69. JsString property2;
  70. if (hint == Types.String)
  71. {
  72. property1 = (JsString) "toString";
  73. property2 = (JsString) "valueOf";
  74. }
  75. else if (hint == Types.Number)
  76. {
  77. property1 = (JsString) "valueOf";
  78. property2 = (JsString) "toString";
  79. }
  80. else
  81. {
  82. ExceptionHelper.ThrowTypeError(input.Engine.Realm);
  83. return null;
  84. }
  85. if (input.Get(property1) is ICallable method1)
  86. {
  87. var val = method1.Call(input, Arguments.Empty);
  88. if (val.IsPrimitive())
  89. {
  90. return val;
  91. }
  92. }
  93. if (input.Get(property2) is ICallable method2)
  94. {
  95. var val = method2.Call(input, Arguments.Empty);
  96. if (val.IsPrimitive())
  97. {
  98. return val;
  99. }
  100. }
  101. ExceptionHelper.ThrowTypeError(input.Engine.Realm);
  102. return null;
  103. }
  104. /// <summary>
  105. /// https://tc39.es/ecma262/#sec-toboolean
  106. /// </summary>
  107. public static bool ToBoolean(JsValue o) => o.ToBoolean();
  108. /// <summary>
  109. /// https://tc39.es/ecma262/#sec-tonumeric
  110. /// </summary>
  111. public static JsValue ToNumeric(JsValue value)
  112. {
  113. if (value.IsNumber() || value.IsBigInt())
  114. {
  115. return value;
  116. }
  117. var primValue = ToPrimitive(value, Types.Number);
  118. if (primValue.IsBigInt())
  119. {
  120. return primValue;
  121. }
  122. return ToNumber(primValue);
  123. }
  124. /// <summary>
  125. /// https://tc39.es/ecma262/#sec-tonumber
  126. /// </summary>
  127. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  128. public static double ToNumber(JsValue o)
  129. {
  130. return o.IsNumber()
  131. ? ((JsNumber) o)._value
  132. : ToNumberUnlikely(o);
  133. }
  134. private static double ToNumberUnlikely(JsValue o)
  135. {
  136. var type = o._type & ~InternalTypes.InternalFlags;
  137. switch (type)
  138. {
  139. case InternalTypes.Undefined:
  140. return double.NaN;
  141. case InternalTypes.Null:
  142. return 0;
  143. case InternalTypes.Boolean:
  144. return ((JsBoolean) o)._value ? 1 : 0;
  145. case InternalTypes.String:
  146. return ToNumber(o.ToString());
  147. case InternalTypes.Symbol:
  148. case InternalTypes.BigInt:
  149. case InternalTypes.Empty:
  150. // TODO proper TypeError would require Engine instance and a lot of API changes
  151. ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a " + type + " value to a number");
  152. return 0;
  153. default:
  154. return ToNumber(ToPrimitive(o, Types.Number));
  155. }
  156. }
  157. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  158. public static JsNumber ToJsNumber(JsValue o)
  159. {
  160. return o.IsNumber() ? (JsNumber) o : ToJsNumberUnlikely(o);
  161. }
  162. private static JsNumber ToJsNumberUnlikely(JsValue o)
  163. {
  164. var type = o._type & ~InternalTypes.InternalFlags;
  165. switch (type)
  166. {
  167. case InternalTypes.Undefined:
  168. return JsNumber.DoubleNaN;
  169. case InternalTypes.Null:
  170. return JsNumber.PositiveZero;
  171. case InternalTypes.Boolean:
  172. return ((JsBoolean) o)._value ? JsNumber.PositiveOne : JsNumber.PositiveZero;
  173. case InternalTypes.String:
  174. return new JsNumber(ToNumber(o.ToString()));
  175. case InternalTypes.Symbol:
  176. case InternalTypes.BigInt:
  177. case InternalTypes.Empty:
  178. // TODO proper TypeError would require Engine instance and a lot of API changes
  179. ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a " + type + " value to a number");
  180. return JsNumber.PositiveZero;
  181. default:
  182. return new JsNumber(ToNumber(ToPrimitive(o, Types.Number)));
  183. }
  184. }
  185. private static double ToNumber(string input)
  186. {
  187. if (string.IsNullOrWhiteSpace(input))
  188. {
  189. return 0;
  190. }
  191. var firstChar = input[0];
  192. if (input.Length == 1)
  193. {
  194. return firstChar is >= '0' and <= '9' ? firstChar - '0' : double.NaN;
  195. }
  196. input = StringPrototype.TrimEx(input);
  197. firstChar = input[0];
  198. const NumberStyles NumberStyles = NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign |
  199. NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite |
  200. NumberStyles.AllowExponent;
  201. if (long.TryParse(input, NumberStyles, CultureInfo.InvariantCulture, out var longValue))
  202. {
  203. return longValue == 0 && firstChar == '-' ? -0.0 : longValue;
  204. }
  205. if (input.Length is 8 or 9)
  206. {
  207. switch (input)
  208. {
  209. case "+Infinity":
  210. case "Infinity":
  211. return double.PositiveInfinity;
  212. case "-Infinity":
  213. return double.NegativeInfinity;
  214. }
  215. if (input.EndsWith("infinity", StringComparison.OrdinalIgnoreCase))
  216. {
  217. // we don't accept other that case-sensitive
  218. return double.NaN;
  219. }
  220. }
  221. if (input.Length > 2 && firstChar == '0' && char.IsLetter(input[1]))
  222. {
  223. var fromBase = input[1] switch
  224. {
  225. 'x' or 'X' => 16,
  226. 'o' or 'O' => 8,
  227. 'b' or 'B' => 2,
  228. _ => 0
  229. };
  230. if (fromBase > 0)
  231. {
  232. try
  233. {
  234. return Convert.ToInt32(input.Substring(2), fromBase);
  235. }
  236. catch
  237. {
  238. return double.NaN;
  239. }
  240. }
  241. }
  242. if (double.TryParse(input, NumberStyles, CultureInfo.InvariantCulture, out var n))
  243. {
  244. return n == 0 && firstChar == '-' ? -0.0 : n;
  245. }
  246. return double.NaN;
  247. }
  248. /// <summary>
  249. /// https://tc39.es/ecma262/#sec-tolength
  250. /// </summary>
  251. public static ulong ToLength(JsValue o)
  252. {
  253. var len = ToInteger(o);
  254. if (len <= 0)
  255. {
  256. return 0;
  257. }
  258. return (ulong) Math.Min(len, NumberConstructor.MaxSafeInteger);
  259. }
  260. /// <summary>
  261. /// https://tc39.es/ecma262/#sec-tointegerorinfinity
  262. /// </summary>
  263. public static double ToIntegerOrInfinity(JsValue argument)
  264. {
  265. var number = ToNumber(argument);
  266. if (double.IsNaN(number) || number == 0)
  267. {
  268. return 0;
  269. }
  270. if (double.IsInfinity(number))
  271. {
  272. return number;
  273. }
  274. var integer = (long) Math.Floor(Math.Abs(number));
  275. if (number < 0)
  276. {
  277. integer *= -1;
  278. }
  279. return integer;
  280. }
  281. /// <summary>
  282. /// https://tc39.es/ecma262/#sec-tointeger
  283. /// </summary>
  284. public static double ToInteger(JsValue o)
  285. {
  286. return ToInteger(ToNumber(o));
  287. }
  288. /// <summary>
  289. /// https://tc39.es/ecma262/#sec-tointeger
  290. /// </summary>
  291. internal static double ToInteger(double number)
  292. {
  293. if (double.IsNaN(number))
  294. {
  295. return 0;
  296. }
  297. if (number == 0 || double.IsInfinity(number))
  298. {
  299. return number;
  300. }
  301. if (number is >= long.MinValue and <= long.MaxValue)
  302. {
  303. return (long) number;
  304. }
  305. var integer = Math.Floor(Math.Abs(number));
  306. if (number < 0)
  307. {
  308. integer *= -1;
  309. }
  310. return integer;
  311. }
  312. internal static int DoubleToInt32Slow(double o)
  313. {
  314. // Computes the integral value of the number mod 2^32.
  315. var doubleBits = BitConverter.DoubleToInt64Bits(o);
  316. var sign = (int) (doubleBits >> 63); // 0 if positive, -1 if negative
  317. var exponent = (int) ((doubleBits >> 52) & 0x7FF) - 1023;
  318. if ((uint) exponent >= 84)
  319. {
  320. // Anything with an exponent that is negative or >= 84 will convert to zero.
  321. // This includes infinities and NaNs, which have exponent = 1024
  322. // The 84 comes from 52 (bits in double mantissa) + 32 (bits in integer)
  323. return 0;
  324. }
  325. var mantissa = (doubleBits & 0xFFFFFFFFFFFFFL) | 0x10000000000000L;
  326. var int32Value = exponent >= 52 ? (int) (mantissa << (exponent - 52)) : (int) (mantissa >> (52 - exponent));
  327. return (int32Value + sign) ^ sign;
  328. }
  329. /// <summary>
  330. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.5
  331. /// </summary>
  332. public static int ToInt32(JsValue o)
  333. {
  334. if (o._type == InternalTypes.Integer)
  335. {
  336. return o.AsInteger();
  337. }
  338. var doubleVal = ToNumber(o);
  339. if (doubleVal >= -(double) int.MinValue && doubleVal <= int.MaxValue)
  340. {
  341. // Double-to-int cast is correct in this range
  342. return (int) doubleVal;
  343. }
  344. return DoubleToInt32Slow(doubleVal);
  345. }
  346. /// <summary>
  347. /// https://tc39.es/ecma262/#sec-touint32
  348. /// </summary>
  349. public static uint ToUint32(JsValue o)
  350. {
  351. if (o._type == InternalTypes.Integer)
  352. {
  353. return (uint) o.AsInteger();
  354. }
  355. var doubleVal = ToNumber(o);
  356. if (doubleVal is >= 0.0 and <= uint.MaxValue)
  357. {
  358. // Double-to-uint cast is correct in this range
  359. return (uint) doubleVal;
  360. }
  361. return (uint) DoubleToInt32Slow(doubleVal);
  362. }
  363. /// <summary>
  364. /// https://tc39.es/ecma262/#sec-touint16
  365. /// </summary>
  366. public static ushort ToUint16(JsValue o)
  367. {
  368. if (o._type == InternalTypes.Integer)
  369. {
  370. var integer = o.AsInteger();
  371. if (integer is >= 0 and <= ushort.MaxValue)
  372. {
  373. return (ushort) integer;
  374. }
  375. }
  376. var number = ToNumber(o);
  377. if (double.IsNaN(number) || number == 0 || double.IsInfinity(number))
  378. {
  379. return 0;
  380. }
  381. var intValue = Math.Floor(Math.Abs(number));
  382. if (number < 0)
  383. {
  384. intValue *= -1;
  385. }
  386. var int16Bit = intValue % 65_536; // 2^16
  387. return (ushort) int16Bit;
  388. }
  389. /// <summary>
  390. /// https://tc39.es/ecma262/#sec-toint16
  391. /// </summary>
  392. internal static double ToInt16(JsValue o)
  393. {
  394. return o._type == InternalTypes.Integer
  395. ? (short) o.AsInteger()
  396. : (short) (long) ToNumber(o);
  397. }
  398. /// <summary>
  399. /// https://tc39.es/ecma262/#sec-toint8
  400. /// </summary>
  401. internal static double ToInt8(JsValue o)
  402. {
  403. return o._type == InternalTypes.Integer
  404. ? (sbyte) o.AsInteger()
  405. : (sbyte) (long) ToNumber(o);
  406. }
  407. /// <summary>
  408. /// https://tc39.es/ecma262/#sec-touint8
  409. /// </summary>
  410. internal static double ToUint8(JsValue o)
  411. {
  412. return o._type == InternalTypes.Integer
  413. ? (byte) o.AsInteger()
  414. : (byte) (long) ToNumber(o);
  415. }
  416. /// <summary>
  417. /// https://tc39.es/ecma262/#sec-touint8clamp
  418. /// </summary>
  419. internal static byte ToUint8Clamp(JsValue o)
  420. {
  421. if (o._type == InternalTypes.Integer)
  422. {
  423. var intValue = o.AsInteger();
  424. if (intValue is > -1 and < 256)
  425. {
  426. return (byte) intValue;
  427. }
  428. }
  429. return ToUint8ClampUnlikely(o);
  430. }
  431. private static byte ToUint8ClampUnlikely(JsValue o)
  432. {
  433. var number = ToNumber(o);
  434. if (double.IsNaN(number))
  435. {
  436. return 0;
  437. }
  438. if (number <= 0)
  439. {
  440. return 0;
  441. }
  442. if (number >= 255)
  443. {
  444. return 255;
  445. }
  446. var f = Math.Floor(number);
  447. if (f + 0.5 < number)
  448. {
  449. return (byte) (f + 1);
  450. }
  451. if (number < f + 0.5)
  452. {
  453. return (byte) f;
  454. }
  455. if (f % 2 != 0)
  456. {
  457. return (byte) (f + 1);
  458. }
  459. return (byte) f;
  460. }
  461. /// <summary>
  462. /// https://tc39.es/ecma262/#sec-tobigint
  463. /// </summary>
  464. public static BigInteger ToBigInt(JsValue value)
  465. {
  466. return value is JsBigInt bigInt
  467. ? bigInt._value
  468. : ToBigIntUnlikely(value);
  469. }
  470. private static BigInteger ToBigIntUnlikely(JsValue value)
  471. {
  472. var prim = ToPrimitive(value, Types.Number);
  473. switch (prim.Type)
  474. {
  475. case Types.BigInt:
  476. return ((JsBigInt) prim)._value;
  477. case Types.Boolean:
  478. return ((JsBoolean) prim)._value ? BigInteger.One : BigInteger.Zero;
  479. case Types.String:
  480. return StringToBigInt(prim.ToString());
  481. default:
  482. ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a " + prim.Type + " to a BigInt");
  483. return BigInteger.One;
  484. }
  485. }
  486. public static JsBigInt ToJsBigInt(JsValue value)
  487. {
  488. return value as JsBigInt ?? ToJsBigIntUnlikely(value);
  489. }
  490. private static JsBigInt ToJsBigIntUnlikely(JsValue value)
  491. {
  492. var prim = ToPrimitive(value, Types.Number);
  493. switch (prim.Type)
  494. {
  495. case Types.BigInt:
  496. return (JsBigInt) prim;
  497. case Types.Boolean:
  498. return ((JsBoolean) prim)._value ? JsBigInt.One : JsBigInt.Zero;
  499. case Types.String:
  500. return new JsBigInt(StringToBigInt(prim.ToString()));
  501. default:
  502. ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a " + prim.Type + " to a BigInt");
  503. return JsBigInt.One;
  504. }
  505. }
  506. internal static BigInteger StringToBigInt(string str)
  507. {
  508. if (!TryStringToBigInt(str, out var result))
  509. {
  510. // TODO: this doesn't seem a JS syntax error, use a dedicated exception type?
  511. throw new SyntaxError("CannotConvertToBigInt", " Cannot convert " + str + " to a BigInt").ToException();
  512. }
  513. return result;
  514. }
  515. internal static bool TryStringToBigInt(string str, out BigInteger result)
  516. {
  517. if (string.IsNullOrWhiteSpace(str))
  518. {
  519. result = BigInteger.Zero;
  520. return true;
  521. }
  522. str = str.Trim();
  523. for (var i = 0; i < str.Length; i++)
  524. {
  525. var c = str[i];
  526. if (!char.IsDigit(c))
  527. {
  528. if (i == 0 && (c == '-' || Character.IsDecimalDigit(c)))
  529. {
  530. // ok
  531. continue;
  532. }
  533. if (i != 1 && Character.IsHexDigit(c))
  534. {
  535. // ok
  536. continue;
  537. }
  538. if (i == 1 && (Character.IsDecimalDigit(c) || c is 'x' or 'X' or 'b' or 'B' or 'o' or 'O'))
  539. {
  540. // allowed, can be probably parsed
  541. continue;
  542. }
  543. result = default;
  544. return false;
  545. }
  546. }
  547. // check if we can get by using plain parsing
  548. if (BigInteger.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
  549. {
  550. return true;
  551. }
  552. if (str.Length > 2)
  553. {
  554. if (str.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
  555. {
  556. // we get better precision if we don't hit floating point parsing that is performed by Esprima
  557. #if SUPPORTS_SPAN_PARSE
  558. var source = str.AsSpan(2);
  559. #else
  560. var source = str.Substring(2);
  561. #endif
  562. var c = source[0];
  563. if (c > 7 && Character.IsHexDigit(c))
  564. {
  565. // ensure we get positive number
  566. source = "0" + source.ToString();
  567. }
  568. if (BigInteger.TryParse(source, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result))
  569. {
  570. return true;
  571. }
  572. }
  573. else if (str.StartsWith("0o", StringComparison.OrdinalIgnoreCase) && Character.IsOctalDigit(str[2]))
  574. {
  575. // try parse large octal
  576. var bigInteger = new BigInteger();
  577. for (var i = 2; i < str.Length; i++)
  578. {
  579. var c = str[i];
  580. if (!Character.IsHexDigit(c))
  581. {
  582. return false;
  583. }
  584. bigInteger = bigInteger * 8 + c - '0';
  585. }
  586. result = bigInteger;
  587. return true;
  588. }
  589. else if (str.StartsWith("0b", StringComparison.OrdinalIgnoreCase) && Character.IsDecimalDigit(str[2]))
  590. {
  591. // try parse large binary
  592. var bigInteger = new BigInteger();
  593. for (var i = 2; i < str.Length; i++)
  594. {
  595. var c = str[i];
  596. if (c != '0' && c != '1')
  597. {
  598. // not good
  599. return false;
  600. }
  601. bigInteger <<= 1;
  602. bigInteger += c == '1' ? 1 : 0;
  603. }
  604. result = bigInteger;
  605. return true;
  606. }
  607. }
  608. return false;
  609. }
  610. /// <summary>
  611. /// https://tc39.es/ecma262/#sec-tobigint64
  612. /// </summary>
  613. internal static long ToBigInt64(BigInteger value)
  614. {
  615. var int64bit = BigIntegerModulo(value, BigInteger.Pow(2, 64));
  616. if (int64bit >= BigInteger.Pow(2, 63))
  617. {
  618. return (long) (int64bit - BigInteger.Pow(2, 64));
  619. }
  620. return (long) int64bit;
  621. }
  622. /// <summary>
  623. /// https://tc39.es/ecma262/#sec-tobiguint64
  624. /// </summary>
  625. internal static ulong ToBigUint64(BigInteger value)
  626. {
  627. return (ulong) BigIntegerModulo(value, BigInteger.Pow(2, 64));
  628. }
  629. /// <summary>
  630. /// Implements the JS spec modulo operation as expected.
  631. /// </summary>
  632. internal static BigInteger BigIntegerModulo(BigInteger a, BigInteger n)
  633. {
  634. return (a %= n) < 0 && n > 0 || a > 0 && n < 0 ? a + n : a;
  635. }
  636. /// <summary>
  637. /// https://tc39.es/ecma262/#sec-canonicalnumericindexstring
  638. /// </summary>
  639. internal static double? CanonicalNumericIndexString(JsValue value)
  640. {
  641. if (value is JsNumber jsNumber)
  642. {
  643. return jsNumber._value;
  644. }
  645. if (value is JsString jsString)
  646. {
  647. if (string.Equals(jsString.ToString(), "-0", StringComparison.Ordinal))
  648. {
  649. return JsNumber.NegativeZero._value;
  650. }
  651. var n = ToNumber(value);
  652. if (!JsValue.SameValue(ToString(n), value))
  653. {
  654. return null;
  655. }
  656. return n;
  657. }
  658. return null;
  659. }
  660. /// <summary>
  661. /// https://tc39.es/ecma262/#sec-toindex
  662. /// </summary>
  663. public static uint ToIndex(Realm realm, JsValue value)
  664. {
  665. if (value.IsUndefined())
  666. {
  667. return 0;
  668. }
  669. var integerIndex = ToIntegerOrInfinity(value);
  670. if (integerIndex < 0)
  671. {
  672. ExceptionHelper.ThrowRangeError(realm);
  673. }
  674. var index = ToLength(integerIndex);
  675. if (integerIndex != index)
  676. {
  677. ExceptionHelper.ThrowRangeError(realm, "Invalid index");
  678. }
  679. return (uint) Math.Min(uint.MaxValue, index);
  680. }
  681. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  682. internal static string ToString(long i)
  683. {
  684. var temp = intToString;
  685. return (ulong) i < (ulong) temp.Length
  686. ? temp[i]
  687. : i.ToString(CultureInfo.InvariantCulture);
  688. }
  689. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  690. internal static string ToString(int i)
  691. {
  692. var temp = intToString;
  693. return (uint) i < (uint) temp.Length
  694. ? temp[i]
  695. : i.ToString(CultureInfo.InvariantCulture);
  696. }
  697. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  698. internal static string ToString(uint i)
  699. {
  700. var temp = intToString;
  701. return i < (uint) temp.Length
  702. ? temp[i]
  703. : i.ToString(CultureInfo.InvariantCulture);
  704. }
  705. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  706. internal static string ToString(char c)
  707. {
  708. var temp = charToString;
  709. return (uint) c < (uint) temp.Length
  710. ? temp[c]
  711. : c.ToString();
  712. }
  713. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  714. internal static string ToString(ulong i)
  715. {
  716. var temp = intToString;
  717. return i < (ulong) temp.Length
  718. ? temp[i]
  719. : i.ToString(CultureInfo.InvariantCulture);
  720. }
  721. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  722. internal static string ToString(double d)
  723. {
  724. if (CanBeStringifiedAsLong(d))
  725. {
  726. // we are dealing with integer that can be cached
  727. return ToString((long) d);
  728. }
  729. return NumberPrototype.ToNumberString(d);
  730. }
  731. /// <summary>
  732. /// Returns true if <see cref="ToString(long)"/> can be used for the
  733. /// provided value <paramref name="d"/>, otherwise false.
  734. /// </summary>
  735. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  736. internal static bool CanBeStringifiedAsLong(double d)
  737. {
  738. return d > long.MinValue && d < long.MaxValue && Math.Abs(d % 1) <= DoubleIsIntegerTolerance;
  739. }
  740. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  741. internal static string ToString(BigInteger bigInteger)
  742. {
  743. return bigInteger.ToString(CultureInfo.InvariantCulture);
  744. }
  745. /// <summary>
  746. /// http://www.ecma-international.org/ecma-262/6.0/#sec-topropertykey
  747. /// </summary>
  748. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  749. public static JsValue ToPropertyKey(JsValue o)
  750. {
  751. const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName;
  752. return (o._type & PropertyKeys) != InternalTypes.Empty
  753. ? o
  754. : ToPropertyKeyNonString(o);
  755. }
  756. [MethodImpl(MethodImplOptions.NoInlining)]
  757. private static JsValue ToPropertyKeyNonString(JsValue o)
  758. {
  759. const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName;
  760. var primitive = ToPrimitive(o, Types.String);
  761. return (primitive._type & PropertyKeys) != InternalTypes.Empty
  762. ? primitive
  763. : ToStringNonString(primitive);
  764. }
  765. /// <summary>
  766. /// https://tc39.es/ecma262/#sec-tostring
  767. /// </summary>
  768. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  769. public static string ToString(JsValue o)
  770. {
  771. return o.IsString() ? o.ToString() : ToStringNonString(o);
  772. }
  773. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  774. internal static JsString ToJsString(JsValue o)
  775. {
  776. if (o is JsString s)
  777. {
  778. return s;
  779. }
  780. return JsString.Create(ToStringNonString(o));
  781. }
  782. private static string ToStringNonString(JsValue o)
  783. {
  784. var type = o._type & ~InternalTypes.InternalFlags;
  785. switch (type)
  786. {
  787. case InternalTypes.Boolean:
  788. return ((JsBoolean) o)._value ? "true" : "false";
  789. case InternalTypes.Integer:
  790. return ToString((int) ((JsNumber) o)._value);
  791. case InternalTypes.Number:
  792. return ToString(((JsNumber) o)._value);
  793. case InternalTypes.BigInt:
  794. return ToString(((JsBigInt) o)._value);
  795. case InternalTypes.Symbol:
  796. ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a Symbol value to a string");
  797. return null;
  798. case InternalTypes.Undefined:
  799. return "undefined";
  800. case InternalTypes.Null:
  801. return "null";
  802. case InternalTypes.PrivateName:
  803. return o.ToString();
  804. case InternalTypes.Object when o is IObjectWrapper p:
  805. return p.Target?.ToString()!;
  806. default:
  807. return ToString(ToPrimitive(o, Types.String));
  808. }
  809. }
  810. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  811. public static ObjectInstance ToObject(Realm realm, JsValue value)
  812. {
  813. if (value is ObjectInstance oi)
  814. {
  815. return oi;
  816. }
  817. return ToObjectNonObject(realm, value);
  818. }
  819. /// <summary>
  820. /// https://tc39.es/ecma262/#sec-isintegralnumber
  821. /// </summary>
  822. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  823. internal static bool IsIntegralNumber(double value)
  824. {
  825. return !double.IsNaN(value) && !double.IsInfinity(value) && value % 1 == 0;
  826. }
  827. private static ObjectInstance ToObjectNonObject(Realm realm, JsValue value)
  828. {
  829. var type = value._type & ~InternalTypes.InternalFlags;
  830. var intrinsics = realm.Intrinsics;
  831. switch (type)
  832. {
  833. case InternalTypes.Boolean:
  834. return intrinsics.Boolean.Construct((JsBoolean) value);
  835. case InternalTypes.Number:
  836. case InternalTypes.Integer:
  837. return intrinsics.Number.Construct((JsNumber) value);
  838. case InternalTypes.BigInt:
  839. return intrinsics.BigInt.Construct((JsBigInt) value);
  840. case InternalTypes.String:
  841. return intrinsics.String.Construct(value as JsString ?? JsString.Create(value.ToString()));
  842. case InternalTypes.Symbol:
  843. return intrinsics.Symbol.Construct((JsSymbol) value);
  844. case InternalTypes.Null:
  845. case InternalTypes.Undefined:
  846. ExceptionHelper.ThrowTypeError(realm, "Cannot convert undefined or null to object");
  847. return null;
  848. default:
  849. ExceptionHelper.ThrowTypeError(realm, "Cannot convert given item to object");
  850. return null;
  851. }
  852. }
  853. [MethodImpl(MethodImplOptions.NoInlining)]
  854. internal static void CheckObjectCoercible(
  855. Engine engine,
  856. JsValue o,
  857. Node sourceNode,
  858. string referenceName)
  859. {
  860. if (!engine._referenceResolver.CheckCoercible(o))
  861. {
  862. ThrowMemberNullOrUndefinedError(engine, o, sourceNode, referenceName);
  863. }
  864. }
  865. [MethodImpl(MethodImplOptions.NoInlining)]
  866. private static void ThrowMemberNullOrUndefinedError(
  867. Engine engine,
  868. JsValue o,
  869. Node sourceNode,
  870. string referencedName)
  871. {
  872. referencedName ??= "unknown";
  873. var message = $"Cannot read property '{referencedName}' of {o}";
  874. throw new JavaScriptException(engine.Realm.Intrinsics.TypeError, message)
  875. .SetJavaScriptCallstack(engine, sourceNode.Location, overwriteExisting: true);
  876. }
  877. public static void CheckObjectCoercible(Engine engine, JsValue o)
  878. {
  879. if (o._type < InternalTypes.Boolean)
  880. {
  881. ExceptionHelper.ThrowTypeError(engine.Realm, "Cannot call method on " + o);
  882. }
  883. }
  884. }