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