Skip to content

Commit b88a30e

Browse files
committed
ES6 generators
1 parent 5ee35f1 commit b88a30e

22 files changed

+801
-89
lines changed

Jint.Tests.Test262/Test262Harness.settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"async-iteration",
1111
"Atomics",
1212
"decorators",
13-
"generators",
1413
"import-assertions",
1514
"iterator-helpers",
1615
"regexp-duplicate-named-groups",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace Jint.Tests.Runtime;
2+
3+
public class GeneratorTests
4+
{
5+
[Fact]
6+
public void BasicYield()
7+
{
8+
const string Script = @"
9+
const foo = function*() {
10+
yield 'a';
11+
yield 'b';
12+
yield 'c';
13+
};
14+
15+
let str = '';
16+
for (const val of foo()) {
17+
str += val;
18+
}";
19+
20+
var engine = new Engine();
21+
Assert.Equal("abc", engine.Evaluate(Script));
22+
}
23+
}

Jint/Engine.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Jint.Native;
66
using Jint.Native.Argument;
77
using Jint.Native.Function;
8+
using Jint.Native.Generator;
89
using Jint.Native.Object;
910
using Jint.Native.Promise;
1011
using Jint.Native.Symbol;
@@ -1391,6 +1392,12 @@ internal void UpdatePrivateEnvironment(PrivateEnvironmentRecord? newEnv)
13911392
_executionContexts.ReplaceTopPrivateEnvironment(newEnv);
13921393
}
13931394

1395+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1396+
internal ref readonly ExecutionContext UpdateGenerator(GeneratorInstance generator)
1397+
{
1398+
return ref _executionContexts.ReplaceTopGenerator(generator);
1399+
}
1400+
13941401
/// <summary>
13951402
/// Invokes the named callable and returns the resulting object.
13961403
/// </summary>
@@ -1562,6 +1569,11 @@ internal void SignalError(ErrorDispatchInfo error)
15621569
_error = error;
15631570
}
15641571

1572+
internal ref readonly ExecutionContext GetExecutionContext(int fromTop)
1573+
{
1574+
return ref _executionContexts.Peek(fromTop);
1575+
}
1576+
15651577
public void Dispose()
15661578
{
15671579
if (_objectWrapperCache is null)

Jint/Native/Function/FunctionConstructor.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,24 @@ private FunctionInstance InstantiateGeneratorFunctionObject(
113113
EnvironmentRecord scope,
114114
PrivateEnvironmentRecord? privateScope)
115115
{
116-
// TODO generators
117-
return InstantiateOrdinaryFunctionObject(functionDeclaration, scope, privateScope);
116+
var thisMode = functionDeclaration.Strict || _engine._isStrict
117+
? FunctionThisMode.Strict
118+
: FunctionThisMode.Global;
119+
120+
var name = functionDeclaration.Function.Id?.Name ?? "default";
121+
var F = OrdinaryFunctionCreate(
122+
_realm.Intrinsics.GeneratorFunction.PrototypeObject,
123+
functionDeclaration,
124+
thisMode,
125+
scope,
126+
privateScope);
127+
128+
F.SetFunctionName(name);
129+
130+
var prototype = OrdinaryObjectCreate(_engine, _realm.Intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject);
131+
F.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
132+
133+
return F;
118134
}
119135
}
120136
}

Jint/Native/Function/FunctionInstance.Dynamic.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Esprima.Ast;
33
using Jint.Native.Object;
44
using Jint.Runtime;
5+
using Jint.Runtime.Descriptors;
56
using Jint.Runtime.Environments;
67
using Jint.Runtime.Interpreter;
78

