Browse Source

Merge sebastienros/dev

New release
Sébastien Ros 6 years ago
parent
commit
3c7c1b6245
100 changed files with 6866 additions and 2614 deletions
  1. 4 3
      .gitattributes
  2. 1 12
      Jint.Benchmark/ArrayStressBenchmark.cs
  3. 4 17
      Jint.Benchmark/DromaeoBenchmark.cs
  4. 2 1
      Jint.Benchmark/Jint.Benchmark.csproj
  5. 1 11
      Jint.Benchmark/LinqJsBenchmark.cs
  6. 4 1
      Jint.Benchmark/MinimalScriptBenchmark.cs
  7. 1 13
      Jint.Benchmark/StopwatchBenchmark.cs
  8. 39 0
      Jint.Benchmark/StringBuilderBenchmark.cs
  9. 1 13
      Jint.Benchmark/SunSpiderBenchmark.cs
  10. 1 13
      Jint.Benchmark/UncacheableExpressionsBenchmark.cs
  11. 24 2
      Jint.Tests.Ecma/EcmaTest.cs
  12. 32 32
      Jint.Tests.Ecma/TestCases/alltests.json
  13. 103 0
      Jint.Tests.Test262/LanguageTests.cs
  14. 4 12
      Jint.Tests.Test262/MathTests.cs
  15. 15 0
      Jint.Tests.Test262/NumberTests.cs
  16. 2 2
      Jint.Tests.Test262/SingleTest.cs
  17. 31 0
      Jint.Tests.Test262/StatementTests.cs
  18. 172 79
      Jint.Tests.Test262/Test262Test.cs
  19. 2 1
      Jint.Tests.Test262/test/built-ins/String/raw/special-characters.js
  20. 3 2
      Jint.Tests.Test262/test/language/expressions/template-literal/tv-line-continuation.js
  21. 566 11
      Jint.Tests.Test262/test/skipped.json
  22. 1 10
      Jint.Tests/Parser/JavascriptParserTests.cs
  23. 1 1
      Jint.Tests/Runtime/Converters/EnumsToStringConverter.cs
  24. 3 3
      Jint.Tests/Runtime/Converters/NegateBoolConverter.cs
  25. 20 0
      Jint.Tests/Runtime/Domain/A.cs
  26. 253 8
      Jint.Tests/Runtime/EngineTests.cs
  27. 196 2
      Jint.Tests/Runtime/InteropTests.cs
  28. 147 0
      Jint.Tests/Runtime/Scripts/dromaeo-string-base64.js
  29. 9 1
      Jint/ArrayExt.cs
  30. 50 174
      Jint/Engine.cs
  31. 21 5
      Jint/EsprimaExtensions.cs
  32. 1 1
      Jint/Jint.csproj
  33. 8 3
      Jint/Native/Argument/ArgumentsInstance.cs
  34. 72 47
      Jint/Native/Array/ArrayConstructor.cs
  35. 0 24
      Jint/Native/Array/ArrayExecutionContext.cs
  36. 69 5
      Jint/Native/Array/ArrayInstance.cs
  37. 41 36
      Jint/Native/Array/ArrayPrototype.cs
  38. 1 1
      Jint/Native/Date/DateConstructor.cs
  39. 21 21
      Jint/Native/Date/DatePrototype.cs
  40. 122 0
      Jint/Native/Function/ArrowFunctionInstance.cs
  41. 6 5
      Jint/Native/Function/EvalFunctionInstance.cs
  42. 8 4
      Jint/Native/Function/FunctionConstructor.cs
  43. 19 4
      Jint/Native/Function/FunctionInstance.cs
  44. 39 45
      Jint/Native/Function/FunctionPrototype.cs
  45. 27 46
      Jint/Native/Function/ScriptFunctionInstance.cs
  46. 6 6
      Jint/Native/Global/GlobalObject.cs
  47. 9 2
      Jint/Native/Iterator/IteratorProtocol.cs
  48. 24 5
      Jint/Native/JsNumber.cs
  49. 12 1
      Jint/Native/JsString.cs
  50. 1 1
      Jint/Native/JsSymbol.cs
  51. 33 15
      Jint/Native/JsValue.cs
  52. 30 52
      Jint/Native/Json/JsonParser.cs
  53. 1 1
      Jint/Native/Json/JsonSerializer.cs
  54. 403 98
      Jint/Native/Math/MathInstance.cs
  55. 656 0
      Jint/Native/Number/Dtoa/Bignum.cs
  56. 694 0
      Jint/Native/Number/Dtoa/BignumDtoa.cs
  57. 101 92
      Jint/Native/Number/Dtoa/CachePowers.cs
  58. 17 25
      Jint/Native/Number/Dtoa/DiyFp.cs
  59. 28 19
      Jint/Native/Number/Dtoa/DoubleHelper.cs
  60. 53 0
      Jint/Native/Number/Dtoa/DtoaBuilder.cs
  61. 9 0
      Jint/Native/Number/Dtoa/DtoaMode.cs
  62. 69 0
      Jint/Native/Number/Dtoa/DtoaNumberFormatter.cs
  63. 283 57
      Jint/Native/Number/Dtoa/FastDtoa.cs
  64. 0 140
      Jint/Native/Number/Dtoa/FastDtoaBuilder.cs
  65. 6 0
      Jint/Native/Number/Dtoa/NumberExtensions.cs
  66. 74 1
      Jint/Native/Number/NumberConstructor.cs
  67. 260 86
      Jint/Native/Number/NumberPrototype.cs
  68. 2 2
      Jint/Native/Object/ObjectConstructor.cs
  69. 8 7
      Jint/Native/Object/ObjectInstance.cs
  70. 9 4
      Jint/Native/RegExp/RegExpPrototype.cs
  71. 38 0
      Jint/Native/String/StringConstructor.cs
  72. 1 16
      Jint/Native/String/StringExecutionContext.cs
  73. 2 2
      Jint/Native/String/StringInstance.cs
  74. 66 58
      Jint/Native/String/StringPrototype.cs
  75. 24 0
      Jint/Options.cs
  76. 278 0
      Jint/Pooling/ConcurrentObjectPool.cs
  77. 63 0
      Jint/Pooling/StringBuilderPool.cs
  78. 1 1
      Jint/Runtime/Completion.cs
  79. 6 6
      Jint/Runtime/Debugger/DebugHandler.cs
  80. 13 1
      Jint/Runtime/Descriptors/PropertyDescriptor.cs
  81. 3 1
      Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs
  82. 1 1
      Jint/Runtime/Descriptors/Specialized/FieldInfoDescriptor.cs
  83. 1 1
      Jint/Runtime/Descriptors/Specialized/IndexDescriptor.cs
  84. 1 1
      Jint/Runtime/Descriptors/Specialized/PropertyInfoDescriptor.cs
  85. 280 44
      Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs
  86. 4 0
      Jint/Runtime/Environments/EnvironmentRecord.cs
  87. 40 16
      Jint/Runtime/Environments/LexicalEnvironment.cs
  88. 24 1
      Jint/Runtime/Environments/ObjectEnvironmentRecord.cs
  89. 26 3
      Jint/Runtime/ExceptionHelper.cs
  90. 0 1094
      Jint/Runtime/ExpressionIntepreter.cs
  91. 11 0
      Jint/Runtime/Interop/DelegateWrapper.cs
  92. 1 1
      Jint/Runtime/Interop/IObjectConverter.cs
  93. 27 38
      Jint/Runtime/Interop/MethodInfoFunctionInstance.cs
  94. 53 35
      Jint/Runtime/Interop/ObjectWrapper.cs
  95. 11 9
      Jint/Runtime/Interop/TypeReference.cs
  96. 269 0
      Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs
  97. 113 0
      Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs
  98. 31 0
      Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs
  99. 230 0
      Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
  100. 313 0
      Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs

+ 4 - 3
.gitattributes

@@ -1,3 +1,4 @@
-Jint.Tests/* linguist-vendored
-Jint.Tests.Ecma/* linguist-vendored
-Jint.Tests.CommonScripts/* linguist-vendored
+Jint.Tests/** linguist-vendored
+Jint.Tests.Ecma/** linguist-vendored
+Jint.Tests.CommonScripts/** linguist-vendored
+Jint.Tests.Test262/** linguist-vendored

+ 1 - 12
Jint.Benchmark/ArrayStressBenchmark.cs

@@ -1,21 +1,10 @@
 using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Jobs;
 
 namespace Jint.Benchmark
 {
-    [Config(typeof(Config))]
+    [MemoryDiagnoser]
     public class ArrayStressBenchmark : SingleScriptBenchmark
     {
-        private class Config : ManualConfig
-        {
-            public Config()
-            {
-                Add(Job.ShortRun);
-                Add(MemoryDiagnoser.Default);
-            }
-        }
         protected override string Script => "var ret=[],tmp,num=100,i=256;for(var j1=0;j1<i*15;j1++){ret=[];ret.length=i}for(var j2=0;j2<i*10;j2++){ret=new Array(i)}ret=[];for(var j3=0;j3<i;j3++){ret.unshift(j3)}ret=[];for(var j4=0;j4<i;j4++){ret.splice(0,0,j4)}var a=ret.slice();for(var j5=0;j5<i;j5++){tmp=a.shift()}var b=ret.slice();for(var j6=0;j6<i;j6++){tmp=b.splice(0,1)}ret=[];for(var j7=0;j7<i*25;j7++){ret.push(j7)}var c=ret.slice();for(var j8=0;j8<i*25;j8++){tmp=c.pop()}var done = true;";
 
         [Params(20)]

+ 4 - 17
Jint.Benchmark/DromaeoBenchmark.cs

@@ -3,33 +3,20 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Jobs;
-using Jint;
 
-namespace Esprima.Benchmark
+namespace Jint.Benchmark
 {
-    [Config(typeof(Config))]
+    [MemoryDiagnoser]
     public class DromaeoBenchmark
     {
-        private class Config : ManualConfig
-        {
-            public Config()
-            {
-                Add(Job.MediumRun.WithLaunchCount(1));
-                Add(MemoryDiagnoser.Default);
-            }
-        }
-        
-        private static readonly Dictionary<string, string> files = new Dictionary<string, string>
+        public static readonly Dictionary<string, string> files = new Dictionary<string, string>
         {
             {"dromaeo-3d-cube", null},
             {"dromaeo-core-eval", null},
             {"dromaeo-object-array", null},
             {"dromaeo-object-regexp", null},
             {"dromaeo-object-string", null},
-            {"dromaeo-string-base64", null}
+            //{"dromaeo-string-base64", null}
         };
 
         private Engine engine;

+ 2 - 1
Jint.Benchmark/Jint.Benchmark.csproj

@@ -19,9 +19,10 @@
     <None Include="..\Jint.Tests.CommonScripts\Scripts\**" CopyToOutputDirectory="PreserveNewest" LinkBase="SunSpider" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="BenchmarkDotNet" Version="0.11.2" />
+    <PackageReference Include="BenchmarkDotNet" Version="0.11.4" />
     <ProjectReference Include="..\Jint\Jint.csproj" />
     <PackageReference Include="Jurassic" Version="3.0.0-alpha2" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
     <PackageReference Include="NiL.JS.NetCore" Version="2.5.1200" />
   </ItemGroup>
 </Project>

+ 1 - 11
Jint.Benchmark/LinqJsBenchmark.cs

@@ -1,20 +1,10 @@
 using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Jobs;
 
 namespace Jint.Benchmark
 {
-    [Config(typeof(Config))]
+    [MemoryDiagnoser]
     public class LinqJsBenchmark : SingleScriptBenchmark
     {
-        private class Config : ManualConfig
-        {
-            public Config()
-            {
-                Add(Job.ShortRun);
-            }
-        }
-
         [Params(10)]
         public override int N { get; set; }
 

+ 4 - 1
Jint.Benchmark/MinimalScriptBenchmark.cs

@@ -1,5 +1,8 @@
-namespace Jint.Benchmark
+using BenchmarkDotNet.Attributes;
+
+namespace Jint.Benchmark
 {
+    [MemoryDiagnoser]
     public class MinimalScriptBenchmark : SingleScriptBenchmark
     {
         protected override string Script => "var done = true;";

+ 1 - 13
Jint.Benchmark/StopwatchBenchmark.cs

@@ -1,22 +1,10 @@
 using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Jobs;
 
 namespace Jint.Benchmark
 {
-    [Config(typeof(Config))]
+    [MemoryDiagnoser]
     public class StopwatchBenchmark : SingleScriptBenchmark
     {
-        private class Config : ManualConfig
-        {
-            public Config()
-            {
-                Add(Job.ShortRun);
-                Add(MemoryDiagnoser.Default);
-            }
-        }
- 
         [Params(1)]
         public override int N { get; set; }
 

+ 39 - 0
Jint.Benchmark/StringBuilderBenchmark.cs

@@ -0,0 +1,39 @@
+using BenchmarkDotNet.Attributes;
+
+namespace Jint.Benchmark
+{
+    [MemoryDiagnoser]
+    public class StringBuilderBenchmark
+    {
+        private const string script = @"
+var x = 'some string';
+";
+
+        private Engine engine;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            engine = new Engine();
+            engine.Execute(script);
+        }
+
+        [Benchmark]
+        public void One()
+        {
+            engine.Execute("`hello ${x}`");
+        }
+
+        [Benchmark]
+        public void Two()
+        {
+            engine.Execute("`hello ${x}, hello ${x}`");
+        }
+
+        [Benchmark]
+        public void Three()
+        {
+            engine.Execute("`hello ${x}, hello ${x}, hello ${x}`");
+        }
+    }
+}

+ 1 - 13
Jint.Benchmark/SunSpiderBenchmark.cs

@@ -3,25 +3,13 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Jobs;
 using Jint;
 
 namespace Esprima.Benchmark
 {
-    [Config(typeof(Config))]
+    [MemoryDiagnoser]
     public class SunSpiderBenchmark
     {
-        private class Config : ManualConfig
-        {
-            public Config()
-            {
-                Add(Job.ShortRun.WithLaunchCount(1));
-                Add(MemoryDiagnoser.Default);
-            }
-        }
-        
         private static readonly Dictionary<string, string> files = new Dictionary<string, string>
         {
             {"3d-cube", null},

+ 1 - 13
Jint.Benchmark/UncacheableExpressionsBenchmark.cs

@@ -4,9 +4,6 @@ using System.IO;
 using System.Linq;
 using System.Text;
 using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Jobs;
 using Jint.Native;
 using Newtonsoft.Json;
 using Undefined = Jint.Native.Undefined;
@@ -16,18 +13,9 @@ namespace Jint.Benchmark
     /// <summary>
     /// Test case for situation where object is projected via filter and map, Jint deems code as uncacheable.
     /// </summary>
-    [Config(typeof(Config))]
+    [MemoryDiagnoser]
     public class UncacheableExpressionsBenchmark
     {
-        private class Config : ManualConfig
-        {
-            public Config()
-            {
-                Add(Job.MediumRun.WithLaunchCount(1));
-                Add(MemoryDiagnoser.Default);
-            }
-        }
-
         private Document doc;
 
         private string targetObject;

+ 24 - 2
Jint.Tests.Ecma/EcmaTest.cs

@@ -5,6 +5,7 @@ using System.Reflection;
 using Jint.Runtime;
 using Newtonsoft.Json.Linq;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Jint.Tests.Ecma
 {
@@ -268,8 +269,13 @@ namespace Jint.Tests.Ecma
             return results;
         }
 
-        public class SourceFile
+        public class SourceFile : IXunitSerializable
         {
+            public SourceFile()
+            {
+
+            }
+
             public SourceFile(JObject node, string basePath)
             {
                 Skip = node["skip"].Value<bool>();
@@ -281,7 +287,23 @@ namespace Jint.Tests.Ecma
             public string Source { get; set; }
             public bool Skip { get; set; }
             public string Reason { get; set; }
-            public string BasePath { get; }
+            public string BasePath { get; set; }
+
+            public void Deserialize(IXunitSerializationInfo info)
+            {
+                Skip = info.GetValue<bool>(nameof(Skip));
+                Source = info.GetValue<string>(nameof(Source));
+                Reason = info.GetValue<string>(nameof(Reason));
+                BasePath = info.GetValue<string>(nameof(BasePath));
+            }
+
+            public void Serialize(IXunitSerializationInfo info)
+            {
+                info.AddValue(nameof(Skip), Skip);
+                info.AddValue(nameof(Source), Source);
+                info.AddValue(nameof(Reason), Reason);
+                info.AddValue(nameof(BasePath), BasePath);
+            }
 
             public override string ToString()
             {

+ 32 - 32
Jint.Tests.Ecma/TestCases/alltests.json

@@ -7145,8 +7145,8 @@
     source: "ch11/11.1/11.1.5/11.1.5-3-s.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate names allowed",
     source: "ch11/11.1/11.1.5/11.1.5-4-4-a-1-s.js"
   },
   {
@@ -7170,43 +7170,43 @@
     source: "ch11/11.1/11.1.5/11.1.5_4-4-a-3.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate names now allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-b-1.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate names now allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-b-2.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate names now allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-c-1.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate names now allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-c-2.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate get get now allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-d-1.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate set set allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-d-2.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate property names now allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-d-3.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "duplicate property names now allowed",
     source: "ch11/11.1/11.1.5/11.1.5_4-4-d-4.js"
   },
   {
@@ -15885,13 +15885,13 @@
     source: "ch12/12.6/12.6.3/S12.6.3_A8_T3.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "obsolete",
     source: "ch12/12.6/12.6.3/S12.6.3_A9.1.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "obsolete",
     source: "ch12/12.6/12.6.3/S12.6.3_A9.js"
   },
   {
@@ -16825,8 +16825,8 @@
     source: "ch13/13.2/13.2-14-s.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "function length is configurable",
     source: "ch13/13.2/13.2-15-1.js"
   },
   {
@@ -24780,8 +24780,8 @@
     source: "ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-186.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "function length is configurable",
     source: "ch15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-187.js"
   },
   {
@@ -38255,18 +38255,18 @@
     source: "ch15/15.3/15.3.5/S15.3.5.1_A1_T3.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "function length is configurable",
     source: "ch15/15.3/15.3.5/S15.3.5.1_A2_T1.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "function length is configurable",
     source: "ch15/15.3/15.3.5/S15.3.5.1_A2_T2.js"
   },
   {
-    skip: false,
-    reason: "",
+    skip: true,
+    reason: "function length is configurable",
     source: "ch15/15.3/15.3.5/S15.3.5.1_A2_T3.js"
   },
   {

+ 103 - 0
Jint.Tests.Test262/LanguageTests.cs

@@ -0,0 +1,103 @@
+using Xunit;
+
+namespace Jint.Tests.Test262
+{
+    public class LanguageTests : Test262Test
+    {
+        [Theory(DisplayName = "language\\rest-parameters")]
+        [MemberData(nameof(SourceFiles), "language\\rest-parameters", false)]
+        [MemberData(nameof(SourceFiles), "language\\rest-parameters", true, Skip = "Skipped")]
+        protected void RestParameters(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\destructuring")]
+        [MemberData(nameof(SourceFiles), "language\\destructuring", false)]
+        [MemberData(nameof(SourceFiles), "language\\destructuring", true, Skip = "Skipped")]
+        protected void Destructuring(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\array")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\array", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\array", true, Skip = "Skipped")]
+        protected void ExpressionsArray(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\arrow-function")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\arrow-function", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\arrow-function", true, Skip = "Skipped")]
+        protected void ArrowFunction(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\function")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\function", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\function", true, Skip = "Skipped")]
+        protected void Function(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\call")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\call", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\call", true, Skip = "Skipped")]
+        protected void ExpressionsCall(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\new")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\new", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\new", true, Skip = "Skipped")]
+        protected void ExpressionsNew(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\object")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\object", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\object", true, Skip = "Skipped")]
+        protected void ExpressionsObject(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\template-literal")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\template-literal", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\template-literal", true, Skip = "Skipped")]
+        protected void ExpressionsTemplateLiteral(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\source-text")]
+        [MemberData(nameof(SourceFiles), "language\\source-text", false)]
+        [MemberData(nameof(SourceFiles), "language\\source-text", true, Skip = "Skipped")]
+        protected void SourceText(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\types")]
+        [MemberData(nameof(SourceFiles), "language\\types", false)]
+        [MemberData(nameof(SourceFiles), "language\\types", true, Skip = "Skipped")]
+        protected void Types(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\white-space")]
+        [MemberData(nameof(SourceFiles), "language\\white-space", false)]
+        [MemberData(nameof(SourceFiles), "language\\white-space", true, Skip = "Skipped")]
+        protected void WhiteSpace(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 4 - 12
Jint.Tests.Test262/MathTests.cs

@@ -4,18 +4,10 @@ namespace Jint.Tests.Test262
 {
     public class MathTests : Test262Test
     {
-        [Theory(DisplayName = "built-ins\\Math\\trunc")]
-        [MemberData(nameof(SourceFiles), "built-ins\\Math\\trunc", false)]
-        [MemberData(nameof(SourceFiles), "built-ins\\Math\\trunc", true, Skip = "Skipped")]
-        protected void Trunc(SourceFile sourceFile)
-        {
-            RunTestInternal(sourceFile);
-        }
-
-        [Theory(DisplayName = "built-ins\\Math\\sign")]
-        [MemberData(nameof(SourceFiles), "built-ins\\Math\\sign", false)]
-        [MemberData(nameof(SourceFiles), "built-ins\\Math\\sign", true, Skip = "Skipped")]
-        protected void Sign(SourceFile sourceFile)
+        [Theory(DisplayName = "built-ins\\Math")]
+        [MemberData(nameof(SourceFiles), "built-ins\\Math", false)]
+        [MemberData(nameof(SourceFiles), "built-ins\\Math", true, Skip = "Skipped")]
+        protected void Math(SourceFile sourceFile)
         {
             RunTestInternal(sourceFile);
         }

+ 15 - 0
Jint.Tests.Test262/NumberTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262
+{
+    public class NumberTests : Test262Test
+    {
+        [Theory(DisplayName = "built-ins\\Number")]
+        [MemberData(nameof(SourceFiles), "built-ins\\Number", false)]
+        [MemberData(nameof(SourceFiles), "built-ins\\Number", true, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 2 - 2
Jint.Tests.Test262/SingleTest.cs

@@ -23,9 +23,9 @@ namespace Jint.Tests.Test262
         [RunnableInDebugOnly]
         public void TestSingle()
         {
-            const string Target = @"built-ins/String/prototype/repeat/repeat-string-n-times.js";
+            const string Target = @"language/statements/for/dstr-const-ary-init-iter-close.js";
             //const string Target = @"built-ins/Array/from/calling-from-valid-2.js";
-            var sourceFile = SourceFiles("built-ins", false)
+            var sourceFile = SourceFiles("language/statements", false)
                 .SelectMany(x => x)
                 .Cast<SourceFile>()
                 .First(x => x.Source == Target);

+ 31 - 0
Jint.Tests.Test262/StatementTests.cs

@@ -0,0 +1,31 @@
+using Xunit;
+
+namespace Jint.Tests.Test262
+{
+    public class StatementTests : Test262Test
+    {
+        [Theory(DisplayName = "language\\statements\\for")]
+        [MemberData(nameof(SourceFiles), "language\\statements\\for", false)]
+        [MemberData(nameof(SourceFiles), "language\\statements\\for", true, Skip = "Skipped")]
+        protected void For(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\statements\\for-in")]
+        [MemberData(nameof(SourceFiles), "language\\statements\\for-in", false)]
+        [MemberData(nameof(SourceFiles), "language\\statements\\for-in", true, Skip = "Skipped")]
+        protected void ForIn(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(Skip = "for of not implemented", DisplayName = "language\\statements\\for-of")]
+        [MemberData(nameof(SourceFiles), "language\\statements\\for-of", false)]
+        [MemberData(nameof(SourceFiles), "language\\statements\\for-of", true, Skip = "Skipped")]
+        protected void ForOf(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 172 - 79
Jint.Tests.Test262/Test262Test.cs

@@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
 using Jint.Runtime;
 using Newtonsoft.Json.Linq;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Jint.Tests.Test262
 {
@@ -21,6 +22,8 @@ namespace Jint.Tests.Test262
         private static readonly Dictionary<string, string> _skipReasons =
             new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
+        private static readonly HashSet<string> _strictSkips =
+            new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 
         static Test262Test()
         {
@@ -51,7 +54,12 @@ namespace Jint.Tests.Test262
             var doc = JArray.Parse(content);
             foreach (var entry in doc.Values<JObject>())
             {
-                _skipReasons[entry["source"].Value<string>()] = entry["reason"].Value<string>();
+                var source = entry["source"].Value<string>();
+                _skipReasons[source] = entry["reason"].Value<string>();
+                if (entry.TryGetValue("mode", out var mode) && mode.Value<string>() == "strict")
+                {
+                    _strictSkips.Add(source);
+                }
             }
         }
 
@@ -67,6 +75,8 @@ namespace Jint.Tests.Test262
             }
 
             string lastError = null;
+
+            bool negative = code.IndexOf("negative:", StringComparison.Ordinal) > -1;
             try
             {
                 engine.Execute(code);
@@ -80,24 +90,32 @@ namespace Jint.Tests.Test262
                 lastError = e.ToString();
             }
 
-            Assert.Null(lastError);
+            if (negative)
+            {
+                Assert.NotNull(lastError);
+            }
+            else
+            {
+                Assert.Null(lastError);
+            }
         }
 
         protected void RunTestInternal(SourceFile sourceFile)
         {
-            RunTestCode(sourceFile.Code);
-        }
+            if (sourceFile.Skip)
+            {
+                return;
+            }
 
-        private void RunTestCode(string code)
-        {
-            if (code.IndexOf("onlyStrict", StringComparison.Ordinal) < 0)
+            if (sourceFile.Code.IndexOf("onlyStrict", StringComparison.Ordinal) < 0)
             {
-                RunTestCode(code, strict: false);
+                RunTestCode(sourceFile.Code, strict: false);
             }
 
-            if (code.IndexOf("noStrict", StringComparison.Ordinal) < 0)
+            if (!_strictSkips.Contains(sourceFile.Source)
+                && sourceFile.Code.IndexOf("noStrict", StringComparison.Ordinal) < 0)
             {
-                RunTestCode(code, strict: true);
+                RunTestCode(sourceFile.Code, strict: true);
             }
         }
 
@@ -115,86 +133,138 @@ namespace Jint.Tests.Test262
 
                 var code = skip ? "" : File.ReadAllText(file);
 
+                var flags = Regex.Match(code, "flags: \\[(.+?)\\]");
+                if (flags.Success)
+                {
+                    var items = flags.Groups[1].Captures[0].Value.Split(",");
+                    foreach (var item in items.Select(x => x.Trim()))
+                    {
+                        switch (item)
+                        {
+                            // TODO implement
+                            case "async":
+                                skip = true;
+                                reason = "async not implemented";
+                                break;
+                        }
+                    }
+                }
+
                 var features = Regex.Match(code, "features: \\[(.+?)\\]");
                 if (features.Success)
                 {
                     var items = features.Groups[1].Captures[0].Value.Split(",");
                     foreach (var item in items.Select(x => x.Trim()))
                     {
-                        // TODO implement
-                        if (item == "cross-realm")
-                        {
-                            skip = true;
-                            reason = "realms not implemented";
-                        }
-                        else if (item == "Symbol.species")
-                        {
-                            skip = true;
-                            reason = "Symbol.species not implemented";
-                        }
-                        else if (item == "Proxy")
-                        {
-                            skip = true;
-                            reason = "Proxies not implemented";
-                        }
-                        else if (item == "Symbol.unscopables")
-                        {
-                            skip = true;
-                            reason = "Symbol.unscopables not implemented";
-                        }
-                        else if (item == "Symbol.match")
-                        {
-                            skip = true;
-                            reason = "Symbol.match not implemented";
-                        }
-                        else if (item == "Symbol.matchAll")
+                        switch (item)
                         {
-                            skip = true;
-                            reason = "Symbol.matchAll not implemented";
-                        }
-                        else if (item == "Symbol.split")
-                        {
-                            skip = true;
-                            reason = "Symbol.split not implemented";
-                        }
-                        else if (item == "String.prototype.matchAll")
-                        {
-                            skip = true;
-                            reason = "proposal stage";
-                        }
-                        else if (item == "Symbol.search")
-                        {
-                            skip = true;
-                            reason = "Symbol.search not implemented";
-                        }
-                        else if (item == "Symbol.replace")
-                        {
-                            skip = true;
-                            reason = "Symbol.replace not implemented";
-                        }
-                        else if (item == "Symbol.toStringTag")
-                        {
-                            skip = true;
-                            reason = "Symbol.toStringTag not implemented";
-                        }
-                        else if (item == "BigInt")
-                        {
-                            skip = true;
-                            reason = "BigInt not implemented";
+                            // TODO implement
+                            case "cross-realm":
+                                skip = true;
+                                reason = "realms not implemented";
+                                break;
+                            case "tail-call-optimization":
+                                skip = true;
+                                reason = "tail-calls not implemented";
+                                break;
+                            case "class":
+                                skip = true;
+                                reason = "class keyword not implemented";
+                                break;
+                            case "Symbol.species":
+                                skip = true;
+                                reason = "Symbol.species not implemented";
+                                break;
+                            case "Proxy":
+                                skip = true;
+                                reason = "Proxies not implemented";
+                                break;
+                            case "object-spread":
+                                skip = true;
+                                reason = "Object spread not implemented";
+                                break;
+                            case "Symbol.unscopables":
+                                skip = true;
+                                reason = "Symbol.unscopables not implemented";
+                                break;
+                            case "Symbol.match":
+                                skip = true;
+                                reason = "Symbol.match not implemented";
+                                break;
+                            case "Symbol.matchAll":
+                                skip = true;
+                                reason = "Symbol.matchAll not implemented";
+                                break;
+                            case "Symbol.split":
+                                skip = true;
+                                reason = "Symbol.split not implemented";
+                                break;
+                            case "String.prototype.matchAll":
+                                skip = true;
+                                reason = "proposal stage";
+                                break;
+                            case "Symbol.search":
+                                skip = true;
+                                reason = "Symbol.search not implemented";
+                                break;
+                            case "Symbol.replace":
+                                skip = true;
+                                reason = "Symbol.replace not implemented";
+                                break;
+                            case "Symbol.toStringTag":
+                                skip = true;
+                                reason = "Symbol.toStringTag not implemented";
+                                break;
+                            case "BigInt":
+                                skip = true;
+                                reason = "BigInt not implemented";
+                                break;
+                            case "generators":
+                                skip = true;
+                                reason = "generators not implemented";
+                                break;
+                            case "let":
+                                skip = true;
+                                reason = "let not implemented";
+                                break;
+                            case "async-functions":
+                                skip = true;
+                                reason = "async-functions not implemented";
+                                break;
+                            case "async-iteration":
+                                skip = true;
+                                reason = "async not implemented";
+                                break;
+                            case "new.target":
+                                skip = true;
+                                reason = "MetaProperty not implemented";
+                                break;
+                            case "super":
+                                skip = true;
+                                reason = "super not implemented";
+                                break;
                         }
                     }
                 }
 
-                if (name.StartsWith("built-ins/String/raw/"))
+                if (code.IndexOf("SpecialCasing.txt") > -1)
                 {
                     skip = true;
-                    reason = "requires template string";
+                    reason = "SpecialCasing.txt not implemented";
                 }
 
-                if (code.IndexOf("SpecialCasing.txt") > -1)
+                if (name.StartsWith("language/expressions/object/dstr-async-gen-meth-"))
                 {
                     skip = true;
-                    reason = "SpecialCasing.txt not implemented";
+                    reason = "Esprima problem, Unexpected token *";
+                }
+
+                if (file.EndsWith("tv-line-continuation.js")
+                    || file.EndsWith("tv-line-terminator-sequence.js")
+                    || file.EndsWith("special-characters.js"))
+                {
+                    // LF endings required
+                    code = code.Replace("\r\n", "\n");
                 }
 
                 var sourceFile = new SourceFile(
@@ -217,8 +287,13 @@ namespace Jint.Tests.Test262
         }
     }
 
-    public class SourceFile
+    public class SourceFile : IXunitSerializable
     {
+        public SourceFile()
+        {
+
+        }
+
         public SourceFile(
             string source,
             string fullPath,
@@ -233,11 +308,29 @@ namespace Jint.Tests.Test262
             Code = code;
         }
 
-        public string Source { get; }
-        public bool Skip { get; }
-        public string Reason { get; }
-        public string FullPath { get; }
-        public string Code { get; }
+        public string Source { get; set; }
+        public bool Skip { get; set; }
+        public string Reason { get; set; }
+        public string FullPath { get; set; }
+        public string Code { get; set; }
+
+        public void Deserialize(IXunitSerializationInfo info)
+        {
+            Skip = info.GetValue<bool>(nameof(Skip));
+            Source = info.GetValue<string>(nameof(Source));
+            Reason = info.GetValue<string>(nameof(Reason));
+            FullPath = info.GetValue<string>(nameof(FullPath));
+            Code = info.GetValue<string>(nameof(Code));
+        }
+
+        public void Serialize(IXunitSerializationInfo info)
+        {
+            info.AddValue(nameof(Skip), Skip);
+            info.AddValue(nameof(Source), Source);
+            info.AddValue(nameof(Reason), Reason);
+            info.AddValue(nameof(FullPath), FullPath);
+            info.AddValue(nameof(Code), Code);
+        }
 
         public override string ToString()
         {

+ 2 - 1
Jint.Tests.Test262/test/built-ins/String/raw/special-characters.js

@@ -13,7 +13,8 @@ assert.sameValue(
   'Unicode escape sequences'
 );
 assert.sameValue(
-  String.raw`\
\
+  String.raw`\
+\
 \
 `,
   '\\\n\\\n\\\n',

+ 3 - 2
Jint.Tests.Test262/test/language/expressions/template-literal/tv-line-continuation.js

@@ -20,9 +20,10 @@ calls = 0;
   assert.sameValue(
     cs.raw[0], '\u005C\n\u005C\n\u005C\n', 'Line Feed and Carriage Return'
   );
-})`\
+})`\
 \
-\
`
+\
+`
 assert.sameValue(calls, 1);
 
 calls = 0;

+ 566 - 11
Jint.Tests.Test262/test/skipped.json

@@ -91,10 +91,6 @@
     "source": "built-ins/Array/from/items-is-arraybuffer.js",
     "reason": "ArrayBuffer not implemented"
   },
-  {
-    "source": "built-ins/Array/prototype/concat/Array.prototype.concat_non-array.js",
-    "reason": "class keyword not implemented"
-  },
   {
     "source": "built-ins/Array/prototype/concat/Array.prototype.concat_large-typed-array.js",
     "reason": "Uint8Array not implemented"
@@ -183,10 +179,6 @@
     "source": "built-ins/Set/prototype/values/does-not-have-setdata-internal-slot-weakset.js",
     "reason": "WeakSet not implemented"
   },
-  {
-    "source": "built-ins/Set/prototype/forEach/this-arg-explicit-cannot-override-lexical-this-arrow.js",
-    "reason": "arrow functions not implemented"
-  },
   {
     "source": "built-ins/Array/prototype/reverse/length-exceeding-integer-limit-with-proxy.js",
     "reason": "proxies not implemented"
@@ -212,7 +204,7 @@
     "reason": "requires toLocaleString changes"
   },
 
-  // experimenta
+  // experimentals start
 
   {
     "source": "built-ins/Array/prototype/flat/array-like-objects.js",
@@ -278,16 +270,579 @@
     "source": "built-ins/Array/prototype/flatMap/thisArg-argument.js",
     "reason": "experimental"
   },
+
+  // experimentals end
+
   {
     "source": "built-ins/String/prototype/padEnd/observable-operations.js",
-    "reason": "observables not implemetned"
+    "reason": "observables not implemented"
   },
   {
     "source": "built-ins/String/prototype/padStart/observable-operations.js",
-    "reason": "observables not implemetned"
+    "reason": "observables not implemented"
   },
   {
     "source": "built-ins/StringIteratorPrototype/next/next-iteration-surrogate-pairs.js",
     "reason": "code point iteration not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-const-bound-names-fordecl-tdz.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-const-fresh-binding-per-iteration.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-decl-expr.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-let-bound-names-fordecl-tdz.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-let-destructuring.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-let-fresh-binding-per-iteration.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-lhs-let.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/scope-head-var-none.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for/head-const-fresh-binding-per-iteration.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for/head-let-destructuring.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for/head-let-fresh-binding-per-iteration.js",
+    "reason": "let/const not implemented"
+  },
+  {
+    "source": "language/statements/for-in/head-var-bound-names-dup.js",
+    "reason": "destructing not implemented"
+  },
+  {
+    "source": "language/rest-parameters/arrow-function.js",
+    "reason": "Github issue #600 - rest parameters hang when not set"
+  },
+  {
+    "source": "language/rest-parameters/object-pattern.js",
+    "reason": "destructing not implemented"
+  },
+  {
+    "source": "built-ins/Math/pow/int32_min-exponent.js",
+    "reason": "const not implemented"
+  },
+  {
+    "source": "language/expressions/template-literal/tv-line-terminator-sequence.js",
+    "reason": "Line feed problems (git, windows, linux)"
+  },
+  {
+    "source": "built-ins/Number/prototype/toPrecision/range.js",
+    "reason": "Github issue #599 - dtoa function not precise enough"
+  },
+  {
+    "source": "built-ins/Number/prototype/toFixed/range.js",
+    "reason": "Github issue #599 - dtoa function not precise enough"
+  },
+  {
+    "source": "built-ins/Number/prototype/toExponential/range.js",
+    "reason": "Github issue #599 - dtoa function not precise enough"
+  },
+  {
+    "source": "language/types/number/8.5.1.js",
+    "reason": "C# can't distinguish 1.797693134862315808e+308 and 1.797693134862315708145274237317e+308"
+  },
+
+  // function behaviour to be implemented
+  {
+    "source": "language/expressions/function/scope-name-var-open-non-strict.js",
+    "reason": "inner binding is immutable (from parameters) Expected SameValue(«null», «function() {{ ... }}») to be true"
+  },
+  {
+    "source": "language/expressions/function/scope-name-var-open-strict.js",
+    "reason": "inner binding rejects modification (from parameters) Expected a Error to be thrown but no exception was thrown at all"
+  },
+
+  // let support
+  {
+    "source": "language/statements/for/dstr-const-ary-ptrn-rest-obj-prop-id.js",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-var-ary-ptrn-rest-obj-prop-id.js",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-let-ary-ptrn-rest-obj-prop-id.js",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-ary-ptrn-elem-id-init-fn-name-cover.js",
+    "mode": "strict",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-ary-ptrn-rest-obj-prop-id.js",
+    "mode": "strict",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-dflt-ary-ptrn-rest-obj-prop-id.js",
+    "mode": "strict",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/expressions/function/dstr-ary-ptrn-rest-obj-prop-id.js",
+    "mode": "strict",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/expressions/function/dstr-dflt-ary-ptrn-rest-obj-prop-id.js",
+    "mode": "strict",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-ary-ptrn-rest-obj-prop-id.js",
+    "mode": "strict",
+    "reason": "let not implemented"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-dflt-ary-ptrn-rest-obj-prop-id.js",
+    "mode": "strict",
+    "reason": "let not implemented"
+  },
+
+  // class support
+  {
+    "source": "language/expressions/arrow-function/dstr-dflt-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/concat/Array.prototype.concat_non-array.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/rest-parameters/with-new-target.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-const-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-let-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-let-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-const-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-var-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/statements/for/dstr-var-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-dflt-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/lexical-super-property-from-within-constructor.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/lexical-supercall-from-immediately-invoked-arrow.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/lexical-super-property.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/arrow-function/lexical-super-call-from-within-constructor.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/function/dstr-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/function/dstr-dflt-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/function/dstr-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/function/dstr-dflt-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-dflt-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-obj-ptrn-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-dflt-ary-ptrn-elem-id-init-fn-name-class.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/object/method-definition/name-invoke-ctor",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/object/method-definition/name-invoke-ctor.js",
+    "reason": "class not implemented"
+  },
+  {
+    "source": "language/expressions/object/method-definition/name-prototype-prop.js",
+    "reason": "class not implemented"
+  },
+
+
+  {
+    "source": "language/expressions/object/accessor-name-computed-yield-id.js",
+    "reason": "accessor / yield not implemented"
+  },
+  {
+    "source": "language/expressions/object/accessor-name-computed.js",
+    "reason": "yield not implemented"
+  },
+  {
+    "source": "language/expressions/object/prop-dup-set-get-set.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/accessor-name-computed-err-to-prop-key.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/accessor-name-computed-err-unresolvable.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/accessor-name-computed-in.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/accessor-name-literal-numeric-leading-decimal.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/accessor-name-literal-numeric-non-canonical.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/fn-name-accessor-get.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/fn-name-accessor-set.js",
+    "reason": "accessor not implemented"
+  },
+  {
+    "source": "language/expressions/object/method-definition/name-prop-name-yield-id.js",
+    "reason": "yield not implemented"
+  },
+
+
+  {
+    "source": "language/expressions/object/method.js",
+    "reason": "setPrototypeOf not implemented"
+  },
+  {
+    "source": "language/expressions/object/setter-super-prop.js",
+    "reason": "setPrototypeOf not implemented"
+  },
+  {
+    "source": "language/expressions/object/getter-super-prop.js",
+    "reason": "setPrototypeOf not implemented"
+  },
+
+
+  {
+    "source": "language/expressions/object/fn-name-arrow.js",
+    "reason": "symbols not identifiable in property name"
+  },
+  {
+    "source": "language/expressions/object/fn-name-cover.js",
+    "reason": "symbols not identifiable in property name"
+  },
+  {
+    "source": "language/expressions/object/fn-name-fn.js",
+    "reason": "symbols not identifiable in property name"
+  },
+  {
+    "source": "language/expressions/object/method-definition/fn-name-fn.js",
+    "reason": "symbols not identifiable in property name"
+  },
+  {
+    "source": "language/expressions/object/method-definition/name-name-prop-symbol.js",
+    "reason": "symbols not identifiable in property name"
+  },
+
+
+  {
+    "source": "language/expressions/arrow-function/scope-paramsbody-var-open.js",
+    "reason": "not implemented: Creation of new variable environment for the function body (as distinct from that for the function's parameters)"
+  },
+  {
+    "source": "language/expressions/function/scope-paramsbody-var-open.js",
+    "reason": "not implemented: Creation of new variable environment for the function body (as distinct from that for the function's parameters)"
+  },
+  {
+    "source": "language/expressions/object/scope-meth-paramsbody-var-open.js",
+    "reason": "not implemented: Creation of new variable environment for the function body (as distinct from that for the function's parameters)"
+  },
+  {
+    "source": "language/expressions/object/scope-setter-paramsbody-var-open.js",
+    "reason": "not implemented: Creation of new variable environment for the function body (as distinct from that for the function's parameters)"
+  },
+
+  // Esprima problems
+  
+  {
+    "source": "language/statements/for/dstr-var-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-var-obj-ptrn-rest-skip-non-enumerable",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-var-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-const-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-const-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-const-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-const-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-let-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-let-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-let-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/dstr-var-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/call/trailing-comma.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/statements/for/head-lhs-let.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/white-space/mongolian-vowel-separator-eval.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/dflt-params-trailing-comma.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-dflt-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-dflt-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-dflt-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/dstr-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/params-trailing-comma-multiple.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/arrow-function/params-trailing-comma-single.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/dflt-params-trailing-comma.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/dstr-dflt-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/dstr-dflt-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/dstr-dflt-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/dstr-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/dstr-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/dstr-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/params-trailing-comma-multiple.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-dflt-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-dflt-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-obj-ptrn-rest-getter.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-obj-ptrn-rest-val-obj.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/let-non-strict-access.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/async-meth-dflt-params-abrupt.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/async-meth-dflt-params-arg-val-not-undefined.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/async-meth-dflt-params-ref-later.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/async-meth-dflt-params-ref-prior.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/async-meth-dflt-params-ref-self.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-dflt-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/dstr-meth-obj-ptrn-rest-skip-non-enumerable.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/let-non-strict-syntax.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/meth-dflt-params-trailing-comma.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/meth-params-trailing-comma-multiple.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/meth-params-trailing-comma-single.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/name-param-id-yield.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/yield-non-strict-access.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/yield-non-strict-syntax.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/object/method-definition/object-method-returns-promise.js",
+    "reason": "Esprima problem"
+  },
+  {
+    "source": "language/expressions/function/params-trailing-comma-single.js",
+    "reason": "Esprima problem"
   }
 ]

+ 1 - 10
Jint.Tests/Parser/JavascriptParserTests.cs

@@ -32,7 +32,6 @@ namespace Jint.Tests.Parser
             var program = new JavaScriptParser("this").ParseProgram();
             var body = program.Body;
 
-            Assert.NotNull(body);
             Assert.Single(body);
             Assert.Equal(Nodes.ThisExpression, body.First().As<ExpressionStatement>().Expression.Type);
         }
@@ -43,7 +42,6 @@ namespace Jint.Tests.Parser
             var program = new JavaScriptParser("null").ParseProgram();
             var body = program.Body;
 
-            Assert.NotNull(body);
             Assert.Single(body);
             Assert.Equal(Nodes.Literal, body.First().As<ExpressionStatement>().Expression.Type);
             Assert.Equal(null, body.First().As<ExpressionStatement>().Expression.As<Literal>().Value);
@@ -59,7 +57,6 @@ namespace Jint.Tests.Parser
             ").ParseProgram();
             var body = program.Body;
 
-            Assert.NotNull(body);
             Assert.Single(body);
             Assert.Equal(Nodes.Literal, body.First().As<ExpressionStatement>().Expression.Type);
             Assert.Equal(42d, body.First().As<ExpressionStatement>().Expression.As<Literal>().Value);
@@ -74,7 +71,6 @@ namespace Jint.Tests.Parser
             var program = new JavaScriptParser("(1 + 2 ) * 3").ParseProgram();
             var body = program.Body;
 
-            Assert.NotNull(body);
             Assert.Single(body);
             Assert.NotNull(binary = body.First().As<ExpressionStatement>().Expression.As<BinaryExpression>());
             Assert.Equal(3d, binary.Right.As<Literal>().Value);
@@ -111,7 +107,6 @@ namespace Jint.Tests.Parser
             var program = new JavaScriptParser(source).ParseProgram();
             var body = program.Body;
 
-            Assert.NotNull(body);
             Assert.Single(body);
             Assert.NotNull(literal = body.First().As<ExpressionStatement>().Expression.As<Literal>());
             Assert.Equal(Convert.ToDouble(expected), Convert.ToDouble(literal.Value));
@@ -131,7 +126,6 @@ namespace Jint.Tests.Parser
             var program = new JavaScriptParser(source).ParseProgram();
             var body = program.Body;
 
-            Assert.NotNull(body);
             Assert.Single(body);
             Assert.NotNull(literal = body.First().As<ExpressionStatement>().Expression.As<Literal>());
             Assert.Equal(expected, literal.Value);
@@ -172,10 +166,7 @@ namespace Jint.Tests.Parser
 
         public void ShouldInsertSemicolons(string source)
         {
-            var program = new JavaScriptParser(source).ParseProgram();
-            var body = program.Body;
-
-            Assert.NotNull(body);
+            new JavaScriptParser(source).ParseProgram();
         }
 
         [Fact]

+ 1 - 1
Jint.Tests/Runtime/Converters/EnumsToStringConverter.cs

@@ -6,7 +6,7 @@ namespace Jint.Tests.Runtime.Converters
 {
     public class EnumsToStringConverter : IObjectConverter
     {
-        public bool TryConvert(object value, out JsValue result)
+        public bool TryConvert(Engine engine, object value, out JsValue result)
         {
             if (value is Enum)
             {

+ 3 - 3
Jint.Tests/Runtime/Converters/NegateBoolConverter.cs

@@ -5,11 +5,11 @@ namespace Jint.Tests.Runtime.Converters
 {
     public class NegateBoolConverter : IObjectConverter
     {
-        public bool TryConvert(object value, out JsValue result)
+        public bool TryConvert(Engine engine, object value, out JsValue result)
         {
-            if (value is bool)
+            if (value is bool b)
             {
-                result = !(bool) value;
+                result = !b;
                 return true;
             }
 

+ 20 - 0
Jint.Tests/Runtime/Domain/A.cs

@@ -104,5 +104,25 @@ namespace Jint.Tests.Runtime.Domain
         {
             callback(18);
         }
+
+        public int Call19(int a = 0)
+        {
+            return a;
+        }
+
+        public static int Call19Static(int a = 0)
+        {
+            return a;
+        }
+
+        public int Call20(int a, int b = 1, int c = 2)
+        {
+            return a + b + c;
+        }
+
+        public static int Call20Static(int a, int b = 1, int c = 2)
+        {
+            return a + b + c;
+        }
     }
 }

+ 253 - 8
Jint.Tests/Runtime/EngineTests.cs

@@ -4,7 +4,9 @@ using System.IO;
 using System.Reflection;
 using Esprima;
 using Esprima.Ast;
-using Jint.Native.Number;
+using Jint.Native;
+using Jint.Native.Array;
+using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Debugger;
 using Xunit;
@@ -105,6 +107,37 @@ namespace Jint.Tests.Runtime
             Assert.Equal(expected, result);
         }
 
+        [Fact]
+        public void ShouldHaveProperReferenceErrorMessage()
+        {
+            RunTest(@"
+                'use strict';
+                var arr = [1, 2];
+                try {
+                    for (i in arr) { }
+                    assert(false);
+                }
+                catch (ex) {
+                    assert(ex.message === 'i is not defined');
+                }
+            ");
+        }
+
+        [Fact]
+        public void ShouldHaveProperNotAFunctionErrorMessage()
+        {
+            RunTest(@"
+                try {
+                    var example = {};
+                    example();
+                    assert(false);
+                }
+                catch (ex) {
+                    assert(ex.message === 'example is not a function');
+                }
+            ");
+        }
+
         [Fact]
         public void ShouldEvaluateHasOwnProperty()
         {
@@ -157,6 +190,7 @@ namespace Jint.Tests.Runtime
 
         }
 
+
         [Fact]
         public void FunctionConstructorCall()
         {
@@ -170,6 +204,45 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void ArrowFunctionCall()
+        {
+            RunTest(@"
+                var add = (a, b) => {
+                    return a + b;
+                }
+
+                var x = add(1, 2);
+                assert(x == 3);
+            ");
+        }
+
+        [Fact]
+        public void ArrowFunctionExpressionCall()
+        {
+            RunTest(@"
+                var add = (a, b) => a + b;
+
+                var x = add(1, 2);
+                assert(x === 3);
+            ");
+        }
+
+        [Fact]
+        public void ArrowFunctionScope()
+        {
+            RunTest(@"
+                var bob = {
+                    _name: ""Bob"",
+                    _friends: [""Alice""],
+                    printFriends() {
+                        this._friends.forEach(f => assert(this._name === ""Bob"" && f === ""Alice""))
+                    }
+                };
+                bob.printFriends();
+            ");
+        }
+
         [Fact]
         public void NewObjectsShouldUsePrivateProperties()
         {
@@ -869,8 +942,8 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void ShouldComputeFractionInBase()
         {
-            Assert.Equal("011", NumberPrototype.ToFractionBase(0.375, 2));
-            Assert.Equal("14141414141414141414141414141414141414141414141414", NumberPrototype.ToFractionBase(0.375, 5));
+            Assert.Equal("011", _engine.Number.PrototypeObject.ToFractionBase(0.375, 2));
+            Assert.Equal("14141414141414141414141414141414141414141414141414", _engine.Number.PrototypeObject.ToFractionBase(0.375, 5));
         }
 
         [Fact]
@@ -936,6 +1009,16 @@ namespace Jint.Tests.Runtime
             Assert.Throws<ArgumentException>(() => _engine.Invoke(foo, obj, new object[] { }));
         }
 
+        [Fact]
+        public void ShouldNotAllowModifyingSharedUndefinedDescriptor()
+        {
+            var e = new Engine();
+            e.Execute("var x = { literal: true };");
+
+            var pd = e.GetValue("x").AsObject().GetProperty("doesNotExist");
+            Assert.Throws<InvalidOperationException>(() => pd.Value = "oh no, assigning this breaks things");
+        }
+
         [Theory]
         [InlineData("0", 0, 16)]
         [InlineData("1", 1, 16)]
@@ -945,7 +1028,7 @@ namespace Jint.Tests.Runtime
         [InlineData("2qgpckvng1s", 10000000000000000L, 36)]
         public void ShouldConvertNumbersToDifferentBase(string expected, long number, int radix)
         {
-            var result = NumberPrototype.ToBase(number, radix);
+            var result = _engine.Number.PrototypeObject.ToBase(number, radix);
             Assert.Equal(expected, result);
         }
 
@@ -1093,7 +1176,7 @@ namespace Jint.Tests.Runtime
             {
                 Assert.Equal(1, e.LineNumber);
                 Assert.Equal(9, e.Column);
-                Assert.Equal("jQuery.js", e.Source);
+                Assert.Equal("jQuery.js", e.SourceText);
             }
         }
         #region DateParsingAndStrings
@@ -1299,6 +1382,20 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void ShouldExecuteDromaeoBase64()
+        {
+            RunTest(@"
+var startTest = function () { };
+var test = function (name, fn) { fn(); };
+var endTest = function () { };
+var prep = function (fn) { fn(); };
+            ");
+
+            var content = GetEmbeddedFile("dromaeo-string-base64.js");
+            RunTest(content);
+        }
+
         [Fact]
         public void ShouldExecuteKnockoutWithErrorWhenIntolerant()
         {
@@ -1920,6 +2017,12 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void RegExpPrototypeToString()
+        {
+            RunTest("assert(RegExp.prototype.toString() === '//');");
+        }
+
         [Fact]
         public void ShouldSetYearBefore1970()
         {
@@ -1994,6 +2097,13 @@ namespace Jint.Tests.Runtime
             Assert.True(result);
         }
 
+        [Fact]
+        public void CanStringifyToConsole()
+        {
+            var engine = new Engine(options => options.AllowClr(typeof(Console).Assembly));
+            engine.Execute("System.Console.WriteLine(JSON.stringify({x:12, y:14}));");
+        }
+
         [Fact]
         public void ShouldNotCompareClrInstancesWithObjects()
         {
@@ -2012,11 +2122,10 @@ namespace Jint.Tests.Runtime
         public void ShouldStringifyNumWithoutV8DToA()
         {
             // 53.6841659 cannot be converted by V8's DToA => "old" DToA code will be used.
-
             var engine = new Engine();
-            Native.JsValue val = engine.Execute("JSON.stringify(53.6841659)").GetCompletionValue();
+            var val = engine.Execute("JSON.stringify(53.6841659)").GetCompletionValue();
 
-            Assert.True(val.AsString() == "53.6841659");
+            Assert.Equal("53.6841659", val.AsString());
         }
 
         [Fact]
@@ -2505,5 +2614,141 @@ namespace Jint.Tests.Runtime
             Assert.Equal("concatwelldone", result);
         }
 
+        [Fact]
+        public void ComplexMappingAndReducing()
+        {
+            const string program = @"
+Object.map = function (o, f, ctx) {
+    ctx = ctx || this;
+    var result = [];
+    Object.keys(o).forEach(function(k) {
+        result.push(f.call(ctx, o[k], k));
+	});
+    return result;
+};
+
+var x1 = {""Value"":1.0,""Elements"":[{""Name"":""a"",""Value"":""b"",""Decimal"":3.2},{""Name"":""a"",""Value"":""b"",""Decimal"": 3.5}],""Values"":{""test"": 2,""test1"":3,""test2"": 4}}
+var x2 = {""Value"":2.0,""Elements"":[{""Name"":""aa"",""Value"":""ba"",""Decimal"":3.5}],""Values"":{""test"":1,""test1"":2,""test2"":3}};
+
+function output(x) {
+	var elements = x.Elements.map(function(a){return a.Decimal;});
+	var values = x.Values;
+	var generated = x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {});
+	return {
+        TestDictionary1 : values, 
+        TestDictionary2 : x.Values, 
+        TestDictionaryDirectAccess1 : Object.keys(x.Values).length,
+        TestDictionaryDirectAccess2 : Object.keys(x.Values),
+        TestDictionaryDirectAccess4 : Object.keys(x.Values).map(function(a){return x.Values[a];}),
+        TestDictionarySum1 : Object.keys(values).map(function(a){return{Key: a,Value:values[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0),
+        TestDictionarySum2 : Object.keys(x.Values).map(function(a){return{Key: a,Value:x.Values[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0),
+        TestDictionarySum3 : Object.keys(x.Values).map(function(a){return x.Values[a];}).reduce(function(a, b) { return a + b; }, 0),
+        TestDictionaryAverage1 : Object.keys(values).map(function(a){return{Key: a,Value:values[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(values).length||1),
+        TestDictionaryAverage2 : Object.keys(x.Values).map(function(a){return{Key: a,Value:x.Values[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(x.Values).length||1),
+        TestDictionaryAverage3 : Object.keys(x.Values).map(function(a){return x.Values[a];}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(x.Values).map(function(a){return x.Values[a];}).length||1),
+        TestDictionaryFunc1 : Object.keys(x.Values).length,
+        TestDictionaryFunc2 : Object.map(x.Values, function(v, k){ return v;}),
+        TestGeneratedDictionary1 : generated,
+        TestGeneratedDictionary2 : x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {}),
+        TestGeneratedDictionary3 : Object.keys(generated).length,
+        TestGeneratedDictionarySum1 : Object.keys(generated).map(function(a){return{Key: a,Value:generated[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0),
+        TestGeneratedDictionarySum2 : Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).map(function(a){return{Key: a,Value:x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0),
+        TestGeneratedDictionaryAverage1 : Object.keys(generated).map(function(a){return{Key: a,Value:generated[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(generated).length||1),
+        TestGeneratedDictionaryAverage2 : Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).map(function(a){return{Key: a,Value:x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).length||1), 
+        TestGeneratedDictionaryDirectAccess1 : Object.keys(generated),
+        TestGeneratedDictionaryDirectAccess2 : Object.keys(generated).map(function(a){return generated[a];}),
+        TestGeneratedDictionaryDirectAccess3 : Object.keys(generated).length, 
+        TestList1 : elements.reduce(function(a, b) { return a + b; }, 0), 
+        TestList2 : x.Elements.map(function(a){return a.Decimal;}).reduce(function(a, b) { return a + b; }, 0),
+        TestList3 : x.Elements.map(function(a){return a.Decimal;}).reduce(function(a, b) { return a + b; }, 0),
+        TestList4 : x.Elements.map(function(a){return a.Decimal;}).reduce(function(a, b) { return a + b; }, 0)/(x.Elements.length||1),
+        TestList5 : x.Elements.map(function(a){return a.Decimal;}).reduce(function(a, b) { return a + b; }, 0)/(x.Elements.map((function(a){return a.Decimal;})).length||1)
+    };
+};
+";
+            _engine.Execute(program);
+            var result1 = (ObjectInstance) _engine.Execute("output(x1)").GetCompletionValue();
+            var result2 = (ObjectInstance) _engine.Execute("output(x2)").GetCompletionValue();
+
+            Assert.Equal(9, TypeConverter.ToNumber(result1.Get("TestDictionarySum1")));
+            Assert.Equal(9, TypeConverter.ToNumber(result1.Get("TestDictionarySum2")));
+            Assert.Equal(9, TypeConverter.ToNumber(result1.Get("TestDictionarySum3")));
+
+            Assert.Equal(3, TypeConverter.ToNumber(result1.Get("TestDictionaryAverage1")));
+            Assert.Equal(3, TypeConverter.ToNumber(result1.Get("TestDictionaryAverage2")));
+            Assert.Equal(3, TypeConverter.ToNumber(result1.Get("TestDictionaryAverage3")));
+
+            Assert.Equal(3, TypeConverter.ToNumber(result1.Get("TestDictionaryFunc1")));
+            Assert.Equal(1, TypeConverter.ToNumber(result1.Get("TestGeneratedDictionary3")));
+
+            Assert.Equal(3.5, TypeConverter.ToNumber(result1.Get("TestGeneratedDictionarySum1")));
+            Assert.Equal(3.5, TypeConverter.ToNumber(result1.Get("TestGeneratedDictionarySum2")));
+            Assert.Equal(3.5, TypeConverter.ToNumber(result1.Get("TestGeneratedDictionaryAverage1")));
+            Assert.Equal(3.5, TypeConverter.ToNumber(result1.Get("TestGeneratedDictionaryAverage2")));
+
+            Assert.Equal(1, TypeConverter.ToNumber(result1.Get("TestGeneratedDictionaryDirectAccess3")));
+
+            Assert.Equal(6.7, TypeConverter.ToNumber(result1.Get("TestList1")));
+            Assert.Equal(6.7, TypeConverter.ToNumber(result1.Get("TestList2")));
+            Assert.Equal(6.7, TypeConverter.ToNumber(result1.Get("TestList3")));
+            Assert.Equal(3.35, TypeConverter.ToNumber(result1.Get("TestList4")));
+            Assert.Equal(3.35, TypeConverter.ToNumber(result1.Get("TestList5")));
+
+            Assert.Equal(6, TypeConverter.ToNumber(result2.Get("TestDictionarySum1")));
+            Assert.Equal(6, TypeConverter.ToNumber(result2.Get("TestDictionarySum2")));
+            Assert.Equal(6, TypeConverter.ToNumber(result2.Get("TestDictionarySum3")));
+
+            Assert.Equal(2, TypeConverter.ToNumber(result2.Get("TestDictionaryAverage1")));
+            Assert.Equal(2, TypeConverter.ToNumber(result2.Get("TestDictionaryAverage2")));
+            Assert.Equal(2, TypeConverter.ToNumber(result2.Get("TestDictionaryAverage3")));
+        }
+        [Fact]
+        public void ShouldBeAbleToSpreadArrayLiteralsAndFunctionParameters()
+        {
+            RunTest(@"
+                function concat(x, a, b) {
+                    x += a;
+                    x += b;
+                    return x;
+                }
+                var s = [...'abc'];
+                var c = concat(1, ...'ab');
+                var arr1 = [1, 2];
+                var arr2 = [3, 4 ];
+                var r = [...arr2, ...arr1];
+            ");
+
+            var arrayInstance = (ArrayInstance) _engine.GetValue("r");
+            Assert.Equal(arrayInstance[0], 3);
+            Assert.Equal(arrayInstance[1], 4);
+            Assert.Equal(arrayInstance[2], 1);
+            Assert.Equal(arrayInstance[3], 2);
+
+            arrayInstance = (ArrayInstance) _engine.GetValue("s");
+            Assert.Equal(arrayInstance[0], 'a');
+            Assert.Equal(arrayInstance[1], 'b');
+            Assert.Equal(arrayInstance[2], 'c');
+
+            var c = _engine.GetValue("c").ToString();
+            Assert.Equal("1ab", c);
+        }
+
+        [Fact]
+        public void ShouldSupportDefaultsInFunctionParameters()
+        {
+            RunTest(@"
+                function f(x, y=12) {
+                  // y is 12 if not passed (or passed as undefined)
+                  return x + y;
+                }
+            ");
+
+            var function = _engine.GetValue("f");
+            var result = function.Invoke(3).ToString();
+            Assert.Equal("15", result);
+
+            result = function.Invoke(3, JsValue.Undefined).ToString();
+            Assert.Equal("15", result);
+        }
     }
 }

+ 196 - 2
Jint.Tests/Runtime/InteropTests.cs

@@ -1,9 +1,13 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
 using System.Reflection;
 using Jint.Native;
 using Jint.Native.Array;
 using Jint.Native.Object;
+using Jint.Runtime.Interop;
 using Jint.Tests.Runtime.Converters;
 using Jint.Tests.Runtime.Domain;
 using Shapes;
@@ -120,6 +124,22 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void DynamicDelegateCanBeSet()
+        {
+#if NETFRAMEWORK
+            var parameters = new[] { Expression.Parameter(typeof(int)), Expression.Parameter(typeof(int)) };
+            var exp = Expression.Add(parameters[0], parameters[1]);
+            var del = Expression.Lambda(exp, parameters).Compile();
+
+            _engine.SetValue("add", del);
+            
+            RunTest(@"
+                assert(add(1,1) === 2);
+            ");
+#endif
+        }
+
         [Fact]
         public void ExtraParametersAreIgnored()
         {
@@ -152,6 +172,28 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void DelegateWithDefaultValueParametersCanBeInvoked()
+        {
+            var instance = new A();
+            _engine.SetValue("Instance", instance);
+            _engine.SetValue("Class", TypeReference.CreateTypeReference(_engine, typeof(A)));
+
+            RunTest(@"
+                assert(Instance.Call19() === 0);
+                assert(Instance.Call19(1) === 1);
+                assert(Instance.Call20(1) === 4);
+                assert(Instance.Call20(1, 2) === 5);
+                assert(Instance.Call20(1 , 2, 3) === 6);
+
+                assert(Class.Call19Static() === 0);
+                assert(Class.Call19Static(1) === 1);
+                assert(Class.Call20Static(1) === 4);
+                assert(Class.Call20Static(1, 2) === 5);
+                assert(Class.Call20Static(1 , 2, 3) === 6);
+            ");
+        }
+
         [Fact]
         public void CanGetObjectProperties()
         {
@@ -1153,6 +1195,83 @@ namespace Jint.Tests.Runtime
             Assert.Equal(Colors.Blue | Colors.Green, s.Color);
         }
 
+        enum TestEnumInt32 : int
+        {
+            None,
+            One = 1,
+            Min = int.MaxValue,
+            Max = int.MaxValue,
+        }
+
+        enum TestEnumUInt32 : uint
+        {
+            None,
+            One = 1,
+            Min = uint.MaxValue,
+            Max = uint.MaxValue,
+        }
+
+        enum TestEnumInt64 : long
+        {
+            None,
+            One = 1,
+            Min = long.MaxValue,
+            Max = long.MaxValue,
+        }
+
+        enum TestEnumUInt64 : ulong
+        {
+            None,
+            One = 1,
+            Min = ulong.MaxValue,
+            Max = ulong.MaxValue,
+        }
+
+        void TestEnum<T>(T enumValue)
+        {
+            object i = Convert.ChangeType(enumValue, Enum.GetUnderlyingType(typeof(T)));
+            string s = Convert.ToString(i, CultureInfo.InvariantCulture);
+            var o = new Tuple<T>(enumValue);
+            _engine.SetValue("o", o);
+            RunTest("assert(o.Item1 === " + s + ");");
+        }
+
+        [Fact]
+        public void ShouldWorkWithEnumInt32()
+        {
+            TestEnum(TestEnumInt32.None);
+            TestEnum(TestEnumInt32.One);
+            TestEnum(TestEnumInt32.Min);
+            TestEnum(TestEnumInt32.Max);
+        }
+
+        [Fact]
+        public void ShouldWorkWithEnumUInt32()
+        {
+            TestEnum(TestEnumUInt32.None);
+            TestEnum(TestEnumUInt32.One);
+            TestEnum(TestEnumUInt32.Min);
+            TestEnum(TestEnumUInt32.Max);
+        }
+
+        [Fact]
+        public void ShouldWorkWithEnumInt64()
+        {
+            TestEnum(TestEnumInt64.None);
+            TestEnum(TestEnumInt64.One);
+            TestEnum(TestEnumInt64.Min);
+            TestEnum(TestEnumInt64.Max);
+        }
+
+        [Fact]
+        public void ShouldWorkWithEnumUInt64()
+        {
+            TestEnum(TestEnumUInt64.None);
+            TestEnum(TestEnumUInt64.One);
+            TestEnum(TestEnumUInt64.Min);
+            TestEnum(TestEnumUInt64.Max);
+        }
+
         [Fact]
         public void EnumIsConvertedToNumber()
         {
@@ -1172,7 +1291,6 @@ namespace Jint.Tests.Runtime
             ");
         }
 
-
         [Fact]
         public void ShouldConvertToEnum()
         {
@@ -1221,7 +1339,6 @@ namespace Jint.Tests.Runtime
             ");
         }
 
-
         [Fact]
         public void ShouldUseExplicitPropertySetter()
         {
@@ -1620,5 +1737,82 @@ namespace Jint.Tests.Runtime
             Assert.Equal(engine.Invoke("throwException3").AsString(), exceptionMessage);
             Assert.Throws<ArgumentNullException>(() => engine.Invoke("throwException4"));
         }
+        
+        [Fact]
+        public void ArrayFromShouldConvertListToArrayLike()
+        {
+            var list = new List<Person>
+            {
+                new Person {Name = "Mike"},
+                new Person {Name = "Mika"}
+            };
+            _engine.SetValue("a", list);
+
+            RunTest(@"
+                var arr = new Array(a);
+                assert(arr.length === 2);
+                assert(arr[0].Name === 'Mike');
+                assert(arr[1].Name === 'Mika');
+            ");
+
+            RunTest(@"
+                var arr = Array.from(a);
+                assert(arr.length === 2);
+                assert(arr[0].Name === 'Mike');
+                assert(arr[1].Name === 'Mika');
+            ");
+        }
+        
+        [Fact]
+        public void ArrayFromShouldConvertArrayToArrayLike()
+        {
+            var list = new []
+            {
+                new Person {Name = "Mike"},
+                new Person {Name = "Mika"}
+            };
+            _engine.SetValue("a", list);
+
+            RunTest(@"
+                var arr = new Array(a);
+                assert(arr.length === 2);
+                assert(arr[0].Name === 'Mike');
+                assert(arr[1].Name === 'Mika');
+            ");
+
+            RunTest(@"
+                var arr = Array.from(a);
+                assert(arr.length === 2);
+                assert(arr[0].Name === 'Mike');
+                assert(arr[1].Name === 'Mika');
+            ");
+        }
+        
+        [Fact]
+        public void ArrayFromShouldConvertIEnumerable()
+        {
+            var enumerable = new []
+            {
+                new Person {Name = "Mike"},
+                new Person {Name = "Mika"}
+            }.Select(x => x);
+            
+            _engine.SetValue("a", enumerable);
+
+            RunTest(@"
+                var arr = new Array(a);
+                assert(arr.length === 2);
+                assert(arr[0].Name === 'Mike');
+                assert(arr[1].Name === 'Mika');
+            ");
+
+            RunTest(@"
+                var arr = Array.from(a);
+                assert(arr.length === 2);
+                assert(arr[0].Name === 'Mike');
+                assert(arr[1].Name === 'Mika');
+            ");
+        }
+
     }
 }

+ 147 - 0
Jint.Tests/Runtime/Scripts/dromaeo-string-base64.js

@@ -0,0 +1,147 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla XML-RPC Client component.
+ *
+ * The Initial Developer of the Original Code is
+ * Digital Creations 2, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Martijn Pieters <[email protected]> (original author)
+ *   Samuel Sieb <[email protected]>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// From: http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
+
+/* Convert data (an array of integers) to a Base64 string. */
+var toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+var base64Pad = '=';
+
+function toBase64(data) {
+    var result = '';
+    var length = data.length;
+    var i;
+    // Convert every three bytes to 4 ascii characters.
+    for (i = 0; i < (length - 2) ; i += 3) {
+        result += toBase64Table[data.charCodeAt(i) >> 2];
+        result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i + 1) >> 4)];
+        result += toBase64Table[((data.charCodeAt(i + 1) & 0x0f) << 2) + (data.charCodeAt(i + 2) >> 6)];
+        result += toBase64Table[data.charCodeAt(i + 2) & 0x3f];
+    }
+
+    // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+    if (length % 3) {
+        i = length - (length % 3);
+        result += toBase64Table[data.charCodeAt(i) >> 2];
+        if ((length % 3) == 2) {
+            result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.chartCodeAt(i + 1) >> 4)];
+            result += toBase64Table[(data.charCodeAt(i + 1) & 0x0f) << 2];
+            result += base64Pad;
+        } else {
+            result += toBase64Table[(data.charCodeAt(i) & 0x03) << 4];
+            result += base64Pad + base64Pad;
+        }
+    }
+
+    return result;
+}
+
+/* Convert Base64 data to a string */
+var toBinaryTable = [
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
+    -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
+];
+
+function base64ToString(data) {
+    var result = '';
+    var leftbits = 0; // number of bits decoded, but yet to be appended
+    var leftdata = 0; // bits decoded, but yet to be appended
+
+    // Convert one by one.
+    for (var i = 0; i < data.length; i++) {
+        var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+        var padding = (data.charCodeAt(i) == base64Pad.charCodeAt(0));
+        // Skip illegal characters and whitespace
+        if (c == -1) continue;
+
+        // Collect data into leftdata, update bitcount
+        leftdata = (leftdata << 6) | c;
+        leftbits += 6;
+
+        // If we have 8 or more bits, append 8 bits to the result
+        if (leftbits >= 8) {
+            leftbits -= 8;
+            // Append if not padding.
+            if (!padding)
+                result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+            leftdata &= (1 << leftbits) - 1;
+        }
+    }
+
+    // If there are any bits left, the base64 string was corrupted
+    if (leftbits)
+        throw Components.Exception('Corrupted base64 string');
+
+    return result;
+}
+
+startTest("dromaeo-string-base64", '09340c18');
+
+var str = [];
+
+for (var i = 0; i < 4096; i++)
+    str.push(String.fromCharCode((25 * Math.random()) + 97));
+
+str = str.join("");
+str += str;
+str += str;
+
+var base64;
+
+test("Convert String to Base 64", function () {
+    base64 = toBase64(str);
+});
+
+prep(function () {
+    if (!base64)
+        base64 = toBase64(str);
+});
+
+test("Convert Base 64 to String", function () {
+    if (str !== base64ToString(base64)) {
+        throw "String conversion mis-match.";
+    }
+});
+
+endTest();

+ 9 - 1
Jint/ArrayExt.cs

@@ -1,4 +1,6 @@
 
+using System.Runtime.CompilerServices;
+
 namespace System
 {
     internal static class ArrayExt
@@ -9,10 +11,16 @@ namespace System
 
             static EmptyArray()
             {
-                EmptyArray<T>.Value = new T[0];
+                Value = new T[0];
             }
         }
 
+        #if NETSTANDARD
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static T[] Empty<T>() => Array.Empty<T>();
+        #else
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static T[] Empty<T>() => EmptyArray<T>.Value;
+        #endif
     }
 }

+ 50 - 174
Jint/Engine.cs

@@ -27,6 +27,7 @@ using Jint.Runtime.Debugger;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
 using Jint.Runtime.Interop;
+using Jint.Runtime.Interpreter;
 using Jint.Runtime.References;
 
 namespace Jint
@@ -36,26 +37,24 @@ namespace Jint
         private static readonly ParserOptions DefaultParserOptions = new ParserOptions
         {
             AdaptRegexp = true,
-            Tolerant = false,
+            Tolerant = true,
             Loc = true
         };
 
-        private readonly ExpressionInterpreter _expressions;
-        private readonly StatementInterpreter _statements;
         private readonly ExecutionContextStack _executionContexts;
         private JsValue _completionValue = JsValue.Undefined;
         private int _statementsCount;
         private long _initialMemoryUsage;
         private long _timeoutTicks;
-        private INode _lastSyntaxNode;
+        internal INode _lastSyntaxNode;
 
         // cached access
         private readonly bool _isDebugMode;
         internal readonly bool _isStrict;
         private readonly int _maxStatements;
         private readonly long _memoryLimit;
-        private readonly bool _runBeforeStatementChecks;
-        private readonly IReferenceResolver _referenceResolver;
+        internal readonly bool _runBeforeStatementChecks;
+        internal readonly IReferenceResolver _referenceResolver;
         internal readonly ReferencePool _referencePool;
         internal readonly ArgumentsInstancePool _argumentsInstancePool;
         internal readonly JsValueArrayPool _jsValueArrayPool;
@@ -246,9 +245,6 @@ namespace Jint
             Eval = new EvalFunctionInstance(this, System.ArrayExt.Empty<string>(), LexicalEnvironment.NewDeclarativeEnvironment(this, ExecutionContext.LexicalEnvironment), StrictModeScope.IsStrictModeCode);
             Global.FastAddProperty("eval", Eval, true, false, true);
 
-            _statements = new StatementInterpreter(this);
-            _expressions = new ExpressionInterpreter(this);
-
             if (Options._IsClrAllowed)
             {
                 Global.FastAddProperty("System", new NamespaceReference(this, "System"), false, false, false);
@@ -259,7 +255,7 @@ namespace Jint
             }
 
             ClrTypeConverter = new DefaultTypeConverter(this);
-            BreakPoints = new List<BreakPoint>();
+            BreakPoints = new System.Collections.Generic.List<BreakPoint>();
             DebugHandler = new DebugHandler(this);
         }
 
@@ -305,7 +301,7 @@ namespace Jint
         public event DebugStepDelegate Step;
         public event BreakDelegate Break;
         internal DebugHandler DebugHandler { get; private set; }
-        public List<BreakPoint> BreakPoints { get; private set; }
+        public System.Collections.Generic.List<BreakPoint> BreakPoints { get; private set; }
 
         internal StepMode? InvokeStepEvent(DebugInformation info)
         {
@@ -431,9 +427,14 @@ namespace Jint
 
             using (new StrictModeScope(_isStrict || program.Strict))
             {
-                DeclarationBindingInstantiation(DeclarationBindingType.GlobalCode, program.HoistingScope.FunctionDeclarations, program.HoistingScope.VariableDeclarations, null, null);
+                DeclarationBindingInstantiation(
+                    DeclarationBindingType.GlobalCode,
+                    program.HoistingScope,
+                    functionInstance: null,
+                    arguments: null);
 
-                var result = _statements.ExecuteProgram(program);
+                var list = new JintStatementList(this, null, program.Body);
+                var result = list.Execute();
                 if (result.Type == CompletionType.Throw)
                 {
                     var ex = new JavaScriptException(result.GetValueOrDefault()).SetCallstack(this, result.Location);
@@ -459,91 +460,7 @@ namespace Jint
             return _completionValue;
         }
 
-        public Completion ExecuteStatement(Statement statement)
-        {
-            _lastSyntaxNode = statement;
-
-            if (_runBeforeStatementChecks)
-            {
-                BeforeExecuteStatement(statement);
-            }
-
-            switch (statement.Type)
-            {
-                case Nodes.BlockStatement:
-                    return _statements.ExecuteStatementList(((BlockStatement) statement).Body);
-
-                case Nodes.ReturnStatement:
-                    var jsValue = ((ReturnStatement) statement).Argument == null
-                        ? Undefined.Instance
-                        : GetValue(EvaluateExpression(((ReturnStatement) statement).Argument), true);
-
-                    return new Completion(CompletionType.Return, jsValue, null);
-
-                case Nodes.VariableDeclaration:
-                    return _statements.ExecuteVariableDeclaration((VariableDeclaration) statement);
-
-                case Nodes.BreakStatement:
-                    return _statements.ExecuteBreakStatement((BreakStatement) statement);
-
-                case Nodes.ContinueStatement:
-                    return _statements.ExecuteContinueStatement((ContinueStatement) statement);
-
-                case Nodes.DoWhileStatement:
-                    return _statements.ExecuteDoWhileStatement((DoWhileStatement) statement);
-
-                case Nodes.EmptyStatement:
-                    return new Completion(CompletionType.Normal, null, null);
-
-                case Nodes.ExpressionStatement:
-                    return new Completion(
-                        CompletionType.Normal,
-                        GetValue(EvaluateExpression(((ExpressionStatement) statement).Expression), true),
-                        null);
-
-                case Nodes.ForStatement:
-                    return _statements.ExecuteForStatement((ForStatement) statement);
-
-                case Nodes.ForInStatement:
-                    return _statements.ExecuteForInStatement((ForInStatement) statement);
-
-                case Nodes.IfStatement:
-                    return _statements.ExecuteIfStatement((IfStatement) statement);
-
-                case Nodes.LabeledStatement:
-                    return _statements.ExecuteLabeledStatement((LabeledStatement) statement);
-
-                case Nodes.SwitchStatement:
-                    return _statements.ExecuteSwitchStatement((SwitchStatement) statement);
-
-                case Nodes.FunctionDeclaration:
-                    return new Completion(CompletionType.Normal, null, null);
-
-                case Nodes.ThrowStatement:
-                    return _statements.ExecuteThrowStatement((ThrowStatement) statement);
-
-                case Nodes.TryStatement:
-                    return _statements.ExecuteTryStatement((TryStatement) statement);
-
-                case Nodes.WhileStatement:
-                    return _statements.ExecuteWhileStatement((WhileStatement) statement);
-
-                case Nodes.WithStatement:
-                    return _statements.ExecuteWithStatement((WithStatement) statement);
-
-                case Nodes.DebuggerStatement:
-                    return _statements.ExecuteDebuggerStatement((DebuggerStatement) statement);
-
-                case Nodes.Program:
-                    return _statements.ExecuteProgram((Program) statement);
-
-                default:
-                    ExceptionHelper.ThrowArgumentOutOfRangeException();
-                    return new Completion(CompletionType.Normal, null, null);
-            }
-        }
-
-        private void BeforeExecuteStatement(Statement statement)
+        internal void RunBeforeExecuteStatementChecks(Statement statement)
         {
             if (_maxStatements > 0 && _statementsCount++ > _maxStatements)
             {
@@ -577,66 +494,6 @@ namespace Jint
             }
         }
 
-        public object EvaluateExpression(INode expression)
-        {
-            _lastSyntaxNode = expression;
-
-            switch (expression.Type)
-            {
-                case Nodes.AssignmentExpression:
-                    return _expressions.EvaluateAssignmentExpression((AssignmentExpression) expression);
-
-                case Nodes.ArrayExpression:
-                    return _expressions.EvaluateArrayExpression((ArrayExpression) expression);
-
-                case Nodes.BinaryExpression:
-                    return _expressions.EvaluateBinaryExpression((BinaryExpression) expression);
-
-                case Nodes.CallExpression:
-                    return _expressions.EvaluateCallExpression((CallExpression) expression);
-
-                case Nodes.ConditionalExpression:
-                    return _expressions.EvaluateConditionalExpression((ConditionalExpression) expression);
-
-                case Nodes.FunctionExpression:
-                    return _expressions.EvaluateFunctionExpression((IFunction) expression);
-
-                case Nodes.Identifier:
-                    return _expressions.EvaluateIdentifier((Identifier) expression);
-
-                case Nodes.Literal:
-                    return _expressions.EvaluateLiteral((Literal) expression);
-
-                case Nodes.LogicalExpression:
-                    return _expressions.EvaluateLogicalExpression((BinaryExpression) expression);
-
-                case Nodes.MemberExpression:
-                    return _expressions.EvaluateMemberExpression((MemberExpression) expression);
-
-                case Nodes.NewExpression:
-                    return _expressions.EvaluateNewExpression((NewExpression) expression);
-
-                case Nodes.ObjectExpression:
-                    return _expressions.EvaluateObjectExpression((ObjectExpression) expression);
-
-                case Nodes.SequenceExpression:
-                    return _expressions.EvaluateSequenceExpression((SequenceExpression) expression);
-
-                case Nodes.ThisExpression:
-                    return _expressions.EvaluateThisExpression((ThisExpression) expression);
-
-                case Nodes.UpdateExpression:
-                    return _expressions.EvaluateUpdateExpression((UpdateExpression) expression);
-
-                case Nodes.UnaryExpression:
-                    return _expressions.EvaluateUnaryExpression((UnaryExpression) expression);
-
-                default:
-                    ExceptionHelper.ThrowArgumentOutOfRangeException();
-                    return null;
-            }
-        }
-
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.7.1
         /// </summary>
@@ -657,6 +514,11 @@ namespace Jint
                 return ((Completion) value).Value;
             }
 
+            return GetValue(reference, returnReferenceToPool);
+        }
+
+        internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
+        {
             if (reference._baseValue._type == Types.Undefined)
             {
                 if (_referenceResolver != null &&
@@ -664,7 +526,8 @@ namespace Jint
                 {
                     return val;
                 }
-                ExceptionHelper.ThrowReferenceError(this, reference.GetReferencedName() + " is not defined");
+
+                ExceptionHelper.ThrowReferenceError(this, reference);
             }
 
             var baseValue = reference._baseValue;
@@ -682,7 +545,8 @@ namespace Jint
                 {
                     _referencePool.Return(reference);
                 }
-                if (!(reference._baseValue._type != Types.Object && reference._baseValue._type != Types.None))
+
+                if (reference._baseValue._type == Types.Object)
                 {
                     var o = TypeConverter.ToObject(this, baseValue);
                     var v = o.Get(referencedName);
@@ -708,7 +572,7 @@ namespace Jint
                         return Undefined.Instance;
                     }
 
-                    var callable = (ICallable)getter.AsObject();
+                    var callable = (ICallable) getter.AsObject();
                     return callable.Call(baseValue, Arguments.Empty);
                 }
             }
@@ -731,15 +595,13 @@ namespace Jint
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.7.2
         /// </summary>
-        /// <param name="reference"></param>
-        /// <param name="value"></param>
         public void PutValue(Reference reference, JsValue value)
         {
             if (reference._baseValue._type == Types.Undefined)
             {
                 if (reference._strict)
                 {
-                    ExceptionHelper.ThrowReferenceError(this);
+                    ExceptionHelper.ThrowReferenceError(this, reference);
                 }
 
                 Global.Put(reference._name, value, false);
@@ -899,8 +761,7 @@ namespace Jint
         //  http://www.ecma-international.org/ecma-262/5.1/#sec-10.5
         internal bool DeclarationBindingInstantiation(
             DeclarationBindingType declarationBindingType,
-            List<FunctionDeclaration> functionDeclarations,
-            List<VariableDeclaration> variableDeclarations,
+            HoistingScope hoistingScope,
             FunctionInstance functionInstance,
             JsValue[] arguments)
         {
@@ -915,18 +776,25 @@ namespace Jint
                 var argsObj = _argumentsInstancePool.Rent(functionInstance, functionInstance._formalParameters, arguments, env, strict);
                 canReleaseArgumentsInstance = true;
 
+
+                var functionDeclaration = (functionInstance as ScriptFunctionInstance)?.FunctionDeclaration ??
+                    (functionInstance as ArrowFunctionInstance)?.FunctionDeclaration;
+
                 if (!ReferenceEquals(der, null))
                 {
-                    der.AddFunctionParameters(functionInstance, arguments, argsObj);
+                    der.AddFunctionParameters(functionInstance, arguments, argsObj, functionDeclaration);
                 }
                 else
                 {
+                    // TODO: match functionality with DeclarationEnvironmentRecord.AddFunctionParameters here
                     // slow path
                     var parameters = functionInstance._formalParameters;
                     for (var i = 0; i < parameters.Length; i++)
                     {
                         var argName = parameters[i];
                         var v = i + 1 > arguments.Length ? Undefined.Instance : arguments[i];
+                        v = DeclarativeEnvironmentRecord.HandleAssignmentPatternIfNeeded(functionDeclaration, v, i);
+
                         var argAlreadyDeclared = env.HasBinding(argName);
                         if (!argAlreadyDeclared)
                         {
@@ -939,11 +807,13 @@ namespace Jint
                 }
             }
 
+            var functionDeclarations = hoistingScope.FunctionDeclarations;
             if (functionDeclarations.Count > 0)
             {
-                AddFunctionDeclarations(functionDeclarations, env, configurableBindings, strict);
+                AddFunctionDeclarations(ref functionDeclarations, env, configurableBindings, strict);
             }
 
+            var variableDeclarations = hoistingScope.VariableDeclarations;
             if (variableDeclarations.Count == 0)
             {
                 return canReleaseArgumentsInstance;
@@ -952,7 +822,7 @@ namespace Jint
             // process all variable declarations in the current parser scope
             if (!ReferenceEquals(der, null))
             {
-                der.AddVariableDeclarations(variableDeclarations);
+                der.AddVariableDeclarations(ref variableDeclarations);
             }
             else
             {
@@ -965,11 +835,13 @@ namespace Jint
                     for (var j = 0; j < declarationsCount; j++)
                     {
                         var d = variableDeclaration.Declarations[j];
-                        var dn = ((Identifier) d.Id).Name;
-                        var varAlreadyDeclared = env.HasBinding(dn);
-                        if (!varAlreadyDeclared)
+                        if (d.Id is Identifier id1)
                         {
-                            env.CreateMutableBinding(dn, Undefined.Instance);
+                            var varAlreadyDeclared = env.HasBinding(id1.Name);
+                            if (!varAlreadyDeclared)
+                            {
+                                env.CreateMutableBinding(id1.Name, Undefined.Instance);
+                            }
                         }
                     }
                 }
@@ -978,7 +850,11 @@ namespace Jint
             return canReleaseArgumentsInstance;
         }
 
-        private void AddFunctionDeclarations(List<FunctionDeclaration> functionDeclarations, EnvironmentRecord env, bool configurableBindings, bool strict)
+        private void AddFunctionDeclarations(
+            ref NodeList<FunctionDeclaration> functionDeclarations,
+            EnvironmentRecord env,
+            bool configurableBindings,
+            bool strict)
         {
             var functionDeclarationsCount = functionDeclarations.Count;
             for (var i = 0; i < functionDeclarationsCount; i++)

+ 21 - 5
Jint/EsprimaExtensions.cs

@@ -1,13 +1,15 @@
 using System;
+using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Native.Symbol;
 using Jint.Runtime;
+using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint
 {
-    internal static class EsprimaExtensions
+    public static class EsprimaExtensions
     {
-        public static string GetKey<T>(this T expression) where T : Expression
+        public static string GetKey<T>(this T expression, Engine engine) where T : class, Expression
         {
             if (expression is Literal literal)
             {
@@ -21,8 +23,8 @@ namespace Jint
 
             if (expression is StaticMemberExpression staticMemberExpression)
             {
-                var obj = staticMemberExpression.Object.GetKey();
-                var property = staticMemberExpression.Property.GetKey();
+                var obj = staticMemberExpression.Object.GetKey(engine);
+                var property = staticMemberExpression.Property.GetKey(engine);
 
                 if (obj == "Symbol")
                 {
@@ -37,7 +39,21 @@ namespace Jint
                 }
             }
 
-            return ExceptionHelper.ThrowArgumentException<string>("Unable to extract correct key");
+            if (expression.Type == Nodes.CallExpression
+                || expression.Type == Nodes.BinaryExpression
+                || expression.Type == Nodes.UpdateExpression)
+            {
+                return Convert.ToString(JintExpression.Build(engine, expression).GetValue());
+            }
+
+            return ExceptionHelper.ThrowArgumentException<string>("Unable to extract correct key, node type: " + expression.Type);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool IsFunctionWithName<T>(this T node) where T : class, INode
+        {
+            var type = node.Type;
+            return type == Nodes.FunctionExpression || type == Nodes.ArrowFunctionExpression || type == Nodes.ArrowParameterPlaceHolder;
         }
     }
 }

+ 1 - 1
Jint/Jint.csproj

@@ -7,6 +7,6 @@
     <LangVersion>latest</LangVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Esprima" Version="1.0.0-beta-1070" />
+    <PackageReference Include="Esprima" Version="1.0.0-beta-1186" />
   </ItemGroup>
 </Project>

+ 8 - 3
Jint/Native/Argument/ArgumentsInstance.cs

@@ -118,10 +118,9 @@ namespace Jint.Native.Argument
                     return desc;
                 }
 
-                var isMapped = ParameterMap.GetOwnProperty(propertyName);
-                if (isMapped != PropertyDescriptor.Undefined)
+                if (ParameterMap.TryGetValue(propertyName, out var jsValue) && !jsValue.IsUndefined())
                 {
-                    desc.Value = ParameterMap.Get(propertyName);
+                    desc.Value = jsValue;
                 }
 
                 return desc;
@@ -173,6 +172,12 @@ namespace Jint.Native.Argument
 
         public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
         {
+            if (_func is ScriptFunctionInstance scriptFunctionInstance && scriptFunctionInstance._function._hasRestParameter)
+            {
+                // immutable
+                return false;
+            }
+
             EnsureInitialized();
 
             if (!_strict && !ReferenceEquals(ParameterMap, null))

+ 72 - 47
Jint/Native/Array/ArrayConstructor.cs

@@ -78,57 +78,71 @@ namespace Jint.Native.Array
 
             if (objectInstance.IsArrayLike)
             {
-                var operations = ArrayPrototype.ArrayOperations.For(objectInstance);
+                return ConstructArrayFromArrayLike(objectInstance, callable, thisArg);
+            }
 
-                var length = operations.GetLength();
+            if (objectInstance is IObjectWrapper wrapper && wrapper.Target is IEnumerable enumerable)
+            {
+                return ConstructArrayFromIEnumerable(enumerable);
+            }
 
-                var a = _engine.Array.ConstructFast(length);
-                var args = !ReferenceEquals(callable, null)
-                    ? _engine._jsValueArrayPool.RentArray(2)
-                    : null;
+            var instance = _engine.Array.ConstructFast(0);
+            if (objectInstance.TryGetIterator(_engine, out var iterator))
+            {
+                var protocol = new ArrayProtocol(_engine, thisArg, instance, iterator, callable);
+                protocol.Execute();
+            }
 
-                uint n = 0;
-                for (uint i = 0; i < length; i++)
-                {
-                    JsValue jsValue;
-                    operations.TryGetValue(i, out var value);
-                    if (!ReferenceEquals(callable, null))
-                    {
-                        args[0] = value;
-                        args[1] = i;
-                        jsValue = callable.Call(thisArg, args);
-
-                        // function can alter data
-                        length = operations.GetLength();
-                    }
-                    else
-                    {
-                        jsValue = value;
-                    }
-                    a.SetIndexValue(i, jsValue, updateLength: false);
-                    n++;
-                }
+            return instance;
+        }
+
+        private ArrayInstance ConstructArrayFromArrayLike(
+            ObjectInstance objectInstance, 
+            ICallable callable, 
+            JsValue thisArg)
+        {
+            var operations = ArrayPrototype.ArrayOperations.For(objectInstance);
 
+            var length = operations.GetLength();
+
+            var a = _engine.Array.ConstructFast(length);
+            var args = !ReferenceEquals(callable, null)
+                ? _engine._jsValueArrayPool.RentArray(2)
+                : null;
+
+            uint n = 0;
+            for (uint i = 0; i < length; i++)
+            {
+                JsValue jsValue;
+                operations.TryGetValue(i, out var value);
                 if (!ReferenceEquals(callable, null))
                 {
-                    _engine._jsValueArrayPool.ReturnArray(args);
+                    args[0] = value;
+                    args[1] = i;
+                    jsValue = callable.Call(thisArg, args);
+
+                    // function can alter data
+                    length = operations.GetLength();
+                }
+                else
+                {
+                    jsValue = value;
                 }
 
-                a.SetLength(length);
-                return a;
+                a.SetIndexValue(i, jsValue, updateLength: false);
+                n++;
             }
 
-            var instance = _engine.Array.ConstructFast(0);
-            if (objectInstance.TryGetIterator(_engine, out var iterator))
+            if (!ReferenceEquals(callable, null))
             {
-                var protocol = new ArrayProtocol(_engine, thisArg, instance, iterator, callable);
-                protocol.Execute();
+                _engine._jsValueArrayPool.ReturnArray(args);
             }
 
-            return instance;
+            a.SetLength(length);
+            return a;
         }
 
-        private sealed class ArrayProtocol : IteratorProtocol
+        internal sealed class ArrayProtocol : IteratorProtocol
         {
             private readonly JsValue _thisArg;
             private readonly ArrayInstance _instance;
@@ -244,22 +258,18 @@ namespace Jint.Native.Array
 
                 instance._length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable);
             }
-            else if (arguments.Length == 1 && arguments[0] is ObjectWrapper objectWrapper)
+            else if (arguments.Length == 1 && arguments[0] is IObjectWrapper objectWrapper)
             {
                 if (objectWrapper.Target is IEnumerable enumerable)
                 {
-                    var jsArray = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
-                    var tempArray = _engine._jsValueArrayPool.RentArray(1);
-                    foreach (var item in enumerable)
-                    {
-                        var jsItem = FromObject(Engine, item);
-                        tempArray[0] = jsItem;
-                        Engine.Array.PrototypeObject.Push(jsArray, tempArray);
-                    }
-                    _engine._jsValueArrayPool.ReturnArray(tempArray);
-                    return jsArray;
+                    return ConstructArrayFromIEnumerable(enumerable);
                 }
             }
+            else if (arguments.Length == 1 && arguments[0] is ArrayInstance arrayInstance)
+            {
+                // direct copy
+                return ConstructArrayFromArrayLike(arrayInstance, null, this);
+            }
             else
             {
                 instance._length = new PropertyDescriptor(0, PropertyFlag.OnlyWritable);
@@ -272,6 +282,21 @@ namespace Jint.Native.Array
             return instance;
         }
 
+        private ArrayInstance ConstructArrayFromIEnumerable(IEnumerable enumerable)
+        {
+            var jsArray = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
+            var tempArray = _engine._jsValueArrayPool.RentArray(1);
+            foreach (var item in enumerable)
+            {
+                var jsItem = FromObject(Engine, item);
+                tempArray[0] = jsItem;
+                Engine.Array.PrototypeObject.Push(jsArray, tempArray);
+            }
+
+            _engine._jsValueArrayPool.ReturnArray(tempArray);
+            return jsArray;
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal ArrayInstance ConstructFast(uint length)
         {

+ 0 - 24
Jint/Native/Array/ArrayExecutionContext.cs

@@ -1,24 +0,0 @@
-using System.Text;
-using System.Threading;
-
-namespace Jint.Native.Array
-{
-    /// <summary>
-    /// Helper to cache common data structures needed in array access on a per thread basis.
-    /// </summary>
-    internal class ArrayExecutionContext
-    {
-        // cache key container for array iteration for less allocations
-        private static readonly ThreadLocal<ArrayExecutionContext> _executionContext = new ThreadLocal<ArrayExecutionContext>(() => new ArrayExecutionContext());
-
-        private StringBuilder _stringBuilder;
-
-        private ArrayExecutionContext()
-        {
-        }
-
-        public StringBuilder StringBuilder => _stringBuilder = _stringBuilder ?? new StringBuilder();
-
-        public static ArrayExecutionContext Current => _executionContext.Value;
-    }
-}

+ 69 - 5
Jint/Native/Array/ArrayInstance.cs

@@ -4,9 +4,6 @@ using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 
-using PropertyDescriptor = Jint.Runtime.Descriptors.PropertyDescriptor;
-using TypeConverter = Jint.Runtime.TypeConverter;
-
 namespace Jint.Native.Array
 {
     public class ArrayInstance : ObjectInstance
@@ -34,6 +31,9 @@ namespace Jint.Native.Array
             }
         }
 
+        /// <summary>
+        /// Possibility to construct valid array fast, requires that supplied array does not have holes.
+        /// </summary>
         public ArrayInstance(Engine engine, PropertyDescriptor[] items) : base(engine, objectClass: "Array")
         {
             int length = 0;
@@ -374,6 +374,32 @@ namespace Jint.Native.Array
             return base.GetOwnProperty(propertyName);
         }
 
+        internal PropertyDescriptor GetOwnProperty(uint index)
+        {
+            if (TryGetDescriptor(index, out var result))
+            {
+                return result;
+            }
+
+            return PropertyDescriptor.Undefined;
+        }
+
+        internal JsValue Get(uint index)
+        {
+            var desc = GetProperty(index);
+            return UnwrapJsValue(desc);
+        }
+
+        internal PropertyDescriptor GetProperty(uint index)
+        {
+            var prop = GetOwnProperty(index);
+            if (prop != PropertyDescriptor.Undefined)
+            {
+                return prop;
+            }
+            return Prototype?.GetProperty(TypeConverter.ToString(index)) ?? PropertyDescriptor.Undefined;
+        }
+
         protected internal override void SetOwnProperty(string propertyName, PropertyDescriptor desc)
         {
             if (IsArrayIndex(propertyName, out var index))
@@ -604,8 +630,7 @@ namespace Jint.Native.Array
 
             if (canUseDense)
             {
-                var temp = _dense;
-                if (index >= (uint) temp.Length)
+                if (index >= (uint) _dense.Length)
                 {
                     EnsureCapacity((uint) newSize);
                 }
@@ -809,5 +834,44 @@ namespace Jint.Native.Array
             }
             return array;
         }
+
+        /// <summary>
+        /// Fast path for concatenating sane-sized arrays, we assume size has been calculated.
+        /// </summary>
+        internal void CopyValues(ArrayInstance source, uint sourceStartIndex, uint targetStartIndex, uint length)
+        {
+            if (length == 0)
+            {
+                return;
+            }
+
+            if (_dense != null && source._dense != null
+                               && _dense.Length >= targetStartIndex + length
+                               && ReferenceEquals(_dense[targetStartIndex], null))
+            {
+                uint j = 0;
+                for (uint i = sourceStartIndex; i < sourceStartIndex + length; ++i, j++)
+                {
+                    var sourcePropertyDescriptor = i < source._dense.Length && source._dense[i] != null
+                        ? source._dense[i]
+                        : source.GetProperty(i.ToString());
+
+                    _dense[targetStartIndex + j] = sourcePropertyDescriptor?._value != null
+                        ? new PropertyDescriptor(sourcePropertyDescriptor._value, PropertyFlag.ConfigurableEnumerableWritable)
+                        : null;
+                }
+            }
+            else
+            {
+                // slower version
+                for (uint k = sourceStartIndex; k < length; k++)
+                {
+                    if (source.TryGetValue(k, out var subElement))
+                    {
+                        SetIndexValue(targetStartIndex, subElement, updateLength: false);
+                    }
+                }
+            }
+        }
     }
 }

+ 41 - 36
Jint/Native/Array/ArrayPrototype.cs

@@ -1,10 +1,14 @@
 using System;
 using System.Collections.Generic;
+using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
+using Jint.Runtime.Interpreter.Expressions;
+
 using static System.String;
 
 namespace Jint.Native.Array
@@ -254,7 +258,7 @@ namespace Jint.Native.Array
             {
                 if (o.TryGetValue(i, out var value))
                 {
-                    var same = ExpressionInterpreter.StrictlyEqual(value, searchElement);
+                    var same = JintBinaryExpression.StrictlyEqual(value, searchElement);
                     if (same)
                     {
                         return i;
@@ -558,7 +562,7 @@ namespace Jint.Native.Array
             {
                 if (o.TryGetValue(k, out var elementK))
                 {
-                    var same = ExpressionInterpreter.StrictlyEqual(elementK, searchElement);
+                    var same = JintBinaryExpression.StrictlyEqual(elementK, searchElement);
                     if (same)
                     {
                         return k;
@@ -889,17 +893,24 @@ namespace Jint.Native.Array
                 ExceptionHelper.ThrowRangeError(_engine, "Invalid array length");;
             }
 
-            var a = Engine.Array.Construct((uint) (final - k));
-            uint n = 0;
-            for (; k < final; k++)
+            var length = (uint) System.Math.Max(0, (long) final - (long) k);
+            var a = Engine.Array.Construct(length);
+            if (thisObj is ArrayInstance ai)
+            {
+                a.CopyValues(ai, (uint) k, 0, length);
+            }
+            else
             {
-                if (o.TryGetValue(k, out var kValue))
+                // slower path
+                for (uint n = 0; k < final; k++, n++)
                 {
-                    a.SetIndexValue(n, kValue, updateLength: true);
+                    if (o.TryGetValue(k, out var kValue))
+                    {
+                        a.SetIndexValue(n, kValue, updateLength: false);
+                    }
                 }
-
-                n++;
             }
+            a.DefineOwnProperty("length", new PropertyDescriptor(length, PropertyFlag.None), false);
 
             return a;
         }
@@ -1000,16 +1011,17 @@ namespace Jint.Native.Array
                 return s;
             }
 
-            var sb = ArrayExecutionContext.Current.StringBuilder;
-            sb.Clear();
-            sb.Append(s);
-            for (uint k = 1; k < len; k++)
+            using (var sb = StringBuilderPool.Rent())
             {
-                sb.Append(sep);
-                sb.Append(StringFromJsValue(o.Get(k)));
-            }
+                sb.Builder.Append(s);
+                for (uint k = 1; k < len; k++)
+                {
+                    sb.Builder.Append(sep);
+                    sb.Builder.Append(StringFromJsValue(o.Get(k)));
+                }
 
-            return sb.ToString();
+                return sb.ToString();
+            }
         }
 
         private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments)
@@ -1058,49 +1070,42 @@ namespace Jint.Native.Array
         private JsValue Concat(JsValue thisObj, JsValue[] arguments)
         {
             var o = TypeConverter.ToObject(Engine, thisObj);
-            uint n = 0;
             var items = new List<JsValue>(arguments.Length + 1) {o};
             items.AddRange(arguments);
 
             // try to find best capacity
-            bool hasNonSpreadables = false;
+            bool hasObjectSpreadables = false;
             uint capacity = 0;
             for (var i = 0; i < items.Count; i++)
             {
                 uint increment;
                 var objectInstance = items[i] as ObjectInstance;
-                if (objectInstance == null
-                    || (hasNonSpreadables |= objectInstance.IsConcatSpreadable) == false)
+                if (objectInstance == null)
                 {
                     increment = 1;
                 }
                 else
                 {
+                    var isConcatSpreadable = objectInstance.IsConcatSpreadable;
+                    hasObjectSpreadables |= isConcatSpreadable;
                     var operations = ArrayOperations.For(objectInstance);
-                    increment = operations.GetLength();
+                    increment = isConcatSpreadable ? operations.GetLength() : 1; 
                 }
                 capacity += increment;
             }
 
+            uint n = 0;
             var a = Engine.Array.ConstructFast(capacity);
             for (var i = 0; i < items.Count; i++)
             {
                 var e = items[i];
                 if (e is ArrayInstance eArray
-                    && (!hasNonSpreadables || eArray.IsConcatSpreadable))
+                    && eArray.IsConcatSpreadable)
                 {
-                    var len = eArray.GetLength();
-                    for (uint k = 0; k < len; k++)
-                    {
-                        if (eArray.TryGetValue(k, out var subElement))
-                        {
-                            a.SetIndexValue(n, subElement, updateLength: false);
-                        }
-
-                        n++;
-                    }
+                    a.CopyValues(eArray, 0, n, eArray.GetLength());
+                    n += eArray.GetLength();
                 }
-                else if (hasNonSpreadables
+                else if (hasObjectSpreadables
                          && e is ObjectInstance oi 
                          && oi.IsConcatSpreadable)
                 {
@@ -1245,7 +1250,7 @@ namespace Jint.Native.Array
         internal abstract class ArrayOperations
         {
             protected internal const ulong MaxArrayLength = 4294967295;
-            protected internal const ulong MaxArrayLikeLength = 9007199254740991;
+            protected internal const ulong MaxArrayLikeLength = NumberConstructor.MaxSafeInteger;
 
             public abstract ObjectInstance Target { get; }
 
@@ -1455,7 +1460,7 @@ namespace Jint.Native.Array
                     return _array.TryGetValue((uint) index, out value);
                 }
 
-                public override JsValue Get(ulong index) => _array.Get(TypeConverter.ToString(index));
+                public override JsValue Get(ulong index) => _array.Get((uint) index);
 
                 public override void DeleteAt(ulong index) => _array.DeleteAt((uint) index);
 

+ 1 - 1
Jint/Native/Date/DateConstructor.cs

@@ -92,7 +92,7 @@ namespace Jint.Native.Date
                         if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
                         {
                             // unrecognized dates should return NaN (15.9.4.2)
-                            return double.NaN;
+                            return JsNumber.DoubleNaN;
                         }
                     }
                 }

+ 21 - 21
Jint/Native/Date/DatePrototype.cs

@@ -130,7 +130,7 @@ namespace Jint.Native.Date
         {
             if (double.IsNaN(EnsureDateInstance(thisObj).PrimitiveValue))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return EnsureDateInstance(thisObj).PrimitiveValue;
@@ -141,7 +141,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return YearFromTime(LocalTime(t));
@@ -152,7 +152,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return YearFromTime(LocalTime(t)) - 1900;
@@ -163,7 +163,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return YearFromTime(t);
@@ -174,7 +174,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return MonthFromTime(LocalTime(t));
@@ -185,7 +185,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return MonthFromTime(t);
@@ -196,7 +196,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return DateFromTime(LocalTime(t));
@@ -207,7 +207,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return DateFromTime(t);
@@ -218,7 +218,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return WeekDay(LocalTime(t));
@@ -229,7 +229,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return WeekDay(t);
@@ -240,7 +240,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return HourFromTime(LocalTime(t));
@@ -251,7 +251,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return HourFromTime(t);
@@ -262,7 +262,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return MinFromTime(LocalTime(t));
@@ -273,7 +273,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return MinFromTime(t);
@@ -284,7 +284,7 @@ namespace Jint.Native.Date
             var t = thisObj.TryCast<DateInstance>().PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return SecFromTime(LocalTime(t));
@@ -295,7 +295,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return SecFromTime(t);
@@ -306,7 +306,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return MsFromTime(LocalTime(t));
@@ -317,7 +317,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return MsFromTime(t);
@@ -328,7 +328,7 @@ namespace Jint.Native.Date
             var t = EnsureDateInstance(thisObj).PrimitiveValue;
             if (double.IsNaN(t))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return (t - LocalTime(t))/MsPerMinute;
@@ -492,7 +492,7 @@ namespace Jint.Native.Date
             if (double.IsNaN(y))
             {
                 EnsureDateInstance(thisObj).PrimitiveValue = double.NaN;
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             var fy = TypeConverter.ToInteger(y);
@@ -654,7 +654,7 @@ namespace Jint.Native.Date
         {
             if (!AreFinite(t))
             {
-                return Double.NaN;
+                return double.NaN;
             }
 
             var sign = (t < 0) ? -1 : 1;

+ 122 - 0
Jint/Native/Function/ArrowFunctionInstance.cs

@@ -0,0 +1,122 @@
+using System.Runtime.CompilerServices;
+using Esprima.Ast;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Environments;
+using Jint.Runtime.Interpreter;
+
+namespace Jint.Native.Function
+{
+    public sealed class ArrowFunctionInstance : FunctionInstance
+    {
+        private readonly JintFunctionDefinition _function;
+        private readonly JsValue _thisBinding;
+
+        /// <summary>
+        /// http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions
+        /// </summary>
+        public ArrowFunctionInstance(
+            Engine engine,
+            IFunction functionDeclaration,
+            LexicalEnvironment scope,
+            bool strict)
+            : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict)
+        {
+        }
+
+        internal ArrowFunctionInstance(
+            Engine engine,
+            JintFunctionDefinition function,
+            LexicalEnvironment scope,
+            bool strict)
+            : base(engine, "", function._parameterNames, scope, strict)
+        {
+            _function = function;
+
+            Extensible = false;
+            Prototype = Engine.Function.PrototypeObject;
+
+            _length = new PropertyDescriptor(JsNumber.Create(function._length), PropertyFlag.Configurable);
+            _thisBinding = _engine.ExecutionContext.ThisBinding;
+        }
+
+        // for example RavenDB wants to inspect this
+        public IFunction FunctionDeclaration => _function._function;
+
+        /// <summary>
+        /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1
+        /// </summary>
+        /// <param name="thisArg"></param>
+        /// <param name="arguments"></param>
+        /// <returns></returns>
+        public override JsValue Call(JsValue thisArg, JsValue[] arguments)
+        {
+            var localEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _scope);
+
+            var strict = Strict || _engine._isStrict;
+            using (new StrictModeScope(strict, true))
+            {
+                _engine.EnterExecutionContext(
+                    localEnv,
+                    localEnv,
+                    _thisBinding);
+
+                try
+                {
+                    var argumentInstanceRented = _engine.DeclarationBindingInstantiation(
+                        DeclarationBindingType.FunctionCode,
+                        _function._hoistingScope,
+                        functionInstance: this,
+                        arguments);
+
+                    var result = _function._body.Execute();
+
+                    var value = result.GetValueOrDefault();
+
+                    if (argumentInstanceRented)
+                    {
+                        _engine.ExecutionContext.LexicalEnvironment?._record?.FunctionWasCalled();
+                        _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled();
+                    }
+
+                    if (result.Type == CompletionType.Throw)
+                    {
+                        ExceptionHelper.ThrowJavaScriptException(_engine, value, result);
+                    }
+
+                    if (result.Type == CompletionType.Return)
+                    {
+                        return value;
+                    }
+                }
+                finally
+                {
+                    _engine.LeaveExecutionContext();
+                }
+
+                return Undefined;
+            }
+        }
+
+        public override void Put(string propertyName, JsValue value, bool throwOnError)
+        {
+            AssertValidPropertyName(propertyName);
+            base.Put(propertyName, value, throwOnError);
+        }
+
+        public override JsValue Get(string propertyName)
+        {
+            AssertValidPropertyName(propertyName);
+            return base.Get(propertyName);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void AssertValidPropertyName(string propertyName)
+        {
+            if (propertyName == "caller" || propertyName ==  "callee" || propertyName == "arguments")
+            {
+                ExceptionHelper.ThrowTypeError(_engine, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them");
+            }
+        }
+    }
+}

+ 6 - 5
Jint/Native/Function/EvalFunctionInstance.cs

@@ -2,6 +2,7 @@
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
+using Jint.Runtime.Interpreter.Statements;
 
 namespace Jint.Native.Function
 {
@@ -57,12 +58,12 @@ namespace Jint.Native.Function
 
                             bool argumentInstanceRented = Engine.DeclarationBindingInstantiation(
                                 DeclarationBindingType.EvalCode,
-                                program.HoistingScope.FunctionDeclarations,
-                                program.HoistingScope.VariableDeclarations,
-                                this, 
+                                program.HoistingScope,
+                                functionInstance: this,
                                 arguments);
 
-                            var result = _engine.ExecuteStatement(program);
+                            var statement = JintStatement.Build(_engine, program);
+                            var result = statement.Execute();
                             var value = result.GetValueOrDefault();
 
                             if (argumentInstanceRented)
@@ -100,7 +101,7 @@ namespace Jint.Native.Function
             {
                 if (e.Description == Messages.InvalidLHSInAssignment)
                 {
-                    ExceptionHelper.ThrowReferenceError(_engine);
+                    ExceptionHelper.ThrowReferenceError(_engine, (string) null);
                 }
 
                 ExceptionHelper.ThrowSyntaxError(_engine);

+ 8 - 4
Jint/Native/Function/FunctionConstructor.cs

@@ -84,8 +84,10 @@ namespace Jint.Native.Function
                 Engine,
                 function,
                 LexicalEnvironment.NewDeclarativeEnvironment(Engine, Engine.ExecutionContext.LexicalEnvironment),
-                function.Strict
-                ) { Extensible = true };
+                function.Strict)
+            {
+                Extensible = true
+            };
 
             return functionObject;
 
@@ -102,8 +104,10 @@ namespace Jint.Native.Function
                 Engine,
                 functionDeclaration,
                 LexicalEnvironment.NewDeclarativeEnvironment(Engine, Engine.ExecutionContext.LexicalEnvironment),
-                functionDeclaration.Strict
-                ) { Extensible = true };
+                functionDeclaration.Strict)
+            {
+                Extensible = true
+            };
 
             return functionObject;
         }

+ 19 - 4
Jint/Native/Function/FunctionInstance.cs

@@ -10,7 +10,7 @@ namespace Jint.Native.Function
     {
         private const string PropertyNamePrototype = "prototype";
         private const int PropertyNamePrototypeLength = 9;
-        protected PropertyDescriptor _prototype;
+        protected internal PropertyDescriptor _prototype;
 
         private const string PropertyNameLength = "length";
         private const int PropertyNameLengthLength = 6;
@@ -22,7 +22,7 @@ namespace Jint.Native.Function
 
         protected readonly LexicalEnvironment _scope;
         protected internal readonly string[] _formalParameters;
-        private readonly bool _strict;
+        protected readonly bool _strict;
 
         protected FunctionInstance(
             Engine engine,
@@ -40,10 +40,13 @@ namespace Jint.Native.Function
             string[] parameters,
             LexicalEnvironment scope,
             bool strict,
-            in string objectClass)
+            string objectClass)
             : base(engine, objectClass)
         {
-            _name = new PropertyDescriptor(name, PropertyFlag.Configurable);
+            if (!string.IsNullOrWhiteSpace(name))
+            {
+                _name = new PropertyDescriptor(name, PropertyFlag.Configurable);
+            }
             _formalParameters = parameters;
             _scope = scope;
             _strict = strict;
@@ -213,5 +216,17 @@ namespace Jint.Native.Function
 
             base.RemoveOwnProperty(propertyName);
         }
+
+        internal void SetFunctionName(string name, bool throwIfExists = false)
+        {
+            if (!HasOwnProperty("name"))
+            {
+                _name = new PropertyDescriptor(name, PropertyFlag.Configurable);
+            }
+            else if (throwIfExists)
+            {
+                ExceptionHelper.ThrowError(_engine, "cannot set name");
+            }
+        }
     }
 }

+ 39 - 45
Jint/Native/Function/FunctionPrototype.cs

@@ -1,4 +1,6 @@
-using Jint.Native.Object;
+using System;
+using Jint.Native.Array;
+using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors.Specialized;
@@ -18,14 +20,14 @@ namespace Jint.Native.Function
 
         public static FunctionPrototype CreatePrototypeObject(Engine engine)
         {
-            var obj = new FunctionPrototype(engine);
-            obj.Extensible = true;
-
-            // The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object
-            obj.Prototype = engine.Object.PrototypeObject;
+            var obj = new FunctionPrototype(engine)
+            {
+                Extensible = true,
+                // The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object
+                Prototype = engine.Object.PrototypeObject
+            };
 
             obj.SetOwnProperty("length", new PropertyDescriptor(0, PropertyFlag.AllForbidden));
-
             return obj;
         }
 
@@ -46,16 +48,18 @@ namespace Jint.Native.Function
             });
 
             var thisArg = arguments.At(0);
-            var f = new BindFunctionInstance(Engine) {Extensible = true};
-            f.TargetFunction = thisObj;
-            f.BoundThis = thisArg;
-            f.BoundArgs = arguments.Skip(1);
-            f.Prototype = Engine.Function.PrototypeObject;
-
-            var o = target as FunctionInstance;
-            if (!ReferenceEquals(o, null))
+            var f = new BindFunctionInstance(Engine)
+            {
+                Extensible = true,
+                TargetFunction = thisObj,
+                BoundThis = thisArg,
+                BoundArgs = arguments.Skip(1),
+                Prototype = Engine.Function.PrototypeObject
+            };
+
+            if (target is FunctionInstance functionInstance)
             {
-                var l = TypeConverter.ToNumber(o.Get("length")) - (arguments.Length - 1);
+                var l = TypeConverter.ToNumber(functionInstance.Get("length")) - (arguments.Length - 1);
                 f.SetOwnProperty("length", new PropertyDescriptor(System.Math.Max(l, 0), PropertyFlag.AllForbidden));
             }
             else
@@ -63,7 +67,6 @@ namespace Jint.Native.Function
                 f.SetOwnProperty("length", new PropertyDescriptor(0, PropertyFlag.AllForbidden));
             }
 
-
             var thrower = Engine.Function.ThrowTypeError;
             const PropertyFlag flags = PropertyFlag.EnumerableSet | PropertyFlag.ConfigurableSet;
             f.DefineOwnProperty("caller", new GetSetPropertyDescriptor(thrower, thrower, flags), false);
@@ -74,47 +77,34 @@ namespace Jint.Native.Function
 
         private JsValue ToString(JsValue thisObj, JsValue[] arguments)
         {
-            var func = thisObj.TryCast<FunctionInstance>();
-
-            if (ReferenceEquals(func, null))
+            if (!(thisObj is FunctionInstance))
             {
-                ExceptionHelper.ThrowTypeError(_engine, "Function object expected.");
+                return ExceptionHelper.ThrowTypeError<FunctionInstance>(_engine, "Function object expected.");
             }
 
             return "function() {{ ... }}";
         }
 
-        public JsValue Apply(JsValue thisObject, JsValue[] arguments)
+        private JsValue Apply(JsValue thisObject, JsValue[] arguments)
         {
-            var func = thisObject.TryCast<ICallable>();
+            var func = thisObject as ICallable ?? ExceptionHelper.ThrowTypeError<ICallable>(Engine);
             var thisArg = arguments.At(0);
             var argArray = arguments.At(1);
 
-            if (func is null)
-            {
-                return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
-            }
-
             if (argArray.IsNullOrUndefined())
             {
                 return func.Call(thisArg, Arguments.Empty);
             }
 
-            var argArrayObj = argArray.TryCast<ObjectInstance>();
-            if (argArrayObj is null)
-            {
-                return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
-            }
-
-            var len = ((JsNumber) argArrayObj.Get("length"))._value;
-            uint n = TypeConverter.ToUint32(len);
+            var argArrayObj = argArray as ObjectInstance ?? ExceptionHelper.ThrowTypeError<ObjectInstance>(Engine);
+            var operations = ArrayPrototype.ArrayOperations.For(argArrayObj);
 
+            uint n = operations.GetLength();
             var argList = _engine._jsValueArrayPool.RentArray((int) n);
-            for (int index = 0; index < n; index++)
+            for (uint i = 0; i < n; i++)
             {
-                string indexName = TypeConverter.ToString(index);
-                var nextArg = argArrayObj.Get(indexName);
-                argList[index] = nextArg;
+                var nextArg = operations.Get(i);
+                argList[i] = nextArg;
             }
 
             var result = func.Call(thisArg, argList);
@@ -123,15 +113,19 @@ namespace Jint.Native.Function
             return result;
         }
 
-        public JsValue CallImpl(JsValue thisObject, JsValue[] arguments)
+        private JsValue CallImpl(JsValue thisObject, JsValue[] arguments)
         {
-            var func = thisObject.TryCast<ICallable>();
-            if (func is null)
+            var func = thisObject as ICallable ?? ExceptionHelper.ThrowTypeError<ICallable>(Engine);
+            JsValue[] values = ArrayExt.Empty<JsValue>();
+            if (arguments.Length > 1)
             {
-                return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
+                values = new JsValue[arguments.Length - 1];
+                System.Array.Copy(arguments, 1, values, 0, arguments.Length - 1);
             }
 
-            return func.Call(arguments.At(0), arguments.Length == 0 ? arguments : arguments.Skip(1));
+            var result = func.Call(arguments.At(0), values);
+
+            return result;
         }
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)

+ 27 - 46
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -5,36 +5,39 @@ using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Environments;
+using Jint.Runtime.Interpreter;
 
 namespace Jint.Native.Function
 {
-    /// <summary>
-    ///
-    /// </summary>
     public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor
     {
-        private readonly IFunction _functionDeclaration;
+        internal readonly JintFunctionDefinition _function;
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
         /// </summary>
-        /// <param name="engine"></param>
-        /// <param name="functionDeclaration"></param>
-        /// <param name="scope"></param>
-        /// <param name="strict"></param>
         public ScriptFunctionInstance(
-            Engine engine, 
-            IFunction functionDeclaration, 
-            LexicalEnvironment scope, 
+            Engine engine,
+            IFunction functionDeclaration,
+            LexicalEnvironment scope,
             bool strict)
-            : base(engine, functionDeclaration.Id?.Name ?? "", GetParameterNames(functionDeclaration), scope, strict)
+            : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict)
         {
-            _functionDeclaration = functionDeclaration;
+        }
+
+        internal ScriptFunctionInstance(
+            Engine engine,
+            JintFunctionDefinition function,
+            LexicalEnvironment scope,
+            bool strict)
+            : base(engine, function._name ?? "", function._parameterNames, scope, strict)
+        {
+            _function = function;
 
             Extensible = true;
             Prototype = _engine.Function.PrototypeObject;
 
-            _length = new PropertyDescriptor(JsNumber.Create(_formalParameters.Length), PropertyFlag.AllForbidden);
+            _length = new PropertyDescriptor(JsNumber.Create(function._length), PropertyFlag.Configurable);
 
             var proto = new ObjectInstanceWithConstructor(engine, this)
             {
@@ -44,11 +47,6 @@ namespace Jint.Native.Function
 
             _prototype = new PropertyDescriptor(proto, PropertyFlag.OnlyWritable);
 
-            if (_functionDeclaration.Id != null)
-            {
-                DefineOwnProperty("name", new PropertyDescriptor(_functionDeclaration.Id.Name, PropertyFlag.None), false);
-            }
-
             if (strict)
             {
                 var thrower = engine.Function.ThrowTypeError;
@@ -58,24 +56,8 @@ namespace Jint.Native.Function
             }
         }
 
-        private static string[] GetParameterNames(IFunction functionDeclaration)
-        {
-            var list = functionDeclaration.Params;
-            var count = list.Count;
-
-            if (count == 0)
-            {
-                return System.ArrayExt.Empty<string>();
-            }
-
-            var names = new string[count];
-            for (var i = 0; i < count; ++i)
-            {
-                names[i] = ((Identifier) list[i]).Name;
-            }
-
-            return names;
-        }
+        // for example RavenDB wants to inspect this
+        public IFunction FunctionDeclaration => _function._function;
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1
@@ -85,7 +67,7 @@ namespace Jint.Native.Function
         /// <returns></returns>
         public override JsValue Call(JsValue thisArg, JsValue[] arguments)
         {
-            var strict = Strict || _engine._isStrict;
+            var strict = _strict || _engine._isStrict;
             using (new StrictModeScope(strict, true))
             {
                 // setup new execution context http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.3
@@ -115,15 +97,14 @@ namespace Jint.Native.Function
                 {
                     var argumentInstanceRented = _engine.DeclarationBindingInstantiation(
                         DeclarationBindingType.FunctionCode,
-                        _functionDeclaration.HoistingScope.FunctionDeclarations,
-                        _functionDeclaration.HoistingScope.VariableDeclarations,
-                        this,
+                        _function._hoistingScope,
+                        functionInstance: this,
                         arguments);
 
-                    var result = _engine.ExecuteStatement(_functionDeclaration.Body);
-                    
+                    var result = _function._body.Execute();
+
                     var value = result.GetValueOrDefault();
-                    
+
                     if (argumentInstanceRented)
                     {
                         _engine.ExecutionContext.LexicalEnvironment?._record?.FunctionWasCalled();
@@ -132,8 +113,7 @@ namespace Jint.Native.Function
 
                     if (result.Type == CompletionType.Throw)
                     {
-                        var ex = new JavaScriptException(value).SetCallstack(_engine, result.Location);
-                        throw ex;
+                        ExceptionHelper.ThrowJavaScriptException(_engine, value, result);
                     }
 
                     if (result.Type == CompletionType.Return)
@@ -158,6 +138,7 @@ namespace Jint.Native.Function
         public ObjectInstance Construct(JsValue[] arguments)
         {
             var proto = Get("prototype").TryCast<ObjectInstance>();
+
             var obj = new ObjectInstance(_engine)
             {
                 Extensible = true,

+ 6 - 6
Jint/Native/Global/GlobalObject.cs

@@ -74,7 +74,7 @@ namespace Jint.Native.Global
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.2
         /// </summary>
-        public static JsValue ParseInt(JsValue thisObject, JsValue[] arguments)
+        public JsValue ParseInt(JsValue thisObject, JsValue[] arguments)
         {
             string inputString = TypeConverter.ToString(arguments.At(0));
             var s = StringPrototype.TrimEx(inputString);
@@ -110,7 +110,7 @@ namespace Jint.Native.Global
             }
             else if (radix < 2 || radix > 36)
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (radix != 16)
             {
@@ -128,7 +128,7 @@ namespace Jint.Native.Global
             }
             catch
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
         }
@@ -175,7 +175,7 @@ namespace Jint.Native.Global
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.3
         /// </summary>
-        public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments)
+        public JsValue ParseFloat(JsValue thisObject, JsValue[] arguments)
         {
             var inputString = TypeConverter.ToString(arguments.At(0));
             var trimmedString = StringPrototype.TrimStartEx(inputString);
@@ -201,7 +201,7 @@ namespace Jint.Native.Global
 
             if (trimmedString.StartsWith("NaN"))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             var separator = (char)0;
@@ -305,7 +305,7 @@ namespace Jint.Native.Global
 
             if (isNan)
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             for (var k = 1; k <= exp; k++)

+ 9 - 2
Jint/Native/Iterator/IteratorProtocol.cs

@@ -40,11 +40,11 @@ namespace Jint.Native.Iterator
                     }
 
                     ProcessItem(args, currentValue);
-                } while (true);
+                } while (ShouldContinue);
             }
             catch
             {
-                _iterator.Return();
+                ReturnIterator();
                 throw;
             }
             finally
@@ -55,6 +55,13 @@ namespace Jint.Native.Iterator
             IterationEnd();
         }
 
+        protected void ReturnIterator()
+        {
+            _iterator.Return();
+        }
+
+        protected virtual bool ShouldContinue => true;
+
         protected virtual void IterationEnd()
         {
         }

+ 24 - 5
Jint/Native/JsNumber.cs

@@ -1,15 +1,19 @@
 using System;
 using System.Globalization;
+using System.Runtime.CompilerServices;
 using Jint.Runtime;
 
 namespace Jint.Native
 {
     public sealed class JsNumber : JsValue, IEquatable<JsNumber>
     {
+        // .NET double epsilon and JS epsilon have different values
+        internal const double JavaScriptEpsilon = 2.2204460492503130808472633361816E-16;
+
         internal readonly double _value;
 
         // how many decimals to check when determining if double is actually an int
-        private const double DoubleIsIntegerTolerance = double.Epsilon * 100;
+        internal const double DoubleIsIntegerTolerance = double.Epsilon * 100;
 
         private static readonly long NegativeZeroBits = BitConverter.DoubleToInt64Bits(-0.0);
 
@@ -19,11 +23,15 @@ namespace Jint.Native
         private static readonly JsNumber[] _doubleToJsValue = new JsNumber[NumbersMax];
         private static readonly JsNumber[] _intToJsValue = new JsNumber[NumbersMax];
 
-        private static readonly JsNumber DoubleNaN = new JsNumber(double.NaN);
-        private static readonly JsNumber DoubleNegativeOne = new JsNumber((double) -1);
-        private static readonly JsNumber DoublePositiveInfinity = new JsNumber(double.PositiveInfinity);
-        private static readonly JsNumber DoubleNegativeInfinity = new JsNumber(double.NegativeInfinity);
+        internal static readonly JsNumber DoubleNaN = new JsNumber(double.NaN);
+        internal static readonly JsNumber DoubleNegativeOne = new JsNumber((double) -1);
+        internal static readonly JsNumber DoublePositiveInfinity = new JsNumber(double.PositiveInfinity);
+        internal static readonly JsNumber DoubleNegativeInfinity = new JsNumber(double.NegativeInfinity);
         private static readonly JsNumber IntegerNegativeOne = new JsNumber(-1);
+        internal static readonly JsNumber NegativeZero = new JsNumber(-0d);
+        internal static readonly JsNumber PositiveZero = new JsNumber(+0);
+
+        internal static readonly JsNumber PI = new JsNumber(System.Math.PI);
 
         static JsNumber()
         {
@@ -69,6 +77,17 @@ namespace Jint.Native
                 return DoubleNegativeOne;
             }
 
+            if (value <= double.MaxValue && value >= double.MinValue)
+            {
+                return new JsNumber(value);
+            }
+
+            return CreateNumberUnlikely(value);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static JsNumber CreateNumberUnlikely(double value)
+        {
             if (value == double.NegativeInfinity)
             {
                 return DoubleNegativeInfinity;

+ 12 - 1
Jint/Native/JsString.cs

@@ -13,6 +13,12 @@ namespace Jint.Native
 
         public static readonly JsString Empty = new JsString("");
         private static readonly JsString NullString = new JsString("null");
+        internal static readonly JsString UndefinedString = new JsString("undefined");
+        internal static readonly JsString ObjectString = new JsString("object");
+        internal static readonly JsString FunctionString = new JsString("function");
+        internal static readonly JsString BooleanString = new JsString("boolean");
+        internal static readonly JsString StringString = new JsString("string");
+        internal static readonly JsString NumberString = new JsString("number");
 
         internal string _value;
 
@@ -203,7 +209,12 @@ namespace Jint.Native
             {
                 if (other is ConcatenatedString cs)
                 {
-                    return _stringBuilder.Equals(cs._stringBuilder);
+                    if (_stringBuilder != null && cs._stringBuilder != null)
+                    {
+                        return _stringBuilder.Equals(cs._stringBuilder);
+                    }
+
+                    return ToString() == cs.ToString();
                 }
 
                 if (other is JsString jsString)

+ 1 - 1
Jint/Native/JsSymbol.cs

@@ -18,7 +18,7 @@ namespace Jint.Native
 
         internal JsSymbol(JsValue value) : base(Types.Symbol)
         {
-            _value = value.IsUndefined() ? "" : TypeConverter.ToString(value);
+            _value = value.IsUndefined() ? "" : value.ToString();
         }
 
         public override object ToObject()

+ 33 - 15
Jint/Native/JsValue.cs

@@ -168,15 +168,18 @@ namespace Jint.Native
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal bool TryGetIterator(Engine engine, out IIterator iterator)
         {
-            if (!(this is ObjectInstance oi)
-                || !oi.TryGetValue(GlobalSymbolRegistry.Iterator._value, out var value)
+            var objectInstance = TypeConverter.ToObject(engine, this);
+
+            if (!objectInstance.TryGetValue(GlobalSymbolRegistry.Iterator._value, out var value)
                 || !(value is ICallable callable))
             {
                 iterator = null;
                 return false;
             }
 
-            var obj = (ObjectInstance) callable.Call(this, Arguments.Empty);
+            var obj = callable.Call(this, Arguments.Empty) as ObjectInstance
+                      ?? ExceptionHelper.ThrowTypeError<ObjectInstance>(engine, "Result of the Symbol.iterator method is not an object");
+
             if (obj is IIterator i)
             {
                 iterator = i;
@@ -219,22 +222,26 @@ namespace Jint.Native
             }
 
             // TODO not implemented
-            return new Completion(CompletionType.Normal, Native.Undefined.Instance, null);
+            return new Completion(CompletionType.Normal, Native.Undefined.Instance, null, default);
+        }
+
+        [Pure]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public T TryCast<T>() where T : class
+        {
+            return this as T;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public T TryCast<T>(Action<JsValue> fail = null) where T : class
+        public T TryCast<T>(Action<JsValue> fail) where T : class
         {
-            if (_type == Types.Object)
+            if (this is T o)
             {
-                if (this is T o)
-                {
-                    return o;
-                }
+                return o;
             }
 
-            fail?.Invoke(this);
+            fail.Invoke(this);
 
             return null;
         }
@@ -287,7 +294,7 @@ namespace Jint.Native
             for (var i = 0; i < convertersCount; i++)
             {
                 var converter = converters[i];
-                if (converter.TryConvert(value, out var result))
+                if (converter.TryConvert(engine, value, out var result))
                 {
                     return result;
                 }
@@ -325,13 +332,24 @@ namespace Jint.Native
                 return new DelegateWrapper(engine, d);
             }
 
-            if (value.GetType().IsEnum)
+            Type t = value.GetType();
+            if (t.IsEnum)
             {
-                return JsNumber.Create((int) value);
+                Type ut = Enum.GetUnderlyingType(t);
+
+                if (ut == typeof(ulong))
+                    return JsNumber.Create(System.Convert.ToDouble(value));
+
+                if (ut == typeof(uint) || ut == typeof(long))
+                    return JsNumber.Create(System.Convert.ToInt64(value));
+
+                return JsNumber.Create(System.Convert.ToInt32(value));
             }
 
             // if no known type could be guessed, wrap it as an ObjectInstance
-            return new ObjectWrapper(engine, value);
+            var h = engine.Options._WrapObjectHandler;
+            ObjectInstance o = h != null ? h(value) : null;
+            return o ?? new ObjectWrapper(engine, value);
         }
 
         private static JsValue Convert(Engine e, object v)

+ 30 - 52
Jint/Native/Json/JsonParser.cs

@@ -484,21 +484,17 @@ namespace Jint.Native.Json
 
         private Token CollectToken()
         {
-            _location = new Location
-                {
-                    Start = new Position
-                        {
-                            Line = _lineNumber,
-                            Column = _index - _lineStart
-                        }
-                };
+            var start = new Position(
+                line: _lineNumber,
+                column: _index - _lineStart);
 
             Token token = Advance();
-            _location.End = new Position
-                {
-                    Line = _lineNumber,
-                    Column = _index - _lineStart
-                };
+
+            var end = new Position(
+                line: _lineNumber,
+                column: _index - _lineStart);
+
+            _location = new Location(start, end, _source);
 
             if (token.Type != Tokens.EOF)
             {
@@ -559,23 +555,18 @@ namespace Jint.Native.Json
         {
             if (_extra.Range != null)
             {
-                node.Range = new int[] {_state.MarkerStack.Pop(), _index};
+                node.Range = new Range(_state.MarkerStack.Pop(), _index);
             }
             if (_extra.Loc.HasValue)
             {
-                node.Location = new Location
-                    {
-                        Start = new Position
-                            {
-                                Line = _state.MarkerStack.Pop(),
-                                Column = _state.MarkerStack.Pop()
-                            },
-                        End = new Position
-                            {
-                                Line = _lineNumber,
-                                Column = _index - _lineStart
-                            }
-                    };
+                node.Location = new Location(
+                    start: new Position(
+                        line: _state.MarkerStack.Pop(),
+                        column: _state.MarkerStack.Pop()),
+                    end: new Position(
+                        line: _lineNumber,
+                        column: _index - _lineStart),
+                    source: _source);
                 PostProcess(node);
             }
             return node;
@@ -624,29 +615,16 @@ namespace Jint.Native.Json
 
         private void ThrowError(Token token, string messageFormat, params object[] arguments)
         {
-            ParserException exception;
             string msg = System.String.Format(messageFormat, arguments);
-
-            if (token.LineNumber.HasValue)
-            {
-                exception = new ParserException("Line " + token.LineNumber + ": " + msg)
-                    {
-                        Index = token.Range[0],
-                        LineNumber = token.LineNumber.Value,
-                        Column = token.Range[0] - _lineStart + 1
-                    };
-            }
-            else
-            {
-                exception = new ParserException("Line " + _lineNumber + ": " + msg)
-                    {
-                        Index = _index,
-                        LineNumber = _lineNumber,
-                        Column = _index - _lineStart + 1
-                    };
-            }
-
-            exception.Description = msg;
+            int lineNumber = token.LineNumber ?? _lineNumber;
+
+            var error = new ParseError(
+                    description: msg,
+                    source: _source,
+                    index: token.Range[0],
+                    position: new Position(token.LineNumber ?? _lineNumber, token.Range[0] - _lineStart + 1));
+            var exception = new ParserException("Line " + lineNumber  + ": " + msg, error);
+            
             throw exception;
         }
 
@@ -694,7 +672,7 @@ namespace Jint.Native.Json
 
         private ObjectInstance ParseJsonArray()
         {
-            var elements = new List<JsValue>();
+            var elements = new System.Collections.Generic.List<JsValue>();
 
             Expect("[");
 
@@ -848,7 +826,7 @@ namespace Jint.Native.Json
             {
                 if (options.Tokens)
                 {
-                    _extra.Tokens = new List<Token>();
+                    _extra.Tokens = new System.Collections.Generic.List<Token>();
                 }
 
             }
@@ -879,7 +857,7 @@ namespace Jint.Native.Json
             public int? Loc;
             public int[] Range;
 
-            public List<Token> Tokens;
+            public System.Collections.Generic.List<Token> Tokens;
         }
 
         private enum Tokens

+ 1 - 1
Jint/Native/Json/JsonSerializer.cs

@@ -186,7 +186,7 @@ namespace Jint.Native.Json
                 var isFinite = GlobalObject.IsFinite(Undefined.Instance, Arguments.From(value));
                 if (((JsBoolean) isFinite)._value)
                 {
-                    return TypeConverter.ToString(value);
+                    return TypeConverter.ToString(((JsNumber) value)._value);
                 }
 
                 return "null";

+ 403 - 98
Jint/Native/Math/MathInstance.cs

@@ -9,7 +9,7 @@ namespace Jint.Native.Math
 {
     public sealed class MathInstance : ObjectInstance
     {
-        private static readonly Random _random = new Random();
+        private Random _random;
 
         private MathInstance(Engine engine) : base(engine, "Math")
         {
@@ -26,28 +26,42 @@ namespace Jint.Native.Math
 
         public void Configure()
         {
-            FastAddProperty("abs", new ClrFunctionInstance(Engine, "abs", Abs), true, false, true);
-            FastAddProperty("acos", new ClrFunctionInstance(Engine, "acos", Acos), true, false, true);
-            FastAddProperty("asin", new ClrFunctionInstance(Engine, "asin", Asin), true, false, true);
-            FastAddProperty("atan", new ClrFunctionInstance(Engine, "atan", Atan), true, false, true);
-            FastAddProperty("atan2", new ClrFunctionInstance(Engine, "atan2", Atan2), true, false, true);
-            FastAddProperty("ceil", new ClrFunctionInstance(Engine, "ceil", Ceil), true, false, true);
-            FastAddProperty("cos", new ClrFunctionInstance(Engine, "cos", Cos), true, false, true);
-            FastAddProperty("exp", new ClrFunctionInstance(Engine, "exp", Exp), true, false, true);
-            FastAddProperty("floor", new ClrFunctionInstance(Engine, "floor", Floor), true, false, true);
-            FastAddProperty("log", new ClrFunctionInstance(Engine, "log", Log), true, false, true);
-            FastAddProperty("max", new ClrFunctionInstance(Engine, "max", Max, 2), true, false, true);
-            FastAddProperty("min", new ClrFunctionInstance(Engine, "min", Min, 2), true, false, true);
-            FastAddProperty("pow", new ClrFunctionInstance(Engine, "pow", Pow, 2), true, false, true);
-            FastAddProperty("random", new ClrFunctionInstance(Engine, "random", Random), true, false, true);
-            FastAddProperty("round", new ClrFunctionInstance(Engine, "round", Round), true, false, true);
-            FastAddProperty("sin", new ClrFunctionInstance(Engine, "sin", Sin), true, false, true);
-            FastAddProperty("sqrt", new ClrFunctionInstance(Engine, "sqrt", Sqrt), true, false, true);
-            FastAddProperty("tan", new ClrFunctionInstance(Engine, "tan", Tan), true, false, true);
+            FastAddProperty("abs", new ClrFunctionInstance(Engine, "abs", Abs, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("acos", new ClrFunctionInstance(Engine, "acos", Acos, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("acosh", new ClrFunctionInstance(Engine, "acosh", Acosh, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("asin", new ClrFunctionInstance(Engine, "asin", Asin, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("asinh", new ClrFunctionInstance(Engine, "asinh", Asinh, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("atan", new ClrFunctionInstance(Engine, "atan", Atan, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("atanh", new ClrFunctionInstance(Engine, "atanh", Atanh, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("atan2", new ClrFunctionInstance(Engine, "atan2", Atan2, 2, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("ceil", new ClrFunctionInstance(Engine, "ceil", Ceil, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("cos", new ClrFunctionInstance(Engine, "cos", Cos, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("cosh", new ClrFunctionInstance(Engine, "cosh", Cosh, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("exp", new ClrFunctionInstance(Engine, "exp", Exp, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("expm1", new ClrFunctionInstance(Engine, "expm1", Expm1, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("floor", new ClrFunctionInstance(Engine, "floor", Floor, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("log", new ClrFunctionInstance(Engine, "log", Log, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("log1p", new ClrFunctionInstance(Engine, "log1p", Log1p, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("log2", new ClrFunctionInstance(Engine, "log2", Log2, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("log10", new ClrFunctionInstance(Engine, "log10", Log10, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("max", new ClrFunctionInstance(Engine, "max", Max, 2, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("min", new ClrFunctionInstance(Engine, "min", Min, 2, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("pow", new ClrFunctionInstance(Engine, "pow", Pow, 2, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("random", new ClrFunctionInstance(Engine, "random", Random, 0, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("round", new ClrFunctionInstance(Engine, "round", Round, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("fround", new ClrFunctionInstance(Engine, "fround", Fround, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("sin", new ClrFunctionInstance(Engine, "sin", Sin, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("sinh", new ClrFunctionInstance(Engine, "sinh", Sinh, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("sqrt", new ClrFunctionInstance(Engine, "sqrt", Sqrt, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("tan", new ClrFunctionInstance(Engine, "tan", Tan, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("tanh", new ClrFunctionInstance(Engine, "tanh", Tanh, 1, PropertyFlag.Configurable), true, false, true);
 
             FastAddProperty("trunc", new ClrFunctionInstance(Engine, "trunc", Truncate, 1, PropertyFlag.Configurable), true, false, true);
             FastAddProperty("sign", new ClrFunctionInstance(Engine, "sign", Sign, 1, PropertyFlag.Configurable), true, false, true);
             FastAddProperty("cbrt", new ClrFunctionInstance(Engine, "cbrt", Cbrt, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("hypot", new ClrFunctionInstance(Engine, "hypot", Hypot, 2, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("imul", new ClrFunctionInstance(Engine, "imul", Imul, 2, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("clz32", new ClrFunctionInstance(Engine, "clz32", Clz32, 1, PropertyFlag.Configurable), true, false, true);
 
             FastAddProperty("E", System.Math.E, false, false, false);
             FastAddProperty("LN10", System.Math.Log(10), false, false, false);
@@ -66,15 +80,15 @@ namespace Jint.Native.Math
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsNegativeZero(x))
             {
-                return +0;
+                return JsNumber.PositiveZero;
             }
             else if (double.IsInfinity(x))
             {
-                return double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
 
             return System.Math.Abs(x);
@@ -86,7 +100,7 @@ namespace Jint.Native.Math
 
             if (double.IsNaN(x) || (x > 1) || (x < -1))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (x == 1)
             {
@@ -96,13 +110,25 @@ namespace Jint.Native.Math
             return System.Math.Acos(x);
         }
 
+        private static JsValue Acosh(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x) || x < 1)
+            {
+                return JsNumber.DoubleNaN;
+            }
+
+            return System.Math.Log(x + System.Math.Sqrt(x * x - 1.0));
+        }
+
         private static JsValue Asin(JsValue thisObject, JsValue[] arguments)
         {
             var x = TypeConverter.ToNumber(arguments.At(0));
 
             if (double.IsNaN(x) || (x > 1) || (x < -1))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
             {
@@ -112,13 +138,24 @@ namespace Jint.Native.Math
             return System.Math.Asin(x);
         }
 
+        private static JsValue Asinh(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+            if (double.IsInfinity(x) || NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
+            {
+                return x;
+            }
+
+            return System.Math.Log(x + System.Math.Sqrt(x * x + 1.0));
+        }
+
         private static JsValue Atan(JsValue thisObject, JsValue[] arguments)
         {
             var x = TypeConverter.ToNumber(arguments.At(0));
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
             {
@@ -135,6 +172,22 @@ namespace Jint.Native.Math
 
             return System.Math.Atan(x);
         }
+        private static JsValue Atanh(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x))
+            {
+                return JsNumber.DoubleNaN;
+            }
+
+            if (NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
+            {
+                return x;
+            }
+
+            return 0.5 * System.Math.Log((1.0 + x) / (1.0 - x));
+        }
 
         private static JsValue Atan2(JsValue thisObject, JsValue[] arguments)
         {
@@ -144,7 +197,7 @@ namespace Jint.Native.Math
             // If either x or y is NaN, the result is NaN.
             if (double.IsNaN(x) || double.IsNaN(y))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             if (y > 0 && x.Equals(0))
@@ -157,25 +210,25 @@ namespace Jint.Native.Math
                 // If y is +0 and x>0, the result is +0.
                 if (x > 0)
                 {
-                    return +0;
+                    return JsNumber.PositiveZero;
                 }
 
                 // If y is +0 and x is +0, the result is +0.
                 if (NumberInstance.IsPositiveZero(x))
                 {
-                    return +0;
+                    return JsNumber.PositiveZero;
                 }
 
                 // If y is +0 and x is −0, the result is an implementation-dependent approximation to +π.
                 if (NumberInstance.IsNegativeZero(x))
                 {
-                    return System.Math.PI;
+                    return JsNumber.PI;
                 }
 
                 // If y is +0 and x<0, the result is an implementation-dependent approximation to +π.
                 if (x < 0)
                 {
-                    return System.Math.PI;
+                    return JsNumber.PI;
                 }
             }
 
@@ -184,13 +237,13 @@ namespace Jint.Native.Math
                 // If y is −0 and x>0, the result is −0.
                 if (x > 0)
                 {
-                    return -0;
+                    return JsNumber.NegativeZero;
                 }
 
                 // If y is −0 and x is +0, the result is −0.
                 if (NumberInstance.IsPositiveZero(x))
                 {
-                    return -0;
+                    return JsNumber.NegativeZero;
                 }
 
                 // If y is −0 and x is −0, the result is an implementation-dependent approximation to −π.
@@ -218,13 +271,13 @@ namespace Jint.Native.Math
             {
                 if (double.IsPositiveInfinity(x))
                 {
-                    return +0;
+                    return JsNumber.PositiveZero;
                 }
 
                 // If y>0 and y is finite and x is −∞, the result if an implementation-dependent approximation to +π.
                 if (double.IsNegativeInfinity(x))
                 {
-                    return System.Math.PI;
+                    return JsNumber.PI;
                 }
             }
 
@@ -235,7 +288,7 @@ namespace Jint.Native.Math
             {
                 if (double.IsPositiveInfinity(x))
                 {
-                    return -0;
+                    return JsNumber.NegativeZero;
                 }
 
                 // If y>0 and y is finite and x is −∞, the result if an implementation-dependent approximation to +π.
@@ -290,23 +343,23 @@ namespace Jint.Native.Math
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x))
             {
-                return +0;
+                return JsNumber.PositiveZero;
             }
             else if (NumberInstance.IsNegativeZero(x))
             {
-                return -0;
+                return JsNumber.NegativeZero;
             }
             else if (double.IsPositiveInfinity(x))
             {
-                return double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
             else if (double.IsNegativeInfinity(x))
             {
-                return double.NegativeInfinity;
+                return JsNumber.DoubleNegativeInfinity;
             }
 
             return System.Math.Ceiling(x);
@@ -318,7 +371,7 @@ namespace Jint.Native.Math
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x))
             {
@@ -330,19 +383,43 @@ namespace Jint.Native.Math
             }
             else if (double.IsInfinity(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return System.Math.Cos(x);
         }
 
+        private static JsValue Cosh(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x))
+            {
+                return JsNumber.DoubleNaN;
+            }
+            else if (NumberInstance.IsPositiveZero(x))
+            {
+                return 1;
+            }
+            else if (NumberInstance.IsNegativeZero(x))
+            {
+                return 1;
+            }
+            else if (double.IsInfinity(x))
+            {
+                return JsNumber.DoublePositiveInfinity;
+            }
+
+            return System.Math.Cosh(x);
+        }
+
         private static JsValue Exp(JsValue thisObject, JsValue[] arguments)
         {
             var x = TypeConverter.ToNumber(arguments.At(0));
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
             {
@@ -350,39 +427,55 @@ namespace Jint.Native.Math
             }
             else if (double.IsPositiveInfinity(x))
             {
-                return double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
             else if (double.IsNegativeInfinity(x))
             {
-                return +0;
+                return JsNumber.PositiveZero;
             }
 
             return System.Math.Exp(x);
         }
 
+        private static JsValue Expm1(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x) || NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x) || double.IsPositiveInfinity(x))
+            {
+                return arguments.At(0);
+            }
+            if (double.IsNegativeInfinity(x))
+            {
+                return JsNumber.DoubleNegativeOne;
+            }
+
+            return System.Math.Exp(x) - 1.0;
+        }
+
         private static JsValue Floor(JsValue thisObject, JsValue[] arguments)
         {
             var x = TypeConverter.ToNumber(arguments.At(0));
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x))
             {
-                return +0;
+                return JsNumber.PositiveZero;
             }
             else if (NumberInstance.IsNegativeZero(x))
             {
-                return -0;
+                return JsNumber.NegativeZero;
             }
             else if (double.IsPositiveInfinity(x))
             {
-                return double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
             else if (double.IsNegativeInfinity(x))
             {
-                return double.NegativeInfinity;
+                return JsNumber.DoubleNegativeInfinity;
             }
 
             return System.Math.Floor(x);
@@ -394,40 +487,123 @@ namespace Jint.Native.Math
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             if (x < 0)
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (x == 0)
             {
-                return double.NegativeInfinity;
+                return JsNumber.DoubleNegativeInfinity;
             }
             else if (double.IsPositiveInfinity(x))
             {
-                return double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
             else if (x == 1)
             {
-                return +0;
+                return JsNumber.PositiveZero;
             }
 
             return System.Math.Log(x);
         }
 
+        private static JsValue Log1p(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x))
+            {
+                return JsNumber.DoubleNaN;
+            }
+
+            if (x < -1)
+            {
+                return JsNumber.DoubleNaN;
+            }
+
+            if (x == -1)
+            {
+                return JsNumber.DoubleNegativeInfinity;
+            }
+
+            if (x == 0 || double.IsPositiveInfinity(x))
+            {
+                return arguments.At(0);
+            }
+
+            return System.Math.Log(1 + x);
+        }
+
+        private static JsValue Log2(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x))
+            {
+                return JsNumber.DoubleNaN;
+            }
+            if (x < 0)
+            {
+                return JsNumber.DoubleNaN;
+            }
+            else if (x == 0)
+            {
+                return JsNumber.DoubleNegativeInfinity;
+            }
+            else if (double.IsPositiveInfinity(x))
+            {
+                return JsNumber.DoublePositiveInfinity;
+            }
+            else if (x == 1)
+            {
+                return JsNumber.PositiveZero;
+            }
+
+            return System.Math.Log(x, 2);
+        }
+
+        private static JsValue Log10(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x))
+            {
+                return JsNumber.DoubleNaN;
+            }
+            if (x < 0)
+            {
+                return JsNumber.DoubleNaN;
+            }
+            else if (x == 0)
+            {
+                return JsNumber.DoubleNegativeInfinity;
+            }
+            else if (double.IsPositiveInfinity(x))
+            {
+                return JsNumber.DoublePositiveInfinity;
+            }
+            else if (x == 1)
+            {
+                return JsNumber.PositiveZero;
+            }
+
+            return System.Math.Log10(x);
+        }
+
         private static JsValue Max(JsValue thisObject, JsValue[] arguments)
         {
             if (arguments.Length == 0)
             {
-                return Double.NegativeInfinity;
+                return JsNumber.DoubleNegativeInfinity;
             }
 
             double max = TypeConverter.ToNumber(arguments.At(0));
 
             if (double.IsNaN(max))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             for (int i = 0; i < arguments.Length; i++)
@@ -436,10 +612,19 @@ namespace Jint.Native.Math
 
                 if (double.IsNaN(value))
                 {
-                    return double.NaN;
+                    return JsNumber.DoubleNaN;
                 }
 
-                max = System.Math.Max(max, value);
+                if (max == 0 && value == 0)
+                {
+                    max = NumberInstance.IsNegativeZero(value)
+                        ? max
+                        : value;
+                }
+                else
+                {
+                    max = System.Math.Max(max, value);
+                }
             }
             return max;
         }
@@ -448,13 +633,23 @@ namespace Jint.Native.Math
         {
             if (arguments.Length == 0)
             {
-                return Double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
 
             double min = TypeConverter.ToNumber(arguments.At(0));
             for (int i = 0; i < arguments.Length; i++)
             {
-                min = System.Math.Min(min, TypeConverter.ToNumber(arguments[i]));
+                var value = TypeConverter.ToNumber(arguments[i]);
+                if (min == 0 && value == 0)
+                {
+                    min = NumberInstance.IsNegativeZero(min)
+                        ? min
+                        : value;
+                }
+                else
+                {
+                    min = System.Math.Min(min, value);
+                }
             }
             return min;
         }
@@ -464,43 +659,55 @@ namespace Jint.Native.Math
             var x = TypeConverter.ToNumber(arguments.At(0));
             var y = TypeConverter.ToNumber(arguments.At(1));
 
-            if (double.IsNaN(y))
+            // check easy case where values are valid
+            if (x > 1 && y > 1 && x < int.MaxValue && y < int.MaxValue)
             {
-                return double.NaN;
+                return System.Math.Pow(x, y);
             }
 
-            if (y.Equals(0))
+            if (y == 0)
             {
                 return 1;
             }
+            
+            return HandlePowUnlikely(y, x);
+        }
+
+        private static JsValue HandlePowUnlikely(double y, double x)
+        {
+            if (double.IsNaN(y))
+            {
+                return JsNumber.DoubleNaN;
+            }
 
-            if (double.IsNaN(x) && !y.Equals(0))
+            if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
-            if (System.Math.Abs(x) > 1)
+            var absX = System.Math.Abs(x);
+            if (absX > 1)
             {
                 if (double.IsPositiveInfinity(y))
                 {
-                    return double.PositiveInfinity;
+                    return JsNumber.DoublePositiveInfinity;
                 }
 
                 if (double.IsNegativeInfinity(y))
                 {
-                    return +0;
+                    return JsNumber.PositiveZero;
                 }
             }
 
-            if (System.Math.Abs(x).Equals(1))
+            if (absX == 1)
             {
                 if (double.IsInfinity(y))
                 {
-                    return double.NaN;
+                    return JsNumber.DoubleNaN;
                 }
             }
 
-            if (System.Math.Abs(x) < 1)
+            if (absX < 1)
             {
                 if (double.IsPositiveInfinity(y))
                 {
@@ -509,7 +716,7 @@ namespace Jint.Native.Math
 
                 if (double.IsNegativeInfinity(y))
                 {
-                    return double.PositiveInfinity;
+                    return JsNumber.DoublePositiveInfinity;
                 }
             }
 
@@ -517,12 +724,12 @@ namespace Jint.Native.Math
             {
                 if (y > 0)
                 {
-                    return double.PositiveInfinity;
+                    return JsNumber.DoublePositiveInfinity;
                 }
 
                 if (y < 0)
                 {
-                    return +0;
+                    return JsNumber.PositiveZero;
                 }
             }
 
@@ -532,20 +739,20 @@ namespace Jint.Native.Math
                 {
                     if (System.Math.Abs(y % 2).Equals(1))
                     {
-                        return double.NegativeInfinity;
+                        return JsNumber.DoubleNegativeInfinity;
                     }
 
-                    return double.PositiveInfinity;
+                    return JsNumber.DoublePositiveInfinity;
                 }
 
                 if (y < 0)
                 {
                     if (System.Math.Abs(y % 2).Equals(1))
                     {
-                        return -0;
+                        return JsNumber.NegativeZero;
                     }
 
-                    return +0;
+                    return JsNumber.PositiveZero;
                 }
             }
 
@@ -560,7 +767,7 @@ namespace Jint.Native.Math
                 // If x is +0 and y<0, the result is +∞.
                 if (y < 0)
                 {
-                    return double.PositiveInfinity;
+                    return JsNumber.DoublePositiveInfinity;
                 }
             }
 
@@ -572,11 +779,11 @@ namespace Jint.Native.Math
                     // If x is −0 and y>0 and y is an odd integer, the result is −0.
                     if (System.Math.Abs(y % 2).Equals(1))
                     {
-                        return -0;
+                        return JsNumber.NegativeZero;
                     }
 
                     // If x is −0 and y>0 and y is not an odd integer, the result is +0.
-                    return +0;
+                    return JsNumber.PositiveZero;
                 }
 
                 if (y < 0)
@@ -584,25 +791,30 @@ namespace Jint.Native.Math
                     // If x is −0 and y<0 and y is an odd integer, the result is −∞.
                     if (System.Math.Abs(y % 2).Equals(1))
                     {
-                        return double.NegativeInfinity;
+                        return JsNumber.DoubleNegativeInfinity;
                     }
 
                     // If x is −0 and y<0 and y is not an odd integer, the result is +∞.
-                    return double.PositiveInfinity;
+                    return JsNumber.DoublePositiveInfinity;
                 }
             }
 
             // If x<0 and x is finite and y is finite and y is not an integer, the result is NaN.
-            if (x < 0 && !double.IsInfinity(x) && !double.IsInfinity(y) && !y.Equals((int)y))
+            if (x < 0 && !double.IsInfinity(x) && !double.IsInfinity(y) && !y.Equals((int) y))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return System.Math.Pow(x, y);
         }
 
-        private static JsValue Random(JsValue thisObject, JsValue[] arguments)
+        private JsValue Random(JsValue thisObject, JsValue[] arguments)
         {
+            if(_random == null)
+            {
+                _random = new Random();
+            }
+            
             return _random.NextDouble();
         }
 
@@ -618,30 +830,64 @@ namespace Jint.Native.Math
             return round;
         }
 
+        private static JsValue Fround(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+            return (double) (float) x;
+        }
+
         private static JsValue Sin(JsValue thisObject, JsValue[] arguments)
         {
             var x = TypeConverter.ToNumber(arguments.At(0));
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x))
             {
-                return +0;
+                return JsNumber.PositiveZero;
             }
             else if (NumberInstance.IsNegativeZero(x))
             {
-                return -0;
+                return JsNumber.NegativeZero;
             }
             else if (double.IsInfinity(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             return System.Math.Sin(x);
         }
 
+        private static JsValue Sinh(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+
+            if (double.IsNaN(x))
+            {
+                return JsNumber.DoubleNaN;
+            }
+            else if (NumberInstance.IsPositiveZero(x))
+            {
+                return JsNumber.PositiveZero;
+            }
+            else if (NumberInstance.IsNegativeZero(x))
+            {
+                return JsNumber.NegativeZero;
+            }
+            else if (double.IsNegativeInfinity(x))
+            {
+                return JsNumber.DoubleNegativeInfinity;
+            }
+            else if (double.IsPositiveInfinity(x))
+            {
+                return JsNumber.DoublePositiveInfinity;
+            }
+
+            return System.Math.Sinh(x);
+        }
+
         private static JsValue Sqrt(JsValue thisObject, JsValue[] arguments)
         {
             var x = TypeConverter.ToNumber(arguments.At(0));
@@ -654,13 +900,19 @@ namespace Jint.Native.Math
             return System.Math.Tan(x);
         }
 
+        private static JsValue Tanh(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments.At(0));
+            return System.Math.Tanh(x);
+        }
+
         private static JsValue Truncate(JsValue thisObject, JsValue[] arguments)
         {
             var x = TypeConverter.ToNumber(arguments.At(0));
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             if (NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
@@ -670,12 +922,12 @@ namespace Jint.Native.Math
 
             if (double.IsPositiveInfinity(x))
             {
-                return double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
 
             if (double.IsNegativeInfinity(x))
             {
-                return double.NegativeInfinity;
+                return JsNumber.DoubleNegativeInfinity;
             }
 
             return System.Math.Truncate(x);
@@ -687,7 +939,7 @@ namespace Jint.Native.Math
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
 
             if (NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
@@ -714,7 +966,7 @@ namespace Jint.Native.Math
 
             if (double.IsNaN(x))
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             else if (NumberInstance.IsPositiveZero(x) || NumberInstance.IsNegativeZero(x))
             {
@@ -722,11 +974,11 @@ namespace Jint.Native.Math
             }
             else if (double.IsPositiveInfinity(x))
             {
-                return double.PositiveInfinity;
+                return JsNumber.DoublePositiveInfinity;
             }
             else if (double.IsNegativeInfinity(x))
             {
-                return double.NegativeInfinity;
+                return JsNumber.DoubleNegativeInfinity;
             }
 
             if (System.Math.Sign(x) >= 0)
@@ -736,5 +988,58 @@ namespace Jint.Native.Math
 
             return -1 * System.Math.Pow(System.Math.Abs(x), 1.0 / 3.0);
         }
+
+        private static JsValue Hypot(JsValue thisObject, JsValue[] arguments)
+        {
+            double y = 0;
+            for (int i = 0; i < arguments.Length; ++i)
+            {
+                var number = TypeConverter.ToNumber(arguments[i]);
+                if (double.IsInfinity(number))
+                {
+                    return JsNumber.DoublePositiveInfinity;
+                }
+                y += number * number;
+            }
+            return System.Math.Sqrt(y);
+        }
+
+        private static JsValue Imul(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToInt32(arguments.At(0));
+            var y = TypeConverter.ToInt32(arguments.At(1));
+
+            return x * y;
+        }
+
+        private static JsValue Clz32(JsValue thisObject, JsValue[] arguments)
+        {
+            var x = TypeConverter.ToInt32(arguments.At(0));
+            if (x < 0)
+            {
+                return 0;
+            }
+
+            if (x == 0)
+            {
+                return 32;
+            }
+
+            var res = 0;
+            var shift = 16;
+            while (x > 1)
+            {
+                var temp = x >> shift;
+                if (temp != 0)
+                {
+                    x = temp;
+                    res += shift;
+                }
+
+                shift >>= 1;
+            }
+
+            return 31 - res;
+        }
     }
 }

+ 656 - 0
Jint/Native/Number/Dtoa/Bignum.cs

@@ -0,0 +1,656 @@
+using System.Diagnostics;
+using Jint.Runtime;
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal sealed class Bignum
+    {
+        // 3584 = 128 * 28. We can represent 2^3584 > 10^1000 accurately.
+        // This bignum can encode much bigger numbers, since it contains an
+        // exponent.
+        private const int kMaxSignificantBits = 3584;
+
+        private const int kChunkSize = sizeof(uint) * 8;
+        private const int kDoubleChunkSize = sizeof(ulong) * 8;
+
+        // With bigit size of 28 we loose some bits, but a double still fits easily
+        // into two chunks, and more importantly we can use the Comba multiplication.
+        private const int kBigitSize = 28;
+        private const uint kBigitMask = (1 << kBigitSize) - 1;
+
+        // Every instance allocates kBigitLength chunks on the stack. Bignums cannot
+        // grow. There are no checks if the stack-allocated space is sufficient.
+        private const int kBigitCapacity = kMaxSignificantBits / kBigitSize;
+
+        private uint[] bigits_ = new uint[kBigitCapacity];
+
+        // The Bignum's value equals value(bigits_) * 2^(exponent_ * kBigitSize).
+        private int exponent_;
+        private int used_digits_;
+
+        private int BigitLength()
+        {
+            return used_digits_ + exponent_;
+        }
+
+        // Precondition: this/other < 16bit.
+        public uint DivideModuloIntBignum(Bignum other)
+        {
+            Debug.Assert(IsClamped());
+            Debug.Assert(other.IsClamped());
+            Debug.Assert(other.used_digits_ > 0);
+
+            // Easy case: if we have less digits than the divisor than the result is 0.
+            // Note: this handles the case where this == 0, too.
+            if (BigitLength() < other.BigitLength()) return 0;
+
+            Align(other);
+
+            uint result = 0;
+
+            // Start by removing multiples of 'other' until both numbers have the same
+            // number of digits.
+            while (BigitLength() > other.BigitLength())
+            {
+                // This naive approach is extremely inefficient if the this divided other
+                // might be big. This function is implemented for doubleToString where
+                // the result should be small (less than 10).
+                Debug.Assert(other.bigits_[other.used_digits_ - 1] >= (1 << kBigitSize) / 16);
+                // Remove the multiples of the first digit.
+                // Example this = 23 and other equals 9. -> Remove 2 multiples.
+                result += bigits_[used_digits_ - 1];
+                SubtractTimes(other, bigits_[used_digits_ - 1]);
+            }
+
+            Debug.Assert(BigitLength() == other.BigitLength());
+
+            // Both bignums are at the same length now.
+            // Since other has more than 0 digits we know that the access to
+            // bigits_[used_digits_ - 1] is safe.
+            var this_bigit = bigits_[used_digits_ - 1];
+            var other_bigit = other.bigits_[other.used_digits_ - 1];
+
+            if (other.used_digits_ == 1)
+            {
+                // Shortcut for easy (and common) case.
+                uint quotient = this_bigit / other_bigit;
+                bigits_[used_digits_ - 1] = this_bigit - other_bigit * quotient;
+                result += quotient;
+                Clamp();
+                return result;
+            }
+
+            uint division_estimate = this_bigit / (other_bigit + 1);
+            result += division_estimate;
+            SubtractTimes(other, division_estimate);
+
+            if (other_bigit * (division_estimate + 1) > this_bigit) return result;
+
+            while (LessEqual(other, this))
+            {
+                SubtractBignum(other);
+                result++;
+            }
+
+            return result;
+        }
+
+        void Align(Bignum other)
+        {
+            if (exponent_ > other.exponent_)
+            {
+                // If "X" represents a "hidden" digit (by the exponent) then we are in the
+                // following case (a == this, b == other):
+                // a:  aaaaaaXXXX   or a:   aaaaaXXX
+                // b:     bbbbbbX      b: bbbbbbbbXX
+                // We replace some of the hidden digits (X) of a with 0 digits.
+                // a:  aaaaaa000X   or a:   aaaaa0XX
+                int zero_digits = exponent_ - other.exponent_;
+                EnsureCapacity(used_digits_ + zero_digits);
+                for (int i = used_digits_ - 1; i >= 0; --i)
+                {
+                    bigits_[i + zero_digits] = bigits_[i];
+                }
+
+                for (int i = 0; i < zero_digits; ++i)
+                {
+                    bigits_[i] = 0;
+                }
+
+                used_digits_ += zero_digits;
+                exponent_ -= zero_digits;
+                Debug.Assert(used_digits_ >= 0);
+                Debug.Assert(exponent_ >= 0);
+            }
+        }
+
+        void EnsureCapacity(int size)
+        {
+            if (size > kBigitCapacity)
+            {
+                ExceptionHelper.ThrowInvalidOperationException();
+            }
+        }
+
+        private void Clamp()
+        {
+            while (used_digits_ > 0 && bigits_[used_digits_ - 1] == 0) used_digits_--;
+            if (used_digits_ == 0) exponent_ = 0;
+        }
+
+
+        private bool IsClamped()
+        {
+            return used_digits_ == 0 || bigits_[used_digits_ - 1] != 0;
+        }
+
+
+        private void Zero()
+        {
+            for (var i = 0; i < used_digits_; ++i) bigits_[i] = 0;
+            used_digits_ = 0;
+            exponent_ = 0;
+        }
+
+        // Guaranteed to lie in one Bigit.
+        internal void AssignUInt16(uint value)
+        {
+            Debug.Assert(kBigitSize <= 8 * sizeof(uint));
+            Zero();
+            if (value == 0) return;
+
+            EnsureCapacity(1);
+            bigits_[0] = value;
+            used_digits_ = 1;
+        }
+
+        internal void AssignUInt64(ulong value)
+        {
+            const int kUInt64Size = 64;
+
+            Zero();
+            if (value == 0) return;
+
+            int needed_bigits = kUInt64Size / kBigitSize + 1;
+            EnsureCapacity(needed_bigits);
+            for (int i = 0; i < needed_bigits; ++i)
+            {
+                bigits_[i] = (uint) (value & kBigitMask);
+                value = value >> kBigitSize;
+            }
+
+            used_digits_ = needed_bigits;
+            Clamp();
+        }
+
+
+        internal void AssignBignum(Bignum other)
+        {
+            exponent_ = other.exponent_;
+            for (int i = 0; i < other.used_digits_; ++i)
+            {
+                bigits_[i] = other.bigits_[i];
+            }
+
+            // Clear the excess digits (if there were any).
+            for (int i = other.used_digits_; i < used_digits_; ++i)
+            {
+                bigits_[i] = 0;
+            }
+
+            used_digits_ = other.used_digits_;
+        }
+
+
+        void SubtractTimes(Bignum other, uint factor)
+        {
+#if DEBUG
+            var a = new Bignum();
+            var b = new Bignum();
+            a.AssignBignum(this);
+            b.AssignBignum(other);
+            b.MultiplyByUInt32(factor);
+            a.SubtractBignum(b);
+#endif
+            Debug.Assert(exponent_ <= other.exponent_);
+            if (factor < 3)
+            {
+                for (int i = 0; i < factor; ++i)
+                {
+                    SubtractBignum(other);
+                }
+
+                return;
+            }
+
+            uint borrow = 0;
+            int exponent_diff = other.exponent_ - exponent_;
+            for (int i = 0; i < other.used_digits_; ++i)
+            {
+                ulong product = factor * other.bigits_[i];
+                ulong remove = borrow + product;
+                uint difference = bigits_[i + exponent_diff] - (uint) (remove & kBigitMask);
+                bigits_[i + exponent_diff] = difference & kBigitMask;
+                borrow = (uint) ((difference >> (kChunkSize - 1)) + (remove >> kBigitSize));
+            }
+
+            for (int i = other.used_digits_ + exponent_diff; i < used_digits_; ++i)
+            {
+                if (borrow == 0) return;
+                uint difference = bigits_[i] - borrow;
+                bigits_[i] = difference & kBigitMask;
+                borrow = difference >> (kChunkSize - 1);
+            }
+
+            Clamp();
+
+#if DEBUG
+            Debug.Assert(Equal(a, this));
+#endif
+        }
+
+
+        void SubtractBignum(Bignum other)
+        {
+            Debug.Assert(IsClamped());
+            Debug.Assert(other.IsClamped());
+            // We require this to be bigger than other.
+            Debug.Assert(LessEqual(other, this));
+
+            Align(other);
+
+            int offset = other.exponent_ - exponent_;
+            uint borrow = 0;
+            int i;
+            for (i = 0; i < other.used_digits_; ++i)
+            {
+                Debug.Assert((borrow == 0) || (borrow == 1));
+                uint difference = bigits_[i + offset] - other.bigits_[i] - borrow;
+                bigits_[i + offset] = difference & kBigitMask;
+                borrow = difference >> (kChunkSize - 1);
+            }
+
+            while (borrow != 0)
+            {
+                uint difference = bigits_[i + offset] - borrow;
+                bigits_[i + offset] = difference & kBigitMask;
+                borrow = difference >> (kChunkSize - 1);
+                ++i;
+            }
+
+            Clamp();
+        }
+
+        internal static bool Equal(Bignum a, Bignum b)
+        {
+            return Compare(a, b) == 0;
+        }
+
+        internal static bool LessEqual(Bignum a, Bignum b)
+        {
+            return Compare(a, b) <= 0;
+        }
+
+        internal static bool Less(Bignum a, Bignum b)
+        {
+            return Compare(a, b) < 0;
+        }
+
+        // Returns a + b == c
+        static bool PlusEqual(Bignum a, Bignum b, Bignum c)
+        {
+            return PlusCompare(a, b, c) == 0;
+        }
+
+        // Returns a + b <= c
+        static bool PlusLessEqual(Bignum a, Bignum b, Bignum c)
+        {
+            return PlusCompare(a, b, c) <= 0;
+        }
+
+        // Returns a + b < c
+        static bool PlusLess(Bignum a, Bignum b, Bignum c)
+        {
+            return PlusCompare(a, b, c) < 0;
+        }
+
+        uint BigitAt(int index)
+        {
+            if (index >= BigitLength()) return 0;
+            if (index < exponent_) return 0;
+            return bigits_[index - exponent_];
+        }
+
+
+        static int Compare(Bignum a, Bignum b)
+        {
+            Debug.Assert(a.IsClamped());
+            Debug.Assert(b.IsClamped());
+            int bigit_length_a = a.BigitLength();
+            int bigit_length_b = b.BigitLength();
+            if (bigit_length_a < bigit_length_b) return -1;
+            if (bigit_length_a > bigit_length_b) return +1;
+            for (int i = bigit_length_a - 1; i >= System.Math.Min(a.exponent_, b.exponent_); --i)
+            {
+                uint bigit_a = a.BigitAt(i);
+                uint bigit_b = b.BigitAt(i);
+                if (bigit_a < bigit_b) return -1;
+                if (bigit_a > bigit_b) return +1;
+                // Otherwise they are equal up to this digit. Try the next digit.
+            }
+
+            return 0;
+        }
+
+
+        internal static int PlusCompare(Bignum a, Bignum b, Bignum c)
+        {
+            Debug.Assert(a.IsClamped());
+            Debug.Assert(b.IsClamped());
+            Debug.Assert(c.IsClamped());
+            if (a.BigitLength() < b.BigitLength())
+            {
+                return PlusCompare(b, a, c);
+            }
+
+            if (a.BigitLength() + 1 < c.BigitLength()) return -1;
+            if (a.BigitLength() > c.BigitLength()) return +1;
+            // The exponent encodes 0-bigits. So if there are more 0-digits in 'a' than
+            // 'b' has digits, then the bigit-length of 'a'+'b' must be equal to the one
+            // of 'a'.
+            if (a.exponent_ >= b.BigitLength() && a.BigitLength() < c.BigitLength())
+            {
+                return -1;
+            }
+
+            uint borrow = 0;
+            // Starting at min_exponent all digits are == 0. So no need to compare them.
+            int min_exponent = System.Math.Min(System.Math.Min(a.exponent_, b.exponent_), c.exponent_);
+            for (int i = c.BigitLength() - 1; i >= min_exponent; --i)
+            {
+                uint chunk_a = a.BigitAt(i);
+                uint chunk_b = b.BigitAt(i);
+                uint chunk_c = c.BigitAt(i);
+                uint sum = chunk_a + chunk_b;
+                if (sum > chunk_c + borrow)
+                {
+                    return +1;
+                }
+                else
+                {
+                    borrow = chunk_c + borrow - sum;
+                    if (borrow > 1) return -1;
+                    borrow <<= kBigitSize;
+                }
+            }
+
+            if (borrow == 0) return 0;
+            return -1;
+        }
+
+        internal void Times10()
+        {
+            MultiplyByUInt32(10);
+        }
+
+        internal void MultiplyByUInt32(uint factor)
+        {
+            if (factor == 1) return;
+            if (factor == 0)
+            {
+                Zero();
+                return;
+            }
+
+            if (used_digits_ == 0) return;
+
+            // The product of a bigit with the factor is of size kBigitSize + 32.
+            // Assert that this number + 1 (for the carry) fits into double chunk.
+            Debug.Assert(kDoubleChunkSize >= kBigitSize + 32 + 1);
+            ulong carry = 0;
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                ulong product = ((ulong) factor) * bigits_[i] + carry;
+                bigits_[i] = (uint) (product & kBigitMask);
+                carry = (product >> kBigitSize);
+            }
+
+            while (carry != 0)
+            {
+                EnsureCapacity(used_digits_ + 1);
+                bigits_[used_digits_] = (uint) (carry & kBigitMask);
+                used_digits_++;
+                carry >>= kBigitSize;
+            }
+        }
+
+        internal void MultiplyByUInt64(ulong factor)
+        {
+            if (factor == 1) return;
+            if (factor == 0) {
+                Zero();
+                return;
+            }
+            Debug.Assert(kBigitSize < 32);
+            ulong carry = 0;
+            ulong low = factor & 0xFFFFFFFF;
+            ulong high = factor >> 32;
+            for (int i = 0; i < used_digits_; ++i) {
+                ulong product_low = low * bigits_[i];
+                ulong product_high = high * bigits_[i];
+                ulong tmp = (carry & kBigitMask) + product_low;
+                bigits_[i] = (uint) (tmp & kBigitMask);
+                carry = (carry >> kBigitSize) + (tmp >> kBigitSize) +
+                        (product_high << (32 - kBigitSize));
+            }
+            while (carry != 0) {
+                EnsureCapacity(used_digits_ + 1);
+                bigits_[used_digits_] = (uint) (carry & kBigitMask);
+                used_digits_++;
+                carry >>= kBigitSize;
+            }
+        }
+
+        internal void ShiftLeft(int shift_amount)
+        {
+            if (used_digits_ == 0) return;
+            exponent_ += shift_amount / kBigitSize;
+            int local_shift = shift_amount % kBigitSize;
+            EnsureCapacity(used_digits_ + 1);
+            BigitsShiftLeft(local_shift);
+        }
+
+        void BigitsShiftLeft(int shift_amount)
+        {
+            Debug.Assert(shift_amount < kBigitSize);
+            Debug.Assert(shift_amount >= 0);
+            uint carry = 0;
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                uint new_carry = bigits_[i] >> (kBigitSize - shift_amount);
+                bigits_[i] = ((bigits_[i] << shift_amount) + carry) & kBigitMask;
+                carry = new_carry;
+            }
+
+            if (carry != 0)
+            {
+                bigits_[used_digits_] = carry;
+                used_digits_++;
+            }
+        }
+
+
+        internal void AssignPowerUInt16(uint baseValue, int power_exponent)
+        {
+            Debug.Assert(baseValue != 0);
+            Debug.Assert(power_exponent >= 0);
+            if (power_exponent == 0)
+            {
+                AssignUInt16(1);
+                return;
+            }
+
+            Zero();
+            int shifts = 0;
+            // We expect baseValue to be in range 2-32, and most often to be 10.
+            // It does not make much sense to implement different algorithms for counting
+            // the bits.
+            while ((baseValue & 1) == 0)
+            {
+                baseValue >>= 1;
+                shifts++;
+            }
+
+            int bit_size = 0;
+            uint tmp_base = baseValue;
+            while (tmp_base != 0)
+            {
+                tmp_base >>= 1;
+                bit_size++;
+            }
+
+            int final_size = bit_size * power_exponent;
+            // 1 extra bigit for the shifting, and one for rounded final_size.
+            EnsureCapacity(final_size / kBigitSize + 2);
+
+            // Left to Right exponentiation.
+            int mask = 1;
+            while (power_exponent >= mask) mask <<= 1;
+
+            // The mask is now pointing to the bit above the most significant 1-bit of
+            // power_exponent.
+            // Get rid of first 1-bit;
+            mask >>= 2;
+            ulong this_value = baseValue;
+
+            bool delayed_multipliciation = false;
+            const ulong max_32bits = 0xFFFFFFFF;
+            while (mask != 0 && this_value <= max_32bits)
+            {
+                this_value = this_value * this_value;
+                // Verify that there is enough space in this_value to perform the
+                // multiplication.  The first bit_size bits must be 0.
+                if ((power_exponent & mask) != 0)
+                {
+                    ulong base_bits_mask = ~((((ulong) 1) << (64 - bit_size)) - 1);
+                    bool high_bits_zero = (this_value & base_bits_mask) == 0;
+                    if (high_bits_zero)
+                    {
+                        this_value *= baseValue;
+                    }
+                    else
+                    {
+                        delayed_multipliciation = true;
+                    }
+                }
+
+                mask >>= 1;
+            }
+
+            AssignUInt64(this_value);
+            if (delayed_multipliciation)
+            {
+                MultiplyByUInt32(baseValue);
+            }
+
+            // Now do the same thing as a bignum.
+            while (mask != 0)
+            {
+                Square();
+                if ((power_exponent & mask) != 0)
+                {
+                    MultiplyByUInt32(baseValue);
+                }
+
+                mask >>= 1;
+            }
+
+            // And finally add the saved shifts.
+            ShiftLeft(shifts * power_exponent);
+        }
+
+        void Square()
+        {
+            Debug.Assert(IsClamped());
+            int product_length = 2 * used_digits_;
+            EnsureCapacity(product_length);
+
+            // Comba multiplication: compute each column separately.
+            // Example: r = a2a1a0 * b2b1b0.
+            //    r =  1    * a0b0 +
+            //        10    * (a1b0 + a0b1) +
+            //        100   * (a2b0 + a1b1 + a0b2) +
+            //        1000  * (a2b1 + a1b2) +
+            //        10000 * a2b2
+            //
+            // In the worst case we have to accumulate nb-digits products of digit*digit.
+            //
+            // Assert that the additional number of bits in a DoubleChunk are enough to
+            // sum up used_digits of Bigit*Bigit.
+            if ((1 << (2 * (kChunkSize - kBigitSize))) <= used_digits_)
+            {
+                ExceptionHelper.ThrowNotImplementedException();
+            }
+
+            ulong accumulator = 0;
+            // First shift the digits so we don't overwrite them.
+            int copy_offset = used_digits_;
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                bigits_[copy_offset + i] = bigits_[i];
+            }
+
+            // We have two loops to avoid some 'if's in the loop.
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                // Process temporary digit i with power i.
+                // The sum of the two indices must be equal to i.
+                int bigit_index1 = i;
+                int bigit_index2 = 0;
+                // Sum all of the sub-products.
+                while (bigit_index1 >= 0)
+                {
+                    uint chunk1 = bigits_[copy_offset + bigit_index1];
+                    uint chunk2 = bigits_[copy_offset + bigit_index2];
+                    accumulator += (ulong) chunk1 * chunk2;
+                    bigit_index1--;
+                    bigit_index2++;
+                }
+
+                bigits_[i] = (uint) accumulator & kBigitMask;
+                accumulator >>= kBigitSize;
+            }
+
+            for (int i = used_digits_; i < product_length; ++i)
+            {
+                int bigit_index1 = used_digits_ - 1;
+                int bigit_index2 = i - bigit_index1;
+                // Invariant: sum of both indices is again equal to i.
+                // Inner loop runs 0 times on last iteration, emptying accumulator.
+                while (bigit_index2 < used_digits_)
+                {
+                    uint chunk1 = bigits_[copy_offset + bigit_index1];
+                    uint chunk2 = bigits_[copy_offset + bigit_index2];
+                    accumulator += (ulong) chunk1 * chunk2;
+                    bigit_index1--;
+                    bigit_index2++;
+                }
+
+                // The overwritten bigits_[i] will never be read in further loop iterations,
+                // because bigit_index1 and bigit_index2 are always greater
+                // than i - used_digits_.
+                bigits_[i] = (uint) accumulator & kBigitMask;
+                accumulator >>= kBigitSize;
+            }
+
+            // Since the result was guaranteed to lie inside the number the
+            // accumulator must be 0 now.
+            Debug.Assert(accumulator == 0);
+
+            // Don't forget to update the used_digits and the exponent.
+            used_digits_ = product_length;
+            exponent_ *= 2;
+            Clamp();
+        }
+    }
+}

+ 694 - 0
Jint/Native/Number/Dtoa/BignumDtoa.cs

@@ -0,0 +1,694 @@
+using System;
+using System.Diagnostics;
+using Jint.Runtime;
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal static class BignumDtoa
+    {
+        public static void NumberToString(
+            double v,
+            DtoaMode mode,
+            int requested_digits,
+            DtoaBuilder builder,
+            out int decimal_point)
+        {
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            var significand = DoubleHelper.Significand(bits);
+            var is_even = (significand & 1) == 0;
+            var exponent = DoubleHelper.Exponent(bits);
+            var normalized_exponent = DoubleHelper.NormalizedExponent(significand, exponent);
+            // estimated_power might be too low by 1.
+            var estimated_power = EstimatePower(normalized_exponent);
+
+            // Shortcut for Fixed.
+            // The requested digits correspond to the digits after the point. If the
+            // number is much too small, then there is no need in trying to get any
+            // digits.
+            if (mode == DtoaMode.Fixed && -estimated_power - 1 > requested_digits)
+            {
+                // Set decimal-point to -requested_digits. This is what Gay does.
+                // Note that it should not have any effect anyways since the string is
+                // empty.
+                decimal_point = -requested_digits;
+                return;
+            }
+
+            Bignum numerator = new Bignum();
+            Bignum denominator = new Bignum();
+            Bignum delta_minus = new Bignum();
+            Bignum delta_plus = new Bignum();
+            // Make sure the bignum can grow large enough. The smallest double equals
+            // 4e-324. In this case the denominator needs fewer than 324*4 binary digits.
+            // The maximum double is 1.7976931348623157e308 which needs fewer than
+            // 308*4 binary digits.
+            var need_boundary_deltas = mode == DtoaMode.Shortest;
+
+            InitialScaledStartValues(
+                v,
+                estimated_power,
+                need_boundary_deltas,
+                numerator,
+                denominator,
+                delta_minus,
+                delta_plus);
+            // We now have v = (numerator / denominator) * 10^estimated_power.
+            FixupMultiply10(
+                estimated_power,
+                is_even,
+                out decimal_point,
+                numerator,
+                denominator,
+                delta_minus,
+                delta_plus);
+            // We now have v = (numerator / denominator) * 10^(decimal_point-1), and
+            //  1 <= (numerator + delta_plus) / denominator < 10
+            switch (mode)
+            {
+                case DtoaMode.Shortest:
+                    GenerateShortestDigits(
+                        numerator,
+                        denominator,
+                        delta_minus,
+                        delta_plus,
+                        is_even,
+                        builder);
+                    break;
+                case DtoaMode.Fixed:
+                    BignumToFixed(
+                        requested_digits,
+                        ref decimal_point,
+                        numerator,
+                        denominator,
+                        builder);
+                    break;
+                case DtoaMode.Precision:
+                    GenerateCountedDigits(
+                        requested_digits,
+                        ref decimal_point,
+                        numerator,
+                        denominator,
+                        builder);
+                    break;
+                default:
+                    ExceptionHelper.ThrowArgumentOutOfRangeException();
+                    break;
+            }
+        }
+
+
+        // The procedure starts generating digits from the left to the right and stops
+        // when the generated digits yield the shortest decimal representation of v. A
+        // decimal representation of v is a number lying closer to v than to any other
+        // double, so it converts to v when read.
+        //
+        // This is true if d, the decimal representation, is between m- and m+, the
+        // upper and lower boundaries. d must be strictly between them if !is_even.
+        //           m- := (numerator - delta_minus) / denominator
+        //           m+ := (numerator + delta_plus) / denominator
+        //
+        // Precondition: 0 <= (numerator+delta_plus) / denominator < 10.
+        //   If 1 <= (numerator+delta_plus) / denominator < 10 then no leading 0 digit
+        //   will be produced. This should be the standard precondition.
+        private static void GenerateShortestDigits(
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus,
+            bool is_even,
+            DtoaBuilder buffer)
+        {
+            // Small optimization: if delta_minus and delta_plus are the same just reuse
+            // one of the two bignums.
+            if (Bignum.Equal(delta_minus, delta_plus))
+            {
+                delta_plus = delta_minus;
+            }
+
+            buffer.Reset();
+            while (true)
+            {
+                uint digit;
+                digit = numerator.DivideModuloIntBignum(denominator);
+                Debug.Assert(digit <= 9); // digit is a uint and therefore always positive.
+                // digit = numerator / denominator (integer division).
+                // numerator = numerator % denominator.
+                buffer.Append((char) (digit + '0'));
+
+                // Can we stop already?
+                // If the remainder of the division is less than the distance to the lower
+                // boundary we can stop. In this case we simply round down (discarding the
+                // remainder).
+                // Similarly we test if we can round up (using the upper boundary).
+                bool in_delta_room_minus;
+                bool in_delta_room_plus;
+                if (is_even)
+                {
+                    in_delta_room_minus = Bignum.LessEqual(numerator, delta_minus);
+                }
+                else
+                {
+                    in_delta_room_minus = Bignum.Less(numerator, delta_minus);
+                }
+                if (is_even)
+                {
+                    in_delta_room_plus = Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0;
+                }
+                else
+                {
+                    in_delta_room_plus = Bignum.PlusCompare(numerator, delta_plus, denominator) > 0;
+                }
+                if (!in_delta_room_minus && !in_delta_room_plus)
+                {
+                    // Prepare for next iteration.
+                    numerator.Times10();
+                    delta_minus.Times10();
+                    // We optimized delta_plus to be equal to delta_minus (if they share the
+                    // same value). So don't multiply delta_plus if they point to the same
+                    // object.
+                    if (delta_minus != delta_plus) delta_plus.Times10();
+                }
+                else if (in_delta_room_minus && in_delta_room_plus)
+                {
+                    // Let's see if 2*numerator < denominator.
+                    // If yes, then the next digit would be < 5 and we can round down.
+                    int compare = Bignum.PlusCompare(numerator, numerator, denominator);
+                    if (compare < 0)
+                    {
+                        // Remaining digits are less than .5. -> Round down (== do nothing).
+                    }
+                    else if (compare > 0)
+                    {
+                        // Remaining digits are more than .5 of denominator. . Round up.
+                        // Note that the last digit could not be a '9' as otherwise the whole
+                        // loop would have stopped earlier.
+                        // We still have an assert here in case the preconditions were not
+                        // satisfied.
+                        Debug.Assert(buffer[buffer.Length - 1] != '9');
+                        buffer[buffer.Length - 1]++;
+                    }
+                    else
+                    {
+                        // Halfway case.
+                        // TODO(floitsch): need a way to solve half-way cases.
+                        //   For now let's round towards even (since this is what Gay seems to
+                        //   do).
+
+                        if ((buffer[buffer.Length - 1] - '0') % 2 == 0)
+                        {
+                            // Round down => Do nothing.
+                        }
+                        else
+                        {
+                            Debug.Assert(buffer[buffer.Length - 1] != '9');
+                            buffer[buffer.Length - 1]++;
+                        }
+                    }
+
+                    return;
+                }
+                else if (in_delta_room_minus)
+                {
+                    // Round down (== do nothing).
+                    return;
+                }
+                else
+                {
+                    // in_delta_room_plus
+                    // Round up.
+                    // Note again that the last digit could not be '9' since this would have
+                    // stopped the loop earlier.
+                    // We still have an DCHECK here, in case the preconditions were not
+                    // satisfied.
+                    Debug.Assert(buffer[buffer.Length - 1] != '9');
+                    buffer[buffer.Length - 1]++;
+                    return;
+                }
+            }
+        }
+        
+        // Let v = numerator / denominator < 10.
+        // Then we generate 'count' digits of d = x.xxxxx... (without the decimal point)
+        // from left to right. Once 'count' digits have been produced we decide wether
+        // to round up or down. Remainders of exactly .5 round upwards. Numbers such
+        // as 9.999999 propagate a carry all the way, and change the
+        // exponent (decimal_point), when rounding upwards.
+        static void GenerateCountedDigits(
+            int count, 
+            ref int decimal_point,
+            Bignum numerator, 
+            Bignum denominator,
+            DtoaBuilder buffer)
+        {
+            Debug.Assert(count >= 0);
+            for (int i = 0; i < count - 1; ++i)
+            {
+                uint d = numerator.DivideModuloIntBignum(denominator);
+                Debug.Assert(d <= 9);  // digit is a uint and therefore always positive.
+                // digit = numerator / denominator (integer division).
+                // numerator = numerator % denominator.
+                buffer.Append((char) (d + '0'));
+                // Prepare for next iteration.
+                numerator.Times10();
+            }
+            // Generate the last digit.
+            uint digit = numerator.DivideModuloIntBignum(denominator);
+            if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0)
+            {
+                digit++;
+            }
+            buffer.Append((char) (digit + '0'));
+            // Correct bad digits (in case we had a sequence of '9's). Propagate the
+            // carry until we hat a non-'9' or til we reach the first digit.
+            for (int i = count - 1; i > 0; --i)
+            {
+                if (buffer[i] != '0' + 10) break;
+                buffer[i] = '0';
+                buffer[i - 1]++;
+            }
+            if (buffer[0] == '0' + 10)
+            {
+                // Propagate a carry past the top place.
+                buffer[0] = '1';
+                decimal_point++;
+            }
+        }
+
+
+        // Generates 'requested_digits' after the decimal point. It might omit
+        // trailing '0's. If the input number is too small then no digits at all are
+        // generated (ex.: 2 fixed digits for 0.00001).
+        //
+        // Input verifies:  1 <= (numerator + delta) / denominator < 10.
+        static void BignumToFixed(
+            int requested_digits,
+            ref int decimal_point,
+            Bignum numerator,
+            Bignum denominator,
+            DtoaBuilder buffer)
+        {
+            // Note that we have to look at more than just the requested_digits, since
+            // a number could be rounded up. Example: v=0.5 with requested_digits=0.
+            // Even though the power of v equals 0 we can't just stop here.
+            if (-(decimal_point) > requested_digits)
+            {
+                // The number is definitively too small.
+                // Ex: 0.001 with requested_digits == 1.
+                // Set decimal-point to -requested_digits. This is what Gay does.
+                // Note that it should not have any effect anyways since the string is
+                // empty.
+                decimal_point = -requested_digits;
+                buffer.Reset();
+                return;
+            }
+
+            if (-decimal_point == requested_digits)
+            {
+                // We only need to verify if the number rounds down or up.
+                // Ex: 0.04 and 0.06 with requested_digits == 1.
+                Debug.Assert(decimal_point == -requested_digits);
+                // Initially the fraction lies in range (1, 10]. Multiply the denominator
+                // by 10 so that we can compare more easily.
+                denominator.Times10();
+                if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0)
+                {
+                    // If the fraction is >= 0.5 then we have to include the rounded
+                    // digit.
+                    buffer[0] = '1';
+                    decimal_point++;
+                }
+                else
+                {
+                    // Note that we caught most of similar cases earlier.
+                    buffer.Reset();
+                }
+            }
+            else
+            {
+                // The requested digits correspond to the digits after the point.
+                // The variable 'needed_digits' includes the digits before the point.
+                int needed_digits = (decimal_point) + requested_digits;
+                GenerateCountedDigits(needed_digits, ref decimal_point, numerator, denominator, buffer);
+            }
+        }
+
+        // Returns an estimation of k such that 10^(k-1) <= v < 10^k where
+        // v = f * 2^exponent and 2^52 <= f < 2^53.
+        // v is hence a normalized double with the given exponent. The output is an
+        // approximation for the exponent of the decimal approimation .digits * 10^k.
+        //
+        // The result might undershoot by 1 in which case 10^k <= v < 10^k+1.
+        // Note: this property holds for v's upper boundary m+ too.
+        //    10^k <= m+ < 10^k+1.
+        //   (see explanation below).
+        //
+        // Examples:
+        //  EstimatePower(0)   => 16
+        //  EstimatePower(-52) => 0
+        //
+        // Note: e >= 0 => EstimatedPower(e) > 0. No similar claim can be made for e<0.
+        private static int EstimatePower(int exponent)
+        {
+            // This function estimates log10 of v where v = f*2^e (with e == exponent).
+            // Note that 10^floor(log10(v)) <= v, but v <= 10^ceil(log10(v)).
+            // Note that f is bounded by its container size. Let p = 53 (the double's
+            // significand size). Then 2^(p-1) <= f < 2^p.
+            //
+            // Given that log10(v) == log2(v)/log2(10) and e+(len(f)-1) is quite close
+            // to log2(v) the function is simplified to (e+(len(f)-1)/log2(10)).
+            // The computed number undershoots by less than 0.631 (when we compute log3
+            // and not log10).
+            //
+            // Optimization: since we only need an approximated result this computation
+            // can be performed on 64 bit integers. On x86/x64 architecture the speedup is
+            // not really measurable, though.
+            //
+            // Since we want to avoid overshooting we decrement by 1e10 so that
+            // floating-point imprecisions don't affect us.
+            //
+            // Explanation for v's boundary m+: the computation takes advantage of
+            // the fact that 2^(p-1) <= f < 2^p. Boundaries still satisfy this requirement
+            // (even for denormals where the delta can be much more important).
+
+            const double k1Log10 = 0.30102999566398114; // 1/lg(10)
+
+            // For doubles len(f) == 53 (don't forget the hidden bit).
+            const int kSignificandSize = 53;
+            double estimate = System.Math.Ceiling((exponent + kSignificandSize - 1) * k1Log10 - 1e-10);
+            return (int) estimate;
+        }
+
+
+        // See comments for InitialScaledStartValues.
+        private static void InitialScaledStartValuesPositiveExponent(
+            double v,
+            int estimated_power,
+            bool need_boundary_deltas,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            // A positive exponent implies a positive power.
+            Debug.Assert(estimated_power >= 0);
+            // Since the estimated_power is positive we simply multiply the denominator
+            // by 10^estimated_power.
+
+            // numerator = v.
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            numerator.AssignUInt64(DoubleHelper.Significand(bits));
+            numerator.ShiftLeft(DoubleHelper.Exponent(bits));
+            // denominator = 10^estimated_power.
+            denominator.AssignPowerUInt16(10, estimated_power);
+
+            if (need_boundary_deltas)
+            {
+                // Introduce a common denominator so that the deltas to the boundaries are
+                // integers.
+                denominator.ShiftLeft(1);
+                numerator.ShiftLeft(1);
+                // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
+                // denominator (of 2) delta_plus equals 2^e.
+                delta_plus.AssignUInt16(1);
+                delta_plus.ShiftLeft(DoubleHelper.Exponent(bits));
+                // Same for delta_minus (with adjustments below if f == 2^p-1).
+                delta_minus.AssignUInt16(1);
+                delta_minus.ShiftLeft(DoubleHelper.Exponent(bits));
+
+                // If the significand (without the hidden bit) is 0, then the lower
+                // boundary is closer than just half a ulp (unit in the last place).
+                // There is only one exception: if the next lower number is a denormal then
+                // the distance is 1 ulp. This cannot be the case for exponent >= 0 (but we
+                // have to test it in the other function where exponent < 0).
+                ulong v_bits = bits;
+                if ((v_bits & DoubleHelper.KSignificandMask) == 0)
+                {
+                    // The lower boundary is closer at half the distance of "normal" numbers.
+                    // Increase the common denominator and adapt all but the delta_minus.
+                    denominator.ShiftLeft(1); // *2
+                    numerator.ShiftLeft(1); // *2
+                    delta_plus.ShiftLeft(1); // *2
+                }
+            }
+        }
+
+
+        // See comments for InitialScaledStartValues
+        private static void InitialScaledStartValuesNegativeExponentPositivePower(
+            double v,
+            int estimated_power, 
+            bool need_boundary_deltas,
+            Bignum numerator, 
+            Bignum denominator,
+            Bignum delta_minus, 
+            Bignum delta_plus)
+        {
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            ulong significand = DoubleHelper.Significand(bits);
+            int exponent = DoubleHelper.Exponent(bits);
+            // v = f * 2^e with e < 0, and with estimated_power >= 0.
+            // This means that e is close to 0 (have a look at how estimated_power is
+            // computed).
+
+            // numerator = significand
+            //  since v = significand * 2^exponent this is equivalent to
+            //  numerator = v * / 2^-exponent
+            numerator.AssignUInt64(significand);
+            // denominator = 10^estimated_power * 2^-exponent (with exponent < 0)
+            denominator.AssignPowerUInt16(10, estimated_power);
+            denominator.ShiftLeft(-exponent);
+
+            if (need_boundary_deltas)
+            {
+                // Introduce a common denominator so that the deltas to the boundaries are
+                // integers.
+                denominator.ShiftLeft(1);
+                numerator.ShiftLeft(1);
+                // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
+                // denominator (of 2) delta_plus equals 2^e.
+                // Given that the denominator already includes v's exponent the distance
+                // to the boundaries is simply 1.
+                delta_plus.AssignUInt16(1);
+                // Same for delta_minus (with adjustments below if f == 2^p-1).
+                delta_minus.AssignUInt16(1);
+
+                // If the significand (without the hidden bit) is 0, then the lower
+                // boundary is closer than just one ulp (unit in the last place).
+                // There is only one exception: if the next lower number is a denormal
+                // then the distance is 1 ulp. Since the exponent is close to zero
+                // (otherwise estimated_power would have been negative) this cannot happen
+                // here either.
+                ulong v_bits = bits;
+                if ((v_bits & DoubleHelper.KSignificandMask) == 0)
+                {
+                    // The lower boundary is closer at half the distance of "normal" numbers.
+                    // Increase the denominator and adapt all but the delta_minus.
+                    denominator.ShiftLeft(1); // *2
+                    numerator.ShiftLeft(1); // *2
+                    delta_plus.ShiftLeft(1); // *2
+                }
+            }
+        }
+
+
+        // See comments for InitialScaledStartValues
+        private static void InitialScaledStartValuesNegativeExponentNegativePower(
+            double v,
+            int estimated_power,
+            bool need_boundary_deltas,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            const ulong kMinimalNormalizedExponent = 0x0010000000000000;
+
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            ulong significand = DoubleHelper.Significand(bits);
+            int exponent = DoubleHelper.Exponent(bits);
+            // Instead of multiplying the denominator with 10^estimated_power we
+            // multiply all values (numerator and deltas) by 10^-estimated_power.
+
+            // Use numerator as temporary container for power_ten.
+            Bignum power_ten = numerator;
+            power_ten.AssignPowerUInt16(10, -estimated_power);
+
+            if (need_boundary_deltas)
+            {
+                // Since power_ten == numerator we must make a copy of 10^estimated_power
+                // before we complete the computation of the numerator.
+                // delta_plus = delta_minus = 10^estimated_power
+                delta_plus.AssignBignum(power_ten);
+                delta_minus.AssignBignum(power_ten);
+            }
+
+            // numerator = significand * 2 * 10^-estimated_power
+            //  since v = significand * 2^exponent this is equivalent to
+            // numerator = v * 10^-estimated_power * 2 * 2^-exponent.
+            // Remember: numerator has been abused as power_ten. So no need to assign it
+            //  to itself.
+            Debug.Assert(numerator == power_ten);
+            numerator.MultiplyByUInt64(significand);
+
+            // denominator = 2 * 2^-exponent with exponent < 0.
+            denominator.AssignUInt16(1);
+            denominator.ShiftLeft(-exponent);
+
+            if (need_boundary_deltas)
+            {
+                // Introduce a common denominator so that the deltas to the boundaries are
+                // integers.
+                numerator.ShiftLeft(1);
+                denominator.ShiftLeft(1);
+                // With this shift the boundaries have their correct value, since
+                // delta_plus = 10^-estimated_power, and
+                // delta_minus = 10^-estimated_power.
+                // These assignments have been done earlier.
+
+                // The special case where the lower boundary is twice as close.
+                // This time we have to look out for the exception too.
+                ulong v_bits = bits;
+                if ((v_bits & DoubleHelper.KSignificandMask) == 0 &&
+                    // The only exception where a significand == 0 has its boundaries at
+                    // "normal" distances:
+                    (v_bits & DoubleHelper.KExponentMask) != kMinimalNormalizedExponent)
+                {
+                    numerator.ShiftLeft(1); // *2
+                    denominator.ShiftLeft(1); // *2
+                    delta_plus.ShiftLeft(1); // *2
+                }
+            }
+        }
+
+
+        // Let v = significand * 2^exponent.
+        // Computes v / 10^estimated_power exactly, as a ratio of two bignums, numerator
+        // and denominator. The functions GenerateShortestDigits and
+        // GenerateCountedDigits will then convert this ratio to its decimal
+        // representation d, with the required accuracy.
+        // Then d * 10^estimated_power is the representation of v.
+        // (Note: the fraction and the estimated_power might get adjusted before
+        // generating the decimal representation.)
+        //
+        // The initial start values consist of:
+        //  - a scaled numerator: s.t. numerator/denominator == v / 10^estimated_power.
+        //  - a scaled (common) denominator.
+        //  optionally (used by GenerateShortestDigits to decide if it has the shortest
+        //  decimal converting back to v):
+        //  - v - m-: the distance to the lower boundary.
+        //  - m+ - v: the distance to the upper boundary.
+        //
+        // v, m+, m-, and therefore v - m- and m+ - v all share the same denominator.
+        //
+        // Let ep == estimated_power, then the returned values will satisfy:
+        //  v / 10^ep = numerator / denominator.
+        //  v's boundarys m- and m+:
+        //    m- / 10^ep == v / 10^ep - delta_minus / denominator
+        //    m+ / 10^ep == v / 10^ep + delta_plus / denominator
+        //  Or in other words:
+        //    m- == v - delta_minus * 10^ep / denominator;
+        //    m+ == v + delta_plus * 10^ep / denominator;
+        //
+        // Since 10^(k-1) <= v < 10^k    (with k == estimated_power)
+        //  or       10^k <= v < 10^(k+1)
+        //  we then have 0.1 <= numerator/denominator < 1
+        //           or    1 <= numerator/denominator < 10
+        //
+        // It is then easy to kickstart the digit-generation routine.
+        //
+        // The boundary-deltas are only filled if need_boundary_deltas is set.
+        private static void InitialScaledStartValues(
+            double v,
+            int estimated_power,
+            bool need_boundary_deltas,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            if (DoubleHelper.Exponent(bits) >= 0)
+            {
+                InitialScaledStartValuesPositiveExponent(
+                    v,
+                    estimated_power,
+                    need_boundary_deltas,
+                    numerator,
+                    denominator,
+                    delta_minus,
+                    delta_plus);
+            }
+            else if (estimated_power >= 0)
+            {
+                InitialScaledStartValuesNegativeExponentPositivePower(
+                    v,
+                    estimated_power,
+                    need_boundary_deltas,
+                    numerator,
+                    denominator,
+                    delta_minus,
+                    delta_plus);
+            }
+            else
+            {
+                InitialScaledStartValuesNegativeExponentNegativePower(
+                    v,
+                    estimated_power,
+                    need_boundary_deltas,
+                    numerator,
+                    denominator,
+                    delta_minus,
+                    delta_plus);
+            }
+        }
+
+
+        // This routine multiplies numerator/denominator so that its values lies in the
+        // range 1-10. That is after a call to this function we have:
+        //    1 <= (numerator + delta_plus) /denominator < 10.
+        // Let numerator the input before modification and numerator' the argument
+        // after modification, then the output-parameter decimal_point is such that
+        //  numerator / denominator * 10^estimated_power ==
+        //    numerator' / denominator' * 10^(decimal_point - 1)
+        // In some cases estimated_power was too low, and this is already the case. We
+        // then simply adjust the power so that 10^(k-1) <= v < 10^k (with k ==
+        // estimated_power) but do not touch the numerator or denominator.
+        // Otherwise the routine multiplies the numerator and the deltas by 10.
+        private static void FixupMultiply10(
+            int estimated_power,
+            bool is_even,
+            out int decimal_point,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            bool in_range;
+            if (is_even)
+                in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0;
+            else
+                in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) > 0;
+            if (in_range)
+            {
+                // Since numerator + delta_plus >= denominator we already have
+                // 1 <= numerator/denominator < 10. Simply update the estimated_power.
+                decimal_point = estimated_power + 1;
+            }
+            else
+            {
+                decimal_point = estimated_power;
+                numerator.Times10();
+                if (Bignum.Equal(delta_minus, delta_plus))
+                {
+                    delta_minus.Times10();
+                    delta_plus.AssignBignum(delta_minus);
+                }
+                else
+                {
+                    delta_minus.Times10();
+                    delta_plus.Times10();
+                }
+            }
+        }
+    }
+}

+ 101 - 92
Jint/Native/Number/Dtoa/CachePowers.cs

@@ -38,13 +38,13 @@ namespace Jint.Native.Number.Dtoa
 
         private class CachedPower
         {
-            internal readonly long Significand;
+            internal readonly ulong Significand;
             internal readonly short BinaryExponent;
             internal readonly short DecimalExponent;
 
             internal CachedPower(ulong significand, short binaryExponent, short decimalExponent)
             {
-                Significand = (long) significand;
+                Significand =  significand;
                 BinaryExponent = binaryExponent;
                 DecimalExponent = decimalExponent;
             }
@@ -62,15 +62,19 @@ namespace Jint.Native.Number.Dtoa
             internal readonly DiyFp cMk;
         }
 
-        internal static GetCachedPowerResult GetCachedPower(int e, int alpha, int gamma)
+        internal static GetCachedPowerResult GetCachedPowerForBinaryExponentRange(int min_exponent, int max_exponent)
         {
             const int kQ = DiyFp.KSignificandSize;
-            double k = System.Math.Ceiling((alpha - e + kQ - 1) * Kd1Log210);
-            int index = (GrisuCacheOffset + (int) k - 1) / CachedPowersSpacing + 1;
+            double k = System.Math.Ceiling((min_exponent + kQ - 1) * Kd1Log210);
+            int foo = kCachedPowersOffset;
+            int index =
+                (foo + (int) k - 1) / kDecimalExponentDistance + 1;
+            Debug.Assert(0 <= index && index < CACHED_POWERS.Length);
             CachedPower cachedPower = CACHED_POWERS[index];
+            Debug.Assert(min_exponent <= cachedPower.BinaryExponent);
+            Debug.Assert(cachedPower.BinaryExponent <= max_exponent);
 
             var cMk = new DiyFp(cachedPower.Significand, cachedPower.BinaryExponent);
-            Debug.Assert((alpha <= cMk.E + e) && (cMk.E + e <= gamma));
             return new GetCachedPowerResult(cachedPower.DecimalExponent, cMk);
         }
 
@@ -78,95 +82,100 @@ namespace Jint.Native.Number.Dtoa
         // Regexp to convert this from original C++ source:
         // \{GRISU_UINT64_C\((\w+), (\w+)\), (\-?\d+), (\-?\d+)\}
 
-        // interval between entries  of the powers cache below
-        private const int CachedPowersSpacing = 8;
-
         private static readonly CachedPower[] CACHED_POWERS =
         {
-            new CachedPower(0xe61acf033d1a45dfL, -1087, -308),
-            new CachedPower(0xab70fe17c79ac6caL, -1060, -300),
-            new CachedPower(0xff77b1fcbebcdc4fL, -1034, -292),
-            new CachedPower(0xbe5691ef416bd60cL, -1007, -284),
-            new CachedPower(0x8dd01fad907ffc3cL, -980, -276),
-            new CachedPower(0xd3515c2831559a83L, -954, -268),
-            new CachedPower(0x9d71ac8fada6c9b5L, -927, -260),
-            new CachedPower(0xea9c227723ee8bcbL, -901, -252),
-            new CachedPower(0xaecc49914078536dL, -874, -244),
-            new CachedPower(0x823c12795db6ce57L, -847, -236),
-            new CachedPower(0xc21094364dfb5637L, -821, -228),
-            new CachedPower(0x9096ea6f3848984fL, -794, -220),
-            new CachedPower(0xd77485cb25823ac7L, -768, -212),
-            new CachedPower(0xa086cfcd97bf97f4L, -741, -204),
-            new CachedPower(0xef340a98172aace5L, -715, -196),
-            new CachedPower(0xb23867fb2a35b28eL, -688, -188),
-            new CachedPower(0x84c8d4dfd2c63f3bL, -661, -180),
-            new CachedPower(0xc5dd44271ad3cdbaL, -635, -172),
-            new CachedPower(0x936b9fcebb25c996L, -608, -164),
-            new CachedPower(0xdbac6c247d62a584L, -582, -156),
-            new CachedPower(0xa3ab66580d5fdaf6L, -555, -148),
-            new CachedPower(0xf3e2f893dec3f126L, -529, -140),
-            new CachedPower(0xb5b5ada8aaff80b8L, -502, -132),
-            new CachedPower(0x87625f056c7c4a8bL, -475, -124),
-            new CachedPower(0xc9bcff6034c13053L, -449, -116),
-            new CachedPower(0x964e858c91ba2655L, -422, -108),
-            new CachedPower(0xdff9772470297ebdL, -396, -100),
-            new CachedPower(0xa6dfbd9fb8e5b88fL, -369, -92),
-            new CachedPower(0xf8a95fcf88747d94L, -343, -84),
-            new CachedPower(0xb94470938fa89bcfL, -316, -76),
-            new CachedPower(0x8a08f0f8bf0f156bL, -289, -68),
-            new CachedPower(0xcdb02555653131b6L, -263, -60),
-            new CachedPower(0x993fe2c6d07b7facL, -236, -52),
-            new CachedPower(0xe45c10c42a2b3b06L, -210, -44),
-            new CachedPower(0xaa242499697392d3L, -183, -36),
-            new CachedPower(0xfd87b5f28300ca0eL, -157, -28),
-            new CachedPower(0xbce5086492111aebL, -130, -20),
-            new CachedPower(0x8cbccc096f5088ccL, -103, -12),
-            new CachedPower(0xd1b71758e219652cL, -77, -4),
-            new CachedPower(0x9c40000000000000L, -50, 4),
-            new CachedPower(0xe8d4a51000000000L, -24, 12),
-            new CachedPower(0xad78ebc5ac620000L, 3, 20),
-            new CachedPower(0x813f3978f8940984L, 30, 28),
-            new CachedPower(0xc097ce7bc90715b3L, 56, 36),
-            new CachedPower(0x8f7e32ce7bea5c70L, 83, 44),
-            new CachedPower(0xd5d238a4abe98068L, 109, 52),
-            new CachedPower(0x9f4f2726179a2245L, 136, 60),
-            new CachedPower(0xed63a231d4c4fb27L, 162, 68),
-            new CachedPower(0xb0de65388cc8ada8L, 189, 76),
-            new CachedPower(0x83c7088e1aab65dbL, 216, 84),
-            new CachedPower(0xc45d1df942711d9aL, 242, 92),
-            new CachedPower(0x924d692ca61be758L, 269, 100),
-            new CachedPower(0xda01ee641a708deaL, 295, 108),
-            new CachedPower(0xa26da3999aef774aL, 322, 116),
-            new CachedPower(0xf209787bb47d6b85L, 348, 124),
-            new CachedPower(0xb454e4a179dd1877L, 375, 132),
-            new CachedPower(0x865b86925b9bc5c2L, 402, 140),
-            new CachedPower(0xc83553c5c8965d3dL, 428, 148),
-            new CachedPower(0x952ab45cfa97a0b3L, 455, 156),
-            new CachedPower(0xde469fbd99a05fe3L, 481, 164),
-            new CachedPower(0xa59bc234db398c25L, 508, 172),
-            new CachedPower(0xf6c69a72a3989f5cL, 534, 180),
-            new CachedPower(0xb7dcbf5354e9beceL, 561, 188),
-            new CachedPower(0x88fcf317f22241e2L, 588, 196),
-            new CachedPower(0xcc20ce9bd35c78a5L, 614, 204),
-            new CachedPower(0x98165af37b2153dfL, 641, 212),
-            new CachedPower(0xe2a0b5dc971f303aL, 667, 220),
-            new CachedPower(0xa8d9d1535ce3b396L, 694, 228),
-            new CachedPower(0xfb9b7cd9a4a7443cL, 720, 236),
-            new CachedPower(0xbb764c4ca7a44410L, 747, 244),
-            new CachedPower(0x8bab8eefb6409c1aL, 774, 252),
-            new CachedPower(0xd01fef10a657842cL, 800, 260),
-            new CachedPower(0x9b10a4e5e9913129L, 827, 268),
-            new CachedPower(0xe7109bfba19c0c9dL, 853, 276),
-            new CachedPower(0xac2820d9623bf429L, 880, 284),
-            new CachedPower(0x80444b5e7aa7cf85L, 907, 292),
-            new CachedPower(0xbf21e44003acdd2dL, 933, 300),
-            new CachedPower(0x8e679c2f5e44ff8fL, 960, 308),
-            new CachedPower(0xd433179d9c8cb841L, 986, 316),
-            new CachedPower(0x9e19db92b4e31ba9L, 1013, 324),
-            new CachedPower(0xeb96bf6ebadf77d9L, 1039, 332),
-            new CachedPower(0xaf87023b9bf0ee6bL, 1066, 340)
+            new CachedPower(0xFA8FD5A0081C0288, -1220, -348),
+            new CachedPower(0xBAAEE17FA23EBF76, -1193, -340),
+            new CachedPower(0x8B16FB203055AC76, -1166, -332),
+            new CachedPower(0xCF42894A5DCE35EA, -1140, -324),
+            new CachedPower(0x9A6BB0AA55653B2D, -1113, -316),
+            new CachedPower(0xE61ACF033D1A45DF, -1087, -308),
+            new CachedPower(0xAB70FE17C79AC6CA, -1060, -300),
+            new CachedPower(0xFF77B1FCBEBCDC4F, -1034, -292),
+            new CachedPower(0xBE5691EF416BD60C, -1007, -284),
+            new CachedPower(0x8DD01FAD907FFC3C, -980, -276),
+            new CachedPower(0xD3515C2831559A83, -954, -268),
+            new CachedPower(0x9D71AC8FADA6C9B5, -927, -260),
+            new CachedPower(0xEA9C227723EE8BCB, -901, -252),
+            new CachedPower(0xAECC49914078536D, -874, -244),
+            new CachedPower(0x823C12795DB6CE57, -847, -236),
+            new CachedPower(0xC21094364DFB5637, -821, -228),
+            new CachedPower(0x9096EA6F3848984F, -794, -220),
+            new CachedPower(0xD77485CB25823AC7, -768, -212),
+            new CachedPower(0xA086CFCD97BF97F4, -741, -204),
+            new CachedPower(0xEF340A98172AACE5, -715, -196),
+            new CachedPower(0xB23867FB2A35B28E, -688, -188),
+            new CachedPower(0x84C8D4DFD2C63F3B, -661, -180),
+            new CachedPower(0xC5DD44271AD3CDBA, -635, -172),
+            new CachedPower(0x936B9FCEBB25C996, -608, -164),
+            new CachedPower(0xDBAC6C247D62A584, -582, -156),
+            new CachedPower(0xA3AB66580D5FDAF6, -555, -148),
+            new CachedPower(0xF3E2F893DEC3F126, -529, -140),
+            new CachedPower(0xB5B5ADA8AAFF80B8, -502, -132),
+            new CachedPower(0x87625F056C7C4A8B, -475, -124),
+            new CachedPower(0xC9BCFF6034C13053, -449, -116),
+            new CachedPower(0x964E858C91BA2655, -422, -108),
+            new CachedPower(0xDFF9772470297EBD, -396, -100),
+            new CachedPower(0xA6DFBD9FB8E5B88F, -369, -92),
+            new CachedPower(0xF8A95FCF88747D94, -343, -84),
+            new CachedPower(0xB94470938FA89BCF, -316, -76),
+            new CachedPower(0x8A08F0F8BF0F156B, -289, -68),
+            new CachedPower(0xCDB02555653131B6, -263, -60),
+            new CachedPower(0x993FE2C6D07B7FAC, -236, -52),
+            new CachedPower(0xE45C10C42A2B3B06, -210, -44),
+            new CachedPower(0xAA242499697392D3, -183, -36),
+            new CachedPower(0xFD87B5F28300CA0E, -157, -28),
+            new CachedPower(0xBCE5086492111AEB, -130, -20),
+            new CachedPower(0x8CBCCC096F5088CC, -103, -12),
+            new CachedPower(0xD1B71758E219652C, -77, -4),
+            new CachedPower(0x9C40000000000000, -50, 4),
+            new CachedPower(0xE8D4A51000000000, -24, 12),
+            new CachedPower(0xAD78EBC5AC620000, 3, 20),
+            new CachedPower(0x813F3978F8940984, 30, 28),
+            new CachedPower(0xC097CE7BC90715B3, 56, 36),
+            new CachedPower(0x8F7E32CE7BEA5C70, 83, 44),
+            new CachedPower(0xD5D238A4ABE98068, 109, 52),
+            new CachedPower(0x9F4F2726179A2245, 136, 60),
+            new CachedPower(0xED63A231D4C4FB27, 162, 68),
+            new CachedPower(0xB0DE65388CC8ADA8, 189, 76),
+            new CachedPower(0x83C7088E1AAB65DB, 216, 84),
+            new CachedPower(0xC45D1DF942711D9A, 242, 92),
+            new CachedPower(0x924D692CA61BE758, 269, 100),
+            new CachedPower(0xDA01EE641A708DEA, 295, 108),
+            new CachedPower(0xA26DA3999AEF774A, 322, 116),
+            new CachedPower(0xF209787BB47D6B85, 348, 124),
+            new CachedPower(0xB454E4A179DD1877, 375, 132),
+            new CachedPower(0x865B86925B9BC5C2, 402, 140),
+            new CachedPower(0xC83553C5C8965D3D, 428, 148),
+            new CachedPower(0x952AB45CFA97A0B3, 455, 156),
+            new CachedPower(0xDE469FBD99A05FE3, 481, 164),
+            new CachedPower(0xA59BC234DB398C25, 508, 172),
+            new CachedPower(0xF6C69A72A3989F5C, 534, 180),
+            new CachedPower(0xB7DCBF5354E9BECE, 561, 188),
+            new CachedPower(0x88FCF317F22241E2, 588, 196),
+            new CachedPower(0xCC20CE9BD35C78A5, 614, 204),
+            new CachedPower(0x98165AF37B2153DF, 641, 212),
+            new CachedPower(0xE2A0B5DC971F303A, 667, 220),
+            new CachedPower(0xA8D9D1535CE3B396, 694, 228),
+            new CachedPower(0xFB9B7CD9A4A7443C, 720, 236),
+            new CachedPower(0xBB764C4CA7A44410, 747, 244),
+            new CachedPower(0x8BAB8EEFB6409C1A, 774, 252),
+            new CachedPower(0xD01FEF10A657842C, 800, 260),
+            new CachedPower(0x9B10A4E5E9913129, 827, 268),
+            new CachedPower(0xE7109BFBA19C0C9D, 853, 276),
+            new CachedPower(0xAC2820D9623BF429, 880, 284),
+            new CachedPower(0x80444B5E7AA7CF85, 907, 292),
+            new CachedPower(0xBF21E44003ACDD2D, 933, 300),
+            new CachedPower(0x8E679C2F5E44FF8F, 960, 308),
+            new CachedPower(0xD433179D9C8CB841, 986, 316),
+            new CachedPower(0x9E19DB92B4E31BA9, 1013, 324),
+            new CachedPower(0xEB96BF6EBADF77D9, 1039, 332),
+            new CachedPower(0xAF87023B9BF0EE6B, 1066, 340)
         };
 
-        private const int GrisuCacheOffset = 308;
+        const int kCachedPowersOffset = 348;  // -1 * the first decimal_exponent.
+        const int kDecimalExponentDistance = 8;
+        const int kMinDecimalExponent = -348;
+        const int kMaxDecimalExponent = 340;
     }
 }

+ 17 - 25
Jint/Native/Number/Dtoa/DiyFp.cs

@@ -43,28 +43,21 @@ namespace Jint.Native.Number.Dtoa
         internal const int KSignificandSize = 64;
         private const ulong KUint64MSB = 0x8000000000000000L;
 
-        internal DiyFp(long f, int e)
+        internal DiyFp(ulong f, int e)
         {
             F = f;
             E = e;
         }
 
-        public readonly long F;
+        public readonly ulong F;
         public readonly int E;
 
-        private static bool Uint64Gte(long a, long b)
-        {
-            // greater-or-equal for unsigned int64 in java-style...
-            return (a == b) || ((a > b) ^ (a < 0) ^ (b < 0));
-        }
-
         // Returns a - b.
         // The exponents of both numbers must be the same and this must be bigger
         // than other. The result will not be normalized.
         internal static DiyFp Minus(in DiyFp a, in DiyFp b)
         {
             Debug.Assert(a.E == b.E);
-            Debug.Assert(Uint64Gte(a.F, b.F));
 
             return new DiyFp(a.F - b.F, a.E);
         }
@@ -74,39 +67,38 @@ namespace Jint.Native.Number.Dtoa
         // returns a * b;
         internal static DiyFp Times(in DiyFp a, in DiyFp b)
         {
-            DiyFp result = new DiyFp(a.F, a.E);
             // Simply "emulates" a 128 bit multiplication.
             // However: the resulting number only contains 64 bits. The least
             // significant 64 bits are only used for rounding the most significant 64
             // bits.
-            const long kM32 = 0xFFFFFFFFL;
-            long a1 = result.F.UnsignedShift(32);
-            long b1 = result.F & kM32;
-            long c = b.F.UnsignedShift(32);
-            long d = b.F & kM32;
-            long ac = a1*c;
-            long bc = b1*c;
-            long ad = a1*d;
-            long bd = b1*d;
-            long tmp = bd.UnsignedShift(32) + (ad & kM32) + (bc & kM32);
+            const ulong kM32 = 0xFFFFFFFFL;
+            ulong a1 = a.F >> 32;
+            ulong b1 = a.F & kM32;
+            ulong c = b.F >> 32;
+            ulong d = b.F & kM32;
+            ulong ac = a1*c;
+            ulong bc = b1*c;
+            ulong ad = a1*d;
+            ulong bd = b1*d;
+            ulong tmp = (bd >> 32) + (ad & kM32) + (bc & kM32);
             // By adding 1U << 31 to tmp we round the final result.
             // Halfway cases will be round up.
             tmp += 1L << 31;
-            long resultF = ac + ad.UnsignedShift(32) + bc.UnsignedShift(32) + tmp.UnsignedShift(32);
-            return new DiyFp(resultF, result.E + b.E + 64);
+            ulong resultF = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32);
+            return new DiyFp(resultF, a.E + b.E + 64);
         }
 
-        internal static DiyFp Normalize(long f, int e)
+        internal static DiyFp Normalize(ulong f, int e)
         {
             // This method is mainly called for normalizing boundaries. In general
             // boundaries need to be shifted by 10 bits. We thus optimize for this case.
-            const long k10MsBits = 0xFFC00000L << 32;
+            const ulong k10MsBits = (ulong) 0x3FF << 54;
             while ((f & k10MsBits) == 0)
             {
                 f <<= 10;
                 e -= 10;
             }
-            while (((ulong) f & KUint64MSB) == 0)
+            while ((f & KUint64MSB) == 0)
             {
                 f <<= 1;
                 e--;

+ 28 - 19
Jint/Native/Number/Dtoa/DoubleHelper.cs

@@ -32,25 +32,25 @@ using System.Diagnostics;
 
 namespace Jint.Native.Number.Dtoa
 {
-
-// Helper functions for doubles.
+    /// <summary>
+    /// Helper functions for doubles.
+    /// </summary>
     internal class DoubleHelper
     {
+        internal const ulong KExponentMask = 0x7FF0000000000000L;
+        internal const ulong KSignificandMask = 0x000FFFFFFFFFFFFFL;
+        private const ulong KHiddenBit = 0x0010000000000000L;
 
-        private const long KExponentMask = 0x7FF0000000000000L;
-        private const long KSignificandMask = 0x000FFFFFFFFFFFFFL;
-        private const long KHiddenBit = 0x0010000000000000L;
-
-        private static DiyFp AsDiyFp(long d64)
+        private static DiyFp AsDiyFp(ulong d64)
         {
             Debug.Assert(!IsSpecial(d64));
             return new DiyFp(Significand(d64), Exponent(d64));
         }
 
         // this->Significand() must not be 0.
-        internal static DiyFp AsNormalizedDiyFp(long d64)
+        internal static DiyFp AsNormalizedDiyFp(ulong d64)
         {
-            long f = Significand(d64);
+            ulong f = Significand(d64);
             int e = Exponent(d64);
 
             Debug.Assert(f != 0);
@@ -67,7 +67,7 @@ namespace Jint.Native.Number.Dtoa
             return new DiyFp(f, e);
         }
 
-        private static int Exponent(long d64)
+        internal static int Exponent(ulong d64)
         {
             if (IsDenormal(d64)) return KDenormalExponent;
 
@@ -75,28 +75,37 @@ namespace Jint.Native.Number.Dtoa
             return biasedE - KExponentBias;
         }
 
-        private static long Significand(long d64)
+        internal static int NormalizedExponent(ulong significand, int exponent)
         {
-            long significand = d64 & KSignificandMask;
-            if (!IsDenormal(d64))
+            Debug.Assert(significand != 0);
+            while ((significand & KHiddenBit) == 0)
             {
-                return significand + KHiddenBit;
+                significand = significand << 1;
+                exponent = exponent - 1;
             }
-            else
+            return exponent;
+        }
+
+        internal static ulong Significand(ulong d64)
+        {
+            ulong significand = d64 & KSignificandMask;
+            if (!IsDenormal(d64))
             {
-                return significand;
+                return significand + KHiddenBit;
             }
+
+            return significand;
         }
 
         // Returns true if the double is a denormal.
-        private static bool IsDenormal(long d64)
+        private static bool IsDenormal(ulong d64)
         {
             return (d64 & KExponentMask) == 0L;
         }
 
         // We consider denormals not to be special.
         // Hence only Infinity and NaN are special.
-        private static bool IsSpecial(long d64)
+        private static bool IsSpecial(ulong d64)
         {
             return (d64 & KExponentMask) == KExponentMask;
         }
@@ -116,7 +125,7 @@ namespace Jint.Native.Number.Dtoa
         // Returns the two boundaries of first argument.
         // The bigger boundary (m_plus) is normalized. The lower boundary has the same
         // exponent as m_plus.
-        internal static NormalizedBoundariesResult NormalizedBoundaries(long d64)
+        internal static NormalizedBoundariesResult NormalizedBoundaries(ulong d64)
         {
             DiyFp v = AsDiyFp(d64);
             bool significandIsZero = (v.F == KHiddenBit);

+ 53 - 0
Jint/Native/Number/Dtoa/DtoaBuilder.cs

@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal class DtoaBuilder
+    {
+        // allocate buffer for generated digits + extra notation + padding zeroes
+        internal readonly char[] _chars;
+        internal int Length;
+
+        public DtoaBuilder(int size)
+        {
+            _chars = new char[size];
+        }
+
+        public DtoaBuilder() : this(FastDtoa.KFastDtoaMaximalLength + 8)
+        {
+        }
+
+        internal void Append(char c)
+        {
+            _chars[Length++] = c;
+        }
+
+        internal void DecreaseLast()
+        {
+            _chars[Length - 1]--;
+        }
+
+        public void Reset()
+        {
+            Length = 0;
+            System.Array.Clear(_chars, 0, _chars.Length);
+        }
+
+        public char this[int i]
+        {
+            get => _chars[i];
+            set
+            {
+                _chars[i] = value;
+                Length = System.Math.Max(Length, i + 1);
+            }
+        }
+
+        public override string ToString()
+        {
+            return "[chars:" + new string(_chars, 0, Length) + "]";
+        }
+    }
+}

+ 9 - 0
Jint/Native/Number/Dtoa/DtoaMode.cs

@@ -0,0 +1,9 @@
+namespace Jint.Native.Number.Dtoa
+{
+    internal enum DtoaMode
+    {
+        Shortest,
+        Precision,
+        Fixed
+    }
+}

+ 69 - 0
Jint/Native/Number/Dtoa/DtoaNumberFormatter.cs

@@ -0,0 +1,69 @@
+using System.Diagnostics;
+using Jint.Runtime;
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal static class DtoaNumberFormatter
+    {
+        public static void DoubleToAscii(
+            DtoaBuilder buffer,
+            double v,
+            DtoaMode mode,
+            int requested_digits,
+            out bool negative,
+            out int point)
+        {
+            Debug.Assert(!double.IsNaN(v));
+            Debug.Assert(!double.IsInfinity(v));
+            Debug.Assert(mode == DtoaMode.Shortest || requested_digits >= 0);
+
+            point = 0;
+            negative = false;
+            buffer.Reset();
+
+            if (v < 0)
+            {
+                negative = true;
+                v = -v;
+            } 
+
+            if (v == 0)
+            {
+                buffer[0] = '0';
+                point = 1;
+                return;
+            }
+
+            if (mode == DtoaMode.Precision && requested_digits == 0)
+            {
+                return;
+            }
+
+            bool fast_worked = false;
+            switch (mode) {
+                case DtoaMode.Shortest:
+                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Shortest, 0, out point, buffer);
+                    break;
+                case DtoaMode.Fixed:
+                    //fast_worked = FastFixedDtoa(v, requested_digits, buffer, length, point);
+                    ExceptionHelper.ThrowNotImplementedException();
+                    break;
+                case DtoaMode.Precision:
+                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Precision, requested_digits, out point, buffer);
+                    break;
+                default:
+                    ExceptionHelper.ThrowArgumentOutOfRangeException<string>();
+                    return;
+            }
+
+            if (fast_worked)
+            {
+                return;
+            }
+
+            // If the fast dtoa didn't succeed use the slower bignum version.
+            buffer.Reset();
+            BignumDtoa.NumberToString(v, mode, requested_digits, buffer, out point);
+       }
+    }
+}

+ 283 - 57
Jint/Native/Number/Dtoa/FastDtoa.cs

@@ -30,16 +30,12 @@
 
 using System;
 using System.Diagnostics;
-using System.Threading;
+using Jint.Runtime;
 
 namespace Jint.Native.Number.Dtoa
 {
-    internal class FastDtoa
+    internal sealed class FastDtoa
     {
-        // share buffer to reduce memory usage
-        private static readonly ThreadLocal<FastDtoaBuilder> cachedBuffer = new ThreadLocal<FastDtoaBuilder>(() => new FastDtoaBuilder());
-
-
         // FastDtoa will produce at most kFastDtoaMaximalLength digits.
         public const int KFastDtoaMaximalLength = 17;
 
@@ -66,15 +62,16 @@ namespace Jint.Native.Number.Dtoa
         // Output: returns true if the buffer is guaranteed to contain the closest
         //    representable number to the input.
         //  Modifies the generated digits in the buffer to approach (round towards) w.
-        private static bool RoundWeed(FastDtoaBuilder buffer,
-            long distanceTooHighW,
-            long unsafeInterval,
-            long rest,
-            long tenKappa,
-            long unit)
+        private static bool RoundWeed(
+            DtoaBuilder buffer,
+            ulong distanceTooHighW,
+            ulong unsafeInterval,
+            ulong rest,
+            ulong tenKappa,
+            ulong unit)
         {
-            long smallDistance = distanceTooHighW - unit;
-            long bigDistance = distanceTooHighW + unit;
+            ulong smallDistance = distanceTooHighW - unit;
+            ulong bigDistance = distanceTooHighW + unit;
             // Let w_low  = too_high - big_distance, and
             //     w_high = too_high - small_distance.
             // Note: w_low < w < w_high
@@ -172,6 +169,71 @@ namespace Jint.Native.Number.Dtoa
             return (2*unit <= rest) && (rest <= unsafeInterval - 4*unit);
         }
 
+        // Rounds the buffer upwards if the result is closer to v by possibly adding
+        // 1 to the buffer. If the precision of the calculation is not sufficient to
+        // round correctly, return false.
+        // The rounding might shift the whole buffer in which case the kappa is
+        // adjusted. For example "99", kappa = 3 might become "10", kappa = 4.
+        //
+        // If 2*rest > ten_kappa then the buffer needs to be round up.
+        // rest can have an error of +/- 1 unit. This function accounts for the
+        // imprecision and returns false, if the rounding direction cannot be
+        // unambiguously determined.
+        //
+        // Precondition: rest < ten_kappa.
+        static bool RoundWeedCounted(
+            DtoaBuilder buffer,
+            ulong rest,
+            ulong ten_kappa,
+            ulong unit,
+            ref int kappa)
+        {
+            Debug.Assert(rest < ten_kappa);
+            // The following tests are done in a specific order to avoid overflows. They
+            // will work correctly with any uint64 values of rest < ten_kappa and unit.
+            //
+            // If the unit is too big, then we don't know which way to round. For example
+            // a unit of 50 means that the real number lies within rest +/- 50. If
+            // 10^kappa == 40 then there is no way to tell which way to round.
+            if (unit >= ten_kappa) return false;
+            // Even if unit is just half the size of 10^kappa we are already completely
+            // lost. (And after the previous test we know that the expression will not
+            // over/underflow.)
+            if (ten_kappa - unit <= unit) return false;
+            // If 2 * (rest + unit) <= 10^kappa we can safely round down.
+            if ((ten_kappa - rest > rest) && (ten_kappa - 2 * rest >= 2 * unit))
+            {
+                return true;
+            }
+
+            // If 2 * (rest - unit) >= 10^kappa, then we can safely round up.
+            if ((rest > unit) && (ten_kappa - (rest - unit) <= (rest - unit)))
+            {
+                // Increment the last digit recursively until we find a non '9' digit.
+                buffer._chars[buffer.Length - 1]++;
+                for (int i = buffer.Length - 1; i > 0; --i)
+                {
+                    if (buffer._chars[i] != '0' + 10) break;
+                    buffer._chars[i] = '0';
+                    buffer._chars[i - 1]++;
+                }
+
+                // If the first digit is now '0'+ 10 we had a buffer with all '9's. With the
+                // exception of the first digit all digits are now '0'. Simply switch the
+                // first digit to '1' and adjust the kappa. Example: "99" becomes "10" and
+                // the power (the kappa) is increased.
+                if (buffer._chars[0] == '0' + 10)
+                {
+                    buffer._chars[0] = '1';
+                    kappa += 1;
+                }
+
+                return true;
+            }
+
+            return false;
+        }
+
         private const int KTen4 = 10000;
         private const int KTen5 = 100000;
         private const int KTen6 = 1000000;
@@ -184,10 +246,8 @@ namespace Jint.Native.Number.Dtoa
         // If number_bits == 0 then 0^-1 is returned
         // The number of bits must be <= 32.
         // Precondition: (1 << number_bits) <= number < (1 << (number_bits + 1)).
-        private static long BiggestPowerTen(int number,
-            int numberBits)
+        private static void BiggestPowerTen(uint number, int numberBits, out uint power, out int exponent)
         {
-            int power, exponent;
             switch (numberBits)
             {
                 case 32:
@@ -304,7 +364,6 @@ namespace Jint.Native.Number.Dtoa
                     // UNREACHABLE();
                     break;
             }
-            return ((long) power << 32) | (0xffffffffL & exponent);
         }
 
         // Generates the digits of input number w.
@@ -353,8 +412,9 @@ namespace Jint.Native.Number.Dtoa
             in DiyFp low,
             in DiyFp w,
             in DiyFp high,
-            FastDtoaBuilder buffer,
-            int mk)
+            DtoaBuilder buffer,
+            int mk,
+            out int kappa)
         {
             // low, w and high are imprecise, but by less than one ulp (unit in the last
             // place).
@@ -367,7 +427,7 @@ namespace Jint.Native.Number.Dtoa
             // We will now start by generating the digits within the uncertain
             // interval. Later we will weed out representations that lie outside the safe
             // interval and thus _might_ lie outside the correct interval.
-            long unit = 1;
+            ulong unit = 1;
             var tooLow = new DiyFp(low.F - unit, low.E);
             var tooHigh = new DiyFp(high.F + unit, high.E);
             // too_low and too_high are guaranteed to lie outside the interval we want the
@@ -380,39 +440,44 @@ namespace Jint.Native.Number.Dtoa
             // such that:   too_low < buffer * 10^kappa < too_high
             // We use too_high for the digit_generation and stop as soon as possible.
             // If we stop early we effectively round down.
-            var one = new DiyFp(1L << -w.E, w.E);
+            var one = new DiyFp(((ulong) 1) << -w.E, w.E);
             // Division by one is a shift.
-            var integrals = (int) (tooHigh.F.UnsignedShift(-one.E) & 0xffffffffL);
+            var integrals = (uint) (tooHigh.F.UnsignedShift(-one.E) & 0xffffffffL);
             // Modulo by one is an and.
-            long fractionals = tooHigh.F & (one.F - 1);
-            long result = BiggestPowerTen(integrals, DiyFp.KSignificandSize - (-one.E));
-            var divider = (int) (result.UnsignedShift(32) & 0xffffffffL);
-            var dividerExponent = (int) (result & 0xffffffffL);
-            var kappa = dividerExponent + 1;
+            ulong fractionals = tooHigh.F & (one.F - 1);
+            BiggestPowerTen(
+                integrals,
+                DiyFp.KSignificandSize - (-one.E),
+                out var divider,
+                out var dividerExponent);
+
+            kappa = dividerExponent + 1;
             // Loop invariant: buffer = too_high / 10^kappa  (integer division)
             // The invariant holds for the first iteration: kappa has been initialized
             // with the divider exponent + 1. And the divider is the biggest power of ten
             // that is smaller than integrals.
             while (kappa > 0)
             {
-                int digit = integrals/divider;
+                int digit = (int) (integrals/divider);
                 buffer.Append((char) ('0' + digit));
                 integrals %= divider;
                 kappa--;
                 // Note that kappa now equals the exponent of the divider and that the
                 // invariant thus holds again.
-                long rest =
-                    ((long) integrals << -one.E) + fractionals;
+                ulong rest = ((ulong) integrals << -one.E) + fractionals;
                 // Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e())
                 // Reminder: unsafe_interval.e() == one.e()
                 if (rest < unsafeInterval.F)
                 {
                     // Rounding down (by not emitting the remaining digits) yields a number
                     // that lies within the unsafe interval.
-                    buffer.Point = buffer.End - mk + kappa;
-                    return RoundWeed(buffer, DiyFp.Minus(tooHigh, w).F,
-                        unsafeInterval.F, rest,
-                        (long) divider << -one.E, unit);
+                    return RoundWeed(
+                        buffer,
+                        DiyFp.Minus(tooHigh, w).F,
+                        unsafeInterval.F,
+                        rest,
+                        (ulong) divider << -one.E,
+                        unit);
                 }
                 divider /= 10;
             }
@@ -444,13 +509,114 @@ namespace Jint.Native.Number.Dtoa
                 kappa--;
                 if (fractionals < unsafeInterval.F)
                 {
-                    buffer.Point = buffer.End - mk + kappa;
-                    return RoundWeed(buffer, DiyFp.Minus(tooHigh, w).F*unit,
-                        unsafeInterval.F, fractionals, one.F, unit);
+                    return RoundWeed(
+                        buffer,
+                        DiyFp.Minus(tooHigh, w).F*unit,
+                        unsafeInterval.F,
+                        fractionals,
+                        one.F,
+                        unit);
                 }
             }
         }
 
+        // Generates (at most) requested_digits of input number w.
+        // w is a floating-point number (DiyFp), consisting of a significand and an
+        // exponent. Its exponent is bounded by kMinimalTargetExponent and
+        // kMaximalTargetExponent.
+        //       Hence -60 <= w.e() <= -32.
+        //
+        // Returns false if it fails, in which case the generated digits in the buffer
+        // should not be used.
+        // Preconditions:
+        //  * w is correct up to 1 ulp (unit in the last place). That
+        //    is, its error must be strictly less than a unit of its last digit.
+        //  * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
+        //
+        // Postconditions: returns false if procedure fails.
+        //   otherwise:
+        //     * buffer is not null-terminated, but length contains the number of
+        //       digits.
+        //     * the representation in buffer is the most precise representation of
+        //       requested_digits digits.
+        //     * buffer contains at most requested_digits digits of w. If there are less
+        //       than requested_digits digits then some trailing '0's have been removed.
+        //     * kappa is such that
+        //            w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2.
+        //
+        // Remark: This procedure takes into account the imprecision of its input
+        //   numbers. If the precision is not enough to guarantee all the postconditions
+        //   then false is returned. This usually happens rarely, but the failure-rate
+        //   increases with higher requested_digits.
+        static bool DigitGenCounted(
+            in DiyFp w,
+            int requested_digits,
+            DtoaBuilder buffer,
+            out int kappa)
+        {
+            Debug.Assert(MinimalTargetExponent <= w.E && w.E <= MaximalTargetExponent);
+
+            // w is assumed to have an error less than 1 unit. Whenever w is scaled we
+            // also scale its error.
+            ulong w_error = 1;
+            // We cut the input number into two parts: the integral digits and the
+            // fractional digits. We don't emit any decimal separator, but adapt kappa
+            // instead. Example: instead of writing "1.2" we put "12" into the buffer and
+            // increase kappa by 1.
+            DiyFp one = new DiyFp(((ulong) 1) << -w.E, w.E);
+            // Division by one is a shift.
+            uint integrals = (uint) (w.F >> -one.E);
+            // Modulo by one is an and.
+            ulong fractionals = w.F & (one.F - 1);
+            BiggestPowerTen(integrals, DiyFp.KSignificandSize - (-one.E), out var divisor, out var divisor_exponent);
+            kappa = divisor_exponent + 1;
+
+            // Loop invariant: buffer = w / 10^kappa  (integer division)
+            // The invariant holds for the first iteration: kappa has been initialized
+            // with the divisor exponent + 1. And the divisor is the biggest power of ten
+            // that is smaller than 'integrals'.
+            while (kappa > 0)
+            {
+                int digit = (int) (integrals / divisor);
+                buffer.Append((char) ('0' + digit));
+                requested_digits--;
+                integrals %= divisor;
+                kappa--;
+                // Note that kappa now equals the exponent of the divisor and that the
+                // invariant thus holds again.
+                if (requested_digits == 0) break;
+                divisor /= 10;
+            }
+
+            if (requested_digits == 0)
+            {
+                ulong rest = (((ulong) integrals) << -one.E) + fractionals;
+                return RoundWeedCounted(buffer, rest,(ulong) divisor << -one.E, w_error, ref kappa);
+            }
+
+          // The integrals have been generated. We are at the point of the decimal
+          // separator. In the following loop we simply multiply the remaining digits by
+          // 10 and divide by one. We just need to pay attention to multiply associated
+          // data (the 'unit'), too.
+          // Note that the multiplication by 10 does not overflow, because w.e >= -60
+          // and thus one.e >= -60.
+          Debug.Assert(one.E >= -60);
+          Debug.Assert(fractionals < one.F);
+
+          while (requested_digits > 0 && fractionals > w_error) {
+            fractionals *= 10;
+            w_error *= 10;
+            // Integer division by one.
+            int digit = (int) (fractionals >> -one.E);
+            buffer.Append((char) ('0' + digit));
+            requested_digits--;
+            fractionals &= one.F - 1;  // Modulo by one.
+            (kappa)--;
+          }
+          if (requested_digits != 0) return false;
+          return RoundWeedCounted(buffer, fractionals, one.F, w_error, ref kappa);
+        }
+
         // Provides a decimal representation of v.
         // Returns true if it succeeds, otherwise the result cannot be trusted.
         // There will be *length digits inside the buffer (not null-terminated).
@@ -462,9 +628,9 @@ namespace Jint.Native.Number.Dtoa
         // The last digit will be closest to the actual v. That is, even if several
         // digits might correctly yield 'v' when read again, the closest will be
         // computed.
-        private static bool Grisu3(double v, FastDtoaBuilder buffer)
+        private static bool Grisu3(double v, DtoaBuilder buffer, out int decimal_exponent)
         {
-            long bits = BitConverter.DoubleToInt64Bits(v);
+            ulong bits = (ulong) BitConverter.DoubleToInt64Bits(v);
             DiyFp w = DoubleHelper.AsNormalizedDiyFp(bits);
             // boundary_minus and boundary_plus are the boundaries between v and its
             // closest floating-point neighbors. Any number strictly between
@@ -476,9 +642,9 @@ namespace Jint.Native.Number.Dtoa
 
             Debug.Assert(boundaryPlus.E == w.E);
 
-            var result = CachedPowers.GetCachedPower(
-                w.E + DiyFp.KSignificandSize,
-                MinimalTargetExponent, MaximalTargetExponent);
+            var result = CachedPowers.GetCachedPowerForBinaryExponentRange(
+                MinimalTargetExponent - (w.E + DiyFp.KSignificandSize),
+                MaximalTargetExponent - (w.E + DiyFp.KSignificandSize));
 
             var mk = result.decimalExponent;
             var tenMk = result.cMk;
@@ -513,30 +679,90 @@ namespace Jint.Native.Number.Dtoa
             // integer than it will be updated. For instance if scaled_w == 1.23 then
             // the buffer will be filled with "123" und the decimal_exponent will be
             // decreased by 2.
-            return DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, buffer, mk);
+            int kappa;
+            var digitGen = DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, buffer, mk, out kappa);
+            decimal_exponent = -mk + kappa;
+            return digitGen;
         }
 
-        public static bool Dtoa(double v, FastDtoaBuilder buffer)
+
+        // The "counted" version of grisu3 (see above) only generates requested_digits
+        // number of digits. This version does not generate the shortest representation,
+        // and with enough requested digits 0.1 will at some point print as 0.9999999...
+        // Grisu3 is too imprecise for real halfway cases (1.5 will not work) and
+        // therefore the rounding strategy for halfway cases is irrelevant.
+        static bool Grisu3Counted(
+            double v,
+            int requested_digits,
+            DtoaBuilder buffer,
+            out int decimal_exponent)
         {
-            Debug.Assert(v > 0);
-            Debug.Assert(!Double.IsNaN(v));
-            Debug.Assert(!Double.IsInfinity(v));
+            ulong bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            DiyFp w = DoubleHelper.AsNormalizedDiyFp(bits);
 
-            return Grisu3(v, buffer);
+            var powerResult = CachedPowers.GetCachedPowerForBinaryExponentRange(
+                MinimalTargetExponent - (w.E + DiyFp.KSignificandSize),
+                MaximalTargetExponent - (w.E + DiyFp.KSignificandSize));
+
+            var mk = powerResult.decimalExponent;
+            var ten_mk = powerResult.cMk;
+            
+            Debug.Assert((MinimalTargetExponent <= w.E + ten_mk.E + DiyFp.KSignificandSize) && (MaximalTargetExponent >= w.E + ten_mk.E + DiyFp.KSignificandSize));
+            // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
+            // 64 bit significand and ten_mk is thus only precise up to 64 bits.
+
+            // The DiyFp::Times procedure rounds its result, and ten_mk is approximated
+            // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
+            // off by a small amount.
+            // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
+            // In other words: let f = scaled_w.f() and e = scaled_w.e(), then
+            //           (f-1) * 2^e < w*10^k < (f+1) * 2^e
+            DiyFp scaled_w = DiyFp.Times(w, ten_mk);
+
+            // We now have (double) (scaled_w * 10^-mk).
+            // DigitGen will generate the first requested_digits digits of scaled_w and
+            // return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It
+            // will not always be exactly the same since DigitGenCounted only produces a
+            // limited number of digits.)
+            bool result = DigitGenCounted(scaled_w, requested_digits, buffer, out var kappa);
+            decimal_exponent = -mk + kappa;
+            return result;
         }
 
-        public static string NumberToString(double v)
+        public static bool NumberToString(
+            double v,
+            DtoaMode mode,
+            int requested_digits,
+            out int decimal_point,
+            DtoaBuilder buffer)
         {
-            cachedBuffer.Value.Reset();
-            if (v < 0)
+            Debug.Assert(v > 0);
+            Debug.Assert(!double.IsNaN(v));
+            Debug.Assert(!double.IsInfinity(v));
+
+            bool result;
+            int decimal_exponent = 0;
+            switch (mode)
+            {
+                case DtoaMode.Shortest:
+                    result = Grisu3(v, buffer, out decimal_exponent);
+                    break;
+                case DtoaMode.Precision:
+                    result = Grisu3Counted(v, requested_digits, buffer, out decimal_exponent);
+                    break;
+                default:
+                    result = ExceptionHelper.ThrowArgumentOutOfRangeException<bool>();
+                    break;
+            }
+
+            if (result)
             {
-                cachedBuffer.Value.Append('-');
-                v = -v;
+                decimal_point = buffer.Length + decimal_exponent;
+                return true;
             }
 
-            var numberToString = Dtoa(v, cachedBuffer.Value);
-            var toString = numberToString ? cachedBuffer.Value.Format() : null;
-            return toString;
+            decimal_point = -1;
+            return false;
         }
     }
 }

+ 0 - 140
Jint/Native/Number/Dtoa/FastDtoaBuilder.cs

@@ -1,140 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-namespace Jint.Native.Number.Dtoa
-{
-    internal class FastDtoaBuilder
-    {
-
-        // allocate buffer for generated digits + extra notation + padding zeroes
-        private readonly char[] _chars = new char[FastDtoa.KFastDtoaMaximalLength + 8];
-        internal int End;
-        internal int Point;
-        private bool _formatted;
-
-        internal void Append(char c)
-        {
-            _chars[End++] = c;
-        }
-
-        internal void DecreaseLast()
-        {
-            _chars[End - 1]--;
-        }
-
-        public void Reset()
-        {
-            Point = 0;
-            End = 0;
-            _formatted = false;
-            System.Array.Clear(_chars, 0, _chars.Length);
-        }
-
-        public override string ToString()
-        {
-            return "[chars:" + new string(_chars, 0, End) + ", point:" + Point + "]";
-        }
-
-        public string Format()
-        {
-            if (!_formatted)
-            {
-                // check for minus sign
-                int firstDigit = _chars[0] == '-' ? 1 : 0;
-                int decPoint = Point - firstDigit;
-                if (decPoint < -5 || decPoint > 21)
-                {
-                    ToExponentialFormat(firstDigit, decPoint);
-                }
-                else
-                {
-                    ToFixedFormat(firstDigit, decPoint);
-                }
-                _formatted = true;
-            }
-            return new string(_chars, 0, End);
-
-        }
-
-        private void ToFixedFormat(int firstDigit, int decPoint)
-        {
-            if (Point < End)
-            {
-                // insert decimal point
-                if (decPoint > 0)
-                {
-                    // >= 1, split decimals and insert point
-                    System.Array.Copy(_chars, Point, _chars, Point + 1, End - Point);
-                    _chars[Point] = '.';
-                    End++;
-                }
-                else
-                {
-                    // < 1,
-                    int target = firstDigit + 2 - decPoint;
-                    System.Array.Copy(_chars, firstDigit, _chars, target, End - firstDigit);
-                    _chars[firstDigit] = '0';
-                    _chars[firstDigit + 1] = '.';
-                    if (decPoint < 0)
-                    {
-                        Fill(_chars, firstDigit + 2, target, '0');
-                    }
-                    End += 2 - decPoint;
-                }
-            }
-            else if (Point > End)
-            {
-                // large integer, add trailing zeroes
-                Fill(_chars, End, Point, '0');
-                End += Point - End;
-            }
-        }
-
-        private void ToExponentialFormat(int firstDigit, int decPoint)
-        {
-            if (End - firstDigit > 1)
-            {
-                // insert decimal point if more than one digit was produced
-                int dot = firstDigit + 1;
-                System.Array.Copy(_chars, dot, _chars, dot + 1, End - dot);
-                _chars[dot] = '.';
-                End++;
-            }
-            _chars[End++] = 'e';
-            char sign = '+';
-            int exp = decPoint - 1;
-            if (exp < 0)
-            {
-                sign = '-';
-                exp = -exp;
-            }
-            _chars[End++] = sign;
-
-            int charPos = exp > 99 ? End + 2 : exp > 9 ? End + 1 : End;
-            End = charPos + 1;
-
-            // code below is needed because Integer.getChars() is not public
-            for (;;)
-            {
-                int r = exp%10;
-                _chars[charPos--] = Digits[r];
-                exp = exp/10;
-                if (exp == 0) break;
-            }
-        }
-
-        private static readonly char[] Digits =
-        {
-            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
-        };
-
-        private static void Fill(char[] array, int fromIndex, int toIndex, char val)
-        {
-            for (int i = fromIndex; i < toIndex; i++)
-            {
-                array[i] = val;
-            }
-        }
-    }
-}

+ 6 - 0
Jint/Native/Number/Dtoa/NumberExtensions.cs

@@ -8,6 +8,12 @@ namespace Jint.Native.Number.Dtoa
         public static long UnsignedShift(this long l, int shift)
         {
             return (long) ((ulong) l >> shift);
+        }        
+        
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ulong UnsignedShift(this ulong l, int shift)
+        {
+            return l >> shift;
         }
     }
 }

+ 74 - 1
Jint/Native/Number/NumberConstructor.cs

@@ -2,11 +2,15 @@
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
 
 namespace Jint.Native.Number
 {
     public sealed class NumberConstructor : FunctionInstance, IConstructor
     {
+        private const long MinSafeInteger = -9007199254740991;
+        internal const long MaxSafeInteger = 9007199254740991;
+
         public NumberConstructor(Engine engine)
             : base(engine, "Number", null, null, false)
         {
@@ -37,7 +41,76 @@ namespace Jint.Native.Number
             SetOwnProperty("NaN", new PropertyDescriptor(double.NaN, PropertyFlag.AllForbidden));
             SetOwnProperty("NEGATIVE_INFINITY", new PropertyDescriptor(double.NegativeInfinity, PropertyFlag.AllForbidden));
             SetOwnProperty("POSITIVE_INFINITY", new PropertyDescriptor(double.PositiveInfinity, PropertyFlag.AllForbidden));
-            SetOwnProperty("EPSILON", new PropertyDescriptor(double.Epsilon, PropertyFlag.AllForbidden));
+            SetOwnProperty("EPSILON", new PropertyDescriptor(JsNumber.JavaScriptEpsilon, PropertyFlag.AllForbidden));
+            SetOwnProperty("MIN_SAFE_INTEGER", new PropertyDescriptor(MinSafeInteger, PropertyFlag.AllForbidden));
+            SetOwnProperty("MAX_SAFE_INTEGER", new PropertyDescriptor(MaxSafeInteger, PropertyFlag.AllForbidden));
+
+            FastAddProperty("isFinite", new ClrFunctionInstance(Engine, "isFinite", IsFinite, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("isInteger", new ClrFunctionInstance(Engine, "isInteger", IsInteger, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("isNaN", new ClrFunctionInstance(Engine, "isNaN", IsNaN, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("isSafeInteger", new ClrFunctionInstance(Engine, "isSafeInteger", IsSafeInteger, 1, PropertyFlag.Configurable), true, false, true);
+
+            FastAddProperty("parseFloat", new ClrFunctionInstance(Engine, "parseFloat", _engine.Global.ParseFloat, 0, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("parseInt", new ClrFunctionInstance(Engine, "parseInt", _engine.Global.ParseInt, 0, PropertyFlag.Configurable), true, false, true);
+        }
+
+        private JsValue IsFinite(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            return double.IsInfinity(num._value) || double.IsNaN(num._value) ? JsBoolean.False : JsBoolean.True;
+        }
+
+        private JsValue IsInteger(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            if (double.IsInfinity(num._value) || double.IsNaN(num._value))
+            {
+                return JsBoolean.False;
+            }
+
+            var integer = TypeConverter.ToInteger(num);
+
+            return integer == num._value;
+        }
+
+        private JsValue IsNaN(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            return double.IsNaN(num._value);
+        }
+
+        private JsValue IsSafeInteger(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            if (double.IsInfinity(num._value) || double.IsNaN(num._value))
+            {
+                return JsBoolean.False;
+            }
+
+            var integer = TypeConverter.ToInteger(num);
+
+            if (integer != num._value)
+            {
+                return false;
+            }
+
+            return System.Math.Abs(integer) <= MaxSafeInteger;
         }
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)

+ 260 - 86
Jint/Native/Number/NumberPrototype.cs

@@ -1,8 +1,10 @@
-using System;
+using System.Diagnostics;
 using System.Globalization;
 using System.Text;
 using Jint.Native.Number.Dtoa;
+using Jint.Pooling;
 using Jint.Runtime;
+using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Number
@@ -12,8 +14,6 @@ namespace Jint.Native.Number
     /// </summary>
     public sealed class NumberPrototype : NumberInstance
     {
-        private static readonly char[] _numberSeparators = {'.', 'e'};
-
         private NumberPrototype(Engine engine)
             : base(engine)
         {
@@ -35,12 +35,12 @@ namespace Jint.Native.Number
 
         public void Configure()
         {
-            FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToNumberString), true, false, true);
-            FastAddProperty("toLocaleString", new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString), true, false, true);
-            FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf), true, false, true);
-            FastAddProperty("toFixed", new ClrFunctionInstance(Engine, "toFixed", ToFixed, 1), true, false, true);
-            FastAddProperty("toExponential", new ClrFunctionInstance(Engine, "toExponential", ToExponential), true, false, true);
-            FastAddProperty("toPrecision", new ClrFunctionInstance(Engine, "toPrecision", ToPrecision), true, false, true);
+            FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToNumberString, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toLocaleString", new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toFixed", new ClrFunctionInstance(Engine, "toFixed", ToFixed, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toExponential", new ClrFunctionInstance(Engine, "toExponential", ToExponential, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toPrecision", new ClrFunctionInstance(Engine, "toPrecision", ToPrecision, 1, PropertyFlag.Configurable), true, false, true);
         }
 
         private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
@@ -100,9 +100,9 @@ namespace Jint.Native.Number
         private JsValue ToFixed(JsValue thisObj, JsValue[] arguments)
         {
             var f = (int)TypeConverter.ToInteger(arguments.At(0, 0));
-            if (f < 0 || f > 20)
+            if (f < 0 || f > 100)
             {
-                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 20");
+                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 100");
             }
 
             var x = TypeConverter.ToNumber(thisObj);
@@ -117,58 +117,213 @@ namespace Jint.Native.Number
                 return ToNumberString(x);
             }
 
+            // handle non-decimal with greater precision
+            if (System.Math.Abs(x - (long) x) < JsNumber.DoubleIsIntegerTolerance)
+            {
+                return ((long) x).ToString("f" + f, CultureInfo.InvariantCulture);
+            }
+
             return x.ToString("f" + f, CultureInfo.InvariantCulture);
         }
 
+        /// <summary>
+        /// https://www.ecma-international.org/ecma-262/6.0/#sec-number.prototype.toexponential
+        /// </summary>
         private JsValue ToExponential(JsValue thisObj, JsValue[] arguments)
         {
-            var f = (int)TypeConverter.ToInteger(arguments.At(0, 16));
-            if (f < 0 || f > 20)
+            if (!thisObj.IsNumber() && ReferenceEquals(thisObj.TryCast<NumberInstance>(), null))
             {
-                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 20");
+                ExceptionHelper.ThrowTypeError(Engine);
             }
 
             var x = TypeConverter.ToNumber(thisObj);
+            var fractionDigits = arguments.At(0);
+            if (fractionDigits.IsUndefined())
+            {
+                fractionDigits = JsNumber.PositiveZero;
+            }
+
+            var f = (int) TypeConverter.ToInteger(fractionDigits);
 
             if (double.IsNaN(x))
             {
                 return "NaN";
             }
 
-            string format = string.Concat("#.", new string('0', f), "e+0");
-            return x.ToString(format, CultureInfo.InvariantCulture);
+            if (double.IsInfinity(x))
+            {
+                return thisObj.ToString();
+            }
+
+            if (f < 0 || f > 100)
+            {
+                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 100");
+            }
+
+            if (arguments.At(0).IsUndefined())
+            {
+                f = -1;
+            }
+
+            bool negative = false;
+            if (x < 0)
+            {
+                x = -x;
+                negative = true;
+            }
+
+            int decimalPoint;
+            var dtoaBuilder = new DtoaBuilder();
+            if (f == -1)
+            {
+                DtoaNumberFormatter.DoubleToAscii(
+                    dtoaBuilder,
+                    x,
+                    DtoaMode.Shortest,
+                    requested_digits: 0,
+                    out _,
+                    out decimalPoint);
+                f = dtoaBuilder.Length - 1;
+            }
+            else
+            {
+                DtoaNumberFormatter.DoubleToAscii(
+                    dtoaBuilder,
+                    x,
+                    DtoaMode.Precision,
+                    requested_digits: f + 1,
+                    out _,
+                    out decimalPoint);
+            }
+
+            Debug.Assert(dtoaBuilder.Length > 0);
+            Debug.Assert(dtoaBuilder.Length <= f + 1);
+
+            int exponent = decimalPoint - 1;
+            var result = CreateExponentialRepresentation(dtoaBuilder, exponent, negative, f+1);
+            return result;
         }
 
         private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments)
         {
+            if (!thisObj.IsNumber() && ReferenceEquals(thisObj.TryCast<NumberInstance>(), null))
+            {
+                ExceptionHelper.ThrowTypeError(Engine);
+            }
+
             var x = TypeConverter.ToNumber(thisObj);
+            var precisionArgument = arguments.At(0);
 
-            if (arguments.At(0).IsUndefined())
+            if (precisionArgument.IsUndefined())
             {
                 return TypeConverter.ToString(x);
             }
 
-            var p = TypeConverter.ToInteger(arguments.At(0));
+            var p = (int) TypeConverter.ToInteger(precisionArgument);
 
-            if (double.IsInfinity(x) || double.IsNaN(x))
+            if (double.IsNaN(x))
             {
-                return TypeConverter.ToString(x);
+                return "NaN";
+            }
+
+            if (double.IsInfinity(x))
+            {
+                return thisObj.ToString();
+            }
+
+            if (p < 1 || p > 100)
+            {
+                ExceptionHelper.ThrowRangeError(_engine, "precision must be between 1 and 100");
             }
 
-            if (p < 1 || p > 21)
+            var dtoaBuilder = new DtoaBuilder();
+            DtoaNumberFormatter.DoubleToAscii(
+                dtoaBuilder,
+                x,
+                DtoaMode.Precision,
+                p,
+                out var negative,
+                out var decimalPoint);
+
+
+            int exponent = decimalPoint - 1;
+            if (exponent < -6 || exponent >= p)
             {
-                ExceptionHelper.ThrowRangeError(_engine, "precision must be between 1 and 21");
+                return CreateExponentialRepresentation(dtoaBuilder, exponent, negative, p);
             }
 
-            // Get the number of decimals
-            string str = x.ToString("e23", CultureInfo.InvariantCulture);
-            int decimals = str.IndexOfAny(_numberSeparators);
-            decimals = decimals == -1 ? str.Length : decimals;
+            using (var builder = StringBuilderPool.Rent())
+            {
+                // Use fixed notation.
+                if (negative)
+                {
+                    builder.Builder.Append('-');
+                }
 
-            p -= decimals;
-            p = p < 1 ? 1 : p;
+                if (decimalPoint <= 0)
+                {
+                    builder.Builder.Append("0.");
+                    builder.Builder.Append('0', -decimalPoint);
+                    builder.Builder.Append(dtoaBuilder._chars, 0, dtoaBuilder.Length);
+                    builder.Builder.Append('0', p - dtoaBuilder.Length);
+                }
+                else
+                {
+                    int m = System.Math.Min(dtoaBuilder.Length, decimalPoint);
+                    builder.Builder.Append(dtoaBuilder._chars, 0, m);
+                    builder.Builder.Append('0', System.Math.Max(0, decimalPoint - dtoaBuilder.Length));
+                    if (decimalPoint < p)
+                    {
+                        builder.Builder.Append('.');
+                        var extra = negative ? 2 : 1;
+                        if (dtoaBuilder.Length > decimalPoint)
+                        {
+                            int len = dtoaBuilder.Length - decimalPoint;
+                            int n = System.Math.Min(len, p - (builder.Builder.Length - extra));
+                            builder.Builder.Append(dtoaBuilder._chars, decimalPoint, n);
+                        }
 
-            return x.ToString("f" + p, CultureInfo.InvariantCulture);
+                        builder.Builder.Append('0', System.Math.Max(0, extra + (p - builder.Builder.Length)));
+                    }
+                }
+
+                return builder.ToString();
+            }
+        }
+
+        private string CreateExponentialRepresentation(
+            DtoaBuilder buffer,
+            int exponent,
+            bool negative,
+            int significantDigits)
+        {
+            bool negativeExponent = false;
+            if (exponent < 0)
+            {
+                negativeExponent = true;
+                exponent = -exponent;
+            }
+
+            using (var builder = StringBuilderPool.Rent())
+            {
+                if (negative)
+                {
+                    builder.Builder.Append('-');
+                }
+                builder.Builder.Append(buffer._chars[0]);
+                if (significantDigits != 1)
+                {
+                    builder.Builder.Append('.');
+                    builder.Builder.Append(buffer._chars, 1, buffer.Length - 1);
+                    int length = buffer.Length;
+                    builder.Builder.Append('0', significantDigits - length);
+                }
+
+                builder.Builder.Append('e');
+                builder.Builder.Append(negativeExponent ? '-' : '+');
+                builder.Builder.Append(exponent);
+                return builder.ToString();
+            }
         }
 
         private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments)
@@ -226,7 +381,7 @@ namespace Jint.Native.Number
             return result;
         }
 
-        public static string ToBase(long n, int radix)
+        public string ToBase(long n, int radix)
         {
             const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
             if (n == 0)
@@ -234,18 +389,20 @@ namespace Jint.Native.Number
                 return "0";
             }
 
-            var result = new StringBuilder();
-            while (n > 0)
+            using (var result = StringBuilderPool.Rent())
             {
-                var digit = (int)(n % radix);
-                n = n / radix;
-                result.Insert(0, digits[digit]);
-            }
+                while (n > 0)
+                {
+                    var digit = (int) (n % radix);
+                    n = n / radix;
+                    result.Builder.Insert(0, digits[digit]);
+                }
 
-            return result.ToString();
+                return result.ToString();
+            }
         }
 
-        public static string ToFractionBase(double n, int radix)
+        public string ToFractionBase(double n, int radix)
         {
             // based on the repeated multiplication method
             // http://www.mathpath.org/concepts/Num/frac.htm
@@ -256,20 +413,33 @@ namespace Jint.Native.Number
                 return "0";
             }
 
-            var result = new StringBuilder();
-            while (n > 0 && result.Length < 50) // arbitrary limit
+            using (var result = StringBuilderPool.Rent())
             {
-                var c = n*radix;
-                var d = (int) c;
-                n = c - d;
+                while (n > 0 && result.Length < 50) // arbitrary limit
+                {
+                    var c = n*radix;
+                    var d = (int) c;
+                    n = c - d;
+
+                    result.Builder.Append(digits[d]);
+                }
 
-                result.Append(digits[d]);
+                return result.ToString();
             }
+        }
 
-            return result.ToString();
+        private string ToNumberString(double m)
+        {
+            using (var stringBuilder = StringBuilderPool.Rent())
+            {
+                return NumberToString(m, new DtoaBuilder(), stringBuilder.Builder);
+            }
         }
 
-        public static string ToNumberString(double m)
+        internal static string NumberToString(
+            double m,
+            DtoaBuilder builder,
+            StringBuilder stringBuilder)
         {
             if (double.IsNaN(m))
             {
@@ -281,67 +451,71 @@ namespace Jint.Native.Number
                 return "0";
             }
 
-            if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
+            if (double.IsPositiveInfinity(m))
             {
                 return "Infinity";
             }
 
-            if (m < 0)
+            if (double.IsNegativeInfinity(m))
             {
-                return "-" + ToNumberString(-m);
+                return "-Infinity";
             }
 
-            // V8 FastDtoa can't convert all numbers, so try it first but
-            // fall back to old DToA in case it fails
-            var result = FastDtoa.NumberToString(m);
-            if (result != null)
-            {
-                return result;
-            }
+            DtoaNumberFormatter.DoubleToAscii(
+                builder,
+                m,
+                DtoaMode.Shortest,
+                0,
+                out var negative,
+                out var decimal_point);
 
-            // s is all digits (significand)
-            // k number of digits of s
-            // n total of digits in fraction s*10^n-k=m
-            // 123.4 s=1234, k=4, n=3
-            // 1234000 s = 1234, k=4, n=7
-            string s = null;
-            var rFormat = m.ToString("r", CultureInfo.InvariantCulture);
-            if (rFormat.IndexOf("e", StringComparison.OrdinalIgnoreCase) == -1)
+            if (negative)
             {
-                s = rFormat.Replace(".", "").TrimStart('0').TrimEnd('0');
+                stringBuilder.Append('-');
             }
 
-            const string format = "0.00000000000000000e0";
-            var parts = m.ToString(format, CultureInfo.InvariantCulture).Split('e');
-            if (s == null)
+            if (builder.Length <= decimal_point && decimal_point <= 21)
             {
-                s = parts[0].TrimEnd('0').Replace(".", "");
+                // ECMA-262 section 9.8.1 step 6.
+                stringBuilder.Append(builder._chars, 0, builder.Length);
+                stringBuilder.Append('0', decimal_point - builder.Length);
             }
-
-            var n = int.Parse(parts[1]) + 1;
-            var k = s.Length;
-
-            if (k <= n && n <= 21)
+            else if (0 < decimal_point && decimal_point <= 21)
             {
-                return s + new string('0', n - k);
+                // ECMA-262 section 9.8.1 step 7.
+                stringBuilder.Append(builder._chars, 0, decimal_point);
+                stringBuilder.Append('.');
+                stringBuilder.Append(builder._chars, decimal_point, builder.Length - decimal_point);
             }
-
-            if (0 < n && n <= 21)
+            else if (decimal_point <= 0 && decimal_point > -6)
             {
-                return s.Substring(0, n) + '.' + s.Substring(n);
+                // ECMA-262 section 9.8.1 step 8.
+                stringBuilder.Append("0.");
+                stringBuilder.Append('0', -decimal_point);
+                stringBuilder.Append(builder._chars, 0, builder.Length);
             }
-
-            if (-6 < n && n <= 0)
+            else
             {
-                return "0." + new string('0', -n) + s;
-            }
+                // ECMA-262 section 9.8.1 step 9 and 10 combined.
+                stringBuilder.Append(builder._chars[0]);
+                if (builder.Length != 1)
+                {
+                    stringBuilder.Append('.');
+                    stringBuilder.Append(builder._chars, 1, builder.Length - 1);
+                }
 
-            if (k == 1)
-            {
-                return s + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
+                stringBuilder.Append('e');
+                stringBuilder.Append((decimal_point >= 0) ? '+' : '-');
+                int exponent = decimal_point - 1;
+                if (exponent < 0)
+                {
+                    exponent = -exponent;
+                }
+
+                stringBuilder.Append(exponent);
             }
 
-            return s.Substring(0, 1) + "." + s.Substring(1) + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
+            return stringBuilder.ToString();
         }
     }
 }

+ 2 - 2
Jint/Native/Object/ObjectConstructor.cs

@@ -109,8 +109,8 @@ namespace Jint.Native.Object
             {
                 Extensible = true,
                 Prototype = Engine.Object.PrototypeObject,
-                _properties =  propertyCount > 0
-                    ? new StringDictionarySlim<PropertyDescriptor>(System.Math.Max(2, propertyCount))
+                _properties =  propertyCount > 1
+                    ? new StringDictionarySlim<PropertyDescriptor>(propertyCount)
                     : null
             };
 

+ 8 - 7
Jint/Native/Object/ObjectInstance.cs

@@ -15,6 +15,7 @@ using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
+using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Native.Object
 {
@@ -25,7 +26,7 @@ namespace Jint.Native.Object
         internal StringDictionarySlim<PropertyDescriptor> _properties;
 
         private readonly string _class;
-        protected readonly Engine _engine;
+        protected internal readonly Engine _engine;
 
         public ObjectInstance(Engine engine) : this(engine, "Object")
         {
@@ -568,9 +569,9 @@ namespace Jint.Native.Object
                 current.Configurable == desc.Configurable && current.ConfigurableSet == desc.ConfigurableSet &&
                 current.Writable == desc.Writable && current.WritableSet == desc.WritableSet &&
                 current.Enumerable == desc.Enumerable && current.EnumerableSet == desc.EnumerableSet &&
-                ((ReferenceEquals(currentGet, null) && ReferenceEquals(descGet, null)) || (!ReferenceEquals(currentGet, null) && !ReferenceEquals(descGet, null) && ExpressionInterpreter.SameValue(currentGet, descGet))) &&
-                ((ReferenceEquals(currentSet, null) && ReferenceEquals(descSet, null)) || (!ReferenceEquals(currentSet, null) && !ReferenceEquals(descSet, null) && ExpressionInterpreter.SameValue(currentSet, descSet))) &&
-                ((ReferenceEquals(currentValue, null) && ReferenceEquals(descValue, null)) || (!ReferenceEquals(currentValue, null) && !ReferenceEquals(descValue, null) && ExpressionInterpreter.StrictlyEqual(currentValue, descValue)))
+                ((ReferenceEquals(currentGet, null) && ReferenceEquals(descGet, null)) || (!ReferenceEquals(currentGet, null) && !ReferenceEquals(descGet, null) && JintExpression.SameValue(currentGet, descGet))) &&
+                ((ReferenceEquals(currentSet, null) && ReferenceEquals(descSet, null)) || (!ReferenceEquals(currentSet, null) && !ReferenceEquals(descSet, null) && JintExpression.SameValue(currentSet, descSet))) &&
+                ((ReferenceEquals(currentValue, null) && ReferenceEquals(descValue, null)) || (!ReferenceEquals(currentValue, null) && !ReferenceEquals(descValue, null) && JintBinaryExpression.StrictlyEqual(currentValue, descValue)))
             )
             {
                 return true;
@@ -647,7 +648,7 @@ namespace Jint.Native.Object
 
                         if (!current.Writable)
                         {
-                            if (!ReferenceEquals(descValue, null) && !ExpressionInterpreter.SameValue(descValue, currentValue))
+                            if (!ReferenceEquals(descValue, null) && !JintExpression.SameValue(descValue, currentValue))
                             {
                                 if (throwOnError)
                                 {
@@ -663,9 +664,9 @@ namespace Jint.Native.Object
                 {
                     if (!current.Configurable)
                     {
-                        if ((!ReferenceEquals(descSet, null) && !ExpressionInterpreter.SameValue(descSet, currentSet ?? Undefined))
+                        if ((!ReferenceEquals(descSet, null) && !JintExpression.SameValue(descSet, currentSet ?? Undefined))
                             ||
-                            (!ReferenceEquals(descGet, null) && !ExpressionInterpreter.SameValue(descGet, currentGet ?? Undefined)))
+                            (!ReferenceEquals(descGet, null) && !JintExpression.SameValue(descGet, currentGet ?? Undefined)))
                         {
                             if (throwOnError)
                             {

+ 9 - 4
Jint/Native/RegExp/RegExpPrototype.cs

@@ -40,11 +40,16 @@ namespace Jint.Native.RegExp
         {
             var regExp = thisObj.TryCast<RegExpInstance>();
 
-            return "/" + regExp.Source + "/"
-                + (regExp.Flags.Contains("g") ? "g" : "")
-                + (regExp.Flags.Contains("i") ? "i" : "")
-                + (regExp.Flags.Contains("m") ? "m" : "")
+            string res = "/" + regExp.Source + "/";
+            if (regExp.Flags != null)
+            {
+                res += (regExp.Flags.Contains("g") ? "g" : "")
+                    + (regExp.Flags.Contains("i") ? "i" : "")
+                    + (regExp.Flags.Contains("m") ? "m" : "")
                 ;
+            }
+
+            return res;
         }
 
         private JsValue Test(JsValue thisObj, JsValue[] arguments)

+ 38 - 0
Jint/Native/String/StringConstructor.cs

@@ -1,6 +1,8 @@
 using System.Collections.Generic;
+using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.Object;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
@@ -35,6 +37,7 @@ namespace Jint.Native.String
         {
             SetOwnProperty("fromCharCode", new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromCharCode", FromCharCode, 1), PropertyFlag.NonEnumerable));
             SetOwnProperty("fromCodePoint", new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromCodePoint", FromCodePoint, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
+            SetOwnProperty("raw", new PropertyDescriptor(new ClrFunctionInstance(Engine, "raw", Raw, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
         }
 
         private static JsValue FromCharCode(JsValue thisObj, JsValue[] arguments)
@@ -88,6 +91,41 @@ namespace Jint.Native.String
             return result + FromCharCode(null, codeUnits.ToArray());
         }
 
+        /// <summary>
+        /// https://www.ecma-international.org/ecma-262/6.0/#sec-string.raw
+        /// </summary>
+        private JsValue Raw(JsValue thisObj, JsValue[] arguments)
+        {
+            var cooked = TypeConverter.ToObject(_engine, arguments.At(0));
+            var raw = TypeConverter.ToObject(_engine, cooked.Get("raw"));
+
+            var operations = ArrayPrototype.ArrayOperations.For(raw);
+            var length = operations.GetLength();
+
+            if (length <= 0)
+            {
+                return JsString.Empty;
+            }
+
+            using (var result = StringBuilderPool.Rent())
+            {
+                for (var i = 0; i < length; i++)
+                {
+                    if (i > 0)
+                    {
+                        if (i < arguments.Length && !arguments[i].IsUndefined())
+                        {
+                            result.Builder.Append(TypeConverter.ToString(arguments[i]));
+                        }
+                    }
+
+                    result.Builder.Append(TypeConverter.ToString(operations.Get((ulong) i)));
+                }
+
+                return result.ToString();
+            }
+        }
+
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
             if (arguments.Length == 0)

+ 1 - 16
Jint/Native/String/StringExecutionContext.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
-using System.Text;
 using System.Threading;
 
 namespace Jint.Native.String
@@ -11,7 +10,6 @@ namespace Jint.Native.String
     {
         private static readonly ThreadLocal<StringExecutionContext> _executionContext = new ThreadLocal<StringExecutionContext>(() => new StringExecutionContext());
 
-        private StringBuilder _stringBuilder;
         private List<string> _splitSegmentList;
         private string[] _splitArray1;
 
@@ -19,21 +17,8 @@ namespace Jint.Native.String
         {
         }
 
-        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 static StringExecutionContext Current => _executionContext.Value;

+ 2 - 2
Jint/Native/String/StringInstance.cs

@@ -52,12 +52,12 @@ namespace Jint.Native.String
                 return desc;
             }
 
-            var integer = TypeConverter.ToInteger(propertyName);
-            if (integer == 0 && propertyName != "0" || propertyName != System.Math.Abs(integer).ToString())
+            if (!TypeConverter.CanBeIndex(propertyName))
             {
                 return PropertyDescriptor.Undefined;
             }
 
+            var integer = TypeConverter.ToInteger(propertyName);
             var str = PrimitiveValue;
             var dIndex = integer;
             if (!IsInt(dIndex))

+ 66 - 58
Jint/Native/String/StringPrototype.cs

@@ -7,6 +7,7 @@ using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Native.RegExp;
 using Jint.Native.Symbol;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
@@ -262,7 +263,7 @@ namespace Jint.Native.String
             return s.Substring(from, length);
         }
 
-        private static JsValue Substr(JsValue thisObj, JsValue[] arguments)
+        private JsValue Substr(JsValue thisObj, JsValue[] arguments)
         {
             var s = TypeConverter.ToString(thisObj);
             var start = TypeConverter.ToInteger(arguments.At(0));
@@ -443,8 +444,8 @@ namespace Jint.Native.String
             }
 
             var len = s.Length;
-            var intStart = (int)TypeConverter.ToInteger(start);
-            var intEnd = arguments.At(1).IsUndefined() ? len : (int)TypeConverter.ToInteger(end);
+            var intStart = (int) start;
+            var intEnd = arguments.At(1).IsUndefined() ? len : (int) TypeConverter.ToInteger(end);
             var from = intStart < 0 ? System.Math.Max(len + intStart, 0) : System.Math.Min(intStart, len);
             var to = intEnd < 0 ? System.Math.Max(len + intEnd, 0) : System.Math.Min(intEnd, len);
             var span = System.Math.Max(to - from, 0);
@@ -520,62 +521,64 @@ 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 = StringExecutionContext.Current.GetStringBuilder(0);
-                    replacementBuilder.Clear();
-                    for (int i = 0; i < replaceString.Length; i++)
+                    using (var replacementBuilder = StringBuilderPool.Rent())
                     {
-                        char c = replaceString[i];
-                        if (c == '$' && i < replaceString.Length - 1)
+                        for (int i = 0; i < replaceString.Length; i++)
                         {
-                            c = replaceString[++i];
-                            if (c == '$')
-                                replacementBuilder.Append('$');
-                            else if (c == '&')
-                                replacementBuilder.Append(matchValue);
-                            else if (c == '`')
-                                replacementBuilder.Append(thisString.Substring(0, matchIndex));
-                            else if (c == '\'')
-                                replacementBuilder.Append(thisString.Substring(matchIndex + matchValue.Length));
-                            else if (c >= '0' && c <= '9')
+                            char c = replaceString[i];
+                            if (c == '$' && i < replaceString.Length - 1)
                             {
-                                int matchNumber1 = c - '0';
-
-                                // The match number can be one or two digits long.
-                                int matchNumber2 = 0;
-                                if (i < replaceString.Length - 1 && replaceString[i + 1] >= '0' && replaceString[i + 1] <= '9')
-                                    matchNumber2 = matchNumber1 * 10 + (replaceString[i + 1] - '0');
-
-                                // Try the two digit capture first.
-                                if (matchNumber2 > 0 && matchNumber2 < args.Length - 2)
-                                {
-                                    // Two digit capture replacement.
-                                    replacementBuilder.Append(TypeConverter.ToString(args[matchNumber2]));
-                                    i++;
-                                }
-                                else if (matchNumber1 > 0 && matchNumber1 < args.Length - 2)
+                                c = replaceString[++i];
+                                if (c == '$')
+                                    replacementBuilder.Builder.Append('$');
+                                else if (c == '&')
+                                    replacementBuilder.Builder.Append(matchValue);
+                                else if (c == '`')
+                                    replacementBuilder.Builder.Append(thisString.Substring(0, matchIndex));
+                                else if (c == '\'')
+                                    replacementBuilder.Builder.Append(thisString.Substring(matchIndex + matchValue.Length));
+                                else if (c >= '0' && c <= '9')
                                 {
-                                    // Single digit capture replacement.
-                                    replacementBuilder.Append(TypeConverter.ToString(args[matchNumber1]));
+                                    int matchNumber1 = c - '0';
+
+                                    // The match number can be one or two digits long.
+                                    int matchNumber2 = 0;
+                                    if (i < replaceString.Length - 1 && replaceString[i + 1] >= '0' && replaceString[i + 1] <= '9')
+                                        matchNumber2 = matchNumber1 * 10 + (replaceString[i + 1] - '0');
+
+                                    // Try the two digit capture first.
+                                    if (matchNumber2 > 0 && matchNumber2 < args.Length - 2)
+                                    {
+                                        // Two digit capture replacement.
+                                        replacementBuilder.Builder.Append(TypeConverter.ToString(args[matchNumber2]));
+                                        i++;
+                                    }
+                                    else if (matchNumber1 > 0 && matchNumber1 < args.Length - 2)
+                                    {
+                                        // Single digit capture replacement.
+                                        replacementBuilder.Builder.Append(TypeConverter.ToString(args[matchNumber1]));
+                                    }
+                                    else
+                                    {
+                                        // Capture does not exist.
+                                        replacementBuilder.Builder.Append('$');
+                                        i--;
+                                    }
                                 }
                                 else
                                 {
-                                    // Capture does not exist.
-                                    replacementBuilder.Append('$');
-                                    i--;
+                                    // Unknown replacement pattern.
+                                    replacementBuilder.Builder.Append('$');
+                                    replacementBuilder.Builder.Append(c);
                                 }
                             }
                             else
-                            {
-                                // Unknown replacement pattern.
-                                replacementBuilder.Append('$');
-                                replacementBuilder.Append(c);
-                            }
+                                replacementBuilder.Builder.Append(c);
                         }
-                        else
-                            replacementBuilder.Append(c);
+
+                        return replacementBuilder.ToString();
                     }
 
-                    return replacementBuilder.ToString();
                 });
             }
 
@@ -638,12 +641,14 @@ namespace Jint.Native.String
                 _engine._jsValueArrayPool.ReturnArray(args);
 
                 // Replace only the first match.
-                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);
-                return result.ToString();
+                using (var result = StringBuilderPool.Rent())
+                {
+                    result.Builder.EnsureCapacity(thisString.Length + (substr.Length - substr.Length));
+                    result.Builder.Append(thisString, 0, start);
+                    result.Builder.Append(replaceString);
+                    result.Builder.Append(thisString, end, thisString.Length - end);
+                    return result.ToString();
+                }
             }
         }
 
@@ -825,7 +830,7 @@ namespace Jint.Native.String
             var position = (int)TypeConverter.ToInteger(pos);
             if (position < 0 || position >= s.Length)
             {
-                return double.NaN;
+                return JsNumber.DoubleNaN;
             }
             return (double) s[position];
         }
@@ -1125,13 +1130,16 @@ namespace Jint.Native.String
                 return new string(str[0], n);
             }
 
-            var sb = new StringBuilder(n * str.Length);
-            for (var i = 0; i < n; ++i)
+            using (var sb = StringBuilderPool.Rent())
             {
-                sb.Append(str);
-            }
+                sb.Builder.EnsureCapacity(n * str.Length);
+                for (var i = 0; i < n; ++i)
+                {
+                    sb.Builder.Append(str);
+                }
 
-            return sb.ToString();
+                return sb.ToString();
+            }
         }
     }
 }

+ 24 - 0
Jint/Options.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.Linq;
 using System.Reflection;
 using Jint.Native;
+using Jint.Native.Object;
 using Jint.Runtime.Interop;
 
 namespace Jint
@@ -14,7 +15,9 @@ namespace Jint
         private bool _strict;
         private bool _allowDebuggerStatement;
         private bool _allowClr;
+        private bool _allowClrWrite = true;
         private readonly List<IObjectConverter> _objectConverters = new List<IObjectConverter>();
+        private Func<object, ObjectInstance> _wrapObjectHandler;
         private int _maxStatements;
         private long _memoryLimit;
         private int _maxRecursionDepth = -1;
@@ -75,6 +78,17 @@ namespace Jint
             return this;
         }
 
+        /// <summary>
+        /// If no known type could be guessed, objects are normally wrapped as an
+        /// ObjectInstance using class ObjectWrapper. This function can be used to
+        /// register a handler for a customized handling.
+        /// </summary>
+        public Options SetWrapObjectHandler(Func<object, ObjectInstance> wrapObjectHandler)
+        {
+            _wrapObjectHandler = wrapObjectHandler;
+            return this;
+        }
+
         /// <summary>
         /// Allows scripts to call CLR types directly like <example>System.IO.File</example>
         /// </summary>
@@ -86,6 +100,12 @@ namespace Jint
             return this;
         }
 
+        public Options AllowClrWrite(bool allow = true)
+        {
+            _allowClrWrite = allow;
+            return this;
+        }
+
         /// <summary>
         /// Exceptions thrown from CLR code are converted to JavaScript errors and
         /// can be used in at try/catch statement. By default these exceptions are bubbled
@@ -168,12 +188,16 @@ namespace Jint
 
         internal bool _IsClrAllowed => _allowClr;
 
+        internal bool _IsClrWriteAllowed => _allowClrWrite;
+
         internal Predicate<Exception> _ClrExceptionsHandler => _clrExceptionsHandler;
 
         internal List<Assembly> _LookupAssemblies => _lookupAssemblies;
 
         internal List<IObjectConverter> _ObjectConverters => _objectConverters;
 
+        internal Func<object, ObjectInstance> _WrapObjectHandler => _wrapObjectHandler;
+
         internal long _MemoryLimit => _memoryLimit;
 
         internal int _MaxStatements => _maxStatements;

+ 278 - 0
Jint/Pooling/ConcurrentObjectPool.cs

@@ -0,0 +1,278 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+// define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will
+// make everything about 2-3x slower
+//
+// #define TRACE_LEAKS
+
+// define DETECT_LEAKS to detect possible leaks
+// #if DEBUG
+// #define DETECT_LEAKS  //for now always enable DETECT_LEAKS in debug.
+// #endif
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+#if DETECT_LEAKS
+using System.Runtime.CompilerServices;
+#endif
+
+namespace Jint.Pooling
+{
+    /// <summary>
+    /// Generic implementation of object pooling pattern with predefined pool size limit. The main
+    /// purpose is that limited number of frequently used objects can be kept in the pool for
+    /// further recycling.
+    ///
+    /// Notes:
+    /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
+    ///    is no space in the pool, extra returned objects will be dropped.
+    ///
+    /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
+    ///    a relatively short time. Keeping checked out objects for long durations is ok, but
+    ///    reduces usefulness of pooling. Just new up your own.
+    ///
+    /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
+    /// Rationale:
+    ///    If there is no intent for reusing the object, do not use pool - just use "new".
+    /// </summary>
+    internal class ConcurrentObjectPool<T> where T : class
+    {
+        [DebuggerDisplay("{Value,nq}")]
+        private struct Element
+        {
+            internal T Value;
+        }
+
+        /// <remarks>
+        /// Not using System.Func{T} because this file is linked into the (debugger) Formatter,
+        /// which does not have that type (since it compiles against .NET 2.0).
+        /// </remarks>
+        internal delegate T Factory();
+
+        // Storage for the pool objects. The first item is stored in a dedicated field because we
+        // expect to be able to satisfy most requests from it.
+        private T _firstItem;
+        private readonly Element[] _items;
+
+        // factory is stored for the lifetime of the pool. We will call this only when pool needs to
+        // expand. compared to "new T()", Func gives more flexibility to implementers and faster
+        // than "new T()".
+        private readonly Factory _factory;
+
+#if DETECT_LEAKS
+        private static readonly ConditionalWeakTable<T, LeakTracker> leakTrackers = new ConditionalWeakTable<T, LeakTracker>();
+
+        private class LeakTracker : IDisposable
+        {
+            private volatile bool disposed;
+
+#if TRACE_LEAKS
+            internal volatile object Trace = null;
+#endif
+
+            public void Dispose()
+            {
+                disposed = true;
+                GC.SuppressFinalize(this);
+            }
+
+            private string GetTrace()
+            {
+#if TRACE_LEAKS
+                return Trace == null ? "" : Trace.ToString();
+#else
+                return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n";
+#endif
+            }
+
+            ~LeakTracker()
+            {
+                if (!this.disposed && !Environment.HasShutdownStarted)
+                {
+                    var trace = GetTrace();
+
+                    // If you are seeing this message it means that object has been allocated from the pool
+                    // and has not been returned back. This is not critical, but turns pool into rather
+                    // inefficient kind of "new".
+                    Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END");
+                }
+            }
+        }
+#endif
+
+        internal ConcurrentObjectPool(Factory factory)
+            : this(factory, Environment.ProcessorCount * 2)
+        { }
+
+        internal ConcurrentObjectPool(Factory factory, int size)
+        {
+            Debug.Assert(size >= 1);
+            _factory = factory;
+            _items = new Element[size - 1];
+        }
+
+        private T CreateInstance()
+        {
+            var inst = _factory();
+            return inst;
+        }
+
+        /// <summary>
+        /// Produces an instance.
+        /// </summary>
+        /// <remarks>
+        /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+        /// Note that Free will try to store recycled objects close to the start thus statistically
+        /// reducing how far we will typically search.
+        /// </remarks>
+        internal T Allocate()
+        {
+            // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
+            // Note that the initial read is optimistically not synchronized. That is intentional.
+            // We will interlock only when we have a candidate. in a worst case we may miss some
+            // recently returned objects. Not a big deal.
+            T inst = _firstItem;
+            if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
+            {
+                inst = AllocateSlow();
+            }
+
+#if DETECT_LEAKS
+            var tracker = new LeakTracker();
+            leakTrackers.Add(inst, tracker);
+
+#if TRACE_LEAKS
+            var frame = CaptureStackTrace();
+            tracker.Trace = frame;
+#endif
+#endif
+            return inst;
+        }
+
+        private T AllocateSlow()
+        {
+            var items = _items;
+
+            for (int i = 0; i < items.Length; i++)
+            {
+                // Note that the initial read is optimistically not synchronized. That is intentional.
+                // We will interlock only when we have a candidate. in a worst case we may miss some
+                // recently returned objects. Not a big deal.
+                T inst = items[i].Value;
+                if (inst != null)
+                {
+                    if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
+                    {
+                        return inst;
+                    }
+                }
+            }
+
+            return CreateInstance();
+        }
+
+        /// <summary>
+        /// Returns objects to the pool.
+        /// </summary>
+        /// <remarks>
+        /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+        /// Note that Free will try to store recycled objects close to the start thus statistically
+        /// reducing how far we will typically search in Allocate.
+        /// </remarks>
+        internal void Free(T obj)
+        {
+            Validate(obj);
+            ForgetTrackedObject(obj);
+
+            if (_firstItem == null)
+            {
+                // Intentionally not using interlocked here.
+                // In a worst case scenario two objects may be stored into same slot.
+                // It is very unlikely to happen and will only mean that one of the objects will get collected.
+                _firstItem = obj;
+            }
+            else
+            {
+                FreeSlow(obj);
+            }
+        }
+
+        private void FreeSlow(T obj)
+        {
+            var items = _items;
+            for (int i = 0; i < items.Length; i++)
+            {
+                if (items[i].Value == null)
+                {
+                    // Intentionally not using interlocked here.
+                    // In a worst case scenario two objects may be stored into same slot.
+                    // It is very unlikely to happen and will only mean that one of the objects will get collected.
+                    items[i].Value = obj;
+                    break;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Removes an object from leak tracking.
+        ///
+        /// This is called when an object is returned to the pool.  It may also be explicitly
+        /// called if an object allocated from the pool is intentionally not being returned
+        /// to the pool.  This can be of use with pooled arrays if the consumer wants to
+        /// return a larger array to the pool than was originally allocated.
+        /// </summary>
+        [Conditional("DEBUG")]
+        internal void ForgetTrackedObject(T old, T replacement = null)
+        {
+#if DETECT_LEAKS
+            LeakTracker tracker;
+            if (leakTrackers.TryGetValue(old, out tracker))
+            {
+                tracker.Dispose();
+                leakTrackers.Remove(old);
+            }
+            else
+            {
+                var trace = CaptureStackTrace();
+                Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END");
+            }
+
+            if (replacement != null)
+            {
+                tracker = new LeakTracker();
+                leakTrackers.Add(replacement, tracker);
+            }
+#endif
+        }
+
+#if DETECT_LEAKS
+        private static Lazy<Type> _stackTraceType = new Lazy<Type>(() => Type.GetType("System.Diagnostics.StackTrace"));
+
+        private static object CaptureStackTrace()
+        {
+            return Activator.CreateInstance(_stackTraceType.Value);
+        }
+#endif
+
+        [Conditional("DEBUG")]
+        private void Validate(object obj)
+        {
+            Debug.Assert(obj != null, "freeing null?");
+
+            Debug.Assert(_firstItem != obj, "freeing twice?");
+
+            var items = _items;
+            for (int i = 0; i < items.Length; i++)
+            {
+                var value = items[i].Value;
+                if (value == null)
+                {
+                    return;
+                }
+
+                Debug.Assert(value != obj, "freeing twice?");
+            }
+        }
+    }}

+ 63 - 0
Jint/Pooling/StringBuilderPool.cs

@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Text;
+
+namespace Jint.Pooling
+{
+    /// <summary>
+    /// Pooling of StringBuilder instances.
+    /// </summary>
+    internal sealed class StringBuilderPool
+    {
+        private static readonly ConcurrentObjectPool<StringBuilder> _pool;
+
+        static StringBuilderPool()
+        {
+            _pool = new ConcurrentObjectPool<StringBuilder>(() => new StringBuilder());
+        }
+
+        public static BuilderWrapper Rent()
+        {
+            var builder = _pool.Allocate();
+            Debug.Assert(builder.Length == 0);
+            return new BuilderWrapper(builder, _pool);
+        }
+
+        internal readonly struct BuilderWrapper : IDisposable
+        {
+            public readonly StringBuilder Builder;
+            private readonly ConcurrentObjectPool<StringBuilder> _pool;
+
+            public BuilderWrapper(StringBuilder builder, ConcurrentObjectPool<StringBuilder> pool)
+            {
+                Builder = builder;
+                _pool = pool;
+            }
+
+            public int Length => Builder.Length;
+
+            public override string ToString()
+            {
+                return Builder.ToString();
+            }
+
+            public void Dispose()
+            {
+                var builder = Builder;
+
+                // do not store builders that are too large.
+                if (builder.Capacity <= 1024)
+                {
+                    builder.Clear();
+                    _pool.Free(builder);
+                }
+                else
+                {
+                    _pool.ForgetTrackedObject(builder);
+                }
+            }
+        }
+    }
+}

+ 1 - 1
Jint/Runtime/Completion.cs

@@ -18,7 +18,7 @@ namespace Jint.Runtime
     /// </summary>
     public readonly struct Completion
     {
-        public Completion(CompletionType type, JsValue value, string identifier, Location location = null)
+        public Completion(CompletionType type, JsValue value, string identifier, Location location)
         {
             Type = type;
             Value = value;

+ 6 - 6
Jint/Runtime/Debugger/DebugHandler.cs

@@ -44,7 +44,7 @@ namespace Jint.Runtime.Debugger
             if (identifier != null)
             {
                 var stack = identifier.Name + "(";
-                var paramStrings = new List<string>();
+                var paramStrings = new System.Collections.Generic.List<string>();
 
                 foreach (var argument in callExpression.Arguments)
                 {
@@ -162,7 +162,7 @@ namespace Jint.Runtime.Debugger
         private static Dictionary<string, JsValue> GetLocalVariables(LexicalEnvironment lex)
         {
             Dictionary<string, JsValue> locals = new Dictionary<string, JsValue>();
-            if (!ReferenceEquals(lex?.Record, null))
+            if (!ReferenceEquals(lex?._record, null))
             {
                 AddRecordsFromEnvironment(lex, locals);
             }
@@ -174,22 +174,22 @@ namespace Jint.Runtime.Debugger
             Dictionary<string, JsValue> globals = new Dictionary<string, JsValue>();
             LexicalEnvironment tempLex = lex;
 
-            while (tempLex != null && !ReferenceEquals(tempLex.Record, null))
+            while (!ReferenceEquals(tempLex?._record, null))
             {
                 AddRecordsFromEnvironment(tempLex, globals);
-                tempLex = tempLex.Outer;
+                tempLex = tempLex._outer;
             }
             return globals;
         }
 
         private static void AddRecordsFromEnvironment(LexicalEnvironment lex, Dictionary<string, JsValue> locals)
         {
-            var bindings = lex.Record.GetAllBindingNames();
+            var bindings = lex._record.GetAllBindingNames();
             foreach (var binding in bindings)
             {
                 if (locals.ContainsKey(binding) == false)
                 {
-                    var jsValue = lex.Record.GetBindingValue(binding, false);
+                    var jsValue = lex._record.GetBindingValue(binding, false);
                     if (jsValue.TryCast<ICallable>() == null)
                     {
                         locals.Add(binding, jsValue);

+ 13 - 1
Jint/Runtime/Descriptors/PropertyDescriptor.cs

@@ -7,7 +7,7 @@ namespace Jint.Runtime.Descriptors
 {
     public class PropertyDescriptor
     {
-        public static readonly PropertyDescriptor Undefined = new PropertyDescriptor(PropertyFlag.None);
+        public static readonly PropertyDescriptor Undefined = new UndefinedPropertyDescriptor();
 
         internal PropertyFlag _flags;
         internal JsValue _value;
@@ -383,5 +383,17 @@ namespace Jint.Runtime.Descriptors
 
             return true;
         }
+
+        private sealed class UndefinedPropertyDescriptor : PropertyDescriptor
+        {
+            public UndefinedPropertyDescriptor() : base(PropertyFlag.None | PropertyFlag.CustomJsValue)
+            {
+            }
+
+            protected internal override JsValue CustomValue
+            {
+                set => ExceptionHelper.ThrowInvalidOperationException("making changes to undefined property's descriptor is not allowed");
+            }
+        }
     }
 }

+ 3 - 1
Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs

@@ -29,7 +29,9 @@ namespace Jint.Runtime.Descriptors.Specialized
 
         private JsValue DoGet(JsValue n)
         {
-            return _env.GetBindingValue(_name, false);
+            return _env.TryGetBinding(_name, false, out var binding)
+                ? binding.Value
+                : JsValue.Undefined;
         }
 
         private void DoSet(JsValue n, JsValue o)

+ 1 - 1
Jint/Runtime/Descriptors/Specialized/FieldInfoDescriptor.cs

@@ -16,7 +16,7 @@ namespace Jint.Runtime.Descriptors.Specialized
             _fieldInfo = fieldInfo;
             _item = item;
 
-            Writable = !fieldInfo.Attributes.HasFlag(FieldAttributes.InitOnly); // don't write to fields marked as readonly
+            Writable = !fieldInfo.Attributes.HasFlag(FieldAttributes.InitOnly) && engine.Options._IsClrWriteAllowed; // don't write to fields marked as readonly
         }
 
         protected internal override JsValue CustomValue

+ 1 - 1
Jint/Runtime/Descriptors/Specialized/IndexDescriptor.cs

@@ -52,7 +52,7 @@ namespace Jint.Runtime.Descriptors.Specialized
                 ExceptionHelper.ThrowInvalidOperationException("No matching indexer found.");
             }
 
-            Writable = true;
+            Writable = engine.Options._IsClrWriteAllowed;
         }
 
         public IndexDescriptor(Engine engine, string key, object item)

+ 1 - 1
Jint/Runtime/Descriptors/Specialized/PropertyInfoDescriptor.cs

@@ -16,7 +16,7 @@ namespace Jint.Runtime.Descriptors.Specialized
             _propertyInfo = propertyInfo;
             _item = item;
 
-            Writable = propertyInfo.CanWrite;
+            Writable = propertyInfo.CanWrite && engine.Options._IsClrWriteAllowed;
         }
 
         protected internal override JsValue CustomValue

+ 280 - 44
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -1,10 +1,13 @@
-using System;
-using System.Collections.Generic;
+using System;
+using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Collections;
 using Jint.Native;
 using Jint.Native.Argument;
+using Jint.Native.Array;
 using Jint.Native.Function;
+using Jint.Native.Iterator;
+using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Runtime.Environments
 {
@@ -118,6 +121,32 @@ namespace Jint.Runtime.Environments
             return ContainsKey(name);
         }
 
+        internal override bool TryGetBinding(string name, bool strict, out Binding binding)
+        {
+            if (_set && _key == name)
+            {
+                binding = _value;
+                return true;
+            }
+
+            if (name.Length == 9
+                && name == BindingNameArguments
+                && !ReferenceEquals(_argumentsBinding.Value, null))
+            {
+                _argumentsBindingWasAccessed = true;
+                binding = _argumentsBinding;
+                return true;
+            }
+
+            if (_dictionary != null)
+            {
+                return _dictionary.TryGetValue(name, out binding);
+            }
+
+            binding = default;
+            return false;
+        }
+
         public override void CreateMutableBinding(string name, JsValue value, bool canBeDeleted = false)
         {
             SetItem(name, new Binding(value, canBeDeleted, mutable: true));
@@ -143,12 +172,22 @@ namespace Jint.Runtime.Environments
         public override JsValue GetBindingValue(string name, bool strict)
         {
             ref var binding = ref GetExistingItem(name);
+            return UnwrapBindingValue(name, strict, binding);
+        }
 
+        internal override JsValue UnwrapBindingValue(string name, bool strict, in Binding binding)
+        {
+            return UnwrapBindingValueInternal(strict, binding);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private JsValue UnwrapBindingValueInternal(bool strict, in Binding binding)
+        {
             if (!binding.Mutable && binding.Value._type == Types.Undefined)
             {
                 if (strict)
                 {
-                    ExceptionHelper.ThrowReferenceError(_engine, "Can't access an uninitialized immutable binding.");
+                    ThrowUninitializedBindingException();
                 }
 
                 return Undefined;
@@ -157,6 +196,11 @@ namespace Jint.Runtime.Environments
             return binding.Value;
         }
 
+        private void ThrowUninitializedBindingException()
+        {
+            throw new JavaScriptException(_engine.ReferenceError, "Can't access an uninitialized immutable binding.");
+        }
+
         public override bool DeleteBinding(string name)
         {
             ref Binding binding = ref GetExistingItem(name);
@@ -212,71 +256,211 @@ namespace Jint.Runtime.Environments
             return keys;
         }
 
-        /// <summary>
-        /// Optimized version for function calls.
-        /// </summary>
         internal void AddFunctionParameters(
             FunctionInstance functionInstance,
             JsValue[] arguments,
-            ArgumentsInstance argumentsInstance)
+            ArgumentsInstance argumentsInstance,
+            IFunction functionDeclaration)
         {
-            var parameters = functionInstance._formalParameters;
+            var parameters = functionDeclaration.Params;
+
             bool empty = _dictionary == null && !_set;
-            if (empty && parameters.Length == 1 && parameters[0].Length != BindingNameArguments.Length)
-            {
-                var jsValue = arguments.Length == 0 ? Undefined : arguments[0];
-                var binding = new Binding(jsValue, false, true);
-                _set = true;
-                _key = parameters[0];
-                _value = binding;
-            }
-            else
+
+            if (ReferenceEquals(_argumentsBinding.Value, null)
+                && !(functionInstance is ArrowFunctionInstance))
             {
-                AddMultipleParameters(arguments, parameters);
+                _argumentsBinding = new Binding(argumentsInstance, canBeDeleted: false, mutable: true);
             }
 
-            if (ReferenceEquals(_argumentsBinding.Value, null))
+            for (var i = 0; i < parameters.Count; i++)
             {
-                _argumentsBinding = new Binding(argumentsInstance, canBeDeleted: false, mutable: true);
+                SetFunctionParameter(parameters[i], arguments, i, empty);
             }
+
         }
 
-        private void AddMultipleParameters(JsValue[] arguments, string[] parameters)
+        private void SetFunctionParameter(
+            INode parameter,
+            JsValue[] arguments,
+            int index,
+            bool initiallyEmpty)
         {
-            bool empty = _dictionary == null && !_set;
-            for (var i = 0; i < parameters.Length; i++)
+            var argument = arguments.Length > index ? arguments[index] : Undefined;
+
+            if (parameter is Identifier identifier)
             {
-                var argName = parameters[i];
-                var jsValue = i + 1 > arguments.Length ? Undefined : arguments[i];
+                SetItemSafely(identifier.Name, argument, initiallyEmpty);
+            }
+            else if (parameter is RestElement restElement)
+            {
+                // index + 1 == parameters.count because rest is last
+                int restCount = arguments.Length - (index + 1) + 1;
+                uint count = restCount > 0 ? (uint) restCount : 0;
+
+                var rest = _engine.Array.ConstructFast(count);
+
+                uint targetIndex = 0;
+                for (var argIndex = index; argIndex < arguments.Length; ++argIndex)
+                {
+                    rest.SetIndexValue(targetIndex++, arguments[argIndex], updateLength: false);
+                }
+
+                argument = rest;
 
-                if (empty || !TryGetValue(argName, out var existing))
+                if (restElement.Argument is Identifier restIdentifier)
                 {
-                    var binding = new Binding(jsValue, false, true);
-                    if (argName.Length == 9 && argName == BindingNameArguments)
+                    SetItemSafely(restIdentifier.Name, argument, initiallyEmpty);
+                }
+                else if (restElement.Argument is BindingPattern bindingPattern)
+                {
+                    SetFunctionParameter(bindingPattern, new [] { argument }, index, initiallyEmpty);
+                }
+                else
+                {
+                    ExceptionHelper.ThrowSyntaxError(_engine, "Rest parameters can only be identifiers or arrays");
+                }
+            }
+            else if (parameter is ArrayPattern arrayPattern)
+            {
+                if (argument.IsNull())
+                {
+                    ExceptionHelper.ThrowTypeError(_engine, "Destructed parameter is null");
+                }
+
+                ArrayInstance array = null;
+                var arrayContents = ArrayExt.Empty<JsValue>();
+                if (argument.IsArray())
+                {
+                    array = argument.AsArray();
+                }
+                else if (argument.IsObject() && argument.TryGetIterator(_engine, out var iterator))
+                {
+                    array = _engine.Array.ConstructFast(0);
+                    var protocol = new ArrayPatternProtocol(_engine, array, iterator, arrayPattern.Elements.Count);
+                    protocol.Execute();
+                }
+
+                if (!ReferenceEquals(array, null))
+                {
+                    arrayContents = new JsValue[array.Length];
+
+                    for (uint contentsIndex = 0; contentsIndex < array.Length; contentsIndex++)
                     {
-                        _argumentsBinding = binding;
+                        arrayContents[contentsIndex] = array.Get(contentsIndex);
                     }
-                    else
+                }
+
+                for (uint arrayIndex = 0; arrayIndex < arrayPattern.Elements.Count; arrayIndex++)
+                {
+                    SetFunctionParameter(arrayPattern.Elements[(int) arrayIndex], arrayContents, (int) arrayIndex, initiallyEmpty);
+                }
+            }
+            else if (parameter is ObjectPattern objectPattern)
+            {
+                if (argument.IsNullOrUndefined())
+                {
+                    ExceptionHelper.ThrowTypeError(_engine, "Destructed parameter is null or undefined");
+                }
+
+                if (!argument.IsObject())
+                {
+                    return;
+                }
+
+                var argumentObject = argument.AsObject();
+
+                var jsValues = _engine._jsValueArrayPool.RentArray(1);
+                foreach (var property in objectPattern.Properties)
+                {
+                    if (property.Key is Identifier propertyIdentifier)
                     {
-                        SetItem(argName, binding);
+                        argument = argumentObject.Get(propertyIdentifier.Name);
                     }
+                    else if (property.Key is Literal propertyLiteral)
+                    {
+                        argument = argumentObject.Get(propertyLiteral.Raw);
+                    }
+                    else if (property.Key is CallExpression callExpression)
+                    {
+                        var jintCallExpression = JintExpression.Build(_engine, callExpression);
+                        argument = argumentObject.Get(jintCallExpression.GetValue().AsString());
+                    }
+
+                    jsValues[0] = argument;
+                    SetFunctionParameter(property.Value, jsValues, 0, initiallyEmpty);
                 }
-                else
+                _engine._jsValueArrayPool.ReturnArray(jsValues);
+            }
+            else if (parameter is AssignmentPattern assignmentPattern)
+            {
+                var idLeft = assignmentPattern.Left as Identifier;
+                if (idLeft != null
+                    && assignmentPattern.Right is Identifier idRight
+                    && idLeft.Name == idRight.Name)
                 {
-                    if (existing.Mutable)
+                    ExceptionHelper.ThrowReferenceError(_engine, idRight.Name);
+                }
+
+                if (argument.IsUndefined())
+                {
+                    JsValue RunInNewParameterEnvironment(JintExpression exp)
                     {
-                        ref var b = ref GetExistingItem(argName);
-                        b.Value = jsValue;
+                        var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
+                        var paramVarEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv);
+
+                        _engine.EnterExecutionContext(paramVarEnv, paramVarEnv, _engine.ExecutionContext.ThisBinding);;
+                        var result = exp.GetValue();
+                        _engine.LeaveExecutionContext();
+
+                        return result;
                     }
-                    else
+
+                    var expression = assignmentPattern.Right.As<Expression>();
+                    var jintExpression = JintExpression.Build(_engine, expression);
+
+                    argument = jintExpression is JintSequenceExpression
+                        ? RunInNewParameterEnvironment(jintExpression)
+                        : jintExpression.GetValue();
+
+                    if (idLeft != null && assignmentPattern.Right.IsFunctionWithName())
                     {
-                        ExceptionHelper.ThrowTypeError(_engine, "Can't update the value of an immutable binding.");
+                        ((FunctionInstance) argument).SetFunctionName(idLeft.Name);
                     }
                 }
+
+                SetFunctionParameter(assignmentPattern.Left, new []{ argument }, 0, initiallyEmpty);
             }
         }
 
-        internal void AddVariableDeclarations(List<VariableDeclaration> variableDeclarations)
+        private void SetItemSafely(string name, JsValue argument, bool initiallyEmpty)
+        {
+            if (initiallyEmpty || !TryGetValue(name, out var existing))
+            {
+                var binding = new Binding(argument, false, true);
+                if (name.Length == 9 && name == BindingNameArguments)
+                {
+                    _argumentsBinding = binding;
+                }
+                else
+                {
+                    SetItem(name, binding);
+                }
+            }
+            else
+            {
+                if (existing.Mutable)
+                {
+                    ref var b = ref GetExistingItem(name);
+                    b.Value = argument;
+                }
+                else
+                {
+                    ExceptionHelper.ThrowTypeError(_engine, "Can't update the value of an immutable binding.");
+                }
+            }
+        }
+
+        internal void AddVariableDeclarations(ref NodeList<VariableDeclaration> variableDeclarations)
         {
             var variableDeclarationsCount = variableDeclarations.Count;
             for (var i = 0; i < variableDeclarationsCount; i++)
@@ -286,16 +470,33 @@ namespace Jint.Runtime.Environments
                 for (var j = 0; j < declarationsCount; j++)
                 {
                     var d = variableDeclaration.Declarations[j];
-                    var dn = ((Identifier) d.Id).Name;
-
-                    if (!ContainsKey(dn))
+                    if (d.Id is Identifier id)
                     {
-                        var binding = new Binding(Undefined, canBeDeleted: false, mutable: true);
-                        SetItem(dn, binding);
+                        var dn = id.Name;
+                        if (!ContainsKey(dn))
+                        {
+                            var binding = new Binding(Undefined, canBeDeleted: false, mutable: true);
+                            SetItem(dn, binding);
+                        }
                     }
                 }
             }
         }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static JsValue HandleAssignmentPatternIfNeeded(IFunction functionDeclaration, JsValue jsValue, int index)
+        {
+            // TODO remove this method, overwrite with above SetFunctionParameter logic
+            if (jsValue.IsUndefined()
+                && index < functionDeclaration?.Params.Count
+                && functionDeclaration.Params[index] is AssignmentPattern ap
+                && ap.Right is Literal l)
+            {
+                return JintLiteralExpression.ConvertToJsValue(l);
+            }
+
+            return jsValue;
+        }
         
         internal override void FunctionWasCalled()
         {
@@ -318,5 +519,40 @@ namespace Jint.Runtime.Environments
                 _argumentsBindingWasAccessed = null;
             }
         }
+
+        private sealed class ArrayPatternProtocol : IteratorProtocol
+        {
+            private readonly ArrayInstance _instance;
+            private readonly int _max;
+            private long _index = -1;
+
+            public ArrayPatternProtocol(
+                Engine engine,
+                ArrayInstance instance,
+                IIterator iterator,
+                int max) : base(engine, iterator, 0)
+            {
+                _instance = instance;
+                _max = max;
+            }
+
+            protected override void ProcessItem(JsValue[] args, JsValue currentValue)
+            {
+                _index++;
+                var jsValue = ExtractValueFromIteratorInstance(currentValue);
+                _instance.SetIndexValue((uint) _index, jsValue, updateLength: false);
+            }
+
+            protected override bool ShouldContinue => _index < _max;
+
+            protected override void IterationEnd()
+            {
+                if (_index >= 0)
+                {
+                    _instance.SetLength((uint) _index);
+                    ReturnIterator();
+                }
+            }
+        }
     }
-}
+}

+ 4 - 0
Jint/Runtime/Environments/EnvironmentRecord.cs

@@ -22,6 +22,10 @@ namespace Jint.Runtime.Environments
         /// <returns><c>true</c> if it does and <c>false</c> if it does not.</returns>
         public abstract bool HasBinding(string name);
 
+        internal abstract bool TryGetBinding(string name, bool strict, out Binding binding);
+
+        internal abstract JsValue UnwrapBindingValue(string name, bool strict, in Binding binding);
+
         /// <summary>
         /// Creates a new mutable binding in an environment record.
         /// </summary>

+ 40 - 16
Jint/Runtime/Environments/LexicalEnvironment.cs

@@ -6,7 +6,7 @@ using Jint.Runtime.References;
 namespace Jint.Runtime.Environments
 {
     /// <summary>
-    /// Represents a Liexical Environment (a.k.a Scope)
+    /// Represents a Lexical Environment (a.k.a Scope)
     /// http://www.ecma-international.org/ecma-262/5.1/#sec-10.2
     /// http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.2
     /// </summary>
@@ -14,7 +14,7 @@ namespace Jint.Runtime.Environments
     {
         private readonly Engine _engine;
         internal readonly EnvironmentRecord _record;
-        private readonly LexicalEnvironment _outer;
+        internal readonly LexicalEnvironment _outer;
 
         public LexicalEnvironment(Engine engine, EnvironmentRecord record, LexicalEnvironment outer)
         {
@@ -23,43 +23,67 @@ namespace Jint.Runtime.Environments
             _outer = outer;
         }
 
-        public EnvironmentRecord Record
-        {
-            [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get { return _record; }
-        }
+        public EnvironmentRecord Record => _record;
 
         public LexicalEnvironment Outer => _outer;
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static Reference GetIdentifierReference(LexicalEnvironment lex, string name, bool strict)
+        {
+            var identifierEnvironment = TryGetIdentifierEnvironmentWithBindingValue(lex, name, strict, out var temp, out _)
+                ? temp
+                : JsValue.Undefined;
+
+            return lex._engine._referencePool.Rent(identifierEnvironment, name, strict);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool TryGetIdentifierEnvironmentWithBindingValue(
+            LexicalEnvironment lex,
+            string name,
+            bool strict,
+            out EnvironmentRecord record,
+            out JsValue value)
         {
             // optimize for common case where result is in one of the nearest scopes
-            if (lex._record.HasBinding(name))
+            if (lex._record.TryGetBinding(name, strict, out var binding))
             {
-                return lex._engine._referencePool.Rent(lex._record, name, strict);
+                record = lex._record;
+                value = lex._record.UnwrapBindingValue(name, strict, binding);
+                return true;
             }
 
             if (lex._outer == null)
             {
-                return new Reference(Undefined.Instance, name, strict);
+                record = default;
+                value = default;
+                return false;
             }
-            
-            return GetIdentifierReferenceLooping(lex._outer, name, strict);
+
+            return TryGetIdentifierReferenceLooping(lex._outer, name, strict, out record, out value);
         }
 
-        private static Reference GetIdentifierReferenceLooping(LexicalEnvironment lex, string name, bool strict)
+        private static bool TryGetIdentifierReferenceLooping(
+            LexicalEnvironment lex,
+            string name,
+            bool strict,
+            out EnvironmentRecord record,
+            out JsValue value)
         {
             while (true)
             {
-                if (lex._record.HasBinding(name))
+                if (lex._record.TryGetBinding(name, strict, out var binding))
                 {
-                    return lex._engine._referencePool.Rent(lex._record, name, strict);
+                    record = lex._record;
+                    value = lex._record.UnwrapBindingValue(name, strict, binding);
+                    return true;
                 }
 
                 if (lex._outer == null)
                 {
-                    return lex._engine._referencePool.Rent(Undefined.Instance, name, strict);
+                    record = default;
+                    value = default;
+                    return false;
                 }
 
                 lex = lex._outer;

+ 24 - 1
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -26,6 +26,24 @@ namespace Jint.Runtime.Environments
             return _bindingObject.HasProperty(name);
         }
 
+        internal override bool TryGetBinding(string name, bool strict, out Binding binding)
+        {
+            if (!_bindingObject.HasProperty(name))
+            {
+                binding = default;
+                return false;
+            }
+
+            // we unwrap by name
+            binding = default;
+            return true;
+        }
+
+        internal override JsValue UnwrapBindingValue(string name, bool strict, in Binding binding)
+        {
+            return GetBindingValue(name, strict);
+        }
+
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.1.2.2
         /// </summary>
@@ -48,7 +66,7 @@ namespace Jint.Runtime.Environments
             var desc = _bindingObject.GetProperty(name);
             if (strict && desc == PropertyDescriptor.Undefined)
             {
-                ExceptionHelper.ThrowReferenceError(_engine);
+                ExceptionHelper.ThrowReferenceError(_engine, name);
             }
 
             return ObjectInstance.UnwrapJsValue(desc, this);
@@ -79,6 +97,11 @@ namespace Jint.Runtime.Environments
             return ArrayExt.Empty<string>();
         }
 
+        public override bool Equals(JsValue other)
+        {
+            return ReferenceEquals(_bindingObject, other);
+        }
+
         internal override void FunctionWasCalled()
         {
         }

+ 26 - 3
Jint/Runtime/ExceptionHelper.cs

@@ -1,5 +1,7 @@
 using System;
+using Jint.Native;
 using Jint.Runtime.CallStack;
+using Jint.Runtime.References;
 
 namespace Jint.Runtime
 {
@@ -32,8 +34,14 @@ namespace Jint.Runtime
             throw new ArgumentException(message, paramName);
         }
 
-        public static void ThrowReferenceError(Engine engine, string message = null)
+        public static void ThrowReferenceError(Engine engine, Reference reference)
         {
+            ThrowReferenceError(engine, reference?.GetReferencedName());
+        }
+
+        public static void ThrowReferenceError(Engine engine, string name)
+        {
+            var message = name != null ? name + " is not defined" : null;
             throw new JavaScriptException(engine.ReferenceError, message);
         }
 
@@ -42,6 +50,11 @@ namespace Jint.Runtime
             throw new TypeErrorException(message);
         }
 
+        public static T ThrowReferenceError<T>(Engine engine, string message = null)
+        {
+            throw new JavaScriptException(engine.ReferenceError, message);
+        }
+
         public static T ThrowTypeError<T>(Engine engine, string message = null, Exception exception = null)
         {
             ThrowTypeError(engine, message, exception);
@@ -78,6 +91,16 @@ namespace Jint.Runtime
             throw new NotImplementedException();
         }
 
+        public static T ThrowNotImplementedException<T>()
+        {
+            throw new NotImplementedException();
+        }
+
+        public static T ThrowArgumentOutOfRangeException<T>()
+        {
+            throw new ArgumentOutOfRangeException();
+        }
+
         public static void ThrowArgumentOutOfRangeException(string paramName, string message)
         {
             throw new ArgumentOutOfRangeException(paramName, message);
@@ -113,9 +136,9 @@ namespace Jint.Runtime
             throw new InvalidOperationException(message);
         }
 
-        public static void ThrowJavaScriptException(string message)
+        public static void ThrowJavaScriptException(Engine engine, JsValue value, Completion result)
         {
-            throw new JavaScriptException("TypeError");
+            throw new JavaScriptException(value).SetCallstack(engine, result.Location);
         }
 
         public static void ThrowRecursionDepthOverflowException(JintCallStack currentStack, string currentExpressionReference)

+ 0 - 1094
Jint/Runtime/ExpressionIntepreter.cs

@@ -1,1094 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Esprima;
-using Esprima.Ast;
-using Jint.Native;
-using Jint.Native.Function;
-using Jint.Native.Number;
-using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
-using Jint.Runtime.Environments;
-using Jint.Runtime.Interop;
-using Jint.Runtime.References;
-
-namespace Jint.Runtime
-{
-    public sealed class ExpressionInterpreter
-    {
-        private readonly Engine _engine;
-        private readonly bool _isDebugMode;
-        private readonly int _maxRecursionDepth;
-        private readonly IReferenceResolver _referenceResolver;
-
-        public ExpressionInterpreter(Engine engine)
-        {
-            _engine = engine;
-            
-            // gather some options as fields for faster checks
-            _isDebugMode = engine.Options.IsDebugMode;
-            _maxRecursionDepth = engine.Options.MaxRecursionDepth;
-            _referenceResolver = engine.Options.ReferenceResolver;
-        }
-
-        private object EvaluateExpression(Expression expression)
-        {
-            return _engine.EvaluateExpression(expression);
-        }
-
-        public JsValue EvaluateConditionalExpression(ConditionalExpression conditionalExpression)
-        {
-            var lref = _engine.EvaluateExpression(conditionalExpression.Test);
-            if (TypeConverter.ToBoolean(_engine.GetValue(lref, true)))
-            {
-                var trueRef = _engine.EvaluateExpression(conditionalExpression.Consequent);
-                return _engine.GetValue(trueRef, true);
-            }
-            else
-            {
-                var falseRef = _engine.EvaluateExpression(conditionalExpression.Alternate);
-                return _engine.GetValue(falseRef, true);
-            }
-        }
-
-        public JsValue EvaluateAssignmentExpression(AssignmentExpression assignmentExpression)
-        {
-            var lref = _engine.EvaluateExpression((Expression) assignmentExpression.Left) as Reference;
-            JsValue rval = _engine.GetValue(_engine.EvaluateExpression(assignmentExpression.Right), true);
-
-            if (lref == null)
-            {
-                ExceptionHelper.ThrowReferenceError(_engine);
-            }
-
-            if (assignmentExpression.Operator == AssignmentOperator.Assign) // "="
-            {
-                lref.AssertValid(_engine);
-
-                _engine.PutValue(lref, rval);
-                _engine._referencePool.Return(lref);
-                return rval;
-            }
-
-            JsValue lval = _engine.GetValue(lref, false);
-
-            switch (assignmentExpression.Operator)
-            {
-                case AssignmentOperator.PlusAssign:
-                    var lprim = TypeConverter.ToPrimitive(lval);
-                    var rprim = TypeConverter.ToPrimitive(rval);
-                    if (lprim.IsString() || rprim.IsString())
-                    {
-                        if (!(lprim is JsString jsString))
-                        {
-                            jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim));
-                        }
-                        lval = jsString.Append(rprim);
-                    }
-                    else
-                    {
-                        lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
-                    }
-                    break;
-
-                case AssignmentOperator.MinusAssign:
-                    lval = TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval);
-                    break;
-
-                case AssignmentOperator.TimesAssign:
-                    if (lval.IsUndefined() || rval.IsUndefined())
-                    {
-                        lval = Undefined.Instance;
-                    }
-                    else
-                    {
-                        lval = TypeConverter.ToNumber(lval) * TypeConverter.ToNumber(rval);
-                    }
-                    break;
-
-                case AssignmentOperator.DivideAssign:
-                    lval = Divide(lval, rval);
-                    break;
-
-                case AssignmentOperator.ModuloAssign:
-                    if (lval.IsUndefined() || rval.IsUndefined())
-                    {
-                        lval = Undefined.Instance;
-                    }
-                    else
-                    {
-                        lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
-                    }
-                    break;
-
-                case AssignmentOperator.BitwiseAndAssign:
-                    lval = TypeConverter.ToInt32(lval) & TypeConverter.ToInt32(rval);
-                    break;
-
-                case AssignmentOperator.BitwiseOrAssign:
-                    lval = TypeConverter.ToInt32(lval) | TypeConverter.ToInt32(rval);
-                    break;
-
-                case AssignmentOperator.BitwiseXOrAssign:
-                    lval = TypeConverter.ToInt32(lval) ^ TypeConverter.ToInt32(rval);
-                    break;
-
-                case AssignmentOperator.LeftShiftAssign:
-                    lval = TypeConverter.ToInt32(lval) << (int)(TypeConverter.ToUint32(rval) & 0x1F);
-                    break;
-
-                case AssignmentOperator.RightShiftAssign:
-                    lval = TypeConverter.ToInt32(lval) >> (int)(TypeConverter.ToUint32(rval) & 0x1F);
-                    break;
-
-                case AssignmentOperator.UnsignedRightShiftAssign:
-                    lval = (uint)TypeConverter.ToInt32(lval) >> (int)(TypeConverter.ToUint32(rval) & 0x1F);
-                    break;
-
-                default:
-                    ExceptionHelper.ThrowNotImplementedException();
-                    return null;
-            }
-
-            _engine.PutValue(lref, lval);
-
-            _engine._referencePool.Return(lref);
-            return lval;
-        }
-
-        private JsValue Divide(JsValue lval, JsValue rval)
-        {
-            if (lval.IsUndefined() || rval.IsUndefined())
-            {
-                return Undefined.Instance;
-            }
-            else
-            {
-                var lN = TypeConverter.ToNumber(lval);
-                var rN = TypeConverter.ToNumber(rval);
-
-                if (double.IsNaN(rN) || double.IsNaN(lN))
-                {
-                    return double.NaN;
-                }
-
-                if (double.IsInfinity(lN) && double.IsInfinity(rN))
-                {
-                    return double.NaN;
-                }
-
-                if (double.IsInfinity(lN) && rN == 0)
-                {
-                    if (NumberInstance.IsNegativeZero(rN))
-                    {
-                        return -lN;
-                    }
-
-                    return lN;
-                }
-
-                if (lN == 0 && rN == 0)
-                {
-                    return double.NaN;
-                }
-
-                if (rN == 0)
-                {
-                    if (NumberInstance.IsNegativeZero(rN))
-                    {
-                        return lN > 0 ? -double.PositiveInfinity : -double.NegativeInfinity;
-                    }
-
-                    return lN > 0 ? double.PositiveInfinity : double.NegativeInfinity;
-                }
-
-                return lN/rN;
-            }
-        }
-
-        public JsValue EvaluateBinaryExpression(BinaryExpression expression)
-        {
-            JsValue left;
-            if (expression.Left.Type == Nodes.Literal)
-            {
-                left = EvaluateLiteral((Literal) expression.Left);
-            }
-            else
-            {
-                left = _engine.GetValue(_engine.EvaluateExpression(expression.Left), true);
-            }
-
-            JsValue right;
-            if (expression.Right.Type == Nodes.Literal)
-            {
-                right = EvaluateLiteral((Literal) expression.Right);
-            }
-            else
-            {
-                right = _engine.GetValue(_engine.EvaluateExpression(expression.Right), true);
-            }
-
-            JsValue value;
-
-            switch (expression.Operator)
-            {
-                case BinaryOperator.Plus:
-                    var lprim = TypeConverter.ToPrimitive(left);
-                    var rprim = TypeConverter.ToPrimitive(right);
-                    if (lprim.IsString() || rprim.IsString())
-                    {
-                        value = TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim);
-                    }
-                    else
-                    {
-                        value = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
-                    }
-                    break;
-
-                case BinaryOperator.Minus:
-                    value = TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right);
-                    break;
-
-                case BinaryOperator.Times:
-                    if (left.IsUndefined() || right.IsUndefined())
-                    {
-                        value = Undefined.Instance;
-                    }
-                    else
-                    {
-                        value = TypeConverter.ToNumber(left) * TypeConverter.ToNumber(right);
-                    }
-                    break;
-
-                case BinaryOperator.Divide:
-                    value = Divide(left, right);
-                    break;
-
-                case BinaryOperator.Modulo:
-                    if (left.IsUndefined() || right.IsUndefined())
-                    {
-                        value = Undefined.Instance;
-                    }
-                    else
-                    {
-                        value = TypeConverter.ToNumber(left) % TypeConverter.ToNumber(right);
-                    }
-                    break;
-
-                case BinaryOperator.Equal:
-                    value = Equal(left, right) ? JsBoolean.True : JsBoolean.False;
-                    break;
-
-                case BinaryOperator.NotEqual:
-                    value = Equal(left, right) ? JsBoolean.False : JsBoolean.True;
-                    break;
-
-                case BinaryOperator.Greater:
-                    value = Compare(right, left, false);
-                    if (value.IsUndefined())
-                    {
-                        value = false;
-                    }
-                    break;
-
-                case BinaryOperator.GreaterOrEqual:
-                    value = Compare(left, right);
-                    if (value.IsUndefined() || ((JsBoolean) value)._value)
-                    {
-                        value = false;
-                    }
-                    else
-                    {
-                        value = true;
-                    }
-                    break;
-
-                case BinaryOperator.Less:
-                    value = Compare(left, right);
-                    if (value.IsUndefined())
-                    {
-                        value = false;
-                    }
-                    break;
-
-                case BinaryOperator.LessOrEqual:
-                    value = Compare(right, left, false);
-                    if (value.IsUndefined() || ((JsBoolean) value)._value)
-                    {
-                        value = false;
-                    }
-                    else
-                    {
-                        value = true;
-                    }
-                    break;
-
-                case BinaryOperator.StrictlyEqual:
-                    return StrictlyEqual(left, right) ? JsBoolean.True : JsBoolean.False;
-
-                case BinaryOperator.StricltyNotEqual:
-                    return StrictlyEqual(left, right)? JsBoolean.False : JsBoolean.True;
-
-                case BinaryOperator.BitwiseAnd:
-                    return TypeConverter.ToInt32(left) & TypeConverter.ToInt32(right);
-
-                case BinaryOperator.BitwiseOr:
-                    return TypeConverter.ToInt32(left) | TypeConverter.ToInt32(right);
-
-                case BinaryOperator.BitwiseXOr:
-                    return TypeConverter.ToInt32(left) ^ TypeConverter.ToInt32(right);
-
-                case BinaryOperator.LeftShift:
-                    return TypeConverter.ToInt32(left) << (int)(TypeConverter.ToUint32(right) & 0x1F);
-
-                case BinaryOperator.RightShift:
-                    return TypeConverter.ToInt32(left) >> (int)(TypeConverter.ToUint32(right) & 0x1F);
-
-                case BinaryOperator.UnsignedRightShift:
-                    return (uint)TypeConverter.ToInt32(left) >> (int)(TypeConverter.ToUint32(right) & 0x1F);
-
-                case BinaryOperator.Exponentiation:
-                    return Math.Pow(TypeConverter.ToNumber(left), TypeConverter.ToNumber(right));
-
-                case BinaryOperator.InstanceOf:
-                    var f = right.TryCast<FunctionInstance>();
-                    if (ReferenceEquals(f, null))
-                    {
-                        ExceptionHelper.ThrowTypeError(_engine, "instanceof can only be used with a function object");
-                    }
-                    value = f.HasInstance(left);
-                    break;
-
-                case BinaryOperator.In:
-                    if (!right.IsObject())
-                    {
-                        ExceptionHelper.ThrowTypeError(_engine, "in can only be used with an object");
-                    }
-
-                    value = right.AsObject().HasProperty(TypeConverter.ToString(left));
-                    break;
-
-                default:
-                    ExceptionHelper.ThrowNotImplementedException();
-                    return null;
-            }
-
-            return value;
-        }
-
-        public JsValue EvaluateLogicalExpression(BinaryExpression binaryExpression)
-        {
-            var left = _engine.GetValue(_engine.EvaluateExpression(binaryExpression.Left), true);
-
-            switch (binaryExpression.Operator)
-            {
-                case BinaryOperator.LogicalAnd:
-                    if (!TypeConverter.ToBoolean(left))
-                    {
-                        return left;
-                    }
-
-                    return _engine.GetValue(_engine.EvaluateExpression(binaryExpression.Right), true);
-
-                case BinaryOperator.LogicalOr:
-                    if (TypeConverter.ToBoolean(left))
-                    {
-                        return left;
-                    }
-
-                    return _engine.GetValue(_engine.EvaluateExpression(binaryExpression.Right), true);
-
-                default:
-                    ExceptionHelper.ThrowNotImplementedException();
-                    return null;
-            }
-        }
-
-        private static bool Equal(JsValue x, JsValue y)
-        {
-            if (x._type == y._type)
-            {
-				return StrictlyEqual(x, y);
-            }
-
-            if (x._type == Types.Null && y._type == Types.Undefined)
-            {
-                return true;
-            }
-
-            if (x._type == Types.Undefined && y._type == Types.Null)
-            {
-                return true;
-            }
-
-            if (x._type == Types.Number && y._type == Types.String)
-            {
-                return Equal(x, TypeConverter.ToNumber(y));
-            }
-
-            if (x._type == Types.String && y._type == Types.Number)
-            {
-                return Equal(TypeConverter.ToNumber(x), y);
-            }
-
-            if (x._type == Types.Boolean)
-            {
-                return Equal(TypeConverter.ToNumber(x), y);
-            }
-
-            if (y._type == Types.Boolean)
-            {
-                return Equal(x, TypeConverter.ToNumber(y));
-            }
-
-            if (y._type == Types.Object && (x._type == Types.String || x._type == Types.Number))
-            {
-                return Equal(x, TypeConverter.ToPrimitive(y));
-            }
-
-            if (x._type == Types.Object && (y._type == Types.String || y._type == Types.Number))
-            {
-                return Equal(TypeConverter.ToPrimitive(x), y);
-            }
-
-            return false;
-        }
-
-        public static bool StrictlyEqual(JsValue x, JsValue y)
-        {
-            if (x._type != y._type)
-            {
-                return false;
-            }
-
-            if (x._type == Types.Boolean || x._type == Types.String)
-            {
-                return x.Equals(y);
-            }
-
-                        
-            if (x._type >= Types.None && x._type <= Types.Null)
-            {
-                return true;
-            }
-
-            if (x is JsNumber jsNumber)
-            {
-                var nx = jsNumber._value;
-                var ny = ((JsNumber) y)._value;
-                return !double.IsNaN(nx) && !double.IsNaN(ny) && nx == ny;
-            }
-
-            if (x is IObjectWrapper xw)
-            {
-                if (!(y is IObjectWrapper yw))
-                {
-                    return false;
-                }
-
-                return Equals(xw.Target, yw.Target);
-            }
-
-            return x == y;
-        }
-
-        public static bool SameValue(JsValue x, JsValue y)
-        {
-            var typea = TypeConverter.GetPrimitiveType(x);
-            var typeb = TypeConverter.GetPrimitiveType(y);
-
-            if (typea != typeb)
-            {
-                return false;
-            }
-
-            switch (typea)
-            {
-                case Types.None:
-                    return true;
-                case Types.Number:
-                var nx = TypeConverter.ToNumber(x);
-                var ny = TypeConverter.ToNumber(y);
-
-                if (double.IsNaN(nx) && double.IsNaN(ny))
-                {
-                    return true;
-                }
-
-                if (nx == ny)
-                {
-                    if (nx == 0)
-                    {
-                        // +0 !== -0
-                        return NumberInstance.IsNegativeZero(nx) == NumberInstance.IsNegativeZero(ny);
-                    }
-
-                    return true;
-                }
-
-                return false;
-                case Types.String:
-                    return TypeConverter.ToString(x) == TypeConverter.ToString(y);
-                case Types.Boolean:
-                    return TypeConverter.ToBoolean(x) == TypeConverter.ToBoolean(y);
-                default:
-                    return x == y;
-            }
-
-        }
-
-        public static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true)
-        {
-            JsValue px, py;
-            if (leftFirst)
-            {
-                px = TypeConverter.ToPrimitive(x, Types.Number);
-                py = TypeConverter.ToPrimitive(y, Types.Number);
-            }
-            else
-            {
-                py = TypeConverter.ToPrimitive(y, Types.Number);
-                px = TypeConverter.ToPrimitive(x, Types.Number);
-            }
-
-            var typea = px.Type;
-            var typeb = py.Type;
-
-            if (typea != Types.String || typeb != Types.String)
-            {
-                var nx = TypeConverter.ToNumber(px);
-                var ny = TypeConverter.ToNumber(py);
-
-                if (double.IsNaN(nx) || double.IsNaN(ny))
-                {
-                    return Undefined.Instance;
-                }
-
-                if (nx == ny)
-                {
-                    return false;
-                }
-
-                if (double.IsPositiveInfinity(nx))
-                {
-                    return false;
-                }
-
-                if (double.IsPositiveInfinity(ny))
-                {
-                    return true;
-                }
-
-                if (double.IsNegativeInfinity(ny))
-                {
-                    return false;
-                }
-
-                if (double.IsNegativeInfinity(nx))
-                {
-                    return true;
-                }
-
-                return nx < ny;
-            }
-            else
-            {
-                return String.CompareOrdinal(TypeConverter.ToString(x), TypeConverter.ToString(y)) < 0;
-            }
-        }
-
-        public Reference EvaluateIdentifier(Identifier identifier)
-        {
-            var env = _engine.ExecutionContext.LexicalEnvironment;
-            var strict = StrictModeScope.IsStrictModeCode;
-
-            return LexicalEnvironment.GetIdentifierReference(env, identifier.Name, strict);
-        }
-
-        public JsValue EvaluateLiteral(Literal literal)
-        {
-            switch (literal.TokenType)
-            {
-                case TokenType.BooleanLiteral:
-                    // bool is fast enough
-                    return literal.NumericValue > 0.0 ? JsBoolean.True : JsBoolean.False;
-                
-                case TokenType.NullLiteral:
-                    // and so is null
-                    return JsValue.Null;
-
-                case TokenType.NumericLiteral:
-                    return (JsValue) (literal.CachedValue = literal.CachedValue ?? JsNumber.Create(literal.NumericValue));
-                
-                case TokenType.StringLiteral:
-                    return (JsValue) (literal.CachedValue = literal.CachedValue ?? JsString.Create((string) literal.Value));
-                
-                case TokenType.RegularExpression:
-                    // should not cache
-                    return _engine.RegExp.Construct((System.Text.RegularExpressions.Regex) literal.Value, literal.Regex.Flags);
-
-                default:
-                    // a rare case, above types should cover all
-                    return JsValue.FromObject(_engine, literal.Value);
-            }
-        }
-
-        public JsValue EvaluateObjectExpression(ObjectExpression objectExpression)
-        {
-            // http://www.ecma-international.org/ecma-262/5.1/#sec-11.1.5
-            var propertiesCount = objectExpression.Properties.Count;
-            var obj = _engine.Object.Construct(propertiesCount);
-            for (var i = 0; i < propertiesCount; i++)
-            {
-                var property = objectExpression.Properties[i];
-                var propName = property.Key.GetKey();
-                if (!obj._properties.TryGetValue(propName, out var previous))
-                {
-                    previous = PropertyDescriptor.Undefined;
-                }
-                
-                PropertyDescriptor propDesc;
-
-                if (property.Kind == PropertyKind.Init || property.Kind == PropertyKind.Data)
-                {
-                    var exprValue = _engine.EvaluateExpression(property.Value);
-                    var propValue = _engine.GetValue(exprValue, true);
-                    propDesc = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable);
-                }
-                else if (property.Kind == PropertyKind.Get || property.Kind == PropertyKind.Set)
-                {
-                    var function = property.Value as IFunction;
-
-                    if (function == null)
-                    {
-                        return ExceptionHelper.ThrowSyntaxError<JsValue>(_engine);
-                    }
-
-                    ScriptFunctionInstance functionInstance;
-                    using (new StrictModeScope(function.Strict))
-                    {
-                        functionInstance = new ScriptFunctionInstance(
-                            _engine,
-                            function,
-                            _engine.ExecutionContext.LexicalEnvironment,
-                            StrictModeScope.IsStrictModeCode
-                        );
-                    }
-
-                    propDesc = new GetSetPropertyDescriptor(
-                        get: property.Kind == PropertyKind.Get ? functionInstance : null,
-                        set: property.Kind == PropertyKind.Set ? functionInstance : null,
-                        PropertyFlag.Enumerable | PropertyFlag.Configurable);
-                }
-                else
-                {
-                    ExceptionHelper.ThrowArgumentOutOfRangeException();
-                    return null;
-                }
-
-                if (previous != PropertyDescriptor.Undefined)
-                {
-                    if (StrictModeScope.IsStrictModeCode && previous.IsDataDescriptor() && propDesc.IsDataDescriptor())
-                    {
-                        ExceptionHelper.ThrowSyntaxError(_engine);
-                    }
-
-                    if (previous.IsDataDescriptor() && propDesc.IsAccessorDescriptor())
-                    {
-                        ExceptionHelper.ThrowSyntaxError(_engine);
-                    }
-
-                    if (previous.IsAccessorDescriptor() && propDesc.IsDataDescriptor())
-                    {
-                        ExceptionHelper.ThrowSyntaxError(_engine);
-                    }
-
-                    if (previous.IsAccessorDescriptor() && propDesc.IsAccessorDescriptor())
-                    {
-                        if (!ReferenceEquals(propDesc.Set, null) && !ReferenceEquals(previous.Set, null))
-                        {
-                            ExceptionHelper.ThrowSyntaxError(_engine);
-                        }
-
-                        if (!ReferenceEquals(propDesc.Get, null) && !ReferenceEquals(previous.Get, null))
-                        {
-                            ExceptionHelper.ThrowSyntaxError(_engine);
-                        }
-                    }
-
-                    obj.DefineOwnProperty(propName, propDesc, false);
-                }
-                else
-                {
-                    // do faster direct set
-                    obj._properties[propName] = propDesc;
-                }
-            }
-
-            return obj;
-        }
-
-        /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.1
-        /// </summary>
-        /// <param name="memberExpression"></param>
-        /// <returns></returns>
-        public Reference EvaluateMemberExpression(MemberExpression memberExpression)
-        {
-            var baseReference = _engine.EvaluateExpression(memberExpression.Object);
-            var baseValue = _engine.GetValue(baseReference, false);
-
-            string propertyNameString;
-            if (!memberExpression.Computed) // index accessor ?
-            {
-                // we can take fast path without querying the engine again
-                propertyNameString = ((Identifier) memberExpression.Property).Name;
-            }
-            else
-            {
-                var propertyNameReference = _engine.EvaluateExpression(memberExpression.Property);
-                var propertyNameValue = _engine.GetValue(propertyNameReference, true);
-                propertyNameString = TypeConverter.ToPropertyKey(propertyNameValue);
-            }
-
-            TypeConverter.CheckObjectCoercible(_engine, baseValue, memberExpression, baseReference);
-
-            if (baseReference is Reference r)
-            {
-                _engine._referencePool.Return(r);
-            }
-            return _engine._referencePool.Rent(baseValue, propertyNameString, StrictModeScope.IsStrictModeCode);
-        }
-
-        public JsValue EvaluateFunctionExpression(IFunction functionExpression)
-        {
-            var funcEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _engine.ExecutionContext.LexicalEnvironment);
-            var envRec = (DeclarativeEnvironmentRecord) funcEnv._record;
-
-            var closure = new ScriptFunctionInstance(
-                _engine,
-                functionExpression,
-                funcEnv,
-                functionExpression.Strict);
-
-            if (!string.IsNullOrEmpty(functionExpression.Id?.Name))
-            {
-                envRec.CreateMutableBinding(functionExpression.Id.Name, closure);
-            }
-
-            return closure;
-        }
-
-        public JsValue EvaluateCallExpression(CallExpression callExpression)
-        {
-            var callee = _engine.EvaluateExpression(callExpression.Callee);
-            
-            if (_isDebugMode)
-            {
-                _engine.DebugHandler.AddToDebugCallStack(callExpression);
-            }
-
-            // todo: implement as in http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.4
-
-            var arguments = ArrayExt.Empty<JsValue>();
-            if (callExpression.Cached)
-            {
-                arguments = (JsValue[]) callExpression.CachedArguments;
-            }
-            else
-            {
-                var allLiteral = true;
-                if (callExpression.Arguments.Count > 0)
-                {
-                    arguments = _engine._jsValueArrayPool.RentArray(callExpression.Arguments.Count);
-                    BuildArguments(callExpression.Arguments, arguments, out allLiteral);
-                }
-
-                if (callExpression.CanBeCached)
-                {
-                    // The arguments array can be cached if they are all literals
-                    if (allLiteral)
-                    {
-                        callExpression.CachedArguments = arguments;
-                        callExpression.Cached = true;
-                    }
-                    else
-                    {
-                        callExpression.CanBeCached = false;
-                    }
-                }
-            }
-
-            var func = _engine.GetValue(callee, false);
-
-            var r = callee as Reference;
-            if (_maxRecursionDepth >= 0)
-            {
-                var stackItem = new CallStackElement(callExpression, func, r?._name ?? "anonymous function");
-
-                var recursionDepth = _engine.CallStack.Push(stackItem);
-
-                if (recursionDepth > _maxRecursionDepth)
-                {
-                    _engine.CallStack.Pop();
-                    ExceptionHelper.ThrowRecursionDepthOverflowException(_engine.CallStack, stackItem.ToString());
-                }
-            }
-
-            if (func._type == Types.Undefined)
-            {
-                ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Object has no method '{r.GetReferencedName()}'");
-            }
-
-            if (func._type != Types.Object)
-            {
-                if (_referenceResolver == null || !_referenceResolver.TryGetCallable(_engine, callee, out func))
-                {
-                    ExceptionHelper.ThrowTypeError(_engine,
-                        r == null ? "" : $"Property '{r.GetReferencedName()}' of object is not a function");
-                }
-            }
-
-            if (!(func is ICallable callable))
-            {
-                return ExceptionHelper.ThrowTypeError<JsValue>(_engine);
-            }
-
-            var thisObject = Undefined.Instance;
-            if (r != null)
-            {
-                if (r.IsPropertyReference())
-                {
-                    thisObject = r._baseValue;
-                }
-                else
-                {
-                    var env = (EnvironmentRecord) r._baseValue;
-                    thisObject = env.ImplicitThisValue();
-                }
-                
-                // is it a direct call to eval ? http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.1.1
-                if (r._name == "eval" && callable is EvalFunctionInstance instance)
-                {
-                    var value = instance.Call(thisObject, arguments, true);
-                    _engine._referencePool.Return(r);
-                    return value;
-                }
-            }
-
-            var result = callable.Call(thisObject, arguments);
-
-            if (_isDebugMode)
-            {
-                _engine.DebugHandler.PopDebugCallStack();
-            }
-
-            if (_maxRecursionDepth >= 0)
-            {
-                _engine.CallStack.Pop();
-            }
-
-            if (!callExpression.Cached && arguments.Length > 0)
-            {
-                _engine._jsValueArrayPool.ReturnArray(arguments);
-            }
-
-            _engine._referencePool.Return(r);
-            return result;
-        }
-
-        public JsValue EvaluateSequenceExpression(SequenceExpression sequenceExpression)
-        {
-            var result = Undefined.Instance;
-            var expressionsCount = sequenceExpression.Expressions.Count;
-            for (var i = 0; i < expressionsCount; i++)
-            {
-                var expression = sequenceExpression.Expressions[i];
-                result = _engine.GetValue(_engine.EvaluateExpression(expression), true);
-            }
-
-            return result;
-        }
-
-        public JsValue EvaluateUpdateExpression(UpdateExpression updateExpression)
-        {
-            var value = _engine.EvaluateExpression(updateExpression.Argument);
-
-            var r = (Reference) value;
-            r.AssertValid(_engine);
-
-            var oldValue = TypeConverter.ToNumber(_engine.GetValue(value, false));
-            double newValue = 0;
-            if (updateExpression.Operator == UnaryOperator.Increment)
-            {
-                newValue = oldValue + 1;
-            }
-            else if (updateExpression.Operator == UnaryOperator.Decrement)
-            {
-                newValue = oldValue - 1;
-            }
-            else
-            {
-                ExceptionHelper.ThrowArgumentException();
-            }
-
-            _engine.PutValue(r, newValue);
-            _engine._referencePool.Return(r);
-            return updateExpression.Prefix ? newValue : oldValue;
-        }
-
-        public JsValue EvaluateThisExpression(ThisExpression thisExpression)
-        {
-            return _engine.ExecutionContext.ThisBinding;
-        }
-
-        public JsValue EvaluateNewExpression(NewExpression newExpression)
-        {
-            var arguments = _engine._jsValueArrayPool.RentArray(newExpression.Arguments.Count);
-            BuildArguments(newExpression.Arguments, arguments, out _);
-
-            // todo: optimize by defining a common abstract class or interface
-            var callee = _engine.GetValue(_engine.EvaluateExpression(newExpression.Callee), true).TryCast<IConstructor>();
-
-            if (callee == null)
-            {
-                ExceptionHelper.ThrowTypeError(_engine, "The object can't be used as constructor.");
-            }
-
-            // construct the new instance using the Function's constructor method
-            var instance = callee.Construct(arguments);
-
-            _engine._jsValueArrayPool.ReturnArray(arguments);
-
-            return instance;
-        }
-
-        public JsValue EvaluateArrayExpression(ArrayExpression arrayExpression)
-        {
-            var elements = arrayExpression.Elements;
-            var count = elements.Count;
-            
-            var a = _engine.Array.ConstructFast((uint) count);
-            for (var n = 0; n < count; n++)
-            {
-                var expr = elements[n];
-                if (expr != null)
-                {
-                    var value = _engine.GetValue(_engine.EvaluateExpression((Expression) expr), true);
-                    a.SetIndexValue((uint) n, value, updateLength: false);
-                }
-            }
-            return a;
-        }
-
-        public JsValue EvaluateUnaryExpression(UnaryExpression unaryExpression)
-        {
-            var value = _engine.EvaluateExpression(unaryExpression.Argument);
-
-            switch (unaryExpression.Operator)
-            {
-                case UnaryOperator.Plus:
-                    return TypeConverter.ToNumber(_engine.GetValue(value, true));
-
-                case UnaryOperator.Minus:
-                    var n = TypeConverter.ToNumber(_engine.GetValue(value, true));
-                    return double.IsNaN(n) ? double.NaN : n*-1;
-
-                case UnaryOperator.BitwiseNot:
-                    return ~TypeConverter.ToInt32(_engine.GetValue(value, true));
-
-                case UnaryOperator.LogicalNot:
-                    return !TypeConverter.ToBoolean(_engine.GetValue(value, true));
-
-                case UnaryOperator.Delete:
-                    var r = value as Reference;
-                    if (r == null)
-                    {
-                        return true;
-                    }
-                    if (r.IsUnresolvableReference())
-                    {
-                        if (r._strict)
-                        {
-                            ExceptionHelper.ThrowSyntaxError(_engine);
-                        }
-
-                        _engine._referencePool.Return(r);
-                        return true;
-                    }
-                    if (r.IsPropertyReference())
-                    {
-                        var o = TypeConverter.ToObject(_engine, r.GetBase());
-                        var jsValue = o.Delete(r._name, r._strict);
-                        _engine._referencePool.Return(r);
-                        return jsValue;
-                    }
-                    if (r._strict)
-                    {
-                        ExceptionHelper.ThrowSyntaxError(_engine);
-                    }
-
-                    var bindings = r.GetBase().TryCast<EnvironmentRecord>();
-                    var referencedName = r.GetReferencedName();
-                    _engine._referencePool.Return(r);
-
-                    return bindings.DeleteBinding(referencedName);
-
-                case UnaryOperator.Void:
-                    _engine.GetValue(value, true);
-                    return Undefined.Instance;
-
-                case UnaryOperator.TypeOf:
-                    r = value as Reference;
-                    if (r != null)
-                    {
-                        if (r.IsUnresolvableReference())
-                        {
-                            _engine._referencePool.Return(r);
-                            return "undefined";
-                        }
-                    }
-
-                    var v = _engine.GetValue(value, true);
-
-                    if (v.IsUndefined())
-                    {
-                        return "undefined";
-                    }
-                    if (v.IsNull())
-                    {
-                        return "object";
-                    }
-
-                    switch (v._type)
-                    {
-                        case Types.Boolean: return "boolean";
-                        case Types.Number: return "number";
-                        case Types.String: return "string";
-                    }
-                    if (v.TryCast<ICallable>() != null)
-                    {
-                        return "function";
-                    }
-                    return "object";
-
-                default:
-                    ExceptionHelper.ThrowArgumentException();
-                    return null;
-            }
-        }
-
-        private void BuildArguments(
-            List<ArgumentListElement> expressionArguments, 
-            JsValue[] targetArray,
-            out bool cacheable)
-        {
-            cacheable = true;
-            for (var i = 0; i < (uint) targetArray.Length; i++)
-            {
-                var argument = (Expression) expressionArguments[i];
-                targetArray[i] = _engine.GetValue(_engine.EvaluateExpression(argument), true);
-                cacheable &= argument is Literal;
-            }
-        }
-    }
-}

+ 11 - 0
Jint/Runtime/Interop/DelegateWrapper.cs

@@ -37,6 +37,17 @@ namespace Jint.Runtime.Interop
         public override JsValue Call(JsValue thisObject, JsValue[] jsArguments)
         {
             var parameterInfos = _d.Method.GetParameters();
+            
+#if NETFRAMEWORK
+            if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(System.Runtime.CompilerServices.Closure))
+            {
+                var reducedLength = parameterInfos.Length - 1;
+                var reducedParameterInfos = new ParameterInfo[reducedLength];
+                Array.Copy(parameterInfos, 1, reducedParameterInfos, 0, reducedLength);
+                parameterInfos = reducedParameterInfos;
+            }
+#endif
+
             int delegateArgumentsCount = parameterInfos.Length;
             int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount;
 

+ 1 - 1
Jint/Runtime/Interop/IObjectConverter.cs

@@ -7,6 +7,6 @@ namespace Jint.Runtime.Interop
     /// </summary>
     public interface IObjectConverter
     {
-        bool TryConvert(object value, out JsValue result);
+        bool TryConvert(Engine engine, object value, out JsValue result);
     }
 }

+ 27 - 38
Jint/Runtime/Interop/MethodInfoFunctionInstance.cs

@@ -1,5 +1,4 @@
-using System;
-using System.Globalization;
+using System.Globalization;
 using System.Linq.Expressions;
 using System.Reflection;
 using Jint.Native;
@@ -25,11 +24,18 @@ namespace Jint.Runtime.Interop
 
         public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] jsArguments)
         {
-            var arguments = ProcessParamsArrays(jsArguments, methodInfos);
+            JsValue[] ArgumentProvider(MethodInfo method, bool hasParams) =>
+                hasParams
+                    ? ProcessParamsArrays(jsArguments, method)
+                    : jsArguments;
+
             var converter = Engine.ClrTypeConverter;
 
-            foreach (var method in TypeConverter.FindBestMatch(methodInfos, arguments))
+            foreach (var tuple in TypeConverter.FindBestMatch(_engine, methodInfos, ArgumentProvider))
             {
+                var method = tuple.Item1;
+                var arguments = tuple.Item2;
+
                 var parameters = new object[arguments.Length];
                 var methodParameters = method.GetParameters();
                 var argumentsMatch = true;
@@ -99,49 +105,32 @@ namespace Jint.Runtime.Interop
         }
 
         /// <summary>
-        /// Reduces a flat list of parameters to a params array
+        /// Reduces a flat list of parameters to a params array, if needed
         /// </summary>
-        private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodInfo[] methodInfos)
+        private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodInfo methodInfo)
         {
-            foreach (var methodInfo in methodInfos)
-            {
-                var parameters = methodInfo.GetParameters();
+            var parameters = methodInfo.GetParameters();
 
-                bool hasParamArrayAttribute = false;
-                foreach (var parameter in parameters)
-                {
-                    if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute)))
-                    {
-                        hasParamArrayAttribute = true;
-                        break;
-                    }
-                }
-                if (!hasParamArrayAttribute)
-                    continue;
+            var nonParamsArgumentsCount = parameters.Length - 1;
+            if (jsArguments.Length < nonParamsArgumentsCount)
+                return jsArguments;
 
-                var nonParamsArgumentsCount = parameters.Length - 1;
-                if (jsArguments.Length < nonParamsArgumentsCount)
-                    continue;
-
-                var argsToTransform = jsArguments.Skip(nonParamsArgumentsCount);
+            var argsToTransform = jsArguments.Skip(nonParamsArgumentsCount);
 
-                if (argsToTransform.Length == 1 && argsToTransform[0].IsArray())
-                    continue;
+            if (argsToTransform.Length == 1 && argsToTransform[0].IsArray())
+                return jsArguments;
 
-                var jsArray = Engine.Array.Construct(Arguments.Empty);
-                Engine.Array.PrototypeObject.Push(jsArray, argsToTransform);
+            var jsArray = Engine.Array.Construct(Arguments.Empty);
+            Engine.Array.PrototypeObject.Push(jsArray, argsToTransform);
 
-                var newArgumentsCollection = new JsValue[nonParamsArgumentsCount + 1];
-                for (var j = 0; j < nonParamsArgumentsCount; ++j)
-                {
-                    newArgumentsCollection[j] = jsArguments[j];
-                }
-
-                newArgumentsCollection[nonParamsArgumentsCount] = jsArray;
-                return newArgumentsCollection;
+            var newArgumentsCollection = new JsValue[nonParamsArgumentsCount + 1];
+            for (var j = 0; j < nonParamsArgumentsCount; ++j)
+            {
+                newArgumentsCollection[j] = jsArguments[j];
             }
 
-            return jsArguments;
+            newArgumentsCollection[nonParamsArgumentsCount] = jsArray;
+            return newArgumentsCollection;
         }
 
     }

+ 53 - 35
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Reflection;
 using Jint.Native;
@@ -17,10 +18,20 @@ namespace Jint.Runtime.Interop
             : base(engine)
         {
             Target = obj;
+            if (obj is ICollection collection)
+            {
+                IsArrayLike = true;
+                // create a forwarder to produce length from Count
+                var functionInstance = new ClrFunctionInstance(engine, "length", (thisObj, arguments) => collection.Count);
+                var descriptor = new GetSetPropertyDescriptor(functionInstance, Undefined, PropertyFlag.AllForbidden);
+                AddProperty("length", descriptor);
+            }
         }
 
         public object Target { get; }
 
+        internal override bool IsArrayLike { get; }
+
         public override void Put(string propertyName, JsValue value, bool throwOnError)
         {
             if (!CanPut(propertyName))
@@ -68,55 +79,62 @@ namespace Jint.Runtime.Interop
             AddProperty(propertyName, descriptor);
             return descriptor;
         }
-
+        
         private static Func<Engine, object, PropertyDescriptor> ResolveProperty(Type type, string propertyName)
         {
-            // look for a property
-            PropertyInfo property = null;
-            foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+            var isNumber = uint.TryParse(propertyName, out _);
+
+            // properties and fields cannot be numbers
+            if (!isNumber)
             {
-                if (EqualsIgnoreCasing(p.Name, propertyName))
+                // look for a property
+                PropertyInfo property = null;
+                foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
                 {
-                    property = p;
-                    break;
+                    if (EqualsIgnoreCasing(p.Name, propertyName))
+                    {
+                        property = p;
+                        break;
+                    }
                 }
-            }
 
-            if (property != null)
-            {
-                return (engine, target) => new PropertyInfoDescriptor(engine, property, target);
-            }
+                if (property != null)
+                {
+                    return (engine, target) => new PropertyInfoDescriptor(engine, property, target);
+                }
 
-            // look for a field
-            FieldInfo field = null;
-            foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
-            {
-                if (EqualsIgnoreCasing(f.Name, propertyName))
+                // look for a field
+                FieldInfo field = null;
+                foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
                 {
-                    field = f;
-                    break;
+                    if (EqualsIgnoreCasing(f.Name, propertyName))
+                    {
+                        field = f;
+                        break;
+                    }
                 }
-            }
 
-            if (field != null)
-            {
-                return (engine, target) => new FieldInfoDescriptor(engine, field, target);
-            }
+                if (field != null)
+                {
+                    return (engine, target) => new FieldInfoDescriptor(engine, field, target);
+                }
+                
+                // if no properties were found then look for a method
+                List<MethodInfo> methods = null;
+                foreach (var m in type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+                {
+                    if (EqualsIgnoreCasing(m.Name, propertyName))
+                    {
+                        methods = methods ?? new List<MethodInfo>();
+                        methods.Add(m);
+                    }
+                }
 
-            // if no properties were found then look for a method
-            List<MethodInfo> methods = null;
-            foreach (var m in type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
-            {
-                if (EqualsIgnoreCasing(m.Name, propertyName))
+                if (methods?.Count > 0)
                 {
-                    methods = methods ?? new List<MethodInfo>();
-                    methods.Add(m);
+                    return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, methods.ToArray()), PropertyFlag.OnlyEnumerable);
                 }
-            }
 
-            if (methods?.Count > 0)
-            {
-                return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, methods.ToArray()), PropertyFlag.OnlyEnumerable);
             }
 
             // if no methods are found check if target implemented indexing

+ 11 - 9
Jint/Runtime/Interop/TypeReference.cs

@@ -53,8 +53,10 @@ namespace Jint.Runtime.Interop
 
             var constructors = ReferenceType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
 
-            foreach (var method in TypeConverter.FindBestMatch(constructors, arguments))
+            foreach (var tuple in TypeConverter.FindBestMatch(_engine, constructors, (info, b) => arguments))
             {
+                var method = tuple.Item1;
+
                 var parameters = new object[arguments.Length];
                 var methodParameters = method.GetParameters();
                 try
@@ -76,9 +78,9 @@ namespace Jint.Runtime.Interop
                         }
                     }
 
-                    var constructor = (ConstructorInfo)method;
+                    var constructor = (ConstructorInfo) method;
                     var instance = constructor.Invoke(parameters);
-                    var result = TypeConverter.ToObject(Engine, JsValue.FromObject(Engine, instance));
+                    var result = TypeConverter.ToObject(Engine, FromObject(Engine, instance));
 
                     // todo: cache method info
 
@@ -96,14 +98,14 @@ namespace Jint.Runtime.Interop
 
         public override bool HasInstance(JsValue v)
         {
-            ObjectWrapper wrapper = v.As<ObjectWrapper>();
-
-            if (ReferenceEquals(wrapper, null))
+            if (v.IsObject())
             {
-                return base.HasInstance(v);
+                var wrapper = v.AsObject() as IObjectWrapper;
+                if (wrapper != null)
+                    return wrapper.Target.GetType() == ReferenceType;
             }
 
-            return wrapper.Target.GetType() == ReferenceType;
+            return base.HasInstance(v);
         }
 
         public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
@@ -196,7 +198,7 @@ namespace Jint.Runtime.Interop
                 }
             }
 
-            if (methodInfo?.Count == 0)
+            if (methodInfo == null || methodInfo.Count == 0)
             {
                 return PropertyDescriptor.Undefined;
             }

+ 269 - 0
Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs

@@ -0,0 +1,269 @@
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Array;
+using Jint.Native.Function;
+using Jint.Native.Iterator;
+using Jint.Runtime.Environments;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal sealed class BindingPatternAssignmentExpression : JintExpression
+    {
+        private readonly BindingPattern _pattern;
+        private readonly JintExpression _right;
+
+        public BindingPatternAssignmentExpression(
+            Engine engine, 
+            AssignmentExpression expression) : base(engine, expression)
+        {
+            _pattern = (BindingPattern) expression.Left;
+            _right = Build(engine, expression.Right);
+        }
+
+        protected override object EvaluateInternal()
+        {
+            var rightValue = _right.GetValue();
+            ProcessPatterns(_engine, _pattern, rightValue);
+            return JsValue.Undefined;
+        }
+
+        internal static void ProcessPatterns(Engine engine, BindingPattern pattern, JsValue argument)
+        {
+            if (pattern is ArrayPattern ap)
+            {
+                HandleArrayPattern(engine, ap, argument);
+            }
+            else if (pattern is ObjectPattern op)
+            {
+                HandleObjectPattern(engine, op, argument);
+            }
+        }
+
+        private static void HandleArrayPattern(Engine engine, ArrayPattern pattern, JsValue argument)
+        {
+            bool ConsumeFromIterator(IIterator it, out JsValue value, out bool done)
+            {
+                var item = it.Next();
+                value = JsValue.Undefined;
+                done = false;
+
+                if (item.TryGetValue("done", out var d) && d.AsBoolean())
+                {
+                    done = true;
+                    return false;
+                }
+
+                if (!item.TryGetValue("value", out value))
+                {
+                    return false;
+                }
+
+                return true;
+            }
+
+            var obj = TypeConverter.ToObject(engine, argument);
+
+            ArrayPrototype.ArrayOperations arrayOperations = null;
+            IIterator iterator = null;
+            if (obj.IsArrayLike)
+            {
+                arrayOperations = ArrayPrototype.ArrayOperations.For(obj);
+            }
+            else
+            {
+                if (!obj.TryGetIterator(engine, out iterator))
+                {
+                    ExceptionHelper.ThrowTypeError(engine);
+                    return;
+                }
+            }
+
+            bool iterationEnded = false;
+            for (uint i = 0; i < pattern.Elements.Count; i++)
+            {
+                var left = pattern.Elements[(int) i];
+                if (left is Identifier identifier)
+                {
+                    JsValue value;
+                    if (arrayOperations != null)
+                    {
+                        arrayOperations.TryGetValue(i, out value);
+                    }
+                    else
+                    {
+                        if (!ConsumeFromIterator(iterator, out value, out iterationEnded))
+                        {
+                            break;
+                        }
+                    }
+                    AssignToIdentifier(engine, identifier.Name, value);
+                }
+                else if (left is BindingPattern bindingPattern)
+                {
+                    JsValue value;
+                    if (arrayOperations != null)
+                    {
+                        arrayOperations.TryGetValue(i, out value);
+                    }
+                    else
+                    {
+                        value = iterator.Next();
+                    }
+                    ProcessPatterns(engine, bindingPattern, value);
+                }
+                else if (left is IArrayPatternElement arrayPatternElement)
+                {
+                    if (arrayPatternElement is RestElement restElement)
+                    {
+                        ArrayInstance array;
+                        if (arrayOperations != null)
+                        {
+                            var length = arrayOperations.GetLength();
+                            array = engine.Array.ConstructFast(length - i);
+                            for (uint j = i; j < length; ++j)
+                            {
+                                arrayOperations.TryGetValue(j, out var indexValue);
+                                array.SetIndexValue(j - i, indexValue, updateLength: false);
+                            }
+                        }
+                        else
+                        {
+                            array = engine.Array.ConstructFast(0);
+                            var protocol = new ArrayConstructor.ArrayProtocol(engine, obj, array, iterator, null);
+                            protocol.Execute();
+                        }
+
+                        if (restElement.Argument is Identifier leftIdentifier)
+                        {
+                            AssignToIdentifier(engine, leftIdentifier.Name, array);
+                        }
+                        else if (restElement.Argument is BindingPattern bp)
+                        {
+                            ProcessPatterns(engine, bp, array);
+                        }
+                    }
+                    else if (arrayPatternElement is AssignmentPattern assignmentPattern)
+                    {
+                        JsValue value;
+                        if (arrayOperations != null)
+                        {
+                            arrayOperations.TryGetValue(i, out value);
+                        }
+                        else
+                        {
+                            ConsumeFromIterator(iterator, out value, out iterationEnded);
+                        }
+
+                        if (value.IsUndefined()
+                            && assignmentPattern.Right is Expression expression)
+                        {
+                            var jintExpression = Build(engine, expression);
+
+                            value = jintExpression.GetValue();
+                        }
+
+                        if (assignmentPattern.Left is Identifier leftIdentifier)
+                        {
+                            if (assignmentPattern.Right.IsFunctionWithName())
+                            {
+                                ((FunctionInstance) value).SetFunctionName(leftIdentifier.Name);
+                            }
+                            AssignToIdentifier(engine, leftIdentifier.Name, value);
+                        }
+                        else if (assignmentPattern.Left is BindingPattern bp)
+                        {
+                            ProcessPatterns(engine, bp, value);
+                        }
+                    }
+                    else
+                    {
+                        ExceptionHelper.ThrowArgumentOutOfRangeException("pattern", "Unable to determine how to handle array pattern element");
+                        break;
+                    }
+                }
+            }
+
+            if (!iterationEnded)
+            {
+                iterator?.Return();
+            }
+        }
+
+        private static void HandleObjectPattern(Engine engine, ObjectPattern pattern, JsValue argument)
+        {
+            var source = TypeConverter.ToObject(engine, argument);
+            for (uint i = 0; i < pattern.Properties.Count; i++)
+            {
+                var left = pattern.Properties[(int) i];
+                string sourceKey;
+                Identifier identifier = left.Key as Identifier;
+                if (identifier == null)
+                {
+                    var keyExpression = Build(engine, left.Key);
+                    sourceKey = TypeConverter.ToPropertyKey(keyExpression.GetValue());
+                }
+                else
+                {
+                    sourceKey = identifier.Name;
+                }
+
+                source.TryGetValue(sourceKey, out var value);
+                if (left.Value is AssignmentPattern assignmentPattern)
+                {
+                    if (value.IsUndefined() && assignmentPattern.Right is Expression expression)
+                    {
+                        var jintExpression = Build(engine, expression);
+
+                        value = jintExpression.GetValue();
+                    }
+                    
+                    if (assignmentPattern.Left is BindingPattern bp)
+                    {
+                        ProcessPatterns(engine, bp, value);
+                        continue;
+                    }
+                    
+                    var target = assignmentPattern.Left as Identifier ?? identifier;
+
+                    if (assignmentPattern.Right.IsFunctionWithName())
+                    {
+                        ((FunctionInstance) value).SetFunctionName(target.Name);
+                    }
+
+                    AssignToIdentifier(engine, target.Name, value);
+                }
+                else if (left.Value is BindingPattern bindingPattern)
+                {
+                    ProcessPatterns(engine, bindingPattern, value);
+                }
+                else
+                {
+                    var target = left.Value as Identifier ?? identifier;
+                    AssignToIdentifier(engine, target.Name, value);
+                }
+            }
+        }
+
+        private static void AssignToIdentifier(Engine engine,
+            string name,
+            JsValue rval)
+        {
+            var env = engine.ExecutionContext.LexicalEnvironment;
+
+            var strict = StrictModeScope.IsStrictModeCode;
+            if (LexicalEnvironment.TryGetIdentifierEnvironmentWithBindingValue(
+                env,
+                name,
+                strict,
+                out var environmentRecord,
+                out _))
+            {
+                environmentRecord.SetMutableBinding(name, rval, strict);
+            }
+            else
+            {
+                env._record.CreateMutableBinding(name, rval);
+            }
+        }
+    }
+}

+ 113 - 0
Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs

@@ -0,0 +1,113 @@
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Array;
+using Jint.Native.Iterator;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal sealed class JintArrayExpression : JintExpression
+    {
+        private JintExpression[] _expressions;
+        private bool _hasSpreads;
+
+        public JintArrayExpression(Engine engine, ArrayExpression expression) : base(engine, expression)
+        {
+            _initialized = false;
+        }
+
+        protected override void Initialize()
+        {
+            var node = (ArrayExpression) _expression;
+            _expressions = new JintExpression[node.Elements.Count];
+            for (var n = 0; n < _expressions.Length; n++)
+            {
+                var expr = node.Elements[n];
+                if (expr != null)
+                {
+                    var expression = Build(_engine, (Expression) expr);
+                    _expressions[n] = expression;
+                    _hasSpreads |= expression is JintSpreadExpression;
+                }
+            }
+
+            // we get called from nested spread expansion in call
+            _initialized = true;
+        }
+
+        protected override object EvaluateInternal()
+        {
+            var a = _engine.Array.ConstructFast(_hasSpreads ? 0 : (uint) _expressions.Length);
+            var expressions = _expressions;
+
+            uint arrayIndexCounter = 0;
+            for (uint i = 0; i < (uint) expressions.Length; i++)
+            {
+                var expr = expressions[i];
+                if (expr == null)
+                {
+                    arrayIndexCounter++;
+                    continue;
+                }
+
+                if (_hasSpreads && expr is JintSpreadExpression jse)
+                {
+                    jse.GetValueAndCheckIterator(out var objectInstance, out var iterator);
+                    // optimize for array
+                    if (objectInstance is ArrayInstance ai)
+                    {
+                        var length = ai.GetLength();
+                        var newLength = arrayIndexCounter + length;
+                        a.EnsureCapacity(newLength);
+                        a.CopyValues(ai, sourceStartIndex: 0, targetStartIndex: arrayIndexCounter, length);
+                        arrayIndexCounter += length;
+                        a.SetLength(newLength);
+                    }
+                    else
+                    {
+                        var protocol = new ArraySpreadProtocol(_engine, a, iterator, arrayIndexCounter);
+                        protocol.Execute();
+                        arrayIndexCounter += protocol._addedCount;
+                    }
+                }
+                else
+                {
+                    var value = expr.GetValue();
+                    a.SetIndexValue(arrayIndexCounter++, value, updateLength: false);
+                }
+            }
+
+            if (_hasSpreads)
+            {
+                a.SetLength(arrayIndexCounter);
+            }
+
+            return a;
+        }
+
+        private sealed class ArraySpreadProtocol : IteratorProtocol
+        {
+            private readonly ArrayInstance _instance;
+            internal long _index;
+            internal uint _addedCount = 0;
+
+            public ArraySpreadProtocol(
+                Engine engine,
+                ArrayInstance instance,
+                IIterator iterator,
+                long startIndex) : base(engine, iterator, 0)
+            {
+                _instance = instance;
+                _index = startIndex - 1;
+            }
+
+            protected override void ProcessItem(JsValue[] args, JsValue currentValue)
+            {
+                _index++;
+                _addedCount++;
+                var jsValue = ExtractValueFromIteratorInstance(currentValue);
+
+                _instance.SetIndexValue((uint) _index, jsValue, updateLength: false);
+            }
+        }
+    }
+}

+ 31 - 0
Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs

@@ -0,0 +1,31 @@
+using Esprima.Ast;
+using Jint.Native.Function;
+using Jint.Runtime.Environments;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal sealed class JintArrowFunctionExpression : JintExpression
+    {
+        private readonly JintFunctionDefinition _function;
+
+        public JintArrowFunctionExpression(Engine engine, IFunction function)
+            : base(engine, ArrowParameterPlaceHolder.Empty)
+        {
+
+            _function = new JintFunctionDefinition(engine, function);
+        }
+
+        protected override object EvaluateInternal()
+        {
+            var funcEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _engine.ExecutionContext.LexicalEnvironment);
+
+            var closure = new ArrowFunctionInstance(
+                _engine,
+                _function,
+                funcEnv,
+                _function._strict);
+
+            return closure;
+        }
+    }
+}

+ 230 - 0
Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs

@@ -0,0 +1,230 @@
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Function;
+using Jint.Runtime.Environments;
+using Jint.Runtime.References;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal sealed class JintAssignmentExpression : JintExpression
+    {
+        private readonly JintExpression _left;
+        private readonly JintExpression _right;
+
+        private JintAssignmentExpression(Engine engine, AssignmentExpression expression) : base(engine, expression)
+        {
+            _left = Build(engine, (Expression) expression.Left);
+            _right = Build(engine, expression.Right);
+        }
+
+        internal static JintExpression Build(Engine engine, AssignmentExpression expression)
+        {
+            if (expression.Operator == AssignmentOperator.Assign)
+            {
+                if (expression.Left is Expression)
+                {
+                    return new SimpleAssignmentExpression(engine, expression);
+                }
+
+                if (expression.Left is BindingPattern)
+                {
+                    return new BindingPatternAssignmentExpression(engine, expression);
+                }
+            }
+
+            return new JintAssignmentExpression(engine, expression);
+        }
+
+        protected override object EvaluateInternal()
+        {
+            var lref = _left.Evaluate() as Reference ?? ExceptionHelper.ThrowReferenceError<Reference>(_engine);
+            JsValue rval = _right.GetValue();
+
+            JsValue lval = _engine.GetValue(lref, false);
+
+            var expression = (AssignmentExpression) _expression;
+            switch (expression.Operator)
+            {
+                case AssignmentOperator.PlusAssign:
+                    var lprim = TypeConverter.ToPrimitive(lval);
+                    var rprim = TypeConverter.ToPrimitive(rval);
+                    if (lprim.IsString() || rprim.IsString())
+                    {
+                        if (!(lprim is JsString jsString))
+                        {
+                            jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim));
+                        }
+
+                        lval = jsString.Append(rprim);
+                    }
+                    else
+                    {
+                        lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
+                    }
+
+                    break;
+
+                case AssignmentOperator.MinusAssign:
+                    lval = TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval);
+                    break;
+
+                case AssignmentOperator.TimesAssign:
+                    if (lval.IsUndefined() || rval.IsUndefined())
+                    {
+                        lval = Undefined.Instance;
+                    }
+                    else
+                    {
+                        lval = TypeConverter.ToNumber(lval) * TypeConverter.ToNumber(rval);
+                    }
+
+                    break;
+
+                case AssignmentOperator.DivideAssign:
+                    lval = Divide(lval, rval);
+                    break;
+
+                case AssignmentOperator.ModuloAssign:
+                    if (lval.IsUndefined() || rval.IsUndefined())
+                    {
+                        lval = Undefined.Instance;
+                    }
+                    else
+                    {
+                        lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
+                    }
+
+                    break;
+
+                case AssignmentOperator.BitwiseAndAssign:
+                    lval = TypeConverter.ToInt32(lval) & TypeConverter.ToInt32(rval);
+                    break;
+
+                case AssignmentOperator.BitwiseOrAssign:
+                    lval = TypeConverter.ToInt32(lval) | TypeConverter.ToInt32(rval);
+                    break;
+
+                case AssignmentOperator.BitwiseXOrAssign:
+                    lval = TypeConverter.ToInt32(lval) ^ TypeConverter.ToInt32(rval);
+                    break;
+
+                case AssignmentOperator.LeftShiftAssign:
+                    lval = TypeConverter.ToInt32(lval) << (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                    break;
+
+                case AssignmentOperator.RightShiftAssign:
+                    lval = TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                    break;
+
+                case AssignmentOperator.UnsignedRightShiftAssign:
+                    lval = (uint) TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                    break;
+
+                default:
+                    ExceptionHelper.ThrowNotImplementedException();
+                    return null;
+            }
+
+            _engine.PutValue(lref, lval);
+
+            _engine._referencePool.Return(lref);
+            return lval;
+        }
+
+        internal sealed class SimpleAssignmentExpression : JintExpression
+        {
+            private readonly JintExpression _left;
+            private readonly JintExpression _right;
+
+            private readonly JintIdentifierExpression _leftIdentifier;
+            private readonly bool _evalOrArguments;
+            private readonly ArrayPattern _arrayPattern;
+
+            public SimpleAssignmentExpression(Engine engine, AssignmentExpression expression) : base(engine, expression)
+            {
+                if (expression.Left is ArrayPattern arrayPattern)
+                {
+                    _arrayPattern = arrayPattern;
+                }
+                else
+                {
+                    _left = Build(engine, (Expression) expression.Left);
+                    _leftIdentifier = _left as JintIdentifierExpression;
+                    _evalOrArguments = _leftIdentifier?._expressionName == "eval" || _leftIdentifier?._expressionName == "arguments";
+                }
+
+                _right = Build(engine, expression.Right);
+            }
+
+            protected override object EvaluateInternal()
+            {
+                JsValue rval = null;
+                if (_leftIdentifier != null)
+                {
+                    rval = AssignToIdentifier(_engine, _leftIdentifier, _right, _evalOrArguments);
+                }
+                else if (_arrayPattern != null)
+                {
+                    foreach (var element in _arrayPattern.Elements)
+                    {
+                        AssignToIdentifier(_engine, _leftIdentifier, _right, false);
+                    }
+                }
+
+                return rval ?? SetValue();
+            }
+
+            private JsValue SetValue()
+            {
+                // slower version
+                var lref = _left.Evaluate() as Reference ?? ExceptionHelper.ThrowReferenceError<Reference>(_engine);
+                lref.AssertValid(_engine);
+
+                var rval = _right.GetValue();
+
+                if (_right._expression.IsFunctionWithName())
+                {
+                    ((FunctionInstance) rval).SetFunctionName(lref.GetReferencedName());
+                }
+
+                _engine.PutValue(lref, rval);
+                _engine._referencePool.Return(lref);
+                return rval;
+            }
+
+            internal static JsValue AssignToIdentifier(
+                Engine engine,
+                JintIdentifierExpression left,
+                JintExpression right,
+                bool hasEvalOrArguments)
+            {
+                var env = engine.ExecutionContext.LexicalEnvironment;
+                var strict = StrictModeScope.IsStrictModeCode;
+                if (LexicalEnvironment.TryGetIdentifierEnvironmentWithBindingValue(
+                    env,
+                    left._expressionName,
+                    strict,
+                    out var environmentRecord,
+                    out _))
+                {
+                    if (strict && hasEvalOrArguments)
+                    {
+                        ExceptionHelper.ThrowSyntaxError(engine);
+                    }
+
+                    var rval = right.GetValue();
+
+                    if (right._expression.IsFunctionWithName())
+                    {
+                        ((FunctionInstance) rval).SetFunctionName(left._expressionName);
+                    }
+
+                    environmentRecord.SetMutableBinding(left._expressionName, rval, strict);
+                    return rval;
+                }
+
+                return null;
+            }
+        }
+    }
+}

+ 313 - 0
Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs

@@ -0,0 +1,313 @@
+using System;
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime.Interop;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal abstract class JintBinaryExpression : JintExpression
+    {
+        private readonly JintExpression _left;
+        private readonly JintExpression _right;
+        private readonly BinaryOperator _operatorType;
+
+        private JintBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+        {
+            _left = Build(engine, expression.Left);
+            _right = Build(engine, expression.Right);
+            _operatorType = expression.Operator;
+        }
+
+        internal static JintExpression Build(Engine engine, BinaryExpression expression)
+        {
+            JintExpression result;
+            switch (expression.Operator)
+            {
+                case BinaryOperator.StrictlyEqual:
+                    result = new StrictlyEqualBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.StricltyNotEqual:
+                    result = new StrictlyNotEqualBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.Less:
+                    result = new LessBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.Greater:
+                    result = new GreaterBinaryExpression(engine, expression);
+                    break;
+                default:
+                    result = new JintGenericBinaryExpression(engine, expression);
+                    break;
+            }
+
+            if (expression.Left.Type == Nodes.Literal
+                && expression.Right.Type == Nodes.Literal
+                && expression.Operator != BinaryOperator.InstanceOf
+                && expression.Operator != BinaryOperator.In)
+            {
+                // calculate eagerly
+                // TODO result = new JintConstantExpression(engine, (JsValue) result.Evaluate());
+            }
+
+            return result;
+        }
+
+        public static bool StrictlyEqual(JsValue x, JsValue y)
+        {
+            var typeX = x.Type;
+            var typeY = y.Type;
+
+            if (typeX != typeY)
+            {
+                return false;
+            }
+
+            switch (typeX)
+            {
+                case Types.Undefined:
+                case Types.Null:
+                    return true;
+                case Types.Number:
+                    var nx = ((JsNumber) x)._value;
+                    var ny = ((JsNumber) y)._value;
+                    return !double.IsNaN(nx) && !double.IsNaN(ny) && nx == ny;
+                case Types.String:
+                    return x.AsStringWithoutTypeCheck() == y.AsStringWithoutTypeCheck();
+                case Types.Boolean:
+                    return ((JsBoolean) x)._value == ((JsBoolean) y)._value;
+                case Types.Object when x.AsObject() is IObjectWrapper xw:
+                    var yw = y.AsObject() as IObjectWrapper;
+                    if (yw == null)
+                        return false;
+                    return Equals(xw.Target, yw.Target);
+                case Types.None:
+                    return true;
+                default:
+                    return x == y;
+            }
+        }
+
+        private sealed class JintGenericBinaryExpression : JintBinaryExpression
+        {
+            private readonly Func<JsValue, JsValue, JsValue> _operator;
+
+            public JintGenericBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+                switch (_operatorType)
+                {
+                    case BinaryOperator.Plus:
+                        _operator = (left, right) =>
+                        {
+                            var lprim = TypeConverter.ToPrimitive(left);
+                            var rprim = TypeConverter.ToPrimitive(right);
+                            return lprim.IsString() || rprim.IsString()
+                                ? (JsValue) JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim))
+                                : JsNumber.Create(TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim));
+                        };
+                        break;
+
+                    case BinaryOperator.Minus:
+                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right));
+                        break;
+
+                    case BinaryOperator.Times:
+                        _operator = (left, right) => left.IsUndefined() || right.IsUndefined()
+                            ? Undefined.Instance
+                            : JsNumber.Create(TypeConverter.ToNumber(left) * TypeConverter.ToNumber(right));
+                        break;
+
+                    case BinaryOperator.Divide:
+                        _operator = Divide;
+                        break;
+
+                    case BinaryOperator.Modulo:
+                        _operator = (left, right) => left.IsUndefined() || right.IsUndefined()
+                            ? Undefined.Instance
+                            : TypeConverter.ToNumber(left) % TypeConverter.ToNumber(right);
+
+                        break;
+
+                    case BinaryOperator.Equal:
+                        _operator = (left, right) => Equal(left, right)
+                            ? JsBoolean.True
+                            : JsBoolean.False;
+                        break;
+
+                    case BinaryOperator.NotEqual:
+                        _operator = (left, right) => Equal(left, right) ? JsBoolean.False : JsBoolean.True;
+                        break;
+
+                    case BinaryOperator.GreaterOrEqual:
+                        _operator = (left, right) =>
+                        {
+                            var value = Compare(left, right);
+                            if (value.IsUndefined() || ((JsBoolean) value)._value)
+                            {
+                                value = JsBoolean.False;
+                            }
+                            else
+                            {
+                                value = JsBoolean.True;
+                            }
+
+                            return value;
+                        };
+
+                        break;
+
+                    case BinaryOperator.LessOrEqual:
+                        _operator = (left, right) =>
+                        {
+                            var value = Compare(right, left, false);
+                            if (value.IsUndefined() || ((JsBoolean) value)._value)
+                            {
+                                value = JsBoolean.False;
+                            }
+                            else
+                            {
+                                value = JsBoolean.True;
+                            }
+
+                            return value;
+                        };
+
+                        break;
+
+                    case BinaryOperator.BitwiseAnd:
+                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) & TypeConverter.ToInt32(right));
+                        break;
+
+                    case BinaryOperator.BitwiseOr:
+                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) | TypeConverter.ToInt32(right));
+                        break;
+
+                    case BinaryOperator.BitwiseXOr:
+                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) ^ TypeConverter.ToInt32(right));
+                        break;
+
+                    case BinaryOperator.LeftShift:
+                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) << (int) (TypeConverter.ToUint32(right) & 0x1F));
+                        break;
+
+                    case BinaryOperator.RightShift:
+                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
+                        break;
+
+                    case BinaryOperator.UnsignedRightShift:
+                        _operator = (left, right) => JsNumber.Create((uint) TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
+                        break;
+
+                    case BinaryOperator.Exponentiation:
+                        _operator = (left, right) => JsNumber.Create(Math.Pow(TypeConverter.ToNumber(left), TypeConverter.ToNumber(right)));
+                        break;
+
+                    case BinaryOperator.InstanceOf:
+                        _operator = (left, right) =>
+                        {
+                            if (!(right is FunctionInstance f))
+                            {
+                                return ExceptionHelper.ThrowTypeError<JsValue>(_engine, "instanceof can only be used with a function object");
+                            }
+
+                            return f.HasInstance(left) ? JsBoolean.True : JsBoolean.False;
+                        };
+                        break;
+
+                    case BinaryOperator.In:
+                        _operator = (left, right) =>
+                        {
+                            if (!(right is ObjectInstance oi))
+                            {
+                                return ExceptionHelper.ThrowTypeError<JsValue>(_engine, "in can only be used with an object");
+                            }
+
+                            return oi.HasProperty(TypeConverter.ToString(left)) ? JsBoolean.True : JsBoolean.False;
+                        };
+
+                        break;
+
+                    default:
+                        _operator = ExceptionHelper.ThrowNotImplementedException<Func<JsValue, JsValue, JsValue>>();
+                        break;
+                }
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                return _operator(left, right);
+            }
+        }
+
+        private sealed class StrictlyEqualBinaryExpression : JintBinaryExpression
+        {
+            public StrictlyEqualBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                return StrictlyEqual(left, right) ? JsBoolean.True : JsBoolean.False;
+            }
+        }
+
+        private sealed class StrictlyNotEqualBinaryExpression : JintBinaryExpression
+        {
+            public StrictlyNotEqualBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                return StrictlyEqual(left, right) ? JsBoolean.False : JsBoolean.True;
+            }
+        }
+
+        private sealed class LessBinaryExpression : JintBinaryExpression
+        {
+            public LessBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                var value = Compare(left, right);
+                if (value._type == Types.Undefined)
+                {
+                    value = JsBoolean.False;
+                }
+
+                return value;
+            }
+        }
+
+        private sealed class GreaterBinaryExpression : JintBinaryExpression
+        {
+            public GreaterBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                var value = Compare(right, left, false);
+                if (value._type == Types.Undefined)
+                {
+                    value = JsBoolean.False;
+                }
+
+                return value;
+            }
+        }
+    }
+}

Some files were not shown because too many files changed in this diff