public override void Visit(JsBinaryOperator node) { if (node != null) { // if this isn't a comma-operator, just recurse normal. // if it is and this is the root block (parent is null) or a function block // or there's already more than one statement in the block, we will want to possibly break // this comma-operator expression statement into separate expression statements. JsBlock parentBlock; if (node.OperatorToken == JsToken.Comma && m_parser.Settings.IsModificationAllowed(JsTreeModifications.UnfoldCommaExpressionStatements) && ((parentBlock = node.Parent as JsBlock) != null) && (parentBlock.Parent == null || parentBlock.Parent is JsFunctionObject || parentBlock.Parent is JsTryNode || parentBlock.Parent is JsSwitchCase || parentBlock.Count > 1)) { // possibly break this one comma statement into multiple statements and recurse PossiblyBreakExpressionStatement(node, parentBlock); } else { // just recurse it normally base.Visit(node); } } }
private void PossiblyBreakExpressionStatement(JsBinaryOperator node, JsBlock parentBlock) { var nodeList = node.Operand2 as JsAstNodeList; if (nodeList != null) { PossiblyBreakExpressionList(node, parentBlock, nodeList); } else { // not a list if (CanBeBroken(node.Operand2)) { // flatten the operator. We have to explicitly recurse the left-hand side. var temp = node.Operand1; parentBlock.ReplaceChild(node, temp); parentBlock.InsertAfter(temp, node.Operand2); temp.Accept(this); } else { // no change; just recurse normally base.Visit(node); } } }
private static void RotateOpeator(JsBinaryOperator node, JsAstNodeList rightSide) { if (rightSide.Count == 0) { // the list is empty -- remove the node altogether node.Parent.ReplaceChild(node, null); } else if (rightSide.Count == 1) { // the list has only one item -- replace the node with the one item node.Parent.ReplaceChild(node, rightSide[0]); } else if (rightSide.Count == 2) { // there are only two items -- rotate the first to the left-hand side // and replace the right-hand side with the second item node.Operand1 = rightSide[0]; node.Operand2 = rightSide[1]; } else { // there will still be more than one left in the list after we peel off the // first one. rotate the first item to the left-hand side var temp = rightSide[0]; rightSide.RemoveAt(0); node.Operand1 = temp; } }
public void Visit(JsBinaryOperator node) { // if there's a left-hand operand, recurse into it if (node != null && node.Operand1 != null) { node.Operand1.Accept(this); } }
private void PossiblyBreakExpressionList(JsBinaryOperator node, JsBlock parentBlock, JsAstNodeList nodeList) { // if the first item can be broken, then we an break it and be done. // otherwise we're going to have to walk until we find a breaking place if (CanBeBroken(nodeList[0])) { // break the first item. insert the left-hand side at our position and // recurse it. Then rotate the node. var index = parentBlock.IndexOf(node); var temp = node.Operand1; RotateOpeator(node, nodeList); parentBlock.Insert(index, temp); // assumes nothing will cause the node to be deleted, because then it // would cause us to miss the following item temp.Accept(this); } else { // the first one can't be broken, so find the first one that can (if any) for (var ndx = 1; ndx < nodeList.Count; ++ndx) { if (CanBeBroken(nodeList[ndx])) { if (ndx == 1) { // the second item is where we are breaking it, so we're going to pull // the first item, replace the list with that first item, then insert // a new comma operator after the current node var temp = nodeList[0]; nodeList.RemoveAt(0); node.Operand2 = temp; // if there's nothing left, then let it die. Otherwise split off // the remainder and insert after the current item. if (nodeList.Count > 0) { parentBlock.InsertAfter(node, CreateSplitNodeFromEnd(nodeList, 0)); } } else { // split off items from the index where we want to split, and insert // it after the current node and leave the node list where it is. parentBlock.InsertAfter(node, CreateSplitNodeFromEnd(nodeList, ndx)); } // and now that we've broken it, bail. break; } } // regardless if anything changed, recurse this node now base.Visit(node); } }
public override void Visit(JsBinaryOperator node) { if (node != null) { // depth-first base.Visit(node); // then evaluate. // do it in a separate method than this one because if this method // allocates a lot of bytes on the stack, we'll overflow our stack for // code that has lots of expression statements that get converted into one // BIG, uber-nested set of comma operators. DoBinaryOperator(node); } }
public override void Visit(JsBinaryOperator node) { if (node != null) { if (m_measure) { // measure MeasureBinaryOperator(node); } else { // convert ConvertBinaryOperator(node); } } }
public void Visit(JsBinaryOperator node) { if (node != null) { if (node.Operand1 != null) { node.Operand1.Accept(this); } if (node.Operand2 != null) { node.Operand2.Accept(this); } node.Index = NextOrderIndex; } }
private void MeasureBinaryOperator(JsBinaryOperator node) { // depending on the operator, calculate the potential difference in length switch (node.OperatorToken) { case JsToken.Equal: case JsToken.NotEqual: case JsToken.StrictEqual: case JsToken.StrictNotEqual: // these operators can be turned into a logical not without any // delta in code size. == becomes !=, etc. break; case JsToken.LessThan: case JsToken.GreaterThan: // these operators would add another character when turnbed into a not. // for example, < becomes >=, etc //++m_delta; //break; case JsToken.LessThanEqual: case JsToken.GreaterThanEqual: // these operators would subtract another character when turnbed into a not. // for example, <= becomes >, etc //--m_delta; //break; case JsToken.Assign: case JsToken.PlusAssign: case JsToken.MinusAssign: case JsToken.MultiplyAssign: case JsToken.DivideAssign: case JsToken.ModuloAssign: case JsToken.BitwiseAndAssign: case JsToken.BitwiseOrAssign: case JsToken.BitwiseXorAssign: case JsToken.LeftShiftAssign: case JsToken.RightShiftAssign: case JsToken.UnsignedRightShiftAssign: case JsToken.BitwiseAnd: case JsToken.BitwiseOr: case JsToken.BitwiseXor: case JsToken.Divide: case JsToken.Multiply: case JsToken.Modulo: case JsToken.Minus: case JsToken.Plus: case JsToken.LeftShift: case JsToken.RightShift: case JsToken.UnsignedRightShift: case JsToken.In: case JsToken.InstanceOf: // these operators have no logical not, which means we need to wrap them in // a unary logical-not operator. And since they have a lower precedence than // the unary logical-not, they'll have to be wrapped in parens. So that means // logical-not'ing these guys adds three characters m_delta += 3; break; case JsToken.Comma: // to logical-not a comma-operator, we just need to logical-not the // right-hand side node.Operand2.Accept(this); break; case JsToken.LogicalAnd: case JsToken.LogicalOr: if (node.Parent is JsBlock || (node.Parent is JsCommaOperator && node.Parent.Parent is JsBlock)) { // if the parent is a block, then this is a simple expression statement: // expr1 || expr2; or expr1 && expr2; If so, then the result isn't // used anywhere and we're just using the || or && operator as a // shorter if-statement. So we don't need to negate the right-hand // side, just the left-hand side. if (node.Operand1 != null) { node.Operand1.Accept(this); } } else { // the logical-not of a logical-and or logical-or operation is the // other operation against the not of each operand. Since the opposite // operator is the same length as this operator, then we just need // to recurse both operands to find the true delta. if (node.Operand1 != null) { node.Operand1.Accept(this); } if (node.Operand2 != null) { node.Operand2.Accept(this); } } break; } }
private void ConvertBinaryOperator(JsBinaryOperator node) { // depending on the operator, perform whatever we need to do to apply a logical // not to the operation switch (node.OperatorToken) { case JsToken.Equal: node.OperatorToken = JsToken.NotEqual; break; case JsToken.NotEqual: node.OperatorToken = JsToken.Equal; break; case JsToken.StrictEqual: node.OperatorToken = JsToken.StrictNotEqual; break; case JsToken.StrictNotEqual: node.OperatorToken = JsToken.StrictEqual; break; case JsToken.LessThan: //node.OperatorToken = JSToken.GreaterThanEqual; //break; case JsToken.GreaterThan: //node.OperatorToken = JSToken.LessThanEqual; //break; case JsToken.LessThanEqual: //node.OperatorToken = JSToken.GreaterThan; //break; case JsToken.GreaterThanEqual: //node.OperatorToken = JSToken.LessThan; //break; case JsToken.Assign: case JsToken.PlusAssign: case JsToken.MinusAssign: case JsToken.MultiplyAssign: case JsToken.DivideAssign: case JsToken.ModuloAssign: case JsToken.BitwiseAndAssign: case JsToken.BitwiseOrAssign: case JsToken.BitwiseXorAssign: case JsToken.LeftShiftAssign: case JsToken.RightShiftAssign: case JsToken.UnsignedRightShiftAssign: case JsToken.BitwiseAnd: case JsToken.BitwiseOr: case JsToken.BitwiseXor: case JsToken.Divide: case JsToken.Multiply: case JsToken.Modulo: case JsToken.Minus: case JsToken.Plus: case JsToken.LeftShift: case JsToken.RightShift: case JsToken.UnsignedRightShift: case JsToken.In: case JsToken.InstanceOf: WrapWithLogicalNot(node); break; case JsToken.Comma: // to logical-not a comma-operator, we just need to logical-not the // right-hand side node.Operand2.Accept(this); break; case JsToken.LogicalAnd: case JsToken.LogicalOr: if (node.Parent is JsBlock || (node.Parent is JsCommaOperator && node.Parent.Parent is JsBlock)) { // if the parent is a block, then this is a simple expression statement: // expr1 || expr2; or expr1 && expr2; If so, then the result isn't // used anywhere and we're just using the || or && operator as a // shorter if-statement. So we don't need to negate the right-hand // side, just the left-hand side. if (node.Operand1 != null) { node.Operand1.Accept(this); } } else { // the logical-not of a logical-and or logical-or operation is the // other operation against the not of each operand. Since the opposite // operator is the same length as this operator, then we just need // to recurse both operands and swap the operator token if (node.Operand1 != null) { node.Operand1.Accept(this); } if (node.Operand2 != null) { node.Operand2.Accept(this); } } node.OperatorToken = node.OperatorToken == JsToken.LogicalAnd ? JsToken.LogicalOr : JsToken.LogicalAnd; break; } }
public void Visit(JsBinaryOperator node) { // lesser precedence than the new operator; use parens m_needsParens = true; }
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; } }
public void Visit(JsBinaryOperator node) { if (node != null) { var symbol = StartSymbol(node); if (node.OperatorToken == JsToken.Comma) { // output the left-hand operand, if we have one if (node.Operand1 != null) { node.Operand1.Accept(this); SetContextOutputPosition(node.Context, node.Operand1.Context); // if we don't have a right-hand operator, don't bother with the comma if (node.Operand2 != null) { OutputPossibleLineBreak(','); MarkSegment(node, null, node.Operand1.TerminatingContext); m_startOfStatement = false; // if the parent is a block, then the comma operator is separating // expression statements -- so break it on the line if (node.Parent is JsBlock) { NewLine(); } else if (m_settings.OutputMode == MinifierOutputMode.MultipleLines) { OutputPossibleLineBreak(' '); } } } // output the right-hand operator, if we have one if (node.Operand2 != null) { node.Operand2.Accept(this); m_startOfStatement = false; } } else { var ourPrecedence = node.Precedence; var isNoIn = m_noIn; if (isNoIn) { if (node.OperatorToken == JsToken.In) { // we're in a no-in situation, but our operator is an in-operator. // so we need to wrap this operator in parens OutputPossibleLineBreak('('); m_noIn = false; } else { m_noIn = ourPrecedence <= JsOperatorPrecedence.Relational; } } if (node.Operand1 != null) { AcceptNodeWithParens(node.Operand1, node.Operand1.Precedence < ourPrecedence); SetContextOutputPosition(node.Context, node.Operand1.Context); } m_startOfStatement = false; if (m_settings.OutputMode == MinifierOutputMode.MultipleLines) { // treat the comma-operator special, since we combine expression statements // with it very often if (node.OperatorToken != JsToken.Comma) { // anything other than a comma operator has a space before it, too OutputPossibleLineBreak(' '); } Output(OperatorString(node.OperatorToken)); MarkSegment(node, null, node.OperatorContext); BreakLine(false); if (!m_onNewLine) { OutputPossibleLineBreak(' '); } } else { Output(OperatorString(node.OperatorToken)); MarkSegment(node, null, node.OperatorContext); BreakLine(false); } if (node.OperatorToken == JsToken.Divide) { // add a function that will check if the next character is also // a forward slash. If it is, the output methods will separate them // with a space so they don't get interpreted as the start of a // single-line comment. m_addSpaceIfTrue = c => c == '/'; } if (node.Operand2 != null) { var rightPrecedence = node.Operand2.Precedence; var rightNeedsParens = rightPrecedence < ourPrecedence; var rightHandBinary = node.Operand2 as JsBinaryOperator; if (rightHandBinary != null) { // they are BOTH binary expressions. This is where it gets complicated. // because most binary tokens (except assignment) are evaluated from left to right, // if we have a binary expression with the same precedence on the RIGHT, then that means the // developer must've put parentheses around it. For some operators, those parentheses // may not be needed (associative operators like multiply and logical AND or logical OR). // Non-associative operators (divide) will need those parens, so we will want to say they // are a higher relative precedence because of those parentheses. // The plus operator is a special case. It is the same physical token, but it can be two // operations depending on the runtime data: numeric addition or string concatenation. // Because of that ambiguity, let's also calculate the precedence for it as if it were // non-associate as well. // commas never need the parens -- they always evaluate left to right and always return the // right value, so any parens will always be unneccessary. if (ourPrecedence == rightPrecedence && ourPrecedence != JsOperatorPrecedence.Assignment && ourPrecedence != JsOperatorPrecedence.Comma) { if (node.OperatorToken == rightHandBinary.OperatorToken) { // the tokens are the same and we're not assignment or comma operators. // so for a few associative operators, we're going to say the relative precedence // is the same so unneeded parens are removed. But for all others, we'll say the // right-hand side is a higher precedence so we maintain the sematic structure // of the expression switch (node.OperatorToken) { case JsToken.Multiply: case JsToken.BitwiseAnd: case JsToken.BitwiseXor: case JsToken.BitwiseOr: case JsToken.LogicalAnd: case JsToken.LogicalOr: // these are the same regardless rightNeedsParens = false; break; // TODO: the plus operator: if we can prove that it is a numeric operator // or a string operator on BOTH sides, then it can be associative, too. But // if one side is a string and the other numeric, or if we can't tell at // compile-time, then we need to preserve the structural precedence. default: // all other operators are structurally a lower precedence when they // are on the right, so they need to be evaluated first rightNeedsParens = true; break; } } else { // they have the same precedence, but the tokens are different. // and the developer had purposely put parens around the right-hand side // to get them on the right (otherwise with the same precedence they // would've ended up on the left. Keep the parens; must've been done for // a purpose. rightNeedsParens = true; } } else { // different precedence -- just base the decision on the relative precedence values rightNeedsParens = rightPrecedence < ourPrecedence; } } m_noIn = isNoIn && ourPrecedence <= JsOperatorPrecedence.Relational; AcceptNodeWithParens(node.Operand2, rightNeedsParens); } if (isNoIn && node.OperatorToken == JsToken.In) { // we're in a no-in situation, but our operator is an in-operator. // so we need to wrap this entire operator in parens OutputPossibleLineBreak(')'); } m_noIn = isNoIn; EndSymbol(symbol); } } }
public void Visit(JsBinaryOperator node) { // invalid! ignore IsValid = false; }
private void EvalFarToTheLeft(JsBinaryOperator node, JsConstantWrapper thisConstant, JsConstantWrapper otherConstant, JsBinaryOperator leftOperator) { if (leftOperator.OperatorToken == JsToken.Minus) { if (node.OperatorToken == JsToken.Plus) { // minus-plus // the minus will be a numeric operator, but if this constant is a string, it will be a // string concatenation and we can't combine it. if (thisConstant.PrimitiveType != JsPrimitiveType.String && thisConstant.PrimitiveType != JsPrimitiveType.Other) { JsConstantWrapper newLiteral = NumericAddition(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { RotateFromRight(node, leftOperator, newLiteral); } } } else if (node.OperatorToken == JsToken.Minus) { // minus-minus JsConstantWrapper newLiteral = Minus(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { RotateFromRight(node, leftOperator, newLiteral); } } } else if (node.OperatorToken == JsToken.Multiply) { if (leftOperator.OperatorToken == JsToken.Multiply || leftOperator.OperatorToken == JsToken.Divide) { JsConstantWrapper newLiteral = Multiply(otherConstant, thisConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(otherConstant, thisConstant, newLiteral)) { RotateFromRight(node, leftOperator, newLiteral); } } } else if (node.OperatorToken == JsToken.Divide) { if (leftOperator.OperatorToken == JsToken.Divide) { // divide-divide JsConstantWrapper newLiteral = Divide(otherConstant, thisConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(otherConstant, thisConstant, newLiteral) && newLiteral.ToCode().Length <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { RotateFromRight(node, leftOperator, newLiteral); } } else if (leftOperator.OperatorToken == JsToken.Multiply) { // mult-divide JsConstantWrapper otherOverThis = Divide(otherConstant, thisConstant); JsConstantWrapper thisOverOther = Divide(thisConstant, otherConstant); int otherOverThisLength = otherOverThis != null ? otherOverThis.ToCode().Length : int.MaxValue; int thisOverOtherLength = thisOverOther != null ? thisOverOther.ToCode().Length : int.MaxValue; if (otherOverThis != null && NoMultiplicativeOverOrUnderFlow(otherConstant, thisConstant, otherOverThis) && (thisOverOther == null || otherOverThisLength < thisOverOtherLength)) { if (otherOverThisLength <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { RotateFromRight(node, leftOperator, otherOverThis); } } else if (thisOverOther != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, thisOverOther)) { if (thisOverOtherLength <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { // swap the operands leftOperator.SwapOperands(); // operator is the opposite leftOperator.OperatorToken = JsToken.Divide; RotateFromLeft(node, leftOperator, thisOverOther); } } } } }
private void EvalFarToTheRight(JsBinaryOperator node, JsConstantWrapper thisConstant, JsConstantWrapper otherConstant, JsBinaryOperator rightOperator) { if (rightOperator.OperatorToken == JsToken.Minus) { if (node.OperatorToken == JsToken.Plus) { // plus-minus // our constant cannot be a string, though if (!thisConstant.IsStringLiteral) { JsConstantWrapper newLiteral = Minus(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { RotateFromLeft(node, rightOperator, newLiteral); } } } else if (node.OperatorToken == JsToken.Minus) { // minus-minus JsConstantWrapper newLiteral = NumericAddition(thisConstant, otherConstant); if (newLiteral != null && NoOverflow(newLiteral)) { // but we need to swap the left and right operands first rightOperator.SwapOperands(); // then rotate the node up after replacing old with new RotateFromRight(node, rightOperator, newLiteral); } } } else if (node.OperatorToken == JsToken.Multiply) { if (rightOperator.OperatorToken == JsToken.Multiply) { // mult-mult JsConstantWrapper newLiteral = Multiply(thisConstant, otherConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, newLiteral)) { RotateFromLeft(node, rightOperator, newLiteral); } } else if (rightOperator.OperatorToken == JsToken.Divide) { // mult-divide JsConstantWrapper otherOverThis = Divide(otherConstant, thisConstant); JsConstantWrapper thisOverOther = Divide(thisConstant, otherConstant); int otherOverThisLength = otherOverThis != null ? otherOverThis.ToCode().Length : int.MaxValue; int thisOverOtherLength = thisOverOther != null ? thisOverOther.ToCode().Length : int.MaxValue; if (otherOverThis != null && NoMultiplicativeOverOrUnderFlow(otherConstant, thisConstant, otherOverThis) && (thisOverOther == null || otherOverThisLength < thisOverOtherLength)) { if (otherOverThisLength <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { // swap the operands, but keep the operator RotateFromLeft(node, rightOperator, otherOverThis); } } else if (thisOverOther != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, thisOverOther)) { if (thisOverOtherLength <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { // swap the operands and opposite operator rightOperator.SwapOperands(); rightOperator.OperatorToken = JsToken.Multiply; RotateFromRight(node, rightOperator, thisOverOther); } } } } else if (node.OperatorToken == JsToken.Divide) { if (rightOperator.OperatorToken == JsToken.Multiply) { // divide-mult JsConstantWrapper newLiteral = Divide(thisConstant, otherConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, newLiteral) && newLiteral.ToCode().Length <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { // swap the operands rightOperator.SwapOperands(); // change the operator rightOperator.OperatorToken = JsToken.Divide; RotateFromRight(node, rightOperator, newLiteral); } } else if (rightOperator.OperatorToken == JsToken.Divide) { // divide-divide JsConstantWrapper newLiteral = Multiply(thisConstant, otherConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, newLiteral)) { // but we need to swap the left and right operands first rightOperator.SwapOperands(); // then rotate the node up after replacing old with new RotateFromRight(node, rightOperator, newLiteral); } } } }
public override void Visit(JsBinaryOperator 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(JsTreeModifications.SimplifyStringToNumericConversion)) { JsLookup lookup = node.Operand1 as JsLookup; if (lookup != null) { JsConstantWrapper right = node.Operand2 as JsConstantWrapper; 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 JsUnaryOperator(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(JsTreeModifications.ReduceStrictOperatorIfTypesAreSame)) { JsPrimitiveType leftType = node.Operand1.FindPrimitiveType(); if (leftType != JsPrimitiveType.Other) { JsPrimitiveType 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 != JsPrimitiveType.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 JsConstantWrapper(node.OperatorToken == JsToken.StrictNotEqual, JsPrimitiveType.Boolean, node.Context, m_parser)); // because we are essentially removing the node from the AST, be sure to detach any references JsDetachReferences.Apply(node); } } } else if (node.IsAssign) { var lookup = node.Operand1 as JsLookup; 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 == JsFieldType.UndefinedGlobal) { // strict mode cannot assign to undefined fields node.Operand1.Context.HandleError(JsError.StrictModeUndefinedVariable, true); } else if(lookup.VariableField.FieldType == JsFieldType.Arguments || (lookup.VariableField.FieldType == JsFieldType.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 JsBlock || (node.Parent is JsCommaOperator && node.Parent.Parent is JsBlock)) && (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 JsLogicalNot(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; } } } }
private void Optimize(JsConditional node) { // now check to see if the condition starts with a not-operator. If so, we can get rid of it // and swap the true/false children var unary = node.Condition as JsUnaryOperator; if (unary != null && unary.OperatorToken == JsToken.LogicalNot && !unary.OperatorInConditionalCompilationComment && m_parser.Settings.IsModificationAllowed(JsTreeModifications.IfNotTrueFalseToIfFalseTrue)) { // get rid of the not by replacing it with its operand // and swap the branches node.Condition = unary.Operand; node.SwapBranches(); } // see if the two branches are both assignment operations to the same variable. // if so, we can pull the assignment outside the conditional and have the conditional // be the assignment var trueAssign = node.TrueExpression as JsBinaryOperator; if (trueAssign != null && trueAssign.IsAssign) { var falseAssign = node.FalseExpression as JsBinaryOperator; if (falseAssign != null && falseAssign.OperatorToken == trueAssign.OperatorToken) { // see if the left-hand-side is equivalent if (trueAssign.Operand1.IsEquivalentTo(falseAssign.Operand1)) { // we're going to be getting rid of the left-hand side in the false-block, // so we need to remove any references it may represent JsDetachReferences.Apply(falseAssign.Operand1); // transform: cond?lhs=expr1:lhs=expr2 to lhs=cond?expr1:expr2s var binaryOp = new JsBinaryOperator(node.Context, m_parser) { Operand1 = trueAssign.Operand1, Operand2 = new JsConditional(node.Context, m_parser) { Condition = node.Condition, QuestionContext = node.QuestionContext, TrueExpression = trueAssign.Operand2, ColonContext = node.ColonContext, FalseExpression = falseAssign.Operand2 }, OperatorContext = trueAssign.OperatorContext, OperatorToken = trueAssign.OperatorToken, TerminatingContext = node.TerminatingContext }; node.Parent.ReplaceChild(node, binaryOp); } } } }
private void IfConditionExpressionToExpression(JsIfNode ifNode, JsAstNode 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 JsLogicalNot(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 JsBinaryOperator(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); }
private static bool AreAssignmentsInVar(JsBinaryOperator binaryOp, JsVar varStatement) { bool areAssignmentsInVar = false; if (binaryOp != null) { // we only want to pop positive for the simple assign (=). If it's any of the // complex assigns (+=, -=, etc) then we don't want to combine them. if (binaryOp.OperatorToken == JsToken.Assign) { // see if the left-hand side is a simple lookup JsLookup lookup = binaryOp.Operand1 as JsLookup; if (lookup != null) { // it is. see if that variable is in the previous var statement areAssignmentsInVar = varStatement.Contains(lookup.Name); } } else if (binaryOp.OperatorToken == JsToken.Comma) { // this is a comma operator, so we will return true only if both // left and right operators are assignments to vars defined in the // var statement areAssignmentsInVar = AreAssignmentsInVar(binaryOp.Operand1 as JsBinaryOperator, varStatement) && AreAssignmentsInVar(binaryOp.Operand2 as JsBinaryOperator, varStatement); } } return areAssignmentsInVar; }
public override void Visit(JsIfNode node) { if (node != null) { if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(JsTreeModifications.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(JsTreeModifications.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. JsConditional conditional; var logicalNot = new JsLogicalNot(node.Condition, m_parser); if (logicalNot.Measure() < 0) { // applying a logical-not makes the condition smaller -- reverse the branches logicalNot.Apply(); conditional = new JsConditional(node.Context, m_parser) { Condition = node.Condition, TrueExpression = node.FalseBlock[0], FalseExpression = node.TrueBlock[0] }; } else { // regular order conditional = new JsConditional(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 JsLogicalNot(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 JsReturnNode; if (trueReturn != null && trueReturn.Operand != null) { // it is -- see if the false-branch is also a return statement var falseReturn = node.FalseBlock[0] as JsReturnNode; if (falseReturn != null && falseReturn.Operand != null) { // transform: if(cond)return expr1;else return expr2 to return cond?expr1:expr2 var conditional = new JsConditional(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 JsReturnNode(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(JsTreeModifications.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 JsLogicalNot(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 JsBinaryOperator(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(JsTreeModifications.IfConditionFalseToIfNotConditionTrue)) { // logical-not the condition // if(cond);else stmt ==> if(!cond)stmt var logicalNot = new JsLogicalNot(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(JsTreeModifications.IfConditionCallToConditionAndCall)) { // convert the if-node to an expression IfConditionExpressionToExpression(node, node.TrueBlock[0]); } } else if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.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(JsTreeModifications.CombineNestedIfs)) { var nestedIf = node.TrueBlock[0] as JsIfNode; 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 JsBinaryOperator(null, m_parser) { Operand1 = node.Condition, Operand2 = nestedIf.Condition, OperatorToken = JsToken.LogicalAnd }; node.TrueBlock = nestedIf.TrueBlock; } } } }
/// <summary> /// We have determined that our right-hand operand is another binary operator, and its /// left-hand operand is a constant that can be combined with our left-hand operand. /// Now we want to set the left-hand operand of that other operator to the newly- /// combined constant value, and then rotate it up -- replace our binary operator /// with this newly-modified binary operator, and then attempt to re-evaluate it. /// </summary> /// <param name="binaryOp">the binary operator that is our right-hand operand</param> /// <param name="newLiteral">the newly-combined literal</param> /// <param name="node"></param> private void RotateFromRight(JsBinaryOperator node, JsBinaryOperator binaryOp, JsConstantWrapper newLiteral) { // replace our node with the binary operator binaryOp.Operand1 = newLiteral; node.Parent.ReplaceChild(node, binaryOp); // and just for good measure.. revisit the node that's taking our place, since // we just changed a constant value. Assuming the other operand is a constant, too. JsConstantWrapper otherConstant = binaryOp.Operand2 as JsConstantWrapper; if (otherConstant != null) { EvalThisOperator(binaryOp, newLiteral, otherConstant); } }
public void Visit(JsBinaryOperator node) { // not applicable; terminate }
private void DoBinaryOperator(JsBinaryOperator node) { if (m_parser.Settings.EvalLiteralExpressions) { // if this is an assign operator, an in, or an instanceof, then we won't // try to evaluate it if (!node.IsAssign && node.OperatorToken != JsToken.In && node.OperatorToken != JsToken.InstanceOf) { if (node.OperatorToken == JsToken.StrictEqual || node.OperatorToken == JsToken.StrictNotEqual) { // the operator is a strict equality (or not-equal). // check the primitive types of the two operands -- if they are known but not the same, we can // shortcut the whole process by just replacing this node with a boolean literal. var leftType = node.Operand1.FindPrimitiveType(); if (leftType != JsPrimitiveType.Other) { var rightType = node.Operand2.FindPrimitiveType(); if (rightType != JsPrimitiveType.Other) { // both sides are known if (leftType != rightType) { // they are not the same type -- replace with a boolean and bail ReplaceNodeWithLiteral( node, new JsConstantWrapper(node.OperatorToken == JsToken.StrictEqual ? false : true, JsPrimitiveType.Boolean, node.Context, m_parser)); return; } // they are the same type -- we can change the operator to simple equality/not equality node.OperatorToken = node.OperatorToken == JsToken.StrictEqual ? JsToken.Equal : JsToken.NotEqual; } } } // see if the left operand is a literal number, boolean, string, or null JsConstantWrapper left = node.Operand1 as JsConstantWrapper; if (left != null) { if (node.OperatorToken == JsToken.Comma) { // the comma operator evaluates the left, then evaluates the right and returns it. // but if the left is a literal, evaluating it doesn't DO anything, so we can replace the // entire operation with the right-hand operand JsConstantWrapper rightConstant = node.Operand2 as JsConstantWrapper; if (rightConstant != null) { // we'll replace the operator with the right-hand operand, BUT it's a constant, too. // first check to see if replacing this node with a constant will result in creating // a member-bracket operator that can be turned into a member-dot. If it is, then that // method will handle the replacement. But if it doesn't, then we should just replace // the comma with the right-hand operand. if (!ReplaceMemberBracketWithDot(node, rightConstant)) { ReplaceNodeWithLiteral(node, rightConstant); } } else if (node is JsCommaOperator) { // this is a collection of expression statements that we've joined together as // an extended comma operator. var list = node.Operand2 as JsAstNodeList; if (list == null) { // not a list, just a single item, so we can just // replace this entire node with the one element ReplaceNodeCheckParens(node, node.Operand2); } else if (list.Count == 1) { // If the list has a single element, then we can just // replace this entire node with the one element ReplaceNodeCheckParens(node, list[0]); } else if (list.Count == 0) { // the recursion ended up emptying the list, so we can just delete // this node altogether ReplaceNodeCheckParens(node, null); } else { // more than one item in the list // move the first item from the list to the left-hand side var firstItem = list[0]; list.RemoveAt(0); node.Operand1 = firstItem; // if there's only one item left in the list, we can get rid of the // extra list node and make it just the remaining node if (list.Count == 1) { firstItem = list[0]; list.RemoveAt(0); node.Operand2 = firstItem; } } } else { // replace the comma operator with the right-hand operand ReplaceNodeCheckParens(node, node.Operand2); } } else { // see if the right operand is a literal number, boolean, string, or null JsConstantWrapper right = node.Operand2 as JsConstantWrapper; if (right != null) { // then they are both constants and we can evaluate the operation EvalThisOperator(node, left, right); } else { // see if the right is a binary operator that can be combined with ours JsBinaryOperator rightBinary = node.Operand2 as JsBinaryOperator; if (rightBinary != null) { JsConstantWrapper rightLeft = rightBinary.Operand1 as JsConstantWrapper; if (rightLeft != null) { // eval our left and the right-hand binary's left and put the combined operation as // the child of the right-hand binary EvalToTheRight(node, left, rightLeft, rightBinary); } else { JsConstantWrapper rightRight = rightBinary.Operand2 as JsConstantWrapper; if (rightRight != null) { EvalFarToTheRight(node, left, rightRight, rightBinary); } } } } } } else { // left is not a constantwrapper. See if the right is JsConstantWrapper right = node.Operand2 as JsConstantWrapper; if (right != null) { // the right is a constant. See if the the left is a binary operator... JsBinaryOperator leftBinary = node.Operand1 as JsBinaryOperator; if (leftBinary != null) { // ...with a constant on the right, and the operators can be combined JsConstantWrapper leftRight = leftBinary.Operand2 as JsConstantWrapper; if (leftRight != null) { EvalToTheLeft(node, right, leftRight, leftBinary); } else { JsConstantWrapper leftLeft = leftBinary.Operand1 as JsConstantWrapper; if (leftLeft != null) { EvalFarToTheLeft(node, right, leftLeft, leftBinary); } } } else if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.SimplifyStringToNumericConversion)) { // see if it's a lookup and this is a minus operation and the constant is a zero JsLookup lookup = node.Operand1 as JsLookup; if (lookup != null && node.OperatorToken == JsToken.Minus && 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. var unary = new JsUnaryOperator(node.Context, m_parser) { Operand = lookup, OperatorToken = JsToken.Plus }; ReplaceNodeCheckParens(node, unary); } } } // TODO: shouldn't we check if they BOTH are binary operators? (a*6)*(5*b) ==> a*30*b (for instance) } } } }
private void EvalThisOperator(JsBinaryOperator node, JsConstantWrapper left, JsConstantWrapper right) { // we can evaluate these operators if we know both operands are literal // number, boolean, string or null JsConstantWrapper newLiteral = null; switch (node.OperatorToken) { case JsToken.Multiply: newLiteral = Multiply(left, right); break; case JsToken.Divide: newLiteral = Divide(left, right); if (newLiteral != null && newLiteral.ToCode().Length > node.ToCode().Length) { // the result is bigger than the expression. // eg: 1/3 is smaller than .333333333333333 // never mind. newLiteral = null; } break; case JsToken.Modulo: newLiteral = Modulo(left, right); if (newLiteral != null && newLiteral.ToCode().Length > node.ToCode().Length) { // the result is bigger than the expression. // eg: 46.5%6.3 is smaller than 2.4000000000000012 // never mind. newLiteral = null; } break; case JsToken.Plus: newLiteral = Plus(left, right); break; case JsToken.Minus: newLiteral = Minus(left, right); break; case JsToken.LeftShift: newLiteral = LeftShift(left, right); break; case JsToken.RightShift: newLiteral = RightShift(left, right); break; case JsToken.UnsignedRightShift: newLiteral = UnsignedRightShift(left, right); break; case JsToken.LessThan: newLiteral = LessThan(left, right); break; case JsToken.LessThanEqual: newLiteral = LessThanOrEqual(left, right); break; case JsToken.GreaterThan: newLiteral = GreaterThan(left, right); break; case JsToken.GreaterThanEqual: newLiteral = GreaterThanOrEqual(left, right); break; case JsToken.Equal: newLiteral = Equal(left, right); break; case JsToken.NotEqual: newLiteral = NotEqual(left, right); break; case JsToken.StrictEqual: newLiteral = StrictEqual(left, right); break; case JsToken.StrictNotEqual: newLiteral = StrictNotEqual(left, right); break; case JsToken.BitwiseAnd: newLiteral = BitwiseAnd(left, right); break; case JsToken.BitwiseOr: newLiteral = BitwiseOr(left, right); break; case JsToken.BitwiseXor: newLiteral = BitwiseXor(left, right); break; case JsToken.LogicalAnd: newLiteral = LogicalAnd(left, right); break; case JsToken.LogicalOr: newLiteral = LogicalOr(left, right); break; default: // an operator we don't want to evaluate break; } // if we can combine them... if (newLiteral != null) { // first we want to check if the new combination is a string literal, and if so, whether // it's now the sole parameter of a member-bracket call operator. If so, instead of replacing our // binary operation with the new constant, we'll replace the entire call with a member-dot // expression if (!ReplaceMemberBracketWithDot(node, newLiteral)) { ReplaceNodeWithLiteral(node, newLiteral); } } }
private static void ConvertAssignmentsToVarDecls(JsBinaryOperator binaryOp, List<JsVariableDeclaration> varDecls, JsParser parser) { // we've already checked that the tree only contains simple assignments separate by commas, // but just in case we'll check for null anyway if (binaryOp != null) { if (binaryOp.OperatorToken == JsToken.Assign) { // we've already cleared this as a simple lookup, but run the check just to be sure JsLookup lookup = binaryOp.Operand1 as JsLookup; if (lookup != null) { var varDecl = new JsVariableDeclaration(binaryOp.Context.Clone(), parser) { Identifier = lookup.Name, NameContext = lookup.Context.Clone(), AssignContext = binaryOp.OperatorContext, Initializer = binaryOp.Operand2, VariableField = lookup.VariableField }; varDecl.VariableField.Declarations.Add(varDecl); varDecls.Add(varDecl); } } else if (binaryOp.OperatorToken == JsToken.Comma) { // recurse both operands ConvertAssignmentsToVarDecls(binaryOp.Operand1 as JsBinaryOperator, varDecls, parser); ConvertAssignmentsToVarDecls(binaryOp.Operand2 as JsBinaryOperator, varDecls, parser); } // shouldn't ever be anything but these two operators } }
private void EvalToTheLeft(JsBinaryOperator node, JsConstantWrapper thisConstant, JsConstantWrapper otherConstant, JsBinaryOperator leftOperator) { if (leftOperator.OperatorToken == JsToken.Plus && node.OperatorToken == JsToken.Plus) { // plus-plus // the other operation goes first, so if the other constant is a string, then we know that // operation will do a string concatenation, which will force our operation to be a string // concatenation. If the other constant is not a string, then we won't know until runtime and // we can't combine them. if (otherConstant.IsStringLiteral) { // the other constant is a string -- so we can do the string concat and combine them JsConstantWrapper newLiteral = StringConcat(otherConstant, thisConstant); if (newLiteral != null) { RotateFromLeft(node, leftOperator, newLiteral); } } } else if (leftOperator.OperatorToken == JsToken.Minus) { if (node.OperatorToken == JsToken.Plus) { // minus-plus // the minus operator goes first and will always convert to number. // if our constant is not a string, then it will be a numeric addition and we can combine them. // if our constant is a string, then we'll end up doing a string concat, so we can't combine if (!thisConstant.IsStringLiteral) { // two numeric operators. a-n1+n2 is the same as a-(n1-n2) JsConstantWrapper newLiteral = Minus(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { // a-(-n) is numerically equivalent as a+n -- and takes fewer characters to represent. // BUT we can't do that because that might change a numeric operation (the original minus) // to a string concatenation if the unknown operand turns out to be a string! RotateFromLeft(node, leftOperator, newLiteral); } else { // if the left-left is a constant, then we can try combining with it JsConstantWrapper leftLeft = leftOperator.Operand1 as JsConstantWrapper; if (leftLeft != null) { EvalFarToTheLeft(node, thisConstant, leftLeft, leftOperator); } } } } else if (node.OperatorToken == JsToken.Minus) { // minus-minus. Both operations are numeric. // (a-n1)-n2 => a-(n1+n2), so we can add the two constants and subtract from // the left-hand non-constant. JsConstantWrapper newLiteral = NumericAddition(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { // make it the new right-hand literal for the left-hand operator // and make the left-hand operator replace our operator RotateFromLeft(node, leftOperator, newLiteral); } else { // if the left-left is a constant, then we can try combining with it JsConstantWrapper leftLeft = leftOperator.Operand1 as JsConstantWrapper; if (leftLeft != null) { EvalFarToTheLeft(node, thisConstant, leftLeft, leftOperator); } } } } else if (leftOperator.OperatorToken == node.OperatorToken && (node.OperatorToken == JsToken.Multiply || node.OperatorToken == JsToken.Divide)) { // either multiply-multiply or divide-divide // either way, we use the other operand and the product of the two constants. // if the product blows up to an infinte value, then don't combine them because that // could change the way the program goes at runtime, depending on the unknown value. JsConstantWrapper newLiteral = Multiply(otherConstant, thisConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(otherConstant, thisConstant, newLiteral)) { RotateFromLeft(node, leftOperator, newLiteral); } } else if ((leftOperator.OperatorToken == JsToken.Multiply && node.OperatorToken == JsToken.Divide) || (leftOperator.OperatorToken == JsToken.Divide && node.OperatorToken == JsToken.Multiply)) { if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { // get the two division operators JsConstantWrapper otherOverThis = Divide(otherConstant, thisConstant); JsConstantWrapper thisOverOther = Divide(thisConstant, otherConstant); // get the lengths int otherOverThisLength = otherOverThis != null ? otherOverThis.ToCode().Length : int.MaxValue; int thisOverOtherLength = thisOverOther != null ? thisOverOther.ToCode().Length : int.MaxValue; // we'll want to use whichever one is shorter, and whichever one does NOT involve an overflow // or possible underflow if (otherOverThis != null && NoMultiplicativeOverOrUnderFlow(otherConstant, thisConstant, otherOverThis) && (thisOverOther == null || otherOverThisLength < thisOverOtherLength)) { // but only if it's smaller than the original expression if (otherOverThisLength <= otherConstant.ToCode().Length + thisConstant.ToCode().Length + 1) { // same operator RotateFromLeft(node, leftOperator, otherOverThis); } } else if (thisOverOther != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, thisOverOther)) { // but only if it's smaller than the original expression if (thisOverOtherLength <= otherConstant.ToCode().Length + thisConstant.ToCode().Length + 1) { // opposite operator leftOperator.OperatorToken = leftOperator.OperatorToken == JsToken.Multiply ? JsToken.Divide : JsToken.Multiply; RotateFromLeft(node, leftOperator, thisOverOther); } } } } else if (node.OperatorToken == leftOperator.OperatorToken && (node.OperatorToken == JsToken.BitwiseAnd || node.OperatorToken == JsToken.BitwiseOr || node.OperatorToken == JsToken.BitwiseXor)) { // identical bitwise operators can be combined JsConstantWrapper newLiteral = null; switch (node.OperatorToken) { case JsToken.BitwiseAnd: newLiteral = BitwiseAnd(otherConstant, thisConstant); break; case JsToken.BitwiseOr: newLiteral = BitwiseOr(otherConstant, thisConstant); break; case JsToken.BitwiseXor: newLiteral = BitwiseXor(otherConstant, thisConstant); break; } if (newLiteral != null) { RotateFromLeft(node, leftOperator, newLiteral); } } }
private void EvalToTheRight(JsBinaryOperator node, JsConstantWrapper thisConstant, JsConstantWrapper otherConstant, JsBinaryOperator rightOperator) { if (node.OperatorToken == JsToken.Plus) { if (rightOperator.OperatorToken == JsToken.Plus && otherConstant.IsStringLiteral) { // plus-plus, and the other constant is a string. So the right operator will be a string-concat // that generates a string. And since this is a plus-operator, then this operator will be a string- // concat as well. So we can just combine the strings now and replace our node with the right-hand // operation JsConstantWrapper newLiteral = StringConcat(thisConstant, otherConstant); if (newLiteral != null) { RotateFromRight(node, rightOperator, newLiteral); } } else if (rightOperator.OperatorToken == JsToken.Minus && !thisConstant.IsStringLiteral) { // plus-minus. Now, the minus operation happens first, and it will perform a numeric // operation. The plus is NOT string, so that means it will also be a numeric operation // and we can combine the operators numericly. JsConstantWrapper newLiteral = NumericAddition(thisConstant, otherConstant); if (newLiteral != null && NoOverflow(newLiteral)) { RotateFromRight(node, rightOperator, newLiteral); } else { JsConstantWrapper rightRight = rightOperator.Operand2 as JsConstantWrapper; if (rightRight != null) { EvalFarToTheRight(node, thisConstant, rightRight, rightOperator); } } } } else if (node.OperatorToken == JsToken.Minus && rightOperator.OperatorToken == JsToken.Minus) { // minus-minus // both operations are numeric, so we can combine the constant operands. However, we // can't combine them into a plus, so make sure we do the minus in the opposite direction JsConstantWrapper newLiteral = Minus(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { rightOperator.SwapOperands(); RotateFromLeft(node, rightOperator, newLiteral); } else { JsConstantWrapper rightRight = rightOperator.Operand2 as JsConstantWrapper; if (rightRight != null) { EvalFarToTheRight(node, thisConstant, rightRight, rightOperator); } } } else if (node.OperatorToken == JsToken.Multiply && (rightOperator.OperatorToken == JsToken.Multiply || rightOperator.OperatorToken == JsToken.Divide)) { // multiply-divide or multiply-multiply // multiply the operands and use the right-hand operator JsConstantWrapper newLiteral = Multiply(thisConstant, otherConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, newLiteral)) { RotateFromRight(node, rightOperator, newLiteral); } } else if (node.OperatorToken == JsToken.Divide) { if (rightOperator.OperatorToken == JsToken.Multiply) { // divide-multiply JsConstantWrapper newLiteral = Divide(thisConstant, otherConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, newLiteral) && newLiteral.ToCode().Length < thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { // flip the operator: multiply becomes divide; devide becomes multiply rightOperator.OperatorToken = JsToken.Divide; RotateFromRight(node, rightOperator, newLiteral); } } else if (rightOperator.OperatorToken == JsToken.Divide) { // divide-divide // get constants for left/right and for right/left JsConstantWrapper leftOverRight = Divide(thisConstant, otherConstant); JsConstantWrapper rightOverLeft = Divide(otherConstant, thisConstant); // get the lengths of the resulting code int leftOverRightLength = leftOverRight != null ? leftOverRight.ToCode().Length : int.MaxValue; int rightOverLeftLength = rightOverLeft != null ? rightOverLeft.ToCode().Length : int.MaxValue; // try whichever is smaller if (leftOverRight != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, leftOverRight) && (rightOverLeft == null || leftOverRightLength < rightOverLeftLength)) { // use left-over-right. // but only if the resulting value is smaller than the original expression if (leftOverRightLength <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { // We don't need to swap the operands, but we do need to switch the operator rightOperator.OperatorToken = JsToken.Multiply; RotateFromRight(node, rightOperator, leftOverRight); } } else if (rightOverLeft != null && NoMultiplicativeOverOrUnderFlow(otherConstant, thisConstant, rightOverLeft)) { // but only if the resulting value is smaller than the original expression if (rightOverLeftLength <= thisConstant.ToCode().Length + otherConstant.ToCode().Length + 1) { // use right-over-left. Keep the operator, but swap the operands rightOperator.SwapOperands(); RotateFromLeft(node, rightOperator, rightOverLeft); } } } } }
/// <summary> /// If the new literal is a string literal, then we need to check to see if our /// parent is a CallNode. If it is, and if the string literal can be an identifier, /// we'll replace it with a Member-Dot operation. /// </summary> /// <param name="newLiteral">newLiteral we intend to replace this binaryop node with</param> /// <returns>true if we replaced the parent callnode with a member-dot operation</returns> /// <param name="node"></param> private bool ReplaceMemberBracketWithDot(JsBinaryOperator node, JsConstantWrapper newLiteral) { if (newLiteral.IsStringLiteral) { // see if this newly-combined string is the sole argument to a // call-brackets node. If it is and the combined string is a valid // identifier (and not a keyword), then we can replace the call // with a member operator. // remember that the parent of the argument won't be the call node -- it // will be the ast node list representing the arguments, whose parent will // be the node list. JsCallNode parentCall = (node.Parent is JsAstNodeList ? node.Parent.Parent as JsCallNode : null); if (parentCall != null && parentCall.InBrackets) { // get the newly-combined string string combinedString = newLiteral.ToString(); // see if this new string is the target of a replacement operation string newName; if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties && m_parser.Settings.IsModificationAllowed(JsTreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = m_parser.Settings.GetNewName(combinedString))) { // yes, it is. Now see if the new name is safe to be converted to a dot-operation. if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember) && JsScanner.IsSafeIdentifier(newName) && !JsScanner.IsKeyword(newName, parentCall.EnclosingScope.UseStrict)) { // we want to replace the call with operator with a new member dot operation, and // since we won't be analyzing it (we're past the analyze phase, we're going to need // to use the new string value JsMember replacementMember = new JsMember(parentCall.Context, m_parser) { Root = parentCall.Function, Name = newName, NameContext = parentCall.Arguments[0].Context }; parentCall.Parent.ReplaceChild(parentCall, replacementMember); return true; } else { // nope, can't be changed to a dot-operator for whatever reason. // just replace the value on this new literal. The old operation will // get replaced with this new literal newLiteral.Value = newName; // and make sure it's type is string newLiteral.PrimitiveType = JsPrimitiveType.String; } } else if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember)) { // our parent is a call-bracket -- now we just need to see if the newly-combined // string can be an identifier if (JsScanner.IsSafeIdentifier(combinedString) && !JsScanner.IsKeyword(combinedString, parentCall.EnclosingScope.UseStrict)) { // yes -- replace the parent call with a new member node using the newly-combined string JsMember replacementMember = new JsMember(parentCall.Context, m_parser) { Root = parentCall.Function, Name = combinedString, NameContext = parentCall.Arguments[0].Context }; parentCall.Parent.ReplaceChild(parentCall, replacementMember); return true; } } } } return false; }