diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index c7c391a485..b11fc6dcfe 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -15,7 +15,6 @@ "class-static-fields-public", "class-static-methods-private", "decorators", - "generators", "import-assertions", "regexp-duplicate-named-groups", "regexp-lookbehind", diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 5ab7ec1a23..adbd48fae0 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -4,6 +4,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; @@ -1263,6 +1264,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. /// @@ -1426,6 +1433,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 1f9c80d431..99ecc9d204 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -115,8 +115,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(_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 f06b45ee02..a26221a415 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()); @@ -157,7 +160,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 812fc975fa..6cef60a341 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -323,6 +323,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..79dbbbc1ef --- /dev/null +++ b/Jint/Native/Generator/GeneratorFunctionConstructor.cs @@ -0,0 +1,46 @@ +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 +/// +internal 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..5ffa2aca9e --- /dev/null +++ b/Jint/Native/Generator/GeneratorFunctionPrototype.cs @@ -0,0 +1,44 @@ +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 +/// +internal 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); + } +} diff --git a/Jint/Native/Generator/GeneratorInstance.cs b/Jint/Native/Generator/GeneratorInstance.cs new file mode 100644 index 0000000000..b68736ce33 --- /dev/null +++ b/Jint/Native/Generator/GeneratorInstance.cs @@ -0,0 +1,134 @@ +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 = null!; + + 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; + } +} 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/Generator/GeneratorPrototype.cs b/Jint/Native/Generator/GeneratorPrototype.cs new file mode 100644 index 0000000000..8b17bd929f --- /dev/null +++ b/Jint/Native/Generator/GeneratorPrototype.cs @@ -0,0 +1,89 @@ +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 +/// +internal 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, null!); + 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, null!); + 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; + } +} diff --git a/Jint/Native/Generator/GeneratorState.cs b/Jint/Native/Generator/GeneratorState.cs new file mode 100644 index 0000000000..3848f9625a --- /dev/null +++ b/Jint/Native/Generator/GeneratorState.cs @@ -0,0 +1,10 @@ +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 eb585dd355..5cb76c32a6 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Runtime.CompilerServices; using System.Text; using Jint.Collections; @@ -46,6 +46,7 @@ protected override void Initialize() ["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, propertyFlags), ["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, propertyFlags), ["Function"] = new PropertyDescriptor(_realm.Intrinsics.Function, propertyFlags), + ["GeneratorFunction"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.GeneratorFunction, propertyFlags), ["Int16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, propertyFlags), ["Int32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, propertyFlags), ["Int8Array"] = 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 21e099c394..0c8d51de0b 100644 --- a/Jint/Native/Iterator/IteratorInstance.cs +++ b/Jint/Native/Iterator/IteratorInstance.cs @@ -91,7 +91,7 @@ public ValueIteratorPosition(Engine engine, JsValue value, bool? done = null) : } } - public sealed class ListIterator : IteratorInstance + internal sealed class ListIterator : IteratorInstance { private readonly List _values; private int _position; diff --git a/Jint/Runtime/Environments/ExecutionContext.cs b/Jint/Runtime/Environments/ExecutionContext.cs index 5adae240c8..6745ca0acc 100644 --- a/Jint/Runtime/Environments/ExecutionContext.cs +++ b/Jint/Runtime/Environments/ExecutionContext.cs @@ -1,69 +1,90 @@ -using System.Runtime.InteropServices; using Jint.Native.Function; -namespace Jint.Runtime.Environments; +using Jint.Native.Generator; -[StructLayout(LayoutKind.Auto)] -internal readonly struct ExecutionContext +namespace Jint.Runtime.Environments { - internal ExecutionContext( - IScriptOrModule? scriptOrModule, - EnvironmentRecord lexicalEnvironment, - EnvironmentRecord variableEnvironment, - PrivateEnvironmentRecord? privateEnvironment, - Realm realm, - FunctionInstance? function = null) + internal readonly struct ExecutionContext { - ScriptOrModule = scriptOrModule; - LexicalEnvironment = lexicalEnvironment; - VariableEnvironment = variableEnvironment; - PrivateEnvironment = privateEnvironment; - Realm = realm; - Function = function; - } + internal ExecutionContext( + IScriptOrModule? scriptOrModule, + EnvironmentRecord lexicalEnvironment, + EnvironmentRecord variableEnvironment, + PrivateEnvironmentRecord? privateEnvironment, + Realm realm, + GeneratorInstance? generator = null, + FunctionInstance? function = null) + { + ScriptOrModule = scriptOrModule; + LexicalEnvironment = lexicalEnvironment; + VariableEnvironment = variableEnvironment; + PrivateEnvironment = privateEnvironment; + Realm = realm; + Function = function; + Generator = generator; + } - public readonly IScriptOrModule? ScriptOrModule; - public readonly EnvironmentRecord LexicalEnvironment; - public readonly EnvironmentRecord VariableEnvironment; - public readonly PrivateEnvironmentRecord? PrivateEnvironment; - public readonly Realm Realm; - public readonly FunctionInstance? Function; + public readonly IScriptOrModule? ScriptOrModule; + public readonly EnvironmentRecord LexicalEnvironment; + public readonly EnvironmentRecord VariableEnvironment; + 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); - } + public ExecutionContext UpdateLexicalEnvironment(EnvironmentRecord lexicalEnvironment) + { + return new ExecutionContext(ScriptOrModule, lexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, Generator, Function); + } - public ExecutionContext UpdateVariableEnvironment(EnvironmentRecord variableEnvironment) - { - return new ExecutionContext(ScriptOrModule, LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Function); - } + public ExecutionContext UpdateVariableEnvironment(EnvironmentRecord variableEnvironment) + { + return new ExecutionContext(ScriptOrModule, LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Generator, Function); + } - public ExecutionContext UpdatePrivateEnvironment(PrivateEnvironmentRecord? privateEnvironment) - { - return new ExecutionContext(ScriptOrModule, LexicalEnvironment, VariableEnvironment, privateEnvironment, Realm, Function); - } + public ExecutionContext UpdatePrivateEnvironment(PrivateEnvironmentRecord? privateEnvironment) + { + return new ExecutionContext(ScriptOrModule, LexicalEnvironment, VariableEnvironment, privateEnvironment, Realm, Generator, Function); + } - /// - /// https://tc39.es/ecma262/#sec-getthisenvironment - /// - internal EnvironmentRecord GetThisEnvironment() - { - // The loop will always terminate because the list of environments always - // ends with the global environment which has a this binding. - var lex = LexicalEnvironment; - while (true) + public ExecutionContext UpdateGenerator(GeneratorInstance generator) { - if (lex != null) + return new ExecutionContext(ScriptOrModule, LexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, generator, Function); + } + + /// + /// https://tc39.es/ecma262/#sec-getthisenvironment + /// + internal EnvironmentRecord GetThisEnvironment() + { + // The loop will always terminate because the list of environments always + // ends with the global environment which has a this binding. + var lex = LexicalEnvironment; + while (true) { - if (lex.HasThisBinding()) + if (lex != null) { - return lex; + if (lex.HasThisBinding()) + { + return lex; + + } + lex = lex._outerEnv; } + } + } - lex = lex._outerEnv; + 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 3066598465..0d892218ff 100644 --- a/Jint/Runtime/ExecutionContextStack.cs +++ b/Jint/Runtime/ExecutionContextStack.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using Jint.Collections; +using Jint.Native.Generator; using Jint.Runtime.Environments; namespace Jint.Runtime @@ -34,6 +35,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 ea2fd12af2..8ff81e2295 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -139,6 +139,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 311751ce10..1f269d1498 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 @@ -122,8 +124,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..da0e4d27a3 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs @@ -0,0 +1,227 @@ +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; + var engine = context.Engine; + + var value = JsValue.Undefined; + if (expression.Argument is not null) + { + value = Build(expression.Argument).GetValue(context); + } + + 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 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(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 785866b038..46d0fee430 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; @@ -61,11 +62,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 { @@ -106,7 +103,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: @@ -147,10 +148,23 @@ 8. Return unused. /// /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody /// - private 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 a909ef9395..ec168505b8 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -1,6 +1,7 @@ -using Esprima.Ast; +using Esprima.Ast; using Jint.Native; using Jint.Native.Error; +using Jint.Native.Generator; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Statements; @@ -85,19 +86,29 @@ public Completion Execute(EvaluationContext context) JsValue? lastValue = null; try { - foreach (var pair in _jintStatements!) + var statements = _jintStatements; + for (var i = _index; i < (uint) statements!.Length; i++) { + var pair = statements![i]; s = pair.Statement; c = pair.Value.GetValueOrDefault(); + if (c.Value is null) { c = s.Execute(context); } + if (_generator && context.Engine.ExecutionContext.Generator?._generatorState == GeneratorState.SuspendedYield) + { + _index = i + 1; + return new Completion(CompletionType.Return, c.Value, s._statement); + } + if (c.Type != CompletionType.Normal) { return new Completion(c.Type, c.Value ?? sl.Value!, c._source); } + sl = c; if (c.Value is not null) { @@ -107,6 +118,8 @@ public Completion Execute(EvaluationContext context) } catch (Exception ex) { + Reset(); + if (ex is JintException) { return HandleException(context, ex, s); @@ -192,5 +205,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 933089afd1..586a994922 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -289,6 +289,11 @@ private Completion BodyEvaluation( if (result.Type != CompletionType.Continue || (context.Target != null && context.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/Interpreter/Statements/JintTryStatement.cs b/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs index 890db75b10..674e6e954c 100644 --- a/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs @@ -42,6 +42,11 @@ protected override Completion ExecuteInternal(EvaluationContext context) if (_finalizer != null) { + if (context.ResumedCompletion.Type != CompletionType.Normal) + { + return context.ResumedCompletion; + } + var f = _finalizer.Execute(context); if (f.Type == CompletionType.Normal) { diff --git a/Jint/Runtime/Intrinsics.cs b/Jint/Runtime/Intrinsics.cs index 4100711185..898a0ec3eb 100644 --- a/Jint/Runtime/Intrinsics.cs +++ b/Jint/Runtime/Intrinsics.cs @@ -10,6 +10,7 @@ using Jint.Native.Error; using Jint.Native.FinalizationRegistry; using Jint.Native.Function; +using Jint.Native.Generator; using Jint.Native.Iterator; using Jint.Native.Json; using Jint.Native.Map; @@ -66,6 +67,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; @@ -244,6 +246,9 @@ internal Intrinsics(Engine engine, Realm realm) public ShadowRealmConstructor ShadowRealm => _shadowRealm ??= new ShadowRealmConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject); + internal GeneratorFunctionConstructor GeneratorFunction => + _generatorFunction ??= new GeneratorFunctionConstructor(_engine, _realm, Function.PrototypeObject, IteratorPrototype); + public EvalFunctionInstance Eval => _eval ??= new EvalFunctionInstance(_engine, _realm, Function.PrototypeObject);