From 56f0d615698388794c4f51db4d2bf4e6c5ca398b 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 | 14 -- Jint/Engine.cs | 12 + Jint/Native/Function/FunctionConstructor.cs | 25 +- Jint/Native/Function/FunctionInstance.cs | 1 + .../Generator/GeneratorFunctionConstructor.cs | 47 ++++ .../Generator/GeneratorFunctionPrototype.cs | 45 ++++ Jint/Native/Generator/GeneratorInstance.cs | 135 ++++++++++ Jint/Native/Generator/GeneratorPrototype.cs | 91 +++++++ Jint/Native/Generator/GeneratorState.cs | 11 + Jint/Native/Global/GlobalObject.cs | 1 + Jint/Runtime/Environments/ExecutionContext.cs | 28 ++- Jint/Runtime/ExecutionContextStack.cs | 8 + .../Interpreter/Expressions/JintExpression.cs | 1 + .../Expressions/JintFunctionExpression.cs | 42 +++- .../Expressions/JintYieldExpression.cs | 235 ++++++++++++++++++ .../Interpreter/JintFunctionDefinition.cs | 18 +- Jint/Runtime/Interpreter/JintStatementList.cs | 30 ++- .../Statements/JintForInForOfStatement.cs | 5 + Jint/Runtime/Intrinsics.cs | 5 + 19 files changed, 720 insertions(+), 34 deletions(-) create mode 100644 Jint/Native/Generator/GeneratorFunctionConstructor.cs create mode 100644 Jint/Native/Generator/GeneratorFunctionPrototype.cs create mode 100644 Jint/Native/Generator/GeneratorInstance.cs create mode 100644 Jint/Native/Generator/GeneratorPrototype.cs create mode 100644 Jint/Native/Generator/GeneratorState.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 530668d217..62df8ac9b2 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -19,7 +19,6 @@ "class-static-fields-public", "class-static-methods-private", "FinalizationRegistry", - "generators", "hashbang", "import-assertions", "import.meta", @@ -138,14 +137,6 @@ "language/expressions/function/scope-name-var-open-non-strict.js", "language/expressions/function/scope-name-var-open-strict.js", - // yield not implemented - "built-ins/TypedArrayConstructors/ctors-bigint/object-arg/as-generator-iterable-returns.js", - "built-ins/TypedArrayConstructors/ctors-bigint/object-arg/iterating-throws.js", - "language/expressions/object/accessor-name-computed-yield-id.js", // accessor / yield not implemented - "language/expressions/object/accessor-name-computed.js", - "built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js", - "language/expressions/object/method-definition/name-prop-name-yield-id.js", - // accessor not implemented "language/expressions/object/prop-dup-set-get-set.js", "language/expressions/object/accessor-name-computed-err-to-prop-key.js", @@ -167,11 +158,6 @@ "language/expressions/object/scope-meth-paramsbody-var-open.js", "language/expressions/object/scope-setter-paramsbody-var-open.js", - // generators not implemented - "built-ins/Object/prototype/toString/proxy-function.js", - "language/statements/class/subclass/builtin-objects/GeneratorFunction/*.js", - "language/**/*-yield-*.js", - // JavaScriptParser cannot handle direct 'super.property' script code "language/expressions/super/prop-dot-cls-val-from-eval.js", "language/expressions/super/prop-dot-obj-val-from-eval.js", diff --git a/Jint/Engine.cs b/Jint/Engine.cs index a1256998de..d65873eb2e 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -6,6 +6,7 @@ using Jint.Native; using Jint.Native.Argument; using Jint.Native.Function; +using Jint.Native.Generator; using Jint.Native.Object; using Jint.Native.Promise; using Jint.Native.Symbol; @@ -1237,6 +1238,12 @@ internal void UpdatePrivateEnvironment(PrivateEnvironmentRecord newEnv) _executionContexts.ReplaceTopPrivateEnvironment(newEnv); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref readonly ExecutionContext UpdateGenerator(GeneratorInstance generator) + { + return ref _executionContexts.ReplaceTopGenerator(generator); + } + /// /// Invokes the named callable and returns the resulting object. /// @@ -1383,6 +1390,11 @@ private ObjectInstance Construct( return result; } + internal ref readonly ExecutionContext GetExecutionContext(int fromTop) + { + return ref _executionContexts.Peek(fromTop); + } + public void Dispose() { // no-op for now diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index 96a95b0150..21ebf1c5db 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -78,6 +78,8 @@ internal FunctionInstance CreateDynamicFunction( fallbackProto = static intrinsics => intrinsics.Function.PrototypeObject; break; case FunctionKind.Generator: + fallbackProto = static intrinsics => intrinsics.GeneratorFunction.PrototypeObject; + break; case FunctionKind.AsyncGenerator: case FunctionKind.Async: default: @@ -194,7 +196,8 @@ internal FunctionInstance CreateDynamicFunction( if (kind == FunctionKind.Generator) { - ExceptionHelper.ThrowNotImplementedException("generators not implemented"); + var prototype = OrdinaryObjectCreate(_realm.Intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject); + F.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable)); } else if (kind == FunctionKind.AsyncGenerator) { @@ -275,8 +278,24 @@ private FunctionInstance 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(_realm.Intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject); + F.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable)); + + return F; } } } diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index 3f96529af7..bab786c0db 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -364,6 +364,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/GeneratorFunctionConstructor.cs b/Jint/Native/Generator/GeneratorFunctionConstructor.cs new file mode 100644 index 0000000000..4f4bb755fd --- /dev/null +++ b/Jint/Native/Generator/GeneratorFunctionConstructor.cs @@ -0,0 +1,47 @@ +using Jint.Native.Function; +using Jint.Native.Iterator; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.Generator +{ + /// + /// https://tc39.es/ecma262/#sec-generatorfunction-constructor + /// + public sealed class GeneratorFunctionConstructor : FunctionInstance, IConstructor + { + private static readonly JsString _functionName = new("GeneratorFunction"); + + internal GeneratorFunctionConstructor( + Engine engine, + Realm realm, + FunctionPrototype prototype, + IteratorPrototype iteratorPrototype) + : base(engine, realm, _functionName) + { + PrototypeObject = new GeneratorFunctionPrototype(engine, this, prototype, iteratorPrototype); + _prototype = PrototypeObject; + _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); + _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable); + } + + public GeneratorFunctionPrototype PrototypeObject { get; } + + protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) + { + return Construct(arguments, thisObject); + } + + public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) + { + var function = _realm.Intrinsics.Function.CreateDynamicFunction( + this, + newTarget, + FunctionKind.Generator, + arguments); + + return function; + } + } +} diff --git a/Jint/Native/Generator/GeneratorFunctionPrototype.cs b/Jint/Native/Generator/GeneratorFunctionPrototype.cs new file mode 100644 index 0000000000..e0fbe1f10a --- /dev/null +++ b/Jint/Native/Generator/GeneratorFunctionPrototype.cs @@ -0,0 +1,45 @@ +using Jint.Collections; +using Jint.Native.Function; +using Jint.Native.Iterator; +using Jint.Native.Object; +using Jint.Native.Symbol; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.Generator +{ + /// + /// https://tc39.es/ecma262/#sec-properties-of-the-generatorfunction-prototype-object + /// + public sealed class GeneratorFunctionPrototype : ObjectInstance + { + private readonly GeneratorFunctionConstructor _constructor; + + internal GeneratorFunctionPrototype( + Engine engine, + GeneratorFunctionConstructor constructor, + FunctionPrototype prototype, + IteratorPrototype iteratorPrototype) : base(engine) + { + _constructor = constructor; + _prototype = prototype; + PrototypeObject = new GeneratorPrototype(engine, this, iteratorPrototype); + } + + public GeneratorPrototype PrototypeObject { get; } + + protected override void Initialize() + { + var properties = new PropertyDictionary(2, checkExistingKeys: false) + { + ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.Configurable), + ["prototype"] = new PropertyDescriptor(PrototypeObject, PropertyFlag.Configurable) + }; + SetProperties(properties); + var symbols = new SymbolDictionary(1) + { + [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("GeneratorFunction", PropertyFlag.Configurable) + }; + SetSymbols(symbols); + } + } +} \ No newline at end of file diff --git a/Jint/Native/Generator/GeneratorInstance.cs b/Jint/Native/Generator/GeneratorInstance.cs new file mode 100644 index 0000000000..ba490931c7 --- /dev/null +++ b/Jint/Native/Generator/GeneratorInstance.cs @@ -0,0 +1,135 @@ +using Jint.Native.Iterator; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Environments; +using Jint.Runtime.Interpreter; + +namespace Jint.Native.Generator +{ + /// + /// https://tc39.es/ecma262/#sec-properties-of-generator-instances + /// + internal sealed class GeneratorInstance : ObjectInstance + { + internal GeneratorState _generatorState; + private ExecutionContext _generatorContext; + private readonly JsValue _generatorBrand = null; + private JintStatementList _generatorBody; + + public GeneratorInstance(Engine engine) : base(engine) + { + } + + /// + /// https://tc39.es/ecma262/#sec-generatorstart + /// + public JsValue GeneratorStart(JintStatementList generatorBody) + { + var genContext = _engine.UpdateGenerator(this); + _generatorBody = generatorBody; + + _generatorContext = genContext; + _generatorState = GeneratorState.SuspendedStart; + + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-generatorresume + /// + public JsValue GeneratorResume(JsValue value, JsValue generatorBrand) + { + var state = GeneratorValidate(generatorBrand); + if (state == GeneratorState.Completed) + { + return new IteratorResult(_engine, value, JsBoolean.True); + } + + var genContext = _generatorContext; + var methodContext = _engine.ExecutionContext; + + // 6. Suspend methodContext. + + var context = _engine._activeEvaluationContext; + return ResumeExecution(genContext, context); + } + + /// + /// https://tc39.es/ecma262/#sec-generatorresumeabrupt + /// + public JsValue GeneratorResumeAbrupt(in Completion abruptCompletion, JsValue generatorBrand) + { + var state = GeneratorValidate(generatorBrand); + if (state == GeneratorState.SuspendedStart) + { + _generatorState = GeneratorState.Completed; + state = GeneratorState.Completed; + } + + if (state == GeneratorState.Completed) + { + if (abruptCompletion.Type == CompletionType.Return) + { + return new IteratorResult(_engine, abruptCompletion.Value, JsBoolean.True); + } + + return abruptCompletion.Value; + } + + var genContext = _generatorContext; + var methodContext = _engine.ExecutionContext; + + // Suspend methodContext. + + return ResumeExecution(genContext, new EvaluationContext(_engine, abruptCompletion)); + } + + private JsValue ResumeExecution(ExecutionContext genContext, EvaluationContext context) + { + _generatorState = GeneratorState.Executing; + _engine.EnterExecutionContext(genContext); + + var result = _generatorBody.Execute(context); + _engine.LeaveExecutionContext(); + + ObjectInstance resultValue = null; + if (result.Type == CompletionType.Normal) + { + _generatorState = GeneratorState.Completed; + var value = context.ResumedCompletion.GetValueOrDefault(); + resultValue = IteratorInstance.ValueIteratorPosition.Done(_engine, value); + } + else if (result.Type == CompletionType.Return) + { + resultValue = new IteratorInstance.ValueIteratorPosition(_engine, result.Value, false); + if (_generatorBody.Completed) + { + _generatorState = GeneratorState.Completed; + } + } + + if (result.Type == CompletionType.Throw) + { + _generatorState = GeneratorState.Completed; + ExceptionHelper.ThrowJavaScriptException(_engine, result.Value, result); + } + + return resultValue; + } + + private GeneratorState GeneratorValidate(JsValue generatorBrand) + { + if (!ReferenceEquals(generatorBrand, _generatorBrand)) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, "Generator brand differs from attached brand"); + } + + if (_generatorState == GeneratorState.Executing) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, "Generator state was unexpectedly executing"); + } + + return _generatorState; + } + } +} \ No newline at end of file diff --git a/Jint/Native/Generator/GeneratorPrototype.cs b/Jint/Native/Generator/GeneratorPrototype.cs new file mode 100644 index 0000000000..84cd748d1c --- /dev/null +++ b/Jint/Native/Generator/GeneratorPrototype.cs @@ -0,0 +1,91 @@ +using Esprima; +using Jint.Collections; +using Jint.Native.Iterator; +using Jint.Native.Object; +using Jint.Native.Symbol; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint.Native.Generator +{ + /// + /// https://tc39.es/ecma262/#sec-generator-objects + /// + public sealed class GeneratorPrototype : ObjectInstance + { + private readonly GeneratorFunctionPrototype _constructor; + + internal GeneratorPrototype( + Engine engine, + GeneratorFunctionPrototype constructor, + IteratorPrototype iteratorPrototype) : base(engine) + { + _constructor = constructor; + _prototype = iteratorPrototype; + } + + protected override void Initialize() + { + const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + const PropertyFlag lengthFlags = PropertyFlag.Configurable; + var properties = new PropertyDictionary(4, false) + { + ["constructor"] = new(_constructor, PropertyFlag.Configurable), + ["next"] = new(new ClrFunctionInstance(Engine, "next", Next, 1, lengthFlags), propertyFlags), + ["return"] = new(new ClrFunctionInstance(Engine, "return", Return, 1, lengthFlags), propertyFlags), + ["throw"] = new(new ClrFunctionInstance(Engine, "throw", Throw, 1, lengthFlags), propertyFlags) + }; + SetProperties(properties); + + var symbols = new SymbolDictionary(1) + { + [GlobalSymbolRegistry.ToStringTag] = new("Generator", PropertyFlag.Configurable) + }; + SetSymbols(symbols); + } + + /// + /// https://tc39.es/ecma262/#sec-generator.prototype.next + /// + private JsValue Next(JsValue thisObject, JsValue[] arguments) + { + var g = AssertGeneratorInstance(thisObject); + var value = arguments.At(0); + return g.GeneratorResume(value, null); + } + + /// + /// https://tc39.es/ecma262/#sec-generator.prototype.return + /// + private JsValue Return(JsValue thisObject, JsValue[] arguments) + { + var g = AssertGeneratorInstance(thisObject); + var value = arguments.At(0); + var C = new Completion(CompletionType.Return, value, new Location()); + return g.GeneratorResumeAbrupt(C, null); + } + + /// + /// https://tc39.es/ecma262/#sec-generator.prototype.throw + /// + private JsValue Throw(JsValue thisObject, JsValue[] arguments) + { + var g = AssertGeneratorInstance(thisObject); + var exception = arguments.At(0); + var C = new Completion(CompletionType.Throw, exception, new Location()); + return g.GeneratorResumeAbrupt(C, null); + } + + private GeneratorInstance AssertGeneratorInstance(JsValue thisObj) + { + var generatorInstance = thisObj as GeneratorInstance; + if (generatorInstance is null) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, "object must be a Generator instance"); + } + + return generatorInstance; + } + } +} \ No newline at end of file diff --git a/Jint/Native/Generator/GeneratorState.cs b/Jint/Native/Generator/GeneratorState.cs new file mode 100644 index 0000000000..76aeffe0d0 --- /dev/null +++ b/Jint/Native/Generator/GeneratorState.cs @@ -0,0 +1,11 @@ +namespace Jint.Native.Generator +{ + internal enum GeneratorState + { + Undefined, + SuspendedStart, + SuspendedYield, + Executing, + Completed + } +} \ No newline at end of file diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index 4f35eda846..34ec4cb038 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -53,6 +53,7 @@ protected override void Initialize() ["BigUint64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.BigUint64Array, propertyFlags), ["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Float32Array, propertyFlags), ["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Float64Array, propertyFlags), + ["GeneratorFunction"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.GeneratorFunction, propertyFlags), ["Map"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Map, propertyFlags), ["Set"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Set, propertyFlags), ["WeakMap"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.WeakMap, propertyFlags), diff --git a/Jint/Runtime/Environments/ExecutionContext.cs b/Jint/Runtime/Environments/ExecutionContext.cs index d9df1a5cf0..50cd368007 100644 --- a/Jint/Runtime/Environments/ExecutionContext.cs +++ b/Jint/Runtime/Environments/ExecutionContext.cs @@ -2,6 +2,8 @@ #nullable enable +using Jint.Native.Generator; + namespace Jint.Runtime.Environments { internal readonly struct ExecutionContext @@ -12,6 +14,7 @@ internal ExecutionContext( EnvironmentRecord variableEnvironment, PrivateEnvironmentRecord? privateEnvironment, Realm realm, + GeneratorInstance? generator = null, FunctionInstance? function = null) { ScriptOrModule = scriptOrModule; @@ -20,6 +23,7 @@ internal ExecutionContext( PrivateEnvironment = privateEnvironment; Realm = realm; Function = function; + Generator = generator; } public readonly IScriptOrModule? ScriptOrModule; @@ -28,20 +32,26 @@ internal ExecutionContext( public readonly PrivateEnvironmentRecord? PrivateEnvironment; public readonly Realm Realm; public readonly FunctionInstance? Function; + public readonly GeneratorInstance? Generator; public ExecutionContext UpdateLexicalEnvironment(EnvironmentRecord lexicalEnvironment) { - return new ExecutionContext(ScriptOrModule, lexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, Function); + return new ExecutionContext(ScriptOrModule, lexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, Generator, Function); } public ExecutionContext UpdateVariableEnvironment(EnvironmentRecord variableEnvironment) { - return new ExecutionContext(ScriptOrModule, LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Function); + return new ExecutionContext(ScriptOrModule, LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Generator, Function); } public ExecutionContext UpdatePrivateEnvironment(PrivateEnvironmentRecord privateEnvironment) { - return new ExecutionContext(ScriptOrModule, LexicalEnvironment, VariableEnvironment, privateEnvironment, Realm, Function); + return new ExecutionContext(ScriptOrModule, LexicalEnvironment, VariableEnvironment, privateEnvironment, Realm, Generator, Function); + } + + public ExecutionContext UpdateGenerator(GeneratorInstance generator) + { + return new ExecutionContext(ScriptOrModule, LexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, generator, Function); } /// @@ -66,5 +76,17 @@ internal EnvironmentRecord GetThisEnvironment() } } } + + internal GeneratorKind GetGeneratorKind() + { + if (Generator is null) + { + return GeneratorKind.NonGenerator; + } + + // TODO If generator has an [[AsyncGeneratorState]] internal slot, return async. + + return GeneratorKind.Sync; + } } } diff --git a/Jint/Runtime/ExecutionContextStack.cs b/Jint/Runtime/ExecutionContextStack.cs index 4350d725e0..c486666604 100644 --- a/Jint/Runtime/ExecutionContextStack.cs +++ b/Jint/Runtime/ExecutionContextStack.cs @@ -37,6 +37,14 @@ public void ReplaceTopPrivateEnvironment(PrivateEnvironmentRecord newEnv) array[size - 1] = array[size - 1].UpdatePrivateEnvironment(newEnv); } + public ref readonly ExecutionContext ReplaceTopGenerator(GeneratorInstance newEnv) + { + var array = _stack._array; + var size = _stack._size; + array[size - 1] = array[size - 1].UpdateGenerator(newEnv); + return ref array[size - 1]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref readonly ExecutionContext Peek() => ref _stack.Peek(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index f7d9476c98..b746ceb102 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -199,6 +199,7 @@ protected internal static JintExpression Build(Engine engine, Expression express Nodes.ChainExpression => ((ChainExpression) expression).Expression.Type == Nodes.CallExpression ? new JintCallExpression((CallExpression) ((ChainExpression) expression).Expression) : new JintMemberExpression((MemberExpression) ((ChainExpression) expression).Expression), + Nodes.YieldExpression => new JintYieldExpression(engine, (YieldExpression) expression), _ => null }; diff --git a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs index 7424849169..4b24bdf02c 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs @@ -1,5 +1,6 @@ using Esprima.Ast; using Jint.Native.Function; +using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; namespace Jint.Runtime.Interpreter.Expressions @@ -75,8 +76,43 @@ private ScriptFunctionInstance InstantiateOrdinaryFunctionExpression(EvaluationC /// 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 = intrinsics.Object.OrdinaryObjectCreate(intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject); + closure.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable)); + + funcEnv?.InitializeBinding(name, closure); + + return closure; } } -} +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs new file mode 100644 index 0000000000..36d385df66 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs @@ -0,0 +1,235 @@ +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 + { + private readonly Engine _engine; + + public JintYieldExpression(Engine engine, YieldExpression expression) : base(expression) + { + _engine = engine; + } + + protected override ExpressionResult EvaluateInternal(EvaluationContext context) + { + var expression = (YieldExpression) _expression; + var engine = context.Engine; + + var value = JsValue.Undefined; + if (expression.Argument is not null) + { + var completion = Build(engine, expression.Argument).GetValue(context); + if (completion.IsAbrupt()) + { + return completion; + } + + value = completion.Value; + } + + if (expression.Delegate) + { + value = YieldDelegate(value); + } + + return new ExpressionResult(ExpressionCompletionType.Return, Yield(value), _expression.Location); + } + + /// + /// https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation + /// + private JsValue YieldDelegate(JsValue value) + { + var generatorKind = _engine.ExecutionContext.GetGeneratorKind(); + var iterator = value.GetIterator(_engine.Realm, generatorKind); + var iteratorRecord = iterator; + var received = new Completion(CompletionType.Normal, JsValue.Undefined, default); + 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.Location); + 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 void AsyncIteratorClose(object iteratorRecord, CompletionType closeCompletion) + { + ExceptionHelper.ThrowNotImplementedException("async"); + } + + /// + /// https://tc39.es/ecma262/#sec-asyncgeneratoryield + /// + private Completion AsyncGeneratorYield(object iteratorValue) + { + ExceptionHelper.ThrowNotImplementedException("async"); + return default; + } + + /// + /// https://tc39.es/ecma262/#await + /// + private ObjectInstance Await(JsValue innerResult) + { + ExceptionHelper.ThrowNotImplementedException("await"); + return null; + } + + /// + /// https://tc39.es/ecma262/#sec-yield + /// + private JsValue Yield(JsValue iterNextObj) + { + 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; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index 14c285410b..7d02778ffb 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -3,6 +3,7 @@ using Esprima.Ast; using Jint.Native; using Jint.Native.Function; +using Jint.Native.Generator; using Jint.Runtime.Interpreter.Expressions; namespace Jint.Runtime.Interpreter @@ -47,9 +48,7 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun } else if (Function.Generator) { - result = EvaluateFunctionBody(context, functionObject, argumentsList); - // TODO generators - // result = EvaluateGeneratorBody(functionObject, argumentsList); + result = EvaluateGeneratorBody(functionObject, argumentsList); } else { @@ -64,8 +63,17 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun /// private Completion EvaluateGeneratorBody(FunctionInstance functionObject, JsValue[] argumentsList) { - ExceptionHelper.ThrowNotImplementedException("generators not implemented"); - return default; + _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.Location); } /// diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index 6374de3170..18addc2c19 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -79,38 +79,46 @@ public Completion Execute(EvaluationContext context) } JintStatement s = null; - var c = new Completion(CompletionType.Normal, null, null, context.LastSyntaxNode?.Location ?? default); - Completion sl = c; + Completion c = context.ResumedCompletion; + Completion sl = context.ResumedCompletion; // The value of a StatementList is the value of the last value-producing item in the StatementList JsValue lastValue = null; try { - foreach (var pair in _jintStatements) + var temp = _jintStatements; + // if we run as generator, keep track of what's the last processed statement + var i = _generator ? _index : 0; + for (; i < temp.Length; i++) { + var pair = temp[i]; s = pair.Statement; c = pair.Value ?? s.Execute(context); - if (c.Type != CompletionType.Normal) + if (c.IsAbrupt()) { + _index = s.SupportsResume ? i : i + 1; return new Completion( c.Type, c.Value ?? sl.Value, c.Target, c.Location); } + sl = c; lastValue = c.Value ?? lastValue; } } catch (JavaScriptException v) { + Reset(); var location = v.Location == default ? s.Location : v.Location; var completion = new Completion(CompletionType.Throw, v.Error, null, location); return completion; } catch (TypeErrorException e) { + Reset(); var error = engine.Realm.Intrinsics.TypeError.Construct(new JsValue[] { e.Message @@ -119,12 +127,15 @@ public Completion Execute(EvaluationContext context) } catch (RangeErrorException e) { + Reset(); var error = engine.Realm.Intrinsics.RangeError.Construct(new JsValue[] { e.Message }); - c = new Completion(CompletionType.Throw, error, null, s.Location); + return new Completion(CompletionType.Throw, error, null, s.Location); } + + Reset(); return new Completion(c.Type, lastValue ?? JsValue.Undefined, c.Target, c.Location); } @@ -165,5 +176,12 @@ internal static void BlockDeclarationInstantiation( } } } + + public bool Completed => _index == _jintStatements.Length; + + public void Reset() + { + _index = 0; + } } -} +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs index 70bd7f7257..f2f0f77a3a 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -287,6 +287,11 @@ private Completion BodyEvaluation( if (result.Type != CompletionType.Continue || (result.Target != null && result.Target != _statement?.LabelSet?.Name)) { 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; diff --git a/Jint/Runtime/Intrinsics.cs b/Jint/Runtime/Intrinsics.cs index 7cc2d061f0..9ad905c796 100644 --- a/Jint/Runtime/Intrinsics.cs +++ b/Jint/Runtime/Intrinsics.cs @@ -7,6 +7,7 @@ using Jint.Native.Date; using Jint.Native.Error; using Jint.Native.Function; +using Jint.Native.Generator; using Jint.Native.Iterator; using Jint.Native.Json; using Jint.Native.Map; @@ -59,6 +60,7 @@ public sealed class Intrinsics private MathInstance _math; private JsonInstance _json; private SymbolConstructor _symbol; + private GeneratorFunctionConstructor _generatorFunction; private RegExpConstructor _regExp; private RegExpStringIteratorPrototype _regExpStringIteratorPrototype; private NumberConstructor _number; @@ -218,6 +220,9 @@ internal Intrinsics(Engine engine, Realm realm) public SymbolConstructor Symbol => _symbol ??= new SymbolConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject); + public GeneratorFunctionConstructor GeneratorFunction => + _generatorFunction ??= new GeneratorFunctionConstructor(_engine, _realm, Function.PrototypeObject, IteratorPrototype); + public EvalFunctionInstance Eval => _eval ??= new EvalFunctionInstance(_engine, _realm, Function.PrototypeObject);