public void Visit(JsConstantWrapper node) { if (node != null) { // allow string, number, true, false, and null. switch (node.PrimitiveType) { case JsPrimitiveType.Boolean: m_writer.Write((bool)node.Value ? "true" : "false"); break; case JsPrimitiveType.Null: m_writer.Write("null"); break; case JsPrimitiveType.Number: OutputNumber((double)node.Value, node.Context); break; case JsPrimitiveType.String: case JsPrimitiveType.Other: // string -- or treat it like a string OutputString(node.Value.ToString()); break; } } }
public override void Visit(JsConstantWrapper node) { if (node != null) { // measure if (node.PrimitiveType == JsPrimitiveType.Boolean) { if (m_measure) { // if we are converting true/false literals to !0/!1, then // a logical-not doesn't add or subtract anything. But if we aren't, // we need to add/subtract the difference in the length between the // "true" and "false" strings if (!m_parser.Settings.MinifyCode || !m_parser.Settings.IsModificationAllowed(JsTreeModifications.BooleanLiteralsToNotOperators)) { // converting true to false adds a character, false to true subtracts m_delta += node.ToBoolean() ? 1 : -1; } } else { // convert - just flip the boolean value node.Value = !node.ToBoolean(); } } else { // just the same typical operation as most other nodes for other types TypicalHandler(node); } } }
public void Visit(JsConstantWrapper node) { if (node != null) { node.Index = NextOrderIndex; } }
private static bool IsMinificationHint(JsConstantWrapper node) { var isHint = false; if (node.PrimitiveType == JsPrimitiveType.String) { // try splitting on commas and removing empty items var sections = node.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var section in sections) { // valid hints are: // name:nomunge don't automatically rename the field defined in this scope named "name" // if name is missing (colon is the first character) or "*", then don't rename ANY // fields defined in the current scope. var ndxColon = section.IndexOf(':'); if (ndxColon >= 0) { // make sure this is a "nomunge" hint. If it is, then the entire node is treated as a hint and // will be removed from the AST. if (string.Compare(section.Substring(ndxColon + 1).Trim(), "nomunge", StringComparison.OrdinalIgnoreCase) == 0) { // it is. isHint = true; // get the name that we don't want to munge. Null means all. Convert "*" // to null. var identifier = section.Substring(0, ndxColon).Trim(); if (string.IsNullOrEmpty(identifier) || string.CompareOrdinal(identifier, "*") == 0) { identifier = null; } // get the current scope and iterate over all the fields within it // looking for just the ones that are defined here (outer is null) var currentScope = node.EnclosingScope; foreach (var field in currentScope.NameTable.Values) { if (field.OuterField == null) { // if the identifier is null or matches exactly, mark it as not crunchable if (identifier == null || string.CompareOrdinal(identifier, field.Name) == 0) { field.CanCrunch = false; } } } } } } } return(isHint); }
public bool IsSingleConstantArgument(string argumentValue) { if (m_list.Count == 1) { JsConstantWrapper constantWrapper = m_list[0] as JsConstantWrapper; if (constantWrapper != null && string.CompareOrdinal(constantWrapper.Value.ToString(), argumentValue) == 0) { return(true); } } return(false); }
public override void Visit(JsConstantWrapper node) { if (node != null) { // no children, so don't bother calling the base. if (node.PrimitiveType == JsPrimitiveType.Boolean && m_parser.Settings.IsModificationAllowed(JsTreeModifications.BooleanLiteralsToNotOperators)) { node.Parent.ReplaceChild(node, new JsUnaryOperator(node.Context, m_parser) { Operand = new JsConstantWrapper(node.ToBoolean() ? 0 : 1, JsPrimitiveType.Number, node.Context, m_parser), OperatorToken = JsToken.LogicalNot }); } } }
public override void Visit(JsConstantWrapper node) { // by default this node has nothing to do and no children to recurse. // but if this node's parent is a block, then this is an expression statement // consisting of a single string literal. Normally we would ignore these -- if // they occured at the top of the block they would be DirectivePrologues. So because // this exists, it must not be at the top. But we still want to check it for the nomunge // hints and respect them if that's what it is. if (node != null && node.Parent is JsBlock) { // if this is a hint, process it as such. if (IsMinificationHint(node)) { // and then remove it. We can do that here, because blocks are processed // in reverse order. node.Parent.ReplaceChild(node, null); } } }
private JsConstantWrapper LessThanOrEqual(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { if (left.IsStringLiteral && right.IsStringLiteral) { if (left.IsOkayToCombine && right.IsOkayToCombine) { // do a straight ordinal comparison of the strings newLiteral = new JsConstantWrapper(string.CompareOrdinal(left.ToString(), right.ToString()) <= 0, JsPrimitiveType.Boolean, null, m_parser); } } else { try { // either one or both are NOT a string -- numeric comparison if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new JsConstantWrapper(left.ToNumber() <= right.ToNumber(), JsPrimitiveType.Boolean, null, m_parser); } } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } } } return newLiteral; }
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); } } } } }
public void Visit(JsConstantWrapper node) { // it's a constant, so we don't care }
private JsAstNode ParseSwitchStatement() { JsContext switchCtx = m_currentToken.Clone(); JsAstNode expr = null; JsAstNodeList cases = null; var braceOnNewLine = false; JsContext braceContext = null; m_blockType.Add(BlockType.Switch); try { // read switch(expr) GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) ReportError(JsError.NoLeftParenthesis); GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet); try { expr = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); } GetNextToken(); if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.NoLeftCurly); } braceOnNewLine = m_foundEndOfLine; braceContext = m_currentToken.Clone(); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1 && IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1) { // give up exc._partiallyComputedNode = null; throw; } else { if (exc._partiallyComputedNode == null) expr = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); else expr = exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) != -1) { if (exc._token == JsToken.RightParenthesis) GetNextToken(); if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.NoLeftCurly); } braceOnNewLine = m_foundEndOfLine; braceContext = m_currentToken.Clone(); GetNextToken(); } } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // parse the switch body cases = new JsAstNodeList(CurrentPositionContext(), this); bool defaultStatement = false; m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet); try { while (JsToken.RightCurly != m_currentToken.Token) { JsSwitchCase caseClause = null; JsAstNode caseValue = null; var caseCtx = m_currentToken.Clone(); JsContext colonContext = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_CaseNoSkipTokenSet); try { if (JsToken.Case == m_currentToken.Token) { // get the case GetNextToken(); caseValue = ParseExpression(); } else if (JsToken.Default == m_currentToken.Token) { // get the default if (defaultStatement) { // we report an error but we still accept the default ReportError(JsError.DupDefault, true); } else { defaultStatement = true; } GetNextToken(); } else { // This is an error, there is no case or default. Assume a default was missing and keep going defaultStatement = true; ReportError(JsError.BadSwitch); } if (JsToken.Colon != m_currentToken.Token) { ReportError(JsError.NoColon); } else { colonContext = m_currentToken.Clone(); } // read the statements inside the case or default GetNextToken(); } catch (RecoveryTokenException exc) { // right now we can only get here for the 'case' statement if (IndexOfToken(NoSkipTokenSet.s_CaseNoSkipTokenSet, exc) == -1) { // ignore the current case or default exc._partiallyComputedNode = null; throw; } else { caseValue = exc._partiallyComputedNode; if (exc._token == JsToken.Colon) { GetNextToken(); } } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_CaseNoSkipTokenSet); } m_blockType.Add(BlockType.Block); try { var statements = new JsBlock(m_currentToken.Clone(), this); m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); try { while (JsToken.RightCurly != m_currentToken.Token && JsToken.Case != m_currentToken.Token && JsToken.Default != m_currentToken.Token) { try { // parse a Statement, not a SourceElement statements.Append(ParseStatement(false)); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) { statements.Append(exc._partiallyComputedNode); exc._partiallyComputedNode = null; } if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1) { throw; } } } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1) { caseClause = new JsSwitchCase(caseCtx, this) { CaseValue = caseValue, ColonContext = colonContext, Statements = statements }; cases.Append(caseClause); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); } caseCtx.UpdateWith(statements.Context); caseClause = new JsSwitchCase(caseCtx, this) { CaseValue = caseValue, ColonContext = colonContext, Statements = statements }; cases.Append(caseClause); } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1) { //save what you can a rethrow switchCtx.UpdateWith(CurrentPositionContext()); exc._partiallyComputedNode = new JsSwitch(switchCtx, this) { Expression = expr, BraceContext = braceContext, Cases = cases, BraceOnNewLine = braceOnNewLine }; throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet); } switchCtx.UpdateWith(m_currentToken); GetNextToken(); } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new JsSwitch(switchCtx, this) { Expression = expr, BraceContext = braceContext, Cases = cases, BraceOnNewLine = braceOnNewLine }; }
/// <summary> /// Return true if the result isn't an overflow condition /// </summary> /// <param name="result">result constant</param> /// <returns>true is not an overflow; false if it is</returns> private static bool NoOverflow(JsConstantWrapper result) { return !result.IsInfinity; }
private JsConstantWrapper BitwiseXor(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { try { Int32 lValue = left.ToInt32(); Int32 rValue = right.ToInt32(); newLiteral = new JsConstantWrapper(Convert.ToDouble(lValue ^ rValue), JsPrimitiveType.Number, null, m_parser); } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } } return newLiteral; }
//--------------------------------------------------------------------------------------- // ParseWhileStatement // // WhileStatement : // 'while' '(' Expression ')' Statement //--------------------------------------------------------------------------------------- private JsWhileNode ParseWhileStatement() { JsContext whileCtx = m_currentToken.Clone(); JsAstNode condition = null; JsAstNode body = null; m_blockType.Add(BlockType.Loop); try { GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) { ReportError(JsError.NoLeftParenthesis); } GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { condition = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); whileCtx.UpdateWith(condition.Context); } else whileCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { // abort the while there is really no much to do here exc._partiallyComputedNode = null; throw; } else { // make up a condition if (exc._partiallyComputedNode != null) condition = exc._partiallyComputedNode; else condition = new JsConstantWrapper(false, JsPrimitiveType.Boolean, CurrentPositionContext(), this); if (JsToken.RightParenthesis == m_currentToken.Token) GetNextToken(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // if this is an assignment, throw a warning in case the developer // meant to use == instead of = // but no warning if the condition is wrapped in parens. var binOp = condition as JsBinaryOperator; if (binOp != null && binOp.OperatorToken == JsToken.Assign) { condition.Context.HandleError(JsError.SuspectAssignment); } // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. body = ParseStatement(false, true); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) body = exc._partiallyComputedNode; else body = new JsBlock(CurrentPositionContext(), this); exc._partiallyComputedNode = new JsWhileNode(whileCtx, this) { Condition = condition, Body = JsAstNode.ForceToBlock(body) }; throw; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new JsWhileNode(whileCtx, this) { Condition = condition, Body = JsAstNode.ForceToBlock(body) }; }
private static string ComputeJoin(JsArrayLiteral arrayLiteral, JsConstantWrapper separatorNode) { // if the separator node is null, then the separator is a single comma character. // otherwise it's just the string value of the separator. var separator = separatorNode == null ? "," : separatorNode.ToString(); var sb = new StringBuilder(); for (var ndx = 0; ndx < arrayLiteral.Elements.Count; ++ndx) { // add the separator between items (if we have one) if (ndx > 0 && !string.IsNullOrEmpty(separator)) { sb.Append(separator); } // the element is a constant wrapper (we wouldn't get this far if it wasn't), // but we've overloaded the virtual ToString method on ConstantWrappers to convert the // constant value to a string value. sb.Append(arrayLiteral.Elements[ndx].ToString()); } return sb.ToString(); }
private JsConstantWrapper LogicalOr(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { try { // if the left-hand side evaluates to true, return the left-hand side. // if the left-hand side is false, return the right-hand side. newLiteral = left.ToBoolean() ? left : right; } catch (InvalidCastException) { // if we couldn't cast to bool, ignore } } return newLiteral; }
private JsConstantWrapper Plus(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (left.IsStringLiteral || right.IsStringLiteral) { // one or both are strings -- this is a strng concat operation newLiteral = StringConcat(left, right); } else { // neither are strings -- this is a numeric addition operation newLiteral = NumericAddition(left, right); } return newLiteral; }
//--------------------------------------------------------------------------------------- // ParseWithStatement // // WithStatement : // 'with' '(' Expression ')' Statement //--------------------------------------------------------------------------------------- private JsWithNode ParseWithStatement() { JsContext withCtx = m_currentToken.Clone(); JsAstNode obj = null; JsBlock block = null; m_blockType.Add(BlockType.Block); try { GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) ReportError(JsError.NoLeftParenthesis); GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { obj = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { withCtx.UpdateWith(obj.Context); ReportError(JsError.NoRightParenthesis); } else withCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { // give up exc._partiallyComputedNode = null; throw; } else { if (exc._partiallyComputedNode == null) obj = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); else obj = exc._partiallyComputedNode; withCtx.UpdateWith(obj.Context); if (exc._token == JsToken.RightParenthesis) GetNextToken(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. JsAstNode statement = ParseStatement(false, true); // but make sure we save it as a block block = statement as JsBlock; if (block == null) { block = new JsBlock(statement.Context, this); block.Append(statement); } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) { block = new JsBlock(CurrentPositionContext(), this); } else { block = exc._partiallyComputedNode as JsBlock; if (block == null) { block = new JsBlock(exc._partiallyComputedNode.Context, this); block.Append(exc._partiallyComputedNode); } } exc._partiallyComputedNode = new JsWithNode(withCtx, this) { WithObject = obj, Body = block }; throw; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new JsWithNode(withCtx, this) { WithObject = obj, Body = block }; }
public void Visit(JsConstantWrapper node) { // not applicable; terminate }
public void Visit(JsConstantWrapper node) { // we're good }
//--------------------------------------------------------------------------------------- // MemberExpression // // Accessor : // <empty> | // Arguments Accessor // '[' Expression ']' Accessor | // '.' Identifier Accessor | // // Don't have this function throwing an exception without checking all the calling sites. // There is state in instance variable that is saved on the calling stack in some function // (i.e ParseFunction and ParseClass) and you don't want to blow up the stack //--------------------------------------------------------------------------------------- private JsAstNode MemberExpression(JsAstNode expression, List<JsContext> newContexts) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); try { switch (m_currentToken.Token) { case JsToken.LeftParenthesis: JsAstNodeList args = null; RecoveryTokenException callError = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken); try { args = ParseExpressionList(JsToken.RightParenthesis); } catch (RecoveryTokenException exc) { args = (JsAstNodeList)exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1) callError = exc; // thrown later on } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken); } expression = new JsCallNode(expression.Context.CombineWith(args.Context), this) { Function = expression, Arguments = args, InBrackets = false }; if (null != newContexts && newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); if (!(expression is JsCallNode)) { expression = new JsCallNode(newContexts[newContexts.Count - 1], this) { Function = expression, Arguments = new JsAstNodeList(CurrentPositionContext(), this) }; } else { expression.Context = newContexts[newContexts.Count - 1]; } ((JsCallNode)expression).IsConstructor = true; newContexts.RemoveAt(newContexts.Count - 1); } if (callError != null) { callError._partiallyComputedNode = expression; throw callError; } GetNextToken(); break; case JsToken.LeftBracket: m_noSkipTokenSet.Add(NoSkipTokenSet.s_BracketToken); try { // // ROTOR parses a[b,c] as a call to a, passing in the arguments b and c. // the correct parse is a member lookup on a of c -- the "b,c" should be // a single expression with a comma operator that evaluates b but only // returns c. // So we'll change the default behavior from parsing an expression list to // parsing a single expression, but returning a single-item list (or an empty // list if there is no expression) so the rest of the code will work. // //args = ParseExpressionList(JSToken.RightBracket); GetNextToken(); args = new JsAstNodeList(CurrentPositionContext(), this); JsAstNode accessor = ParseExpression(); if (accessor != null) { args.Append(accessor); } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BracketToken, exc) == -1) { if (exc._partiallyComputedNode != null) { exc._partiallyComputedNode = new JsCallNode(expression.Context.CombineWith(m_currentToken.Clone()), this) { Function = expression, Arguments = (JsAstNodeList)exc._partiallyComputedNode, InBrackets = true }; } else { exc._partiallyComputedNode = expression; } throw; } else args = (JsAstNodeList)exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken); } expression = new JsCallNode(expression.Context.CombineWith(m_currentToken.Clone()), this) { Function = expression, Arguments = args, InBrackets = true }; // there originally was code here in the ROTOR sources that checked the new context list and // changed this member call to a constructor call, effectively combining the two. I believe they // need to remain separate. // remove the close bracket token GetNextToken(); break; case JsToken.AccessField: JsConstantWrapper id = null; JsContext nameContext = m_currentToken.Clone(); GetNextToken(); if (JsToken.Identifier != m_currentToken.Token) { string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { // don't report an error here -- it's actually okay to have a property name // that is a keyword which is okay to be an identifier. For instance, // jQuery has a commonly-used method named "get" to make an ajax request //ForceReportInfo(JSError.KeywordUsedAsIdentifier); id = new JsConstantWrapper(identifier, JsPrimitiveType.String, m_currentToken.Clone(), this); } else if (JsScanner.IsValidIdentifier(m_currentToken.Code)) { // it must be a keyword, because it can't technically be an identifier, // but it IS a valid identifier format. Throw a warning but still // create the constant wrapper so we can output it as-is ReportError(JsError.KeywordUsedAsIdentifier, m_currentToken.Clone(), true); id = new JsConstantWrapper(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this); } else { ReportError(JsError.NoIdentifier); SkipTokensAndThrow(expression); } } else { id = new JsConstantWrapper(m_scanner.Identifier, JsPrimitiveType.String, m_currentToken.Clone(), this); } GetNextToken(); expression = new JsMember(expression.Context.CombineWith(id.Context), this) { Root = expression, Name = id.Context.Code, NameContext = nameContext.CombineWith(id.Context) }; break; default: if (null != newContexts) { while (newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); expression = new JsCallNode(newContexts[newContexts.Count - 1], this) { Function = expression, Arguments = new JsAstNodeList(CurrentPositionContext(), this) }; ((JsCallNode)expression).IsConstructor = true; newContexts.RemoveAt(newContexts.Count - 1); } } return expression; } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_MemberExprNoSkipTokenSet, exc) != -1) expression = exc._partiallyComputedNode; else { Debug.Assert(exc._partiallyComputedNode == expression); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); } } }
public override void Visit(JsMember node) { if (node != null) { // depth-first base.Visit(node); if (string.CompareOrdinal(node.Name, "length") == 0 && m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateLiteralLengths)) { // if we create a constant, we'll replace the current node with it JsConstantWrapper length = null; JsArrayLiteral arrayLiteral; var constantWrapper = node.Root as JsConstantWrapper; if (constantWrapper != null) { if (constantWrapper.PrimitiveType == JsPrimitiveType.String && !constantWrapper.MayHaveIssues) { length = new JsConstantWrapper(constantWrapper.ToString().Length, JsPrimitiveType.Number, node.Context, node.Parser); } } else if ((arrayLiteral = node.Root as JsArrayLiteral) != null && !arrayLiteral.MayHaveIssues) { // get the count of items in the array literal, create a constant wrapper from it, and // replace this node with it length = new JsConstantWrapper(arrayLiteral.Elements.Count, JsPrimitiveType.Number, node.Context, node.Parser); } if (length != null) { node.Parent.ReplaceChild(node, length); } } } }
/// <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); } }
/// <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; }
private JsConstantWrapper StringConcat(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; // if we don't want to combine adjacent string literals, then we know we don't want to do // anything here. if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineAdjacentStringLiterals)) { // if either one of the operands is not a string literal, then check to see if we allow // evaluation of numeric expression; if not, then no-go. IF they are both string literals, // then it doesn't matter what the numeric flag says. if ((left.IsStringLiteral && right.IsStringLiteral) || m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { // if either value is a floating-point number (a number, not NaN, not Infinite, not an Integer), // then we won't do the string concatenation because different browsers may have subtle differences // in their double-to-string conversion algorithms. // so if neither is a numeric literal, or if one or both are, if they are both integer literals // in the range that we can EXACTLY represent them in a double, then we can proceed. // NaN, +Infinity and -Infinity are also acceptable if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new JsConstantWrapper(left.ToString() + right.ToString(), JsPrimitiveType.String, null, m_parser); } } } return newLiteral; }
private JsConstantWrapper StrictNotEqual(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { JsPrimitiveType leftType = left.PrimitiveType; if (leftType == right.PrimitiveType) { // the values are the same type switch (leftType) { case JsPrimitiveType.Null: // null !== null is false newLiteral = new JsConstantWrapper(false, JsPrimitiveType.Boolean, null, m_parser); break; case JsPrimitiveType.Boolean: // compare boolean values newLiteral = new JsConstantWrapper(left.ToBoolean() != right.ToBoolean(), JsPrimitiveType.Boolean, null, m_parser); break; case JsPrimitiveType.String: // compare string ordinally if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new JsConstantWrapper(string.CompareOrdinal(left.ToString(), right.ToString()) != 0, JsPrimitiveType.Boolean, null, m_parser); } break; case JsPrimitiveType.Number: try { // compare the values // +0 and -0 are treated as "equal" in C#, so we don't need to test them separately. // and NaN is always unequal to everything else, including itself. if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new JsConstantWrapper(left.ToNumber() != right.ToNumber(), JsPrimitiveType.Boolean, null, m_parser); } } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } break; } } else { // if they aren't the same type, they are not equal newLiteral = new JsConstantWrapper(true, JsPrimitiveType.Boolean, null, m_parser); } } return newLiteral; }
private JsConstantWrapper UnsignedRightShift(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { try { // left-hand value is a 32-bit signed integer UInt32 lvalue = left.ToUInt32(); // mask only the bottom 5 bits of the right-hand value int rvalue = (int)(right.ToUInt32() & 0x1F); // convert the result to a double double result = Convert.ToDouble(lvalue >> rvalue); newLiteral = new JsConstantWrapper(result, JsPrimitiveType.Number, null, m_parser); } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } } return newLiteral; }
/// <summary> /// Return true is not an overflow or underflow, for multiplication operations /// </summary> /// <param name="left">left operand</param> /// <param name="right">right operand</param> /// <param name="result">result</param> /// <returns>true if result not overflow or underflow; false if it is</returns> private static bool NoMultiplicativeOverOrUnderFlow(JsConstantWrapper left, JsConstantWrapper right, JsConstantWrapper result) { // check for overflow bool okayToProceed = !result.IsInfinity; // if we still might be good, check for possible underflow if (okayToProceed) { // if the result is zero, we might have an underflow. But if one of the operands // was zero, then it's okay. // Inverse: if neither operand is zero, then a zero result is not okay okayToProceed = !result.IsZero || (left.IsZero || right.IsZero); } return okayToProceed; }
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); } } } } }
/// <summary> /// replace the node with a literal. If the node was wrapped in a grouping operator /// before (parentheses around it), then we can get rid of the parentheses too, since /// we are replacing the node with a single literal entity. /// </summary> /// <param name="node">node to replace</param> /// <param name="newLiteral">literal to replace the node with</param> private static void ReplaceNodeWithLiteral(JsAstNode node, JsConstantWrapper newLiteral) { var grouping = node.Parent as JsGroupingOperator; if (grouping != null) { // because we are replacing the operator with a literal, the parentheses // the grouped this operator are now superfluous. Replace them, too grouping.Parent.ReplaceChild(grouping, newLiteral); } else { // just replace the node with the literal node.Parent.ReplaceChild(node, newLiteral); } }
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); } } } }
private JsConstantWrapper Divide(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (left.IsOkayToCombine && right.IsOkayToCombine && m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { try { double leftValue = left.ToNumber(); double rightValue = right.ToNumber(); double result = leftValue / rightValue; if (JsConstantWrapper.NumberIsOkayToCombine(result)) { newLiteral = new JsConstantWrapper(result, JsPrimitiveType.Number, null, m_parser); } else { if (!left.IsNumericLiteral && JsConstantWrapper.NumberIsOkayToCombine(leftValue)) { left.Parent.ReplaceChild(left, new JsConstantWrapper(leftValue, JsPrimitiveType.Number, left.Context, m_parser)); } if (!right.IsNumericLiteral && JsConstantWrapper.NumberIsOkayToCombine(rightValue)) { right.Parent.ReplaceChild(right, new JsConstantWrapper(rightValue, JsPrimitiveType.Number, right.Context, m_parser)); } } } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } } return newLiteral; }
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); } } }