@@ -42,6 +43,8 @@ internal FunctionInstance CreateDynamicFunction(
4243
fallbackProto = static intrinsics => intrinsics.AsyncFunction.PrototypeObject;
4344
break;
4445
case FunctionKind.Generator:
46+
fallbackProto = static intrinsics => intrinsics.GeneratorFunction.PrototypeObject;
47+
break;
4548
case FunctionKind.AsyncGenerator:
4649
default:
4750
ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
@@ -161,7 +164,8 @@ internal FunctionInstance CreateDynamicFunction(
161164

162165
if (kind == FunctionKind.Generator)
163166
{
164-
ExceptionHelper.ThrowNotImplementedException("generators not implemented");
167+
var prototype = OrdinaryObjectCreate(_engine, _realm.Intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject);
168+
F.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
165169
}
166170
else if (kind == FunctionKind.AsyncGenerator)
167171
{

Jint/Native/Function/FunctionInstance.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ internal ExecutionContext PrepareForOrdinaryCall(JsValue newTarget)
330330
variableEnvironment: localEnv,
331331
_privateEnvironment,
332332
calleeRealm,
333+
generator: null,
333334
function: this);
334335

335336
// If callerContext is not already suspended, suspend callerContext.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Jint.Native.Function;
2+
using Jint.Native.Iterator;
3+
using Jint.Native.Object;
4+
using Jint.Runtime;
5+
using Jint.Runtime.Descriptors;
6+
7+
namespace Jint.Native.Generator;
8+
9+
/// <summary>
10+
/// https://tc39.es/ecma262/#sec-generatorfunction-constructor
11+
/// </summary>
12+
internal sealed class GeneratorFunctionConstructor : FunctionInstance, IConstructor
13+
{
14+
private static readonly JsString _functionName = new("GeneratorFunction");
15+
16+
internal GeneratorFunctionConstructor(
17+
Engine engine,
18+
Realm realm,
19+
FunctionPrototype prototype,
20+
IteratorPrototype iteratorPrototype)
21+
: base(engine, realm, _functionName)
22+
{
23+
PrototypeObject = new GeneratorFunctionPrototype(engine, this, prototype, iteratorPrototype);
24+
_prototype = PrototypeObject;
25+
_prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
26+
_length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable);
27+
}
28+
29+
public GeneratorFunctionPrototype PrototypeObject { get; }
30+
31+
protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
32+
{
33+
return Construct(arguments, thisObject);
34+
}
35+
36+
public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
37+
{
38+
var function = _realm.Intrinsics.Function.CreateDynamicFunction(
39+
this,
40+
newTarget,
41+
FunctionKind.Generator,
42+
arguments);
43+
44+
return function;
45+
}
46+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Jint.Collections;
2+
using Jint.Native.Function;
3+
using Jint.Native.Iterator;
4+
using Jint.Native.Object;
5+
using Jint.Native.Symbol;
6+
using Jint.Runtime.Descriptors;
7+
8+
namespace Jint.Native.Generator;
9+
10+
/// <summary>
11+
/// https://tc39.es/ecma262/#sec-properties-of-the-generatorfunction-prototype-object
12+
/// </summary>
13+
internal sealed class GeneratorFunctionPrototype : ObjectInstance
14+
{
15+
private readonly GeneratorFunctionConstructor? _constructor;
16+
17+
internal GeneratorFunctionPrototype(
18+
Engine engine,
19+
GeneratorFunctionConstructor constructor,
20+
FunctionPrototype prototype,
21+
IteratorPrototype iteratorPrototype) : base(engine)
22+
{
23+
_constructor = constructor;
24+
_prototype = prototype;
25+
PrototypeObject = new GeneratorPrototype(engine, this, iteratorPrototype);
26+
}
27+
28+
public GeneratorPrototype PrototypeObject { get; }
29+
30+
protected override void Initialize()
31+
{
32+
var properties = new PropertyDictionary(2, checkExistingKeys: false)
33+
{
34+
["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.Configurable),
35+
["prototype"] = new PropertyDescriptor(PrototypeObject, PropertyFlag.Configurable)
36+
};
37+
SetProperties(properties);
38+
var symbols = new SymbolDictionary(1)
39+
{
40+
[GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("GeneratorFunction", PropertyFlag.Configurable)
41+
};
42+
SetSymbols(symbols);
43+
}
44+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using Jint.Native.Iterator;
2+
using Jint.Native.Object;
3+
using Jint.Runtime;
4+
using Jint.Runtime.Environments;
5+
using Jint.Runtime.Interpreter;
6+
7+
namespace Jint.Native.Generator;
8+
9+
/// <summary>
10+
/// https://tc39.es/ecma262/#sec-properties-of-generator-instances
11+
/// </summary>
12+
internal sealed class GeneratorInstance : ObjectInstance
13+
{
14+
internal GeneratorState _generatorState;
15+
private ExecutionContext _generatorContext;
16+
private readonly JsValue? _generatorBrand = null;
17+
private JintStatementList _generatorBody = null!;
18+
19+
public GeneratorInstance(Engine engine) : base(engine)
20+
{
21+
}
22+
23+
/// <summary>
24+
/// https://tc39.es/ecma262/#sec-generatorstart
25+
/// </summary>
26+
public JsValue GeneratorStart(JintStatementList generatorBody)
27+
{
28+
var genContext = _engine.UpdateGenerator(this);
29+
_generatorBody = generatorBody;
30+
31+
_generatorContext = genContext;
32+
_generatorState = GeneratorState.SuspendedStart;
33+
34+
return Undefined;
35+
}
36+
37+
/// <summary>
38+
/// https://tc39.es/ecma262/#sec-generatorresume
39+
/// </summary>
40+
public JsValue GeneratorResume(JsValue value, JsValue? generatorBrand)
41+
{
42+
var state = GeneratorValidate(generatorBrand);
43+
if (state == GeneratorState.Completed)
44+
{
45+
return new IteratorResult(_engine, value, JsBoolean.True);
46+
}
47+
48+
var genContext = _generatorContext;
49+
var methodContext = _engine.ExecutionContext;
50+
51+
// 6. Suspend methodContext.
52+
53+
var context = _engine._activeEvaluationContext;
54+
return ResumeExecution(genContext, context!);
55+
}
56+
57+
/// <summary>
58+
/// https://tc39.es/ecma262/#sec-generatorresumeabrupt
59+
/// </summary>
60+
public JsValue GeneratorResumeAbrupt(in Completion abruptCompletion, JsValue? generatorBrand)
61+
{
62+
var state = GeneratorValidate(generatorBrand);
63+
if (state == GeneratorState.SuspendedStart)
64+
{
65+
_generatorState = GeneratorState.Completed;
66+
state = GeneratorState.Completed;
67+
}
68+
69+
if (state == GeneratorState.Completed)
70+
{
71+
if (abruptCompletion.Type == CompletionType.Return)
72+
{
73+
return new IteratorResult(_engine, abruptCompletion.Value, JsBoolean.True);
74+
}
75+
76+
return abruptCompletion.Value;
77+
}
78+
79+
var genContext = _generatorContext;
80+
var methodContext = _engine.ExecutionContext;
81+
82+
// Suspend methodContext.
83+
84+
return ResumeExecution(genContext, new EvaluationContext(_engine, abruptCompletion));
85+
}
86+
87+
private JsValue ResumeExecution(ExecutionContext genContext, EvaluationContext context)
88+
{
89+
_generatorState = GeneratorState.Executing;
90+
_engine.EnterExecutionContext(genContext);
91+
92+
var result = _generatorBody.Execute(context);
93+
_engine.LeaveExecutionContext();
94+
95+
ObjectInstance? resultValue = null;
96+
if (result.Type == CompletionType.Normal)
97+
{
98+
_generatorState = GeneratorState.Completed;
99+
var value = context.ResumedCompletion.GetValueOrDefault();
100+
resultValue = IteratorInstance.ValueIteratorPosition.Done(_engine, value);
101+
}
102+
else if (result.Type == CompletionType.Return)
103+
{
104+
resultValue = new IteratorInstance.ValueIteratorPosition(_engine, result.Value, false);
105+
if (_generatorBody.Completed)
106+
{
107+
_generatorState = GeneratorState.Completed;
108+
}
109+
}
110+
111+
if (result.Type == CompletionType.Throw)
112+
{
113+
_generatorState = GeneratorState.Completed;
114+
ExceptionHelper.ThrowJavaScriptException(_engine, result.Value, result);
115+
}
116+
117+
return resultValue!;
118+
}
119+
120+
private GeneratorState GeneratorValidate(JsValue? generatorBrand)
121+
{
122+
if (!ReferenceEquals(generatorBrand, _generatorBrand))
123+
{
124+
ExceptionHelper.ThrowTypeError(_engine.Realm, "Generator brand differs from attached brand");
125+
}
126+
127+
if (_generatorState == GeneratorState.Executing)
128+
{
129+
ExceptionHelper.ThrowTypeError(_engine.Realm, "Generator state was unexpectedly executing");
130+
}
131+
132+
return _generatorState;
133+
}
134+
}
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
namespace Jint.Native.Generator
1+
namespace Jint.Native.Generator;
2+
3+
internal enum GeneratorKind
24
{
3-
internal enum GeneratorKind
4-
{
5-
NonGenerator,
6-
Sync,
7-
Async
8-
}
9-
}
5+
NonGenerator,
6+
Sync,
7+
Async
8+
}

0 commit comments

Comments
 (0)