Browse Source

Sync WIP generator structures to main (#1762)

Marko Lahma 1 year ago
parent
commit
62ab48186c

+ 32 - 0
Jint.Tests/Runtime/AwaitTests.cs → Jint.Tests/Runtime/AsyncTests.cs

@@ -148,4 +148,36 @@ public class AsyncTests
         Assert.Equal("12", log);
     }
 #endif
+
+    [Fact(Skip = "TODO es6-await https://github.com/sebastienros/jint/issues/1385")]
+    public void ShouldHaveCorrectOrder()
+    {
+        var engine = new Engine();
+        engine.Evaluate("var log = [];");
+
+        const string Script = """
+          async function foo(name) {
+            log.push(name + " start");
+            await log.push(name + " middle");
+            log.push(name + " end");
+          }
+
+          foo("First");
+          foo("Second");
+        """;
+
+        engine.Execute(Script);
+
+        var log = engine.GetValue("log").AsArray();
+        string[] expected = [
+            "First start",
+            "First middle",
+            "Second start",
+            "Second middle",
+            "First end",
+            "Second end",
+        ];
+
+        Assert.Equal(expected, log.Select(x => x.AsString()).ToArray());
+    }
 }

+ 1 - 0
Jint/Native/Function/Function.cs

@@ -332,6 +332,7 @@ namespace Jint.Native.Function
                 variableEnvironment: localEnv,
                 _privateEnvironment,
                 calleeRealm,
+                generator: null,
                 function: this);
 
             // If callerContext is not already suspended, suspend callerContext.

+ 18 - 2
Jint/Native/Function/FunctionConstructor.cs

@@ -114,8 +114,24 @@ namespace Jint.Native.Function
             Environment scope,
             PrivateEnvironment? privateScope)
         {
-            // TODO generators
-            return InstantiateOrdinaryFunctionObject(functionDeclaration, scope, privateScope);
+            var thisMode = functionDeclaration.Strict || _engine._isStrict
+                ? FunctionThisMode.Strict
+                : FunctionThisMode.Global;
+
+            var name = functionDeclaration.Function.Id?.Name ?? "default";
+            var F = OrdinaryFunctionCreate(
+                _realm.Intrinsics.GeneratorFunction.PrototypeObject,
+                functionDeclaration,
+                thisMode,
+                scope,
+                privateScope);
+
+            F.SetFunctionName(name);
+
+            var prototype = OrdinaryObjectCreate(_engine, _realm.Intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject);
+            F.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
+
+            return F;
         }
     }
 }

+ 5 - 1
Jint/Native/Function/FunctionInstance.Dynamic.cs

@@ -2,6 +2,7 @@ using Esprima;
 using Esprima.Ast;
 using Jint.Native.Object;
 using Jint.Runtime;
+using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
 using Jint.Runtime.Interpreter;
 using Environment = Jint.Runtime.Environments.Environment;
@@ -45,6 +46,8 @@ public partial class Function
                 fallbackProto = static intrinsics => intrinsics.AsyncFunction.PrototypeObject;
                 break;
             case FunctionKind.Generator:
+                fallbackProto = static intrinsics => intrinsics.GeneratorFunction.PrototypeObject;
+                break;
             case FunctionKind.AsyncGenerator:
             default:
                 ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
@@ -164,7 +167,8 @@ public partial class Function
 
         if (kind == FunctionKind.Generator)
         {
-            ExceptionHelper.ThrowNotImplementedException("generators not implemented");
+            var prototype = OrdinaryObjectCreate(_engine, _realm.Intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject);
+            F.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
         }
         else if (kind == FunctionKind.AsyncGenerator)
         {

+ 7 - 8
Jint/Native/Generator/GeneratorKind.cs

@@ -1,9 +1,8 @@
-namespace Jint.Native.Generator
+namespace Jint.Native.Generator;
+
+internal enum GeneratorKind
 {
-    internal enum GeneratorKind
-    {
-        NonGenerator,
-        Sync,
-        Async
-    }
-}
+    NonGenerator,
+    Sync,
+    Async
+}

