private AstNode ParseLeftHandSideExpression()

in AjaxMinDll/JavaScript/jsparser.cs [3746:4111]


        private AstNode ParseLeftHandSideExpression(bool isMinus)
        {
            AstNode ast = null;
            List<Context> newContexts = null;

        TryItAgain:

            // new expression
            while (m_currentToken.Is(JSToken.New))
            {
                if (null == newContexts)
                    newContexts = new List<Context>(4);
                newContexts.Add(m_currentToken.Clone());
                GetNextToken();
            }
            JSToken token = m_currentToken.Token;
            switch (token)
            {
                // primary expression
                case JSToken.Identifier:
                    ast = new Lookup(m_currentToken.Clone())
                        {
                            Name = m_scanner.Identifier
                        };
                    GetNextToken();
                    break;

                case JSToken.TemplateLiteral:
                    ast = ParseTemplateLiteral();
                    break;

                case JSToken.ConditionalCommentStart:
                    // skip past the start to the next token
                    GetNextToken();
                    if (m_currentToken.Is(JSToken.ConditionalCompilationVariable))
                    {
                        // we have /*@id
                        ast = new ConstantWrapperPP(m_currentToken.Clone())
                            {
                                VarName = m_currentToken.Code,
                                ForceComments = true
                            };

                        GetNextToken();

                        if (m_currentToken.Is(JSToken.ConditionalCommentEnd))
                        {
                            // skip past the closing comment
                            GetNextToken();
                        }
                        else
                        {
                            // we ONLY support /*@id@*/ in expressions right now. If there's not
                            // a closing comment after the ID, then we don't support it.
                            // throw an error, skip to the end of the comment, then ignore it and start
                            // looking for the next token.
                            CCTooComplicated(null);
                            goto TryItAgain;
                        }
                    }
                    else if (m_currentToken.Is(JSToken.ConditionalCommentEnd))
                    {
                        // empty conditional comment! Ignore.
                        GetNextToken();
                        goto TryItAgain;
                    }
                    else
                    {
                        // we DON'T have "/*@IDENT". We only support "/*@IDENT @*/", so since this isn't
                        // and id, throw the error, skip to the end of the comment, and ignore it
                        // by looping back and looking for the NEXT token.
                        m_currentToken.HandleError(JSError.ConditionalCompilationTooComplex);

                        // skip to end of conditional comment
                        while (m_currentToken.IsNot(JSToken.EndOfFile) && m_currentToken.IsNot(JSToken.ConditionalCommentEnd))
                        {
                            GetNextToken();
                        }
                        GetNextToken();
                        goto TryItAgain;
                    }
                    break;

                case JSToken.This:
                    ast = new ThisLiteral(m_currentToken.Clone());
                    GetNextToken();
                    break;

                case JSToken.StringLiteral:
                    ast = new ConstantWrapper(m_scanner.StringLiteralValue, PrimitiveType.String, m_currentToken.Clone())
                        {
                            MayHaveIssues = m_scanner.LiteralHasIssues
                        };
                    GetNextToken();
                    break;

                case JSToken.IntegerLiteral:
                case JSToken.NumericLiteral:
                    {
                        Context numericContext = m_currentToken.Clone();
                        double doubleValue;
                        if (ConvertNumericLiteralToDouble(m_currentToken.Code, (token == JSToken.IntegerLiteral), out doubleValue))
                        {
                            // conversion worked fine
                            // check for some boundary conditions
                            var mayHaveIssues = m_scanner.LiteralHasIssues;
                            if (doubleValue == double.MaxValue)
                            {
                                ReportError(JSError.NumericMaximum, numericContext);
                            }
                            else if (isMinus && -doubleValue == double.MinValue)
                            {
                                ReportError(JSError.NumericMinimum, numericContext);
                            }

                            // create the constant wrapper from the value
                            ast = new ConstantWrapper(doubleValue, PrimitiveType.Number, numericContext)
                                {
                                    MayHaveIssues = mayHaveIssues
                                };
                        }
                        else
                        {
                            // if we went overflow or are not a number, then we will use the "Other"
                            // primitive type so we don't try doing any numeric calcs with it. 
                            if (double.IsInfinity(doubleValue))
                            {
                                // overflow
                                // and if we ARE an overflow, report it
                                ReportError(JSError.NumericOverflow, numericContext);
                            }

                            // regardless, we're going to create a special constant wrapper
                            // that simply echos the input as-is
                            ast = new ConstantWrapper(m_currentToken.Code, PrimitiveType.Other, numericContext)
                            {
                                MayHaveIssues = true
                            };
                        }

                        GetNextToken();
                        break;
                    }

                case JSToken.True:
                    ast = new ConstantWrapper(true, PrimitiveType.Boolean, m_currentToken.Clone());
                    GetNextToken();
                    break;

                case JSToken.False:
                    ast = new ConstantWrapper(false, PrimitiveType.Boolean, m_currentToken.Clone());
                    GetNextToken();
                    break;

                case JSToken.Null:
                    ast = new ConstantWrapper(null, PrimitiveType.Null, m_currentToken.Clone());
                    GetNextToken();
                    break;

                case JSToken.ConditionalCompilationVariable:
                    ast = new ConstantWrapperPP(m_currentToken.Clone())
                        {
                            VarName = m_currentToken.Code,
                            ForceComments = false
                        };
                    GetNextToken();
                    break;

                // normally this token is not allowed on the left-hand side of an expression.
                // BUT, this might be the start of a regular expression that begins with an equals sign!
                // we need to test to see if we can parse a regular expression, and if not, THEN
                // we can fail the parse.
                case JSToken.DivideAssign:
                case JSToken.Divide:
                    // could it be a regexp?
                    ast = ScanRegularExpression();
                    if (ast != null)
                    {
                        // yup -- we're done here
                        break;
                    }

                    // nope -- go to the default branch
                    goto default;

                case JSToken.Modulo:
                    // could it be a replacement token in the format %name%? If so, we 
                    // want to treat that as a constant wrapper.
                    ast = ScanReplacementToken();
                    if (ast != null)
                    {
                        break;
                    }

                    goto default;

                // expression
                case JSToken.LeftParenthesis:
                    {
                        var leftParen = m_currentToken.Clone();
                        GetNextToken();
                        if (m_currentToken.Is(JSToken.For))
                        {
                            // generator comprehension in ES6 format
                            ast = ParseComprehension(false, leftParen, null);
                        }
                        else
                        {
                            if (m_currentToken.Is(JSToken.RightParenthesis))
                            {
                                // shortcut the empty parenthetical grouping
                                // normally not allowed; however this might be the (empty) parameter list
                                // to an arrow function.
                                // add the closing paren to the expression context
                                ast = new GroupingOperator(leftParen);
                                ast.UpdateWith(m_currentToken);
                                GetNextToken();
                            }
                            else if (m_currentToken.Is(JSToken.RestSpread))
                            {
                                // we have (...
                                // parse an assignment expression, make it the operand of a unary with the rest.
                                var restContext = m_currentToken.Clone();
                                GetNextToken();
                                ast = ParseExpression(true);
                                if (ast != null)
                                {
                                    ast = new UnaryOperator(restContext.CombineWith(ast.Context))
                                        {
                                            OperatorContext = restContext,
                                            OperatorToken = JSToken.RestSpread,
                                            Operand = ast
                                        };
                                }

                                // now, we want to continue parsing if there is a comma
                                if (m_currentToken.Is(JSToken.Comma))
                                {
                                    ast = ParseExpression(ast, false, true, JSToken.None);
                                }

                                if (m_currentToken.Is(JSToken.RightParenthesis))
                                {
                                    ast = new GroupingOperator(leftParen)
                                        {
                                            Operand = ast
                                        };
                                    ast.UpdateWith(m_currentToken);
                                    GetNextToken();
                                }
                                else
                                {
                                    ReportError(JSError.NoRightParenthesis);
                                }
                            }
                            else
                            {
                                // parse an expression
                                var operand = ParseExpression();
                                if (m_currentToken.Is(JSToken.For))
                                {
                                    // generator comprehension in Mozille format
                                    ast = ParseComprehension(false, leftParen, operand);
                                }
                                else
                                {
                                    ast = new GroupingOperator(leftParen)
                                        {
                                            Operand = operand
                                        };
                                    ast.UpdateWith(operand.Context);

                                    if (m_currentToken.IsNot(JSToken.RightParenthesis))
                                    {
                                        ReportError(JSError.NoRightParenthesis);
                                    }
                                    else
                                    {
                                        // add the closing paren to the expression context
                                        ast.UpdateWith(m_currentToken);
                                        GetNextToken();
                                    }
                                }
                            }
                        }
                    }
                    break;

                // array initializer
                case JSToken.LeftBracket:
                    ast = ParseArrayLiteral(false);
                    break;

                // object initializer
                case JSToken.LeftCurly:
                    ast = ParseObjectLiteral(false);
                    break;

                // function expression
                case JSToken.Function:
                    ast = ParseFunction(FunctionType.Expression, m_currentToken.Clone());
                    break;

                // class expression
                case JSToken.Class:
                    ast = ParseClassNode(ClassType.Expression);
                    break;

                case JSToken.AspNetBlock:
                    ast = new AspNetBlockNode(m_currentToken.Clone())
                        {
                            AspNetBlockText = m_currentToken.Code
                        };
                    GetNextToken();
                    break;

                case JSToken.Yield:
                    {
                        // TODO: not sure if this is the right place to hook for the ES6 YieldExpression semantics!
                        if (ParsedVersion == ScriptVersion.EcmaScript6 || m_settings.ScriptVersion == ScriptVersion.EcmaScript6)
                        {
                            // we already KNOW we're ES6 code, so just parse this as a yield expression.
                            // in fact, we SHOULD already know, since yield should only be used within a generator
                            // function, which for ES6 code should have the "*" generator indicator, which when
                            // parsed should have already set the ES6 flag.
                            ast = ParseYieldExpression();
                        }
                        else
                        {
                            // we need to protect against non-ES6 code using "yield" as a variable name versus the
                            // Mozilla yield syntax. We'll do that further upstream.
                            ast = new Lookup(m_currentToken.Clone())
                                {
                                    Name = "yield"
                                };
                            GetNextToken();
                        }
                    }
                    break;

                default:
                    var identifier = JSKeyword.CanBeIdentifier(m_currentToken.Token);
                    if (identifier != null)
                    {
                        ast = new Lookup(m_currentToken.Clone())
                            {
                                Name = identifier
                            };
                        GetNextToken();
                    }
                    else
                    {
                        ReportError(JSError.ExpressionExpected);
                    }
                    break;
            }

            if (m_currentToken.Is(JSToken.ArrowFunction))
            {
                ParsedVersion = ScriptVersion.EcmaScript6;
                ast = ParseArrowFunction(ast);
            }

            // can be a CallExpression, that is, followed by '.' or '(' or '['
            return ParseMemberExpression(ast, newContexts);
        }