public void Visit(JsBlock node)
        {
            if (node != null)
            {
                node.Index = NextOrderIndex;
                if (node.BlockScope == null
                    && node.Parent != null
                    && !(node.Parent is JsSwitchCase)
                    && !(node.Parent is JsFunctionObject)
                    && !(node.Parent is JsConditionalCompilationComment))
                {
                    node.BlockScope = new JsBlockScope(CurrentLexicalScope, node.Context, m_settings)
                    {
                        IsInWithScope = m_withDepth > 0
                    };
                }

                if (node.BlockScope != null)
                {
                    m_lexicalStack.Push(node.BlockScope);
                }

                try
                {
                    // recurse the block statements
                    for (var ndx = 0; ndx < node.Count; ++ndx)
                    {
                        var statement = node[ndx];
                        if (statement != null)
                        {
                            statement.Accept(this);
                        }
                    }
                }
                finally
                {
                    // be sure to reset the unreachable flag when we exit this block
                    m_isUnreachable = false;

                    if (node.BlockScope != null)
                    {
                        Debug.Assert(CurrentLexicalScope == node.BlockScope);
                        m_lexicalStack.Pop();
                    }
                }

                // now, if the block has no lex-decls, we really don't need a separate scope.
                if (node.BlockScope != null
                    && !(node.BlockScope is JsWithScope)
                    && !(node.BlockScope is JsCatchScope)
                    && node.BlockScope.LexicallyDeclaredNames.Count == 0)
                {
                    CollapseBlockScope(node.BlockScope);
                    node.BlockScope = null;
                }
            }
        }
        private static int RelocateFunction(JsBlock block, int insertAt, JsFunctionObject funcDecl)
        {
            if (block[insertAt] != funcDecl)
            {
                // technically function declarations can only be direct children of the program or a function block.
                // and since we are passing in such a block, the parent of the function declaration better be that
                // block. If it isn't, we don't want to move it because it's not in an allowed place, and different
                // browsers treat that situation differently. Some browsers would process such funcdecls as if
                // they were a direct child of the main block. Others will treat it like a function expression with
                // an external name, and only assign the function to the name if that line of code is actually
                // executed. So since there's a difference, just leave them as-is and only move valid funcdecls.
                if (funcDecl.Parent == block)
                {
                    // remove the function from it's parent, which will take it away from where it is right now.
                    funcDecl.Parent.ReplaceChild(funcDecl, null);

                    // now insert it into the block at the new location, incrementing the location so the next function
                    // will be inserted after it. It is important that they be in the same order as the source, or the semantics
                    // will change when there are functions with the same name.
                    block.Insert(insertAt++, funcDecl);
                }
            }
            else
            {
                // we're already in the right place. Just increment the pointer to move to the next position
                // for next time
                ++insertAt;
            }

            // return the new position
            return insertAt;
        }
        // unnest any child blocks
        private static void UnnestBlocks(JsBlock node)
        {
            // walk the list of items backwards -- if we come
            // to any blocks, unnest the block recursively.
            // Remove any empty statements as well.
            // We walk backwards because we could be adding any number of statements
            // and we don't want to have to modify the counter.
            for (int ndx = node.Count - 1; ndx >= 0; --ndx)
            {
                var nestedBlock = node[ndx] as JsBlock;
                if (nestedBlock != null)
                {
                    // unnest recursively
                    UnnestBlocks(nestedBlock);

                    // if the block has a block scope, then we can't really unnest it
                    // without merging lexical scopes
                    if (nestedBlock.BlockScope == null)
                    {
                        // remove the nested block
                        node.RemoveAt(ndx);

                        // then start adding the statements in the nested block to our own.
                        // go backwards so we can just keep using the same index
                        node.InsertRange(ndx, nestedBlock.Children);
                    }
                }
                else if (node[ndx] is JsEmptyStatement)
                {
                    // remove empty statements (lone semicolons)
                    node.RemoveAt(ndx);
                }
                else if (ndx > 0)
                {
                    // see if the previous node is a conditional-compilation comment, because
                    // we will also combine adjacent those
                    var previousComment = node[ndx - 1] as JsConditionalCompilationComment;
                    if (previousComment != null)
                    {
                        JsConditionalCompilationComment thisComment = node[ndx] as JsConditionalCompilationComment;
                        if (thisComment != null)
                        {
                            // two adjacent conditional comments -- combine them into the first.
                            previousComment.Statements.Append(thisComment.Statements);

                            // and remove the second one (which is now a duplicate)
                            node.RemoveAt(ndx);
                        }
                    }
                }
            }
        }
Example #4
0
        //
        // statements (we should only hit expressions)
        //

        public void Visit(JsBlock node)
        {
            Debug.Fail("shouldn't get here");
        }
        public override void Visit(JsBlock node)
        {
            if (node != null)
            {
                // there really is no point in nesting blocks that don't have any special scopes
                // attached to them. Unnest any now, before we start combining var statements.
                UnnestBlocks(node);

                // if we get here, we are going to want to optimize the curly-braces to eliminate
                // unneeded ones in all blocks except try/catch/finally. So make sure we reset the
                // force-braces properties for all blocks whose parent isn't a try-statement.
                node.ForceBraces = node.Parent is JsTryNode;

                if (m_combineAdjacentVars)
                {
                    // look at the statements in the block.
                    // if there are multiple var statements adjacent to each other, combine them.
                    // walk BACKWARDS down the list because we'll be removing items when we encounter
                    // multiple vars, etc.
                    // we also don't need to check the first one, since there is nothing before it.
                    for (int ndx = node.Count - 1; ndx > 0; --ndx)
                    {
                        // if the previous node is not a Var, then we don't need to try and combine
                        // it with the current node
                        var previousVar = node[ndx - 1] as JsVar;
                        if (previousVar != null && node[ndx] is JsVar)
                        {
                            // add the items in this VAR to the end of the previous
                            previousVar.Append(node[ndx]);

                            // delete this item from the block
                            node.RemoveAt(ndx);
                        }
                        else
                        {
                            // do the same thing for lexical declarations
                            var previousLex = node[ndx - 1] as JsLexicalDeclaration;
                            var thisLex = node[ndx] as JsLexicalDeclaration;
                            if (previousLex != null && thisLex != null)
                            {
                                // but we can only combine them if they are the same type (let or const)
                                if (previousLex.StatementToken == thisLex.StatementToken)
                                {
                                    previousLex.Append(node[ndx]);
                                    node.RemoveAt(ndx);
                                }
                            }
                            else
                            {
                                // try doing the same for const-statements: combine adjacent ones
                                var previousConst = node[ndx - 1] as JsConstStatement;
                                if (previousConst != null && node[ndx] is JsConstStatement)
                                {
                                    // they are both ConstStatements, so adding the current one to the
                                    // previous one will combine them, then delete the latter one.
                                    previousConst.Append(node[ndx]);
                                    node.RemoveAt(ndx);
                                }
                            }
                        }
                    }
                }

                // recurse down the tree after we've combined the adjacent var statements
                base.Visit(node);
            }
        }
Example #6
0
        //---------------------------------------------------------------------------------------
        // ParseTryStatement
        //
        //  TryStatement :
        //    'try' Block Catch Finally
        //
        //  Catch :
        //    <empty> | 'catch' '(' Identifier ')' Block
        //
        //  Finally :
        //    <empty> |
        //    'finally' Block
        //---------------------------------------------------------------------------------------
        private JsAstNode ParseTryStatement()
        {
            JsContext tryCtx = m_currentToken.Clone();
            JsContext catchContext = null;
            JsContext finallyContext = null;
            JsBlock body = null;
            JsContext idContext = null;
            JsBlock handler = null;
            JsBlock finally_block = null;
            RecoveryTokenException excInFinally = null;
            m_blockType.Add(BlockType.Block);
            try
            {
                bool catchOrFinally = false;
                GetNextToken();
                if (JsToken.LeftCurly != m_currentToken.Token)
                {
                    ReportError(JsError.NoLeftCurly);
                }
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_NoTrySkipTokenSet);
                try
                {
                    body = ParseBlock();
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_NoTrySkipTokenSet, exc) == -1)
                    {
                        // do nothing and just return the containing block, if any
                        throw;
                    }
                    else
                    {
                        body = exc._partiallyComputedNode as JsBlock;
                        if (body == null)
                        {
                            body = new JsBlock(exc._partiallyComputedNode.Context, this);
                            body.Append(exc._partiallyComputedNode);
                        }
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_NoTrySkipTokenSet);
                }
                if (JsToken.Catch == m_currentToken.Token)
                {
                    m_noSkipTokenSet.Add(NoSkipTokenSet.s_NoTrySkipTokenSet);
                    try
                    {
                        catchOrFinally = true;
                        catchContext = m_currentToken.Clone();
                        GetNextToken();
                        if (JsToken.LeftParenthesis != m_currentToken.Token)
                        {
                            ReportError(JsError.NoLeftParenthesis);
                        }

                        GetNextToken();
                        if (JsToken.Identifier != m_currentToken.Token)
                        {
                            string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token);
                            if (null != identifier)
                            {
                                idContext = m_currentToken.Clone();
                            }
                            else
                            {
                                ReportError(JsError.NoIdentifier);
                            }
                        }
                        else
                        {
                            idContext = m_currentToken.Clone();
                        }

                        GetNextToken();
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                        try
                        {
                            if (JsToken.RightParenthesis != m_currentToken.Token)
                            {
                                ReportError(JsError.NoRightParenthesis);
                            }
                            GetNextToken();
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                            {
                                exc._partiallyComputedNode = null;
                                // rethrow
                                throw;
                            }
                            else
                            {
                                if (m_currentToken.Token == JsToken.RightParenthesis)
                                {
                                    GetNextToken();
                                }
                            }
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                        }

                        if (JsToken.LeftCurly != m_currentToken.Token)
                        {
                            ReportError(JsError.NoLeftCurly);
                        }

                        // parse the block
                        handler = ParseBlock();

                        tryCtx.UpdateWith(handler.Context);
                    }
                    catch (RecoveryTokenException exc)
                    {
                        if (exc._partiallyComputedNode == null)
                        {
                            handler = new JsBlock(CurrentPositionContext(), this);
                        }
                        else
                        {
                            handler = exc._partiallyComputedNode as JsBlock;
                            if (handler == null)
                            {
                                handler = new JsBlock(exc._partiallyComputedNode.Context, this);
                                handler.Append(exc._partiallyComputedNode);
                            }
                        }
                        if (IndexOfToken(NoSkipTokenSet.s_NoTrySkipTokenSet, exc) == -1)
                        {
                            throw;
                        }
                    }
                    finally
                    {
                        m_noSkipTokenSet.Remove(NoSkipTokenSet.s_NoTrySkipTokenSet);
                    }
                }

                try
                {
                    if (JsToken.Finally == m_currentToken.Token)
                    {
                        finallyContext = m_currentToken.Clone();
                        GetNextToken();
                        m_blockType.Add(BlockType.Finally);
                        try
                        {
                            finally_block = ParseBlock();
                            catchOrFinally = true;
                        }
                        finally
                        {
                            m_blockType.RemoveAt(m_blockType.Count - 1);
                        }
                        tryCtx.UpdateWith(finally_block.Context);
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    excInFinally = exc; // thrown later so we can execute code below
                }

                if (!catchOrFinally)
                {
                    ReportError(JsError.NoCatch, true);
                    finally_block = new JsBlock(CurrentPositionContext(), this); // make a dummy empty block
                }
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            JsParameterDeclaration catchParameter = null;
            if (idContext != null)
            {
                catchParameter = new JsParameterDeclaration(idContext, this)
                    {
                        Name = idContext.Code
                    };
            }

            if (excInFinally != null)
            {
                excInFinally._partiallyComputedNode = new JsTryNode(tryCtx, this)
                    {
                        TryBlock = body,
                        CatchContext = catchContext,
                        CatchParameter = catchParameter,
                        CatchBlock = handler,
                        FinallyContext = finallyContext,
                        FinallyBlock = finally_block
                    };
                throw excInFinally;
            }
            return new JsTryNode(tryCtx, this)
                {
                    TryBlock = body,
                    CatchContext = catchContext,
                    CatchParameter = catchParameter,
                    CatchBlock = handler,
                    FinallyContext = finallyContext,
                    FinallyBlock = finally_block
                };
        }
