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); } } } } }
// // 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); } }
//--------------------------------------------------------------------------------------- // 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 }; }
//--------------------------------------------------------------------------------------- // 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 }
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 }; }
//--------------------------------------------------------------------------------------- // 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; }
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(); } } }
//--------------------------------------------------------------------------------------- // 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 }; }
//--------------------------------------------------------------------------------------- // 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 }; }
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; }
/// <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; }
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; }
//--------------------------------------------------------------------------------------- // 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); } } }
//--------------------------------------------------------------------------------------- // 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); } } } }