#nullable enable
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Esprima;
using Esprima.Ast;
using Jint.Native;
using Jint.Native.Array;
using Jint.Native.Iterator;
using Jint.Native.Number;
using Jint.Runtime.References;
namespace Jint.Runtime.Interpreter.Expressions
{
///
/// Adapter to get different types of results, including Reference which is not a JsValue.
///
internal readonly struct ExpressionResult
{
public readonly ExpressionCompletionType Type;
public readonly Location Location;
public readonly object Value;
public ExpressionResult(ExpressionCompletionType type, object value, in Location location)
{
Type = type;
Value = value;
Location = location;
}
public bool IsAbrupt() => Type != ExpressionCompletionType.Normal && Type != ExpressionCompletionType.Reference;
public static implicit operator ExpressionResult(in Completion result)
{
return new ExpressionResult((ExpressionCompletionType) result.Type, result.Value!, result.Location);
}
}
internal enum ExpressionCompletionType : byte
{
Normal = 0,
Return = 1,
Throw = 2,
Reference
}
internal abstract class JintExpression
{
// require sub-classes to set to false explicitly to skip virtual call
protected bool _initialized = true;
protected internal readonly Expression _expression;
protected JintExpression(Expression expression)
{
_expression = expression;
}
///
/// Resolves the underlying value for this expression.
/// By default uses the Engine for resolving.
///
///
///
public virtual Completion GetValue(EvaluationContext context)
{
var result = Evaluate(context);
if (result.Type != ExpressionCompletionType.Reference)
{
return new Completion((CompletionType) result.Type, (JsValue) result.Value, result.Location);
}
var jsValue = context.Engine.GetValue((Reference) result.Value, true);
return new Completion(CompletionType.Normal, jsValue, null, _expression.Location);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ExpressionResult Evaluate(EvaluationContext context)
{
context.LastSyntaxNode = _expression;
if (!_initialized)
{
Initialize(context);
_initialized = true;
}
return EvaluateInternal(context);
}
///
/// Opportunity to build one-time structures and caching based on lexical context.
///
///
protected virtual void Initialize(EvaluationContext context)
{
}
protected abstract ExpressionResult EvaluateInternal(EvaluationContext context);
///
/// https://tc39.es/ecma262/#sec-normalcompletion
///
///
/// We use custom type that is translated to Completion later on.
///
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected ExpressionResult NormalCompletion(JsValue value)
{
return new ExpressionResult(ExpressionCompletionType.Normal, value, _expression.Location);
}
protected ExpressionResult NormalCompletion(Reference value)
{
return new ExpressionResult(ExpressionCompletionType.Reference, value, _expression.Location);
}
///
/// https://tc39.es/ecma262/#sec-throwcompletion
///
///
/// We use custom type that is translated to Completion later on.
///
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected ExpressionResult ThrowCompletion(JsValue value)
{
return new ExpressionResult(ExpressionCompletionType.Throw, value, _expression.Location);
}
///
/// If we'd get Esprima source, we would just refer to it, but this makes error messages easier to decipher.
///
internal string SourceText => ToString(_expression) ?? "*unknown*";
internal static string? ToString(Expression expression)
{
while (true)
{
if (expression is Literal literal)
{
return EsprimaExtensions.LiteralKeyToString(literal);
}
if (expression is Identifier identifier)
{
return identifier.Name;
}
if (expression is MemberExpression memberExpression)
{
return ToString(memberExpression.Object) + "." + ToString(memberExpression.Property);
}
if (expression is CallExpression callExpression)
{
expression = callExpression.Callee;
continue;
}
return null;
}
}
protected internal static JintExpression Build(Engine engine, Expression expression)
{
var result = expression.Type switch
{
Nodes.AssignmentExpression => JintAssignmentExpression.Build(engine, (AssignmentExpression) expression),
Nodes.ArrayExpression => new JintArrayExpression((ArrayExpression) expression),
Nodes.ArrowFunctionExpression => new JintArrowFunctionExpression(engine, (ArrowFunctionExpression) expression),
Nodes.BinaryExpression => JintBinaryExpression.Build(engine, (BinaryExpression) expression),
Nodes.CallExpression => new JintCallExpression((CallExpression) expression),
Nodes.ConditionalExpression => new JintConditionalExpression(engine, (ConditionalExpression) expression),
Nodes.FunctionExpression => new JintFunctionExpression(engine, (FunctionExpression) expression),
Nodes.Identifier => new JintIdentifierExpression((Identifier) expression),
Nodes.Literal => JintLiteralExpression.Build((Literal) expression),
Nodes.LogicalExpression => ((BinaryExpression) expression).Operator switch
{
BinaryOperator.LogicalAnd => new JintLogicalAndExpression((BinaryExpression) expression),
BinaryOperator.LogicalOr => new JintLogicalOrExpression(engine, (BinaryExpression) expression),
BinaryOperator.NullishCoalescing => new NullishCoalescingExpression(engine, (BinaryExpression) expression),
_ => null
},
Nodes.MemberExpression => new JintMemberExpression((MemberExpression) expression),
Nodes.NewExpression => new JintNewExpression((NewExpression) expression),
Nodes.ObjectExpression => new JintObjectExpression((ObjectExpression) expression),
Nodes.SequenceExpression => new JintSequenceExpression((SequenceExpression) expression),
Nodes.ThisExpression => new JintThisExpression((ThisExpression) expression),
Nodes.UpdateExpression => new JintUpdateExpression((UpdateExpression) expression),
Nodes.UnaryExpression => JintUnaryExpression.Build(engine, (UnaryExpression) expression),
Nodes.SpreadElement => new JintSpreadExpression(engine, (SpreadElement) expression),
Nodes.TemplateLiteral => new JintTemplateLiteralExpression((TemplateLiteral) expression),
Nodes.TaggedTemplateExpression => new JintTaggedTemplateExpression((TaggedTemplateExpression) expression),
Nodes.ClassExpression => new JintClassExpression((ClassExpression) expression),
Nodes.Super => new JintSuperExpression((Super) expression),
Nodes.MetaProperty => new JintMetaPropertyExpression((MetaProperty) expression),
Nodes.ChainExpression => ((ChainExpression) expression).Expression.Type == Nodes.CallExpression
? new JintCallExpression((CallExpression) ((ChainExpression) expression).Expression)
: new JintMemberExpression((MemberExpression) ((ChainExpression) expression).Expression),
_ => null
};
if (result is null)
{
ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(expression), $"unsupported expression type '{expression.Type}'");
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static JsValue Divide(JsValue lval, JsValue rval)
{
return AreIntegerOperands(lval, rval)
? DivideInteger(lval, rval)
: DivideComplex(lval, rval);
}
private static JsValue DivideInteger(JsValue lval, JsValue rval)
{
var lN = lval.AsInteger();
var rN = rval.AsInteger();
if (lN == 0 && rN == 0)
{
return JsNumber.DoubleNaN;
}
if (rN == 0)
{
return lN > 0 ? double.PositiveInfinity : double.NegativeInfinity;
}
if (lN % rN == 0)
{
return lN / rN;
}
return (double) lN / rN;
}
private static JsValue DivideComplex(JsValue lval, JsValue rval)
{
if (lval.IsUndefined() || rval.IsUndefined())
{
return Undefined.Instance;
}
else
{
var lN = TypeConverter.ToNumber(lval);
var rN = TypeConverter.ToNumber(rval);
if (double.IsNaN(rN) || double.IsNaN(lN))
{
return JsNumber.DoubleNaN;
}
if (double.IsInfinity(lN) && double.IsInfinity(rN))
{
return JsNumber.DoubleNaN;
}
if (double.IsInfinity(lN) && rN == 0)
{
if (NumberInstance.IsNegativeZero(rN))
{
return -lN;
}
return lN;
}
if (lN == 0 && rN == 0)
{
return JsNumber.DoubleNaN;
}
if (rN == 0)
{
if (NumberInstance.IsNegativeZero(rN))
{
return lN > 0 ? -double.PositiveInfinity : -double.NegativeInfinity;
}
return lN > 0 ? double.PositiveInfinity : double.NegativeInfinity;
}
return lN / rN;
}
}
protected static bool Equal(JsValue x, JsValue y)
{
return x.Type == y.Type
? JintBinaryExpression.StrictlyEqual(x, y)
: EqualUnlikely(x, y);
}
private static bool EqualUnlikely(JsValue x, JsValue y)
{
if (x._type == InternalTypes.Null && y._type == InternalTypes.Undefined)
{
return true;
}
if (x._type == InternalTypes.Undefined && y._type == InternalTypes.Null)
{
return true;
}
if (x.IsNumber() && y.IsString())
{
return Equal(x, TypeConverter.ToNumber(y));
}
if (x.IsString() && y.IsNumber())
{
return Equal(TypeConverter.ToNumber(x), y);
}
if (x.IsBoolean())
{
return Equal(TypeConverter.ToNumber(x), y);
}
if (y.IsBoolean())
{
return Equal(x, TypeConverter.ToNumber(y));
}
const InternalTypes stringOrNumber = InternalTypes.String | InternalTypes.Integer | InternalTypes.Number;
if (y.IsObject() && (x._type & stringOrNumber) != 0)
{
return Equal(x, TypeConverter.ToPrimitive(y));
}
if (x.IsObject() && ((y._type & stringOrNumber) != 0))
{
return Equal(TypeConverter.ToPrimitive(x), y);
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true) =>
x._type == y._type && x._type == InternalTypes.Integer
? CompareInteger(x, y, leftFirst)
: CompareComplex(x, y, leftFirst);
private static JsValue CompareInteger(JsValue x, JsValue y, bool leftFirst)
{
int nx, ny;
if (leftFirst)
{
nx = x.AsInteger();
ny = y.AsInteger();
}
else
{
ny = y.AsInteger();
nx = x.AsInteger();
}
return nx < ny;
}
private static JsValue CompareComplex(JsValue x, JsValue y, bool leftFirst)
{
JsValue px, py;
if (leftFirst)
{
px = TypeConverter.ToPrimitive(x, Types.Number);
py = TypeConverter.ToPrimitive(y, Types.Number);
}
else
{
py = TypeConverter.ToPrimitive(y, Types.Number);
px = TypeConverter.ToPrimitive(x, Types.Number);
}
var typea = px.Type;
var typeb = py.Type;
if (typea != Types.String || typeb != Types.String)
{
var nx = TypeConverter.ToNumber(px);
var ny = TypeConverter.ToNumber(py);
if (double.IsNaN(nx) || double.IsNaN(ny))
{
return Undefined.Instance;
}
if (nx == ny)
{
return false;
}
if (double.IsPositiveInfinity(nx))
{
return false;
}
if (double.IsPositiveInfinity(ny))
{
return true;
}
if (double.IsNegativeInfinity(ny))
{
return false;
}
if (double.IsNegativeInfinity(nx))
{
return true;
}
return nx < ny;
}
return string.CompareOrdinal(TypeConverter.ToString(x), TypeConverter.ToString(y)) < 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static void BuildArguments(EvaluationContext context, JintExpression[] jintExpressions, JsValue[] targetArray)
{
for (var i = 0; i < jintExpressions.Length; i++)
{
var completion = jintExpressions[i].GetValue(context);
targetArray[i] = completion.Value!.Clone();
}
}
protected JsValue[] BuildArgumentsWithSpreads(EvaluationContext context, JintExpression[] jintExpressions)
{
var args = new System.Collections.Generic.List(jintExpressions.Length);
for (var i = 0; i < jintExpressions.Length; i++)
{
var jintExpression = jintExpressions[i];
if (jintExpression is JintSpreadExpression jse)
{
jse.GetValueAndCheckIterator(context, out var objectInstance, out var iterator);
// optimize for array unless someone has touched the iterator
if (objectInstance is ArrayInstance ai && ai.HasOriginalIterator)
{
var length = ai.GetLength();
for (uint j = 0; j < length; ++j)
{
if (ai.TryGetValue(j, out var value))
{
args.Add(value);
}
}
}
else
{
var protocol = new ArraySpreadProtocol(context.Engine, args, iterator);
protocol.Execute();
}
}
else
{
var completion = jintExpression.GetValue(context);
args.Add(completion.Value!.Clone());
}
}
return args.ToArray();
}
private sealed class ArraySpreadProtocol : IteratorProtocol
{
private readonly System.Collections.Generic.List _instance;
public ArraySpreadProtocol(
Engine engine,
System.Collections.Generic.List instance,
IIterator iterator) : base(engine, iterator, 0)
{
_instance = instance;
}
protected override void ProcessItem(JsValue[] args, JsValue currentValue)
{
_instance.Add(currentValue);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static bool AreIntegerOperands(JsValue left, JsValue right)
{
return left._type == right._type && left._type == InternalTypes.Integer;
}
}
}