using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native; using Jint.Native.Array; using Jint.Native.Iterator; using Jint.Native.Number; using Jint.Runtime.Environments; 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 INode _expression; protected JintExpression(Engine engine, INode 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(); protected internal static JintExpression Build(Engine engine, Expression expression) { switch (expression.Type) { case Nodes.AssignmentExpression: return JintAssignmentExpression.Build(engine, (AssignmentExpression) expression); case Nodes.ArrayExpression: return new JintArrayExpression(engine, (ArrayExpression) expression); case Nodes.ArrowFunctionExpression: return new JintArrowFunctionExpression(engine, (IFunction) expression); case Nodes.BinaryExpression: return JintBinaryExpression.Build(engine, (BinaryExpression) expression); case Nodes.CallExpression: return new JintCallExpression(engine, (CallExpression) expression); case Nodes.ConditionalExpression: return new JintConditionalExpression(engine, (ConditionalExpression) expression); case Nodes.FunctionExpression: return new JintFunctionExpression(engine, (IFunction) expression); case Nodes.Identifier: return new JintIdentifierExpression(engine, (Esprima.Ast.Identifier) expression); case Nodes.Literal: return new JintLiteralExpression(engine, (Literal) expression); case Nodes.LogicalExpression: var binaryExpression = (BinaryExpression) expression; switch (binaryExpression.Operator) { case BinaryOperator.LogicalAnd: return new JintLogicalAndExpression(engine, binaryExpression); case BinaryOperator.LogicalOr: return new JintLogicalOrExpression(engine, binaryExpression); default: return ExceptionHelper.ThrowArgumentOutOfRangeException(); } case Nodes.MemberExpression: return new JintMemberExpression(engine, (MemberExpression) expression); case Nodes.NewExpression: return new JintNewExpression(engine, (NewExpression) expression); case Nodes.ObjectExpression: return new JintObjectExpression(engine, (ObjectExpression) expression); case Nodes.SequenceExpression: return new JintSequenceExpression(engine, (SequenceExpression) expression); case Nodes.ThisExpression: return new JintThisExpression(engine, (ThisExpression) expression); case Nodes.UpdateExpression: return new JintUpdateExpression(engine, (UpdateExpression) expression); case Nodes.UnaryExpression: return new JintUnaryExpression(engine, (UnaryExpression) expression); case Nodes.SpreadElement: return new JintSpreadExpression(engine, (SpreadElement) expression); case Nodes.TemplateLiteral: return new JintTemplateLiteralExpression(engine, (TemplateLiteral) expression); case Nodes.TaggedTemplateExpression: return new JintTaggedTemplateExpression(engine, (TaggedTemplateExpression) expression); default: ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(expression), $"unsupported language element '{expression.Type}'"); return null; } } protected JsValue Divide(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) { if (x._type == y._type) { return JintBinaryExpression.StrictlyEqual(x, y); } if (x._type == Types.Null && y._type == Types.Undefined) { return true; } if (x._type == Types.Undefined && y._type == Types.Null) { return true; } if (x._type == Types.Number && y._type == Types.String) { return Equal(x, TypeConverter.ToNumber(y)); } if (x._type == Types.String && y._type == Types.Number) { return Equal(TypeConverter.ToNumber(x), y); } if (x._type == Types.Boolean) { return Equal(TypeConverter.ToNumber(x), y); } if (y._type == Types.Boolean) { return Equal(x, TypeConverter.ToNumber(y)); } if (y._type == Types.Object && (x._type == Types.String || x._type == Types.Number)) { return Equal(x, TypeConverter.ToPrimitive(y)); } if (x._type == Types.Object && (y._type == Types.String || y._type == Types.Number)) { return Equal(TypeConverter.ToPrimitive(x), y); } return false; } protected internal static bool SameValue(JsValue x, JsValue y) { var typea = TypeConverter.GetPrimitiveType(x); var typeb = TypeConverter.GetPrimitiveType(y); if (typea != typeb) { return false; } switch (typea) { case Types.None: return true; case Types.Number: var nx = TypeConverter.ToNumber(x); var ny = TypeConverter.ToNumber(y); if (double.IsNaN(nx) && double.IsNaN(ny)) { return true; } if (nx == ny) { if (nx == 0) { // +0 !== -0 return NumberInstance.IsNegativeZero(nx) == NumberInstance.IsNegativeZero(ny); } return true; } return false; case Types.String: return TypeConverter.ToString(x) == TypeConverter.ToString(y); case Types.Boolean: return TypeConverter.ToBoolean(x) == TypeConverter.ToBoolean(y); default: return x == y; } } protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true) { 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; } else { 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(); } } 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 if (objectInstance is ArrayInstance ai) { 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()); } } 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 bool TryGetIdentifierEnvironmentWithBindingValue( in Key expressionName, out EnvironmentRecord record, out JsValue value) { var env = _engine.ExecutionContext.LexicalEnvironment; var strict = StrictModeScope.IsStrictModeCode; return LexicalEnvironment.TryGetIdentifierEnvironmentWithBindingValue( env, expressionName, strict, out record, out value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected bool TryGetIdentifierEnvironmentWithBindingValue( bool strict, in Key expressionName, out EnvironmentRecord record, out JsValue value) { var env = _engine.ExecutionContext.LexicalEnvironment; return LexicalEnvironment.TryGetIdentifierEnvironmentWithBindingValue( env, expressionName, strict, out record, out value); } } }