/// <summary>
 /// Indicates that the next token is identical to the given one.  Throws an exception if
 /// this is not the case.  Consumes the token.
 /// </summary>
 /// <param name="token"> The expected token. </param>
 private void Expect(Token token)
 {
     if (this.nextToken == token)
         Consume();
     else
         throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected '{0}'", token.Text));
 }
        //     TOKEN HELPERS
        //_________________________________________________________________________________________

        /// <summary>
        /// Discards the current token and reads the next one.
        /// </summary>
        private void Consume()
        {
            do
            {
                this.nextToken = this.lexer.NextToken();
            } while ((this.nextToken is WhiteSpaceToken) == true);
        }
Beispiel #3
0
 /// <summary>
 /// Converts the token to the string suitable for embedding in an error message.
 /// </summary>
 /// <param name="token"> The token to convert.  Can be <c>null</c>. </param>
 /// <returns> A string suitable for embedding in an error message. </returns>
 public static string ToText(Token token)
 {
     if (token == null)
         return "end of input";
     return string.Format("'{0}'", token.Text);
 }
Beispiel #4
0
 /// <summary>
 /// Indicates that the next token is identical to the given one.  Throws an exception if
 /// this is not the case.  Consumes the token.
 /// </summary>
 /// <param name="token"> The expected token. </param>
 private void Expect(Token token)
 {
     if (this.nextToken == token)
         Consume();
     else
         throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected '{0}' but found {1}", token.Text, Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath);
 }
Beispiel #5
0
        /// <summary>
        /// Reads the next token from the reader.
        /// </summary>
        /// <returns> A token, or <c>null</c> if there are no more tokens. </returns>
        public Token NextToken()
        {
            int c1 = ReadNextChar();

            if (IsPunctuatorStartChar(c1) == true)
            {
                // Punctuator (puntcuation + operators).
                this.lastSignificantToken = ReadPunctuator(c1);
                return this.lastSignificantToken;
            }
            else if (IsWhiteSpace(c1) == true)
            {
                // White space.
                return ReadWhiteSpace();
            }
            else if (IsIdentifierStartChar(c1) == true)
            {
                // Identifier or reserved word.
                this.lastSignificantToken = ReadIdentifier(c1);
                return this.lastSignificantToken;
            }
            else if (IsStringLiteralStartChar(c1) == true)
            {
                // String literal.
                this.lastSignificantToken = ReadStringLiteral(c1);
                return this.lastSignificantToken;
            }
            else if (IsNumericLiteralStartChar(c1) == true)
            {
                // Number literal.
                this.lastSignificantToken = ReadNumericLiteral(c1);
                return this.lastSignificantToken;
            }
            else if (IsLineTerminator(c1) == true)
            {
                // Line Terminator.
                this.lastSignificantToken = ReadLineTerminator(c1);
                return this.lastSignificantToken;
            }
            else if (c1 == '/')
            {
                // Comment or divide or regular expression.
                this.lastSignificantToken = ReadDivideCommentOrRegularExpression();
                return this.lastSignificantToken;
            }
            else if (c1 == -1)
            {
                // End of input.
                this.lastSignificantToken = null;
                return null;
            }
            else
                throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Unexpected character '{0}'.", (char)c1), this.lineNumber, this.Source.Path);
        }
Beispiel #6
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;
        }
Beispiel #7
0
        //     TOKEN HELPERS
        //_________________________________________________________________________________________

        /// <summary>
        /// Discards the current token and reads the next one.
        /// </summary>
        /// <param name="expressionState"> Indicates whether the next token can be a literal or an
        /// operator. </param>
        private void Consume(ParserExpressionState expressionState = ParserExpressionState.Literal)
        {
            this.expressionState = expressionState;
            this.lexer.ParserExpressionState = expressionState;
            this.consumedLineTerminator = false;
            this.positionBeforeWhitespace = new SourceCodePosition(this.lexer.LineNumber, this.lexer.ColumnNumber);
            this.positionAfterWhitespace = this.positionBeforeWhitespace;
            while (true)
            {
                this.nextToken = this.lexer.NextToken();
                if ((this.nextToken is WhiteSpaceToken) == false)
                    break;
                if (((WhiteSpaceToken)this.nextToken).LineTerminatorCount > 0)
                    this.consumedLineTerminator = true;
                this.positionAfterWhitespace = new SourceCodePosition(this.lexer.LineNumber, this.lexer.ColumnNumber);
            }
        }
