using System.Runtime.CompilerServices; using Jint.Collections; using Jint.Native; using Jint.Native.Error; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Statements; namespace Jint.Runtime.Interpreter; internal sealed class JintStatementList { private readonly record struct Pair(JintStatement Statement, JsValue? Value); private readonly Statement? _statement; private readonly NodeList _statements; private Pair[]? _jintStatements; private bool _initialized; private uint _index; private readonly bool _generator; public JintStatementList(IFunction function) : this((FunctionBody) function.Body) { _generator = function.Generator; } public JintStatementList(BlockStatement blockStatement) : this(blockStatement, blockStatement.Body) { } public JintStatementList(Program program) : this(null, program.Body) { } public JintStatementList(Statement? statement, in NodeList statements) { _statement = statement; _statements = statements; } private void Initialize(EvaluationContext context) { var jintStatements = new Pair[_statements.Count]; for (var i = 0; i < jintStatements.Length; i++) { var esprimaStatement = _statements[i]; var statement = JintStatement.Build(esprimaStatement); // When in debug mode, don't do FastResolve: Stepping requires each statement to be actually executed. var value = context.DebugMode ? null : JintStatement.FastResolve(esprimaStatement); jintStatements[i] = new Pair(statement, value); } _jintStatements = jintStatements; } [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)] public Completion Execute(EvaluationContext context) { if (!_initialized) { Initialize(context); _initialized = true; } if (_statement is not null) { context.LastSyntaxElement = _statement; context.RunBeforeExecuteStatementChecks(_statement); } Completion c = Completion.Empty(); Completion sl = c; // The value of a StatementList is the value of the last value-producing item in the StatementList var lastValue = JsEmpty.Instance; var i = _index; var temp = _jintStatements!; try { for (; i < (uint) temp.Length; i++) { ref readonly var pair = ref temp[i]; if (pair.Value is null) { c = pair.Statement.Execute(context); if (context.Engine._error is not null) { c = HandleError(context.Engine, pair.Statement); break; } } else { c = new Completion(CompletionType.Return, pair.Value, pair.Statement._statement); } if (_generator) { if (context.Engine.ExecutionContext.Suspended) { _index = i + 1; c = new Completion(CompletionType.Return, c.Value, pair.Statement._statement); break; } } if (c.Type != CompletionType.Normal) { return c.UpdateEmpty(sl.Value); } sl = c; if (!c.Value.IsEmpty) { lastValue = c.Value; } } } catch (Exception ex) { Reset(); if (ex is JintException) { c = HandleException(context, ex, temp[i].Statement); } else { throw; } } return c.UpdateEmpty(lastValue).UpdateEmpty(JsValue.Undefined); } internal static Completion HandleException(EvaluationContext context, Exception exception, JintStatement? s) { return exception switch { JavaScriptException javaScriptException => CreateThrowCompletion(s, javaScriptException), TypeErrorException typeErrorException => CreateThrowCompletion(context.Engine.Realm.Intrinsics.TypeError, typeErrorException, typeErrorException.Node ?? s!._statement), RangeErrorException rangeErrorException => CreateThrowCompletion(context.Engine.Realm.Intrinsics.RangeError, rangeErrorException, s!._statement), _ => throw exception }; } internal static Completion HandleError(Engine engine, JintStatement? s) { var error = engine._error!; engine._error = null; return CreateThrowCompletion(error.ErrorConstructor, error.Message, engine._lastSyntaxElement ?? s!._statement); } private static Completion CreateThrowCompletion(ErrorConstructor errorConstructor, string? message, Node s) { var error = errorConstructor.Construct(message); return new Completion(CompletionType.Throw, error, s); } private static Completion CreateThrowCompletion(ErrorConstructor errorConstructor, Exception e, Node s) { var error = errorConstructor.Construct(e.Message); return new Completion(CompletionType.Throw, error, s); } private static Completion CreateThrowCompletion(JintStatement? s, JavaScriptException v) { Node source = s!._statement; if (v.Location != default) { source = AstExtensions.CreateLocationNode(v.Location); } return new Completion(CompletionType.Throw, v.Error, source); } /// /// https://tc39.es/ecma262/#sec-blockdeclarationinstantiation /// internal static void BlockDeclarationInstantiation(DeclarativeEnvironment env, DeclarationCache declarations) { var privateEnv = env._engine.ExecutionContext.PrivateEnvironment; var list = declarations.Declarations; var dictionary = env._dictionary ??= new HybridDictionary(list.Count, checkExistingKeys: !declarations.AllLexicalScoped); dictionary.EnsureCapacity(list.Count); for (var i = 0; i < list.Count; i++) { var declaration = list[i]; foreach (var bn in declaration.BoundNames) { if (declaration.IsConstantDeclaration) { dictionary.CreateImmutableBinding(bn, strict: true); } else { dictionary.CreateMutableBinding(bn, canBeDeleted: false); } } if (declaration.Declaration is FunctionDeclaration functionDeclaration) { var definition = new JintFunctionDefinition(functionDeclaration); var fn = definition.Name!; var fo = env._engine.Realm.Intrinsics.Function.InstantiateFunctionObject(definition, env, privateEnv); env.InitializeBinding(fn, fo, DisposeHint.Normal); } } dictionary.CheckExistingKeys = true; } public bool Completed => _index == _jintStatements?.Length; public void Reset() { _index = 0; } }