diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json
index 9a84905da8..cf93a04109 100644
--- a/Jint.Tests.Test262/Test262Harness.settings.json
+++ b/Jint.Tests.Test262/Test262Harness.settings.json
@@ -8,7 +8,6 @@
"Array.fromAsync",
"async-iteration",
"Atomics",
- "generators",
"import-assertions",
"iterator-helpers",
"regexp-duplicate-named-groups",
diff --git a/Jint.Tests/Runtime/GeneratorTests.cs b/Jint.Tests/Runtime/GeneratorTests.cs
new file mode 100644
index 0000000000..fa6b996f35
--- /dev/null
+++ b/Jint.Tests/Runtime/GeneratorTests.cs
@@ -0,0 +1,256 @@
+namespace Jint.Tests.Runtime;
+
+public class GeneratorTests
+{
+ [Fact]
+ public void LoopYield()
+ {
+ const string Script = """
+ const foo = function*() {
+ yield 'a';
+ yield 'b';
+ yield 'c';
+ };
+
+ let str = '';
+ for (const val of foo()) {
+ str += val;
+ }
+ return str;
+ """;
+
+ var engine = new Engine();
+ Assert.Equal("abc", engine.Evaluate(Script));
+ }
+
+ [Fact]
+ public void ReturnDuringYield()
+ {
+ const string Script = """
+ const foo = function*() {
+ yield 'a';
+ return;
+ yield 'c';
+ };
+
+ let str = '';
+ for (const val of foo()) {
+ str += val;
+ }
+ return str;
+ """;
+
+ var engine = new Engine();
+ Assert.Equal("a", engine.Evaluate(Script));
+ }
+
+ [Fact]
+ public void LoneReturnInYield()
+ {
+ const string Script = """
+ const foo = function*() {
+ return;
+ };
+
+ let str = '';
+ for (const val of foo()) {
+ str += val;
+ }
+ return str;
+ """;
+
+ var engine = new Engine();
+ Assert.Equal("", engine.Evaluate(Script));
+ }
+
+ [Fact]
+ public void LoneReturnValueInYield()
+ {
+ const string Script = """
+ const foo = function*() {
+ return 'a';
+ };
+
+ let str = '';
+ for (const val of foo()) {
+ str += val;
+ }
+ return str;
+ """;
+
+ var engine = new Engine();
+ Assert.Equal("", engine.Evaluate(Script));
+ }
+
+ [Fact]
+ public void YieldUndefined()
+ {
+ const string Script = """
+ const foo = function*() {
+ yield undefined;
+ };
+
+ let str = '';
+ for (const val of foo()) {
+ str += val;
+ }
+ return str;
+ """;
+
+ var engine = new Engine();
+ Assert.Equal("undefined", engine.Evaluate(Script));
+ }
+
+ [Fact]
+ public void ReturnUndefined()
+ {
+ const string Script = """
+ const foo = function*() {
+ return undefined;
+ };
+
+ let str = '';
+ for (const val of foo()) {
+ str += val;
+ }
+ return str;
+ """;
+
+ var engine = new Engine();
+ Assert.Equal("", engine.Evaluate(Script));
+ }
+
+ [Fact]
+ public void Basic()
+ {
+ var engine = new Engine();
+ engine.Execute("function * generator() { yield 5; yield 6; };");
+ engine.Execute("var iterator = generator(); var item = iterator.next();");
+ Assert.Equal(5, engine.Evaluate("item.value"));
+ Assert.False(engine.Evaluate("item.done").AsBoolean());
+ engine.Execute("item = iterator.next();");
+ Assert.Equal(6, engine.Evaluate("item.value"));
+ Assert.False(engine.Evaluate("item.done").AsBoolean());
+ engine.Execute("item = iterator.next();");
+ Assert.True(engine.Evaluate("item.value === void undefined").AsBoolean());
+ Assert.True(engine.Evaluate("item.done").AsBoolean());
+ }
+
+ [Fact]
+ public void FunctionExpressions()
+ {
+ var engine = new Engine();
+ engine.Execute("var generator = function * () { yield 5; yield 6; };");
+ engine.Execute("var iterator = generator(); var item = iterator.next();");
+ Assert.Equal(5, engine.Evaluate("item.value"));
+ Assert.False(engine.Evaluate("item.done").AsBoolean());
+ engine.Execute("item = iterator.next();");
+ Assert.Equal(6, engine.Evaluate("item.value"));
+ Assert.False(engine.Evaluate("item.done").AsBoolean());
+ engine.Execute("item = iterator.next();");
+ Assert.True(engine.Evaluate("item.value === void undefined").AsBoolean());
+ Assert.True(engine.Evaluate("item.done").AsBoolean());
+ }
+
+ [Fact]
+ public void CorrectThisBinding()
+ {
+ var engine = new Engine();
+ engine.Execute("var generator = function * () { yield 5; yield 6; };");
+ engine.Execute("var iterator = { g: generator, x: 5, y: 6 }.g(); var item = iterator.next();");
+ Assert.Equal(5, engine.Evaluate("item.value"));
+ Assert.False(engine.Evaluate("item.done").AsBoolean());
+ engine.Execute("item = iterator.next();");
+ Assert.Equal(6, engine.Evaluate("item.value"));
+ Assert.False(engine.Evaluate("item.done").AsBoolean());
+ engine.Execute("item = iterator.next();");
+ Assert.True(engine.Evaluate("item.value === void undefined").AsBoolean());
+ Assert.True(engine.Evaluate("item.done").AsBoolean());
+ }
+
+ [Fact(Skip = "TODO es6-generators")]
+ public void Sending()
+ {
+ const string Script = """
+ var sent;
+ function * generator() {
+ sent = [yield 5, yield 6];
+ };
+ var iterator = generator();
+ iterator.next();
+ iterator.next("foo");
+ iterator.next("bar");
+ """;
+
+ var engine = new Engine();
+ engine.Execute(Script);
+
+ Assert.Equal("foo", engine.Evaluate("sent[0]"));
+ Assert.Equal("bar", engine.Evaluate("sent[1]"));
+ }
+
+ [Fact(Skip = "TODO es6-generators")]
+ public void Sending2()
+ {
+ const string Script = """
+ function* counter(value) {
+ while (true) {
+ const step = yield value++;
+
+ if (step) {
+ value += step;
+ }
+ }
+ }
+
+ const generatorFunc = counter(0);
+ """;
+
+ var engine = new Engine();
+ engine.Execute(Script);
+
+ Assert.Equal(0, engine.Evaluate("generatorFunc.next().value")); // 0
+ Assert.Equal(1, engine.Evaluate("generatorFunc.next().value")); // 1
+ Assert.Equal(2, engine.Evaluate("generatorFunc.next().value")); // 2
+ Assert.Equal(3, engine.Evaluate("generatorFunc.next().value")); // 3
+ Assert.Equal(14, engine.Evaluate("generatorFunc.next(10).value")); // 14
+ Assert.Equal(15, engine.Evaluate("generatorFunc.next().value")); // 15
+ Assert.Equal(26, engine.Evaluate("generatorFunc.next(10).value")); // 26
+ }
+
+ [Fact(Skip = "TODO es6-generators")]
+ public void Fibonacci()
+ {
+ const string Script = """
+ function* fibonacci() {
+ let current = 0;
+ let next = 1;
+ while (true) {
+ const reset = yield current;
+ [current, next] = [next, next + current];
+ if (reset) {
+ current = 0;
+ next = 1;
+ }
+ }
+ }
+
+ const sequence = fibonacci();
+ """;
+
+ var engine = new Engine();
+ engine.Execute(Script);
+
+ Assert.Equal(0, engine.Evaluate("sequence.next().value"));
+ Assert.Equal(1, engine.Evaluate("sequence.next().value"));
+ Assert.Equal(1, engine.Evaluate("sequence.next().value"));
+ Assert.Equal(2, engine.Evaluate("sequence.next().value"));
+ Assert.Equal(3, engine.Evaluate("sequence.next().value"));
+ Assert.Equal(5, engine.Evaluate("sequence.next().value"));
+ Assert.Equal(9, engine.Evaluate("sequence.next().value"));
+ Assert.Equal(0, engine.Evaluate("sequence.next(true).value"));
+ Assert.Equal(1, engine.Evaluate("sequence.next().value)"));
+ Assert.Equal(1, engine.Evaluate("sequence.next().value)"));
+ Assert.Equal(2, engine.Evaluate("sequence.next().value)"));
+ }
+}
diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs
index 2329635f1d..cbd8978df7 100644
--- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs
+++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs
@@ -132,6 +132,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/JintYieldExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs
new file mode 100644
index 0000000000..ca977c864f
--- /dev/null
+++ b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs
@@ -0,0 +1,234 @@
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Generator;
+using Jint.Native.Iterator;
+using Jint.Native.Object;
+
+namespace Jint.Runtime.Interpreter.Expressions;
+
+internal sealed class JintYieldExpression : JintExpression
+{
+ public JintYieldExpression(YieldExpression expression) : base(expression)
+ {
+ }
+
+ protected override object EvaluateInternal(EvaluationContext context)
+ {
+ var expression = (YieldExpression) _expression;
+
+ JsValue value;
+ if (context.Engine.ExecutionContext.Generator?._nextValue is not null)
+ {
+ value = context.Engine.ExecutionContext.Generator._nextValue;
+ }
+ else if (expression.Argument is not null)
+ {
+ value = Build(expression.Argument).GetValue(context);
+ }
+ else
+ {
+ value = JsValue.Undefined;
+ }
+
+ if (expression.Delegate)
+ {
+ value = YieldDelegate(context, value);
+ }
+
+ return Yield(context, value);
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation
+ ///
+ private JsValue YieldDelegate(EvaluationContext context, JsValue value)
+ {
+ var engine = context.Engine;
+ var generatorKind = engine.ExecutionContext.GetGeneratorKind();
+ var iterator = value.GetIterator(engine.Realm, generatorKind);
+ var iteratorRecord = iterator;
+ var received = new Completion(CompletionType.Normal, JsValue.Undefined, _expression);
+ while (true)
+ {
+ if (received.Type == CompletionType.Normal)
+ {
+ iterator.TryIteratorStep(out var innerResult);
+ if (generatorKind == GeneratorKind.Async)
+ {
+ innerResult = Await(innerResult);
+ }
+
+ if (innerResult is not IteratorResult oi)
+ {
+ ExceptionHelper.ThrowTypeError(engine.Realm);
+ }
+
+ var done = IteratorComplete(innerResult);
+ if (done)
+ {
+ return IteratorValue(innerResult);
+ }
+
+ if (generatorKind == GeneratorKind.Async)
+ {
+ received = AsyncGeneratorYield(IteratorValue(innerResult));
+ }
+ else
+ {
+ received = GeneratorYield(innerResult);
+ }
+
+ }
+ else if (received.Type == CompletionType.Throw)
+ {
+ var throwMethod = iterator.GetMethod("throw");
+ if (throwMethod is not null)
+ {
+ var innerResult = throwMethod.Call(iterator, new[]{ received.Value });
+ if (generatorKind == GeneratorKind.Async)
+ {
+ innerResult = Await(innerResult);
+ }
+ // NOTE: Exceptions from the inner iterator throw method are propagated.
+ // Normal completions from an inner throw method are processed similarly to an inner next.
+ if (innerResult is not ObjectInstance oi)
+ {
+ ExceptionHelper.ThrowTypeError(engine.Realm);
+ }
+
+ var done = IteratorComplete(innerResult);
+ if (done)
+ {
+ IteratorValue(innerResult);
+ }
+
+ if (generatorKind == GeneratorKind.Async)
+ {
+ received = AsyncGeneratorYield(IteratorValue(innerResult));
+ }
+ else
+ {
+ received = GeneratorYield(innerResult);
+ }
+ }
+ else
+ {
+ // NOTE: If iterator does not have a throw method, this throw is going to terminate the yield* loop.
+ // But first we need to give iterator a chance to clean up.
+ var closeCompletion = new Completion(CompletionType.Normal, null!, _expression);
+ if (generatorKind == GeneratorKind.Async)
+ {
+ AsyncIteratorClose(iteratorRecord, CompletionType.Normal);
+ }
+ else
+ {
+ iteratorRecord.Close(CompletionType.Normal);
+ }
+
+ ExceptionHelper.ThrowTypeError(engine.Realm, "Iterator does not have close method");
+ }
+ }
+ else
+ {
+ var returnMethod = iterator.GetMethod("return");
+ if (returnMethod is null)
+ {
+ var temp = received.Value;
+ if (generatorKind == GeneratorKind.Async)
+ {
+ temp = Await(received.Value);
+ }
+
+ return temp;
+ }
+
+ var innerReturnResult = returnMethod.Call(iterator, new[] { received.Value });
+ if (generatorKind == GeneratorKind.Async)
+ {
+ innerReturnResult = Await(innerReturnResult);
+ }
+
+ if (innerReturnResult is not ObjectInstance oi)
+ {
+ ExceptionHelper.ThrowTypeError(engine.Realm);
+ }
+
+ var done = IteratorComplete(innerReturnResult);
+ if (done)
+ {
+ var val = IteratorValue(innerReturnResult);
+ return val;
+ }
+
+ if (generatorKind == GeneratorKind.Async)
+ {
+ received = AsyncGeneratorYield(IteratorValue(innerReturnResult));
+ }
+ else
+ {
+ received = GeneratorYield(innerReturnResult);
+ }
+ }
+ }
+ }
+
+ private Completion GeneratorYield(JsValue innerResult)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ private static bool IteratorComplete(JsValue iterResult)
+ {
+ return TypeConverter.ToBoolean(iterResult.Get(CommonProperties.Done));
+ }
+
+ private static JsValue IteratorValue(JsValue iterResult)
+ {
+ return iterResult.Get(CommonProperties.Value);
+ }
+
+ private static void AsyncIteratorClose(object iteratorRecord, CompletionType closeCompletion)
+ {
+ ExceptionHelper.ThrowNotImplementedException("async");
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-asyncgeneratoryield
+ ///
+ private static Completion AsyncGeneratorYield(object iteratorValue)
+ {
+ ExceptionHelper.ThrowNotImplementedException("async");
+ return default;
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#await
+ ///
+ private static ObjectInstance Await(JsValue innerResult)
+ {
+ ExceptionHelper.ThrowNotImplementedException("await");
+ return null;
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-yield
+ ///
+ private static JsValue Yield(EvaluationContext context, JsValue iterNextObj)
+ {
+ var engine = context.Engine;
+ var generatorKind = engine.ExecutionContext.GetGeneratorKind();
+ if (generatorKind == GeneratorKind.Async)
+ {
+ // TODO return ? AsyncGeneratorYield(undefined);
+ ExceptionHelper.ThrowNotImplementedException("async not implemented");
+ }
+
+ // https://tc39.es/ecma262/#sec-generatoryield
+ var genContext = engine.ExecutionContext;
+ var generator = genContext.Generator;
+ generator!._generatorState = GeneratorState.SuspendedYield;
+ //_engine.LeaveExecutionContext();
+
+ return iterNextObj;
+ }
+}