Beispiel #8
0
        /// <summary>
        /// Parses a function declaration or a function expression.
        /// </summary>
        /// <param name="functionType"> The type of function to parse. </param>
        /// <param name="parentScope"> The parent scope for the function. </param>
        /// <returns> A function expression. </returns>
        private FunctionExpression ParseFunction(FunctionType functionType, Scope parentScope)
        {
            if (functionType != FunctionType.Getter && functionType != FunctionType.Setter)
            {
                // Consume the function keyword.
                this.Expect(KeywordToken.Function);
            }

            // Read the function name.
            var functionName = string.Empty;
            if (functionType == FunctionType.Declaration)
            {
                functionName = this.ExpectIdentifier();
            }
            else if (functionType == FunctionType.Expression)
            {
                // The function name is optional for function expressions.
                if (this.nextToken is IdentifierToken)
                    functionName = this.ExpectIdentifier();
            }
            else if (functionType == FunctionType.Getter || functionType == FunctionType.Setter)
            {
                // Getters and setters can have any name that is allowed of a property.
                bool wasIdentifier;
                functionName = ReadPropertyName(out wasIdentifier);
            }
            else
                throw new ArgumentOutOfRangeException("functionType");
            ValidateVariableName(functionName);

            // Read the left parenthesis.
            this.Expect(PunctuatorToken.LeftParenthesis);

            // Read zero or more argument names.
            var argumentNames = new List<string>();

            // Read the first argument name.
            if (this.nextToken != PunctuatorToken.RightParenthesis)
            {
                var argumentName = this.ExpectIdentifier();
                ValidateVariableName(argumentName);
                argumentNames.Add(argumentName);
            }

            while (true)
            {
                if (this.nextToken == PunctuatorToken.Comma)
                {
                    // Consume the comma.
                    this.Consume();

                    // Read and validate the argument name.
                    var argumentName = this.ExpectIdentifier();
                    ValidateVariableName(argumentName);
                    argumentNames.Add(argumentName);
                }
                else if (this.nextToken == PunctuatorToken.RightParenthesis)
                    break;
                else
                    throw new JavaScriptException(this.engine, "SyntaxError", "Expected ',' or ')'", this.LineNumber, this.SourcePath);
            }

            // Getters must have zero arguments.
            if (functionType == FunctionType.Getter && argumentNames.Count != 0)
                throw new JavaScriptException(this.engine, "SyntaxError", "Getters cannot have arguments", this.LineNumber, this.SourcePath);

            // Setters must have one argument.
            if (functionType == FunctionType.Setter && argumentNames.Count != 1)
                throw new JavaScriptException(this.engine, "SyntaxError", "Setters must have a single argument", this.LineNumber, this.SourcePath);

            // Read the right parenthesis.
            this.Expect(PunctuatorToken.RightParenthesis);

            // Record the start of the function body.
            var startPosition = this.PositionBeforeWhitespace;

            // Since the parser reads one token in advance, start capturing the function body here.
            var bodyTextBuilder = new System.Text.StringBuilder();
            var originalBodyTextBuilder = this.lexer.InputCaptureStringBuilder;
            this.lexer.InputCaptureStringBuilder = bodyTextBuilder;

            // Read the start brace.
            this.Expect(PunctuatorToken.LeftBrace);

            // This context has a nested function.
            this.methodOptimizationHints.HasNestedFunction = true;

            // Create a new scope and assign variables within the function body to the scope.
            bool includeNameInScope = functionType != FunctionType.Getter && functionType != FunctionType.Setter;
            var scope = DeclarativeScope.CreateFunctionScope(parentScope, includeNameInScope ? functionName : string.Empty, argumentNames);

            // Read the function body.
            var functionParser = Parser.CreateFunctionBodyParser(this, scope);
            var body = functionParser.Parse();

            // Transfer state back from the function parser.
            this.nextToken = functionParser.nextToken;
            this.lexer.StrictMode = this.StrictMode;
            this.lexer.InputCaptureStringBuilder = originalBodyTextBuilder;
            if (originalBodyTextBuilder != null)
                originalBodyTextBuilder.Append(bodyTextBuilder);

            SourceCodePosition endPosition;
            if (functionType == FunctionType.Expression)
            {
                // The end token '}' will be consumed by the parent function.
                if (this.nextToken != PunctuatorToken.RightBrace)
                    throw new JavaScriptException(this.engine, "SyntaxError", "Expected '}'", this.LineNumber, this.SourcePath);

                // Record the end of the function body.
                endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1);
            }
            else
            {
                // Consume the '}'.
                this.Expect(PunctuatorToken.RightBrace);

                // Record the end of the function body.
                endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1);
            }

            // Create a new function expression.
            var options = this.options.Clone();
            options.ForceStrictMode = functionParser.StrictMode;
            var context = new FunctionMethodGenerator(this.engine, scope, functionName,
                includeNameInScope, argumentNames,
                bodyTextBuilder.ToString(0, bodyTextBuilder.Length - 1), body,
                this.SourcePath, options);
            context.MethodOptimizationHints = functionParser.methodOptimizationHints;
            return new FunctionExpression(context);
        }