+ 2 - 0
Jint/Native/Global/GlobalObject.Properties.cs

@@ -23,6 +23,7 @@ public partial class GlobalObject
     private static readonly Key propertyFloat32Array = "Float32Array";
     private static readonly Key propertyFloat64Array = "Float64Array";
     private static readonly Key propertyFunction = "Function";
+    private static readonly Key propertyGeneratorFunction = "Generator";
     private static readonly Key propertyInt16Array = "Int16Array";
     private static readonly Key propertyInt32Array = "Int32Array";
     private static readonly Key propertyInt8Array = "Int8Array";
@@ -97,6 +98,7 @@ public partial class GlobalObject
         properties.AddDangerous(propertyFloat32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, PropertyFlags));
         properties.AddDangerous(propertyFloat64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, PropertyFlags));
         properties.AddDangerous(propertyFunction, new PropertyDescriptor(_realm.Intrinsics.Function, PropertyFlags));
+        properties.AddDangerous(propertyGeneratorFunction, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.GeneratorFunction, PropertyFlags));
         properties.AddDangerous(propertyInt16Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, PropertyFlags));
         properties.AddDangerous(propertyInt32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, PropertyFlags));
         properties.AddDangerous(propertyInt8Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int8Array, PropertyFlags));

+ 19 - 1
Jint/Native/Iterator/IteratorInstance.cs

@@ -1,4 +1,5 @@
 using System.Globalization;
+using Jint.Native.Generator;
 using Jint.Native.Object;
 using Jint.Native.RegExp;
 using Jint.Runtime;
@@ -68,7 +69,7 @@ namespace Jint.Native.Iterator
                 var instance = jsValue as ObjectInstance;
                 if (instance is null)
                 {
-                    ExceptionHelper.ThrowTypeError(_target.Engine.Realm, "Iterator result " + jsValue + " is not an object");
+                    ExceptionHelper.ThrowTypeError(_target.Engine.Realm, $"Iterator result {jsValue} is not an object");
                 }
 
                 return instance;
@@ -210,5 +211,22 @@ namespace Jint.Native.Iterator
                 return false;
             }
         }
+
+        internal sealed class GeneratorIterator : IteratorInstance
+        {
+            private readonly GeneratorInstance _generator;
+
+            public GeneratorIterator(Engine engine, GeneratorInstance generator) : base(engine)
+            {
+                _generator = generator;
+            }
+
+            public override bool TryIteratorStep(out ObjectInstance nextItem)
+            {
+                nextItem = IteratorResult.CreateValueIteratorPosition(_engine, done: JsBoolean.True);
+                return false;
+            }
+        }
+
     }
 }

+ 1 - 4
Jint/Runtime/Interpreter/EvaluationContext.cs

