Kaynağa Gözat

Reduce string operations memory allocations (#457)

Marko Lahma 7 yıl önce
ebeveyn
işleme
b25e243ee8

+ 1 - 1
Jint/Engine.cs

@@ -248,7 +248,7 @@ namespace Jint
 
         public Engine SetValue(string name, string value)
         {
-            return SetValue(name, new JsValue(value));
+            return SetValue(name, (JsValue) value);
         }
 
         public Engine SetValue(string name, double value)

+ 9 - 4
Jint/Native/Array/ArrayExecutionContext.cs

@@ -11,15 +11,20 @@ namespace Jint.Native.Array
         // cache key container for array iteration for less allocations
         private static readonly ThreadLocal<ArrayExecutionContext> _executionContext = new ThreadLocal<ArrayExecutionContext>(() => new ArrayExecutionContext());
 
+        private List<uint> _keyCache;
+        private JsValue[] _callArray1;
+        private JsValue[] _callArray3;
+        private JsValue[] _callArray4;
+
         private ArrayExecutionContext()
         {
         }
 
-        public List<uint> KeyCache = new List<uint>();
+        public List<uint> KeyCache => _keyCache = _keyCache ?? new List<uint>();
 
-        public JsValue[] CallArray1 = new JsValue[1];
-        public JsValue[] CallArray3 = new JsValue[3];
-        public JsValue[] CallArray4 = new JsValue[4];
+        public JsValue[] CallArray1 => _callArray1 = _callArray1 ?? new JsValue[1];
+        public JsValue[] CallArray3 => _callArray3 = _callArray3 ?? new JsValue[3];
+        public JsValue[] CallArray4 => _callArray4 = _callArray4 ?? new JsValue[4];
 
         public static ArrayExecutionContext Current => _executionContext.Value;
     }

+ 1 - 1
Jint/Native/Array/ArrayInstance.cs

@@ -156,7 +156,7 @@ namespace Jint.Native.Array
                                 var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
                                 if (!deleteSucceeded)
                                 {
-                                    newLenDesc.Value = new JsValue(keyIndex + 1);
+                                    newLenDesc.Value = keyIndex + 1;
                                     if (!newWritable)
                                     {
                                         newLenDesc.Writable = false;

+ 57 - 51
Jint/Native/JsValue.cs

@@ -24,9 +24,13 @@ namespace Jint.Native
         // integer values converted to doubles
         private static readonly Dictionary<double, JsValue> _doubleToJsValue = new Dictionary<double, JsValue>();
         private static readonly JsValue[] _intToJsValue = new JsValue[1024];
-        private static readonly JsValue[] _charToJsValue = new JsValue[256];
 
-        private static readonly JsValue _function = new JsValue("function");
+        private const int AsciiMax = 126;
+        private static readonly JsValue[] _charToJsValue = new JsValue[AsciiMax + 1];
+        private static readonly JsValue[] _charToStringJsValue = new JsValue[AsciiMax + 1];
+
+        private static readonly JsValue EmptyString = new JsValue("");
+        private static readonly JsValue NullString = new JsValue("null");
 
         public static readonly JsValue Undefined = new JsValue(Types.Undefined);
         public static readonly JsValue Null = new JsValue(Types.Null);
@@ -48,9 +52,10 @@ namespace Jint.Native
                     _doubleToJsValue[i] = new JsValue((double) i);
                 }
             }
-            for (int i = 0; i < _charToJsValue.Length; i++)
+            for (int i = 0; i <= AsciiMax; i++)
             {
                 _charToJsValue[i] = new JsValue((char) i);
+                _charToStringJsValue[i] = new JsValue(((char) i).ToString());
             }
         }
 
@@ -273,10 +278,7 @@ namespace Jint.Native
                 }
             }
 
-            if (fail != null)
-            {
-                fail(this);
-            }
+            fail?.Invoke(this);
 
             return null;
         }