Beispiel #9
0
 /// <summary>
 /// Finds a operator given a token and an indication whether the prefix or infix/postfix
 /// version is desired.
 /// </summary>
 /// <param name="token"> The token to search for. </param>
 /// <param name="postfixOrInfix"> <c>true</c> if the infix/postfix version of the operator
 /// is desired; <c>false</c> otherwise. </param>
 /// <returns> An Operator instance, or <c>null</c> if the operator could not be found. </returns>
 private static Operator OperatorFromToken(Token token, bool postfixOrInfix)
 {
     Operator result;
     if (operatorLookup == null)
     {
         // Initialize the operator lookup table.
         var temp = InitializeOperatorLookup();
         System.Threading.Thread.MemoryBarrier();
         operatorLookup = temp;
     }
     if (operatorLookup.TryGetValue(new OperatorKey() { Token = token, PostfixOrInfix = postfixOrInfix }, out result) == false)
         return null;
     return result;
 }
Beispiel #10
0
 /// <summary>
 /// Initializes a lookup table by combining the base list with a second list of keywords.
 /// </summary>
 /// <param name="additionalKeywords"> A list of additional keywords. </param>
 /// <returns> A lookup table. </returns>
 private static Dictionary<string, Token> InitializeLookupTable(Token[] additionalKeywords)
 {
     var result = new Dictionary<string, Token>(keywords.Length + additionalKeywords.Length);
     foreach (var token in keywords)
         result.Add(token.Text, token);
     foreach (var token in additionalKeywords)
         result.Add(token.Text, token);
     return result;
 }
        //     INITIALIZATION
        //_________________________________________________________________________________________

        /// <summary>
        /// Creates a new operator instance.
        /// </summary>
        /// <param name="token"> The token that corresponds to this operator.  If the operator consists
        /// of multiple tokens (e.g. ?:) then this is the first token in the sequence. </param>
        /// <param name="precedence"> An integer that indicates the order of evaluation.  Higher
        /// precedence operators are evaluated first. </param>
        /// <param name="placement"> A value that indicates where operands are allowed. </param>
        /// <param name="associativity"> Gets a value that indicates whether multiple operators of this
        /// type are grouped left-to-right or right-to-left. </param>
        /// <param name="type"> The type of operator: this decides what algorithm to use to calculate the result. </param>
        /// <param name="secondaryToken"> The second token in the sequence. </param>
        /// <param name="rhsPrecedence"> The precedence for the secondary or tertiary operand. </param>
        private Operator(Token token, int precedence, OperatorPlacement placement, OperatorAssociativity associativity, OperatorType type, Token secondaryToken = null, int rhsPrecedence = -1)
        {
            if (token == null)
                throw new ArgumentNullException("token");
            if (token == secondaryToken)
                throw new ArgumentException("Token and secondaryToken must be different.");
            if ((placement & (OperatorPlacement.HasLHSOperand | OperatorPlacement.HasRHSOperand)) == 0)
                throw new ArgumentException("An operator must take at least one operand.");
            if (secondaryToken == null && (placement & OperatorPlacement.HasSecondaryRHSOperand) != 0)
                throw new ArgumentException("HasSecondaryRHSOperand can only be specified along with a secondary token.");
            if (secondaryToken == null && (placement & OperatorPlacement.InnerOperandIsOptional) != 0)
                throw new ArgumentException("InnerOperandIsOptional can only be specified along with a secondary token.");
            if ((placement & OperatorPlacement.InnerOperandIsOptional) != 0 && (placement & OperatorPlacement.HasRHSOperand) == 0)
                throw new ArgumentException("InnerOperandIsOptional can only be specified along with HasRHSOperand.");
            this.Token = token;
            this.HasLHSOperand = (placement & OperatorPlacement.HasLHSOperand) != 0;
            this.HasRHSOperand = (placement & OperatorPlacement.HasRHSOperand) != 0;
            this.Associativity = associativity;
            this.Type = type;
            this.SecondaryToken = secondaryToken;
            this.HasSecondaryRHSOperand = (placement & OperatorPlacement.HasSecondaryRHSOperand) != 0;
            this.InnerOperandIsOptional = (placement & OperatorPlacement.InnerOperandIsOptional) != 0;
            this.Precedence = this.SecondaryPrecedence = this.TertiaryPrecedence = precedence;
            if (rhsPrecedence >= 0 && this.HasRHSOperand)
                this.SecondaryPrecedence = rhsPrecedence;
            if (rhsPrecedence >= 0 && this.HasSecondaryRHSOperand)
                this.TertiaryPrecedence = rhsPrecedence;

            // Every operator instance is stored in a list for retrieval by the parser.
            allOperators.Add(this);
        }
