using System.Runtime.CompilerServices;
using Jint.Native;
using Jint.Native.Iterator;
namespace Jint.Runtime.Interpreter.Expressions;
///
/// Optimizes constants values from expression array and only returns the actual JsValue in consecutive calls.
///
internal sealed class ExpressionCache
{
private object?[] _expressions = [];
private bool _fullyCached;
internal bool HasSpreads { get; private set; }
internal void Initialize(EvaluationContext context, ReadOnlySpan arguments)
{
if (arguments.Length == 0)
{
_fullyCached = true;
_expressions = [];
return;
}
_expressions = new object?[arguments.Length];
_fullyCached = true;
for (var i = 0; i < (uint) arguments.Length; i++)
{
var argument = arguments[i];
if (argument is null)
{
_fullyCached = false;
continue;
}
var expression = JintExpression.Build(argument);
if (argument.Type == NodeType.Literal)
{
_expressions[i] = expression.GetValue(context).Clone();
continue;
}
_expressions[i] = expression;
_fullyCached &= argument.Type == NodeType.Literal;
HasSpreads |= CanSpread(argument);
if (argument.Type == NodeType.ArrayExpression)
{
ref readonly var elements = ref ((ArrayExpression) argument).Elements;
foreach (var e in elements.AsSpan())
{
HasSpreads |= CanSpread(e);
}
}
}
}
public JsValue[] ArgumentListEvaluation(EvaluationContext context, out bool rented)
{
rented = false;
if (_fullyCached)
{
return Unsafe.As(_expressions);
}
if (HasSpreads)
{
var args = new List(_expressions.Length);
BuildArgumentsWithSpreads(context, args);
return args.ToArray();
}
var arguments = context.Engine._jsValueArrayPool.RentArray(_expressions.Length);
rented = true;
BuildArguments(context, arguments);
return arguments;
}
internal void BuildArguments(EvaluationContext context, JsValue[] targetArray)
{
var expressions = _expressions;
for (uint i = 0; i < (uint) expressions.Length; i++)
{
targetArray[i] = GetValue(context, expressions[i])!;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public JsValue GetValue(EvaluationContext context, int index)
{
return GetValue(context, _expressions[index]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static JsValue GetValue(EvaluationContext context, object? value)
{
return value switch
{
JintExpression expression => expression.GetValue(context).Clone(),
_ => (JsValue) value!,
};
}
public bool IsAnonymousFunctionDefinition(int index)
{
var expressions = _expressions;
return index < expressions.Length && (expressions[index] as JintExpression)?._expression.IsAnonymousFunctionDefinition() == true;
}
private static bool CanSpread(Node? e)
{
if (e is null)
{
return false;
}
return e.Type == NodeType.SpreadElement || e is AssignmentExpression { Right.Type: NodeType.SpreadElement };
}
internal JsValue[] DefaultSuperCallArgumentListEvaluation(EvaluationContext context)
{
// This branch behaves similarly to constructor(...args) { super(...args); }.
// The most notable distinction is that while the aforementioned ECMAScript source text observably calls
// the @@iterator method on %Array.prototype%, this function does not.
var spreadExpression = (JintSpreadExpression) _expressions[0]!;
var array = (JsArray) spreadExpression._argument.GetValue(context);
var length = array.GetLength();
var args = new List((int) length);
for (uint j = 0; j < length; ++j)
{
array.TryGetValue(j, out var value);
args.Add(value);
}
return args.ToArray();
}
internal void BuildArgumentsWithSpreads(EvaluationContext context, List target)
{
foreach (var expression in _expressions)
{
if (expression is JintSpreadExpression jse)
{
jse.GetValueAndCheckIterator(context, out var objectInstance, out var iterator);
// optimize for array unless someone has touched the iterator
if (objectInstance is JsArray { HasOriginalIterator: true } ai)
{
var length = ai.GetLength();
for (uint j = 0; j < length; ++j)
{
ai.TryGetValue(j, out var value);
target.Add(value);
}
}
else
{
var protocol = new ArraySpreadProtocol(context.Engine, target, iterator!);
protocol.Execute();
}
}
else
{
target.Add(GetValue(context, expression)!);
}
}
}
private sealed class ArraySpreadProtocol : IteratorProtocol
{
private readonly List _instance;
public ArraySpreadProtocol(
Engine engine,
List instance,
IteratorInstance iterator) : base(engine, iterator, 0)
{
_instance = instance;
}
protected override void ProcessItem(JsValue[] arguments, JsValue currentValue)
{
_instance.Add(currentValue);
}
}
}