TypeConverter.cs 12 KB

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