Beispiel #12
0
 /// <summary>
 /// Finds a operator given a token and an indication whether the prefix or infix/postfix
 /// version is desired.
 /// </summary>
 /// <param name="token"> The token to search for. </param>
 /// <param name="postfixOrInfix"> <c>true</c> if the infix/postfix version of the operator
 /// is desired; <c>false</c> otherwise. </param>
 /// <returns> An Operator instance, or <c>null</c> if the operator could not be found. </returns>
 private static Operator OperatorFromToken(Token token, bool postfixOrInfix)
 {
     Operator result;
     if (operatorLookup == null)
     {
         // Initialize the operator lookup table.
         var temp = InitializeOperatorLookup();
         System.Threading.Thread.MemoryBarrier();
         operatorLookup = temp;
     }
     if (operatorLookup.TryGetValue(new OperatorKey() { Token = token, PostfixOrInfix = postfixOrInfix }, out result) == false)
     {
         // Tagged template literals are treated like function calls.
         if (token is TemplateLiteralToken)
             return Operator.FunctionCall;
         return null;
     }
     return result;
 }
Beispiel #13
0
        /// <summary>
        /// Parses a comma-separated list of function arguments.
        /// </summary>
        /// <param name="endToken"> The token that ends parsing. </param>
        /// <returns> A list of parsed arguments. </returns>
        internal List<FunctionArgument> ParseFunctionArguments(Token endToken)
        {
            var arguments = new List<FunctionArgument>();
            if (this.nextToken != endToken)
            {
                while (true)
                {
                    // Read the argument name.
                    var argument = new FunctionArgument();
                    argument.Name = this.ExpectIdentifier();
                    ValidateVariableName(argument.Name);

                    // Check if the argument has a default value.
                    if (this.nextToken == PunctuatorToken.Assignment)
                    {
                        // Read past the assignment token.
                        Consume();

                        // Parse the expression that follows.
                        argument.DefaultValue = ParseExpression(PunctuatorToken.Comma, endToken);
                    }

                    // Add the variable to the scope so that it can be used in the default value of
                    // the next argument.  Do this *after* parsing the default value.
                    this.currentVarScope.DeclareVariable(argument.Name);

                    // Add the argument to the list.
                    arguments.Add(argument);

                    if (this.nextToken == PunctuatorToken.Comma)
                    {
                        // Consume the comma.
                        this.Consume();
                    }
                    else if (this.nextToken == endToken)
                        break;
                    else
                        throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Expected ',' or ')'", this.LineNumber, this.SourcePath);
                }
            }
            return arguments;
        }
