TypeConverter.cs 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  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. namespace Jint.Runtime
  11. {
  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. throw new ParseErrorException(" Cannot convert " + str + " to a BigInt");
  511. }
  512. return result;
  513. }
  514. internal static bool TryStringToBigInt(string str, out BigInteger result)
  515. {
  516. if (string.IsNullOrWhiteSpace(str))
  517. {
  518. result = BigInteger.Zero;
  519. return true;
  520. }
  521. str = str.Trim();
  522. for (var i = 0; i < str.Length; i++)
  523. {
  524. var c = str[i];
  525. if (!char.IsDigit(c))
  526. {
  527. if (i == 0 && (c == '-' || Character.IsDecimalDigit(c)))
  528. {
  529. // ok
  530. continue;
  531. }
  532. if (i != 1 && Character.IsHexDigit(c))
  533. {
  534. // ok
  535. continue;
  536. }
  537. if (i == 1 && (Character.IsDecimalDigit(c) || c is 'x' or 'X' or 'b' or 'B' or 'o' or 'O'))
  538. {
  539. // allowed, can be probably parsed
  540. continue;
  541. }
  542. result = default;
  543. return false;
  544. }
  545. }
  546. // check if we can get by using plain parsing
  547. if (BigInteger.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
  548. {
  549. return true;
  550. }
  551. if (str.Length > 2)
  552. {
  553. if (str.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
  554. {
  555. // we get better precision if we don't hit floating point parsing that is performed by Esprima
  556. #if SUPPORTS_SPAN_PARSE
  557. var source = str.AsSpan(2);
  558. #else
  559. var source = str.Substring(2);
  560. #endif
  561. var c = source[0];
  562. if (c > 7 && Character.IsHexDigit(c))
  563. {
  564. // ensure we get positive number
  565. source = "0" + source.ToString();
  566. }
  567. if (BigInteger.TryParse(source, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result))
  568. {
  569. return true;
  570. }
  571. }
  572. else if (str.StartsWith("0o", StringComparison.OrdinalIgnoreCase) && Character.IsOctalDigit(str[2]))
  573. {
  574. // try parse large octal
  575. var bigInteger = new BigInteger();
  576. for (var i = 2; i < str.Length; i++)
  577. {
  578. var c = str[i];
  579. if (!Character.IsHexDigit(c))
  580. {
  581. return false;
  582. }
  583. bigInteger = bigInteger * 8 + c - '0';
  584. }
  585. result = bigInteger;
  586. return true;
  587. }
  588. else if (str.StartsWith("0b", StringComparison.OrdinalIgnoreCase) && Character.IsDecimalDigit(str[2]))
  589. {
  590. // try parse large binary
  591. var bigInteger = new BigInteger();
  592. for (var i = 2; i < str.Length; i++)
  593. {
  594. var c = str[i];
  595. if (c != '0' && c != '1')
  596. {
  597. // not good
  598. return false;
  599. }
  600. bigInteger <<= 1;
  601. bigInteger += c == '1' ? 1 : 0;
  602. }
  603. result = bigInteger;
  604. return true;
  605. }
  606. }
  607. return false;
  608. }
  609. /// <summary>
  610. /// https://tc39.es/ecma262/#sec-tobigint64
  611. /// </summary>
  612. internal static long ToBigInt64(BigInteger value)
  613. {
  614. var int64bit = BigIntegerModulo(value, BigInteger.Pow(2, 64));
  615. if (int64bit >= BigInteger.Pow(2, 63))
  616. {
  617. return (long) (int64bit - BigInteger.Pow(2, 64));
  618. }
  619. return (long) int64bit;
  620. }
  621. /// <summary>
  622. /// https://tc39.es/ecma262/#sec-tobiguint64
  623. /// </summary>
  624. internal static ulong ToBigUint64(BigInteger value)
  625. {
  626. return (ulong) BigIntegerModulo(value, BigInteger.Pow(2, 64));
  627. }
  628. /// <summary>
  629. /// Implements the JS spec modulo operation as expected.
  630. /// </summary>
  631. internal static BigInteger BigIntegerModulo(BigInteger a, BigInteger n)
  632. {
  633. return (a %= n) < 0 && n > 0 || a > 0 && n < 0 ? a + n : a;
  634. }
  635. /// <summary>
  636. /// https://tc39.es/ecma262/#sec-canonicalnumericindexstring
  637. /// </summary>
  638. internal static double? CanonicalNumericIndexString(JsValue value)
  639. {
  640. if (value is JsNumber jsNumber)
  641. {
  642. return jsNumber._value;
  643. }
  644. if (value is JsString jsString)
  645. {
  646. if (string.Equals(jsString.ToString(), "-0", StringComparison.Ordinal))
  647. {
  648. return JsNumber.NegativeZero._value;
  649. }
  650. var n = ToNumber(value);
  651. if (!JsValue.SameValue(ToString(n), value))
  652. {
  653. return null;
  654. }
  655. return n;
  656. }
  657. return null;
  658. }
  659. /// <summary>
  660. /// https://tc39.es/ecma262/#sec-toindex
  661. /// </summary>
  662. public static uint ToIndex(Realm realm, JsValue value)
  663. {
  664. if (value.IsUndefined())
  665. {
  666. return 0;
  667. }
  668. var integerIndex = ToIntegerOrInfinity(value);
  669. if (integerIndex < 0)
  670. {
  671. ExceptionHelper.ThrowRangeError(realm);
  672. }
  673. var index = ToLength(integerIndex);
  674. if (integerIndex != index)
  675. {
  676. ExceptionHelper.ThrowRangeError(realm, "Invalid index");
  677. }
  678. return (uint) Math.Min(uint.MaxValue, index);
  679. }
  680. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  681. internal static string ToString(long i)
  682. {
  683. var temp = intToString;
  684. return (ulong) i < (ulong) temp.Length
  685. ? temp[i]
  686. : i.ToString(CultureInfo.InvariantCulture);
  687. }
  688. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  689. internal static string ToString(int i)
  690. {
  691. var temp = intToString;
  692. return (uint) i < (uint) temp.Length
  693. ? temp[i]
  694. : i.ToString(CultureInfo.InvariantCulture);
  695. }
  696. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  697. internal static string ToString(uint i)
  698. {
  699. var temp = intToString;
  700. return i < (uint) temp.Length
  701. ? temp[i]
  702. : i.ToString(CultureInfo.InvariantCulture);
  703. }
  704. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  705. internal static string ToString(char c)
  706. {
  707. var temp = charToString;
  708. return (uint) c < (uint) temp.Length
  709. ? temp[c]
  710. : c.ToString();
  711. }
  712. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  713. internal static string ToString(ulong i)
  714. {
  715. var temp = intToString;
  716. return i < (ulong) temp.Length
  717. ? temp[i]
  718. : i.ToString(CultureInfo.InvariantCulture);
  719. }
  720. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  721. internal static string ToString(double d)
  722. {
  723. if (CanBeStringifiedAsLong(d))
  724. {
  725. // we are dealing with integer that can be cached
  726. return ToString((long) d);
  727. }
  728. return NumberPrototype.ToNumberString(d);
  729. }
  730. /// <summary>
  731. /// Returns true if <see cref="ToString(long)"/> can be used for the
  732. /// provided value <paramref name="d"/>, otherwise false.
  733. /// </summary>
  734. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  735. internal static bool CanBeStringifiedAsLong(double d)
  736. {
  737. return d > long.MinValue && d < long.MaxValue && Math.Abs(d % 1) <= DoubleIsIntegerTolerance;
  738. }
  739. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  740. internal static string ToString(BigInteger bigInteger)
  741. {
  742. return bigInteger.ToString(CultureInfo.InvariantCulture);
  743. }
  744. /// <summary>
  745. /// http://www.ecma-international.org/ecma-262/6.0/#sec-topropertykey
  746. /// </summary>
  747. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  748. public static JsValue ToPropertyKey(JsValue o)
  749. {
  750. const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName;
  751. return (o._type & PropertyKeys) != InternalTypes.Empty
  752. ? o
  753. : ToPropertyKeyNonString(o);
  754. }
  755. [MethodImpl(MethodImplOptions.NoInlining)]
  756. private static JsValue ToPropertyKeyNonString(JsValue o)
  757. {
  758. const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName;
  759. var primitive = ToPrimitive(o, Types.String);
  760. return (primitive._type & PropertyKeys) != InternalTypes.Empty
  761. ? primitive
  762. : ToStringNonString(primitive);
  763. }
  764. /// <summary>
  765. /// https://tc39.es/ecma262/#sec-tostring
  766. /// </summary>
  767. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  768. public static string ToString(JsValue o)
  769. {
  770. return o.IsString() ? o.ToString() : ToStringNonString(o);
  771. }
  772. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  773. internal static JsString ToJsString(JsValue o)
  774. {
  775. if (o is JsString s)
  776. {
  777. return s;
  778. }
  779. return JsString.Create(ToStringNonString(o));
  780. }
  781. private static string ToStringNonString(JsValue o)
  782. {
  783. var type = o._type & ~InternalTypes.InternalFlags;
  784. switch (type)
  785. {
  786. case InternalTypes.Boolean:
  787. return ((JsBoolean) o)._value ? "true" : "false";
  788. case InternalTypes.Integer:
  789. return ToString((int) ((JsNumber) o)._value);
  790. case InternalTypes.Number:
  791. return ToString(((JsNumber) o)._value);
  792. case InternalTypes.BigInt:
  793. return ToString(((JsBigInt) o)._value);
  794. case InternalTypes.Symbol:
  795. ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a Symbol value to a string");
  796. return null;
  797. case InternalTypes.Undefined:
  798. return "undefined";
  799. case InternalTypes.Null:
  800. return "null";
  801. case InternalTypes.PrivateName:
  802. return o.ToString();
  803. case InternalTypes.Object when o is IObjectWrapper p:
  804. return p.Target?.ToString()!;
  805. default:
  806. return ToString(ToPrimitive(o, Types.String));
  807. }
  808. }
  809. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  810. public static ObjectInstance ToObject(Realm realm, JsValue value)
  811. {
  812. if (value is ObjectInstance oi)
  813. {
  814. return oi;
  815. }
  816. return ToObjectNonObject(realm, value);
  817. }
  818. /// <summary>
  819. /// https://tc39.es/ecma262/#sec-isintegralnumber
  820. /// </summary>
  821. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  822. internal static bool IsIntegralNumber(double value)
  823. {
  824. return !double.IsNaN(value) && !double.IsInfinity(value) && value % 1 == 0;
  825. }
  826. private static ObjectInstance ToObjectNonObject(Realm realm, JsValue value)
  827. {
  828. var type = value._type & ~InternalTypes.InternalFlags;
  829. var intrinsics = realm.Intrinsics;
  830. switch (type)
  831. {
  832. case InternalTypes.Boolean:
  833. return intrinsics.Boolean.Construct((JsBoolean) value);
  834. case InternalTypes.Number:
  835. case InternalTypes.Integer:
  836. return intrinsics.Number.Construct((JsNumber) value);
  837. case InternalTypes.BigInt:
  838. return intrinsics.BigInt.Construct((JsBigInt) value);
  839. case InternalTypes.String:
  840. return intrinsics.String.Construct(value as JsString ?? JsString.Create(value.ToString()));
  841. case InternalTypes.Symbol:
  842. return intrinsics.Symbol.Construct((JsSymbol) value);
  843. case InternalTypes.Null:
  844. case InternalTypes.Undefined:
  845. ExceptionHelper.ThrowTypeError(realm, "Cannot convert undefined or null to object");
  846. return null;
  847. default:
  848. ExceptionHelper.ThrowTypeError(realm, "Cannot convert given item to object");
  849. return null;
  850. }
  851. }
  852. [MethodImpl(MethodImplOptions.NoInlining)]
  853. internal static void CheckObjectCoercible(
  854. Engine engine,
  855. JsValue o,
  856. Node sourceNode,
  857. string referenceName)
  858. {
  859. if (!engine._referenceResolver.CheckCoercible(o))
  860. {
  861. ThrowMemberNullOrUndefinedError(engine, o, sourceNode, referenceName);
  862. }
  863. }
  864. [MethodImpl(MethodImplOptions.NoInlining)]
  865. private static void ThrowMemberNullOrUndefinedError(
  866. Engine engine,
  867. JsValue o,
  868. Node sourceNode,
  869. string referencedName)
  870. {
  871. referencedName ??= "unknown";
  872. var message = $"Cannot read property '{referencedName}' of {o}";
  873. throw new JavaScriptException(engine.Realm.Intrinsics.TypeError, message)
  874. .SetJavaScriptCallstack(engine, sourceNode.Location, overwriteExisting: true);
  875. }
  876. public static void CheckObjectCoercible(Engine engine, JsValue o)
  877. {
  878. if (o._type < InternalTypes.Boolean)
  879. {
  880. ExceptionHelper.ThrowTypeError(engine.Realm, "Cannot call method on " + o);
  881. }
  882. }
  883. }
  884. }