Example #7
0
        //---------------------------------------------------------------------------------------
        // ParseWhileStatement
        //
        //  WhileStatement :
        //    'while' '(' Expression ')' Statement
        //---------------------------------------------------------------------------------------
        private JsWhileNode ParseWhileStatement()
        {
            JsContext whileCtx = m_currentToken.Clone();
            JsAstNode condition = null;
            JsAstNode body = null;
            m_blockType.Add(BlockType.Loop);
            try
            {
                GetNextToken();
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                {
                    ReportError(JsError.NoLeftParenthesis);
                }
                GetNextToken();
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                try
                {
                    condition = ParseExpression();
                    if (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        ReportError(JsError.NoRightParenthesis);
                        whileCtx.UpdateWith(condition.Context);
                    }
                    else
                        whileCtx.UpdateWith(m_currentToken);

                    GetNextToken();
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                    {
                        // abort the while there is really no much to do here
                        exc._partiallyComputedNode = null;
                        throw;
                    }
                    else
                    {
                        // make up a condition
                        if (exc._partiallyComputedNode != null)
                            condition = exc._partiallyComputedNode;
                        else
                            condition = new JsConstantWrapper(false, JsPrimitiveType.Boolean, CurrentPositionContext(), this);

                        if (JsToken.RightParenthesis == m_currentToken.Token)
                            GetNextToken();
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                }

                // if this is an assignment, throw a warning in case the developer
                // meant to use == instead of =
                // but no warning if the condition is wrapped in parens.
                var binOp = condition as JsBinaryOperator;
                if (binOp != null && binOp.OperatorToken == JsToken.Assign)
                {
                    condition.Context.HandleError(JsError.SuspectAssignment);
                }

                // if the statements aren't withing curly-braces, throw a possible error
                if (JsToken.LeftCurly != m_currentToken.Token)
                {
                    ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                }
                try
                {
                    // parse a Statement, not a SourceElement
                    // and ignore any important comments that spring up right here.
                    body = ParseStatement(false, true);
                }
                catch (RecoveryTokenException exc)
                {
                    if (exc._partiallyComputedNode != null)
                        body = exc._partiallyComputedNode;
                    else
                        body = new JsBlock(CurrentPositionContext(), this);

                    exc._partiallyComputedNode = new JsWhileNode(whileCtx, this)
                        {
                            Condition = condition,
                            Body = JsAstNode.ForceToBlock(body)
                        };
                    throw;
                }

            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            return new JsWhileNode(whileCtx, this)
                {
                    Condition = condition,
                    Body = JsAstNode.ForceToBlock(body)
                };
        }
        public void Visit(JsBlock node)
        {
            if (node != null)
            {
                // don't create a symbol for the root node -- it can encompass any of the input files
                var symbol = node.Parent != null ? StartSymbol(node) : null;

                if (node.Parent != null)
                {
                    // not the root block.
                    // if the parent is a function node, we will need a "use strict" directive
                    // if the function's scope is strict but the parent scope is not
                    var parentFunction = node.Parent as JsFunctionObject;
                    if (parentFunction != null
                        && parentFunction.FunctionScope.UseStrict
                        && !parentFunction.FunctionScope.Parent.UseStrict)
                    {
                        m_needsStrictDirective = true;
                    }

                    // always enclose in curly-braces
                    OutputPossibleLineBreak('{');
                    SetContextOutputPosition(node.Context);
                    MarkSegment(node, null, node.Context);
                    Indent();
                }
                else
                {
                    // root block.
                    // we will need a "use strict" directive IF this scope is strict and we
                    // haven't already gone past where we can insert global directive prologues
                    m_needsStrictDirective = node.EnclosingScope.UseStrict && !m_doneWithGlobalDirectives;
                }

                JsAstNode prevStatement = null;
                for (var ndx = 0; ndx < node.Count; ++ndx)
                {
                    var statement = node[ndx];
                    if (statement != null && !statement.HideFromOutput)
                    {
                        if (prevStatement != null && prevStatement.RequiresSeparator)
                        {
                            OutputPossibleLineBreak(';');
                            MarkSegment(prevStatement, null, prevStatement.TerminatingContext);
                        }

                        if (!(statement is JsDirectivePrologue))
                        {
                            if (m_needsStrictDirective)
                            {
                                // we need a strict directive, but we didn't have one.
                                // add it now
                                Output("\"use strict\";");
                                m_needsStrictDirective = false;
                            }

                            m_doneWithGlobalDirectives = true;
                        }

                        NewLine();
                        m_startOfStatement = true;
                        statement.Accept(this);
                        prevStatement = statement;
                    }
                }

                if (node.Parent != null)
                {
                    Unindent();

                    // if there weren't any statements, the curly-braces will be on the same line.
                    // otherwise we want them on a new line
                    if (node.Count > 0)
                    {
                        NewLine();
                    }

                    OutputPossibleLineBreak('}');
                    MarkSegment(node, null, node.Context);
                }
                else if (prevStatement != null && prevStatement.RequiresSeparator && m_settings.TermSemicolons)
                {
                    // this is the root block (parent is null) and we want to make sure we end
                    // with a terminating semicolon, so don't replace it
                    OutputPossibleLineBreak(';');
                    MarkSegment(prevStatement, null, prevStatement.TerminatingContext);
                }

                if (symbol != null)
                {
                    EndSymbol(symbol);
                }
            }
        }
 public JsConditionalCompilationComment(JsContext context, JsParser parser)
     : base(context, parser)
 {
     Statements = new JsBlock(null, parser);
 }
 /// <summary>
 /// outputs a semicolon for an empty block, just the statement for a single-statement block,
 /// and recurses to the Block visitor for mutiple-statement blocks
 /// </summary>
 /// <param name="block">block to output</param>
 private void OutputBlock(JsBlock block)
 {
     if (block != null && block.ForceBraces)
     {
         // always output the braces
         OutputBlockWithBraces(block);
     }
     else if (block == null || block.Count == 0)
     {
         // semicolon-replacement cannot generate an empty statement
         OutputPossibleLineBreak(';');
         MarkSegment(block, null, block.IfNotNull(b => b.Context));
     }
     else if (block.Count == 1)
     {
         Indent();
         NewLine();
         if (block[0].HideFromOutput)
         {
             // semicolon-replacement cannot generate an empty statement
             OutputPossibleLineBreak(';');
             MarkSegment(block, null, block.Context);
         }
         else if (block[0] is JsImportantComment)
         {
             // not a REAL statement, so follow the comment with a semicolon to
             // be the actual statement for this block.
             block[0].Accept(this);
             OutputPossibleLineBreak(';');
             MarkSegment(block, null, block.Context);
         }
         else
         {
             m_startOfStatement = true;
             block[0].Accept(this);
         }
         Unindent();
     }
     else
     {
         // always output the braces
         OutputBlockWithBraces(block);
     }
 }
        private void OutputBlockWithBraces(JsBlock block)
        {
            if (m_settings.BlocksStartOnSameLine == MinifierBlockStart.NewLine
                || (m_settings.BlocksStartOnSameLine == MinifierBlockStart.UseSource && block.BraceOnNewLine))
            {
                NewLine();
            }
            else if (m_settings.OutputMode == MinifierOutputMode.MultipleLines)
            {
                OutputPossibleLineBreak(' ');
            }

            block.Accept(this);
        }
 private void CombineWithPreviousExpression(JsBlock node, int ndx)
 {
     JsIfNode ifNode;
     JsForNode forNode;
     JsWhileNode whileNode;
     JsReturnNode returnNode;
     if (node[ndx].IsExpression)
     {
         CombineTwoExpressions(node, ndx);
     }
     else if ((returnNode = node[ndx] as JsReturnNode) != null)
     {
         CombineReturnWithExpression(node, ndx, returnNode);
     }
     else if ((forNode = node[ndx] as JsForNode) != null)
     {
         CombineForNodeWithExpression(node, ndx, forNode);
     }
     else if ((ifNode = node[ndx] as JsIfNode) != null)
     {
         // transform: expr;if(cond)... => if(expr,cond)...
         // combine the previous expression with the if-condition via comma, then delete
         // the previous statement.
         ifNode.Condition = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], ifNode.Condition);
         node.RemoveAt(ndx - 1);
     }
     else if ((whileNode = node[ndx] as JsWhileNode) != null
         && m_parser.Settings.IsModificationAllowed(JsTreeModifications.ChangeWhileToFor))
     {
         // transform: expr;while(cond)... => for(expr;cond;)...
         // zero-sum, and maybe a little worse for performance because of the nop iterator,
         // but combines two statements into one, which may have savings later on.
         var initializer = node[ndx - 1];
         node[ndx] = new JsForNode(null, m_parser)
         {
             Initializer = initializer,
             Condition = whileNode.Condition,
             Body = whileNode.Body
         };
         node.RemoveAt(ndx - 1);
     }
 }
        private void CombineTwoExpressions(JsBlock node, int ndx)
        {
            var prevBinary = node[ndx - 1] as JsBinaryOperator;
            var curBinary = node[ndx] as JsBinaryOperator;
            JsLookup lookup;
            if (prevBinary != null
                && curBinary != null
                && prevBinary.IsAssign
                && curBinary.IsAssign
                && curBinary.OperatorToken != JsToken.Assign
                && (lookup = curBinary.Operand1 as JsLookup) != null
                && prevBinary.Operand1.IsEquivalentTo(curBinary.Operand1))
            {
                if (prevBinary.OperatorToken == JsToken.Assign)
                {
                    // transform: lookup=expr1;lookup[OP]=expr2;  ==>  lookup=expr1[OP]expr2
                    var binOp = new JsBinaryOperator(prevBinary.Operand2.Context.Clone().CombineWith(curBinary.Operand2.Context), prevBinary.Parser)
                    {
                        Operand1 = prevBinary.Operand2,
                        Operand2 = curBinary.Operand2,
                        OperatorToken = JsScanner.StripAssignment(curBinary.OperatorToken),
                        OperatorContext = curBinary.OperatorContext
                    };
                    prevBinary.Operand2 = binOp;

                    // we are removing the second lookup, so clean up the reference on the field
                    if (lookup.VariableField != null)
                    {
                        lookup.VariableField.References.Remove(lookup);
                    }

                    // and remove the current assignment expression (everything was combined into the previous)
                    node[ndx] = null;
                }
                else
                {
                    // there's lots of ins-and-outs in terms of strings versus numerics versus precedence and all
                    // sorts of stuff. I need to iron this out a little better, but until then, just combine with a comma.
                    // transform: expr1;expr2  ==>  expr1,expr2
                    var binOp = JsCommaOperator.CombineWithComma(prevBinary.Context.Clone().CombineWith(curBinary.Context), m_parser, prevBinary, curBinary);

                    // replace the previous node and delete the current
                    node[ndx - 1] = binOp;
                    node[ndx] = null;
                }
            }
            else
            {
                // transform: expr1;expr2 to expr1,expr2
                // use the special comma operator object so we can handle it special
                // and don't create stack-breakingly deep trees
                var binOp = JsCommaOperator.CombineWithComma(node[ndx - 1].Context.Clone().CombineWith(node[ndx].Context), m_parser, node[ndx - 1], node[ndx]);

                // replace the current node and delete the previous
                node[ndx] = binOp;
                node[ndx - 1] = null;
            }
        }
        private void CombineReturnWithExpression(JsBlock node, int ndx, JsReturnNode returnNode)
        {
            // see if the return node has an expression operand
            if (returnNode.Operand != null && returnNode.Operand.IsExpression)
            {
                // check for lookup[ASSIGN]expr2;return expr1.
                var beforeExpr = node[ndx - 1] as JsBinaryOperator;
                JsLookup lookup;
                if (beforeExpr != null
                    && beforeExpr.IsAssign
                    && (lookup = beforeExpr.Operand1 as JsLookup) != null)
                {
                    if (returnNode.Operand.IsEquivalentTo(lookup))
                    {
                        // we have lookup[ASSIGN]expr2;return lookup.
                        // if lookup is a local variable in the current scope, we can replace with return expr2;
                        // if lookup is an outer reference, we can replace with return lookup[ASSIGN]expr2
                        if (beforeExpr.OperatorToken == JsToken.Assign)
                        {
                            // check to see if lookup is in the current scope from which we are returning
                            if (lookup.VariableField == null
                                || lookup.VariableField.OuterField != null
                                || lookup.VariableField.IsReferencedInnerScope)
                            {
                                // transform: lookup[ASSIGN]expr2;return lookup => return lookup[ASSIGN]expr2
                                // lookup points to outer field (or we don't know)
                                // replace the operand on the return node with the previous expression and
                                // delete the previous node.
                                // first be sure to remove the lookup in the return operand from the references
                                // to field.
                                JsDetachReferences.Apply(returnNode.Operand);
                                returnNode.Operand = beforeExpr;
                                node[ndx - 1] = null;
                            }
                            else
                            {
                                // transform: lookup[ASSIGN]expr2;return lookup => return expr2
                                // lookup is a variable local to the current scope, so when we return, the
                                // variable won't exists anymore anyway.
                                // replace the operand on the return node oprand with the right-hand operand of the
                                // previous expression and delete the previous node.
                                // we're eliminating the two lookups altogether, so remove them both from the
                                // field's reference table.
                                var varField = lookup.VariableField;
                                JsDetachReferences.Apply(lookup, returnNode.Operand);

                                returnNode.Operand = beforeExpr.Operand2;
                                node[ndx - 1] = null;

                                // now that we've eliminated the two lookups, see if the local variable isn't
                                // referenced anymore. If it isn't, we might be able to remove the variable, too.
                                // (need to pick up those changes to keep track of a field's declarations, though)
                                if (varField.RefCount == 0)
                                {
                                    // it's not. if there's only one declaration and it either has no initializer or
                                    // is initialized to a constant, get rid of it.
                                    var nameDecl = varField.OnlyDeclaration;
                                    if (nameDecl != null)
                                    {
                                        // we only had one declaration.
                                        if (nameDecl.Initializer == null || nameDecl.Initializer.IsConstant)
                                        {
                                            // and it either had no initializer or it was initialized to a constant.
                                            // but it has no references, so let's whack it. Actually, only if it was
                                            // a var-decl (leave parameter and function decls alone).
                                            var varDecl = nameDecl as JsVariableDeclaration;
                                            if (varDecl != null)
                                            {
                                                // save the declaration parent (var, const, or let) and remove the
                                                // child vardecl from its list
                                                var declStatement = varDecl.Parent as JsDeclaration;
                                                declStatement.Remove(varDecl);
                                                varField.WasRemoved = true;

                                                // if the parent statement is now empty, remove it, too. this will
                                                // move everything up one index, but that'll just mean an extra loop.
                                                if (declStatement.Count == 0)
                                                {
                                                    declStatement.Parent.ReplaceChild(declStatement, null);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            // it's an assignment, but it's not =. That means it's one of the OP= operators.
                            // we can't remove the field altogether. But we can move the assignment into the
                            // return statement and get rid of the lone lookup.
                            // transform: lookup OP= expr;return lookup   =>   return lookup OP= expr;
                            if (lookup.VariableField != null)
                            {
                                // we're getting rid of the lookup, so remove it from the field's list of references
                                JsDetachReferences.Apply(returnNode.Operand);
                            }

                            // remove the expression from the block and put it in the operand of
                            // the return statement.
                            node.RemoveAt(ndx - 1);
                            returnNode.Operand = beforeExpr;

                            // is this field scoped only to this function?
                            if (lookup.VariableField != null
                                && lookup.VariableField.OuterField == null
                                && !lookup.VariableField.IsReferencedInnerScope)
                            {
                                // in fact, the lookup is in the current scope, so assigning to it is a waste
                                // because we're going to return (this is a return statement, after all).
                                // we can get rid of the assignment part and just keep the operator:
                                // transform: lookup OP= expr;return lookup   =>   return lookup OP expr;
                                beforeExpr.OperatorToken = JsScanner.StripAssignment(beforeExpr.OperatorToken);
                            }
                        }
                    }
                    else
                    {
                        // transform: expr1;return expr2 to return expr1,expr2
                        var binOp = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], returnNode.Operand);

                        // replace the operand on the return node with the new expression and
                        // delete the previous node
                        returnNode.Operand = binOp;
                        node[ndx - 1] = null;
                    }
                }
                else
                {
                    // transform: expr1;return expr2 to return expr1,expr2
                    var binOp = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], returnNode.Operand);

                    // replace the operand on the return node with the new expression and
                    // delete the previous node
                    returnNode.Operand = binOp;
                    node[ndx - 1] = null;
                }
            }
        }
        private void CombineForNodeWithExpression(JsBlock node, int ndx, JsForNode forNode)
        {
            // if we aren't allowing in-operators to be moved into for-statements, then
            // first check to see if that previous expression statement is free of in-operators
            // before trying to move it.
            if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.MoveInExpressionsIntoForStatement)
                || !node[ndx - 1].ContainsInOperator)
            {
                if (forNode.Initializer == null)
                {
                    // transform: expr1;for(;...) to for(expr1;...)
                    // simply move the previous expression to the for-statement's initializer
                    forNode.Initializer = node[ndx - 1];
                    node[ndx - 1] = null;
                }
                else if (forNode.Initializer.IsExpression)
                {
                    // transform: expr1;for(expr2;...) to for(expr1,expr2;...)
                    var binOp = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], forNode.Initializer);

                    // replace the initializer with the new binary operator and remove the previous node
                    forNode.Initializer = binOp;
                    node[ndx - 1] = null;
                }
            }
        }
 public void Visit(JsBlock node)
 {
     // not applicable; terminate
 }
