TypeConverter.cs 11 KB

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