From 8cc14874a9fc1077b98bcc3d16f48499c02d8a5b Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 2 Nov 2022 08:23:15 +0200 Subject: [PATCH] wip-check --- Jint.Tests/Runtime/GeneratorTests.cs | 180 +++++++++++++++++- Jint/Native/Generator/GeneratorInstance.cs | 21 +- Jint/Native/Generator/GeneratorPrototype.cs | 12 +- Jint/Native/Global/GlobalObject.Properties.cs | 2 + Jint/Runtime/Interpreter/EvaluationContext.cs | 5 +- .../Expressions/JintYieldExpression.cs | 13 +- Jint/Runtime/Interpreter/JintStatementList.cs | 11 +- .../Statements/JintBlockStatement.cs | 2 +- .../Interpreter/Statements/JintStatement.cs | 8 - .../Statements/JintTryStatement.cs | 7 +- 10 files changed, 221 insertions(+), 40 deletions(-) diff --git a/Jint.Tests/Runtime/GeneratorTests.cs b/Jint.Tests/Runtime/GeneratorTests.cs index 39260c4ff6..5183ddf875 100644 --- a/Jint.Tests/Runtime/GeneratorTests.cs +++ b/Jint.Tests/Runtime/GeneratorTests.cs @@ -3,7 +3,7 @@ namespace Jint.Tests.Runtime; public class GeneratorTests { [Fact] - public void BasicYield() + public void LoopYield() { const string Script = @" const foo = function*() { @@ -15,9 +15,185 @@ public void BasicYield() 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() + { + const string Script = @" + function * generator(){ + yield 5; yield 6; + }; + var iterator = generator(); + var item = iterator.next(); + var passed = item.value === 5 && item.done === false; + item = iterator.next(); + passed &= item.value === 6 && item.done === false; + item = iterator.next(); + passed &= item.value === void undefined && item.done === true; + return passed;"; + + var engine = new Engine(); + Assert.True(engine.Evaluate(Script).AsBoolean()); + } + + [Fact] + public void FunctionExpressions() + { + const string Script = @" + var generator = function * (){ + yield 5; yield 6; + }; + var iterator = generator(); + var item = iterator.next(); + var passed = item.value === 5 && item.done === false; + item = iterator.next(); + passed &= item.value === 6 && item.done === false; + item = iterator.next(); + passed &= item.value === void undefined && item.done === true; + return passed;"; + + var engine = new Engine(); + Assert.True(engine.Evaluate(Script).AsBoolean()); + } + + [Fact] + public void CorrectThisBinding() + { + const string Script = @" + function * generator(){ + yield this.x; yield this.y; + }; + var iterator = { g: generator, x: 5, y: 6 }.g(); + var item = iterator.next(); + var passed = item.value === 5 && item.done === false; + item = iterator.next(); + passed &= item.value === 6 && item.done === false; + item = iterator.next(); + passed &= item.value === void undefined && item.done === true; + return passed;"; + + var engine = new Engine(); + Assert.True(engine.Evaluate(Script).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); + + var sent0 = engine.Evaluate("sent[0]"); + var sent1 = engine.Evaluate("sent[1]"); + + Assert.Equal("foo", sent0); + Assert.Equal("bar", sent1); + } } diff --git a/Jint/Native/Generator/GeneratorInstance.cs b/Jint/Native/Generator/GeneratorInstance.cs index b68736ce33..978416b6f5 100644 --- a/Jint/Native/Generator/GeneratorInstance.cs +++ b/Jint/Native/Generator/GeneratorInstance.cs @@ -15,6 +15,7 @@ internal sealed class GeneratorInstance : ObjectInstance private ExecutionContext _generatorContext; private readonly JsValue? _generatorBrand = null; private JintStatementList _generatorBody = null!; + public JsValue? _nextValue; public GeneratorInstance(Engine engine) : base(engine) { @@ -50,6 +51,8 @@ public JsValue GeneratorResume(JsValue value, JsValue? generatorBrand) // 6. Suspend methodContext. + _nextValue = value; + var context = _engine._activeEvaluationContext; return ResumeExecution(genContext, context!); } @@ -79,9 +82,10 @@ public JsValue GeneratorResumeAbrupt(in Completion abruptCompletion, JsValue? ge var genContext = _generatorContext; var methodContext = _engine.ExecutionContext; - // Suspend methodContext. + // Suspend methodContext + _nextValue = abruptCompletion.Value; - return ResumeExecution(genContext, new EvaluationContext(_engine, abruptCompletion)); + return ResumeExecution(genContext, new EvaluationContext(_engine)); } private JsValue ResumeExecution(ExecutionContext genContext, EvaluationContext context) @@ -92,19 +96,24 @@ private JsValue ResumeExecution(ExecutionContext genContext, EvaluationContext c var result = _generatorBody.Execute(context); _engine.LeaveExecutionContext(); + _nextValue = null; + ObjectInstance? resultValue = null; if (result.Type == CompletionType.Normal) { _generatorState = GeneratorState.Completed; - var value = context.ResumedCompletion.GetValueOrDefault(); - resultValue = IteratorInstance.ValueIteratorPosition.Done(_engine, value); + resultValue = IteratorResult.CreateValueIteratorPosition(_engine, result.Value, done: JsBoolean.True); } else if (result.Type == CompletionType.Return) { - resultValue = new IteratorInstance.ValueIteratorPosition(_engine, result.Value, false); - if (_generatorBody.Completed) + if (_generatorState == GeneratorState.SuspendedYield) + { + resultValue = IteratorResult.CreateValueIteratorPosition(_engine, result.Value, done: JsBoolean.False); + } + else { _generatorState = GeneratorState.Completed; + resultValue = IteratorResult.CreateValueIteratorPosition(_engine, null!, done: JsBoolean.True); } } diff --git a/Jint/Native/Generator/GeneratorPrototype.cs b/Jint/Native/Generator/GeneratorPrototype.cs index 8b17bd929f..cc284498b6 100644 --- a/Jint/Native/Generator/GeneratorPrototype.cs +++ b/Jint/Native/Generator/GeneratorPrototype.cs @@ -26,14 +26,14 @@ internal GeneratorPrototype( protected override void Initialize() { - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - const PropertyFlag lengthFlags = PropertyFlag.Configurable; + 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) + ["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); @@ -50,7 +50,7 @@ protected override void Initialize() private JsValue Next(JsValue thisObject, JsValue[] arguments) { var g = AssertGeneratorInstance(thisObject); - var value = arguments.At(0); + var value = arguments.At(0, null!); return g.GeneratorResume(value, null); } diff --git a/Jint/Native/Global/GlobalObject.Properties.cs b/Jint/Native/Global/GlobalObject.Properties.cs index a8ae037981..7f34a25843 100644 --- a/Jint/Native/Global/GlobalObject.Properties.cs +++ b/Jint/Native/Global/GlobalObject.Properties.cs @@ -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"; @@ -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)); diff --git a/Jint/Runtime/Interpreter/EvaluationContext.cs b/Jint/Runtime/Interpreter/EvaluationContext.cs index 5b1bff1eff..497f72f8a4 100644 --- a/Jint/Runtime/Interpreter/EvaluationContext.cs +++ b/Jint/Runtime/Interpreter/EvaluationContext.cs @@ -10,10 +10,9 @@ internal sealed class EvaluationContext { private readonly bool _shouldRunBeforeExecuteStatementChecks; - public EvaluationContext(Engine engine, in Completion? resumedCompletion = null) + public EvaluationContext(Engine engine) { Engine = engine; - ResumedCompletion = resumedCompletion ?? default; // TODO later OperatorOverloadingAllowed = engine.Options.Interop.AllowOperatorOverloading; _shouldRunBeforeExecuteStatementChecks = engine._constraints.Length > 0 || engine._isDebugMode; } @@ -22,13 +21,11 @@ public EvaluationContext(Engine engine, in Completion? resumedCompletion = null) public EvaluationContext() { Engine = null!; - ResumedCompletion = default; // TODO later OperatorOverloadingAllowed = false; _shouldRunBeforeExecuteStatementChecks = false; } public readonly Engine Engine; - public readonly Completion ResumedCompletion; public bool DebugMode => Engine._isDebugMode; public SyntaxElement LastSyntaxElement diff --git a/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs index da0e4d27a3..95efc05a7e 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintYieldExpression.cs @@ -15,13 +15,20 @@ 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) + 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) { diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index 06fd8bf9cb..bad34d6c5d 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -20,7 +20,7 @@ private sealed class Pair private Pair[]? _jintStatements; private bool _initialized; - private readonly uint _index; + private uint _index; private readonly bool _generator; public JintStatementList(IFunction function) @@ -102,10 +102,13 @@ public Completion Execute(EvaluationContext context) } } - if (_generator && context.Engine.ExecutionContext.Generator?._generatorState == GeneratorState.SuspendedYield) + if (_generator) { - _index = i + 1; - return new Completion(CompletionType.Return, c.Value, s._statement); + if (context.Engine.ExecutionContext.Generator?._generatorState == GeneratorState.SuspendedYield) + { + _index = i + 1; + return new Completion(CompletionType.Return, c.Value, s._statement); + } } if (c.Type != CompletionType.Normal) diff --git a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs index 62956a033b..0f9f6e1b63 100644 --- a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs @@ -18,7 +18,7 @@ protected override void Initialize(EvaluationContext context) _lexicalDeclarations = HoistingScope.GetLexicalDeclarations(_statement); } - internal override bool SupportsResume => true; + internal bool SupportsResume => true; /// /// Optimized for direct access without virtual dispatch. diff --git a/Jint/Runtime/Interpreter/Statements/JintStatement.cs b/Jint/Runtime/Interpreter/Statements/JintStatement.cs index 1f59a6640d..b5faf85124 100644 --- a/Jint/Runtime/Interpreter/Statements/JintStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintStatement.cs @@ -1,7 +1,6 @@ using System.Runtime.CompilerServices; using Esprima; using Esprima.Ast; -using Jint.Native; using Jint.Runtime.Interpreter.Expressions; namespace Jint.Runtime.Interpreter.Statements @@ -41,16 +40,9 @@ public Completion Execute(EvaluationContext context) _initialized = true; } - if (context.ResumedCompletion.IsAbrupt() && !SupportsResume) - { - return new Completion(CompletionType.Normal, JsValue.Undefined, _statement); - } - return ExecuteInternal(context); } - internal virtual bool SupportsResume => false; - protected abstract Completion ExecuteInternal(EvaluationContext context); public ref readonly Location Location => ref _statement.Location; diff --git a/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs b/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs index 0140703be1..1ac51cac1f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs @@ -27,7 +27,7 @@ protected override void Initialize(EvaluationContext context) } } - internal override bool SupportsResume => true; + internal bool SupportsResume => true; protected override Completion ExecuteInternal(EvaluationContext context) { @@ -42,11 +42,6 @@ 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) {