using Jint.Native; using Jint.Native.Iterator; namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintArrayExpression : JintExpression { private JintExpression?[] _expressions = Array.Empty(); private bool _hasSpreads; private bool _initialized; private JintArrayExpression(ArrayExpression expression) : base(expression) { } public static JintExpression Build(ArrayExpression expression) { return expression.Elements.Count == 0 ? JintEmptyArrayExpression.Instance : new JintArrayExpression(expression); } private void Initialize() { ref readonly var elements = ref ((ArrayExpression) _expression).Elements; var expressions = _expressions = new JintExpression[((ArrayExpression) _expression).Elements.Count]; for (var n = 0; n < expressions.Length; n++) { var expr = elements[n]; if (expr != null) { var expression = Build(expr); expressions[n] = expression; _hasSpreads |= expr.Type == NodeType.SpreadElement; } } // we get called from nested spread expansion in call _initialized = true; } protected override object EvaluateInternal(EvaluationContext context) { if (!_initialized) { Initialize(); _initialized = true; } var engine = context.Engine; var a = engine.Realm.Intrinsics.Array.ArrayCreate(_hasSpreads ? 0 : (uint) _expressions.Length); uint arrayIndexCounter = 0; foreach (var expr in _expressions) { if (expr == null) { a.SetIndexValue(arrayIndexCounter++, null, updateLength: false); } else if (_hasSpreads && expr is JintSpreadExpression jse) { jse.GetValueAndCheckIterator(context, out var objectInstance, out var iterator); // optimize for array if (objectInstance is JsArray ai) { var length = ai.GetLength(); var newLength = arrayIndexCounter + length; a.EnsureCapacity(newLength); a.CopyValues(ai, sourceStartIndex: 0, targetStartIndex: arrayIndexCounter, length); arrayIndexCounter += length; a.SetLength(newLength); } else { var protocol = new ArraySpreadProtocol(engine, a, iterator!, arrayIndexCounter); protocol.Execute(); arrayIndexCounter += protocol._addedCount; } } else { var value = expr.GetValue(context); a.SetIndexValue(arrayIndexCounter++, value, updateLength: false); } } if (_hasSpreads) { a.SetLength(arrayIndexCounter); } return a; } private sealed class ArraySpreadProtocol : IteratorProtocol { private readonly JsArray _instance; private long _index; internal uint _addedCount; public ArraySpreadProtocol( Engine engine, JsArray instance, IteratorInstance iterator, long startIndex) : base(engine, iterator, 0) { _instance = instance; _index = startIndex - 1; } protected override void ProcessItem(JsValue[] arguments, JsValue currentValue) { _index++; _addedCount++; _instance.SetIndexValue((uint) _index, currentValue, updateLength: false); } } internal sealed class JintEmptyArrayExpression : JintExpression { public static JintEmptyArrayExpression Instance = new(new ArrayExpression(NodeList.From(Array.Empty()))); private JintEmptyArrayExpression(Expression expression) : base(expression) { } protected override object EvaluateInternal(EvaluationContext context) { return new JsArray(context.Engine, Array.Empty()); } public override JsValue GetValue(EvaluationContext context) { return new JsArray(context.Engine, Array.Empty()); } } }