public override void Visit(ConstantWrapper node) { if (node != null) { // measure if (node.PrimitiveType == PrimitiveType.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(TreeModifications.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(ConstantWrapper node) { if (node != null) { // allow string, number, true, false, and null. switch (node.PrimitiveType) { case PrimitiveType.Boolean: m_writer.Write((bool)node.Value ? "true" : "false"); break; case PrimitiveType.Null: m_writer.Write("null"); break; case PrimitiveType.Number: OutputNumber((double)node.Value, node.Context); break; case PrimitiveType.String: case PrimitiveType.Other: // string -- or treat it like a string OutputString(node.Value.ToString()); break; } } }
public void Visit(ConstantWrapper node) { if (node != null) { node.Index = NextOrderIndex; } }
private static bool IsMinificationHint(ConstantWrapper node) { var isHint = false; if (node.PrimitiveType == PrimitiveType.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) { ConstantWrapper constantWrapper = m_list[0] as ConstantWrapper; if (constantWrapper != null && string.CompareOrdinal(constantWrapper.Value.ToString(), argumentValue) == 0) { return(true); } } return(false); }
public override void Visit(ConstantWrapper node) { if (node != null) { // no children, so don't bother calling the base. if (node.PrimitiveType == PrimitiveType.Boolean && m_parser.Settings.IsModificationAllowed(TreeModifications.BooleanLiteralsToNotOperators)) { node.Parent.ReplaceChild(node, new UnaryOperator(node.Context, m_parser) { Operand = new ConstantWrapper(node.ToBoolean() ? 0 : 1, PrimitiveType.Number, node.Context, m_parser), OperatorToken = JSToken.LogicalNot }); } } }
public override void Visit(ConstantWrapper 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 Block) { // 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 AstNode ParseLeftHandSideExpression(bool isMinus) { AstNode ast = null; bool skipToken = true; List<Context> newContexts = null; TryItAgain: // new expression while (JSToken.New == m_currentToken.Token) { if (null == newContexts) newContexts = new List<Context>(4); newContexts.Add(m_currentToken.Clone()); GetNextToken(); } JSToken token = m_currentToken.Token; switch (token) { // primary expression case JSToken.Identifier: ast = new Lookup(m_currentToken.Clone(), this) { Name = m_scanner.Identifier }; break; case JSToken.ConditionalCommentStart: // skip past the start to the next token GetNextToken(); if (m_currentToken.Token == JSToken.ConditionalCompilationVariable) { // we have /*@id ast = new ConstantWrapperPP(m_currentToken.Clone(), this) { VarName = m_currentToken.Code, ForceComments = true }; GetNextToken(); if (m_currentToken.Token == JSToken.ConditionalCommentEnd) { // skip past the closing comment GetNextToken(); } else { // we ONLY support /*@id@*/ in expressions right now. If there's not // a closing comment after the ID, then we don't support it. // throw an error, skip to the end of the comment, then ignore it and start // looking for the next token. CCTooComplicated(null); goto TryItAgain; } } else if (m_currentToken.Token == JSToken.ConditionalCommentEnd) { // empty conditional comment! Ignore. GetNextToken(); goto TryItAgain; } else { // we DON'T have "/*@IDENT". We only support "/*@IDENT @*/", so since this isn't // and id, throw the error, skip to the end of the comment, and ignore it // by looping back and looking for the NEXT token. m_currentToken.HandleError(JSError.ConditionalCompilationTooComplex); // skip to end of conditional comment while (m_currentToken.Token != JSToken.EndOfFile && m_currentToken.Token != JSToken.ConditionalCommentEnd) { GetNextToken(); } GetNextToken(); goto TryItAgain; } break; case JSToken.This: ast = new ThisLiteral(m_currentToken.Clone(), this); break; case JSToken.StringLiteral: ast = new ConstantWrapper(m_scanner.StringLiteralValue, PrimitiveType.String, m_currentToken.Clone(), this) { MayHaveIssues = m_scanner.LiteralHasIssues }; break; case JSToken.IntegerLiteral: case JSToken.NumericLiteral: { Context numericContext = m_currentToken.Clone(); double doubleValue; if (ConvertNumericLiteralToDouble(m_currentToken.Code, (token == JSToken.IntegerLiteral), out doubleValue)) { // conversion worked fine // check for some boundary conditions var mayHaveIssues = m_scanner.LiteralHasIssues; if (doubleValue == double.MaxValue) { ReportError(JSError.NumericMaximum, numericContext, true); } else if (isMinus && -doubleValue == double.MinValue) { ReportError(JSError.NumericMinimum, numericContext, true); } // create the constant wrapper from the value ast = new ConstantWrapper(doubleValue, PrimitiveType.Number, numericContext, this) { MayHaveIssues = mayHaveIssues }; } else { // check to see if we went overflow if (double.IsInfinity(doubleValue)) { ReportError(JSError.NumericOverflow, numericContext, true); } // regardless, we're going to create a special constant wrapper // that simply echos the input as-is ast = new ConstantWrapper(m_currentToken.Code, PrimitiveType.Other, numericContext, this) { MayHaveIssues = true }; } break; } case JSToken.True: ast = new ConstantWrapper(true, PrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JSToken.False: ast = new ConstantWrapper(false, PrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JSToken.Null: ast = new ConstantWrapper(null, PrimitiveType.Null, m_currentToken.Clone(), this); break; case JSToken.ConditionalCompilationVariable: ast = new ConstantWrapperPP(m_currentToken.Clone(), this) { VarName = m_currentToken.Code, ForceComments = false }; break; case JSToken.DivideAssign: // normally this token is not allowed on the left-hand side of an expression. // BUT, this might be the start of a regular expression that begins with an equals sign! // we need to test to see if we can parse a regular expression, and if not, THEN // we can fail the parse. case JSToken.Divide: // could it be a regexp? String source = m_scanner.ScanRegExp(); if (source != null) { // parse the flags (if any) String flags = m_scanner.ScanRegExpFlags(); // create the literal ast = new RegExpLiteral(m_currentToken.Clone(), this) { Pattern = source, PatternSwitches = flags }; break; } goto default; // expression case JSToken.LeftParenthesis: { var groupingOp = new GroupingOperator(m_currentToken.Clone(), this); ast = groupingOp; GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenExpressionNoSkipToken); try { // parse an expression groupingOp.Operand = ParseExpression(); if (JSToken.RightParenthesis != m_currentToken.Token) { ReportError(JSError.NoRightParenthesis); } else { // add the closing paren to the expression context ast.Context.UpdateWith(m_currentToken); } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_ParenExpressionNoSkipToken, exc) == -1) throw; else groupingOp.Operand = exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenExpressionNoSkipToken); } } break; // array initializer case JSToken.LeftBracket: Context listCtx = m_currentToken.Clone(); GetNextToken(); AstNodeList list = new AstNodeList(CurrentPositionContext(), this); while (JSToken.RightBracket != m_currentToken.Token) { if (JSToken.Comma != m_currentToken.Token) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); try { var expression = ParseExpression(true); list.Append(expression); if (JSToken.Comma != m_currentToken.Token) { if (JSToken.RightBracket != m_currentToken.Token) { ReportError(JSError.NoRightBracket); } break; } else { // we have a comma -- skip it after adding it as a terminator // on the previous expression expression.IfNotNull(e => e.TerminatingContext = m_currentToken.Clone()); GetNextToken(); // if the next token is the closing brackets, then we need to // add a missing value to the array because we end in a comma and // we need to keep it for cross-platform compat. // TECHNICALLY, that puts an extra item into the array for most modern browsers, but not ALL. if (m_currentToken.Token == JSToken.RightBracket) { list.Append(new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this)); } } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) list.Append(exc._partiallyComputedNode); if (IndexOfToken(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet, exc) == -1) { listCtx.UpdateWith(CurrentPositionContext()); exc._partiallyComputedNode = new ArrayLiteral(listCtx, this) { Elements = list }; throw; } else { if (JSToken.RightBracket == m_currentToken.Token) break; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); } } else { // comma -- missing array item in the list list.Append(new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this) { TerminatingContext = m_currentToken.Clone() }); // skip over the comma GetNextToken(); // if the next token is the closing brace, then we end with a comma -- and we need to // add ANOTHER missing value to make sure this last comma doesn't get left off. // TECHNICALLY, that puts an extra item into the array for most modern browsers, but not ALL. if (m_currentToken.Token == JSToken.RightBracket) { list.Append(new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this)); } } } listCtx.UpdateWith(m_currentToken); ast = new ArrayLiteral(listCtx, this) { Elements = list }; break; // object initializer case JSToken.LeftCurly: Context objCtx = m_currentToken.Clone(); GetNextToken(); var propertyList = new AstNodeList(CurrentPositionContext(), this); if (JSToken.RightCurly != m_currentToken.Token) { for (; ; ) { ObjectLiteralField field = null; AstNode value = null; bool getterSetter = false; string ident; switch (m_currentToken.Token) { case JSToken.Identifier: field = new ObjectLiteralField(m_scanner.Identifier, PrimitiveType.String, m_currentToken.Clone(), this); break; case JSToken.StringLiteral: field = new ObjectLiteralField(m_scanner.StringLiteralValue, PrimitiveType.String, m_currentToken.Clone(), this) { MayHaveIssues = m_scanner.LiteralHasIssues }; break; case JSToken.IntegerLiteral: case JSToken.NumericLiteral: { double doubleValue; if (ConvertNumericLiteralToDouble(m_currentToken.Code, (m_currentToken.Token == JSToken.IntegerLiteral), out doubleValue)) { // conversion worked fine field = new ObjectLiteralField( doubleValue, PrimitiveType.Number, m_currentToken.Clone(), this ); } else { // something went wrong and we're not sure the string representation in the source is // going to convert to a numeric value well if (double.IsInfinity(doubleValue)) { ReportError(JSError.NumericOverflow, m_currentToken.Clone(), true); } // use the source as the field name, not the numeric value field = new ObjectLiteralField( m_currentToken.Code, PrimitiveType.Other, m_currentToken.Clone(), this); } break; } case JSToken.Get: case JSToken.Set: if (PeekToken() == JSToken.Colon) { // the field is either "get" or "set" and isn't the special Mozilla getter/setter field = new ObjectLiteralField(m_currentToken.Code, PrimitiveType.String, m_currentToken.Clone(), this); } else { // ecma-script get/set property construct getterSetter = true; bool isGet = (m_currentToken.Token == JSToken.Get); value = ParseFunction( (JSToken.Get == m_currentToken.Token ? FunctionType.Getter : FunctionType.Setter), m_currentToken.Clone() ); FunctionObject funcExpr = value as FunctionObject; if (funcExpr != null) { // getter/setter is just the literal name with a get/set flag field = new GetterSetter( funcExpr.Name, isGet, funcExpr.IdContext.Clone(), this ); } else { ReportError(JSError.FunctionExpressionExpected); } } break; default: // NOT: identifier token, string, number, or getter/setter. // see if it's a token that COULD be an identifierName. ident = m_scanner.Identifier; if (JSScanner.IsValidIdentifier(ident)) { // BY THE SPEC, if it's a valid identifierName -- which includes reserved words -- then it's // okay for object literal syntax. However, reserved words here won't work in all browsers, // so if it is a reserved word, let's throw a low-sev cross-browser warning on the code. if (JSKeyword.CanBeIdentifier(m_currentToken.Token) == null) { ReportError(JSError.ObjectLiteralKeyword, m_currentToken.Clone(), true); } field = new ObjectLiteralField(ident, PrimitiveType.String, m_currentToken.Clone(), this); } else { // throw an error but use it anyway, since that's what the developer has going on ReportError(JSError.NoMemberIdentifier, m_currentToken.Clone(), true); field = new ObjectLiteralField(m_currentToken.Code, PrimitiveType.String, m_currentToken.Clone(), this); } break; } if (field != null) { if (!getterSetter) { GetNextToken(); } m_noSkipTokenSet.Add(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet); try { if (!getterSetter) { // get the value if (JSToken.Colon != m_currentToken.Token) { ReportError(JSError.NoColon, true); value = ParseExpression(true); } else { field.ColonContext = m_currentToken.Clone(); GetNextToken(); value = ParseExpression(true); } } // put the pair into the list of fields var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context)); var property = new ObjectLiteralProperty(propCtx, this) { Name = field, Value = value }; propertyList.Append(property); if (JSToken.RightCurly == m_currentToken.Token) { break; } else { if (JSToken.Comma == m_currentToken.Token) { // skip the comma after adding it to the property as a terminating context property.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone()); GetNextToken(); // if the next token is the right-curly brace, then we ended // the list with a comma, which is perfectly fine if (m_currentToken.Token == JSToken.RightCurly) { break; } } else { if (m_foundEndOfLine) { ReportError(JSError.NoRightCurly); } else ReportError(JSError.NoComma, true); SkipTokensAndThrow(); } } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) { // the problem was in ParseExpression trying to determine value value = exc._partiallyComputedNode; var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context)); var property = new ObjectLiteralProperty(propCtx, this) { Name = field, Value = value }; propertyList.Append(property); } if (IndexOfToken(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new ObjectLiteral(objCtx, this) { Properties = propertyList }; throw; } else { if (JSToken.Comma == m_currentToken.Token) GetNextToken(); if (JSToken.RightCurly == m_currentToken.Token) break; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet); } } } } objCtx.UpdateWith(m_currentToken); ast = new ObjectLiteral(objCtx, this) { Properties = propertyList }; break; // function expression case JSToken.Function: ast = ParseFunction(FunctionType.Expression, m_currentToken.Clone()); skipToken = false; break; case JSToken.AspNetBlock: ast = new AspNetBlockNode(m_currentToken.Clone(), this) { AspNetBlockText = m_currentToken.Code }; break; default: string identifier = JSKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { ast = new Lookup(m_currentToken.Clone(), this) { Name = identifier }; } else { ReportError(JSError.ExpressionExpected); SkipTokensAndThrow(); } break; } // can be a CallExpression, that is, followed by '.' or '(' or '[' if (skipToken) GetNextToken(); return MemberExpression(ast, newContexts); }
public void Visit(ConstantWrapper node) { // we're good }
private static bool IsMinificationHint(ConstantWrapper node) { var isHint = false; if (node.PrimitiveType == PrimitiveType.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; }
//--------------------------------------------------------------------------------------- // ParseExpressionList // // Given a starting this.currentToken '(' or '[', parse a list of expression separated by // ',' until matching ')' or ']' //--------------------------------------------------------------------------------------- private AstNodeList ParseExpressionList(JSToken terminator) { Context listCtx = m_currentToken.Clone(); GetNextToken(); AstNodeList list = new AstNodeList(listCtx, this); if (terminator != m_currentToken.Token) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet); try { AstNode item; if (JSToken.Comma == m_currentToken.Token) { item = new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this); list.Append(item); } else if (terminator == m_currentToken.Token) { break; } else { item = ParseExpression(true); list.Append(item); } if (terminator == m_currentToken.Token) { break; } else { if (JSToken.Comma == m_currentToken.Token) { item.IfNotNull(n => n.TerminatingContext = m_currentToken.Clone()); } else { if (terminator == JSToken.RightParenthesis) { // in ASP+ it's easy to write a semicolon at the end of an expression // not realizing it is going to go inside a function call // (ie. Response.Write()), so make a special check here if (JSToken.Semicolon == m_currentToken.Token) { if (JSToken.RightParenthesis == PeekToken()) { ReportError(JSError.UnexpectedSemicolon, true); GetNextToken(); break; } } ReportError(JSError.NoRightParenthesisOrComma); } else { ReportError(JSError.NoRightBracketOrComma); } SkipTokensAndThrow(); } } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) list.Append(exc._partiallyComputedNode); if (IndexOfToken(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = list; throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet); } GetNextToken(); } } listCtx.UpdateWith(m_currentToken); return list; }
public void Visit(ConstantWrapper node) { // not applicable; terminate }
private ConstantWrapper Modulo(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper newLiteral = null; if (left.IsOkayToCombine && right.IsOkayToCombine && m_parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { try { double leftValue = left.ToNumber(); double rightValue = right.ToNumber(); double result = leftValue % rightValue; if (ConstantWrapper.NumberIsOkayToCombine(result)) { newLiteral = new ConstantWrapper(result, PrimitiveType.Number, null, m_parser); } else { if (!left.IsNumericLiteral && ConstantWrapper.NumberIsOkayToCombine(leftValue)) { left.Parent.ReplaceChild(left, new ConstantWrapper(leftValue, PrimitiveType.Number, left.Context, m_parser)); } if (!right.IsNumericLiteral && ConstantWrapper.NumberIsOkayToCombine(rightValue)) { right.Parent.ReplaceChild(right, new ConstantWrapper(rightValue, PrimitiveType.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 EvalToTheRight(BinaryOperator node, ConstantWrapper thisConstant, ConstantWrapper otherConstant, BinaryOperator 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 ConstantWrapper 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. ConstantWrapper newLiteral = NumericAddition(thisConstant, otherConstant); if (newLiteral != null && NoOverflow(newLiteral)) { RotateFromRight(node, rightOperator, newLiteral); } else { ConstantWrapper rightRight = rightOperator.Operand2 as ConstantWrapper; 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 ConstantWrapper newLiteral = Minus(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { rightOperator.SwapOperands(); RotateFromLeft(node, rightOperator, newLiteral); } else { ConstantWrapper rightRight = rightOperator.Operand2 as ConstantWrapper; 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 ConstantWrapper 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 ConstantWrapper 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 ConstantWrapper leftOverRight = Divide(thisConstant, otherConstant); ConstantWrapper 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); } } } } }
private ConstantWrapper BitwiseXor(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { try { Int32 lValue = left.ToInt32(); Int32 rValue = right.ToInt32(); newLiteral = new ConstantWrapper(Convert.ToDouble(lValue ^ rValue), PrimitiveType.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; }
private ConstantWrapper StrictNotEqual(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { PrimitiveType leftType = left.PrimitiveType; if (leftType == right.PrimitiveType) { // the values are the same type switch (leftType) { case PrimitiveType.Null: // null !== null is false newLiteral = new ConstantWrapper(false, PrimitiveType.Boolean, null, m_parser); break; case PrimitiveType.Boolean: // compare boolean values newLiteral = new ConstantWrapper(left.ToBoolean() != right.ToBoolean(), PrimitiveType.Boolean, null, m_parser); break; case PrimitiveType.String: // compare string ordinally if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new ConstantWrapper(string.CompareOrdinal(left.ToString(), right.ToString()) != 0, PrimitiveType.Boolean, null, m_parser); } break; case PrimitiveType.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 ConstantWrapper(left.ToNumber() != right.ToNumber(), PrimitiveType.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 ConstantWrapper(true, PrimitiveType.Boolean, null, m_parser); } } return newLiteral; }
private ConstantWrapper GreaterThanOrEqual(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { if (left.IsStringLiteral && right.IsStringLiteral) { if (left.IsOkayToCombine && right.IsOkayToCombine) { // do a straight ordinal comparison of the strings newLiteral = new ConstantWrapper(string.CompareOrdinal(left.ToString(), right.ToString()) >= 0, PrimitiveType.Boolean, null, m_parser); } } else { try { // either one or both are NOT a string -- numeric comparison if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new ConstantWrapper(left.ToNumber() >= right.ToNumber(), PrimitiveType.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; }
/// <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(AstNode node, ConstantWrapper newLiteral) { var grouping = node.Parent as GroupingOperator; 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 ConstantWrapper UnsignedRightShift(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(TreeModifications.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 ConstantWrapper(result, PrimitiveType.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; }
private ConstantWrapper StringConcat(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper 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(TreeModifications.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(TreeModifications.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 ConstantWrapper(left.ToString() + right.ToString(), PrimitiveType.String, null, m_parser); } } } return newLiteral; }
private ConstantWrapper LogicalOr(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(TreeModifications.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 void EvalFarToTheRight(BinaryOperator node, ConstantWrapper thisConstant, ConstantWrapper otherConstant, BinaryOperator rightOperator) { if (rightOperator.OperatorToken == JSToken.Minus) { if (node.OperatorToken == JSToken.Plus) { // plus-minus // our constant cannot be a string, though if (!thisConstant.IsStringLiteral) { ConstantWrapper newLiteral = Minus(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { RotateFromLeft(node, rightOperator, newLiteral); } } } else if (node.OperatorToken == JSToken.Minus) { // minus-minus ConstantWrapper 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 ConstantWrapper newLiteral = Multiply(thisConstant, otherConstant); if (newLiteral != null && NoMultiplicativeOverOrUnderFlow(thisConstant, otherConstant, newLiteral)) { RotateFromLeft(node, rightOperator, newLiteral); } } else if (rightOperator.OperatorToken == JSToken.Divide) { // mult-divide ConstantWrapper otherOverThis = Divide(otherConstant, thisConstant); ConstantWrapper 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 ConstantWrapper 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 ConstantWrapper 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 static string ComputeJoin(ArrayLiteral arrayLiteral, ConstantWrapper 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 ConstantWrapper Plus(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper 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; }
private void EvalThisOperator(BinaryOperator node, ConstantWrapper left, ConstantWrapper right) { // we can evaluate these operators if we know both operands are literal // number, boolean, string or null ConstantWrapper 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); } } }
/// <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> private void RotateFromRight(BinaryOperator node, BinaryOperator binaryOp, ConstantWrapper 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. ConstantWrapper otherConstant = binaryOp.Operand2 as ConstantWrapper; if (otherConstant != null) { EvalThisOperator(binaryOp, newLiteral, otherConstant); } }
/// <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(ConstantWrapper left, ConstantWrapper right, ConstantWrapper 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; }
//--------------------------------------------------------------------------------------- // ParseWithStatement // // WithStatement : // 'with' '(' Expression ')' Statement //--------------------------------------------------------------------------------------- private WithNode ParseWithStatement() { Context withCtx = m_currentToken.Clone(); AstNode obj = null; Block 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 ConstantWrapper(true, PrimitiveType.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 AstNode statement = ParseStatement(false); // but make sure we save it as a block block = statement as Block; if (block == null) { block = new Block(statement.Context, this); block.Append(statement); } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) { block = new Block(CurrentPositionContext(), this); } else { block = exc._partiallyComputedNode as Block; if (block == null) { block = new Block(exc._partiallyComputedNode.Context, this); block.Append(exc._partiallyComputedNode); } } exc._partiallyComputedNode = new WithNode(withCtx, this) { WithObject = obj, Body = block }; throw; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new WithNode(withCtx, this) { WithObject = obj, Body = block }; }
//--------------------------------------------------------------------------------------- // 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 AstNode MemberExpression(AstNode expression, List<Context> newContexts) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); try { switch (m_currentToken.Token) { case JSToken.LeftParenthesis: AstNodeList args = null; RecoveryTokenException callError = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken); try { args = ParseExpressionList(JSToken.RightParenthesis); } catch (RecoveryTokenException exc) { args = (AstNodeList)exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1) callError = exc; // thrown later on } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken); } expression = new CallNode(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 CallNode)) { expression = new CallNode(newContexts[newContexts.Count - 1], this) { Function = expression, Arguments = new AstNodeList(CurrentPositionContext(), this) }; } else { expression.Context = newContexts[newContexts.Count - 1]; } ((CallNode)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 AstNodeList(CurrentPositionContext(), this); AstNode 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 CallNode(expression.Context.CombineWith(m_currentToken.Clone()), this) { Function = expression, Arguments = (AstNodeList)exc._partiallyComputedNode, InBrackets = true }; } else { exc._partiallyComputedNode = expression; } throw; } else args = (AstNodeList)exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken); } expression = new CallNode(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: ConstantWrapper id = null; Context 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 ConstantWrapper(identifier, PrimitiveType.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 ConstantWrapper(m_currentToken.Code, PrimitiveType.String, m_currentToken.Clone(), this); } else { ReportError(JSError.NoIdentifier); SkipTokensAndThrow(expression); } } else { id = new ConstantWrapper(m_scanner.Identifier, PrimitiveType.String, m_currentToken.Clone(), this); } GetNextToken(); expression = new Member(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 CallNode(newContexts[newContexts.Count - 1], this) { Function = expression, Arguments = new AstNodeList(CurrentPositionContext(), this) }; ((CallNode)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); } } }
private void EvalToTheLeft(BinaryOperator node, ConstantWrapper thisConstant, ConstantWrapper otherConstant, BinaryOperator 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 ConstantWrapper 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) ConstantWrapper 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 ConstantWrapper leftLeft = leftOperator.Operand1 as ConstantWrapper; 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. ConstantWrapper 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 ConstantWrapper leftLeft = leftOperator.Operand1 as ConstantWrapper; 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. ConstantWrapper 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(TreeModifications.EvaluateNumericExpressions)) { // get the two division operators ConstantWrapper otherOverThis = Divide(otherConstant, thisConstant); ConstantWrapper 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 ConstantWrapper 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); } } }
/// <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> private bool ReplaceMemberBracketWithDot(BinaryOperator node, ConstantWrapper 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. CallNode parentCall = (node.Parent is AstNodeList ? node.Parent.Parent as CallNode : 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(TreeModifications.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(TreeModifications.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 Member replacementMember = new Member(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 = PrimitiveType.String; } } else if (m_parser.Settings.IsModificationAllowed(TreeModifications.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 Member replacementMember = new Member(parentCall.Context, m_parser) { Root = parentCall.Function, Name = combinedString, NameContext = parentCall.Arguments[0].Context }; parentCall.Parent.ReplaceChild(parentCall, replacementMember); return true; } } } } return false; }
private AstNode ParseSwitchStatement() { Context switchCtx = m_currentToken.Clone(); AstNode expr = null; AstNodeList cases = null; var braceOnNewLine = false; Context 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 ConstantWrapper(true, PrimitiveType.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 AstNodeList(CurrentPositionContext(), this); bool defaultStatement = false; m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet); try { while (JSToken.RightCurly != m_currentToken.Token) { SwitchCase caseClause = null; AstNode caseValue = null; var caseCtx = m_currentToken.Clone(); Context 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 Block(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 SwitchCase(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 SwitchCase(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 Switch(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 Switch(switchCtx, this) { Expression = expr, BraceContext = braceContext, Cases = cases, BraceOnNewLine = braceOnNewLine }; }
private void EvalFarToTheLeft(BinaryOperator node, ConstantWrapper thisConstant, ConstantWrapper otherConstant, BinaryOperator 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 != PrimitiveType.String && thisConstant.PrimitiveType != PrimitiveType.Other) { ConstantWrapper newLiteral = NumericAddition(otherConstant, thisConstant); if (newLiteral != null && NoOverflow(newLiteral)) { RotateFromRight(node, leftOperator, newLiteral); } } } else if (node.OperatorToken == JSToken.Minus) { // minus-minus ConstantWrapper 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) { ConstantWrapper 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 ConstantWrapper 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 ConstantWrapper otherOverThis = Divide(otherConstant, thisConstant); ConstantWrapper 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> /// 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(ConstantWrapper result) { return !result.IsInfinity; }