@@ -10,10 +10,9 @@ internal sealed class EvaluationContext
 {
     private readonly bool _shouldRunBeforeExecuteStatementChecks;
 
-    public EvaluationContext(Engine engine, in Completion? resumedCompletion = null)
+    public EvaluationContext(Engine engine)
     {
         Engine = engine;
-        ResumedCompletion = resumedCompletion ?? default; // TODO later
         OperatorOverloadingAllowed = engine.Options.Interop.AllowOperatorOverloading;
         _shouldRunBeforeExecuteStatementChecks = engine._constraints.Length > 0 || engine._isDebugMode;
     }
@@ -22,13 +21,11 @@ internal sealed class EvaluationContext
     public EvaluationContext()
     {
         Engine = null!;
-        ResumedCompletion = default; // TODO later
         OperatorOverloadingAllowed = false;
         _shouldRunBeforeExecuteStatementChecks = false;
     }
 
     public readonly Engine Engine;
-    public readonly Completion ResumedCompletion;
     public bool DebugMode => Engine._isDebugMode;
 
     public SyntaxElement LastSyntaxElement

+ 39 - 2
Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs

@@ -1,6 +1,8 @@
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
 
 namespace Jint.Runtime.Interpreter.Expressions
@@ -123,8 +125,43 @@ namespace Jint.Runtime.Interpreter.Expressions
         /// </summary>
         private ScriptFunction InstantiateGeneratorFunctionExpression(EvaluationContext context, string? name)
         {
-            // TODO generators
-            return InstantiateOrdinaryFunctionExpression(context, name);
+            var engine = context.Engine;
+            var runningExecutionContext = engine.ExecutionContext;
+            var scope = runningExecutionContext.LexicalEnvironment;
+
+            DeclarativeEnvironment? funcEnv = null;
+            if (!string.IsNullOrWhiteSpace(name))
+            {
+                funcEnv = JintEnvironment.NewDeclarativeEnvironment(engine, engine.ExecutionContext.LexicalEnvironment);
+                funcEnv.CreateImmutableBinding(name!, strict: false);
+            }
+
+            var privateScope = runningExecutionContext.PrivateEnvironment;
+
+            var thisMode = _function.Strict || engine._isStrict
+                ? FunctionThisMode.Strict
+                : FunctionThisMode.Global;
+
+            var intrinsics = engine.Realm.Intrinsics;
+            var closure = intrinsics.Function.OrdinaryFunctionCreate(
+                intrinsics.GeneratorFunction.PrototypeObject,
+                _function,
+                thisMode,
+                funcEnv ?? scope,
+                privateScope
+            );
+
+            if (name is not null)
+            {
+                closure.SetFunctionName(name);
+            }
+
+            var prototype = ObjectInstance.OrdinaryObjectCreate(engine, intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject);
+            closure.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
+
+            funcEnv?.InitializeBinding(name!, closure);
+
+            return closure;
         }
     }
 }

+ 23 - 9
Jint/Runtime/Interpreter/JintFunctionDefinition.cs

@@ -2,6 +2,7 @@ using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Function;
+using Jint.Native.Generator;
 using Jint.Native.Promise;
 using Jint.Runtime.Environments;
 using Jint.Runtime.Interpreter.Expressions;
