using System.Collections.Generic;
using Esprima.Ast;
using Jint.Native;
using Jint.Runtime.Environments;
using Jint.Runtime.Interpreter.Expressions;
namespace Jint.Runtime.Interpreter.Statements
{
///
/// https://tc39.es/ecma262/#sec-forbodyevaluation
///
internal sealed class JintForStatement : JintStatement
{
private JintVariableDeclaration _initStatement;
private JintExpression _initExpression;
private JintExpression _test;
private JintExpression _increment;
private JintStatement _body;
private List _boundNames;
private bool _shouldCreatePerIterationEnvironment;
public JintForStatement(Engine engine, ForStatement statement) : base(engine, statement)
{
_initialized = false;
}
protected override void Initialize()
{
_body = Build(_engine, _statement.Body);
if (_statement.Init != null)
{
if (_statement.Init.Type == Nodes.VariableDeclaration)
{
var d = (VariableDeclaration) _statement.Init;
if (d.Kind != VariableDeclarationKind.Var)
{
_boundNames = new List();
d.GetBoundNames(_boundNames);
}
_initStatement = new JintVariableDeclaration(_engine, d);
_shouldCreatePerIterationEnvironment = d.Kind == VariableDeclarationKind.Let;
}
else
{
_initExpression = JintExpression.Build(_engine, (Expression) _statement.Init);
}
}
if (_statement.Test != null)
{
_test = JintExpression.Build(_engine, _statement.Test);
}
if (_statement.Update != null)
{
_increment = JintExpression.Build(_engine, _statement.Update);
}
}
protected override Completion ExecuteInternal()
{
EnvironmentRecord oldEnv = null;
EnvironmentRecord loopEnv = null;
if (_boundNames != null)
{
oldEnv = _engine.ExecutionContext.LexicalEnvironment;
loopEnv = JintEnvironment.NewDeclarativeEnvironment(_engine, oldEnv);
var loopEnvRec = loopEnv;
var kind = _initStatement._statement.Kind;
for (var i = 0; i < _boundNames.Count; i++)
{
var name = _boundNames[i];
if (kind == VariableDeclarationKind.Const)
{
loopEnvRec.CreateImmutableBinding(name, true);
}
else
{
loopEnvRec.CreateMutableBinding(name, false);
}
}
_engine.UpdateLexicalEnvironment(loopEnv);
}
try
{
if (_initExpression != null)
{
_initExpression?.GetValue();
}
else
{
_initStatement?.Execute();
}
return ForBodyEvaluation();
}
finally
{
if (oldEnv is not null)
{
_engine.UpdateLexicalEnvironment(oldEnv);
}
}
}
///
/// https://tc39.es/ecma262/#sec-forbodyevaluation
///
private Completion ForBodyEvaluation()
{
var v = Undefined.Instance;
if (_shouldCreatePerIterationEnvironment)
{
CreatePerIterationEnvironment();
}
while (true)
{
if (_test != null)
{
if (!TypeConverter.ToBoolean(_test.GetValue()))
{
return new Completion(CompletionType.Normal, v, null, Location);
}
}
var result = _body.Execute();
if (!ReferenceEquals(result.Value, null))
{
v = result.Value;
}
if (result.Type == CompletionType.Break && (result.Identifier == null || result.Identifier == _statement?.LabelSet?.Name))
{
return new Completion(CompletionType.Normal, result.Value, null, Location);
}
if (result.Type != CompletionType.Continue || (result.Identifier != null && result.Identifier != _statement?.LabelSet?.Name))
{
if (result.Type != CompletionType.Normal)
{
return result;
}
}
if (_shouldCreatePerIterationEnvironment)
{
CreatePerIterationEnvironment();
}
_increment?.GetValue();
}
}
private void CreatePerIterationEnvironment()
{
if (_boundNames == null || _boundNames.Count == 0)
{
return;
}
var lastIterationEnv = _engine.ExecutionContext.LexicalEnvironment;
var lastIterationEnvRec = lastIterationEnv;
var outer = lastIterationEnv._outerEnv;
var thisIterationEnv = JintEnvironment.NewDeclarativeEnvironment(_engine, outer);
var thisIterationEnvRec = (DeclarativeEnvironmentRecord) thisIterationEnv;
for (var j = 0; j < _boundNames.Count; j++)
{
var bn = _boundNames[j];
var lastValue = lastIterationEnvRec.GetBindingValue(bn, true);
thisIterationEnvRec.CreateMutableBindingAndInitialize(bn, false, lastValue);
}
_engine.UpdateLexicalEnvironment(thisIterationEnv);
}
}
}