Skip to content

Commit

Permalink
ES6 generators
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Dec 31, 2023
1 parent 84cd41a commit fedcf93
Show file tree
Hide file tree
Showing 16 changed files with 676 additions and 49 deletions.
1 change: 0 additions & 1 deletion Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"arraybuffer-transfer",
"async-iteration",
"Atomics",
"generators",
"import-assertions",
"import-attributes",
"iterator-helpers",
Expand Down
32 changes: 32 additions & 0 deletions Jint.Tests/Runtime/AwaitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,36 @@ public void ShouldTaskAwaitCurrentStack()
engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();");
Assert.Equal("12", log);
}

[Fact(Skip = "TODO https://github.com/sebastienros/jint/issues/1385")]
public void ShouldHaveCorrectOrder()
{
var engine = new Engine();
engine.Evaluate("var log = [];");

const string Script = """
async function foo(name) {
log.push(name + " start");
await log.push(name + " middle");
log.push(name + " end");
}

foo("First");
foo("Second");
""";

engine.Execute(Script);

var log = engine.GetValue("log").AsArray();
string[] expected = [
"First start",
"First middle",
"Second start",
"Second middle",
"First end",
"Second end",
];

Assert.Equal(expected, log.Select(x => x.AsString()).ToArray());
}
}
256 changes: 256 additions & 0 deletions Jint.Tests/Runtime/GeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -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]
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]"));

Check failure on line 188 in Jint.Tests/Runtime/GeneratorTests.cs

View workflow job for this annotation

GitHub Actions / macos

Jint.Tests.Runtime.GeneratorTests.Sending

Assert.Equal() Failure: Values differ Expected: foo Actual: 5

Check failure on line 188 in Jint.Tests/Runtime/GeneratorTests.cs

View workflow job for this annotation

GitHub Actions / linux

Jint.Tests.Runtime.GeneratorTests.Sending

Assert.Equal() Failure: Values differ Expected: foo Actual: 5
Assert.Equal("bar", engine.Evaluate("sent[1]"));
}

[Fact(Skip = "WIP")]
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 = "WIP")]
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)"));
}
}
20 changes: 18 additions & 2 deletions Jint/Native/Function/FunctionConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,24 @@ private ScriptFunctionInstance 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;
}
}
}
6 changes: 5 additions & 1 deletion Jint/Native/Function/FunctionInstance.Dynamic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -161,7 +164,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)
{
Expand Down
1 change: 1 addition & 0 deletions Jint/Native/Function/FunctionInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ internal ExecutionContext PrepareForOrdinaryCall(JsValue newTarget)
variableEnvironment: localEnv,
_privateEnvironment,
calleeRealm,
generator: null,
function: this);

// If callerContext is not already suspended, suspend callerContext.
Expand Down
15 changes: 7 additions & 8 deletions Jint/Native/Generator/GeneratorKind.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
namespace Jint.Native.Generator
namespace Jint.Native.Generator;

internal enum GeneratorKind
{
internal enum GeneratorKind
{
NonGenerator,
Sync,
Async
}
}
NonGenerator,
Sync,
Async
}
2 changes: 2 additions & 0 deletions Jint/Native/Global/GlobalObject.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public partial class GlobalObject
private static readonly Key propertyFloat32Array = "Float32Array";
private static readonly Key propertyFloat64Array = "Float64Array";
private static readonly Key propertyFunction = "Function";
private static readonly Key propertyGeneratorFunction = "Generator";
private static readonly Key propertyInt16Array = "Int16Array";
private static readonly Key propertyInt32Array = "Int32Array";
private static readonly Key propertyInt8Array = "Int8Array";
Expand Down Expand Up @@ -97,6 +98,7 @@ protected override void Initialize()
properties.AddDangerous(propertyFloat32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, PropertyFlags));
properties.AddDangerous(propertyFloat64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, PropertyFlags));
properties.AddDangerous(propertyFunction, new PropertyDescriptor(_realm.Intrinsics.Function, PropertyFlags));
properties.AddDangerous(propertyGeneratorFunction, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.GeneratorFunction, PropertyFlags));
properties.AddDangerous(propertyInt16Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, PropertyFlags));
properties.AddDangerous(propertyInt32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, PropertyFlags));
properties.AddDangerous(propertyInt8Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int8Array, PropertyFlags));
Expand Down
Loading

0 comments on commit fedcf93

Please sign in to comment.