@@ -412,7 +414,7 @@ namespace Jint.Native
 
         internal static JsValue FromChar(char value)
         {
-            if (value >= 0 && value < _charToJsValue.Length)
+            if (value >= 0 && value <= AsciiMax)
             {
                 return _charToJsValue[value];
             }
@@ -434,8 +436,7 @@ namespace Jint.Native
 
             foreach (var converter in engine.Options._ObjectConverters)
             {
-                JsValue result;
-                if (converter.TryConvert(value, out result))
+                if (converter.TryConvert(value, out var result))
                 {
                     return result;
                 }
@@ -445,15 +446,13 @@ namespace Jint.Native
 
             var typeMappers = Engine.TypeMappers;
 
-            Func<Engine, object, JsValue> typeMapper;
-            if (typeMappers.TryGetValue(valueType, out typeMapper))
+            if (typeMappers.TryGetValue(valueType, out var typeMapper))
             {
                 return typeMapper(engine, value);
             }
 
             // if an ObjectInstance is passed directly, use it as is
-            var instance = value as ObjectInstance;
-            if (instance != null)
+            if (value is ObjectInstance instance)
             {
                 // Learn conversion.
                 // Learn conversion, racy, worst case we'll try again later
@@ -471,32 +470,31 @@ namespace Jint.Native
                 return typeReference.JsValue;
             }
 
-            var a = value as System.Array;
-            if (a != null)
+            if (value is System.Array a)
             {
-                Func<Engine, object, JsValue> convert = (Engine e, object v) =>
+                JsValue Convert(Engine e, object v)
                 {
-                    var array = (System.Array)v;
+                    var array = (System.Array) v;
 
                     var jsArray = engine.Array.Construct(a.Length);
                     foreach (var item in array)
                     {
-                        var jsItem = JsValue.FromObject(engine, item);
+                        var jsItem = FromObject(engine, item);
                         engine.Array.PrototypeObject.Push(jsArray, Arguments.From(jsItem));
                     }
 
                     return jsArray;
-                };
+                }
+
                 // racy, we don't care, worst case we'll catch up later
                 Interlocked.CompareExchange(ref Engine.TypeMappers, new Dictionary<Type, Func<Engine, object, JsValue>>(typeMappers)
                 {
-                    [valueType] = convert
+                    [valueType] = Convert
                 }, typeMappers);
-                return convert(engine, a);
+                return Convert(engine, a);
             }
 
-            var d = value as Delegate;
-            if (d != null)
+            if (value is Delegate d)
             {
                 return new DelegateWrapper(engine, d);
             }
@@ -529,8 +527,7 @@ namespace Jint.Native
                 case Types.Number:
                     return _double;
                 case Types.Object:
-                    var wrapper = _object as IObjectWrapper;
-                    if (wrapper != null)
+                    if (_object is IObjectWrapper wrapper)
                     {
                         return wrapper.Target;
                     }
@@ -538,8 +535,7 @@ namespace Jint.Native
                     switch ((_object as ObjectInstance).Class)
                     {
                         case "Array":
-                            var arrayInstance = _object as ArrayInstance;
-                            if (arrayInstance != null)
+                            if (_object is ArrayInstance arrayInstance)
                             {
                                 var len = TypeConverter.ToInt32(arrayInstance.Get("length"));
                                 var result = new object[len];
@@ -562,8 +558,7 @@ namespace Jint.Native
                             break;
 
                         case "String":
-                            var stringInstance = _object as StringInstance;
-                            if (stringInstance != null)
+                            if (_object is StringInstance stringInstance)
                             {
                                 return stringInstance.PrimitiveValue.AsString();
                             }
@@ -571,8 +566,7 @@ namespace Jint.Native
                             break;
 
                         case "Date":
-                            var dateInstance = _object as DateInstance;
-                            if (dateInstance != null)
+                            if (_object is DateInstance dateInstance)
                             {
                                 return dateInstance.ToDateTime();
                             }
@@ -580,8 +574,7 @@ namespace Jint.Native
                             break;
 
                         case "Boolean":
-                            var booleanInstance = _object as BooleanInstance;
-                            if (booleanInstance != null)
+                            if (_object is BooleanInstance booleanInstance)
                             {
                                 return booleanInstance.PrimitiveValue.AsBoolean();
                             }
@@ -589,8 +582,7 @@ namespace Jint.Native
                             break;
 
                         case "Function":
-                            var function = _object as FunctionInstance;
-                            if (function != null)
+                            if (_object is FunctionInstance function)
                             {
                                 return (Func<JsValue, JsValue[], JsValue>)function.Call;
                             }
@@ -598,8 +590,7 @@ namespace Jint.Native
                             break;
 
                         case "Number":
-                            var numberInstance = _object as NumberInstance;
-                            if (numberInstance != null)
+                            if (_object is NumberInstance numberInstance)
                             {
                                 return numberInstance.NumberData.AsNumber();
                             }
@@ -607,8 +598,7 @@ namespace Jint.Native
                             break;
 
                         case "RegExp":
-                            var regeExpInstance = _object as RegExpInstance;
-                            if (regeExpInstance != null)
+                            if (_object is RegExpInstance regeExpInstance)
                             {
                                 return regeExpInstance.Value;
                             }
@@ -623,14 +613,15 @@ namespace Jint.Native
                             IDictionary<string, object> o = new ExpandoObject();
 #endif
 
-                            foreach (var p in (_object as ObjectInstance).GetOwnProperties())
+                            var objectInstance = (ObjectInstance) _object;
+                            foreach (var p in objectInstance.GetOwnProperties())
                             {
                                 if (!p.Value.Enumerable.HasValue || p.Value.Enumerable.Value == false)
                                 {
                                     continue;
                                 }
 
-                                o.Add(p.Key, (_object as ObjectInstance).Get(p.Key).ToObject());
+                                o.Add(p.Key, objectInstance.Get(p.Key).ToObject());
                             }
 
                             return o;
@@ -741,12 +732,12 @@ namespace Jint.Native
             return !a.Equals(b);
         }
 
-        static public implicit operator JsValue(int value)
+        public static implicit operator JsValue(int value)
         {
             return FromInt(value);
         }
 
-        static public implicit operator JsValue(double value)
+        public static implicit operator JsValue(double value)
         {
             if (value < 0 || value >= _doubleToJsValue.Count || !_doubleToJsValue.TryGetValue(value, out var jsValue))
             {
@@ -755,23 +746,38 @@ namespace Jint.Native
             return jsValue;
         }
 
-        static public implicit operator JsValue(bool value)
+        public static implicit operator JsValue(bool value)
         {
             return value ? True : False;
         }
 
-        static public implicit operator JsValue(string value)
+        public static implicit operator JsValue(string value)
         {
-            // some common ones can be cached here
-            if (value == "function")
+            if (value.Length <= 1)
+            {
+                if (value == "")
+                {
+                    return EmptyString;
+                }
+
+                if (value.Length == 1)
+                {
+                    if (value[0] >= 0 && value[0] <= AsciiMax)
+                    {
+                        return _charToStringJsValue[value[0]];
+                    }
+                }
+
+            }
+            else if (value == Native.Null.Text)
             {
-                return _function;
+                return NullString;
             }
 
             return new JsValue(value);
         }
 
-        static public implicit operator JsValue(ObjectInstance value)
+        public static implicit operator JsValue(ObjectInstance value)
         {
             return value.JsValue;
         }
@@ -814,7 +820,7 @@ namespace Jint.Native
         public override bool Equals(object obj)
         {
             if (ReferenceEquals(null, obj)) return false;
-            return obj is JsValue && Equals((JsValue)obj);
+            return obj is JsValue value && Equals(value);
         }
 
         public override int GetHashCode()

+ 3 - 1
Jint/Native/Json/JsonParser.cs

@@ -788,9 +788,11 @@ namespace Jint.Native.Json
                     var v = Lex().Value;
                     return Null.Instance;
                 case Tokens.BooleanLiteral:
+                    // implicit conversion operator goes through caching
                     return (bool) Lex().Value ? JsValue.True : JsValue.False;
                 case Tokens.String:
-                    return new JsValue((string)Lex().Value);
+                    // implicit conversion operator goes through caching
+                    return (string) Lex().Value;
                 case Tokens.Number:
                     return new JsValue((double)Lex().Value);
             }

+ 2 - 2
Jint/Native/Null.cs

@@ -2,7 +2,7 @@
 {
     public static class Null
     {
-        public readonly static JsValue Instance = JsValue.Null;
-        public readonly static string Text = "null";
+        public static readonly JsValue Instance = JsValue.Null;
+        public const string Text = "null";
     }
 }

+ 43 - 0
Jint/Native/String/StringExecutionContext.cs

@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace Jint.Native.String
+{
+    /// <summary>
+    /// Helper to cache common data structures when manipulating strings.
+    /// </summary>
+    internal class StringExecutionContext
+    {
+        private static readonly ThreadLocal<StringExecutionContext> _executionContext = new ThreadLocal<StringExecutionContext>(() => new StringExecutionContext());
+
+        private StringBuilder _stringBuilder;
+        private List<string> _splitSegmentList;
+        private string[] _splitArray1;
+        private JsValue[] _callArray3;
+
+        private StringExecutionContext()
+        {
+        }
+
+        public StringBuilder GetStringBuilder(int capacity)
+        {
+            if (_stringBuilder == null)
+            {
+                _stringBuilder = new StringBuilder(capacity);
+            }
+            else
+            {
+                _stringBuilder.EnsureCapacity(capacity);
+            }
+
+            return _stringBuilder;
+        }
+
+        public List<string> SplitSegmentList => _splitSegmentList = _splitSegmentList ?? new List<string>();
+        public string[] SplitArray1 => _splitArray1 = _splitArray1 ?? new string[1];
+        public JsValue[] CallArray3 => _callArray3 = _callArray3 ?? new JsValue[3];
+
+        public static StringExecutionContext Current => _executionContext.Value;
+    }
+}

+ 18 - 26
Jint/Native/String/StringInstance.cs

@@ -11,23 +11,11 @@ namespace Jint.Native.String
         {
         }
 
-        public override string Class
-        {
-            get
-            {
-                return "String";
-            }
-        }
+        public override string Class => "String";
 
-        Types IPrimitiveInstance.Type
-        {
-            get { return Types.String; }
-        }
+        Types IPrimitiveInstance.Type => Types.String;
 
-        JsValue IPrimitiveInstance.PrimitiveValue
-        {
-            get { return PrimitiveValue; }
-        }
+        JsValue IPrimitiveInstance.PrimitiveValue => PrimitiveValue;
 
         public JsValue PrimitiveValue { get; set; }
 
@@ -35,17 +23,19 @@ namespace Jint.Native.String
         {
             if (d >= long.MinValue && d <= long.MaxValue)
             {
-                var l = (long)d;
+                var l = (long) d;
                 return l >= int.MinValue && l <= int.MaxValue;
             }
-            else 
-                return false;
+
+            return false;
         }
 
         public override PropertyDescriptor GetOwnProperty(string propertyName)
         {
-            if(propertyName == "Infinity")
+            if (propertyName == "Infinity")
+            {
                 return PropertyDescriptor.Undefined;
+            }
 
             var desc = base.GetOwnProperty(propertyName);
             if (desc != PropertyDescriptor.Undefined)
@@ -53,24 +43,26 @@ namespace Jint.Native.String
                 return desc;
             }
 
-            if (propertyName != System.Math.Abs(TypeConverter.ToInteger(propertyName)).ToString())
+            var integer = TypeConverter.ToInteger(propertyName);
+            if (integer == 0 && propertyName != "0" || propertyName != System.Math.Abs(integer).ToString())
             {
                 return PropertyDescriptor.Undefined;
             }
 
             var str = PrimitiveValue;
-            var dIndex = TypeConverter.ToInteger(propertyName);
-            if(!IsInt(dIndex))
+            var dIndex = integer;
+            if (!IsInt(dIndex))
                 return PropertyDescriptor.Undefined;
 
-            var index = (int)dIndex;
+            var index = (int) dIndex;
             var len = str.AsString().Length;
             if (len <= index || index < 0)
             {
                 return PropertyDescriptor.Undefined;
             }
-            var resultStr = str.AsString()[index].ToString();
-            return new PropertyDescriptor(new JsValue(resultStr), false, true, false);
+
+            var resultStr = TypeConverter.ToString(str.AsString()[index]);
+            return new PropertyDescriptor(resultStr, false, true, false);
         }
     }
