using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native; using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.String; using Jint.Runtime.References; using Jint.Native.Symbol; namespace Jint.Runtime { public enum Types { None, Undefined, Null, Boolean, String, Number, Object, Completion, Symbol } public class TypeConverter { private static readonly string[] intToString = new string[1024]; private static readonly string[] charToString = new string[256]; static TypeConverter() { for (var i = 0; i < intToString.Length; ++i) { intToString[i] = i.ToString(); } for (var i = 0; i < charToString.Length; ++i) { var c = (char) i; charToString[i] = c.ToString(); } } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.1 /// /// /// /// public static JsValue ToPrimitive(JsValue input, Types preferredType = Types.None) { if (input == Null.Instance || input == Undefined.Instance) { return input; } if (input.IsPrimitive()) { return input; } return input.AsObject().DefaultValue(preferredType); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.2 /// /// /// public static bool ToBoolean(JsValue o) { if (o.IsObject()) { return true; } if (o == Undefined.Instance || o == Null.Instance) { return false; } if (o.IsBoolean()) { return o.AsBoolean(); } if (o.IsNumber()) { var n = o.AsNumber(); if (n.Equals(0) || double.IsNaN(n)) { return false; } else { return true; } } if (o.IsString()) { var s = o.AsString(); if (String.IsNullOrEmpty(s)) { return false; } else { return true; } } return true; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.3 /// /// /// public static double ToNumber(JsValue o) { // check number first as this is what is usually expected if (o.IsNumber()) { return o.AsNumber(); } if (o.IsObject()) { var p = o.AsObject() as IPrimitiveInstance; if (p != null) { o = p.PrimitiveValue; } } if (o == Undefined.Instance) { return double.NaN; } if (o == Null.Instance) { return 0; } if (o.IsBoolean()) { return o.AsBoolean() ? 1 : 0; } if (o.IsString()) { var s = StringPrototype.TrimEx(o.AsString()); if (String.IsNullOrEmpty(s)) { return 0; } if ("+Infinity".Equals(s) || "Infinity".Equals(s)) { return double.PositiveInfinity; } if ("-Infinity".Equals(s)) { return double.NegativeInfinity; } // todo: use a common implementation with JavascriptParser try { if (!s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { var start = s[0]; if (start != '+' && start != '-' && start != '.' && !char.IsDigit(start)) { return double.NaN; } double n = Double.Parse(s, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); if (s.StartsWith("-") && n.Equals(0)) { return -0.0; } return n; } int i = int.Parse(s.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); return i; } catch (OverflowException) { return s.StartsWith("-") ? double.NegativeInfinity : double.PositiveInfinity; } catch { return double.NaN; } } return ToNumber(ToPrimitive(o, Types.Number)); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.4 /// /// /// public static double ToInteger(JsValue o) { var number = ToNumber(o); if (double.IsNaN(number)) { return 0; } if (number.Equals(0) || double.IsInfinity(number)) { return number; } return (long) number; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.5 /// /// /// public static int ToInt32(JsValue o) { return (int) (uint) ToNumber(o); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.6 /// /// /// public static uint ToUint32(JsValue o) { return (uint) ToNumber(o); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.7 /// /// /// public static ushort ToUint16(JsValue o) { return (ushort) (uint) ToNumber(o); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(long i) { return i >= 0 && i < intToString.Length ? intToString[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(int i) { return i >= 0 && i < intToString.Length ? intToString[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(uint i) { return i >= 0 && i < intToString.Length ? intToString[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(char c) { return c >= 0 && c < charToString.Length ? charToString[c] : c.ToString(); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.8 /// /// /// public static string ToString(JsValue o) { if (o.IsString()) { return o.AsString(); } if (o.IsObject()) { var p = o.AsObject() as IPrimitiveInstance; if (p != null) { o = p.PrimitiveValue; } else { var s = o.AsInstance(); if (s != null) { // TODO: throw a TypeError // NB: But it requires an Engine reference throw new JavaScriptException(new JsValue("TypeError")); } } } if (o == Undefined.Instance) { return Undefined.Text; } if (o == Null.Instance) { return Null.Text; } if (o.IsBoolean()) { return o.AsBoolean() ? "true" : "false"; } if (o.IsNumber()) { return NumberPrototype.ToNumberString(o.AsNumber()); } return ToString(ToPrimitive(o, Types.String)); } public static ObjectInstance ToObject(Engine engine, JsValue value) { if (value.IsObject()) { return value.AsObject(); } if (value == Undefined.Instance) { throw new JavaScriptException(engine.TypeError); } if (value == Null.Instance) { throw new JavaScriptException(engine.TypeError); } if (value.IsBoolean()) { return engine.Boolean.Construct(value.AsBoolean()); } if (value.IsNumber()) { return engine.Number.Construct(value.AsNumber()); } if (value.IsString()) { return engine.String.Construct(value.AsString()); } if (value.IsSymbol()) { return engine.Symbol.Construct(value.AsSymbol()); } throw new JavaScriptException(engine.TypeError); } public static Types GetPrimitiveType(JsValue value) { if (value.IsObject()) { var primitive = value.TryCast(); if (primitive != null) { return primitive.Type; } return Types.Object; } return value.Type; } public static void CheckObjectCoercible(Engine engine, JsValue o, MemberExpression expression, object baseReference) { if (o != Undefined.Instance && o != Null.Instance) return; if (engine.Options._ReferenceResolver != null && engine.Options._ReferenceResolver.CheckCoercible(o)) return; var message = string.Empty; var reference = baseReference as Reference; if (reference != null) message = $"{reference.GetReferencedName()} is {o}"; throw new JavaScriptException(engine.TypeError, message) .SetCallstack(engine, expression.Location); } public static void CheckObjectCoercible(Engine engine, JsValue o) { if (o == Undefined.Instance || o == Null.Instance) { throw new JavaScriptException(engine.TypeError); } } public static IEnumerable FindBestMatch(Engine engine, MethodBase[] methods, JsValue[] arguments) { methods = methods .Where(m => m.GetParameters().Count() == arguments.Length) .ToArray(); if (methods.Length == 1 && !methods[0].GetParameters().Any()) { yield return methods[0]; yield break; } var objectArguments = arguments.Select(x => x.ToObject()).ToArray(); foreach (var method in methods) { var perfectMatch = true; var parameters = method.GetParameters(); for (var i = 0; i < arguments.Length; i++) { var arg = objectArguments[i]; var paramType = parameters[i].ParameterType; if (arg == null) { if (!TypeIsNullable(paramType)) { perfectMatch = false; break; } } else if (arg.GetType() != paramType) { perfectMatch = false; break; } } if (perfectMatch) { yield return method; yield break; } } foreach (var method in methods) { yield return method; } } public static bool TypeIsNullable(Type type) { return !type.IsValueType() || Nullable.GetUnderlyingType(type) != null; } } }