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);