From 9afa790fd913f11536daa67c197fa0d81800e00d Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 10 Oct 2020 12:00:49 +0300 Subject: [PATCH] wip --- .../Language/Expressions/YieldTests.cs | 15 ++ Jint.Tests.Test262/Test262Test.cs | 4 - Jint/Engine.cs | 8 + Jint/Native/Array/ArrayConstructor.cs | 3 +- Jint/Native/Function/FunctionConstructor.cs | 175 ++++++++++++++---- Jint/Native/Function/FunctionInstance.cs | 108 +++++++++-- Jint/Native/Function/FunctionKind.cs | 10 + .../Native/Function/ScriptFunctionInstance.cs | 84 +-------- Jint/Native/Generator/Generator.cs | 139 ++++++++++++++ .../Generator/GeneratorFunctionConstructor.cs | 43 +++++ .../Generator/GeneratorFunctionInstance.cs | 43 +++++ .../Generator/GeneratorFunctionPrototype.cs | 28 +++ Jint/Native/Generator/GeneratorKind.cs | 9 + Jint/Native/Generator/GeneratorPrototype.cs | 74 ++++++++ Jint/Native/Generator/GeneratorState.cs | 11 ++ Jint/Native/Iterator/IIterator.cs | 2 +- Jint/Native/Iterator/IteratorResult.cs | 45 +++++ Jint/Native/Map/MapPrototype.cs | 6 +- Jint/Native/Object/ObjectInstance.cs | 3 +- Jint/Native/Set/SetConstructor.cs | 2 +- Jint/Native/String/StringConstructor.cs | 2 +- .../TypedArray/TypedArrayConstructor.cs | 4 +- Jint/Runtime/Environments/ExecutionContext.cs | 29 ++- Jint/Runtime/ExecutionContextStack.cs | 9 + .../BindingPatternAssignmentExpression.cs | 2 +- .../Interpreter/Expressions/JintExpression.cs | 1 + .../Expressions/JintFunctionExpression.cs | 28 ++- .../Expressions/JintYieldExpression.cs | 51 +++++ .../Interpreter/JintFunctionDefinition.cs | 3 +- Jint/Runtime/Interpreter/JintStatementList.cs | 31 +++- .../Statements/JintBlockStatement.cs | 2 +- .../Statements/JintExpressionStatement.cs | 6 +- .../Interpreter/Statements/JintScript.cs | 2 +- Jint/Runtime/Intrinsics.cs | 5 + Jint/Runtime/KnownKeys.cs | 2 + 35 files changed, 818 insertions(+), 171 deletions(-) create mode 100644 Jint.Tests.Test262/Language/Expressions/YieldTests.cs create mode 100644 Jint/Native/Function/FunctionKind.cs create mode 100644 Jint/Native/Generator/Generator.cs create mode 100644 Jint/Native/Generator/GeneratorFunctionConstructor.cs create mode 100644 Jint/Native/Generator/GeneratorFunctionInstance.cs create mode 100644 Jint/Native/Generator/GeneratorFunctionPrototype.cs create mode 100644 Jint/Native/Generator/GeneratorKind.cs create mode 100644 Jint/Native/Generator/GeneratorPrototype.cs create mode 100644 Jint/Native/Generator/GeneratorState.cs create mode 100644 Jint/Native/Iterator/IteratorResult.cs create mode 100644 Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs diff --git a/Jint.Tests.Test262/Language/Expressions/YieldTests.cs b/Jint.Tests.Test262/Language/Expressions/YieldTests.cs new file mode 100644 index 0000000000..844527cacc --- /dev/null +++ b/Jint.Tests.Test262/Language/Expressions/YieldTests.cs @@ -0,0 +1,15 @@ +using Xunit; + +namespace Jint.Tests.Test262.Language.Expressions +{ + public class YieldTests : Test262Test + { + [Theory(DisplayName = "language\\expressions\\yield")] + [MemberData(nameof(SourceFiles), "language\\expressions\\yield", false)] + [MemberData(nameof(SourceFiles), "language\\expressions\\yield", true, Skip = "Skipped")] + protected void New(SourceFile sourceFile) + { + RunTestInternal(sourceFile); + } + } +} \ No newline at end of file diff --git a/Jint.Tests.Test262/Test262Test.cs b/Jint.Tests.Test262/Test262Test.cs index a830f09157..f0b22860a1 100644 --- a/Jint.Tests.Test262/Test262Test.cs +++ b/Jint.Tests.Test262/Test262Test.cs @@ -244,10 +244,6 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) skip = true; reason = "BigInt not implemented"; break; - case "generators": - skip = true; - reason = "generators not implemented"; - break; case "async-functions": skip = true; reason = "async-functions not implemented"; diff --git a/Jint/Engine.cs b/Jint/Engine.cs index c274d3e080..65b513b6b9 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; @@ -1222,6 +1223,13 @@ internal void UpdateVariableEnvironment(EnvironmentRecord newEnv) _executionContexts.ReplaceTopVariableEnvironment(newEnv); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ExecutionContext UpdateGenerator(Generator generator) + { + // TODO + return ExecutionContext; + } + internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, JintExpression expression) { if (callable is FunctionInstance functionInstance) diff --git a/Jint/Native/Array/ArrayConstructor.cs b/Jint/Native/Array/ArrayConstructor.cs index cb1f6603f7..a7c9931290 100644 --- a/Jint/Native/Array/ArrayConstructor.cs +++ b/Jint/Native/Array/ArrayConstructor.cs @@ -282,6 +282,7 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) } var proto = GetPrototypeFromConstructor( + _engine, newTarget, static intrinsics => intrinsics.Array.PrototypeObject); @@ -418,7 +419,7 @@ public ObjectInstance ArraySpeciesCreate(ObjectInstance originalArray, ulong len if (c.IsConstructor) { var thisRealm = _engine.ExecutionContext.Realm; - var realmC = GetFunctionRealm(c); + var realmC = GetFunctionRealm(_engine, c); if (!ReferenceEquals(thisRealm, realmC)) { if (ReferenceEquals(c, realmC.Intrinsics.Array)) diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index e0ea70eaf9..04b4d1b3f5 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -1,9 +1,12 @@ -using Esprima; +using System; +using Esprima; using Esprima.Ast; +using Jint.Native.Generator; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; +using Jint.Runtime.Interpreter; namespace Jint.Native.Function { @@ -32,43 +35,116 @@ public override JsValue Call(JsValue thisObject, JsValue[] arguments) return Construct(arguments, thisObject); } + public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) + { + var function = CreateDynamicFunction( + _engine, + this, + newTarget, + FunctionKind.Normal, + arguments); + + return function; + } + /// /// https://tc39.es/ecma262/#sec-createdynamicfunction /// - public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) + internal static FunctionInstance CreateDynamicFunction( + Engine engine, + ObjectInstance constructor, + JsValue newTarget, + FunctionKind kind, + JsValue[] args) { - var argCount = arguments.Length; - string p = ""; - string body = ""; + if (newTarget.IsUndefined()) + { + newTarget = constructor; + } + + Func fallbackProto = null; + switch (kind) + { + case FunctionKind.Normal: + fallbackProto = intrinsics => intrinsics.Function.PrototypeObject; + break; + case FunctionKind.Generator: + fallbackProto = intrinsics => intrinsics.GeneratorFunction.PrototypeObject; + break; + case FunctionKind.AsyncGenerator: + case FunctionKind.Async: + default: + ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString()); + break; + } + + var argCount = args.Length; + var p = ""; + var body = ""; if (argCount == 1) { - body = TypeConverter.ToString(arguments[0]); + body = TypeConverter.ToString(args[0]); } else if (argCount > 1) { - var firstArg = arguments[0]; + var firstArg = args[0]; p = TypeConverter.ToString(firstArg); for (var k = 1; k < argCount - 1; k++) { - var nextArg = arguments[k]; + var nextArg = args[k]; p += "," + TypeConverter.ToString(nextArg); } - body = TypeConverter.ToString(arguments[argCount-1]); + body = TypeConverter.ToString(args[argCount - 1]); } IFunction function = null; try { - string functionExpression; + string functionExpression = null; if (argCount == 0) { - functionExpression = "function f(){}"; + switch (kind) + { + case FunctionKind.Normal: + functionExpression = "function f(){}"; + break; + case FunctionKind.Generator: + functionExpression = "function* f(){}"; + break; + case FunctionKind.Async: + ExceptionHelper.ThrowNotImplementedException("Async functions not implemented"); + break; + case FunctionKind.AsyncGenerator: + ExceptionHelper.ThrowNotImplementedException("Async generators not implemented"); + break; + default: + ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString()); + break; + } } else { - functionExpression = "function f("; + switch (kind) + { + case FunctionKind.Normal: + functionExpression = "function f("; + break; + case FunctionKind.Generator: + functionExpression = "function* f("; + break; + case FunctionKind.Async: + ExceptionHelper.ThrowNotImplementedException("Async functions not implemented"); + break; + case FunctionKind.AsyncGenerator: + ExceptionHelper.ThrowNotImplementedException("Async generators not implemented"); + break; + default: + ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString()); + break; + } + if (p.IndexOf('/') != -1) { // ensure comments don't screw up things @@ -95,49 +171,70 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) var parser = new JavaScriptParser(functionExpression, ParserOptions); function = (IFunction) parser.ParseScript().Body[0]; } - catch (ParserException) + catch (ParserException ex) { - ExceptionHelper.ThrowSyntaxError(_realm); + ExceptionHelper.ThrowSyntaxError(engine.Realm, ex.Message); } - // TODO generators etc, rewrite logic - var proto = GetPrototypeFromConstructor(newTarget, static intrinsics => intrinsics.Function.PrototypeObject); - - var functionObject = new ScriptFunctionInstance( - Engine, - function, - _realm.GlobalEnv, - function.Strict, - proto) - { - _realm = _realm - }; + var proto = GetPrototypeFromConstructor(engine, newTarget, fallbackProto); + var thisMode = function.Strict + ? FunctionThisMode.Strict + : FunctionThisMode.Global; - functionObject.MakeConstructor(); + FunctionInstance F = new ScriptFunctionInstance( + engine, + new JintFunctionDefinition(engine, function), + engine.Realm.GlobalEnv, + thisMode, + proto); - // the function is not actually a named function - functionObject.SetFunctionName(_functionNameAnonymous, force: true); + if (kind == FunctionKind.Generator) + { + F = new GeneratorFunctionInstance(engine, engine.Realm, F); + } + else if (kind == FunctionKind.AsyncGenerator) + { + // TODO + // Let prototype be ! OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%). + // Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). + ExceptionHelper.ThrowNotImplementedException(); + } + else if (kind == FunctionKind.Normal) + { + F.MakeConstructor(); + } - return functionObject; + F.SetFunctionName(_functionNameAnonymous, force: true); + return F; } /// /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiatefunctionobject /// - internal FunctionInstance InstantiateFunctionObject(FunctionDeclaration functionDeclaration, EnvironmentRecord env) + internal FunctionInstance InstantiateFunctionObject(FunctionDeclaration functionDeclaration, EnvironmentRecord scope) { - var functionObject = new ScriptFunctionInstance( + var strict = functionDeclaration.Strict || _engine._isStrict; + FunctionInstance F = new ScriptFunctionInstance( Engine, functionDeclaration, - env, - functionDeclaration.Strict || _engine._isStrict) - { - _realm = _realm - }; + scope, + strict, + _realm.Intrinsics.Function.PrototypeObject);; - functionObject.MakeConstructor(); + var name = functionDeclaration.Id?.Name ?? "default"; + if (functionDeclaration.Generator) + { + // https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-instantiatefunctionobject + F = new GeneratorFunctionInstance(_engine, _realm, F); + } + else + { + // https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-instantiatefunctionobject + F.MakeConstructor(); + } - return functionObject; + F.SetFunctionName(name); + return F; } } } \ No newline at end of file diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index 4ba7b72b33..3099e2edeb 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -246,7 +246,7 @@ internal T OrdinaryCreateFromConstructor( Func objectCreator, JsValue state = null) where T : ObjectInstance { - var proto = GetPrototypeFromConstructor(constructor, intrinsicDefaultProto); + var proto = GetPrototypeFromConstructor(_engine, constructor, intrinsicDefaultProto); var obj = objectCreator(_engine, _realm, state); obj._prototype = proto; @@ -257,12 +257,12 @@ internal T OrdinaryCreateFromConstructor( /// https://tc39.es/ecma262/#sec-getprototypefromconstructor /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ObjectInstance GetPrototypeFromConstructor(JsValue constructor, Func intrinsicDefaultProto) + internal static ObjectInstance GetPrototypeFromConstructor(Engine engine, JsValue constructor, Func intrinsicDefaultProto) { var proto = constructor.Get(CommonProperties.Prototype, constructor) as ObjectInstance; if (proto is null) { - var realm = GetFunctionRealm(constructor); + var realm = GetFunctionRealm(engine, constructor); proto = intrinsicDefaultProto(realm.Intrinsics); } return proto; @@ -271,7 +271,7 @@ internal ObjectInstance GetPrototypeFromConstructor(JsValue constructor, Func /// https://tc39.es/ecma262/#sec-getfunctionrealm /// - internal Realm GetFunctionRealm(JsValue obj) + internal static Realm GetFunctionRealm(Engine engine, JsValue obj) { if (obj is FunctionInstance functionInstance && functionInstance._realm is not null) { @@ -280,7 +280,7 @@ internal Realm GetFunctionRealm(JsValue obj) if (obj is BindFunctionInstance bindFunctionInstance) { - return GetFunctionRealm(bindFunctionInstance.TargetFunction); + return GetFunctionRealm(engine, bindFunctionInstance.TargetFunction); } if (obj is ProxyInstance proxyInstance) @@ -290,10 +290,10 @@ internal Realm GetFunctionRealm(JsValue obj) ExceptionHelper.ThrowTypeErrorNoEngine(); } - return GetFunctionRealm(proxyInstance._target); + return GetFunctionRealm(engine, proxyInstance._target); } - return _engine.ExecutionContext.Realm; + return engine.ExecutionContext.Realm; } internal void MakeMethod(ObjectInstance homeObject) @@ -315,7 +315,6 @@ internal void OrdinaryCallBindThis(ExecutionContext calleeContext, JsValue thisA var calleeRealm = _realm; var localEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment; - JsValue thisValue; if (_thisMode == FunctionThisMode.Strict) { @@ -341,10 +340,7 @@ internal Completion OrdinaryCallEvaluateBody( JsValue[] arguments, ExecutionContext calleeContext) { - var argumentsInstance = _engine.FunctionDeclarationInstantiation( - functionInstance: this, - arguments, - calleeContext.LexicalEnvironment); + var argumentsInstance = _engine.FunctionDeclarationInstantiation(functionInstance: this, arguments, _environment); var result = _functionDefinition.Execute(); var value = result.GetValueOrDefault().Clone(); @@ -365,11 +361,12 @@ internal ExecutionContext PrepareForOrdinaryCall(JsValue newTarget) var calleeRealm = _realm; var calleeContext = new ExecutionContext( - localEnv, - localEnv, + lexicalEnvironment: localEnv, + variableEnvironment: localEnv, _privateEnvironment, calleeRealm, - this); + generator: null, + function: this); // If callerContext is not already suspended, suspend callerContext. // Push calleeContext onto the execution context stack; calleeContext is now the running execution context. @@ -379,6 +376,20 @@ internal ExecutionContext PrepareForOrdinaryCall(JsValue newTarget) return _engine.EnterExecutionContext(calleeContext); } + internal void MakeConstructor(bool writableProperty = true, ObjectInstance prototype = null) + { + _constructorKind = ConstructorKind.Base; + if (prototype is null) + { + prototype = new ObjectInstanceWithConstructor(_engine, this) + { + _prototype = _realm.Intrinsics.Object.PrototypeObject + }; + } + + _prototypeDescriptor = new PropertyDescriptor(prototype, writableProperty, enumerable: false, configurable: false); + } + public override string ToString() { // TODO no way to extract SourceText from Esprima at the moment, just returning native code @@ -390,5 +401,72 @@ public override string ToString() } return "function " + name + "() { [native code] }"; } + + private class ObjectInstanceWithConstructor : ObjectInstance + { + private PropertyDescriptor _constructor; + + public ObjectInstanceWithConstructor(Engine engine, ObjectInstance thisObj) : base(engine) + { + _constructor = new PropertyDescriptor(thisObj, PropertyFlag.NonEnumerable); + } + + public override IEnumerable> GetOwnProperties() + { + if (_constructor != null) + { + yield return new KeyValuePair(CommonProperties.Constructor, _constructor); + } + + foreach (var entry in base.GetOwnProperties()) + { + yield return entry; + } + } + + public override PropertyDescriptor GetOwnProperty(JsValue property) + { + if (property == CommonProperties.Constructor) + { + return _constructor ?? PropertyDescriptor.Undefined; + } + + return base.GetOwnProperty(property); + } + + protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) + { + if (property == CommonProperties.Constructor) + { + _constructor = desc; + } + else + { + base.SetOwnProperty(property, desc); + } + } + + public override bool HasOwnProperty(JsValue property) + { + if (property == CommonProperties.Constructor) + { + return _constructor != null; + } + + return base.HasOwnProperty(property); + } + + public override void RemoveOwnProperty(JsValue property) + { + if (property == CommonProperties.Constructor) + { + _constructor = null; + } + else + { + base.RemoveOwnProperty(property); + } + } + } } } diff --git a/Jint/Native/Function/FunctionKind.cs b/Jint/Native/Function/FunctionKind.cs new file mode 100644 index 0000000000..dbc0d57255 --- /dev/null +++ b/Jint/Native/Function/FunctionKind.cs @@ -0,0 +1,10 @@ +namespace Jint.Native.Function +{ + internal enum FunctionKind + { + Normal, + Async, + Generator, + AsyncGenerator + } +} \ No newline at end of file diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index f5123acb4a..602292181d 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Esprima.Ast; +using Esprima.Ast; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; @@ -181,90 +180,9 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return (ObjectInstance) constructorEnv.GetThisBinding(); } - internal void MakeConstructor(bool writableProperty = true, ObjectInstance prototype = null) - { - _constructorKind = ConstructorKind.Base; - if (prototype is null) - { - prototype = new ObjectInstanceWithConstructor(_engine, this) - { - _prototype = _realm.Intrinsics.Object.PrototypeObject - }; - } - - _prototypeDescriptor = new PropertyDescriptor(prototype, writableProperty, enumerable: false, configurable: false); - } - internal void MakeClassConstructor() { _isClassConstructor = true; } - - private class ObjectInstanceWithConstructor : ObjectInstance - { - private PropertyDescriptor _constructor; - - public ObjectInstanceWithConstructor(Engine engine, ObjectInstance thisObj) : base(engine) - { - _constructor = new PropertyDescriptor(thisObj, PropertyFlag.NonEnumerable); - } - - public override IEnumerable> GetOwnProperties() - { - if (_constructor != null) - { - yield return new KeyValuePair(CommonProperties.Constructor, _constructor); - } - - foreach (var entry in base.GetOwnProperties()) - { - yield return entry; - } - } - - public override PropertyDescriptor GetOwnProperty(JsValue property) - { - if (property == CommonProperties.Constructor) - { - return _constructor ?? PropertyDescriptor.Undefined; - } - - return base.GetOwnProperty(property); - } - - protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) - { - if (property == CommonProperties.Constructor) - { - _constructor = desc; - } - else - { - base.SetOwnProperty(property, desc); - } - } - - public override bool HasOwnProperty(JsValue property) - { - if (property == CommonProperties.Constructor) - { - return _constructor != null; - } - - return base.HasOwnProperty(property); - } - - public override void RemoveOwnProperty(JsValue property) - { - if (property == CommonProperties.Constructor) - { - _constructor = null; - } - else - { - base.RemoveOwnProperty(property); - } - } - } } } \ No newline at end of file diff --git a/Jint/Native/Generator/Generator.cs b/Jint/Native/Generator/Generator.cs new file mode 100644 index 0000000000..aa8572495e --- /dev/null +++ b/Jint/Native/Generator/Generator.cs @@ -0,0 +1,139 @@ +using Jint.Native.Function; +using Jint.Native.Iterator; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Environments; + +namespace Jint.Native.Generator +{ + /// + /// https://tc39.es/ecma262/#sec-properties-of-generator-instances + /// + internal sealed class Generator : ObjectInstance + { + internal GeneratorState _generatorState; + private ExecutionContext _generatorContext; + private readonly object _generatorBrand = null; + private FunctionInstance _generatorBody; + + public Generator(Engine engine, Realm realm) : base(engine) + { + _prototype = realm.Intrinsics.GeneratorFunction.PrototypeObject.Prototype; + } + + /// + /// https://tc39.es/ecma262/#sec-generatorstart + /// + public JsValue GeneratorStart(FunctionInstance generatorBody) + { + var genContext = _engine.UpdateGenerator(this); + _generatorBody = generatorBody; + + /* + *Set the code evaluation state of genContext such that when evaluation is resumed for that execution context the following steps will be performed: +If generatorBody is a Parse Node, then +Let result be the result of evaluating generatorBody. +Else, +Assert: generatorBody is an Abstract Closure with no parameters. +Let result be generatorBody(). +Assert: If we return here, the generator either threw an exception or performed either an implicit or explicit return. +Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. +Set generator.[[GeneratorState]] to completed. +Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point. +If result.[[Type]] is normal, let resultValue be undefined. +Else if result.[[Type]] is return, let resultValue be result.[[Value]]. +Else, +Assert: result.[[Type]] is throw. +Return Completion(result). +Return CreateIterResultObject(resultValue, true). + */ + + _generatorContext = genContext; + _generatorState = GeneratorState.SuspendedStart; + + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-generatorresume + /// + public JsValue GeneratorResume(JsValue value, object generatorBrand) + { + var state = GeneratorValidate(generatorBrand); + if (state == GeneratorState.Completed) + { + return new IteratorResult(_engine, Undefined, JsBoolean.True); + } + + var genContext = _generatorContext; + var methodContext = _engine.ExecutionContext; + + // 6. Suspend methodContext. + + _generatorState = GeneratorState.Executing; + _engine.EnterExecutionContext(genContext); + + // TODO + var result = _generatorBody._functionDefinition.Execute(); + + return result.Value.IsUndefined() + ? new IteratorResult(_engine, Undefined, JsBoolean.True) + : result.Value; + } + + /// + /// https://tc39.es/ecma262/#sec-generatorresumeabrupt + /// + public JsValue GeneratorResumeAbrupt(in Completion abruptCompletion, object 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. + + _generatorState = GeneratorState.Executing; + + _engine.EnterExecutionContext(genContext); + + // Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. + // Let result be the completion record returned by the resumed computation. + var result = _generatorBody.OrdinaryCallEvaluateBody(Arguments.Empty, genContext); + + return result.Value; + } + + + private GeneratorState GeneratorValidate(object generatorBrand) + { + if (!ReferenceEquals(generatorBrand, _generatorBrand)) + { + ExceptionHelper.ThrowTypeError(_engine.Realm); + } + + if (_generatorState == GeneratorState.Executing) + { + ExceptionHelper.ThrowTypeError(_engine.Realm); + } + + return _generatorState; + } + + } +} \ No newline at end of file diff --git a/Jint/Native/Generator/GeneratorFunctionConstructor.cs b/Jint/Native/Generator/GeneratorFunctionConstructor.cs new file mode 100644 index 0000000000..0e9d94cd1a --- /dev/null +++ b/Jint/Native/Generator/GeneratorFunctionConstructor.cs @@ -0,0 +1,43 @@ +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.Generator +{ + public sealed class GeneratorFunctionConstructor : FunctionInstance, IConstructor + { + private static readonly JsString _functionName = new JsString("Function"); + + internal GeneratorFunctionConstructor( + Engine engine, + Realm realm, + FunctionPrototype prototype) + : base(engine, realm, _functionName) + { + PrototypeObject = new GeneratorFunctionPrototype(engine, prototype); + _prototype = PrototypeObject; + _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); + _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable); + } + + public GeneratorFunctionPrototype PrototypeObject { get; } + + public override JsValue Call(JsValue thisObject, JsValue[] arguments) + { + return Construct(arguments, thisObject); + } + + public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) + { + var function = FunctionConstructor.CreateDynamicFunction( + _engine, + this, + newTarget, + FunctionKind.Generator, + arguments); + + return function; + } + } +} \ No newline at end of file diff --git a/Jint/Native/Generator/GeneratorFunctionInstance.cs b/Jint/Native/Generator/GeneratorFunctionInstance.cs new file mode 100644 index 0000000000..e90f6ed6f0 --- /dev/null +++ b/Jint/Native/Generator/GeneratorFunctionInstance.cs @@ -0,0 +1,43 @@ +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.Generator +{ + /// + /// https://tc39.es/ecma262/#sec-generatorfunction-objects + /// + internal sealed class GeneratorFunctionInstance : FunctionInstance + { + private readonly FunctionInstance _functionBody; + + public GeneratorFunctionInstance( + Engine engine, + Realm realm, + FunctionInstance functionBody) : base(engine, realm, JsString.Empty) + { + _functionBody = functionBody; + _prototype = realm.Intrinsics.GeneratorFunction.PrototypeObject; + var prototype = new ObjectInstance(_engine) + { + _prototype = realm.Intrinsics.GeneratorFunction.PrototypeObject._prototype + }; + _prototypeDescriptor = new PropertyDescriptor(prototype, PropertyFlag.Writable); + } + + /// + /// https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluatebody + /// + public override JsValue Call(JsValue functionObject, JsValue[] arguments) + { + _engine.FunctionDeclarationInstantiation(_functionBody, arguments, _environment); + var G = OrdinaryCreateFromConstructor( + functionObject, + intrinsics => intrinsics.GeneratorFunction.PrototypeObject.Prototype, + (engine, realm, state) => new Generator(engine, realm)); + G.GeneratorStart(_functionBody); + return G; + } + } +} \ No newline at end of file diff --git a/Jint/Native/Generator/GeneratorFunctionPrototype.cs b/Jint/Native/Generator/GeneratorFunctionPrototype.cs new file mode 100644 index 0000000000..fe842157c3 --- /dev/null +++ b/Jint/Native/Generator/GeneratorFunctionPrototype.cs @@ -0,0 +1,28 @@ +using Jint.Collections; +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Native.Symbol; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.Generator +{ + /// + /// https://tc39.es/ecma262/#sec-generator-objects + /// + public sealed class GeneratorFunctionPrototype : ObjectInstance + { + internal GeneratorFunctionPrototype(Engine engine, FunctionPrototype prototype) : base(engine) + { + _prototype = prototype; + } + + protected override void Initialize() + { + 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/GeneratorKind.cs b/Jint/Native/Generator/GeneratorKind.cs new file mode 100644 index 0000000000..9c8abf9e4c --- /dev/null +++ b/Jint/Native/Generator/GeneratorKind.cs @@ -0,0 +1,9 @@ +namespace Jint.Native.Generator +{ + internal enum GeneratorKind + { + 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..72b0d4d72d --- /dev/null +++ b/Jint/Native/Generator/GeneratorPrototype.cs @@ -0,0 +1,74 @@ +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 + /// + internal sealed class GeneratorPrototype : ObjectInstance + { + internal GeneratorPrototype(Engine engine, Realm realm) : base(engine) + { + _prototype = new IteratorPrototype(engine, realm, name: "", realm.Intrinsics.Object.PrototypeObject); + } + + protected override void Initialize() + { + const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + const PropertyFlag lengthFlags = PropertyFlag.Configurable; + var properties = new PropertyDictionary(7, false) + { + ["constructor"] = new(_engine.Realm.Intrinsics.GeneratorFunction, PropertyFlag.NonEnumerable), + ["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 static JsValue Next(JsValue thisObject, JsValue[] arguments) + { + var g = (Generator) thisObject; + var value = arguments.At(0); + return g.GeneratorResume(value, null); + } + + /// + /// https://tc39.es/ecma262/#sec-generator.prototype.return + /// + private static JsValue Return(JsValue thisObject, JsValue[] arguments) + { + var g = (Generator) thisObject; + var value = arguments.At(0); + var C = new Completion(CompletionType.Return, value, null, new Location()); + return g.GeneratorResumeAbrupt(C, null); + } + + /// + /// https://tc39.es/ecma262/#sec-generator.prototype.throw + /// + private static JsValue Throw(JsValue thisObject, JsValue[] arguments) + { + var g = (Generator) thisObject; + var exception = arguments.At(0); + var C = new Completion(CompletionType.Throw, exception, null, new Location()); + return g.GeneratorResumeAbrupt(C, null); + } + } +} \ 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/Iterator/IIterator.cs b/Jint/Native/Iterator/IIterator.cs index e1dee208e4..0ef7149df9 100644 --- a/Jint/Native/Iterator/IIterator.cs +++ b/Jint/Native/Iterator/IIterator.cs @@ -3,7 +3,7 @@ namespace Jint.Native.Iterator { - public interface IIterator + internal interface IIterator { bool TryIteratorStep(out ObjectInstance nextItem); void Close(CompletionType completion); diff --git a/Jint/Native/Iterator/IteratorResult.cs b/Jint/Native/Iterator/IteratorResult.cs new file mode 100644 index 0000000000..cc4e28cfd6 --- /dev/null +++ b/Jint/Native/Iterator/IteratorResult.cs @@ -0,0 +1,45 @@ +using Jint.Native.Object; +using Jint.Runtime; + +namespace Jint.Native.Iterator +{ + /// + /// https://tc39.es/ecma262/#sec-createiterresultobject + /// + internal class IteratorResult : ObjectInstance + { + private readonly JsValue _value; + private readonly JsBoolean _done; + + public IteratorResult(Engine engine, JsValue value, JsBoolean done) : base(engine) + { + _value = value; + _done = done; + } + + public override JsValue Get(JsValue property, JsValue receiver) + { + if (property == CommonProperties.Value) + { + return _value; + } + + if (property == CommonProperties.Done) + { + return _done; + } + + return base.Get(property, receiver); + } + + public override object ToObject() + { + return this; + } + + public override bool Equals(JsValue other) + { + return ReferenceEquals(this, other); + } + } +} \ No newline at end of file diff --git a/Jint/Native/Map/MapPrototype.cs b/Jint/Native/Map/MapPrototype.cs index ed2d25a2c3..58ed0c68a7 100644 --- a/Jint/Native/Map/MapPrototype.cs +++ b/Jint/Native/Map/MapPrototype.cs @@ -33,7 +33,7 @@ protected override void Initialize() ["constructor"] = new PropertyDescriptor(_mapConstructor, PropertyFlag.NonEnumerable), ["clear"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "clear", Clear, 0, PropertyFlag.Configurable), propertyFlags), ["delete"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "delete", Delete, 1, PropertyFlag.Configurable), propertyFlags), - ["entries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "entries", Iterator, 0, PropertyFlag.Configurable), propertyFlags), + ["entries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "entries", Entries, 0, PropertyFlag.Configurable), propertyFlags), ["forEach"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), propertyFlags), ["get"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "get", Get, 1, PropertyFlag.Configurable), propertyFlags), ["has"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "has", Has, 1, PropertyFlag.Configurable), propertyFlags), @@ -46,7 +46,7 @@ protected override void Initialize() var symbols = new SymbolDictionary(2) { - [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "iterator", Iterator, 1, PropertyFlag.Configurable), propertyFlags), + [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "iterator", Entries, 1, PropertyFlag.Configurable), propertyFlags), [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("Map", false, false, true), }; SetSymbols(symbols); @@ -107,7 +107,7 @@ private JsValue ForEach(JsValue thisObj, JsValue[] arguments) return Undefined; } - private ObjectInstance Iterator(JsValue thisObj, JsValue[] arguments) + private ObjectInstance Entries(JsValue thisObj, JsValue[] arguments) { var map = AssertMapInstance(thisObj); return map.Iterator(); diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 6a050adfa0..1025ec5af5 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -329,8 +329,7 @@ internal static JsValue UnwrapJsValue(PropertyDescriptor desc, JsValue thisObjec : desc._value; // IsDataDescriptor inlined - if ((desc._flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 - || !ReferenceEquals(value, null)) + if ((desc._flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 || value is not null) { return value ?? Undefined; } diff --git a/Jint/Native/Set/SetConstructor.cs b/Jint/Native/Set/SetConstructor.cs index 679ce7cf7b..6df3c5db04 100644 --- a/Jint/Native/Set/SetConstructor.cs +++ b/Jint/Native/Set/SetConstructor.cs @@ -83,7 +83,7 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return set; } - next.TryGetValue(CommonProperties.Value, out var nextValue); + var nextValue = next.Get(CommonProperties.Value); args[0] = nextValue; adder.Call(set, args); } while (true); diff --git a/Jint/Native/String/StringConstructor.cs b/Jint/Native/String/StringConstructor.cs index 9f028a1f1c..3af773b32e 100644 --- a/Jint/Native/String/StringConstructor.cs +++ b/Jint/Native/String/StringConstructor.cs @@ -166,7 +166,7 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return StringCreate(s, PrototypeObject); } - return StringCreate(s, GetPrototypeFromConstructor(newTarget, static intrinsics => intrinsics.String.PrototypeObject)); + return StringCreate(s, GetPrototypeFromConstructor(_engine, newTarget, static intrinsics => intrinsics.String.PrototypeObject)); } public StringInstance Construct(string value) diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs index eeac9b8315..6fe9d1225b 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs @@ -265,8 +265,8 @@ private static void InitializeTypedArrayFromArrayLike(TypedArrayInstance o, Obje /// private TypedArrayInstance AllocateTypedArray(JsValue newTarget, Func defaultProto, uint length = 0) { - var proto = GetPrototypeFromConstructor(newTarget, defaultProto); - var realm = GetFunctionRealm(newTarget); + var proto = GetPrototypeFromConstructor(_engine, newTarget, defaultProto); + var realm = GetFunctionRealm(_engine, newTarget); var obj = new TypedArrayInstance(_engine, realm.Intrinsics, _arrayElementType, length) { _prototype = proto diff --git a/Jint/Runtime/Environments/ExecutionContext.cs b/Jint/Runtime/Environments/ExecutionContext.cs index e1e96bfed9..aa26aedc3d 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 @@ -11,6 +13,7 @@ internal ExecutionContext( EnvironmentRecord variableEnvironment, PrivateEnvironmentRecord? privateEnvironment, Realm realm, + Generator? generator = null, FunctionInstance? function = null) { LexicalEnvironment = lexicalEnvironment; @@ -18,23 +21,24 @@ internal ExecutionContext( PrivateEnvironment = privateEnvironment; Realm = realm; Function = function; + Generator = generator; } public readonly EnvironmentRecord LexicalEnvironment; - public readonly EnvironmentRecord VariableEnvironment; public readonly PrivateEnvironmentRecord? PrivateEnvironment; public readonly Realm Realm; public readonly FunctionInstance? Function; + public readonly Generator? Generator; public ExecutionContext UpdateLexicalEnvironment(EnvironmentRecord lexicalEnvironment) { - return new ExecutionContext(lexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, Function); + return new ExecutionContext(lexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, Generator, Function); } public ExecutionContext UpdateVariableEnvironment(EnvironmentRecord variableEnvironment) { - return new ExecutionContext(LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Function); + return new ExecutionContext(LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Generator, Function); } /// @@ -52,12 +56,29 @@ internal EnvironmentRecord GetThisEnvironment() if (lex.HasThisBinding()) { return lex; - + } 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; + } + + public ExecutionContext UpdateGenerator(Generator newEnv) + { + throw new System.NotImplementedException(); + } } } diff --git a/Jint/Runtime/ExecutionContextStack.cs b/Jint/Runtime/ExecutionContextStack.cs index f87a171b00..f790a646a9 100644 --- a/Jint/Runtime/ExecutionContextStack.cs +++ b/Jint/Runtime/ExecutionContextStack.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Jint.Collections; +using Jint.Native.Generator; using Jint.Runtime.Environments; namespace Jint.Runtime @@ -29,6 +30,14 @@ public void ReplaceTopVariableEnvironment(EnvironmentRecord newEnv) array[size - 1] = array[size - 1].UpdateVariableEnvironment(newEnv); } + public ref readonly ExecutionContext ReplaceTopGenerator(Generator 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/BindingPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs index 1925e48197..3bfd9a002a 100644 --- a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs @@ -63,7 +63,7 @@ private static bool ConsumeFromIterator(IIterator it, out JsValue value, out boo return false; } - d.TryGetValue(CommonProperties.Value, out value); + value = d.Get(CommonProperties.Value); return true; } diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index b44314e6ce..b947ca8b7c 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -124,6 +124,7 @@ protected internal static JintExpression Build(Engine engine, Expression express Nodes.ChainExpression => ((ChainExpression) expression).Expression.Type == Nodes.CallExpression ? new JintCallExpression(engine, (CallExpression) ((ChainExpression) expression).Expression) : new JintMemberExpression(engine, (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 1ad2b96a72..00f9e54a59 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.Native.Generator; using Jint.Runtime.Environments; namespace Jint.Runtime.Interpreter.Expressions @@ -17,24 +18,37 @@ public JintFunctionExpression(Engine engine, IFunction function) protected override object EvaluateInternal() { var funcEnv = JintEnvironment.NewDeclarativeEnvironment(_engine, _engine.ExecutionContext.LexicalEnvironment); - - var functionThisMode = _function.Strict || _engine._isStrict + var strict = _function.Strict || _engine._isStrict; + var functionThisMode = strict ? FunctionThisMode.Strict : FunctionThisMode.Global; - var closure = new ScriptFunctionInstance( + var name = _function.Name ?? ""; + FunctionInstance closure = new ScriptFunctionInstance( _engine, _function, funcEnv, functionThisMode); - closure.MakeConstructor(); - - if (_function.Name != null) + if (_function.Function.Generator) + { + // https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation + closure = new GeneratorFunctionInstance(_engine, _engine.Realm, closure); + } + else { - funcEnv.CreateMutableBindingAndInitialize(_function.Name, canBeDeleted: false, closure); + funcEnv.CreateMutableBindingAndInitialize(name, canBeDeleted: false, closure); + // https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluation + closure = new ScriptFunctionInstance( + _engine, + _function, + funcEnv, + functionThisMode); + + closure.MakeConstructor(); } + funcEnv.CreateImmutableBindingAndInitialize(name, strict: false, 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..ef9edeb60f --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs @@ -0,0 +1,51 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Generator; +using Jint.Native.Iterator; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed class JintYieldExpression : JintExpression + { + public JintYieldExpression(Engine engine, YieldExpression expression) : base(engine, expression) + { + } + + protected override object EvaluateInternal() + { + var expression = (YieldExpression) _expression; + var generatorKind = _engine.ExecutionContext.GetGeneratorKind(); + if (generatorKind == GeneratorKind.Async) + { + // TODO return ? AsyncGeneratorYield(undefined); + ExceptionHelper.ThrowNotImplementedException(); + } + + var value = expression.Argument is not null + ? Build(_engine, expression.Argument).GetValue() + : JsValue.Undefined; + + return GeneratorYield(new IteratorResult(_engine, value, JsBoolean.False)); + } + + + /// + /// https://tc39.es/ecma262/#sec-generatoryield + /// + private object GeneratorYield(JsValue iterNextObj) + { + var genContext = _engine.ExecutionContext; + var generator = genContext.Generator; + generator._generatorState = GeneratorState.SuspendedYield; + _engine.LeaveExecutionContext(); + + /* + Set the code evaluation state of genContext such that when evaluation is resumed with a Completion resumptionValue the following steps will be performed: + Return resumptionValue. + NOTE: This returns to the evaluation of the YieldExpression that originally called this abstract operation. + */ + + return iterNextObj; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index ae848cd346..b9a1b87004 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -57,8 +57,7 @@ internal Completion Execute() return new Completion(CompletionType.Return, jsValue, null, Function.Body.Location); } - var blockStatement = (BlockStatement) Function.Body; - _bodyStatementList ??= new JintStatementList(_engine, blockStatement, blockStatement.Body); + _bodyStatementList ??= new JintStatementList(_engine, Function); return _bodyStatementList.Execute(); } diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index a739472c78..79f07d43e5 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -8,6 +8,8 @@ namespace Jint.Runtime.Interpreter { internal class JintStatementList { + private readonly bool _generator; + private class Pair { internal JintStatement Statement; @@ -20,8 +22,25 @@ private class Pair private Pair[] _jintStatements; private bool _initialized; + private int _index; + + public JintStatementList(Engine engine, IFunction function) + : this(engine, (BlockStatement) function.Body) + { + _generator = function.Generator; + } + + public JintStatementList(Engine engine, BlockStatement blockStatement) + : this(engine, blockStatement, blockStatement.Body) + { + } + + public JintStatementList(Engine engine, Program program) + : this(engine, null, program.Body) + { + } - public JintStatementList(Engine engine, Statement statement, NodeList statements) + public JintStatementList(Engine engine, Statement statement, in NodeList statements) { _engine = engine; _statement = statement; @@ -66,19 +85,25 @@ public Completion Execute() 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 (; (uint) i < temp.Length; i++) { + var pair = temp[ i]; s = pair.Statement; c = pair.Value ?? s.Execute(); if (c.Type != CompletionType.Normal) { + _index = i + 1; return new Completion( c.Type, c.Value ?? sl.Value, c.Identifier, c.Location); } + sl = c; lastValue = c.Value ?? lastValue; } @@ -105,6 +130,8 @@ public Completion Execute() }); c = new Completion(CompletionType.Throw, error, null, s.Location); } + + _index = 0; return new Completion(c.Type, lastValue ?? JsValue.Undefined, c.Identifier, c.Location); } diff --git a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs index 628c1a19ac..d296a6ab79 100644 --- a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs @@ -16,7 +16,7 @@ public JintBlockStatement(Engine engine, BlockStatement blockStatement) : base(e protected override void Initialize() { - _statementList = new JintStatementList(_engine, _statement, _statement.Body); + _statementList = new JintStatementList(_engine, _statement); _lexicalDeclarations = HoistingScope.GetLexicalDeclarations(_statement); } diff --git a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs index 228139c23a..fa7da53808 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs @@ -6,16 +6,20 @@ namespace Jint.Runtime.Interpreter.Statements internal sealed class JintExpressionStatement : JintStatement { private readonly JintExpression _expression; + private readonly CompletionType _completionType; public JintExpressionStatement(Engine engine, ExpressionStatement statement) : base(engine, statement) { _expression = JintExpression.Build(engine, statement.Expression); + _completionType = statement.Expression.Type == Nodes.YieldExpression + ? CompletionType.Return + : CompletionType.Normal; } protected override Completion ExecuteInternal() { var value = _expression.GetValue(); - return new Completion(CompletionType.Normal, value, null, Location); + return new Completion(_completionType, value, null, Location); } } } \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintScript.cs b/Jint/Runtime/Interpreter/Statements/JintScript.cs index 59234b8c17..0149011137 100644 --- a/Jint/Runtime/Interpreter/Statements/JintScript.cs +++ b/Jint/Runtime/Interpreter/Statements/JintScript.cs @@ -8,7 +8,7 @@ internal sealed class JintScript : JintStatement