Represents a literal expression.
Inheritance: Jurassic.Compiler.Expression
示例#1
0
        /// <summary>
        /// Parses a javascript expression.
        /// </summary>
        /// <param name="endToken"> A token that indicates the end of the expression. </param>
        /// <returns> An expression tree that represents the expression. </returns>
        private Expression ParseExpression(params Token[] endTokens)
        {
            // The root of the expression tree.
            Expression root = null;

            // The active operator, i.e. the one last encountered.
            OperatorExpression unboundOperator = null;

            while (this.nextToken != null)
            {
                if (this.nextToken is LiteralToken ||
                    this.nextToken is IdentifierToken ||
                    this.nextToken == KeywordToken.Function ||
                    this.nextToken == KeywordToken.This ||
                    this.nextToken == PunctuatorToken.LeftBrace ||
                    (this.nextToken == PunctuatorToken.LeftBracket && this.expressionState == ParserExpressionState.Literal) ||
                    (this.nextToken is KeywordToken && unboundOperator != null && unboundOperator.OperatorType == OperatorType.MemberAccess && this.expressionState == ParserExpressionState.Literal))
                {
                    // If a literal was found where an operator was expected, insert a semi-colon
                    // automatically (if this would fix the error and a line terminator was
                    // encountered) or throw an error.
                    if (this.expressionState != ParserExpressionState.Literal)
                    {
                        // Check for automatic semi-colon insertion.
                        if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true)
                            break;
                        throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected operator but found {0}", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath);
                    }

                    // New in ECMAScript 5 is the ability to use keywords as property names.
                    if ((this.nextToken is KeywordToken || (this.nextToken is LiteralToken && ((LiteralToken)this.nextToken).IsKeyword == true)) &&
                        unboundOperator != null &&
                        unboundOperator.OperatorType == OperatorType.MemberAccess &&
                        this.expressionState == ParserExpressionState.Literal)
                    {
                        this.nextToken = new IdentifierToken(this.nextToken.Text);
                    }

                    Expression terminal;
                    if (this.nextToken is LiteralToken)
                        // If the token is a literal, convert it to a literal expression.
                        terminal = new LiteralExpression(((LiteralToken)this.nextToken).Value);
                    else if (this.nextToken is IdentifierToken)
                    {
                        // If the token is an identifier, convert it to a NameExpression.
                        var identifierName = ((IdentifierToken)this.nextToken).Name;
                        terminal = new NameExpression(this.currentScope, identifierName);

                        // Record each occurance of a variable name.
                        if (unboundOperator == null || unboundOperator.OperatorType != OperatorType.MemberAccess)
                            this.methodOptimizationHints.EncounteredVariable(identifierName);
                    }
                    else if (this.nextToken == KeywordToken.This)
                    {
                        // Convert "this" to an expression.
                        terminal = new ThisExpression();

                        // Add method optimization info.
                        this.methodOptimizationHints.HasThis = true;
                    }
                    else if (this.nextToken == PunctuatorToken.LeftBracket)
                        // Array literal.
                        terminal = ParseArrayLiteral();
                    else if (this.nextToken == PunctuatorToken.LeftBrace)
                        // Object literal.
                        terminal = ParseObjectLiteral();
                    else if (this.nextToken == KeywordToken.Function)
                        terminal = ParseFunctionExpression();
                    else
                        throw new InvalidOperationException("Unsupported literal type.");

                    // Push the literal to the most recent unbound operator, or, if there is none, to
                    // the root of the tree.
                    if (root == null)
                    {
                        // This is the first term in an expression.
                        root = terminal;
                    }
                    else
                    {
                        Debug.Assert(unboundOperator != null && unboundOperator.AcceptingOperands == true);
                        unboundOperator.Push(terminal);
                    }
                }
                else if (this.nextToken is PunctuatorToken || this.nextToken is KeywordToken)
                {
                    // The token is an operator (o1).
                    Operator newOperator = OperatorFromToken(this.nextToken, postfixOrInfix: this.expressionState == ParserExpressionState.Operator);

                    // Make sure the token is actually an operator and not just a random keyword.
                    if (newOperator == null)
                    {
                        // Check if the token is an end token, for example a semi-colon.
                        if (Array.IndexOf(endTokens, this.nextToken) >= 0)
                            break;
                        // Check for automatic semi-colon insertion.
                        if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && (this.consumedLineTerminator == true || this.nextToken == PunctuatorToken.RightBrace))
                            break;
                        throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Unexpected token {0} in expression.", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath);
                    }

                    // Post-fix increment and decrement cannot have a line terminator in between
                    // the operator and the operand.
                    if (this.consumedLineTerminator == true && (newOperator == Operator.PostIncrement || newOperator == Operator.PostDecrement))
                        break;

                    // There are four possibilities:
                    // 1. The token is the second of a two-part operator (for example, the ':' in a
                    //    conditional operator.  In this case, we need to go up the tree until we find
                    //    an instance of the operator and make that the active unbound operator.
                    if (this.nextToken == newOperator.SecondaryToken)
                    {
                        // Traverse down the tree looking for the parent operator that corresponds to
                        // this token.
                        OperatorExpression parentExpression = null;
                        var node = root as OperatorExpression;
                        while (node != null)
                        {
                            if (node.Operator.Token == newOperator.Token && node.SecondTokenEncountered == false)
                                parentExpression = node;
                            if (node == unboundOperator)
                                break;
                            node = node.RightBranch;
                        }

                        // If the operator was not found, then this is a mismatched token, unless
                        // it is the end token.  For example, if an unbalanced right parenthesis is
                        // found in an if statement then it is merely the end of the test expression.
                        if (parentExpression == null)
                        {
                            // Check if the token is an end token, for example a right parenthesis.
                            if (Array.IndexOf(endTokens, this.nextToken) >= 0)
                                break;
                            // Check for automatic semi-colon insertion.
                            if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true)
                                break;
                            throw new JavaScriptException(this.engine, "SyntaxError", "Mismatched closing token in expression.", this.LineNumber, this.SourcePath);
                        }

                        // Mark that we have seen the closing token.
                        unboundOperator = parentExpression;
                        unboundOperator.SecondTokenEncountered = true;
                    }
                    else
                    {
                        // Check if the token is an end token, for example the comma in a variable
                        // declaration.
                        if (Array.IndexOf(endTokens, this.nextToken) >= 0)
                        {
                            // But make sure the token isn't inside an operator.
                            // For example, in the expression "var x = f(a, b)" the comma does not
                            // indicate the start of a new variable clause because it is inside the
                            // function call operator.
                            bool insideOperator = false;
                            var node = root as OperatorExpression;
                            while (node != null)
                            {
                                if (node.Operator.SecondaryToken != null && node.SecondTokenEncountered == false)
                                    insideOperator = true;
                                if (node == unboundOperator)
                                    break;
                                node = node.RightBranch;
                            }
                            if (insideOperator == false)
                                break;
                        }

                        // All the other situations involve the creation of a new operator.
                        var newExpression = OperatorExpression.FromOperator(newOperator);

                        // 2. The new operator is a prefix operator.  The new operator becomes an operand
                        //    of the previous operator.
                        if (newOperator.HasLHSOperand == false)
                        {
                            if (root == null)
                                // "!"
                                root = newExpression;
                            else if (unboundOperator != null && unboundOperator.AcceptingOperands == true)
                            {
                                // "5 + !"
                                unboundOperator.Push(newExpression);
                            }
                            else
                            {
                                // "5 !" or "5 + 5 !"
                                // Check for automatic semi-colon insertion.
                                if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true)
                                    break;
                                throw new JavaScriptException(this.engine, "SyntaxError", "Invalid use of prefix operator.", this.LineNumber, this.SourcePath);
                            }
                        }
                        else
                        {
                            // Search up the tree for an operator that has a lower precedence.
                            // Because we don't store the parent link, we have to traverse down the
                            // tree and take the last one we find instead.
                            OperatorExpression lowPrecedenceOperator = null;
                            if (unboundOperator == null ||
                                (newOperator.Associativity == OperatorAssociativity.LeftToRight && unboundOperator.Precedence < newOperator.Precedence) ||
                                (newOperator.Associativity == OperatorAssociativity.RightToLeft && unboundOperator.Precedence <= newOperator.Precedence))
                            {
                                // Performance optimization: look at the previous operator first.
                                lowPrecedenceOperator = unboundOperator;
                            }
                            else
                            {
                                // Search for a lower precedence operator by traversing the tree.
                                var node = root as OperatorExpression;
                                while (node != null && node != unboundOperator)
                                {
                                    if ((newOperator.Associativity == OperatorAssociativity.LeftToRight && node.Precedence < newOperator.Precedence) ||
                                        (newOperator.Associativity == OperatorAssociativity.RightToLeft && node.Precedence <= newOperator.Precedence))
                                        lowPrecedenceOperator = node;
                                    node = node.RightBranch;
                                }
                            }

                            if (lowPrecedenceOperator == null)
                            {
                                // 3. The new operator has a lower precedence (or if the associativity is left to
                                //    right, a lower or equal precedence) than all the parent operators.  The new
                                //    operator goes to the root of the tree and the previous operator becomes the
                                //    first operand for the new operator.
                                if (root != null)
                                    newExpression.Push(root);
                                root = newExpression;
                            }
                            else
                            {
                                // 4. Otherwise, the new operator can steal the last operand from the previous
                                //    operator and then put itself in the place of that last operand.
                                if (lowPrecedenceOperator.OperandCount == 0)
                                {
                                    // "! ++"
                                    // Check for automatic semi-colon insertion.
                                    if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true)
                                        break;
                                    throw new JavaScriptException(this.engine, "SyntaxError", "Invalid use of prefix operator.", this.LineNumber, this.SourcePath);
                                }
                                newExpression.Push(lowPrecedenceOperator.Pop());
                                lowPrecedenceOperator.Push(newExpression);
                            }
                        }

                        unboundOperator = newExpression;
                    }
                }
                else
                {
                    throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Unexpected token {0} in expression", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath);
                }

                // Read the next token.
                this.Consume(root != null && (unboundOperator == null || unboundOperator.AcceptingOperands == false) ? ParserExpressionState.Operator : ParserExpressionState.Literal);
            }

            // Empty expressions are invalid.
            if (root == null)
                throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected an expression but found {0} instead", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath);

            // Check the AST is valid.
            CheckASTValidity(root);

            // A literal is the next valid expression token.
            this.expressionState = ParserExpressionState.Literal;
            this.lexer.ParserExpressionState = expressionState;

            // Resolve all the unbound operators into real operators.
            return root;
        }
