using Jint.Native; 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-forbodyevaluation /// internal sealed class JintForStatement : JintStatement { private JintVariableDeclaration? _initStatement; private JintExpression? _initExpression; private JintExpression? _test; private JintExpression? _increment; private ProbablyBlockStatement _body; private List? _boundNames; private bool _shouldCreatePerIterationEnvironment; public JintForStatement(ForStatement statement) : base(statement) { } protected override void Initialize(EvaluationContext context) { var engine = context.Engine; _body = new ProbablyBlockStatement(_statement.Body); if (_statement.Init != null) { if (_statement.Init.Type == NodeType.VariableDeclaration) { var d = (VariableDeclaration) _statement.Init; if (d.Kind != VariableDeclarationKind.Var) { _boundNames = new List(); d.GetBoundNames(_boundNames); } _initStatement = new JintVariableDeclaration(d); _shouldCreatePerIterationEnvironment = d.Kind == VariableDeclarationKind.Let; } else { _initExpression = JintExpression.Build((Expression) _statement.Init); } } if (_statement.Test != null) { _test = JintExpression.Build(_statement.Test); } if (_statement.Update != null) { _increment = JintExpression.Build(_statement.Update); } } protected override Completion ExecuteInternal(EvaluationContext context) { Environment? oldEnv = null; Environment? loopEnv = null; var engine = context.Engine; 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); } else { loopEnvRec.CreateMutableBinding(name); } } engine.UpdateLexicalEnvironment(loopEnv); } try { if (_initExpression != null) { _initExpression?.GetValue(context); } else { _initStatement?.Execute(context); } return ForBodyEvaluation(context); } finally { if (oldEnv is not null) { engine.UpdateLexicalEnvironment(oldEnv); } } } /// /// https://tc39.es/ecma262/#sec-forbodyevaluation /// private Completion ForBodyEvaluation(EvaluationContext context) { var v = JsValue.Undefined; if (_shouldCreatePerIterationEnvironment) { CreatePerIterationEnvironment(context); } var debugHandler = context.DebugMode ? context.Engine.Debugger : null; while (true) { if (_test != null) { debugHandler?.OnStep(_test._expression); if (!TypeConverter.ToBoolean(_test.GetValue(context))) { return new Completion(CompletionType.Normal, v, ((JintStatement) this)._statement); } } var result = _body.Execute(context); if (!result.Value.IsEmpty) { v = result.Value; } if (result.Type == CompletionType.Break && (context.Target == null || string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal))) { return new Completion(CompletionType.Normal, result.Value, ((JintStatement) this)._statement); } if (result.Type != CompletionType.Continue || (context.Target != null && !string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal))) { if (result.Type != CompletionType.Normal) { return result; } } if (_shouldCreatePerIterationEnvironment) { CreatePerIterationEnvironment(context); } if (_increment != null) { debugHandler?.OnStep(_increment._expression); _increment.Evaluate(context); } } } private void CreatePerIterationEnvironment(EvaluationContext context) { var engine = context.Engine; var lastIterationEnv = (DeclarativeEnvironment) engine.ExecutionContext.LexicalEnvironment; var thisIterationEnv = JintEnvironment.NewDeclarativeEnvironment(engine, lastIterationEnv._outerEnv); lastIterationEnv.TransferTo(_boundNames!, thisIterationEnv); engine.UpdateLexicalEnvironment(thisIterationEnv); } } }