#nullable enable using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native; using Jint.Native.Array; using Jint.Native.Iterator; using Jint.Native.Number; using Jint.Native.Symbol; namespace Jint.Runtime.Interpreter.Expressions { internal abstract class JintExpression { // require sub-classes to set to false explicitly to skip virtual call protected bool _initialized = true; protected readonly Engine _engine; protected internal readonly Expression _expression; protected JintExpression(Engine engine, Expression expression) { _engine = engine; _expression = expression; } /// /// Resolves the underlying value for this expression. /// By default uses the Engine for resolving. /// /// public virtual JsValue GetValue() { return _engine.GetValue(Evaluate(), true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public object Evaluate() { _engine._lastSyntaxNode = _expression; if (!_initialized) { Initialize(); _initialized = true; } return EvaluateInternal(); } /// /// Opportunity to build one-time structures and caching based on lexical context. /// protected virtual void Initialize() { } protected abstract object EvaluateInternal(); /// /// 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) { return expression.Type switch { Nodes.AssignmentExpression => JintAssignmentExpression.Build(engine, (AssignmentExpression) expression), Nodes.ArrayExpression => new JintArrayExpression(engine, (ArrayExpression) expression), Nodes.ArrowFunctionExpression => new JintArrowFunctionExpression(engine, (IFunction) expression), Nodes.BinaryExpression => JintBinaryExpression.Build(engine, (BinaryExpression) expression), Nodes.CallExpression => new JintCallExpression(engine, (CallExpression) expression), Nodes.ConditionalExpression => new JintConditionalExpression(engine, (ConditionalExpression) expression), Nodes.FunctionExpression => new JintFunctionExpression(engine, (IFunction) expression), Nodes.Identifier => new JintIdentifierExpression(engine, (Identifier) expression), Nodes.Literal => JintLiteralExpression.Build(engine, (Literal) expression), Nodes.LogicalExpression => ((BinaryExpression) expression).Operator switch { BinaryOperator.LogicalAnd => new JintLogicalAndExpression(engine, (BinaryExpression) expression), BinaryOperator.LogicalOr => new JintLogicalOrExpression(engine, (BinaryExpression) expression), BinaryOperator.NullishCoalescing => new NullishCoalescingExpression(engine, (BinaryExpression) expression), _ => ExceptionHelper.ThrowArgumentOutOfRangeException() }, Nodes.MemberExpression => new JintMemberExpression(engine, (MemberExpression) expression), Nodes.NewExpression => new JintNewExpression(engine, (NewExpression) expression), Nodes.ObjectExpression => new JintObjectExpression(engine, (ObjectExpression) expression), Nodes.SequenceExpression => new JintSequenceExpression(engine, (SequenceExpression) expression), Nodes.ThisExpression => new JintThisExpression(engine, (ThisExpression) expression), Nodes.UpdateExpression => new JintUpdateExpression(engine, (UpdateExpression) expression), Nodes.UnaryExpression => JintUnaryExpression.Build(engine, (UnaryExpression) expression), Nodes.SpreadElement => new JintSpreadExpression(engine, (SpreadElement) expression), Nodes.TemplateLiteral => new JintTemplateLiteralExpression(engine, (TemplateLiteral) expression), Nodes.TaggedTemplateExpression => new JintTaggedTemplateExpression(engine, (TaggedTemplateExpression) expression), Nodes.ClassExpression => new JintClassExpression(engine, (ClassExpression) expression), Nodes.Super => new JintSuperExpression(engine, (Super) expression), Nodes.MetaProperty => new JintMetaPropertyExpression(engine, (MetaProperty) expression), _ => ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(expression), $"unsupported expression type '{expression.Type}'") }; } [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(JintExpression[] jintExpressions, JsValue[] targetArray) { for (var i = 0; i < jintExpressions.Length; i++) { targetArray[i] = jintExpressions[i].GetValue().Clone(); } } protected JsValue[] BuildArgumentsWithSpreads(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(out var objectInstance, out var iterator); // optimize for array unless someone has touched the iterator if (objectInstance is ArrayInstance ai && ReferenceEquals(ai.Get(GlobalSymbolRegistry.Iterator), _engine.Array.PrototypeObject._originalIteratorFunction)) { 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(_engine, args, iterator); protocol.Execute(); } } else { args.Add(jintExpression.GetValue().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) { var jsValue = ExtractValueFromIteratorInstance(currentValue); _instance.Add(jsValue); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static bool AreIntegerOperands(JsValue left, JsValue right) { return left._type == right._type && left._type == InternalTypes.Integer; } } }