TypeConverter.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Runtime.CompilerServices;
  7. using Esprima.Ast;
  8. using Jint.Native;
  9. using Jint.Native.Number;
  10. using Jint.Native.Object;
  11. using Jint.Native.String;
  12. using Jint.Runtime.References;
  13. using Jint.Native.Symbol;
  14. namespace Jint.Runtime
  15. {
  16. public enum Types
  17. {
  18. None,
  19. Undefined,
  20. Null,
  21. Boolean,
  22. String,
  23. Number,
  24. Object,
  25. Completion,
  26. Symbol
  27. }
  28. public class TypeConverter
  29. {
  30. private static readonly string[] intToString = new string[1024*10];
  31. private static readonly string[] charToString = new string[1024];
  32. /// <summary>
  33. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.1
  34. /// </summary>
  35. /// <param name="input"></param>
  36. /// <param name="preferredType"></param>
  37. /// <returns></returns>
  38. public static JsValue ToPrimitive(JsValue input, Types preferredType = Types.None)
  39. {
  40. if (input == Null.Instance || input == Undefined.Instance)
  41. {
  42. return input;
  43. }
  44. if (input.IsPrimitive())
  45. {
  46. return input;
  47. }
  48. return input.AsObject().DefaultValue(preferredType);
  49. }
  50. /// <summary>
  51. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
  52. /// </summary>
  53. /// <param name="o"></param>
  54. /// <returns></returns>
  55. public static bool ToBoolean(JsValue o)
  56. {
  57. if (o.IsObject())
  58. {
  59. return true;
  60. }
  61. if (o == Undefined.Instance || o == Null.Instance)
  62. {
  63. return false;
  64. }
  65. if (o.IsBoolean())
  66. {
  67. return o.AsBoolean();
  68. }
  69. if (o.IsNumber())
  70. {
  71. var n = o.AsNumber();
  72. if (n.Equals(0) || double.IsNaN(n))
  73. {
  74. return false;
  75. }
  76. else
  77. {
  78. return true;
  79. }
  80. }
  81. if (o.IsString())
  82. {
  83. var s = o.AsString();
  84. if (String.IsNullOrEmpty(s))
  85. {
  86. return false;
  87. }
  88. else
  89. {
  90. return true;
  91. }
  92. }
  93. return true;
  94. }
  95. /// <summary>
  96. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.3
  97. /// </summary>
  98. /// <param name="o"></param>
  99. /// <returns></returns>
  100. public static double ToNumber(JsValue o)
  101. {
  102. // check number first as this is what is usually expected
  103. if (o.IsNumber())
  104. {
  105. return o.AsNumber();
  106. }
  107. if (o.IsObject())
  108. {
  109. var p = o.AsObject() as IPrimitiveInstance;
  110. if (p != null)
  111. {
  112. o = p.PrimitiveValue;
  113. }
  114. }
  115. if (o == Undefined.Instance)
  116. {
  117. return double.NaN;
  118. }
  119. if (o == Null.Instance)
  120. {
  121. return 0;
  122. }
  123. if (o.IsBoolean())
  124. {
  125. return o.AsBoolean() ? 1 : 0;
  126. }
  127. if (o.IsString())
  128. {
  129. var s = StringPrototype.TrimEx(o.AsString());
  130. if (String.IsNullOrEmpty(s))
  131. {
  132. return 0;
  133. }
  134. if ("+Infinity".Equals(s) || "Infinity".Equals(s))
  135. {
  136. return double.PositiveInfinity;
  137. }
  138. if ("-Infinity".Equals(s))
  139. {
  140. return double.NegativeInfinity;
  141. }
  142. // todo: use a common implementation with JavascriptParser
  143. try
  144. {
  145. if (!s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
  146. {
  147. var start = s[0];
  148. if (start != '+' && start != '-' && start != '.' && !char.IsDigit(start))
  149. {
  150. return double.NaN;
  151. }
  152. double n = Double.Parse(s,
  153. NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign |
  154. NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite |
  155. NumberStyles.AllowExponent, CultureInfo.InvariantCulture);
  156. if (s.StartsWith("-") && n.Equals(0))
  157. {
  158. return -0.0;
  159. }
  160. return n;
  161. }
  162. int i = int.Parse(s.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
  163. return i;
  164. }
  165. catch (OverflowException)
  166. {
  167. return s.StartsWith("-") ? double.NegativeInfinity : double.PositiveInfinity;
  168. }
  169. catch
  170. {
  171. return double.NaN;
  172. }
  173. }
  174. return ToNumber(ToPrimitive(o, Types.Number));
  175. }
  176. /// <summary>
  177. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
  178. /// </summary>
  179. /// <param name="o"></param>
  180. /// <returns></returns>
  181. public static double ToInteger(JsValue o)
  182. {
  183. var number = ToNumber(o);
  184. if (double.IsNaN(number))
  185. {
  186. return 0;
  187. }
  188. if (number.Equals(0) || double.IsInfinity(number))
  189. {
  190. return number;
  191. }
  192. return (long)number;
  193. }
  194. /// <summary>
  195. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.5
  196. /// </summary>
  197. /// <param name="o"></param>
  198. /// <returns></returns>
  199. public static int ToInt32(JsValue o)
  200. {
  201. return (int) (uint) ToNumber(o);
  202. }
  203. /// <summary>
  204. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.6
  205. /// </summary>
  206. /// <param name="o"></param>
  207. /// <returns></returns>
  208. public static uint ToUint32(JsValue o)
  209. {
  210. return (uint) ToNumber(o);
  211. }
  212. /// <summary>
  213. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.7
  214. /// </summary>
  215. /// <param name="o"></param>
  216. /// <returns></returns>
  217. public static ushort ToUint16(JsValue o)
  218. {
  219. return (ushort) (uint) ToNumber(o);
  220. }
  221. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  222. internal static string ToString(long i)
  223. {
  224. return i >= 0 && i < intToString.Length
  225. ? (intToString[i] = intToString[i] ?? i.ToString())
  226. : i.ToString();
  227. }
  228. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  229. internal static string ToString(int i)
  230. {
  231. return i >= 0 && i < intToString.Length
  232. ? (intToString[i] = intToString[i] ?? i.ToString())
  233. : i.ToString();
  234. }
  235. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  236. internal static string ToString(uint i)
  237. {
  238. return i >= 0 && i < intToString.Length
  239. ? (intToString[i] = intToString[i] ?? i.ToString())
  240. : i.ToString();
  241. }
  242. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  243. internal static string ToString(char c)
  244. {
  245. return c >= 0 && c < charToString.Length
  246. ? (charToString[c] = charToString[c] ?? c.ToString())
  247. : c.ToString();
  248. }
  249. /// <summary>
  250. /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.8
  251. /// </summary>
  252. /// <param name="o"></param>
  253. /// <returns></returns>
  254. public static string ToString(JsValue o)
  255. {
  256. if (o.IsString())
  257. {
  258. return o.AsString();
  259. }
  260. if (o.IsObject())
  261. {
  262. var p = o.AsObject() as IPrimitiveInstance;
  263. if (p != null)
  264. {
  265. o = p.PrimitiveValue;
  266. }
  267. else
  268. {
  269. var s = o.AsInstance<SymbolInstance>();
  270. if (s != null)
  271. {
  272. // TODO: throw a TypeError
  273. // NB: But it requires an Engine reference
  274. throw new JavaScriptException(new JsValue("TypeError"));
  275. }
  276. }
  277. }
  278. if (o == Undefined.Instance)
  279. {
  280. return Undefined.Text;
  281. }
  282. if (o == Null.Instance)
  283. {
  284. return Null.Text;
  285. }
  286. if (o.IsBoolean())
  287. {
  288. return o.AsBoolean() ? "true" : "false";
  289. }
  290. if (o.IsNumber())
  291. {
  292. return NumberPrototype.ToNumberString(o.AsNumber());
  293. }
  294. return ToString(ToPrimitive(o, Types.String));
  295. }
  296. public static ObjectInstance ToObject(Engine engine, JsValue value)
  297. {
  298. if (value.IsObject())
  299. {
  300. return value.AsObject();
  301. }
  302. if (value == Undefined.Instance)
  303. {
  304. throw new JavaScriptException(engine.TypeError);
  305. }
  306. if (value == Null.Instance)
  307. {
  308. throw new JavaScriptException(engine.TypeError);
  309. }
  310. if (value.IsBoolean())
  311. {
  312. return engine.Boolean.Construct(value.AsBoolean());
  313. }
  314. if (value.IsNumber())
  315. {
  316. return engine.Number.Construct(value.AsNumber());
  317. }
  318. if (value.IsString())
  319. {
  320. return engine.String.Construct(value.AsString());
  321. }
  322. if (value.IsSymbol())
  323. {
  324. return engine.Symbol.Construct(value.AsSymbol());
  325. }
  326. throw new JavaScriptException(engine.TypeError);
  327. }
  328. public static Types GetPrimitiveType(JsValue value)
  329. {
  330. if (value.IsObject())
  331. {
  332. var primitive = value.TryCast<IPrimitiveInstance>();
  333. if (primitive != null)
  334. {
  335. return primitive.Type;
  336. }
  337. return Types.Object;
  338. }
  339. return value.Type;
  340. }
  341. public static void CheckObjectCoercible(Engine engine, JsValue o, MemberExpression expression,
  342. object baseReference)
  343. {
  344. if (o != Undefined.Instance && o != Null.Instance)
  345. return;
  346. if (engine.Options._ReferenceResolver != null &&
  347. engine.Options._ReferenceResolver.CheckCoercible(o))
  348. return;
  349. var message = string.Empty;
  350. var reference = baseReference as Reference;
  351. if (reference != null)
  352. message = $"{reference.GetReferencedName()} is {o}";
  353. throw new JavaScriptException(engine.TypeError, message)
  354. .SetCallstack(engine, expression.Location);
  355. }
  356. public static void CheckObjectCoercible(Engine engine, JsValue o)
  357. {
  358. if (o == Undefined.Instance || o == Null.Instance)
  359. {
  360. throw new JavaScriptException(engine.TypeError);
  361. }
  362. }
  363. public static IEnumerable<MethodBase> FindBestMatch(Engine engine, MethodBase[] methods, JsValue[] arguments)
  364. {
  365. methods = methods
  366. .Where(m => m.GetParameters().Count() == arguments.Length)
  367. .ToArray();
  368. if (methods.Length == 1 && !methods[0].GetParameters().Any())
  369. {
  370. yield return methods[0];
  371. yield break;
  372. }
  373. var objectArguments = arguments.Select(x => x.ToObject()).ToArray();
  374. foreach (var method in methods)
  375. {
  376. var perfectMatch = true;
  377. var parameters = method.GetParameters();
  378. for (var i = 0; i < arguments.Length; i++)
  379. {
  380. var arg = objectArguments[i];
  381. var paramType = parameters[i].ParameterType;
  382. if (arg == null)
  383. {
  384. if (!TypeIsNullable(paramType))
  385. {
  386. perfectMatch = false;
  387. break;
  388. }
  389. }
  390. else if (arg.GetType() != paramType)
  391. {
  392. perfectMatch = false;
  393. break;
  394. }
  395. }
  396. if (perfectMatch)
  397. {
  398. yield return method;
  399. yield break;
  400. }
  401. }
  402. foreach (var method in methods)
  403. {
  404. yield return method;
  405. }
  406. }
  407. public static bool TypeIsNullable(Type type)
  408. {
  409. return !type.IsValueType() || Nullable.GetUnderlyingType(type) != null;
  410. }
  411. }
  412. }