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) { if ((context.Engine.Options.ExperimentalFeatures & ExperimentalFeature.Generators) == ExperimentalFeature.None) { ExceptionHelper.ThrowJavaScriptException( context.Engine.Intrinsics.Error, "Yield expressions are not supported in the engine, you can enable the experimental feature 'Generators' in engine options to use them."); } 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; } }