Selaa lähdekoodia

Improve parseInt logic (#1229)

* use span
* don't use recursion
* create fast case for regular number input
Marko Lahma 3 vuotta sitten
vanhempi
commit
5faf2fd0f2

+ 2 - 1
Jint.Tests/Runtime/EngineTests.cs

@@ -756,6 +756,7 @@ namespace Jint.Tests.Runtime
         [InlineData(double.NaN, "parseInt(Infinity)")]
         [InlineData(-1d, "parseInt(-1)")]
         [InlineData(-1d, "parseInt('-1')")]
+        [InlineData(double.NaN, "parseInt(new Array(100000).join('Z'))")]
         public void ShouldEvaluateParseInt(object expected, string source)
         {
             var engine = new Engine();
@@ -1989,7 +1990,7 @@ var prep = function (fn) { fn(); };
                 }
                 test('arg');
             ";
-            
+
             var ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate(source));
             Assert.Equal(3, ex.Location.Start.Line);
         }

+ 52 - 57
Jint/Native/Global/GlobalObject.cs

@@ -140,41 +140,21 @@ namespace Jint.Native.Global
         }
 
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.2
+        /// https://tc39.es/ecma262/#sec-parseint-string-radix
         /// </summary>
         public static JsValue ParseInt(JsValue thisObject, JsValue[] arguments)
         {
-            string inputString = TypeConverter.ToString(arguments.At(0));
-            var s = StringPrototype.TrimEx(inputString);
-
-            var sign = 1;
-            if (!System.String.IsNullOrEmpty(s))
-            {
-                if (s[0] == '-')
-                {
-                    sign = -1;
-                }
+            var inputString = TypeConverter.ToString(arguments.At(0));
+            var trimmed = StringPrototype.TrimEx(inputString);
+            var s = trimmed.AsSpan();
 
-                if (s[0] == '-' || s[0] == '+')
-                {
-                    s = s.Substring(1);
-                }
-            }
+            var radix = arguments.Length > 1 ? TypeConverter.ToInt32(arguments[1]) : 0;
+            var hexStart = s.Length > 1 && trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase);
 
             var stripPrefix = true;
-
-            int radix = arguments.Length > 1 ? TypeConverter.ToInt32(arguments[1]) : 0;
-
             if (radix == 0)
             {
-                if (s.Length >= 2 && s.StartsWith("0x") || s.StartsWith("0X"))
-                {
-                    radix = 16;
-                }
-                else
-                {
-                    radix = 10;
-                }
+                radix = hexStart ? 16 : 10;
             }
             else if (radix < 2 || radix > 36)
             {
@@ -185,59 +165,67 @@ namespace Jint.Native.Global
                 stripPrefix = false;
             }
 
-            if (stripPrefix && s.Length >= 2 && s.StartsWith("0x") || s.StartsWith("0X"))
+            // check fast case
+            if (radix == 10 && int.TryParse(trimmed, out var number))
             {
-                s = s.Substring(2);
+                return JsNumber.Create(number);
             }
 
-            try
+            var sign = 1;
+            if (s.Length > 0)
             {
-                return sign * Parse(s, radix);
+                var c = s[0];
+                if (c == '-')
+                {
+                    sign = -1;
+                }
+
+                if (c is '-' or '+')
+                {
+                    s = s.Slice(1);
+                }
             }
-            catch
+
+            if (stripPrefix && hexStart)
             {
-                return JsNumber.DoubleNaN;
+                s = s.Slice(2);
             }
 
-        }
-
-        private static double Parse(string number, int radix)
-        {
-            if (number == "")
+            if (s.Length == 0)
             {
                 return double.NaN;
             }
 
+            var hasResult = false;
             double result = 0;
             double pow = 1;
-            for (int i = number.Length - 1; i >= 0; i--)
+            for (var i = s.Length - 1; i >= 0; i--)
             {
-                double index = double.NaN;
-                char digit = number[i];
+                var digit = s[i];
 
-                if (digit >= '0' && digit <= '9')
+                var index = digit switch
                 {
-                    index = digit - '0';
-                }
-                else if (digit >= 'a' && digit <= 'z')
-                {
-                    index = digit - 'a' + 10;
-                }
-                else if (digit >= 'A' && digit <= 'Z')
-                {
-                    index = digit - 'A' + 10;
-                }
+                    >= '0' and <= '9' => digit - '0',
+                    >= 'a' and <= 'z' => digit - 'a' + 10,
+                    >= 'A' and <= 'Z' => digit - 'A' + 10,
+                    _ => -1
+                };
 
-                if (double.IsNaN(index) || index >= radix)
+                if (index == -1 || index >= radix)
                 {
-                    return Parse(number.Substring(0, i), radix);
+                    // reset
+                    hasResult = false;
+                    result = 0;
+                    pow = 1;
+                    continue;
                 }
 
+                hasResult = true;
                 result += index * pow;
-                pow = pow * radix;
+                pow *= radix;
             }
 
-            return result;
+            return hasResult ? JsNumber.Create(sign  * result) : JsNumber.DoubleNaN;
         }
 
         /// <summary>
@@ -396,7 +384,14 @@ namespace Jint.Native.Global
         /// </summary>
         public static JsValue IsNaN(JsValue thisObject, JsValue[] arguments)
         {
-            var x = TypeConverter.ToNumber(arguments.At(0));
+            var value = arguments.At(0);
+
+            if (ReferenceEquals(value, JsNumber.DoubleNaN))
+            {
+                return true;
+            }
+
+            var x = TypeConverter.ToNumber(value);
             return double.IsNaN(x);
         }
 

+ 1 - 1
Jint/Native/WeakMap/WeakMapInstance.cs

@@ -31,7 +31,7 @@ public sealed class WeakMapInstance : ObjectInstance
             ExceptionHelper.ThrowTypeError(_engine.Realm, "WeakMap key must be an object, got " + key);
         }
 
-#if NETSTANDARD2_1
+#if NETSTANDARD2_1_OR_GREATER
          _table.AddOrUpdate(key, value);
 #else
         _table.Remove(key);

+ 1 - 1
Jint/Native/WeakSet/WeakSetInstance.cs

@@ -31,7 +31,7 @@ public sealed class WeakSetInstance : ObjectInstance
             ExceptionHelper.ThrowTypeError(_engine.Realm, "WeakSet value must be an object, got " + value);
         }
 
-#if NETSTANDARD2_1
+#if NETSTANDARD2_1_OR_GREATER
         _table.AddOrUpdate(value, Undefined);
 #else
         _table.Remove(value);

+ 2 - 2
Jint/Runtime/Interop/TypeResolver.cs

@@ -338,7 +338,7 @@ namespace Jint.Runtime.Interop
 
                 if (equals && x.Length > 1)
                 {
-#if NETSTANDARD2_1
+#if NETSTANDARD2_1_OR_GREATER
                     equals = x.AsSpan(1).SequenceEqual(y.AsSpan(1));
 #else
                     equals = x.Substring(1) == y.Substring(1);
@@ -354,4 +354,4 @@ namespace Jint.Runtime.Interop
             }
         }
     }
-}
+}