示例#2
0
        /// <summary>
        /// Reads a property name, used in object literals.
        /// </summary>
        /// <param name="nameType"> Identifies the particular way the property was specified. </param>
        /// <returns> An expression that evaluates to the property name. </returns>
        private Expression ReadPropertyNameExpression(out PropertyNameType nameType)
        {
            Expression result;
            if (this.nextToken is LiteralToken)
            {
                // The property name can be a string or a number or (in ES5) a keyword.
                if (((LiteralToken)this.nextToken).IsKeyword == true)
                {
                    // false, true or null.
                    result = new LiteralExpression(this.nextToken.Text);
                }
                else
                {
                    object literalValue = ((LiteralToken)this.nextToken).Value;
                    if (literalValue is string || literalValue is int)
                        result = new LiteralExpression(((LiteralToken)this.nextToken).Value.ToString());
                    else if (literalValue is double)
                        result = new LiteralExpression(((double)((LiteralToken)this.nextToken).Value).ToString(CultureInfo.InvariantCulture));
                    else
                        throw new JavaScriptException(this.engine, ErrorType.SyntaxError, string.Format("Expected property name but found {0}", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath);
                }
                nameType = PropertyNameType.Name;
            }
            else if (this.nextToken is IdentifierToken)
            {
                // An identifier is also okay.
                var name = ((IdentifierToken)this.nextToken).Name;
                if (name == "get")
                    nameType = PropertyNameType.Get;
                else if (name == "set")
                    nameType = PropertyNameType.Set;
                else
                    nameType = PropertyNameType.Name;
                result = new LiteralExpression(name);
            }
            else if (this.nextToken is KeywordToken)
            {
                // In ES5 a keyword is also okay.
                result = new LiteralExpression(((KeywordToken)this.nextToken).Name);
                nameType = PropertyNameType.Name;
            }
            else if (this.nextToken == PunctuatorToken.LeftBracket)
            {
                // ES6 computed property.
                this.Consume();
                result = ParseExpression(PunctuatorToken.RightBracket);
                nameType = PropertyNameType.Expression;
            }
            else
                throw new JavaScriptException(this.engine, ErrorType.SyntaxError, string.Format("Expected property name but found {0}", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath);

            // Consume the token.
            this.Consume();

            // Return the property name.
            return result;
        }