Example #17
0
        private JsAstNode ParseSwitchStatement()
        {
            JsContext switchCtx = m_currentToken.Clone();
            JsAstNode expr = null;
            JsAstNodeList cases = null;
            var braceOnNewLine = false;
            JsContext braceContext = null;
            m_blockType.Add(BlockType.Switch);
            try
            {
                // read switch(expr)
                GetNextToken();
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                    ReportError(JsError.NoLeftParenthesis);
                GetNextToken();
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                try
                {
                    expr = ParseExpression();

                    if (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        ReportError(JsError.NoRightParenthesis);
                    }

                    GetNextToken();
                    if (JsToken.LeftCurly != m_currentToken.Token)
                    {
                        ReportError(JsError.NoLeftCurly);
                    }

                    braceOnNewLine = m_foundEndOfLine;
                    braceContext = m_currentToken.Clone();
                    GetNextToken();

                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1
                          && IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1)
                    {
                        // give up
                        exc._partiallyComputedNode = null;
                        throw;
                    }
                    else
                    {
                        if (exc._partiallyComputedNode == null)
                            expr = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this);
                        else
                            expr = exc._partiallyComputedNode;

                        if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) != -1)
                        {
                            if (exc._token == JsToken.RightParenthesis)
                                GetNextToken();

                            if (JsToken.LeftCurly != m_currentToken.Token)
                            {
                                ReportError(JsError.NoLeftCurly);
                            }
                            braceOnNewLine = m_foundEndOfLine;
                            braceContext = m_currentToken.Clone();
                            GetNextToken();
                        }

                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                }

                // parse the switch body
                cases = new JsAstNodeList(CurrentPositionContext(), this);
                bool defaultStatement = false;
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                try
                {
                    while (JsToken.RightCurly != m_currentToken.Token)
                    {
                        JsSwitchCase caseClause = null;
                        JsAstNode caseValue = null;
                        var caseCtx = m_currentToken.Clone();
                        JsContext colonContext = null;
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_CaseNoSkipTokenSet);
                        try
                        {
                            if (JsToken.Case == m_currentToken.Token)
                            {
                                // get the case
                                GetNextToken();
                                caseValue = ParseExpression();
                            }
                            else if (JsToken.Default == m_currentToken.Token)
                            {
                                // get the default
                                if (defaultStatement)
                                {
                                    // we report an error but we still accept the default
                                    ReportError(JsError.DupDefault, true);
                                }
                                else
                                {
                                    defaultStatement = true;
                                }
                                GetNextToken();
                            }
                            else
                            {
                                // This is an error, there is no case or default. Assume a default was missing and keep going
                                defaultStatement = true;
                                ReportError(JsError.BadSwitch);
                            }

                            if (JsToken.Colon != m_currentToken.Token)
                            {
                                ReportError(JsError.NoColon);
                            }
                            else
                            {
                                colonContext = m_currentToken.Clone();
                            }

                            // read the statements inside the case or default
                            GetNextToken();
                        }
                        catch (RecoveryTokenException exc)
                        {
                            // right now we can only get here for the 'case' statement
                            if (IndexOfToken(NoSkipTokenSet.s_CaseNoSkipTokenSet, exc) == -1)
                            {
                                // ignore the current case or default
                                exc._partiallyComputedNode = null;
                                throw;
                            }
                            else
                            {
                                caseValue = exc._partiallyComputedNode;

                                if (exc._token == JsToken.Colon)
                                {
                                    GetNextToken();
                                }
                            }
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_CaseNoSkipTokenSet);
                        }

                        m_blockType.Add(BlockType.Block);
                        try
                        {
                            var statements = new JsBlock(m_currentToken.Clone(), this);
                            m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                            m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                            try
                            {
                                while (JsToken.RightCurly != m_currentToken.Token && JsToken.Case != m_currentToken.Token && JsToken.Default != m_currentToken.Token)
                                {
                                    try
                                    {
                                        // parse a Statement, not a SourceElement
                                        statements.Append(ParseStatement(false));
                                    }
                                    catch (RecoveryTokenException exc)
                                    {
                                        if (exc._partiallyComputedNode != null)
                                        {
                                            statements.Append(exc._partiallyComputedNode);
                                            exc._partiallyComputedNode = null;
                                        }

                                        if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                        {
                                            throw;
                                        }
                                    }
                                }
                            }
                            catch (RecoveryTokenException exc)
                            {
                                if (IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1)
                                {
                                    caseClause = new JsSwitchCase(caseCtx, this)
                                        {
                                            CaseValue = caseValue,
                                            ColonContext = colonContext,
                                            Statements = statements
                                        };
                                    cases.Append(caseClause);
                                    throw;
                                }
                            }
                            finally
                            {
                                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                            }

                            caseCtx.UpdateWith(statements.Context);
                            caseClause = new JsSwitchCase(caseCtx, this)
                                {
                                    CaseValue = caseValue,
                                    ColonContext = colonContext,
                                    Statements = statements
                                };
                            cases.Append(caseClause);
                        }
                        finally
                        {
                            m_blockType.RemoveAt(m_blockType.Count - 1);
                        }
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        //save what you can a rethrow
                        switchCtx.UpdateWith(CurrentPositionContext());
                        exc._partiallyComputedNode = new JsSwitch(switchCtx, this)
                            {
                                Expression = expr,
                                BraceContext = braceContext,
                                Cases = cases,
                                BraceOnNewLine = braceOnNewLine
                            };
                        throw;
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                }
                switchCtx.UpdateWith(m_currentToken);
                GetNextToken();
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            return new JsSwitch(switchCtx, this)
                {
                    Expression = expr,
                    BraceContext = braceContext,
                    Cases = cases,
                    BraceOnNewLine = braceOnNewLine
                };
        }