Beispiel #14
0
        /// <summary>
        /// Parses a function declaration or a function expression.
        /// </summary>
        /// <param name="functionType"> The type of function to parse. </param>
        /// <param name="parentScope"> The parent scope for the function. </param>
        /// <param name="functionName"> The name of the function (can be empty). </param>
        /// <returns> A function expression. </returns>
        private FunctionExpression ParseFunction(FunctionDeclarationType functionType, Scope parentScope, string functionName)
        {
            // Read the left parenthesis.
            this.Expect(PunctuatorToken.LeftParenthesis);

            // Create a new scope and assign variables within the function body to the scope.
            bool includeNameInScope = functionType != FunctionDeclarationType.Getter && functionType != FunctionDeclarationType.Setter;
            var scope = DeclarativeScope.CreateFunctionScope(parentScope, includeNameInScope ? functionName : string.Empty, null);

            // Replace scope and methodOptimizationHints.
            var originalScope = this.currentVarScope;
            var originalMethodOptimizationHints = this.methodOptimizationHints;
            var newMethodOptimizationHints = new MethodOptimizationHints();
            this.methodOptimizationHints = newMethodOptimizationHints;
            this.currentVarScope = scope;

            // Read zero or more arguments.
            var arguments = ParseFunctionArguments(PunctuatorToken.RightParenthesis);

            // Restore scope and methodOptimizationHints.
            this.methodOptimizationHints = originalMethodOptimizationHints;
            this.currentVarScope = originalScope;

            // Getters must have zero arguments.
            if (functionType == FunctionDeclarationType.Getter && arguments.Count != 0)
                throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Getters cannot have arguments", this.LineNumber, this.SourcePath);

            // Setters must have one argument.
            if (functionType == FunctionDeclarationType.Setter && arguments.Count != 1)
                throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Setters must have a single argument", this.LineNumber, this.SourcePath);

            // Read the right parenthesis.
            this.Expect(PunctuatorToken.RightParenthesis);

            // Record the start of the function body.
            var startPosition = this.PositionBeforeWhitespace;

            // Since the parser reads one token in advance, start capturing the function body here.
            var bodyTextBuilder = new System.Text.StringBuilder();
            var originalBodyTextBuilder = this.lexer.InputCaptureStringBuilder;
            this.lexer.InputCaptureStringBuilder = bodyTextBuilder;

            // Read the start brace.
            this.Expect(PunctuatorToken.LeftBrace);

            // This context has a nested function.
            this.methodOptimizationHints.HasNestedFunction = true;

            // Read the function body.
            var functionParser = CreateFunctionBodyParser(this, scope, newMethodOptimizationHints);
            var body = functionParser.Parse();

            // Transfer state back from the function parser.
            this.nextToken = functionParser.nextToken;
            this.lexer.StrictMode = this.StrictMode;
            this.lexer.InputCaptureStringBuilder = originalBodyTextBuilder;
            if (originalBodyTextBuilder != null)
                originalBodyTextBuilder.Append(bodyTextBuilder);

            SourceCodePosition endPosition;
            if (functionType == FunctionDeclarationType.Expression)
            {
                // The end token '}' will be consumed by the parent function.
                if (this.nextToken != PunctuatorToken.RightBrace)
                    throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Expected '}'", this.LineNumber, this.SourcePath);

                // Record the end of the function body.
                endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1);
            }
            else
            {
                // Consume the '}'.
                this.Expect(PunctuatorToken.RightBrace);

                // Record the end of the function body.
                endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1);
            }

            // Create a new function expression.
            var options = this.options.Clone();
            options.ForceStrictMode = functionParser.StrictMode;
            var context = new FunctionMethodGenerator(this.engine, scope, functionName,
                functionType, arguments,
                bodyTextBuilder.ToString(0, bodyTextBuilder.Length - 1), body,
                this.SourcePath, options);
            context.MethodOptimizationHints = functionParser.methodOptimizationHints;
            return new FunctionExpression(context);
        }