using System.Diagnostics.CodeAnalysis;
using Jint.Native;
using Jint.Native.Iterator;
using Jint.Runtime.Environments;
using Jint.Runtime.Interpreter.Expressions;
using Environment = Jint.Runtime.Environments.Environment;
namespace Jint.Runtime.Interpreter.Statements
{
///
/// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
///
internal sealed class JintForInForOfStatement : JintStatement
{
private readonly Node _leftNode;
private readonly Statement _forBody;
private readonly Expression _rightExpression;
private readonly IterationKind _iterationKind;
private ProbablyBlockStatement _body;
private JintExpression? _expr;
private DestructuringPattern? _assignmentPattern;
private JintExpression _right = null!;
private List? _tdzNames;
private bool _destructuring;
private LhsKind _lhsKind;
public JintForInForOfStatement(ForInStatement statement) : base(statement)
{
_leftNode = statement.Left;
_rightExpression = statement.Right;
_forBody = statement.Body;
_iterationKind = IterationKind.Enumerate;
}
public JintForInForOfStatement(ForOfStatement statement) : base(statement)
{
_leftNode = statement.Left;
_rightExpression = statement.Right;
_forBody = statement.Body;
_iterationKind = IterationKind.Iterate;
}
protected override void Initialize(EvaluationContext context)
{
_lhsKind = LhsKind.Assignment;
var engine = context.Engine;
if (_leftNode is VariableDeclaration variableDeclaration)
{
_lhsKind = variableDeclaration.Kind == VariableDeclarationKind.Var
? LhsKind.VarBinding
: LhsKind.LexicalBinding;
var variableDeclarationDeclaration = variableDeclaration.Declarations[0];
var id = variableDeclarationDeclaration.Id;
if (_lhsKind == LhsKind.LexicalBinding)
{
_tdzNames = new List(1);
id.GetBoundNames(_tdzNames);
}
if (id is DestructuringPattern pattern)
{
_destructuring = true;
_assignmentPattern = pattern;
}
else
{
var identifier = (Identifier) id;
_expr = new JintIdentifierExpression(identifier);
}
}
else if (_leftNode is DestructuringPattern pattern)
{
_destructuring = true;
_assignmentPattern = pattern;
}
else if (_leftNode is MemberExpression memberExpression)
{
_expr = new JintMemberExpression(memberExpression);
}
else
{
_expr = new JintIdentifierExpression((Identifier) _leftNode);
}
_body = new ProbablyBlockStatement(_forBody);
_right = JintExpression.Build(_rightExpression);
}
protected override Completion ExecuteInternal(EvaluationContext context)
{
if (!HeadEvaluation(context, out var keyResult))
{
return new Completion(CompletionType.Normal, JsValue.Undefined, _statement);
}
return BodyEvaluation(context, _expr, _body, keyResult, IterationKind.Enumerate, _lhsKind);
}
///
/// https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofheadevaluation-tdznames-expr-iterationkind
///
private bool HeadEvaluation(EvaluationContext context, [NotNullWhen(true)] out IteratorInstance? result)
{
var engine = context.Engine;
var oldEnv = engine.ExecutionContext.LexicalEnvironment;
var tdz = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv);
if (_tdzNames != null)
{
var TDZEnvRec = tdz;
foreach (var name in _tdzNames)
{
TDZEnvRec.CreateMutableBinding(name);
}
}
engine.UpdateLexicalEnvironment(tdz);
var exprValue = _right.GetValue(context);
engine.UpdateLexicalEnvironment(oldEnv);
if (_iterationKind == IterationKind.Enumerate)
{
if (exprValue.IsNullOrUndefined())
{
result = null;
return false;
}
var obj = TypeConverter.ToObject(engine.Realm, exprValue);
result = new IteratorInstance.EnumerableIterator(engine, obj.GetKeys());
}
else
{
result = exprValue as IteratorInstance ?? exprValue.GetIterator(engine.Realm);
}
return true;
}
///
/// https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset
///
private Completion BodyEvaluation(
EvaluationContext context,
JintExpression? lhs,
in ProbablyBlockStatement stmt,
IteratorInstance iteratorRecord,
IterationKind iterationKind,
LhsKind lhsKind,
IteratorKind iteratorKind = IteratorKind.Sync)
{
var engine = context.Engine;
var oldEnv = engine.ExecutionContext.LexicalEnvironment;
var v = JsValue.Undefined;
var destructuring = _destructuring;
string? lhsName = null;
var completionType = CompletionType.Normal;
var close = false;
try
{
while (true)
{
Environment? iterationEnv = null;
if (!iteratorRecord.TryIteratorStep(out var nextResult))
{
close = true;
return new Completion(CompletionType.Normal, v, _statement!);
}
if (iteratorKind == IteratorKind.Async)
{
// nextResult = await nextResult;
ExceptionHelper.ThrowNotImplementedException("await");
}
var nextValue = nextResult.Get(CommonProperties.Value);
close = true;
object lhsRef = null!;
if (lhsKind != LhsKind.LexicalBinding)
{
if (!destructuring)
{
lhsRef = lhs!.Evaluate(context);
}
}
else
{
iterationEnv = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv);
if (_tdzNames != null)
{
BindingInstantiation(iterationEnv);
}
engine.UpdateLexicalEnvironment(iterationEnv);
if (!destructuring)
{
var identifier = (Identifier) ((VariableDeclaration) _leftNode).Declarations[0].Id;
lhsName ??= identifier.Name;
lhsRef = engine.ResolveBinding(lhsName);
}
}
if (context.DebugMode)
{
context.Engine.Debugger.OnStep(_leftNode);
}
var status = CompletionType.Normal;
if (!destructuring)
{
if (context.IsAbrupt())
{
close = true;
status = context.Completion;
}
else
{
var reference = (Reference) lhsRef;
if (lhsKind == LhsKind.LexicalBinding || _leftNode.Type == NodeType.Identifier && !reference.IsUnresolvableReference)
{
reference.InitializeReferencedBinding(nextValue);
}
else
{
engine.PutValue(reference, nextValue);
}
}
}
else
{
nextValue = DestructuringPatternAssignmentExpression.ProcessPatterns(
context,
_assignmentPattern!,
nextValue,
iterationEnv,
checkPatternPropertyReference: _lhsKind != LhsKind.VarBinding);
status = context.Completion;
if (lhsKind == LhsKind.Assignment)
{
// DestructuringAssignmentEvaluation of assignmentPattern using nextValue as the argument.
}
#pragma warning disable MA0140
else if (lhsKind == LhsKind.VarBinding)
{
// BindingInitialization for lhs passing nextValue and undefined as the arguments.
}
else
{
// BindingInitialization for lhs passing nextValue and iterationEnv as arguments
}
#pragma warning restore MA0140
}
if (status != CompletionType.Normal)
{
engine.UpdateLexicalEnvironment(oldEnv);
if (_iterationKind == IterationKind.AsyncIterate)
{
iteratorRecord.Close(status);
return new Completion(status, nextValue, context.LastSyntaxElement);
}
if (iterationKind == IterationKind.Enumerate)
{
return new Completion(status, nextValue, context.LastSyntaxElement);
}
iteratorRecord.Close(status);
return new Completion(status, nextValue, context.LastSyntaxElement);
}
var result = stmt.Execute(context);
engine.UpdateLexicalEnvironment(oldEnv);
if (!result.Value.IsEmpty)
{
v = result.Value;
}
if (result.Type == CompletionType.Break && (context.Target == null || string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal)))
{
completionType = CompletionType.Normal;
return new Completion(CompletionType.Normal, v, _statement!);
}
if (result.Type != CompletionType.Continue || (context.Target != null && !string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal)))
{
completionType = result.Type;
if (iterationKind == IterationKind.Enumerate)
{
// TODO es6-generators make sure we can start from where we left off
//return result;
}
if (result.IsAbrupt())
{
close = true;
return result;
}
}
}
}
catch
{
completionType = CompletionType.Throw;
throw;
}
finally
{
if (close)
{
try
{
iteratorRecord.Close(completionType);
}
catch
{
// if we already have and exception, use it
if (completionType != CompletionType.Throw)
{
#pragma warning disable CA2219
#pragma warning disable MA0072
throw;
#pragma warning restore MA0072
#pragma warning restore CA2219
}
}
}
engine.UpdateLexicalEnvironment(oldEnv);
}
}
private void BindingInstantiation(Environment environment)
{
var envRec = (DeclarativeEnvironment) environment;
var variableDeclaration = (VariableDeclaration) _leftNode;
var boundNames = new List();
variableDeclaration.GetBoundNames(boundNames);
for (var i = 0; i < boundNames.Count; i++)
{
var name = boundNames[i];
if (variableDeclaration.Kind == VariableDeclarationKind.Const)
{
envRec.CreateImmutableBinding(name, strict: true);
}
else
{
envRec.CreateMutableBinding(name, canBeDeleted: false);
}
}
}
private enum LhsKind
{
Assignment,
VarBinding,
LexicalBinding
}
private enum IteratorKind
{
Sync,
Async
}
private enum IterationKind
{
Enumerate,
Iterate,
AsyncIterate
}
}
}