Example #18
0
        //---------------------------------------------------------------------------------------
        // ParseBlock
        //
        //  Block :
        //    '{' OptionalStatements '}'
        //---------------------------------------------------------------------------------------
        JsBlock ParseBlock()
        {
            m_blockType.Add(BlockType.Block);

            // set the force-braces property to true because we are assuming this is only called
            // when we encounter a left-brace and we will want to keep it going forward. If we are optimizing
            // the code, we will reset these properties as we encounter them so that unneeded curly-braces
            // can be removed.
            JsBlock codeBlock = new JsBlock(m_currentToken.Clone(), this)
                {
                    ForceBraces = true
                };
            codeBlock.BraceOnNewLine = m_foundEndOfLine;
            GetNextToken();

            m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
            m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
            try
            {
                try
                {
                    while (JsToken.RightCurly != m_currentToken.Token)
                    {
                        try
                        {
                            // pass false because we really only want Statements, and FunctionDeclarations
                            // are technically not statements. We'll still handle them, but we'll issue a warning.
                            codeBlock.Append(ParseStatement(false));
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (exc._partiallyComputedNode != null)
                                codeBlock.Append(exc._partiallyComputedNode);
                            if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                    }

                    // make sure any important comments before the closing brace are kept
                    AppendImportantComments(codeBlock);
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = codeBlock;
                        throw;
                    }
                }
            }
            finally
            {
                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            codeBlock.TerminatingContext = m_currentToken.Clone();
            // update the block context
            codeBlock.Context.UpdateWith(m_currentToken);
            GetNextToken();
            return codeBlock;
        }
Example #19
0
        private void AppendImportantComments(JsBlock block)
        {
            if (block != null)
            {
                // make sure any important comments before the closing brace are kept
                if (m_importantComments.Count > 0
                    && m_settings.PreserveImportantComments
                    && m_settings.IsModificationAllowed(JsTreeModifications.PreserveImportantComments))
                {
                    // we have important comments before the EOF. Add the comment(s) to the program.
                    foreach (var importantComment in m_importantComments)
                    {
                        block.Append(new JsImportantComment(importantComment, this));
                    }

                    m_importantComments.Clear();
                }
            }
        }
Example #20
0
        //---------------------------------------------------------------------------------------
        // ParseDoStatement
        //
        //  DoStatement:
        //    'do' Statement 'while' '(' Expression ')'
        //---------------------------------------------------------------------------------------
        private JsDoWhile ParseDoStatement()
        {
            var doCtx = m_currentToken.Clone();
            JsContext whileContext = null;
            JsContext terminatorContext = null;
            JsAstNode body = null;
            JsAstNode condition = null;
            m_blockType.Add(BlockType.Loop);
            try
            {
                GetNextToken();
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet);
                // if the statements aren't withing curly-braces, throw a possible error
                if (JsToken.LeftCurly != m_currentToken.Token)
                {
                    ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                }
                try
                {
                    // parse a Statement, not a SourceElement
                    // and ignore any important comments that spring up right here.
                    body = ParseStatement(false, true);
                }
                catch (RecoveryTokenException exc)
                {
                    // make up a block for the do while
                    if (exc._partiallyComputedNode != null)
                        body = exc._partiallyComputedNode;
                    else
                        body = new JsBlock(CurrentPositionContext(), this);
                    if (IndexOfToken(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet, exc) == -1)
                    {
                        // we have to pass the exception to someone else, make as much as you can from the 'do while'
                        exc._partiallyComputedNode = new JsDoWhile(doCtx.UpdateWith(CurrentPositionContext()), this)
                            {
                                Body = JsAstNode.ForceToBlock(body),
                                Condition = new JsConstantWrapper(false, JsPrimitiveType.Boolean, CurrentPositionContext(), this)
                            };
                        throw;
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet);
                }

                if (JsToken.While != m_currentToken.Token)
                {
                    ReportError(JsError.NoWhile);
                }

                whileContext = m_currentToken.Clone();
                doCtx.UpdateWith(whileContext);
                GetNextToken();

                if (JsToken.LeftParenthesis != m_currentToken.Token)
                {
                    ReportError(JsError.NoLeftParenthesis);
                }

                GetNextToken();
                // catch here so the body of the do_while is not thrown away
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                try
                {
                    condition = ParseExpression();
                    if (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        ReportError(JsError.NoRightParenthesis);
                        doCtx.UpdateWith(condition.Context);
                    }
                    else
                    {
                        doCtx.UpdateWith(m_currentToken);
                    }

                    GetNextToken();
                }
                catch (RecoveryTokenException exc)
                {
                    // make up a condition
                    if (exc._partiallyComputedNode != null)
                        condition = exc._partiallyComputedNode;
                    else
                        condition = new JsConstantWrapper(false, JsPrimitiveType.Boolean, CurrentPositionContext(), this);

                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = new JsDoWhile(doCtx, this)
                            {
                                Body = JsAstNode.ForceToBlock(body),
                                WhileContext = whileContext,
                                Condition = condition
                            };
                        throw;
                    }
                    else
                    {
                        if (JsToken.RightParenthesis == m_currentToken.Token)
                            GetNextToken();
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                }
                if (JsToken.Semicolon == m_currentToken.Token)
                {
                    // JScript 5 allowed statements like
                    //   do{print(++x)}while(x<10) print(0)
                    // even though that does not strictly follow the automatic semicolon insertion
                    // rules for the required semi after the while().  For backwards compatibility
                    // we should continue to support this.
                    terminatorContext = m_currentToken.Clone();
                    GetNextToken();
                }
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            // if this is an assignment, throw a warning in case the developer
            // meant to use == instead of =
            // but no warning if the condition is wrapped in parens.
            var binOp = condition as JsBinaryOperator;
            if (binOp != null && binOp.OperatorToken == JsToken.Assign)
            {
                condition.Context.HandleError(JsError.SuspectAssignment);
            }

            return new JsDoWhile(doCtx, this)
                {
                    Body = JsAstNode.ForceToBlock(body),
                    WhileContext = whileContext,
                    Condition = condition,
                    TerminatingContext = terminatorContext
                };
        }
Example #21
0
        //---------------------------------------------------------------------------------------
        // ParseWithStatement
        //
        //  WithStatement :
        //    'with' '(' Expression ')' Statement
        //---------------------------------------------------------------------------------------
        private JsWithNode ParseWithStatement()
        {
            JsContext withCtx = m_currentToken.Clone();
            JsAstNode obj = null;
            JsBlock block = null;
            m_blockType.Add(BlockType.Block);
            try
            {
                GetNextToken();
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                    ReportError(JsError.NoLeftParenthesis);
                GetNextToken();
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                try
                {
                    obj = ParseExpression();
                    if (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        withCtx.UpdateWith(obj.Context);
                        ReportError(JsError.NoRightParenthesis);
                    }
                    else
                        withCtx.UpdateWith(m_currentToken);
                    GetNextToken();
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                    {
                        // give up
                        exc._partiallyComputedNode = null;
                        throw;
                    }
                    else
                    {
                        if (exc._partiallyComputedNode == null)
                            obj = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this);
                        else
                            obj = exc._partiallyComputedNode;
                        withCtx.UpdateWith(obj.Context);

                        if (exc._token == JsToken.RightParenthesis)
                            GetNextToken();
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                }

                // if the statements aren't withing curly-braces, throw a possible error
                if (JsToken.LeftCurly != m_currentToken.Token)
                {
                    ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                }

                try
                {
                    // parse a Statement, not a SourceElement
                    // and ignore any important comments that spring up right here.
                    JsAstNode statement = ParseStatement(false, true);

                    // but make sure we save it as a block
                    block = statement as JsBlock;
                    if (block == null)
                    {
                        block = new JsBlock(statement.Context, this);
                        block.Append(statement);
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    if (exc._partiallyComputedNode == null)
                    {
                        block = new JsBlock(CurrentPositionContext(), this);
                    }
                    else
                    {
                        block = exc._partiallyComputedNode as JsBlock;
                        if (block == null)
                        {
                            block = new JsBlock(exc._partiallyComputedNode.Context, this);
                            block.Append(exc._partiallyComputedNode);
                        }
                    }
                    exc._partiallyComputedNode = new JsWithNode(withCtx, this)
                        {
                            WithObject = obj,
                            Body = block
                        };
                    throw;
                }
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            return new JsWithNode(withCtx, this)
                {
                    WithObject = obj,
                    Body = block
                };
        }
Example #22
0
        private JsAstNode ParseForStatement()
        {
            m_blockType.Add(BlockType.Loop);
            JsAstNode forNode = null;
            try
            {
                JsContext forCtx = m_currentToken.Clone();
                GetNextToken();
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                {
                    ReportError(JsError.NoLeftParenthesis);
                }

                GetNextToken();
                bool isForIn = false, recoveryInForIn = false;
                JsAstNode lhs = null, initializer = null, condOrColl = null, increment = null;
                JsContext operatorContext = null;
                JsContext separator1Context = null;
                JsContext separator2Context = null;

                try
                {
                    if (JsToken.Var == m_currentToken.Token
                        || JsToken.Let == m_currentToken.Token
                        || JsToken.Const == m_currentToken.Token)
                    {
                        isForIn = true;
                        JsDeclaration declaration;
                        if (m_currentToken.Token == JsToken.Var)
                        {
                            declaration = new JsVar(m_currentToken.Clone(), this);
                        }
                        else
                        {
                            declaration = new JsLexicalDeclaration(m_currentToken.Clone(), this)
                                {
                                    StatementToken = m_currentToken.Token
                                };
                        }

                        declaration.Append(ParseIdentifierInitializer(JsToken.In));

                        // a list of variable initializers is allowed only in a for(;;)
                        while (JsToken.Comma == m_currentToken.Token)
                        {
                            isForIn = false;
                            declaration.Append(ParseIdentifierInitializer(JsToken.In));
                            //initializer = new Comma(initializer.context.CombineWith(var.context), initializer, var);
                        }

                        initializer = declaration;

                        // if it could still be a for..in, now it's time to get the 'in'
                        // TODO: for ES6 might be 'of'
                        if (isForIn)
                        {
                            if (JsToken.In == m_currentToken.Token
                                || (m_currentToken.Token == JsToken.Identifier && string.CompareOrdinal(m_currentToken.Code, "of") == 0))
                            {
                                operatorContext = m_currentToken.Clone();
                                GetNextToken();
                                condOrColl = ParseExpression();
                            }
                            else
                            {
                                isForIn = false;
                            }
                        }
                    }
                    else
                    {
                        if (JsToken.Semicolon != m_currentToken.Token)
                        {
                            bool isLHS;
                            initializer = ParseUnaryExpression(out isLHS, false);
                            if (isLHS && (JsToken.In == m_currentToken.Token
                                || (m_currentToken.Token == JsToken.Identifier && string.CompareOrdinal(m_currentToken.Code, "of") == 0)))
                            {
                                isForIn = true;
                                operatorContext = m_currentToken.Clone();

                                lhs = initializer;
                                initializer = null;
                                GetNextToken();
                                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                                try
                                {
                                    condOrColl = ParseExpression();
                                }
                                catch (RecoveryTokenException exc)
                                {
                                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                                    {
                                        exc._partiallyComputedNode = null;
                                        throw;
                                    }
                                    else
                                    {
                                        if (exc._partiallyComputedNode == null)
                                            condOrColl = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); // what could we put here?
                                        else
                                            condOrColl = exc._partiallyComputedNode;
                                    }
                                    if (exc._token == JsToken.RightParenthesis)
                                    {
                                        GetNextToken();
                                        recoveryInForIn = true;
                                    }
                                }
                                finally
                                {
                                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                                }
                            }
                            else
                            {
                                initializer = ParseExpression(initializer, false, isLHS, JsToken.In);
                            }
                        }
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    // error is too early abort for
                    exc._partiallyComputedNode = null;
                    throw;
                }

                // at this point we know whether or not is a for..in
                if (isForIn)
                {
                    if (!recoveryInForIn)
                    {
                        if (JsToken.RightParenthesis != m_currentToken.Token)
                            ReportError(JsError.NoRightParenthesis);
                        forCtx.UpdateWith(m_currentToken);
                        GetNextToken();
                    }

                    JsAstNode body = null;
                    // if the statements aren't withing curly-braces, throw a possible error
                    if (JsToken.LeftCurly != m_currentToken.Token)
                    {
                        ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                    }
                    try
                    {
                        // parse a Statement, not a SourceElement
                        // and ignore any important comments that spring up right here.
                        body = ParseStatement(false, true);
                    }
                    catch (RecoveryTokenException exc)
                    {
                        if (exc._partiallyComputedNode == null)
                            body = new JsBlock(CurrentPositionContext(), this);
                        else
                            body = exc._partiallyComputedNode;
                        exc._partiallyComputedNode = new JsForIn(forCtx, this)
                            {
                                Variable = (lhs != null ? lhs : initializer),
                                OperatorContext = operatorContext,
                                Collection = condOrColl,
                                Body = JsAstNode.ForceToBlock(body),
                            };
                        throw;
                    }

                    // for (a in b)
                    //      lhs = a, initializer = null
                    // for (var a in b)
                    //      lhs = null, initializer = var a
                    forNode = new JsForIn(forCtx, this)
                        {
                            Variable = (lhs != null ? lhs : initializer),
                            OperatorContext = operatorContext,
                            Collection = condOrColl,
                            Body = JsAstNode.ForceToBlock(body),
                        };
                }
                else
                {
                    m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                    try
                    {
                        if (JsToken.Semicolon == m_currentToken.Token)
                        {
                            separator1Context = m_currentToken.Clone();
                        }
                        else
                        {
                            ReportError(JsError.NoSemicolon);
                            if (JsToken.Colon == m_currentToken.Token)
                            {
                                m_noSkipTokenSet.Add(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet);
                                try
                                {
                                    SkipTokensAndThrow();
                                }
                                catch (RecoveryTokenException)
                                {
                                    if (JsToken.Semicolon == m_currentToken.Token)
                                    {
                                        m_useCurrentForNext = false;
                                    }
                                    else
                                    {
                                        throw;
                                    }
                                }
                                finally
                                {
                                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet);
                                }
                            }
                        }

                        GetNextToken();
                        if (JsToken.Semicolon != m_currentToken.Token)
                        {
                            condOrColl = ParseExpression();
                            if (JsToken.Semicolon != m_currentToken.Token)
                            {
                                ReportError(JsError.NoSemicolon);
                            }
                        }

                        separator2Context = m_currentToken.Clone();
                        GetNextToken();

                        if (JsToken.RightParenthesis != m_currentToken.Token)
                        {
                            increment = ParseExpression();
                        }

                        if (JsToken.RightParenthesis != m_currentToken.Token)
                        {
                            ReportError(JsError.NoRightParenthesis);
                        }

                        forCtx.UpdateWith(m_currentToken);
                        GetNextToken();
                    }
                    catch (RecoveryTokenException exc)
                    {
                        if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                        {
                            exc._partiallyComputedNode = null;
                            throw;
                        }
                        else
                        {
                            // discard any partial info, just genrate empty condition and increment and keep going
                            exc._partiallyComputedNode = null;
                            if (condOrColl == null)
                                condOrColl = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this);
                        }
                        if (exc._token == JsToken.RightParenthesis)
                        {
                            GetNextToken();
                        }
                    }
                    finally
                    {
                        m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                    }

                    // if this is an assignment, throw a warning in case the developer
                    // meant to use == instead of =
                    // but no warning if the condition is wrapped in parens.
                    var binOp = condOrColl as JsBinaryOperator;
                    if (binOp != null && binOp.OperatorToken == JsToken.Assign)
                    {
                        condOrColl.Context.HandleError(JsError.SuspectAssignment);
                    }

                    JsAstNode body = null;
                    // if the statements aren't withing curly-braces, throw a possible error
                    if (JsToken.LeftCurly != m_currentToken.Token)
                    {
                        ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                    }
                    try
                    {
                        // parse a Statement, not a SourceElement
                        // and ignore any important comments that spring up right here.
                        body = ParseStatement(false, true);
                    }
                    catch (RecoveryTokenException exc)
                    {
                        if (exc._partiallyComputedNode == null)
                            body = new JsBlock(CurrentPositionContext(), this);
                        else
                            body = exc._partiallyComputedNode;
                        exc._partiallyComputedNode = new JsForNode(forCtx, this)
                            {
                                Initializer = initializer,
                                Separator1Context = separator1Context,
                                Condition = condOrColl,
                                Separator2Context = separator2Context,
                                Incrementer = increment,
                                Body = JsAstNode.ForceToBlock(body)
                            };
                        throw;
                    }
                    forNode = new JsForNode(forCtx, this)
                        {
                            Initializer = initializer,
                            Separator1Context = separator1Context,
                            Condition = condOrColl,
                            Separator2Context = separator2Context,
                            Incrementer = increment,
                            Body = JsAstNode.ForceToBlock(body)
                        };
                }
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            return forNode;
        }
 //
 // statements (we should only hit expressions)
 //
 public void Visit(JsBlock node)
 {
     Debug.Fail("shouldn't get here");
 }
Example #24
0
        /// <summary>
        /// Parse the source code using the given settings, getting back an abstract syntax tree Block node as the root
        /// representing the list of statements in the source code.
        /// </summary>
        /// <param name="settings">code settings to use to process the source code</param>
        /// <returns>root Block node representing the top-level statements</returns>
        public JsBlock Parse(JsSettings settings)
        {
            // initialize the scanner with our settings
            // make sure the RawTokens setting is OFF or we won't be able to create our AST
            InitializeScanner(settings);

            // make sure we initialize the global scope's strict mode to our flag, whether or not it
            // is true. This means if the setting is false, we will RESET the flag to false if we are
            // reusing the scope and a previous Parse call had code that set it to strict with a
            // program directive.
            GlobalScope.UseStrict = m_settings.StrictMode;

            // make sure the global scope knows about our known global names
            GlobalScope.SetAssumedGlobals(m_settings);

            // start of a new module
            m_newModule = true;

            var timePoints = m_timingPoints = new long[9];
            var timeIndex = timePoints.Length;
            var stopWatch = new Stopwatch();
            stopWatch.Start();

            JsBlock scriptBlock = null;
            JsBlock returnBlock = null;
            try
            {
                switch (m_settings.SourceMode)
                {
                    case JsSourceMode.Program:
                        // simply parse a block of statements
                        returnBlock = scriptBlock = ParseStatements();
                        break;

                    case JsSourceMode.Expression:
                        // create a block, get the first token, add in the parse of a single expression,
                        // and we'll go fron there.
                        returnBlock = scriptBlock = new JsBlock(CurrentPositionContext(), this);
                        GetNextToken();
                        try
                        {
                            var expr = ParseExpression();
                            if (expr != null)
                            {
                                scriptBlock.Append(expr);
                                scriptBlock.UpdateWith(expr.Context);
                            }
                        }
                        catch (EndOfStreamException)
                        {
                            Debug.WriteLine("EOF");
                        }
                        break;

                    case JsSourceMode.EventHandler:
                        // we're going to create the global block, add in a function expression with a single
                        // parameter named "event", and then we're going to parse the input as the body of that
                        // function expression. We're going to resolve the global block, but only return the body
                        // of the function.
                        scriptBlock = new JsBlock(null, this);

                        var parameters = new JsAstNodeList(null, this);
                        parameters.Append(new JsParameterDeclaration(null, this)
                            {
                                Name = "event",
                                RenameNotAllowed = true
                            });

                        var funcExpression = new JsFunctionObject(null, this)
                            {
                                FunctionType = JsFunctionType.Expression,
                                ParameterDeclarations = parameters
                            };
                        scriptBlock.Append(funcExpression);
                        funcExpression.Body = returnBlock = ParseStatements();
                        break;

                    default:
                        Debug.Fail("Unexpected source mode enumeration");
                        return null;
                }
            }
            catch (RecoveryTokenException)
            {
                // this should never happen but let's make SURE we don't expose our
                // private exception object to the outside world
                m_currentToken.HandleError(JsError.ApplicationError, true);
            }

            timePoints[--timeIndex] = stopWatch.ElapsedTicks;

            if (scriptBlock != null)
            {
                // resolve everything
                JsResolutionVisitor.Apply(scriptBlock, GlobalScope, m_settings);
            }

            timePoints[--timeIndex] = stopWatch.ElapsedTicks;

            if (scriptBlock != null && Settings.MinifyCode && !Settings.PreprocessOnly)
            {
                // this visitor doesn't just reorder scopes. It also combines the adjacent var variables,
                // unnests blocks, identifies prologue directives, and sets the strict mode on scopes.
                JsReorderScopeVisitor.Apply(scriptBlock, this);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // analyze the entire node tree (needed for hypercrunch)
                // root to leaf (top down)
                var analyzeVisitor = new JsAnalyzeNodeVisitor(this);
                scriptBlock.Accept(analyzeVisitor);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // analyze the scope chain (also needed for hypercrunch)
                // root to leaf (top down)
                GlobalScope.AnalyzeScope();
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // if we want to crunch any names....
                if (m_settings.LocalRenaming != JsLocalRenaming.KeepAll
                    && m_settings.IsModificationAllowed(JsTreeModifications.LocalRenaming))
                {
                    // then do a top-down traversal of the scope tree. For each field that had not
                    // already been crunched (globals and outers will already be crunched), crunch
                    // the name with a crunch iterator that does not use any names in the verboten set.
                    GlobalScope.AutoRenameFields();
                }

                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // if we want to evaluate literal expressions, do so now
                if (m_settings.EvalLiteralExpressions)
                {
                    var visitor = new JsEvaluateLiteralVisitor(this);
                    scriptBlock.Accept(visitor);
                }

                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // make the final cleanup pass
                JsFinalPassVisitor.Apply(scriptBlock, this);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // we want to walk all the scopes to make sure that any generated
                // variables that haven't been crunched have been assigned valid
                // variable names that don't collide with any existing variables.
                GlobalScope.ValidateGeneratedNames();
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;
            }

            if (returnBlock != null && returnBlock.Parent != null)
            {
                returnBlock.Parent = null;
            }

            return returnBlock;
        }
        private static int RelocateDirectivePrologue(JsBlock block, int insertAt, JsDirectivePrologue directivePrologue)
        {
            // skip over any important comments
            while (insertAt < block.Count && (block[insertAt] is JsImportantComment))
            {
                ++insertAt;
            }

            // if the one we want to insert is already at this spot, then we're good to go
            if (block[insertAt] != directivePrologue)
            {
                // remove it from where it is right now and insert it into the proper location
                directivePrologue.Parent.ReplaceChild(directivePrologue, null);
                block.Insert(insertAt, directivePrologue);
            }

            // and move up to the next slot
            return ++insertAt;
        }
Example #26
0
        private JsFunctionObject ParseFunction(JsFunctionType functionType, JsContext fncCtx)
        {
            JsLookup name = null;
            JsAstNodeList formalParameters = null;
            JsBlock body = null;
            bool inExpression = (functionType == JsFunctionType.Expression);
            JsContext paramsContext = null;

            GetNextToken();

            // get the function name or make an anonymous function if in expression "position"
            if (JsToken.Identifier == m_currentToken.Token)
            {
                name = new JsLookup(m_currentToken.Clone(), this)
                    {
                        Name = m_scanner.Identifier
                    };
                GetNextToken();
            }
            else
            {
                string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token);
                if (null != identifier)
                {
                    name = new JsLookup(m_currentToken.Clone(), this)
                        {
                            Name = identifier
                        };
                    GetNextToken();
                }
                else
                {
                    if (!inExpression)
                    {
                        // if this isn't a function expression, then we need to throw an error because
                        // function DECLARATIONS always need a valid identifier name
                        ReportError(JsError.NoIdentifier, m_currentToken.Clone(), true);

                        // BUT if the current token is a left paren, we don't want to use it as the name.
                        // (fix for issue #14152)
                        if (m_currentToken.Token != JsToken.LeftParenthesis
                            && m_currentToken.Token != JsToken.LeftCurly)
                        {
                            identifier = m_currentToken.Code;
                            name = new JsLookup(CurrentPositionContext(), this)
                                {
                                    Name = identifier
                                };
                            GetNextToken();
                        }
                    }
                }
            }

            // make a new state and save the old one
            List<BlockType> blockType = m_blockType;
            m_blockType = new List<BlockType>(16);
            Dictionary<string, LabelInfo> labelTable = m_labelTable;
            m_labelTable = new Dictionary<string, LabelInfo>();

            try
            {
                // get the formal parameters
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                {
                    // we expect a left paren at this point for standard cross-browser support.
                    // BUT -- some versions of IE allow an object property expression to be a function name, like window.onclick.
                    // we still want to throw the error, because it syntax errors on most browsers, but we still want to
                    // be able to parse it and return the intended results.
                    // Skip to the open paren and use whatever is in-between as the function name. Doesn't matter that it's
                    // an invalid identifier; it won't be accessible as a valid field anyway.
                    bool expandedIndentifier = false;
                    while (m_currentToken.Token != JsToken.LeftParenthesis
                        && m_currentToken.Token != JsToken.LeftCurly
                        && m_currentToken.Token != JsToken.Semicolon
                        && m_currentToken.Token != JsToken.EndOfFile)
                    {
                        name.Context.UpdateWith(m_currentToken);
                        GetNextToken();
                        expandedIndentifier = true;
                    }

                    // if we actually expanded the identifier context, then we want to report that
                    // the function name needs to be an identifier. Otherwise we didn't expand the
                    // name, so just report that we expected an open paren at this point.
                    if (expandedIndentifier)
                    {
                        name.Name = name.Context.Code;
                        name.Context.HandleError(JsError.FunctionNameMustBeIdentifier, false);
                    }
                    else
                    {
                        ReportError(JsError.NoLeftParenthesis, true);
                    }
                }

                if (m_currentToken.Token == JsToken.LeftParenthesis)
                {
                    // create the parameter list
                    formalParameters = new JsAstNodeList(m_currentToken.Clone(), this);
                    paramsContext = m_currentToken.Clone();

                    // skip the open paren
                    GetNextToken();

                    // create the list of arguments and update the context
                    while (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        String id = null;
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
                        try
                        {
                            JsParameterDeclaration paramDecl = null;
                            if (JsToken.Identifier != m_currentToken.Token && (id = JsKeyword.CanBeIdentifier(m_currentToken.Token)) == null)
                            {
                                if (JsToken.LeftCurly == m_currentToken.Token)
                                {
                                    ReportError(JsError.NoRightParenthesis);
                                    break;
                                }
                                else if (JsToken.Comma == m_currentToken.Token)
                                {
                                    // We're missing an argument (or previous argument was malformed and
                                    // we skipped to the comma.)  Keep trying to parse the argument list --
                                    // we will skip the comma below.
                                    ReportError(JsError.SyntaxError, true);
                                }
                                else
                                {
                                    ReportError(JsError.SyntaxError, true);
                                    SkipTokensAndThrow();
                                }
                            }
                            else
                            {
                                if (null == id)
                                {
                                    id = m_scanner.Identifier;
                                }

                                paramDecl = new JsParameterDeclaration(m_currentToken.Clone(), this)
                                    {
                                        Name = id,
                                        Position = formalParameters.Count
                                    };
                                formalParameters.Append(paramDecl);
                                GetNextToken();
                            }

                            // got an arg, it should be either a ',' or ')'
                            if (JsToken.RightParenthesis == m_currentToken.Token)
                            {
                                break;
                            }
                            else if (JsToken.Comma == m_currentToken.Token)
                            {
                                // append the comma context as the terminator for the parameter
                                paramDecl.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone());
                            }
                            else
                            {
                                // deal with error in some "intelligent" way
                                if (JsToken.LeftCurly == m_currentToken.Token)
                                {
                                    ReportError(JsError.NoRightParenthesis);
                                    break;
                                }
                                else
                                {
                                    if (JsToken.Identifier == m_currentToken.Token)
                                    {
                                        // it's possible that the guy was writing the type in C/C++ style (i.e. int x)
                                        ReportError(JsError.NoCommaOrTypeDefinitionError);
                                    }
                                    else
                                        ReportError(JsError.NoComma);
                                }
                            }

                            GetNextToken();
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (IndexOfToken(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
                        }
                    }

                    fncCtx.UpdateWith(m_currentToken);
                    GetNextToken();
                }

                // read the function body of non-abstract functions.
                if (JsToken.LeftCurly != m_currentToken.Token)
                    ReportError(JsError.NoLeftCurly, true);

                m_blockType.Add(BlockType.Block);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                try
                {
                    // parse the block locally to get the exact end of function
                    body = new JsBlock(m_currentToken.Clone(), this);
                    body.BraceOnNewLine = m_foundEndOfLine;
                    GetNextToken();

                    var possibleDirectivePrologue = true;
                    while (JsToken.RightCurly != m_currentToken.Token)
                    {
                        try
                        {
                            // function body's are SourceElements (Statements + FunctionDeclarations)
                            var statement = ParseStatement(true);
                            if (possibleDirectivePrologue)
                            {
                                var constantWrapper = statement as JsConstantWrapper;
                                if (constantWrapper != null && constantWrapper.PrimitiveType == JsPrimitiveType.String)
                                {
                                    // if it's already a directive prologues, we're good to go
                                    if (!(constantWrapper is JsDirectivePrologue))
                                    {
                                        // make the statement a directive prologue instead of a constant wrapper
                                        statement = new JsDirectivePrologue(constantWrapper.Value.ToString(), constantWrapper.Context, constantWrapper.Parser)
                                            {
                                                MayHaveIssues = constantWrapper.MayHaveIssues
                                            };
                                    }
                                }
                                else if (!m_newModule)
                                {
                                    // no longer considering constant wrappers
                                    possibleDirectivePrologue = false;
                                }
                            }
                            else if (m_newModule)
                            {
                                // we scanned into a new module -- we might find directive prologues again
                                possibleDirectivePrologue = true;
                            }

                            // add it to the body
                            body.Append(statement);
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (exc._partiallyComputedNode != null)
                            {
                                body.Append(exc._partiallyComputedNode);
                            }
                            if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                    }

                    // make sure any important comments before the closing brace are kept
                    AppendImportantComments(body);

                    body.Context.UpdateWith(m_currentToken);
                    fncCtx.UpdateWith(m_currentToken);
                }
                catch (EndOfStreamException)
                {
                    // if we get an EOF here, we never had a chance to find the closing curly-brace
                    fncCtx.HandleError(JsError.UnclosedFunction, true);
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = new JsFunctionObject(fncCtx, this)
                            {
                                FunctionType = (inExpression ? JsFunctionType.Expression : JsFunctionType.Declaration),
                                IdContext = name.IfNotNull(n => n.Context),
                                Name = name.IfNotNull(n => n.Name),
                                ParameterDeclarations = formalParameters,
                                ParametersContext = paramsContext,
                                Body = body
                            };
                        throw;
                    }
                }
                finally
                {
                    m_blockType.RemoveAt(m_blockType.Count - 1);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                }

                GetNextToken();
            }
            finally
            {
                // restore state
                m_blockType = blockType;
                m_labelTable = labelTable;
            }

            return new JsFunctionObject(fncCtx, this)
                {
                    FunctionType = functionType,
                    IdContext = name.IfNotNull(n => n.Context),
                    Name = name.IfNotNull(n => n.Name),
                    ParameterDeclarations = formalParameters,
                    ParametersContext = paramsContext,
                    Body = body
                };
        }
        private static int RelocateVar(JsBlock block, int insertAt, JsVar varStatement)
        {
            // if the var statement is at the next position to insert, then we don't need
            // to do anything.
            if (block[insertAt] != varStatement)
            {
                // check to see if the current position is a var and we are the NEXT statement.
                // if that's the case, we don't need to break out the initializer, just append all the
                // vardecls as-is to the current position.
                var existingVar = block[insertAt] as JsVar;
                if (existingVar != null && block[insertAt + 1] == varStatement)
                {
                    // just append our vardecls to the insertion point, then delete our statement
                    existingVar.Append(varStatement);
                    block.RemoveAt(insertAt + 1);
                }
                else
                {
                    // iterate through the decls and count how many have initializers
                    var initializerCount = 0;
                    for (var ndx = 0; ndx < varStatement.Count; ++ndx)
                    {
                        if (varStatement[ndx].Initializer != null)
                        {
                            ++initializerCount;
                        }
                    }

                    // if there are more than two decls with initializers, then we won't actually
                    // be gaining anything by moving the var to the top. We'll get rid of the four
                    // bytes for the "var ", but we'll be adding two bytes for the name and comma
                    // because name=init will still need to remain behind.
                    if (initializerCount <= 2)
                    {
                        // first iterate through all the declarations in the var statement,
                        // constructing an expression statement that is made up of assignment
                        // operators for each of the declarations that have initializers (if any)
                        // and removing all the initializers
                        var assignments = new List<JsAstNode>();
                        for (var ndx = 0; ndx < varStatement.Count; ++ndx)
                        {
                            var varDecl = varStatement[ndx];
                            if (varDecl.Initializer != null)
                            {
                                if (varDecl.IsCCSpecialCase)
                                {
                                    // create a vardecl with the same name and no initializer
                                    var copyDecl = new JsVariableDeclaration(varDecl.Context, varDecl.Parser)
                                        {
                                            Identifier = varDecl.Identifier,
                                            NameContext = varDecl.VariableField.OriginalContext,
                                            VariableField = varDecl.VariableField
                                        };

                                    // replace the special vardecl with the copy
                                    varStatement[ndx] = copyDecl;

                                    // add the original vardecl to the list of "assignments"
                                    assignments.Add(varDecl);

                                    // add the new decl to the field's declaration list, and remove the old one
                                    // because we're going to change that to an assignment.
                                    varDecl.VariableField.Declarations.Add(copyDecl);
                                    varDecl.VariableField.Declarations.Remove(varDecl);
                                }
                                else
                                {
                                    // hold on to the object so we don't lose it to the GC
                                    var initializer = varDecl.Initializer;

                                    // remove it from the vardecl
                                    varDecl.Initializer = null;

                                    // create an assignment operator for a lookup to the name
                                    // as the left, and the initializer as the right, and add it to the list
                                    var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varDecl.Parser)
                                        {
                                            Name = varDecl.Identifier,
                                            VariableField = varDecl.VariableField,
                                        };
                                    assignments.Add(new JsBinaryOperator(varDecl.Context, varDecl.Parser)
                                        {
                                            Operand1 = lookup,
                                            Operand2 = initializer,
                                            OperatorToken = JsToken.Assign,
                                            OperatorContext = varDecl.AssignContext
                                        });

                                    // add the new lookup to the field's references
                                    varDecl.VariableField.References.Add(lookup);
                                }
                            }
                        }

                        // now if there were any initializers...
                        if (assignments.Count > 0)
                        {
                            // we want to create one big expression from all the assignments and replace the
                            // var statement with the assignment(s) expression. Start at position n=1 and create
                            // a binary operator of n-1 as the left, n as the right, and using a comma operator.
                            var expression = assignments[0];
                            for (var ndx = 1; ndx < assignments.Count; ++ndx)
                            {
                                expression = JsCommaOperator.CombineWithComma(null, expression.Parser, expression, assignments[ndx]);
                            }

                            // replace the var with the expression.
                            // we still have a pointer to the var, so we can insert it back into the proper
                            // place next.
                            varStatement.Parent.ReplaceChild(varStatement, expression);
                        }
                        else
                        {
                            // no initializers.
                            // if the parent is a for-in statement...
                            var forInParent = varStatement.Parent as JsForIn;
                            if (forInParent != null)
                            {
                                // we want to replace the var statement with a lookup for the var
                                // there should be only one vardecl
                                var varDecl = varStatement[0];
                                var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varStatement.Parser)
                                    {
                                        Name = varDecl.Identifier,
                                        VariableField = varDecl.VariableField
                                    };
                                varStatement.Parent.ReplaceChild(varStatement, lookup);
                                varDecl.VariableField.References.Add(lookup);
                            }
                            else
                            {
                                // just remove the var statement altogether
                                varStatement.Parent.ReplaceChild(varStatement, null);
                            }
                        }

                        // if the statement at the insertion point is a var-statement already,
                        // then we just need to append our vardecls to it. Otherwise we'll insert our
                        // var statement at the right point
                        if (existingVar != null)
                        {
                            // append the varstatement we want to move to the existing var, which will
                            // transfer all the vardecls to it.
                            existingVar.Append(varStatement);
                        }
                        else
                        {
                            // move the var to the insert point, incrementing the position or next time
                            block.Insert(insertAt, varStatement);
                        }
                    }
                }
            }

            return insertAt;
        }