@@ -63,11 +64,7 @@ internal sealed class JintFunctionDefinition
         }
         else if (Function.Generator)
         {
-            // TODO generators
-            // result = EvaluateGeneratorBody(functionObject, argumentsList);
-            argumentsInstance = context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
-            _bodyStatementList ??= new JintStatementList(Function);
-            result = _bodyStatementList.Execute(context);
+            result = EvaluateGeneratorBody(context, functionObject, argumentsList);
         }
         else
         {
@@ -108,7 +105,11 @@ internal sealed class JintFunctionDefinition
     /// <summary>
     /// https://tc39.es/ecma262/#sec-asyncblockstart
     /// </summary>
-    private static void AsyncBlockStart(EvaluationContext context, PromiseCapability promiseCapability, Func<EvaluationContext, Completion> asyncBody, in ExecutionContext asyncContext)
+    private static void AsyncBlockStart(
+        EvaluationContext context,
+        PromiseCapability promiseCapability,
+        Func<EvaluationContext, Completion> asyncBody,
+        in ExecutionContext asyncContext)
     {
         var runningContext = context.Engine.ExecutionContext;
         // Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution contxt the following steps will be performed:
@@ -149,10 +150,23 @@ internal sealed class JintFunctionDefinition
     /// <summary>
     /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody
     /// </summary>
-    private static Completion EvaluateGeneratorBody(Function functionObject, JsValue[] argumentsList)
+    private Completion EvaluateGeneratorBody(
+        EvaluationContext context,
+        Function functionObject,
+        JsValue[] argumentsList)
     {
-        ExceptionHelper.ThrowNotImplementedException("generators not implemented");
-        return default;
+        var engine = context.Engine;
+        engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
+        var G = engine.Realm.Intrinsics.Function.OrdinaryCreateFromConstructor(
+            functionObject,
+            static intrinsics => intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject,
+            static (Engine engine , Realm _, object? _) => new GeneratorInstance(engine));
+
+        _bodyStatementList ??= new JintStatementList(Function);
+        _bodyStatementList.Reset();
+        G.GeneratorStart(_bodyStatementList);
+
+        return new Completion(CompletionType.Return, G, Function.Body);
     }
 
     internal State Initialize()

+ 28 - 20
Jint/Runtime/Interpreter/JintStatementList.cs

@@ -1,4 +1,4 @@
-using System.Runtime.CompilerServices;
+using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Error;
@@ -16,7 +16,7 @@ namespace Jint.Runtime.Interpreter
 
         private Pair[]? _jintStatements;
         private bool _initialized;
-        private readonly uint _index;
+        private uint _index;
         private readonly bool _generator;
 
         public JintStatementList(IFunction function)
@@ -81,7 +81,7 @@ namespace Jint.Runtime.Interpreter
             var temp = _jintStatements!;
             try
             {
-                for (i = 0; i < (uint) temp.Length; i++)
+                for (; i < (uint) temp.Length; i++)
                 {
                     ref readonly var pair = ref temp[i];
 
@@ -99,6 +99,16 @@ namespace Jint.Runtime.Interpreter
                         c = new Completion(CompletionType.Return, pair.Value, pair.Statement._statement);
                     }
 
+                    if (_generator)
+                    {
+                        if (context.Engine.ExecutionContext.Suspended)
+                        {
+                            _index = i + 1;
+                            c = new Completion(CompletionType.Return, c.Value, pair.Statement._statement);
+                            break;
+                        }
+                    }
+
                     if (c.Type != CompletionType.Normal)
                     {
                         return c.UpdateEmpty(sl.Value);
@@ -113,6 +123,8 @@ namespace Jint.Runtime.Interpreter
             }
             catch (Exception ex)
             {
+                Reset();
+
                 if (ex is JintException)
                 {
                     c = HandleException(context, ex, temp[i].Statement);
@@ -128,24 +140,13 @@ namespace Jint.Runtime.Interpreter
 
         internal static Completion HandleException(EvaluationContext context, Exception exception, JintStatement? s)
         {
-            if (exception is JavaScriptException javaScriptException)
-            {
-                return CreateThrowCompletion(s, javaScriptException);
-            }
-
-            if (exception is TypeErrorException typeErrorException)
+            return exception switch
             {
-                var node = typeErrorException.Node ?? s!._statement;
-                return CreateThrowCompletion(context.Engine.Realm.Intrinsics.TypeError, typeErrorException, node);
-            }
-
-            if (exception is RangeErrorException rangeErrorException)
-            {
-                return CreateThrowCompletion(context.Engine.Realm.Intrinsics.RangeError, rangeErrorException, s!._statement);
-            }
-
-            // should not happen unless there's problem in the engine
-            throw exception;
+                JavaScriptException javaScriptException => CreateThrowCompletion(s, javaScriptException),
+                TypeErrorException typeErrorException => CreateThrowCompletion(context.Engine.Realm.Intrinsics.TypeError, typeErrorException, typeErrorException.Node ?? s!._statement),
+                RangeErrorException rangeErrorException => CreateThrowCompletion(context.Engine.Realm.Intrinsics.RangeError, rangeErrorException, s!._statement),
+                _ => throw exception
+            };
         }
 
         internal static Completion HandleError(Engine engine, JintStatement? s)
@@ -212,5 +213,12 @@ namespace Jint.Runtime.Interpreter
                 }
             }
         }
+
+        public bool Completed => _index == _jintStatements?.Length;
+
+        public void Reset()
+        {
+            _index = 0;
+        }
     }
 }

+ 5 - 0
Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs

@@ -293,6 +293,11 @@ namespace Jint.Runtime.Interpreter.Statements
                     if (result.Type != CompletionType.Continue || (context.Target != null && !string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal)))
                     {
                         completionType = result.Type;
+                        if (iterationKind == IterationKind.Enumerate)
+                        {
+                            // TODO es6-generators make sure we can start from where we left off
+                            //return result;
+                        }
                         if (result.IsAbrupt())
                         {
                             close = true;