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;
}
}