Example #28
0
        //---------------------------------------------------------------------------------------
        // ParseIfStatement
        //
        //  IfStatement :
        //    'if' '(' Expression ')' Statement ElseStatement
        //
        //  ElseStatement :
        //    <empty> |
        //    'else' Statement
        //---------------------------------------------------------------------------------------
        private JsIfNode ParseIfStatement()
        {
            JsContext ifCtx = m_currentToken.Clone();
            JsAstNode condition = null;
            JsAstNode trueBranch = null;
            JsAstNode falseBranch = null;
            JsContext elseCtx = null;

            m_blockType.Add(BlockType.Block);
            try
            {
                // parse condition
                GetNextToken();
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                try
                {
                    if (JsToken.LeftParenthesis != m_currentToken.Token)
                        ReportError(JsError.NoLeftParenthesis);
                    GetNextToken();
                    condition = ParseExpression();

                    // parse statements
                    if (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        ifCtx.UpdateWith(condition.Context);
                        ReportError(JsError.NoRightParenthesis);
                    }
                    else
                        ifCtx.UpdateWith(m_currentToken);

                    GetNextToken();
                }
                catch (RecoveryTokenException exc)
                {
                    // make up an if condition
                    if (exc._partiallyComputedNode != null)
                        condition = exc._partiallyComputedNode;
                    else
                        condition = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this);

                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = null; // really not much to pass up
                        // the if condition was so bogus we do not have a chance to make an If node, give up
                        throw;
                    }
                    else
                    {
                        if (exc._token == JsToken.RightParenthesis)
                            GetNextToken();
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                }

                // if this is an assignment, throw a warning in case the developer
                // meant to use == instead of =
                // but no warning if the condition is wrapped in parens.
                var binOp = condition as JsBinaryOperator;
                if (binOp != null && binOp.OperatorToken == JsToken.Assign)
                {
                    condition.Context.HandleError(JsError.SuspectAssignment);
                }

                m_noSkipTokenSet.Add(NoSkipTokenSet.s_IfBodyNoSkipTokenSet);
                if (JsToken.Semicolon == m_currentToken.Token)
                {
                    m_currentToken.HandleError(JsError.SuspectSemicolon);
                }
                else if (JsToken.LeftCurly != m_currentToken.Token)
                {
                    // if the statements aren't withing curly-braces, throw a possible error
                    ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                }

                try
                {
                    // parse a Statement, not a SourceElement
                    // and ignore any important comments that spring up right here.
                    trueBranch = ParseStatement(false, true);
                }
                catch (RecoveryTokenException exc)
                {
                    // make up a block for the if part
                    if (exc._partiallyComputedNode != null)
                        trueBranch = exc._partiallyComputedNode;
                    else
                        trueBranch = new JsBlock(CurrentPositionContext(), this);
                    if (IndexOfToken(NoSkipTokenSet.s_IfBodyNoSkipTokenSet, exc) == -1)
                    {
                        // we have to pass the exception to someone else, make as much as you can from the if
                        exc._partiallyComputedNode = new JsIfNode(ifCtx, this)
                            {
                                Condition = condition,
                                TrueBlock = JsAstNode.ForceToBlock(trueBranch)
                            };
                        throw;
                    }
                }
                finally
                {
                    if (trueBranch != null)
                    {
                        ifCtx.UpdateWith(trueBranch.Context);
                    }

                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_IfBodyNoSkipTokenSet);
                }

                // parse else, if any
                if (JsToken.Else == m_currentToken.Token)
                {
                    elseCtx = m_currentToken.Clone();
                    GetNextToken();
                    if (JsToken.Semicolon == m_currentToken.Token)
                    {
                        m_currentToken.HandleError(JsError.SuspectSemicolon);
                    }
                    else if (JsToken.LeftCurly != m_currentToken.Token
                      && JsToken.If != m_currentToken.Token)
                    {
                        // if the statements aren't withing curly-braces (or start another if-statement), throw a possible error
                        ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                    }

                    try
                    {
                        // parse a Statement, not a SourceElement
                        // and ignore any important comments that spring up right here.
                        falseBranch = ParseStatement(false, true);
                    }
                    catch (RecoveryTokenException exc)
                    {
                        // make up a block for the else part
                        if (exc._partiallyComputedNode != null)
                            falseBranch = exc._partiallyComputedNode;
                        else
                            falseBranch = new JsBlock(CurrentPositionContext(), this);
                        exc._partiallyComputedNode = new JsIfNode(ifCtx, this)
                            {
                                Condition = condition,
                                TrueBlock = JsAstNode.ForceToBlock(trueBranch),
                                ElseContext = elseCtx,
                                FalseBlock = JsAstNode.ForceToBlock(falseBranch)
                            };
                        throw;
                    }
                    finally
                    {
                        if (falseBranch != null)
                        {
                            ifCtx.UpdateWith(falseBranch.Context);
                        }
                    }
                }
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            return new JsIfNode(ifCtx, this)
                {
                    Condition = condition,
                    TrueBlock = JsAstNode.ForceToBlock(trueBranch),
                    ElseContext = elseCtx,
                    FalseBlock = JsAstNode.ForceToBlock(falseBranch)
                };
        }
        public static void Apply(JsBlock block, JsParser parser)
        {
            // create a new instance of the visitor and apply it to the block
            var visitor = new JsReorderScopeVisitor(parser);
            block.Accept(visitor);

            // if there were any module directive prologues we need to promote, do them first
            var insertAt = 0;
            if (visitor.m_moduleDirectives != null)
            {
                foreach (var directivePrologue in visitor.m_moduleDirectives)
                {
                    insertAt = RelocateDirectivePrologue(block, insertAt, directivePrologue);
                }
            }

            // Make sure that we skip over any remaining comments and directive prologues.
            // we do NOT want to insert anything between the start of the scope and any directive prologues.
            while (insertAt < block.Count
                && (block[insertAt] is JsDirectivePrologue || block[insertAt] is JsImportantComment))
            {
                ++insertAt;
            }

            // first, we want to move all function declarations to the top of this block
            if (visitor.m_functionDeclarations != null)
            {
                foreach (var funcDecl in visitor.m_functionDeclarations)
                {
                    insertAt = RelocateFunction(block, insertAt, funcDecl);
                }
            }

            // special case: if there is only one var statement in the entire scope,
            // then just leave it alone because we will only add bytes by moving it around,
            // or be byte-neutral at best (no initializers and not in a for-statement).
            if (visitor.m_varStatements != null && visitor.m_varStatements.Count > 1)
            {
                // then we want to move all variable declarations after to the top (after the functions)
                foreach (var varStatement in visitor.m_varStatements)
                {
                    insertAt = RelocateVar(block, insertAt, varStatement);
                }
            }

            // then we want to do the same thing for all child functions (declarations AND other)
            if (visitor.m_functionDeclarations != null)
            {
                foreach (var funcDecl in visitor.m_functionDeclarations)
                {
                    Apply(funcDecl.Body, parser);
                }
            }

            if (visitor.m_functionExpressions != null)
            {
                foreach (var funcExpr in visitor.m_functionExpressions)
                {
                    Apply(funcExpr.Body, parser);
                }
            }
        }
