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); } } }
private void IfConditionExpressionToExpression(IfNode ifNode, AstNode expression) { // 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(ifNode.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(ifNode.Context, m_parser) { Operand1 = ifNode.Condition, Operand2 = expression, OperatorToken = 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 ifNode.Parent.ReplaceChild(ifNode, binaryOp); }
public override void Visit(BinaryOperator node) { if (node != null) { base.Visit(node); // see if this operation is subtracting zero from a lookup -- that is typically done to // coerce a value to numeric. There's a simpler way: unary plus operator. if (node.OperatorToken == JSToken.Minus && m_parser.Settings.IsModificationAllowed(TreeModifications.SimplifyStringToNumericConversion)) { Lookup lookup = node.Operand1 as Lookup; if (lookup != null) { ConstantWrapper right = node.Operand2 as ConstantWrapper; if (right != null && right.IsIntegerLiteral && right.ToNumber() == 0) { // okay, so we have "lookup - 0" // this is done frequently to force a value to be numeric. // There is an easier way: apply the unary + operator to it. // transform: lookup - 0 => +lookup var unary = new UnaryOperator(node.Context, m_parser) { Operand = lookup, OperatorToken = JSToken.Plus }; node.Parent.ReplaceChild(node, unary); // because we recursed at the top of this function, we don't need to Analyze // the new Unary node. This visitor's method for UnaryOperator only does something // if the operand is a constant -- and this one is a Lookup. And we already analyzed // the lookup. } } } else if ((node.OperatorToken == JSToken.StrictEqual || node.OperatorToken == JSToken.StrictNotEqual) && m_parser.Settings.IsModificationAllowed(TreeModifications.ReduceStrictOperatorIfTypesAreSame)) { PrimitiveType leftType = node.Operand1.FindPrimitiveType(); if (leftType != PrimitiveType.Other) { PrimitiveType rightType = node.Operand2.FindPrimitiveType(); if (leftType == rightType) { // the are the same known types. We can reduce the operators node.OperatorToken = node.OperatorToken == JSToken.StrictEqual ? JSToken.Equal : JSToken.NotEqual; } else if (rightType != PrimitiveType.Other) { // they are not the same, but they are both known. We can completely remove the operator // and replace it with true (!==) or false (===). // transform: x !== y => true // transform: x === y => false node.Context.HandleError(JSError.StrictComparisonIsAlwaysTrueOrFalse, false); node.Parent.ReplaceChild( node, new ConstantWrapper(node.OperatorToken == JSToken.StrictNotEqual, PrimitiveType.Boolean, node.Context, m_parser)); // because we are essentially removing the node from the AST, be sure to detach any references DetachReferences.Apply(node); } } } else if (node.IsAssign) { var lookup = node.Operand1 as Lookup; if (lookup != null) { if (lookup.VariableField != null && lookup.VariableField.InitializationOnly) { // the field is an initialization-only field -- we should NOT be assigning to it lookup.Context.HandleError(JSError.AssignmentToConstant, true); } else if (m_scopeStack.Peek().UseStrict) { if (lookup.VariableField == null || lookup.VariableField.FieldType == FieldType.UndefinedGlobal) { // strict mode cannot assign to undefined fields node.Operand1.Context.HandleError(JSError.StrictModeUndefinedVariable, true); } else if(lookup.VariableField.FieldType == FieldType.Arguments || (lookup.VariableField.FieldType == FieldType.Predefined && string.CompareOrdinal(lookup.Name, "eval") == 0)) { // strict mode cannot assign to lookup "eval" or "arguments" node.Operand1.Context.HandleError(JSError.StrictModeInvalidAssign, true); } } } } else if ((node.Parent is Block || (node.Parent is CommaOperator && node.Parent.Parent is Block)) && (node.OperatorToken == JSToken.LogicalOr || node.OperatorToken == JSToken.LogicalAnd)) { // this is an expression statement where the operator is || or && -- basically // it's a shortcut for an if-statement: // expr1&&expr2; ==> if(expr1)expr2; // expr1||expr2; ==> if(!expr1)expr2; // let's check to see if the not of expr1 is smaller. If so, we can not the expression // and change the operator var logicalNot = new LogicalNot(node.Operand1, node.Parser); if (logicalNot.Measure() < 0) { // it would be smaller! Change it. // transform: expr1&&expr2 => !expr1||expr2 // transform: expr1||expr2 => !expr1&&expr2 logicalNot.Apply(); node.OperatorToken = node.OperatorToken == JSToken.LogicalAnd ? JSToken.LogicalOr : JSToken.LogicalAnd; } } } }
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.TrueBlock = null; } if (node.FalseBlock != null && node.FalseBlock.IsDebuggerStatement) { 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.TrueBlock = null; } if (node.FalseBlock != null && node.FalseBlock.Count == 0) { node.FalseBlock = null; } if (node.TrueBlock != null && node.FalseBlock != null) { // neither true block nor false block is null. // if they're both expressions, convert them to a condition operator if (node.TrueBlock.IsExpression && node.FalseBlock.IsExpression && m_parser.Settings.IsModificationAllowed(TreeModifications.IfExpressionsToExpression)) { // 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) { Condition = node.Condition, TrueExpression = node.FalseBlock[0], FalseExpression = node.TrueBlock[0] }; } else { // regular order conditional = new Conditional(node.Context, m_parser) { Condition = node.Condition, TrueExpression = node.TrueBlock[0], FalseExpression = node.FalseBlock[0] }; } node.Parent.ReplaceChild( node, conditional); Optimize(conditional); } else { // 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) { Condition = node.Condition, TrueExpression = trueReturn.Operand, FalseExpression = 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) { Operand = conditional }; node.Parent.ReplaceChild( node, returnNode); Optimize(conditional); } } } } } else if (node.FalseBlock != null) { // true block must be 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.FalseBlock.IsExpression && m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionCallToConditionAndCall)) { // if (cond); else expr ==> cond || expr // but first -- which operator to use? if(a);else b --> a||b, and if(!a);else b --> a&&b // so determine which one is smaller: a or !a // assume we'll use the logical-or, since that doesn't require changing the condition var newOperator = JSToken.LogicalOr; 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.LogicalAnd; } var binaryOp = new BinaryOperator(node.Context, m_parser) { Operand1 = node.Condition, Operand2 = node.FalseBlock[0], OperatorToken = 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); } else if (m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionFalseToIfNotConditionTrue)) { // logical-not the condition // if(cond);else stmt ==> if(!cond)stmt var logicalNot = new LogicalNot(node.Condition, m_parser); logicalNot.Apply(); // and swap the branches node.SwapBranches(); } } else if (node.TrueBlock != null) { // false block must be null if (node.TrueBlock.IsExpression && m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionCallToConditionAndCall)) { // convert the if-node to an expression IfConditionExpressionToExpression(node, node.TrueBlock[0]); } } else if (m_parser.Settings.IsModificationAllowed(TreeModifications.IfEmptyToExpression)) { // NEITHER branches have anything now! // 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. // but how do we KNOW there are no side-effects? // if the condition is a constant or operations on constants, delete it. // or if the condition itself is a debugger statement -- a call, lookup, or member. var remove = node.Condition.IsConstant || node.Condition.IsDebuggerStatement; if (remove) { // we're pretty sure there are no side-effects; remove it altogether node.Parent.ReplaceChild(node, null); } else { // 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 // no need to analyze -- we already recursed node.Parent.ReplaceChild(node, node.Condition); } } if (node.FalseBlock == null && node.TrueBlock != null && node.TrueBlock.Count == 1 && m_parser.Settings.IsModificationAllowed(TreeModifications.CombineNestedIfs)) { var nestedIf = node.TrueBlock[0] as IfNode; if (nestedIf != null && nestedIf.FalseBlock == null) { // we have nested if-blocks. // transform if(cond1)if(cond2){...} to if(cond1&&cond2){...} // change the first if-statement's condition to be cond1&&cond2 // move the nested if-statement's true block to the outer if-statement node.Condition = new BinaryOperator(null, m_parser) { Operand1 = node.Condition, Operand2 = nestedIf.Condition, OperatorToken = JSToken.LogicalAnd }; node.TrueBlock = nestedIf.TrueBlock; } } } }