-}
+}

+ 42 - 28
Jint/Native/String/StringPrototype.cs

@@ -1,19 +1,16 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
+using System.Runtime.CompilerServices;
 using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Native.RegExp;
 using Jint.Runtime;
-using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.String
 {
-
-
     /// <summary>
     /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4
     /// </summary>
@@ -79,6 +76,7 @@ namespace Jint.Native.String
         const char BOM_CHAR = '\uFEFF';
         const char MONGOLIAN_VOWEL_SEPARATOR = '\u180E';
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static bool IsWhiteSpaceEx(char c)
         {
             return
@@ -93,6 +91,9 @@ namespace Jint.Native.String
             if (s.Length == 0)
                 return string.Empty;
 
+            if (!IsWhiteSpaceEx(s[s.Length - 1]))
+                return s;
+
             var i = s.Length - 1;
             while (i >= 0)
             {
@@ -112,6 +113,9 @@ namespace Jint.Native.String
             if (s.Length == 0)
                 return string.Empty;
 
+            if (!IsWhiteSpaceEx(s[0]))
+                return s;
+
             var i = 0;
             while (i < s.Length)
             {
@@ -165,7 +169,7 @@ namespace Jint.Native.String
         private static int ToIntegerSupportInfinity(JsValue numberVal)
         {
             var doubleVal = TypeConverter.ToInteger(numberVal);
-            var intVal = (int) doubleVal;
+            int intVal;
             if (double.IsPositiveInfinity(doubleVal))
                 intVal = int.MaxValue;
             else if (double.IsNegativeInfinity(doubleVal))
@@ -232,13 +236,12 @@ namespace Jint.Native.String
 
             // Coerce into a number, true will become 1
             var l = arguments.At(1);
-            var a = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
-            var limit = l == Undefined.Instance ? UInt32.MaxValue : TypeConverter.ToUint32(l);
+            var limit = l == Undefined.Instance ? uint.MaxValue : TypeConverter.ToUint32(l);
             var len = s.Length;
 
             if (limit == 0)
             {
-                return a;
+                return Engine.Array.Construct(Arguments.Empty);
             }
 
             if (separator == Null.Instance)
@@ -265,6 +268,7 @@ namespace Jint.Native.String
                 rx.Source != regExpForMatchingAllCharactere // We need pattern to be defined -> for s.split(new RegExp)
                 )
             {
+                var a = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
                 var match = rx.Value.Match(s, 0);
 
                 if (!match.Success) // No match at all return the string in an array
@@ -320,11 +324,16 @@ namespace Jint.Native.String
             }
             else
             {
-                List<string> segments;
+                var segments = StringExecutionContext.Current.SplitSegmentList;
+                segments.Clear();
                 var sep = TypeConverter.ToString(separator);
 
                 if (sep == string.Empty || (rx != null && rx.Source == regExpForMatchingAllCharactere)) // for s.split(new RegExp)
                 {
+                    if (s.Length > segments.Capacity)
+                    {
+                        segments.Capacity = s.Length;
+                    }
                     segments = new List<string>(s.Length);
                     foreach (var c in s)
                     {
@@ -333,14 +342,16 @@ namespace Jint.Native.String
                 }
                 else
                 {
-                    segments = new List<string>(s.Split(new[] {sep}, StringSplitOptions.None));
+                    var array = StringExecutionContext.Current.SplitArray1;
+                    array[0] = sep;
+                    segments.AddRange(s.Split(array, StringSplitOptions.None));
                 }
 
+                var a = Engine.Array.Construct(Arguments.Empty, (uint) segments.Count);
                 for (int i = 0; i < segments.Count && i < limit; i++)
                 {
                     a.SetIndexValue((uint) i, segments[i], throwOnError: false);
                 }
-
                 return a;
             }
         }
@@ -435,7 +446,8 @@ namespace Jint.Native.String
                     // $`	Inserts the portion of the string that precedes the matched substring.
                     // $'	Inserts the portion of the string that follows the matched substring.
                     // $n or $nn	Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
-                    var replacementBuilder = new StringBuilder();
+                    var replacementBuilder = StringExecutionContext.Current.GetStringBuilder(0);
+                    replacementBuilder.Clear();
                     for (int i = 0; i < replaceString.Length; i++)
                     {
                         char c = replaceString[i];
@@ -497,11 +509,11 @@ namespace Jint.Native.String
 
             if (searchValue.IsNull())
             {
-                searchValue = new JsValue(Null.Text);
+                searchValue = Null.Text;
             }
             if (searchValue.IsUndefined())
             {
-                searchValue = new JsValue(Undefined.Text);
+                searchValue = Undefined.Text;
             }
 
             var rx = TypeConverter.ToObject(Engine, searchValue) as RegExpInstance;
@@ -510,18 +522,17 @@ namespace Jint.Native.String
                 // Replace the input string with replaceText, recording the last match found.
                 string result = rx.Value.Replace(thisString, match =>
                 {
-                    var args = new List<JsValue>();
-
+                    var args = new JsValue[match.Groups.Count + 2];
                     for (var k = 0; k < match.Groups.Count; k++)
                     {
                         var group = match.Groups[k];
-                        args.Add(group.Value);
+                        args[k] = @group.Value;
                     }
 
-                    args.Add(match.Index);
-                    args.Add(thisString);
+                    args[match.Groups.Count] = match.Index;
+                    args[match.Groups.Count + 1] = thisString;
 
-                    var v = TypeConverter.ToString(replaceFunction.Call(Undefined.Instance, args.ToArray()));
+                    var v = TypeConverter.ToString(replaceFunction.Call(Undefined.Instance, args));
                     return v;
                 }, rx.Global == true ? -1 : 1);
 
@@ -543,15 +554,16 @@ namespace Jint.Native.String
                     return thisString;
                 int end = start + substr.Length;
 
-                var args = new List<JsValue>();
-                args.Add(substr);
-                args.Add(start);
-                args.Add(thisString);
+                var args = StringExecutionContext.Current.CallArray3;
+                args[0] = substr;
+                args[1] = start;
+                args[2] = thisString;
 
-                var replaceString = TypeConverter.ToString(replaceFunction.Call(Undefined.Instance, args.ToArray()));
+                var replaceString = TypeConverter.ToString(replaceFunction.Call(Undefined.Instance, args));
 
                 // Replace only the first match.
-                var result = new StringBuilder(thisString.Length + (substr.Length - substr.Length));
+                var result = StringExecutionContext.Current.GetStringBuilder(thisString.Length + (substr.Length - substr.Length));
+                result.Clear();
                 result.Append(thisString, 0, start);
                 result.Append(replaceString);
                 result.Append(thisString, end, thisString.Length - end);
@@ -699,7 +711,9 @@ namespace Jint.Native.String
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
             var s = TypeConverter.ToString(thisObj);
-            var sb = new StringBuilder(s);
+            var sb = StringExecutionContext.Current.GetStringBuilder(0);
+            sb.Clear();
+            sb.Append(s);
             for (int i = 0; i < arguments.Length; i++)
             {
                 sb.Append(TypeConverter.ToString(arguments[i]));
@@ -779,7 +793,7 @@ namespace Jint.Native.String
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
             var targetLength = TypeConverter.ToInt32(arguments.At(0));
-            var padString = TypeConverter.ToString(arguments.At(1, new JsValue(" ")));
+            var padString = TypeConverter.ToString(arguments.At(1, " "));
 
             var s = TypeConverter.ToString(thisObj);
             if (s.Length > targetLength)

+ 2 - 1
Jint/Runtime/ExpressionIntepreter.cs

@@ -620,7 +620,8 @@ namespace Jint.Runtime
                     // implicit conversion operator goes through caching
                     return literal.NumericValue;
                 case TokenType.StringLiteral:
-                    return new JsValue(literal.StringValue);
+                    // implicit conversion operator goes through caching
+                    return literal.StringValue;
             }
 
             if (literal.RegexValue != null) //(literal.Type == Nodes.RegularExpressionLiteral)

+ 72 - 44
Jint/Runtime/TypeConverter.cs

@@ -158,61 +158,72 @@ namespace Jint.Runtime
 
             if (o.IsString())
             {
-                var s = StringPrototype.TrimEx(o.AsString());
+                return ToNumber(o.AsString());
+            }
 
-                if (String.IsNullOrEmpty(s))
-                {
-                    return 0;
-                }
+            return ToNumber(ToPrimitive(o, Types.Number));
+        }
 
-                if ("+Infinity".Equals(s) || "Infinity".Equals(s))
-                {
-                    return double.PositiveInfinity;
-                }
+        private static double ToNumber(string input)
+        {
+            // eager checks to save time and trimming
+            if (string.IsNullOrEmpty(input))
+            {
+                return 0;
+            }
 
-                if ("-Infinity".Equals(s))
-                {
-                    return double.NegativeInfinity;
-                }
+            var s = StringPrototype.TrimEx(input);
 
-                // 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;
-                        }
+            if (string.IsNullOrEmpty(s))
+            {
+                return 0;
+            }
 
-                        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;
-                        }
+            if ("+Infinity".Equals(s) || "Infinity".Equals(s))
+            {
+                return double.PositiveInfinity;
+            }
+
+            if ("-Infinity".Equals(s))
+            {
+                return double.NegativeInfinity;
+            }
 
-                        return n;
+            // 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;
                     }
 
-                    int i = int.Parse(s.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
+                    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 i;
-                }
-                catch (OverflowException)
-                {
-                    return s.StartsWith("-") ? double.NegativeInfinity : double.PositiveInfinity;
+                    return n;
                 }
-                catch
-                {
-                    return double.NaN;
-                }
-            }
 
-            return ToNumber(ToPrimitive(o, Types.Number));
+                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;
+            }
         }
 
         /// <summary>
@@ -237,6 +248,23 @@ namespace Jint.Runtime
             return (long) number;
         }
 
+        internal static double ToInteger(string o)
+        {
+            var number = ToNumber(o);
+
+            if (double.IsNaN(number))
+            {
+                return 0;
+            }
+
+            if (number.Equals(0) || double.IsInfinity(number))
+            {
+                return number;
+            }
+
+            return (long) number;
+        }
+
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.5
         /// </summary>