Example #30
0
        //---------------------------------------------------------------------------------------
        // ParseStatements
        //
        // statements :
        //   <empty> |
        //   statement statements
        //
        //---------------------------------------------------------------------------------------
        private JsBlock ParseStatements()
        {
            var program = new JsBlock(CurrentPositionContext(), this);
            m_blockType.Add(BlockType.Block);
            m_useCurrentForNext = false;
            try
            {
                // get the first token
                GetNextToken();

                // if the block doesn't have a proper file context, then let's set it from the
                // first token -- that token might have had a ///#source directive!
                if (string.IsNullOrEmpty(program.Context.Document.FileContext))
                {
                    program.Context.Document.FileContext = m_currentToken.Document.FileContext;
                }

                m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_TopLevelNoSkipTokenSet);

                try
                {
                    var possibleDirectivePrologue = true;
                    int lastEndPosition = m_currentToken.EndPosition;
                    while (m_currentToken.Token != JsToken.EndOfFile)
                    {
                        JsAstNode ast = null;
                        try
                        {
                            // parse a statement -- pass true because we really want a SourceElement,
                            // which is a Statement OR a FunctionDeclaration. Technically, FunctionDeclarations
                            // are not statements!
                            ast = ParseStatement(true);

                            // if we are still possibly looking for directive prologues
                            if (possibleDirectivePrologue)
                            {
                                var constantWrapper = ast as JsConstantWrapper;
                                if (constantWrapper != null && constantWrapper.PrimitiveType == JsPrimitiveType.String)
                                {
                                    if (!(constantWrapper is JsDirectivePrologue))
                                    {
                                        // use a directive prologue node instead
                                        ast = new JsDirectivePrologue(constantWrapper.Value.ToString(), ast.Context, ast.Parser)
                                            {
                                                MayHaveIssues = constantWrapper.MayHaveIssues
                                            };
                                    }
                                }
                                else if (!m_newModule)
                                {
                                    // nope -- no longer finding directive prologues
                                    possibleDirectivePrologue = false;
                                }
                            }
                            else if (m_newModule)
                            {
                                // we aren't looking for directive prologues anymore, but we did scan
                                // into a new module after that last AST, so reset the flag because that
                                // new module might have directive prologues for next time
                                possibleDirectivePrologue = true;
                            }
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, exc)
                                || TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc))
                            {
                                ast = exc._partiallyComputedNode;
                                GetNextToken();
                            }
                            else
                            {
                                m_useCurrentForNext = false;
                                do
                                {
                                    GetNextToken();
                                } while (m_currentToken.Token != JsToken.EndOfFile && !TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, m_currentToken.Token)
                                  && !TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, m_currentToken.Token));
                            }
                        }

                        if (null != ast)
                        {
                            // append the token to the program
                            program.Append(ast);

                            // set the last end position to be the start of the current token.
                            // if we parse the next statement and the end is still the start, we know
                            // something is up and might get into an infinite loop.
                            lastEndPosition = m_currentToken.EndPosition;
                        }
                        else if (!m_scanner.IsEndOfFile && m_currentToken.StartLinePosition == lastEndPosition)
                        {
                            // didn't parse a statement, we're not at the EOF, and we didn't move
                            // anywhere in the input stream. If we just keep looping around, we're going
                            // to get into an infinite loop. Break it.
                            m_currentToken.HandleError(JsError.ApplicationError, true);
                            break;
                        }
                    }

                    AppendImportantComments(program);
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_TopLevelNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                }

            }
            catch (EndOfStreamException)
            {
            }

            program.UpdateWith(CurrentPositionContext());
            return program;
        }
 public void Visit(JsBlock node)
 {
     if (node != null && node.Count > 0)
     {
         // there should only be one "statement"
         node[0].Accept(this);
     }
 }
        private void CombineExpressions(JsBlock node)
        {
            // walk backwards because we'll be removing items as we go along.
            // and don't bother looking at the first element, because we'll be attempting to combine
            // the current element with the previous element -- and the first element (0) has no
            // previous element.
            // we will check for:
            //      1) expr1;expr2           ==> expr1,expr2
            //      2) expr1;for(;...)       ==> for(expr1;...)
            //      3) expr1;for(expr2;...)  ==> for(expr1,expr2;...)
            //      4) expr1;return expr2    ==> return expr1,expr2
            //      5) expr1;if(cond)...     ==> if(expr1,cond)...
            //      6) expr1;while(cond)...  ==> for(expr;cond;)...
            //      7) lookup=expr1;lookup[OP]=expr2;   ==> lookup=expr1[OP]expr2
            //      8) lookup[OP1]=expr1;lookup[OP2]=expr2  ==> lookup=(lookup[OP1]expr1)[OP2]expr2
            for (var ndx = node.Count - 1; ndx > 0; --ndx)
            {
                // we may have deleted more than 1 statement, in which case we need to loop around
                // again to let ndx catch up to the last item in the block
                if (ndx >= node.Count)
                {
                    continue;
                }

                // see if the previous statement is an expression
                if (node[ndx - 1].IsExpression)
                {
                    CombineWithPreviousExpression(node, ndx);
                }
                else
                {
                    var previousVar = node[ndx - 1] as JsVar;
                    if (previousVar != null)
                    {
                        CombineWithPreviousVar(node, ndx, previousVar);
                    }
                }
            }
        }