From fedcf9334d0b051bf9b0b6ad9ff7acdf0fba9882 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 10 Oct 2021 09:49:36 +0300 Subject: [PATCH] ES6 generators --- .../Test262Harness.settings.json | 1 - Jint.Tests/Runtime/AwaitTests.cs | 32 +++ Jint.Tests/Runtime/GeneratorTests.cs | 256 ++++++++++++++++++ Jint/Native/Function/FunctionConstructor.cs | 20 +- .../Function/FunctionInstance.Dynamic.cs | 6 +- Jint/Native/Function/FunctionInstance.cs | 1 + Jint/Native/Generator/GeneratorKind.cs | 15 +- Jint/Native/Global/GlobalObject.Properties.cs | 2 + Jint/Native/Iterator/IteratorInstance.cs | 20 +- Jint/Runtime/Interpreter/EvaluationContext.cs | 5 +- .../Interpreter/Expressions/JintExpression.cs | 1 + .../Expressions/JintFunctionExpression.cs | 41 ++- .../Expressions/JintYieldExpression.cs | 234 ++++++++++++++++ .../Interpreter/JintFunctionDefinition.cs | 32 ++- Jint/Runtime/Interpreter/JintStatementList.cs | 54 ++-- .../Statements/JintForInForOfStatement.cs | 5 + 16 files changed, 676 insertions(+), 49 deletions(-) create mode 100644 Jint.Tests/Runtime/GeneratorTests.cs create mode 100644 Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index b5c25a499e..ffa40ef9f6 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -9,7 +9,6 @@ "arraybuffer-transfer", "async-iteration", "Atomics", - "generators", "import-assertions", "import-attributes", "iterator-helpers", diff --git a/Jint.Tests/Runtime/AwaitTests.cs b/Jint.Tests/Runtime/AwaitTests.cs index f3a840e3cd..09fcc67bc5 100644 --- a/Jint.Tests/Runtime/AwaitTests.cs +++ b/Jint.Tests/Runtime/AwaitTests.cs @@ -78,4 +78,36 @@ public void ShouldTaskAwaitCurrentStack() engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();"); Assert.Equal("12", log); } + + [Fact(Skip = "TODO 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()); + } } diff --git a/Jint.Tests/Runtime/GeneratorTests.cs b/Jint.Tests/Runtime/GeneratorTests.cs new file mode 100644 index 0000000000..e913d65ab0 --- /dev/null +++ b/Jint.Tests/Runtime/GeneratorTests.cs @@ -0,0 +1,256 @@ +namespace Jint.Tests.Runtime; + +public class GeneratorTests +{ + [Fact] + public void LoopYield() + { + const string Script = """ + const foo = function*() { + yield 'a'; + yield 'b'; + yield 'c'; + }; + + let str = ''; + for (const val of foo()) { + str += val; + } + return str; + """; + + var engine = new Engine(); + Assert.Equal("abc", engine.Evaluate(Script)); + } + + [Fact] + public void ReturnDuringYield() + { + const string Script = """ + const foo = function*() { + yield 'a'; + return; + yield 'c'; + }; + + let str = ''; + for (const val of foo()) { + str += val; + } + return str; + """; + + var engine = new Engine(); + Assert.Equal("a", engine.Evaluate(Script)); + } + + [Fact] + public void LoneReturnInYield() + { + const string Script = """ + const foo = function*() { + return; + }; + + let str = ''; + for (const val of foo()) { + str += val; + } + return str; + """; + + var engine = new Engine(); + Assert.Equal("", engine.Evaluate(Script)); + } + + [Fact] + public void LoneReturnValueInYield() + { + const string Script = """ + const foo = function*() { + return 'a'; + }; + + let str = ''; + for (const val of foo()) { + str += val; + } + return str; + """; + + var engine = new Engine(); + Assert.Equal("", engine.Evaluate(Script)); + } + + [Fact] + public void YieldUndefined() + { + const string Script = """ + const foo = function*() { + yield undefined; + }; + + let str = ''; + for (const val of foo()) { + str += val; + } + return str; + """; + + var engine = new Engine(); + Assert.Equal("undefined", engine.Evaluate(Script)); + } + + [Fact] + public void ReturnUndefined() + { + const string Script = """ + const foo = function*() { + return undefined; + }; + + let str = ''; + for (const val of foo()) { + str += val; + } + return str; + """; + + var engine = new Engine(); + Assert.Equal("", engine.Evaluate(Script)); + } + + [Fact] + public void Basic() + { + var engine = new Engine(); + engine.Execute("function * generator() { yield 5; yield 6; };"); + engine.Execute("var iterator = generator(); var item = iterator.next();"); + Assert.Equal(5, engine.Evaluate("item.value")); + Assert.False(engine.Evaluate("item.done").AsBoolean()); + engine.Execute("item = iterator.next();"); + Assert.Equal(6, engine.Evaluate("item.value")); + Assert.False(engine.Evaluate("item.done").AsBoolean()); + engine.Execute("item = iterator.next();"); + Assert.True(engine.Evaluate("item.value === void undefined").AsBoolean()); + Assert.True(engine.Evaluate("item.done").AsBoolean()); + } + + [Fact] + public void FunctionExpressions() + { + var engine = new Engine(); + engine.Execute("var generator = function * () { yield 5; yield 6; };"); + engine.Execute("var iterator = generator(); var item = iterator.next();"); + Assert.Equal(5, engine.Evaluate("item.value")); + Assert.False(engine.Evaluate("item.done").AsBoolean()); + engine.Execute("item = iterator.next();"); + Assert.Equal(6, engine.Evaluate("item.value")); + Assert.False(engine.Evaluate("item.done").AsBoolean()); + engine.Execute("item = iterator.next();"); + Assert.True(engine.Evaluate("item.value === void undefined").AsBoolean()); + Assert.True(engine.Evaluate("item.done").AsBoolean()); + } + + [Fact] + public void CorrectThisBinding() + { + var engine = new Engine(); + engine.Execute("var generator = function * () { yield 5; yield 6; };"); + engine.Execute("var iterator = { g: generator, x: 5, y: 6 }.g(); var item = iterator.next();"); + Assert.Equal(5, engine.Evaluate("item.value")); + Assert.False(engine.Evaluate("item.done").AsBoolean()); + engine.Execute("item = iterator.next();"); + Assert.Equal(6, engine.Evaluate("item.value")); + Assert.False(engine.Evaluate("item.done").AsBoolean()); + engine.Execute("item = iterator.next();"); + Assert.True(engine.Evaluate("item.value === void undefined").AsBoolean()); + Assert.True(engine.Evaluate("item.done").AsBoolean()); + } + + [Fact] + public void Sending() + { + const string Script = """ + var sent; + function * generator() { + sent = [yield 5, yield 6]; + }; + var iterator = generator(); + iterator.next(); + iterator.next("foo"); + iterator.next("bar"); + """; + + var engine = new Engine(); + engine.Execute(Script); + + Assert.Equal("foo", engine.Evaluate("sent[0]")); + Assert.Equal("bar", engine.Evaluate("sent[1]")); + } + + [Fact(Skip = "WIP")] + public void Sending2() + { + const string Script = """ + function* counter(value) { + while (true) { + const step = yield value++; + + if (step) { + value += step; + } + } + } + + const generatorFunc = counter(0); + """; + + var engine = new Engine(); + engine.Execute(Script); + + Assert.Equal(0, engine.Evaluate("generatorFunc.next().value")); // 0 + Assert.Equal(1, engine.Evaluate("generatorFunc.next().value")); // 1 + Assert.Equal(2, engine.Evaluate("generatorFunc.next().value")); // 2 + Assert.Equal(3, engine.Evaluate("generatorFunc.next().value")); // 3 + Assert.Equal(14, engine.Evaluate("generatorFunc.next(10).value")); // 14 + Assert.Equal(15, engine.Evaluate("generatorFunc.next().value")); // 15 + Assert.Equal(26, engine.Evaluate("generatorFunc.next(10).value")); // 26 + } + + [Fact(Skip = "WIP")] + public void Fibonacci() + { + const string Script = """ + function* fibonacci() { + let current = 0; + let next = 1; + while (true) { + const reset = yield current; + [current, next] = [next, next + current]; + if (reset) { + current = 0; + next = 1; + } + } + } + + const sequence = fibonacci(); + """; + + var engine = new Engine(); + engine.Execute(Script); + + Assert.Equal(0, engine.Evaluate("sequence.next().value")); + Assert.Equal(1, engine.Evaluate("sequence.next().value")); + Assert.Equal(1, engine.Evaluate("sequence.next().value")); + Assert.Equal(2, engine.Evaluate("sequence.next().value")); + Assert.Equal(3, engine.Evaluate("sequence.next().value")); + Assert.Equal(5, engine.Evaluate("sequence.next().value")); + Assert.Equal(9, engine.Evaluate("sequence.next().value")); + Assert.Equal(0, engine.Evaluate("sequence.next(true).value")); + Assert.Equal(1, engine.Evaluate("sequence.next().value)")); + Assert.Equal(1, engine.Evaluate("sequence.next().value)")); + Assert.Equal(2, engine.Evaluate("sequence.next().value)")); + } +} diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index c9637dbed6..53d13e0c8f 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -113,8 +113,24 @@ private ScriptFunctionInstance InstantiateGeneratorFunctionObject( EnvironmentRecord scope, PrivateEnvironmentRecord? 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; } } } diff --git a/Jint/Native/Function/FunctionInstance.Dynamic.cs b/Jint/Native/Function/FunctionInstance.Dynamic.cs index 57714f8429..dd31cbe6c9 100644 --- a/Jint/Native/Function/FunctionInstance.Dynamic.cs +++ b/Jint/Native/Function/FunctionInstance.Dynamic.cs @@ -2,6 +2,7 @@ using Esprima.Ast; using Jint.Native.Object; using Jint.Runtime; +using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter; @@ -42,6 +43,8 @@ internal FunctionInstance CreateDynamicFunction( 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()); @@ -161,7 +164,8 @@ internal FunctionInstance CreateDynamicFunction( 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) { diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index 3e6b897288..14a951dc4a 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -330,6 +330,7 @@ internal ExecutionContext PrepareForOrdinaryCall(JsValue newTarget) variableEnvironment: localEnv, _privateEnvironment, calleeRealm, + generator: null, function: this); // If callerContext is not already suspended, suspend callerContext. diff --git a/Jint/Native/Generator/GeneratorKind.cs b/Jint/Native/Generator/GeneratorKind.cs index 7f79d8cf58..fa51f8fefc 100644 --- a/Jint/Native/Generator/GeneratorKind.cs +++ b/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 +} \ No newline at end of file diff --git a/Jint/Native/Global/GlobalObject.Properties.cs b/Jint/Native/Global/GlobalObject.Properties.cs index a8ae037981..7f34a25843 100644 --- a/Jint/Native/Global/GlobalObject.Properties.cs +++ b/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 @@ protected override void Initialize() 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)); diff --git a/Jint/Native/Iterator/IteratorInstance.cs b/Jint/Native/Iterator/IteratorInstance.cs index 0aaa57907d..62e41a24a0 100644 --- a/Jint/Native/Iterator/IteratorInstance.cs +++ b/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 @@ private ObjectInstance IteratorNext() 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 @@ public override bool TryIteratorStep(out ObjectInstance nextItem) 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; + } + } + } } diff --git a/Jint/Runtime/Interpreter/EvaluationContext.cs b/Jint/Runtime/Interpreter/EvaluationContext.cs index 5b1bff1eff..497f72f8a4 100644 --- a/Jint/Runtime/Interpreter/EvaluationContext.cs +++ b/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 @@ public EvaluationContext(Engine engine, in Completion? resumedCompletion = null) 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 diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index 8d31bc0d00..f2e7e99689 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -128,6 +128,7 @@ protected internal static JintExpression Build(Expression expression) ? new JintCallExpression((CallExpression) ((ChainExpression) expression).Expression) : new JintMemberExpression((MemberExpression) ((ChainExpression) expression).Expression), Nodes.AwaitExpression => new JintAwaitExpression((AwaitExpression) expression), + Nodes.YieldExpression => new JintYieldExpression((YieldExpression) expression), _ => null }; diff --git a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs index cf4574bf92..ab1e91ccdf 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs +++ b/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 @@ private ScriptFunctionInstance InstantiateAsyncFunctionExpression(EvaluationCont /// private ScriptFunctionInstance InstantiateGeneratorFunctionExpression(EvaluationContext context, string? name) { - // TODO generators - return InstantiateOrdinaryFunctionExpression(context, name); + var engine = context.Engine; + var runningExecutionContext = engine.ExecutionContext; + var scope = runningExecutionContext.LexicalEnvironment; + + DeclarativeEnvironmentRecord? 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; } } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs new file mode 100644 index 0000000000..ca977c864f --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs @@ -0,0 +1,234 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Generator; +using Jint.Native.Iterator; +using Jint.Native.Object; + +namespace Jint.Runtime.Interpreter.Expressions; + +internal sealed class JintYieldExpression : JintExpression +{ + public JintYieldExpression(YieldExpression expression) : base(expression) + { + } + + protected override object EvaluateInternal(EvaluationContext context) + { + var expression = (YieldExpression) _expression; + + JsValue value; + if (context.Engine.ExecutionContext.Generator?._nextValue is not null) + { + value = context.Engine.ExecutionContext.Generator._nextValue; + } + else if (expression.Argument is not null) + { + value = Build(expression.Argument).GetValue(context); + } + else + { + value = JsValue.Undefined; + } + + if (expression.Delegate) + { + value = YieldDelegate(context, value); + } + + return Yield(context, value); + } + + /// + /// https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation + /// + private JsValue YieldDelegate(EvaluationContext context, JsValue value) + { + var engine = context.Engine; + var generatorKind = engine.ExecutionContext.GetGeneratorKind(); + var iterator = value.GetIterator(engine.Realm, generatorKind); + var iteratorRecord = iterator; + var received = new Completion(CompletionType.Normal, JsValue.Undefined, _expression); + while (true) + { + if (received.Type == CompletionType.Normal) + { + iterator.TryIteratorStep(out var innerResult); + if (generatorKind == GeneratorKind.Async) + { + innerResult = Await(innerResult); + } + + if (innerResult is not IteratorResult oi) + { + ExceptionHelper.ThrowTypeError(engine.Realm); + } + + var done = IteratorComplete(innerResult); + if (done) + { + return IteratorValue(innerResult); + } + + if (generatorKind == GeneratorKind.Async) + { + received = AsyncGeneratorYield(IteratorValue(innerResult)); + } + else + { + received = GeneratorYield(innerResult); + } + + } + else if (received.Type == CompletionType.Throw) + { + var throwMethod = iterator.GetMethod("throw"); + if (throwMethod is not null) + { + var innerResult = throwMethod.Call(iterator, new[]{ received.Value }); + if (generatorKind == GeneratorKind.Async) + { + innerResult = Await(innerResult); + } + // NOTE: Exceptions from the inner iterator throw method are propagated. + // Normal completions from an inner throw method are processed similarly to an inner next. + if (innerResult is not ObjectInstance oi) + { + ExceptionHelper.ThrowTypeError(engine.Realm); + } + + var done = IteratorComplete(innerResult); + if (done) + { + IteratorValue(innerResult); + } + + if (generatorKind == GeneratorKind.Async) + { + received = AsyncGeneratorYield(IteratorValue(innerResult)); + } + else + { + received = GeneratorYield(innerResult); + } + } + else + { + // NOTE: If iterator does not have a throw method, this throw is going to terminate the yield* loop. + // But first we need to give iterator a chance to clean up. + var closeCompletion = new Completion(CompletionType.Normal, null!, _expression); + if (generatorKind == GeneratorKind.Async) + { + AsyncIteratorClose(iteratorRecord, CompletionType.Normal); + } + else + { + iteratorRecord.Close(CompletionType.Normal); + } + + ExceptionHelper.ThrowTypeError(engine.Realm, "Iterator does not have close method"); + } + } + else + { + var returnMethod = iterator.GetMethod("return"); + if (returnMethod is null) + { + var temp = received.Value; + if (generatorKind == GeneratorKind.Async) + { + temp = Await(received.Value); + } + + return temp; + } + + var innerReturnResult = returnMethod.Call(iterator, new[] { received.Value }); + if (generatorKind == GeneratorKind.Async) + { + innerReturnResult = Await(innerReturnResult); + } + + if (innerReturnResult is not ObjectInstance oi) + { + ExceptionHelper.ThrowTypeError(engine.Realm); + } + + var done = IteratorComplete(innerReturnResult); + if (done) + { + var val = IteratorValue(innerReturnResult); + return val; + } + + if (generatorKind == GeneratorKind.Async) + { + received = AsyncGeneratorYield(IteratorValue(innerReturnResult)); + } + else + { + received = GeneratorYield(innerReturnResult); + } + } + } + } + + private Completion GeneratorYield(JsValue innerResult) + { + throw new System.NotImplementedException(); + } + + private static bool IteratorComplete(JsValue iterResult) + { + return TypeConverter.ToBoolean(iterResult.Get(CommonProperties.Done)); + } + + private static JsValue IteratorValue(JsValue iterResult) + { + return iterResult.Get(CommonProperties.Value); + } + + private static void AsyncIteratorClose(object iteratorRecord, CompletionType closeCompletion) + { + ExceptionHelper.ThrowNotImplementedException("async"); + } + + /// + /// https://tc39.es/ecma262/#sec-asyncgeneratoryield + /// + private static Completion AsyncGeneratorYield(object iteratorValue) + { + ExceptionHelper.ThrowNotImplementedException("async"); + return default; + } + + /// + /// https://tc39.es/ecma262/#await + /// + private static ObjectInstance Await(JsValue innerResult) + { + ExceptionHelper.ThrowNotImplementedException("await"); + return null; + } + + /// + /// https://tc39.es/ecma262/#sec-yield + /// + private static JsValue Yield(EvaluationContext context, JsValue iterNextObj) + { + var engine = context.Engine; + var generatorKind = engine.ExecutionContext.GetGeneratorKind(); + if (generatorKind == GeneratorKind.Async) + { + // TODO return ? AsyncGeneratorYield(undefined); + ExceptionHelper.ThrowNotImplementedException("async not implemented"); + } + + // https://tc39.es/ecma262/#sec-generatoryield + var genContext = engine.ExecutionContext; + var generator = genContext.Generator; + generator!._generatorState = GeneratorState.SuspendedYield; + //_engine.LeaveExecutionContext(); + + return iterNextObj; + } +} diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index 040720cdd5..429db18f91 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -3,6 +3,7 @@ using Jint.Native; using Jint.Native.Argument; using Jint.Native.Function; +using Jint.Native.Generator; using Jint.Native.Promise; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Expressions; @@ -62,11 +63,7 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun } 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 { @@ -107,7 +104,11 @@ private static void AsyncFunctionStart(EvaluationContext context, PromiseCapabil /// /// https://tc39.es/ecma262/#sec-asyncblockstart /// - private static void AsyncBlockStart(EvaluationContext context, PromiseCapability promiseCapability, Func asyncBody, in ExecutionContext asyncContext) + private static void AsyncBlockStart( + EvaluationContext context, + PromiseCapability promiseCapability, + Func 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: @@ -148,10 +149,23 @@ 8. Return unused. /// /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody /// - private static Completion EvaluateGeneratorBody(FunctionInstance functionObject, JsValue[] argumentsList) + private Completion EvaluateGeneratorBody( + EvaluationContext context, + FunctionInstance 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() diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index 70c80b6ece..467a9fe368 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/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 @@ internal sealed class JintStatementList private Pair[]? _jintStatements; private bool _initialized; - private readonly uint _index; + private uint _index; private readonly bool _generator; public JintStatementList(IFunction function) @@ -50,7 +50,11 @@ private void Initialize(EvaluationContext context) var statement = JintStatement.Build(esprimaStatement); // When in debug mode, don't do FastResolve: Stepping requires each statement to be actually executed. var value = context.DebugMode ? null : JintStatement.FastResolve(esprimaStatement); - jintStatements[i] = new Pair(statement, value); + jintStatements[i] = new Pair + { + Statement = statement, + Value = value + }; } _jintStatements = jintStatements; @@ -81,7 +85,7 @@ public Completion Execute(EvaluationContext context) 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 +103,16 @@ public Completion Execute(EvaluationContext context) 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 +127,8 @@ public Completion Execute(EvaluationContext context) } catch (Exception ex) { + Reset(); + if (ex is JintException) { c = HandleException(context, ex, temp[i].Statement); @@ -128,24 +144,13 @@ public Completion Execute(EvaluationContext context) private 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 + }; } private static Completion HandleError(Engine engine, JintStatement? s) @@ -215,5 +220,12 @@ internal static void BlockDeclarationInstantiation( } } } + + public bool Completed => _index == _jintStatements?.Length; + + public void Reset() + { + _index = 0; + } } } diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs index 6f08a16451..43399e2527 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -293,6 +293,11 @@ private Completion BodyEvaluation( 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 make sure we can start from where we left off + //return result; + } if (result.IsAbrupt()) { close = true;