public void NotExpressions() { var expressionSource = TestContext.DataRow[0].ToString(); var expectedResult = TestContext.DataRow[1].ToString(); // parse the source into an AST var parser = new JSParser(); var block = parser.Parse(expressionSource, new CodeSettings() { MinifyCode = false, SourceMode = JavaScriptSourceMode.Expression }); if (block.Count == 1) { var expression = block[0]; // create the logical-not visitor on the expression var logicalNot = new Microsoft.Ajax.Utilities.LogicalNot(expression, parser.Settings); // get the original code var original = OutputVisitor.Apply(expression, parser.Settings); Trace.Write("ORIGINAL EXPRESSION: "); Trace.WriteLine(original); // get the measured delta var measuredDelta = logicalNot.Measure(); // perform the logical-not operation logicalNot.Apply(); // get the resulting code -- should still be only one statement in the block var notted = OutputVisitor.Apply(block[0], parser.Settings); Trace.Write("LOGICAL-NOT EXPRESSION: "); Trace.WriteLine(notted); Trace.Write("EXPECTED EXPRESSION: "); Trace.WriteLine(expectedResult); Trace.Write("DELTA: "); Trace.WriteLine(measuredDelta); // what's the actual difference var actualDelta = notted.Length - original.Length; Assert.AreEqual(actualDelta, measuredDelta, "Measurement was off; calculated {0} but was actually {1}", measuredDelta, actualDelta); Assert.AreEqual(expectedResult, notted, "Expected output is not the same!!!!"); } else { Assert.Fail(string.Format("Source line '{0}' parsed to more than one statement!", expressionSource)); } }
public override void Visit(Block node) { if (node != null) { // we might things differently if these statements are the body collection for a function // because we can assume the implicit return statement at the end of it bool isFunctionLevel = (node.Parent is FunctionObject); // if we want to remove debug statements... if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // do it now before we try doing other things StripDebugStatements(node); } // analyze all the statements in our block and recurse them if (node.BlockScope != null) { ScopeStack.Push(node.BlockScope); } try { // call the base class to recurse base.Visit(node); } finally { if (node.BlockScope != null) { ScopeStack.Pop(); } } if (m_parser.Settings.RemoveUnneededCode) { // go forward, and check the count each iteration because we might be ADDING statements to the block. // let's look at all our if-statements. If a true-clause ends in a return, then we don't // need the else-clause; we can pull its statements out and stick them after the if-statement. // also, if we encounter a return-, break- or continue-statement, we can axe everything after it for (var ndx = 0; ndx < node.Count; ++ndx) { // see if it's an if-statement with both a true and a false block var ifNode = node[ndx] as IfNode; if (ifNode != null && ifNode.TrueBlock != null && ifNode.TrueBlock.Count > 0 && ifNode.FalseBlock != null) { // now check to see if the true block ends in a return statement if (ifNode.TrueBlock[ifNode.TrueBlock.Count - 1] is ReturnNode) { // transform: if(cond){statements1;return}else{statements2} to if(cond){statements1;return}statements2 // it does. insert all the false-block statements after the if-statement node.InsertRange(ndx + 1, ifNode.FalseBlock.Children); // and then remove the false block altogether ifNode.ReplaceChild(ifNode.FalseBlock, null); } } else if (node[ndx] is ReturnNode || node[ndx] is Break || node[ndx] is ContinueNode) { // we have a return node -- no statments afterwards will be executed, so clear them out. // transform: {...;return;...} to {...;return} // transform: {...;break;...} to {...;break} // transform: {...;continue;...} to {...;continue} // we've found a return statement, and it's not the last statement in the function. // walk the rest of the statements and delete anything that isn't a function declaration // or a var statement. for (var ndxRemove = node.Count - 1; ndxRemove > ndx; --ndxRemove) { var funcObject = node[ndxRemove] as FunctionObject; if (funcObject == null || funcObject.FunctionType != FunctionType.Declaration) { var varStatement = node[ndxRemove] as Var; if (varStatement != null) { // var statements can't be removed, but any initializers should // be deleted since they won't get executed. for (var ndxDecl = 0; ndxDecl < varStatement.Count; ++ndxDecl) { if (varStatement[ndxDecl].Initializer != null) { varStatement[ndxDecl].ReplaceChild(varStatement[ndxDecl].Initializer, null); } } } else { // not a function declaration, and not a var statement -- get rid of it node.RemoveAt(ndxRemove); } } } } } } // now check the last statement -- if it's an if-statement where the true-block is a single return // and there is no false block, convert this one statement to a conditional. We might back it out later // if we don't combine the conditional with other stuff. // but we can only do this if we're at the functional level because of the implied return at the end // of that block. if (isFunctionLevel && node.Count > 0 && m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionReturnToCondition)) { ReturnNode returnNode; var ifNode = FindLastStatement(node) as IfNode; if (ifNode != null && ifNode.FalseBlock == null && ifNode.TrueBlock.Count == 1 && (returnNode = ifNode.TrueBlock[0] as ReturnNode) != null) { // if the return node doesn't have an operand, then we can just replace the if-statement with its conditional if (returnNode.Operand == null) { // transform: if(cond)return;} to cond} // TODO: if the condition is a constant, then eliminate it altogether node.ReplaceChild(ifNode, ifNode.Condition); } else if (returnNode.Operand.IsExpression) { // transform: if(cond)return expr;} to return cond?expr:void 0} var conditional = new Conditional( null, m_parser, ifNode.Condition, returnNode.Operand, CreateVoidNode()); // replace the if-statement with the new return node node.ReplaceChild(ifNode, new ReturnNode(ifNode.Context, m_parser, conditional)); Optimize(conditional); } } } // now walk through and combine adjacent expression statements, and adjacent var-for statements // and adjecent expression-return statements if (m_parser.Settings.IsModificationAllowed(TreeModifications.CombineAdjacentExpressionStatements)) { CombineExpressions(node); } // check to see if we want to combine a preceding var with a for-statement if (m_parser.Settings.IsModificationAllowed(TreeModifications.MoveVarIntoFor)) { // look at the statements in the block. // walk BACKWARDS down the list because we'll be removing items when we encounter // var statements that can be moved inside a for statement's initializer // 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) { // see if the previous statement is a var statement // (we've already combined adjacent var-statements) ForNode forNode; var previousVar = node[ndx - 1] as Var; if (previousVar != null && (forNode = node[ndx] as ForNode) != null) { // and see if the forNode's initializer is empty if (forNode.Initializer != null) { // not empty -- see if it is a Var node Var varInitializer = forNode.Initializer as Var; if (varInitializer != null) { // transform: var decls1;for(var decls2;...) to for(var decls1,decls2;...) // we want to PREPEND the initializers in the previous var-statement // to our for-statement's initializer var-statement list varInitializer.InsertAt(0, previousVar); // then remove the previous var statement node.RemoveAt(ndx - 1); // this will bump the for node up one position in the list, so the next iteration // will be right back on this node in case there are other var statements we need // to combine } else { // we want to see if the initializer expression is a series of one or more // simple assignments to variables that are in the previous var statement. // if all the expressions are assignments to variables that are defined in the // previous var statement, then we can just move the var statement into the // for statement. BinaryOperator binaryOp = forNode.Initializer as BinaryOperator; if (binaryOp != null && AreAssignmentsInVar(binaryOp, previousVar)) { // transform: var decls;for(expr1;...) to for(var decls,expr1;...) // create a list and fill it with all the var-decls created from the assignment // operators in the expression var varDecls = new List<VariableDeclaration>(); ConvertAssignmentsToVarDecls(binaryOp, varDecls, m_parser); // then go through and append each one to the var statement before us foreach (var varDecl in varDecls) { previousVar.Append(varDecl); } // move the previous var-statement into our initializer forNode.ReplaceChild(forNode.Initializer, previousVar); // and remove the previous var-statement from the list. node.RemoveAt(ndx - 1); // this will bump the for node up one position in the list, so the next iteration // will be right back on this node, but the initializer will not be null } } } else { // transform: var decls;for(;...) to for(var decls;...) // if it's empty, then we're free to add the previous var statement // to this for statement's initializer. remove it from it's current // position and add it as the initializer node.RemoveAt(ndx - 1); forNode.SetInitializer(previousVar); // this will bump the for node up one position in the list, so the next iteration // will be right back on this node, but the initializer will not be null } } } } // see if the last statement is a return statement ReturnNode lastReturn; if ((lastReturn = FindLastStatement(node) as ReturnNode) != null) { // set this flag to true if we end up adding an expression to the block. // before exiting, we'll go through and combine adjacent expressions again if this // flag has been set to true. bool changedStatementToExpression = false; // get the index of the statement before the last return // (skip over function decls and importand comments) var indexPrevious = PreviousStatementIndex(node, lastReturn); // just out of curiosity, let's see if we fit a common pattern: // var name=expr;return name; // if so, we can cut out the var and simply return the expression Lookup lookup; if ((lookup = lastReturn.Operand as Lookup) != null && indexPrevious >= 0) { var varStatement = node[indexPrevious] as Var; if (varStatement != null) { // if the last vardecl in the var statement matches the return lookup, and no // other references exist for this field (refcount == 1)... VariableDeclaration varDecl; if ((varDecl = varStatement[varStatement.Count - 1]).Initializer != null && varDecl.IsEquivalentTo(lookup) && varDecl.Field.RefCount == 1) { if (varStatement.Count == 1) { // transform: ...;var name=expr;return name} to ...;return expr} // there's only one vardecl in the var, so get rid of the entire statement lastReturn.ReplaceChild(lookup, varDecl.Initializer); node.RemoveAt(indexPrevious); } else { // multiple vardecls are in the statement; we only need to get rid of the last one lastReturn.ReplaceChild(lookup, varDecl.Initializer); varStatement.ReplaceChild(varDecl, null); } } } } // check to see if we can combine the return statement with a previous if-statement // into a simple return-conditional. The true statement needs to have no false block, // and only one statement in the true block. Conditional conditional; IfNode previousIf; while (indexPrevious >= 0 && lastReturn != null && (previousIf = node[indexPrevious] as IfNode) != null && previousIf.TrueBlock != null && previousIf.TrueBlock.Count == 1 && previousIf.FalseBlock == null) { // assume no change is made for this loop bool somethingChanged = false; // and that one true-block statement needs to be a return statement var previousReturn = previousIf.TrueBlock[0] as ReturnNode; if (previousReturn != null) { if (lastReturn.Operand == null) { if (previousReturn.Operand == null) { // IF we are at the function level, then the block ends in an implicit return (undefined) // and we can change this if to just the condition. If we aren't at the function level, // then we have to leave the return, but we can replace the if with just the condition. if (!isFunctionLevel) { // transform: if(cond)return;return} to cond;return} node.ReplaceChild(previousIf, previousIf.Condition); } else { // transform: if(cond)return;return} to cond} // replace the final return with just the condition, then remove the previous if if (node.ReplaceChild(lastReturn, previousIf.Condition)) { node.RemoveAt(indexPrevious); somethingChanged = true; } } } else { // transform: if(cond)return expr;return} to return cond?expr:void 0 conditional = new Conditional(null, m_parser, previousIf.Condition, previousReturn.Operand, CreateVoidNode()); // replace the final return with the new return, then delete the previous if-statement if (node.ReplaceChild(lastReturn, new ReturnNode(null, m_parser, conditional))) { node.RemoveAt(indexPrevious); Optimize(conditional); somethingChanged = true; } } } else { if (previousReturn.Operand == null) { // transform: if(cond)return;return expr} to return cond?void 0:expr conditional = new Conditional(null, m_parser, previousIf.Condition, CreateVoidNode(), lastReturn.Operand); // replace the final return with the new return, then delete the previous if-statement if (node.ReplaceChild(lastReturn, new ReturnNode(null, m_parser, conditional))) { node.RemoveAt(indexPrevious); Optimize(conditional); somethingChanged = true; } } else if (previousReturn.Operand.IsEquivalentTo(lastReturn.Operand)) { // transform: if(cond)return expr;return expr} to return cond,expr} // create a new binary op with the condition and the final-return operand, // replace the operand on the final-return with the new binary operator, // and then delete the previous if-statement if (lastReturn.ReplaceChild(lastReturn.Operand, new BinaryOperator(null, m_parser, previousIf.Condition, lastReturn.Operand, JSToken.Comma))) { node.RemoveAt(indexPrevious); somethingChanged = true; } } else { // transform: if(cond)return expr1;return expr2} to return cond?expr1:expr2} // create a new conditional with the condition and the return operands, // replace the operand on the final-return with the new conditional operator, // and then delete the previous if-statement // transform: if(cond)return expr1;return expr2} to return cond?expr1:expr2} conditional = new Conditional(null, m_parser, previousIf.Condition, previousReturn.Operand, lastReturn.Operand); // replace the operand on the final-return with the new conditional operator, // and then delete the previous if-statement if (lastReturn.ReplaceChild(lastReturn.Operand, conditional)) { node.RemoveAt(indexPrevious); Optimize(conditional); somethingChanged = true; } } } } if (!somethingChanged) { // nothing changed -- break out of the loop break; } else { // set the flag that indicates something changed in at least one of these loops changedStatementToExpression = true; // and since we changed something, we need to bump the index down one // AFTER we grab the last return node (which has slipped into the same position // as the previous node) lastReturn = node[indexPrevious--] as ReturnNode; } } // if we added any more expressions since we ran our expression-combination logic, // run it again. if (changedStatementToExpression && m_parser.Settings.IsModificationAllowed(TreeModifications.CombineAdjacentExpressionStatements)) { CombineExpressions(node); } // and FINALLY, we want to see if what we did previously didn't pan out and we end // in something like return cond?expr:void 0, in which case we want to change it // back to a simple if(condition)return expr; (saves four bytes). // see if the last statement is a return statement that returns a conditional if (lastReturn != null && (conditional = lastReturn.Operand as Conditional) != null) { VoidNode voidOperator = conditional.FalseExpression as VoidNode; if (voidOperator != null && voidOperator.Operand is ConstantWrapper) { voidOperator = conditional.TrueExpression as VoidNode; if (voidOperator != null) { if (isFunctionLevel) { // transform: ...;return cond?void 0:void 0} to ...;cond} // function level ends in an implicit "return void 0" node.ReplaceChild(lastReturn, conditional.Condition); } else { // transform: ...;return cond?void 0:void 0} to ...;cond;return} // non-function level doesn't end in an implicit return, // so we need to break them out into two statements node.ReplaceChild(lastReturn, conditional.Condition); node.Append(new ReturnNode(null, m_parser, null)); } } else if (isFunctionLevel) { // transform: ...;return cond?expr:void 0} to ...;if(cond)return expr} // (only works at the function-level because of the implicit return statement) var ifNode = new IfNode(lastReturn.Context, m_parser, conditional.Condition, new ReturnNode(null, m_parser, conditional.TrueExpression), null); node.ReplaceChild(lastReturn, ifNode); } } else if (isFunctionLevel) { voidOperator = conditional.TrueExpression as VoidNode; if (voidOperator != null && voidOperator.Operand is ConstantWrapper) { // transform: ...;return cond?void 0;expr} to ...;if(!cond)return expr} // (only works at the function level because of the implicit return) // get the logical-not of the conditional var logicalNot = new LogicalNot(conditional.Condition, m_parser); logicalNot.Apply(); // create a new if-node based on the condition, with the branches swapped // (true-expression goes to false-branch, false-expression goes to true-branch var ifNode = new IfNode(lastReturn.Context, m_parser, conditional.Condition, new ReturnNode(null, m_parser, conditional.FalseExpression), null); node.ReplaceChild(lastReturn, ifNode); } } } } } }
public override void Visit(IfNode node) { if (node != null) { if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { if (node.TrueBlock != null && node.TrueBlock.IsDebuggerStatement) { node.ReplaceChild(node.TrueBlock, null); } if (node.FalseBlock != null && node.FalseBlock.IsDebuggerStatement) { node.ReplaceChild(node.FalseBlock, null); } } // recurse.... base.Visit(node); // now check to see if the two branches are now empty. // if they are, null them out. if (node.TrueBlock != null && node.TrueBlock.Count == 0) { node.ReplaceChild(node.TrueBlock, null); } if (node.FalseBlock != null && node.FalseBlock.Count == 0) { node.ReplaceChild(node.FalseBlock, null); } // if there is no true branch but a false branch, then // put a not on the condition and move the false branch to the true branch. if (node.TrueBlock == null && node.FalseBlock != null && m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionFalseToIfNotConditionTrue)) { // logical-not the condition var logicalNot = new LogicalNot(node.Condition, m_parser); logicalNot.Apply(); // and swap the branches node.SwapBranches(); } else if (node.TrueBlock != null && node.FalseBlock != null) { // see if logical-notting the condition produces something smaller var logicalNot = new LogicalNot(node.Condition, m_parser); if (logicalNot.Measure() < 0) { // it does -- not the condition and swap the branches logicalNot.Apply(); node.SwapBranches(); } // see if the true- and false-branches each contain only a single statement if (node.TrueBlock.Count == 1 && node.FalseBlock.Count == 1) { // they do -- see if the true-branch's statement is a return-statement var trueReturn = node.TrueBlock[0] as ReturnNode; if (trueReturn != null && trueReturn.Operand != null) { // it is -- see if the false-branch is also a return statement var falseReturn = node.FalseBlock[0] as ReturnNode; if (falseReturn != null && falseReturn.Operand != null) { // transform: if(cond)return expr1;else return expr2 to return cond?expr1:expr2 var conditional = new Conditional(null, m_parser, node.Condition, trueReturn.Operand, falseReturn.Operand); // create a new return node from the conditional and replace // our if-node with it var returnNode = new ReturnNode( node.Context, m_parser, conditional); node.Parent.ReplaceChild( node, returnNode); Optimize(conditional); } } } } else if (node.TrueBlock == null && node.FalseBlock == null && m_parser.Settings.IsModificationAllowed(TreeModifications.IfEmptyToExpression)) { // NEITHER branches have anything now! // something we can do in the future: as long as the condition doesn't // contain calls or assignments, we should be able to completely delete // the statement altogether rather than changing it to an expression // statement on the condition. // I'm just not doing it yet because I don't // know what the effect will be on the iteration of block statements. // if we're on item, 5, for instance, and we delete it, will the next // item be item 6, or will it return the NEW item 5 (since the old item // 5 was deleted and everything shifted up)? // We don't know what it is and what the side-effects may be, so // just change this statement into an expression statement by replacing us with // the expression node.Parent.ReplaceChild(node, node.Condition); // no need to analyze -- we already recursed } // if the true block is not empty, but it's an expression, there are a couple more // optimizations we can make if (node.TrueBlock != null && node.TrueBlock.IsExpression && m_parser.Settings.IsModificationAllowed(TreeModifications.IfExpressionsToExpression)) { if (node.FalseBlock != null && node.FalseBlock.IsExpression) { // if this statement has both true and false blocks, and they are both expressions, // then we can simplify this to a conditional expression. // because the blocks are expressions, we know they only have ONE statement in them, // so we can just dereference them directly. Conditional conditional; var logicalNot = new LogicalNot(node.Condition, m_parser); if (logicalNot.Measure() < 0) { // applying a logical-not makes the condition smaller -- reverse the branches logicalNot.Apply(); conditional = new Conditional( node.Context, m_parser, node.Condition, node.FalseBlock[0], node.TrueBlock[0]); } else { // regular order conditional = new Conditional( node.Context, m_parser, node.Condition, node.TrueBlock[0], node.FalseBlock[0]); } node.Parent.ReplaceChild( node, conditional); Optimize(conditional); } else if (node.FalseBlock == null && m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionCallToConditionAndCall)) { // but first -- which operator to use? if(a)b --> a&&b, and if(!a)b --> a||b // so determine which one is smaller: a or !a // assume we'll use the logical-and, since that doesn't require changing the condition var newOperator = JSToken.LogicalAnd; var logicalNot = new LogicalNot(node.Condition, m_parser); if (logicalNot.Measure() < 0) { // !a is smaller, so apply it and use the logical-or operator logicalNot.Apply(); newOperator = JSToken.LogicalOr; } // because the true block is an expression, we know it must only have // ONE statement in it, so we can just dereference it directly. var binaryOp = new BinaryOperator( node.Context, m_parser, node.Condition, node.TrueBlock[0], newOperator ); // we don't need to analyse this new node because we've already analyzed // the pieces parts as part of the if. And this visitor's method for the BinaryOperator // doesn't really do anything else. Just replace our current node with this // new node node.Parent.ReplaceChild(node, binaryOp); } } } }
private void RunTest(CodeSettings settings) { var source = GetSource(".js"); var expected = GetExpected(".js"); settings = settings ?? new CodeSettings() { MinifyCode = false }; if (source.Length == expected.Length) { for (var ndx = 0; ndx < source.Length; ++ndx) { Trace.WriteLine(""); Trace.WriteLine("----------------------------------------------------------------------------"); Trace.WriteLine(""); // parse the source into an AST var parser = new JSParser(source[ndx]); var block = parser.Parse(settings); // there should only be one statement in the block if (block.Count == 1) { var expression = block[0]; // create the logical-not visitor on the expression var logicalNot = new Microsoft.Ajax.Utilities.LogicalNot(expression, parser); // get the original code var original = expression.ToCode(); Trace.Write("ORIGINAL EXPRESSION: "); Trace.WriteLine(original); // get the measured delta var measuredDelta = logicalNot.Measure(); // perform the logical-not operation logicalNot.Apply(); // get the resulting code -- should still be only one statement in the block var notted = block[0].ToCode(); Trace.Write("LOGICAL-NOT EXPRESSION: "); Trace.WriteLine(notted); Trace.Write("EXPECTED EXPRESSION: "); Trace.WriteLine(expected[ndx]); Trace.Write("DELTA: "); Trace.WriteLine(measuredDelta); // what's the actual difference var actualDelta = notted.Length - original.Length; Assert.IsTrue(actualDelta == measuredDelta, "Measurement was off; calculated {0} but was actually {1}", measuredDelta, actualDelta); Assert.IsTrue(string.CompareOrdinal(expected[ndx], notted) == 0, "Expected output is not the same!!!!"); } else { Assert.Fail(string.Format("Source line {0} parsed to more than one statement!", ndx + 1)); } } } else { Assert.Fail("Input and Expected files have different number of lines!"); } }
public override void Visit(Conditional node) { if (node != null) { // we have two choices for the conditional. Either: // 1. we wrap the whole thing in a logical-not operator, which means we also need to // add parentheses, since conditional is lower-precedence than the logicial not, or // 2. apply the logical-not to both the true- and false-branches. // The first is guaranteed 3 additional characters. We have to check the delta for // each branch and add them together to know how much the second would cost. If it's // greater than 3, then we just want to not the whole thing. var notTrue = new LogicalNot(node.TrueExpression, m_parser); var notFalse = new LogicalNot(node.FalseExpression, m_parser); var costNottingBoth = notTrue.Measure() + notFalse.Measure(); if (m_measure) { // we're just measuring -- adjust the delta accordingly // (the lesser of the two options) m_delta += (costNottingBoth > 3) ? 3 : costNottingBoth; } else if (costNottingBoth > 3) { // just wrap the whole thing WrapWithLogicalNot(node); } else { // less bytes to wrap each branch separately node.TrueExpression.Accept(this); node.FalseExpression.Accept(this); } } }