using System.Numerics; using System.Runtime.CompilerServices; using Jint.Native; using Jint.Native.Number; namespace Jint.Runtime.Interpreter.Expressions; internal abstract class JintExpression { 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 JsValue GetValue(EvaluationContext context) { var result = Evaluate(context); if (result is not Reference reference) { return (JsValue) result; } return context.Engine.GetValue(reference, returnReferenceToPool: true); } [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)] public object Evaluate(EvaluationContext context) { var oldSyntaxElement = context.LastSyntaxElement; context.PrepareFor(_expression); var result = EvaluateInternal(context); context.LastSyntaxElement = oldSyntaxElement; return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal object EvaluateWithoutNodeTracking(EvaluationContext context) { return EvaluateInternal(context); } protected abstract object EvaluateInternal(EvaluationContext context); /// /// 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 AstExtensions.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(Expression expression) { if (expression.UserData is JintExpression preparedExpression) { return preparedExpression; } var result = expression.Type switch { NodeType.AssignmentExpression => JintAssignmentExpression.Build((AssignmentExpression) expression), NodeType.ArrayExpression => JintArrayExpression.Build((ArrayExpression) expression), NodeType.ArrowFunctionExpression => new JintArrowFunctionExpression((ArrowFunctionExpression) expression), NodeType.BinaryExpression => JintBinaryExpression.Build((NonLogicalBinaryExpression) expression), NodeType.CallExpression => new JintCallExpression((CallExpression) expression), NodeType.ConditionalExpression => new JintConditionalExpression((ConditionalExpression) expression), NodeType.FunctionExpression => new JintFunctionExpression((FunctionExpression) expression), NodeType.Identifier => new JintIdentifierExpression((Identifier) expression), NodeType.PrivateIdentifier => new JintPrivateIdentifierExpression((PrivateIdentifier) expression), NodeType.Literal => JintLiteralExpression.Build((Literal) expression), NodeType.LogicalExpression => ((LogicalExpression) expression).Operator switch { Operator.LogicalAnd => new JintLogicalAndExpression((LogicalExpression) expression), Operator.LogicalOr => new JintLogicalOrExpression((LogicalExpression) expression), Operator.NullishCoalescing => new NullishCoalescingExpression((LogicalExpression) expression), _ => null }, NodeType.MemberExpression => new JintMemberExpression((MemberExpression) expression), NodeType.NewExpression => new JintNewExpression((NewExpression) expression), NodeType.ObjectExpression => JintObjectExpression.Build((ObjectExpression) expression), NodeType.SequenceExpression => new JintSequenceExpression((SequenceExpression) expression), NodeType.ThisExpression => new JintThisExpression((ThisExpression) expression), NodeType.UpdateExpression => new JintUpdateExpression((UpdateExpression) expression), NodeType.UnaryExpression => JintUnaryExpression.Build((NonUpdateUnaryExpression) expression), NodeType.SpreadElement => new JintSpreadExpression((SpreadElement) expression), NodeType.TemplateLiteral => new JintTemplateLiteralExpression((TemplateLiteral) expression), NodeType.TaggedTemplateExpression => new JintTaggedTemplateExpression((TaggedTemplateExpression) expression), NodeType.ClassExpression => new JintClassExpression((ClassExpression) expression), NodeType.ImportExpression => new JintImportExpression((ImportExpression) expression), NodeType.Super => new JintSuperExpression((Super) expression), NodeType.MetaProperty => new JintMetaPropertyExpression((MetaProperty) expression), NodeType.ChainExpression => ((ChainExpression) expression).Expression.Type == NodeType.CallExpression ? new JintCallExpression((CallExpression) ((ChainExpression) expression).Expression) : new JintMemberExpression((MemberExpression) ((ChainExpression) expression).Expression), NodeType.AwaitExpression => new JintAwaitExpression((AwaitExpression) expression), NodeType.YieldExpression => new JintYieldExpression((YieldExpression) expression), _ => null }; if (result is null) { Throw.ArgumentOutOfRangeException(nameof(expression), $"unsupported expression type '{expression.Type}'"); } return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static JsValue Divide(EvaluationContext context, JsValue left, JsValue right) { JsValue result; if (AreIntegerOperands(left, right)) { result = DivideInteger(left, right); } else if (JintBinaryExpression.AreNonBigIntOperands(left, right)) { result = DivideComplex(left, right); } else { JintBinaryExpression.AssertValidBigIntArithmeticOperands(left, right); var x = TypeConverter.ToBigInt(left); var y = TypeConverter.ToBigInt(right); if (y == 0) { Throw.RangeError(context.Engine.Realm, "Division by zero"); } result = JsBigInt.Create(x / y); } return result; } 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 && (lN != 0 || rN > 0)) { return JsNumber.Create(lN / rN); } return (double) lN / rN; } private static JsValue DivideComplex(JsValue lval, JsValue rval) { if (lval.IsUndefined() || rval.IsUndefined()) { return JsValue.Undefined; } 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; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true) => x.IsNumber() && y.IsNumber() ? CompareNumber(x, y, leftFirst) : CompareComplex(x, y, leftFirst); private static JsValue CompareNumber(JsValue x, JsValue y, bool leftFirst) { double nx, ny; if (leftFirst) { nx = x.AsNumber(); ny = y.AsNumber(); } else { ny = y.AsNumber(); nx = x.AsNumber(); } if (x.IsInteger() && y.IsInteger()) { return (int) nx < (int) ny ? JsBoolean.True : JsBoolean.False; } if (!double.IsInfinity(nx) && !double.IsInfinity(ny) && !double.IsNaN(nx) && !double.IsNaN(ny)) { return nx < ny ? JsBoolean.True : JsBoolean.False; } return CompareComplex(x, y, leftFirst); } 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) { if (typea == Types.BigInt || typeb == Types.BigInt) { if (typea == typeb) { return TypeConverter.ToBigInt(px) < TypeConverter.ToBigInt(py) ? JsBoolean.True : JsBoolean.False; } if (typea == Types.BigInt) { if (py is JsString jsStringY) { if (!TypeConverter.TryStringToBigInt(jsStringY.ToString(), out var temp)) { return JsValue.Undefined; } return TypeConverter.ToBigInt(px) < temp ? JsBoolean.True : JsBoolean.False; } var numberB = TypeConverter.ToNumber(py); if (double.IsNaN(numberB)) { return JsValue.Undefined; } if (double.IsPositiveInfinity(numberB)) { return JsBoolean.True; } if (double.IsNegativeInfinity(numberB)) { return JsBoolean.False; } var normalized = new BigInteger(Math.Ceiling(numberB)); return TypeConverter.ToBigInt(px) < normalized ? JsBoolean.True : JsBoolean.False; } if (px is JsString jsStringX) { if (!TypeConverter.TryStringToBigInt(jsStringX.ToString(), out var temp)) { return JsValue.Undefined; } return temp < TypeConverter.ToBigInt(py) ? JsBoolean.True : JsBoolean.False; } var numberA = TypeConverter.ToNumber(px); if (double.IsNaN(numberA)) { return JsValue.Undefined; } if (double.IsPositiveInfinity(numberA)) { return JsBoolean.False; } if (double.IsNegativeInfinity(numberA)) { return JsBoolean.True; } var normalizedA = new BigInteger(Math.Floor(numberA)); return normalizedA < TypeConverter.ToBigInt(py); } var nx = TypeConverter.ToNumber(px); var ny = TypeConverter.ToNumber(py); if (double.IsNaN(nx) || double.IsNaN(ny)) { return JsValue.Undefined; } if (nx == ny) { return JsBoolean.False; } if (double.IsPositiveInfinity(nx)) { return JsBoolean.False; } if (double.IsPositiveInfinity(ny)) { return JsBoolean.True; } if (double.IsNegativeInfinity(ny)) { return JsBoolean.False; } if (double.IsNegativeInfinity(nx)) { return JsBoolean.True; } return nx < ny ? JsBoolean.True : JsBoolean.False; } return string.CompareOrdinal(TypeConverter.ToString(x), TypeConverter.ToString(y)) < 0 ? JsBoolean.True : JsBoolean.False; } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static bool AreIntegerOperands(JsValue left, JsValue right) { return left._type == right._type && left._type == InternalTypes.Integer; } }