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 override void CleanupNodes() { base.CleanupNodes(); if (Parser.Settings.EvalLiteralExpressions && Parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { // if the if-condition is a constant, we can eliminate one of the two branches ConstantWrapper constantCondition = Condition as ConstantWrapper; if (constantCondition != null) { // instead, replace the condition with a 1 if it's always true or a 0 if it's always false if (constantCondition.IsNotOneOrPositiveZero) { try { Condition = new ConstantWrapper(constantCondition.ToBoolean() ? 1 : 0, PrimitiveType.Number, null, Parser); } catch (InvalidCastException) { // ignore any invalid cast exceptions } } } } }
public override void CleanupNodes() { base.CleanupNodes(); if (Parser.Settings.EvalLiteralExpressions && Parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { // if the condition is a literal, evaluating the condition doesn't do anything, AND // we know now whether it's true or not. ConstantWrapper literalCondition = m_condition as ConstantWrapper; if (literalCondition != null) { try { // if the boolean represenation of the literal is true, we can replace the condition operator // with the true expression; otherwise we can replace it with the false expression Parent.ReplaceChild(this, literalCondition.ToBoolean() ? m_trueExpression : m_falseExpression); } catch (InvalidCastException) { // ignore any invalid cast errors } } } }
public override void CleanupNodes() { base.CleanupNodes(); if (Parser.Settings.EvalLiteralExpressions && Parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { ConstantWrapper constantCondition = Condition as ConstantWrapper; if (constantCondition != null) { try { // if condition is always false, change it to a zero (only one byte) // and if it is always true, remove it (default behavior) if (constantCondition.ToBoolean()) { // always true -- don't need a condition at all Condition = null; } else if (constantCondition.IsNotOneOrPositiveZero) { // always false and it's not already a zero. Make it so (only one byte) Condition = new ConstantWrapper(0, PrimitiveType.Number, null, Parser); } } catch (InvalidCastException) { // ignore any invalid cast exceptions } } } }
public void Visit(ConstantWrapper node) { if (node != null) { DoesRequire = true; } }
public void Visit(ConstantWrapper node) { // elisions in rrays are allowed, but don't recurse or contribute. // nothing else is allowed, though. if (node != null && node.Value != Missing.Value) { ReportError(node); } }
public override void Visit(CallNode node) { base.Visit(node); if (ShouldInsertModulePathArgument(node)) { var modulePathNode = new ConstantWrapper(modulePath, PrimitiveType.String, node.Context, node.Parser); node.Arguments.Insert(0, modulePathNode); } }
private 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 ?? m_globalScope; 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 LiteralReference(ConstantWrapper constantWrapper, ActivationObject childScope, List <ConstantWrapper> sharedList) { m_count = 1; m_childScope = childScope; // use the shared list passed to us, or create a new one m_constantWrappers = sharedList ?? new List <ConstantWrapper>(); // only add the constant wrapper passed to us IF it isn't ALREADY in the list // (will only happen with a shared list) if (!m_constantWrappers.Contains(constantWrapper)) { m_constantWrappers.Add(constantWrapper); } }
internal override void AnalyzeNode() { // recurse first, then check to see if the unary is still needed base.AnalyzeNode(); // if the operand is a numeric literal ConstantWrapper constantWrapper = Operand as ConstantWrapper; if (constantWrapper != null && constantWrapper.IsNumericLiteral) { // get the value of the constant. We've already screened it for numeric, so // we don't have to worry about catching any errors double doubleValue = constantWrapper.ToNumber(); // if this is a unary minus... if (OperatorToken == JSToken.Minus && Parser.Settings.IsModificationAllowed(TreeModifications.ApplyUnaryMinusToNumericLiteral)) { // negate the value constantWrapper.Value = -doubleValue; // replace us with the negated constant if (Parent.ReplaceChild(this, constantWrapper)) { // the context for the minus will include the number (its operand), // but the constant will just be the number. Update the context on // the constant to be a copy of the context on the operator constantWrapper.Context = Context.Clone(); return; } } else if (OperatorToken == JSToken.Plus && Parser.Settings.IsModificationAllowed(TreeModifications.RemoveUnaryPlusOnNumericLiteral)) { // +NEG is still negative, +POS is still positive, and +0 is still 0. // so just get rid of the unary operator altogether if (Parent.ReplaceChild(this, constantWrapper)) { // the context for the unary will include the number (its operand), // but the constant will just be the number. Update the context on // the constant to be a copy of the context on the operator constantWrapper.Context = Context.Clone(); return; } } } }
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 NumericUnary( node.Context, m_parser, new ConstantWrapper(node.ToBoolean() ? 0 : 1, PrimitiveType.Number, node.Context, m_parser), JSToken.LogicalNot)); } } }
internal override string GetFunctionGuess(AstNode target) { // MSN VOODOO: treat the as and ns methods as special if the expression is the root, // the parent is the call, and there is one string parameter -- use the string parameter if (Root == target && (Name == "as" || Name == "ns")) { CallNode call = Parent as CallNode; if (call != null && call.Arguments.Count == 1) { ConstantWrapper firstParam = call.Arguments[0] as ConstantWrapper; if (firstParam != null) { return(firstParam.ToString()); } } } return(Name); }
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); } } }
/// <summary> /// Optimized string literal concatenation for repeat usage pattern, avoiding multiple copying and allocation /// </summary> /// <param name="other"></param> /// <returns></returns> public StringList Concat(ConstantWrapper other) { var left = this.Value; if (this.PrimitiveType != PrimitiveType.String) { left = this.ToString(); } object right = null; if (other != null) { right = other.Value; if (other.PrimitiveType != PrimitiveType.String) { right = other.ToString(); } } return(new StringList(left, right)); }
internal override string GetFunctionGuess(AstNode target) { // get our guess from the function call string funcName = m_func.GetFunctionGuess(target); // MSN VOODOO: if this is the addMethod method, then the // name of the function is the first parameter. // The syntax of the add method call is: obj.addMethod("name",function(){...}) // so there should be two parameters.... if (funcName == "addMethod" && m_args.Count == 2) { // the first one should be a string constant.... ConstantWrapper firstParam = m_args[0] as ConstantWrapper; // and the second one should be the function expression we're looking for if ((firstParam != null) && (firstParam.Value is string) && (m_args[1] == target)) { // use that first parameter as the guess funcName = firstParam.ToString(); } } return(funcName); }
public override void CleanupNodes() { base.CleanupNodes(); if (Parser.Settings.EvalLiteralExpressions && Parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { // if the condition is a constant, we can simplify things ConstantWrapper constantCondition = Condition as ConstantWrapper; if (constantCondition != null && constantCondition.IsNotOneOrPositiveZero) { try { // the condition is a constant, so it is always either true or false // we can replace the condition with a one or a zero -- only one byte Condition = new ConstantWrapper(constantCondition.ToBoolean() ? 1 : 0, PrimitiveType.Number, null, Parser); } catch (InvalidCastException) { // ignore any invalid cast errors } } } }
/// <summary> /// Evaluate: (CONST [op] OTHER) [op] CONST /// </summary> /// <param name="thisConstant">second constant</param> /// <param name="otherConstant">first constant</param> /// <param name="leftOperator">first operator</param> private void EvalFarToTheLeft(BinaryOperator node, ConstantWrapper thisConstant, ConstantWrapper otherConstant, BinaryOperator leftOperator) { if (leftOperator.OperatorToken == JSToken.Minus) { if (node.OperatorToken == JSToken.Plus) { // minus-plus 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); } } } } }
internal override void AnalyzeNode() { // if we don't even have any resource strings, then there's nothing // we need to do and we can just perform the base operation ResourceStrings resourceStrings = Parser.ResourceStrings; if (resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = Root as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // it is -- we're going to replace this with a string value. // if this member name is a string on the object, we'll replacve it with // the literal. Otherwise we'll replace it with an empty string. // see if the string resource contains this value ConstantWrapper stringLiteral = new ConstantWrapper( resourceStrings[Name], PrimitiveType.String, Context, Parser ); Parent.ReplaceChild(this, stringLiteral); // analyze the literal stringLiteral.AnalyzeNode(); return; } } // if we are replacing property names and we have something to replace if (Parser.Settings.HasRenamePairs && Parser.Settings.ManualRenamesProperties && Parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming)) { // see if this name is a target for replacement string newName = Parser.Settings.GetNewName(Name); if (!string.IsNullOrEmpty(newName)) { // it is -- set the name to the new name Name = newName; } } // recurse base.AnalyzeNode(); /* REVIEW: might be too late in the parsing -- references to the variable may have * already been analyzed and found undefined * // see if we are assigning to a member on the global window object * BinaryOperator binaryOp = Parent as BinaryOperator; * if (binaryOp != null && binaryOp.IsAssign && m_rootObject.IsWindowLookup) * { * // make sure the name of this property is a valid global variable, since we * // are now assigning to it. * if (Parser.GlobalScope[Name] == null) * { * // it's not -- we need to add it to our list of known globals * // by defining a global field for it * Parser.GlobalScope.DeclareField(Name, null, 0); * } * } */ }
//--------------------------------------------------------------------------------------- // ParseLeftHandSideExpression // // LeftHandSideExpression : // PrimaryExpression Accessor | // 'new' LeftHandSideExpression | // FunctionExpression // // PrimaryExpression : // 'this' | // Identifier | // Literal | // '(' Expression ')' // // FunctionExpression : // 'function' OptionalFuncName '(' FormalParameterList ')' { FunctionBody } // // OptionalFuncName : // <empty> | // Identifier //--------------------------------------------------------------------------------------- private AstNode ParseLeftHandSideExpression(bool isMinus) { AstNode ast = null; bool isFunction = false; 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_scanner.GetIdentifier(), m_currentToken.Clone(), this); break; case JSToken.ConditionalCommentStart: // skip past the start to the next token GetNextToken(); if (m_currentToken.Token == JSToken.PreprocessorConstant) { // we have /*@id ast = new ConstantWrapperPP(m_currentToken.Code, true, m_currentToken.Clone(), this); 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. 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; } } 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.StringLiteral, PrimitiveType.String, m_currentToken.Clone(), this); 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 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); } 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); } 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.PreprocessorConstant: ast = new ConstantWrapperPP(m_currentToken.Code, false, m_currentToken.Clone(), this); 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(source, flags, m_currentToken.Clone(), this); break; } goto default; // expression case JSToken.LeftParenthesis: { // save the current context reference Context openParenContext = m_currentToken.Clone(); GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenExpressionNoSkipToken); try { // parse an expression ast = ParseExpression(); // update the expression's context with the context of the open paren ast.Context.UpdateWith(openParenContext); 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 ast = exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenExpressionNoSkipToken); } if (ast == null) //this can only happen when catching the exception and nothing was sent up by the caller SkipTokensAndThrow(); } break; // array initializer case JSToken.LeftBracket: Context listCtx = m_currentToken.Clone(); AstNodeList list = new AstNodeList(m_currentToken.Clone(), this); GetNextToken(); while (JSToken.RightBracket != m_currentToken.Token) { if (JSToken.Comma != m_currentToken.Token) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); try { list.Append(ParseExpression(true)); if (JSToken.Comma != m_currentToken.Token) { if (JSToken.RightBracket != m_currentToken.Token) ReportError(JSError.NoRightBracket); break; } else { // we have a comma -- skip it 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, 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)); // 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, list); break; // object initializer case JSToken.LeftCurly: Context objCtx = m_currentToken.Clone(); GetNextToken(); // we're going to keep the keys and values in separate lists, but make sure // that the indexes correlate (keyList[n] is associated with valueList[n]) List<ObjectLiteralField> keyList = new List<ObjectLiteralField>(); List<AstNode> valueList = new List<AstNode>(); 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.GetIdentifier(), PrimitiveType.String, m_currentToken.Clone(), this); break; case JSToken.StringLiteral: field = new ObjectLiteralField(m_scanner.StringLiteral, PrimitiveType.String, m_currentToken.Clone(), this); 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 (m_scanner.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, string, number, or getter/setter. // see if it's a token that COULD be an identifier. ident = JSKeyword.CanBeIdentifier(m_currentToken.Token); if (ident != null) { // don't throw a warning -- it's okay to have a keyword that // can be an identifier here. field = new ObjectLiteralField(ident, PrimitiveType.String, m_currentToken.Clone(), this); } else { ReportError(JSError.NoMemberIdentifier); field = new ObjectLiteralField("_#Missing_Field#_" + s_cDummyName++, PrimitiveType.String, CurrentPositionContext(), 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 { GetNextToken(); value = ParseExpression(true); } } // put the pair into the list of fields keyList.Add(field); valueList.Add(value); if (JSToken.RightCurly == m_currentToken.Token) break; else { if (JSToken.Comma == m_currentToken.Token) { // skip the comma 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_scanner.GotEndOfLine) { 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; keyList.Add(field); valueList.Add(value); } if (IndexOfToken(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new ObjectLiteral(objCtx, this, keyList.ToArray(), valueList.ToArray()); 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, keyList.ToArray(), valueList.ToArray()); break; // function expression case JSToken.Function: ast = ParseFunction(FunctionType.Expression, m_currentToken.Clone()); isFunction = true; break; case JSToken.AspNetBlock: ast = ParseAspNetBlock(consumeSemicolonIfPossible: false); break; default: string identifier = JSKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { ast = new Lookup(identifier, m_currentToken.Clone(), this); } else { ReportError(JSError.ExpressionExpected); SkipTokensAndThrow(); } break; } // can be a CallExpression, that is, followed by '.' or '(' or '[' if (!isFunction) GetNextToken(); return MemberExpression(ast, newContexts); }
//--------------------------------------------------------------------------------------- // 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, withCtx, true); } WithScope withScope = new WithScope(ScopeStack.Peek(), withCtx, this); ScopeStack.Push(withScope); 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); } } block.BlockScope = withScope; exc._partiallyComputedNode = new WithNode(withCtx, this, obj, block); throw; } finally { // pop off the with-scope ScopeStack.Pop(); // save the with-scope on the block block.BlockScope = withScope; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new WithNode(withCtx, this, obj, block); }
//--------------------------------------------------------------------------------------- // ParseDoStatement // // DoStatement: // 'do' Statement 'while' '(' Expression ')' //--------------------------------------------------------------------------------------- private DoWhile ParseDoStatement() { Context doCtx = null; AstNode body = null; AstNode condition = null; m_blockType.Add(BlockType.Loop); try { GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet); // if the statements aren't withing curly-braces, throw a possible error if (JSToken.LeftCurly != m_currentToken.Token) { ReportError(JSError.StatementBlockExpected, m_currentToken.Clone(), true); } try { // parse a Statement, not a SourceElement body = ParseStatement(false); } catch (RecoveryTokenException exc) { // make up a block for the do while if (exc._partiallyComputedNode != null) body = exc._partiallyComputedNode; else body = new Block(CurrentPositionContext(), this); if (IndexOfToken(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet, exc) == -1) { // we have to pass the exception to someone else, make as much as you can from the 'do while' exc._partiallyComputedNode = new DoWhile(CurrentPositionContext(), this, body, new ConstantWrapper(false, PrimitiveType.Boolean, CurrentPositionContext(), this)); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet); } if (JSToken.While != m_currentToken.Token) { ReportError(JSError.NoWhile); } doCtx = m_currentToken.Clone(); GetNextToken(); if (JSToken.LeftParenthesis != m_currentToken.Token) { ReportError(JSError.NoLeftParenthesis); } GetNextToken(); // catch here so the body of the do_while is not thrown away m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { condition = ParseExpression(); if (JSToken.RightParenthesis != m_currentToken.Token) { ReportError(JSError.NoRightParenthesis); doCtx.UpdateWith(condition.Context); } else doCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { // make up a condition if (exc._partiallyComputedNode != null) condition = exc._partiallyComputedNode; else condition = new ConstantWrapper(false, PrimitiveType.Boolean, CurrentPositionContext(), this); if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new DoWhile(doCtx, this, body, condition); throw; } else { if (JSToken.RightParenthesis == m_currentToken.Token) GetNextToken(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } if (JSToken.Semicolon == m_currentToken.Token) { // JScript 5 allowed statements like // do{print(++x)}while(x<10) print(0) // even though that does not strictly follow the automatic semicolon insertion // rules for the required semi after the while(). For backwards compatibility // we should continue to support this. doCtx.UpdateWith(m_currentToken); GetNextToken(); } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new DoWhile(doCtx, this, body, condition); }
public void Visit(ConstantWrapper node) { // we're good }
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; }
public override void CleanupNodes() { base.CleanupNodes(); if (Parser.Settings.EvalLiteralExpressions && Parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { // see if our operand is a ConstantWrapper ConstantWrapper literalOperand = Operand as ConstantWrapper; if (literalOperand != null) { // must be number, boolean, string, or null switch (OperatorToken) { case JSToken.Plus: try { // replace with a constant representing operand.ToNumber, Parent.ReplaceChild(this, new ConstantWrapper(literalOperand.ToNumber(), PrimitiveType.Number, Context, 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; case JSToken.Minus: try { // replace with a constant representing the negative of operand.ToNumber Parent.ReplaceChild(this, new ConstantWrapper(-literalOperand.ToNumber(), PrimitiveType.Number, Context, 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; case JSToken.BitwiseNot: try { // replace with a constant representing the bitwise-not of operant.ToInt32 Parent.ReplaceChild(this, new ConstantWrapper(Convert.ToDouble(~literalOperand.ToInt32()), PrimitiveType.Number, Context, 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; case JSToken.LogicalNot: // replace with a constant representing the opposite of operand.ToBoolean try { Parent.ReplaceChild(this, new ConstantWrapper(!literalOperand.ToBoolean(), PrimitiveType.Boolean, Context, Parser)); } catch (InvalidCastException) { // ignore any invalid cast exceptions } break; } } } }
/// <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; }
// the global scope does nothing when told to add literals -- just returns null internal override List <ConstantWrapper> AddLiteral(ConstantWrapper constantWrapper, ActivationObject refScope) { return(null); }
/// <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; }
/// <summary> /// Eval the two constants: CONST [op] (OTHER [op] CONST) /// </summary> /// <param name="thisConstant">first constant</param> /// <param name="otherConstant">second constant</param> /// <param name="rightOperator">second operator</param> 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) { // keep the order, but opposite operator 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); } } } }
// called during ConstantWrapper.AnalyzeNode internal virtual List <ConstantWrapper> AddLiteral(ConstantWrapper constantWrapper, ActivationObject refScope) { List <ConstantWrapper> nodeList = null; // numeric constants that are NaN or Infinity need not apply if (!constantWrapper.IsSpecialNumeric) { // if the constant is only one character long, it's never a good idea to // try and replace it string constantValue = constantWrapper.ToCode(); if (constantValue.Length > 1) { // go up the chain recursively. return the highest shared // constant node list so we can share it if we need to nodeList = ((ActivationObject)Parent).AddLiteral(constantWrapper, this); // if we haven't created our literal map yet, do so now if (m_literalMap == null) { m_literalMap = new Dictionary <string, LiteralReference>(); } // now handle our scope LiteralReference literalReference; // see if this constant is in our map if (m_literalMap.ContainsKey(constantValue)) { literalReference = m_literalMap[constantValue]; // increment the counter literalReference.Increment(); // add this constant wrapper to the list if (!literalReference.ConstantWrapperList.Contains(constantWrapper)) { literalReference.ConstantWrapperList.Add(constantWrapper); } // if this is the ref scope, or if the ref scope is not the child scope, // set the child scope to null if (literalReference.ChildScope != null && (refScope == this || refScope != literalReference.ChildScope)) { literalReference.ChildScope = null; } } else { // add to the table with count = 1 and our given constant wrapper // if this is the ref scope, child scope is null; otherwise use refScope // if nodelist is null, create a new list; otherwise use the shared list literalReference = new LiteralReference( constantWrapper, (refScope != this ? refScope : null), nodeList ); m_literalMap.Add(constantValue, literalReference); } // return whatever list it is we used for our node. // it might be shared, or it might be new if we didn't find a shared list nodeList = literalReference.ConstantWrapperList; } } return(nodeList); }
// the global scope does nothing when told to add literals -- just returns null internal override List<ConstantWrapper> AddLiteral(ConstantWrapper constantWrapper, ActivationObject refScope) { return null; }
private ConstantWrapper Divide(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 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.IsNumericLiteral || left.IsExactInteger || left.IsNaN || left.IsInfinity) && (!right.IsNumericLiteral || right.IsExactInteger || right.IsNaN || right.IsInfinity)) { // they are both either string, bool, null, or integer newLiteral = new ConstantWrapper(left.ToString() + right.ToString(), PrimitiveType.String, null, m_parser); } } } return newLiteral; }
//--------------------------------------------------------------------------------------- // ParseWhileStatement // // WhileStatement : // 'while' '(' Expression ')' Statement //--------------------------------------------------------------------------------------- private WhileNode ParseWhileStatement() { Context whileCtx = m_currentToken.Clone(); AstNode condition = null; AstNode 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 ConstantWrapper(false, PrimitiveType.Boolean, CurrentPositionContext(), this); if (JSToken.RightParenthesis == m_currentToken.Token) 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, whileCtx, true); } try { // parse a Statement, not a SourceElement body = ParseStatement(false); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) body = exc._partiallyComputedNode; else body = new Block(CurrentPositionContext(), this); exc._partiallyComputedNode = new WhileNode(whileCtx, this, condition, body); throw; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new WhileNode(whileCtx, this, condition, body); }
/// <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.ReplaceChild(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); } }
//--------------------------------------------------------------------------------------- // ParseSwitchStatement // // SwitchStatement : // 'switch' '(' Expression ')' '{' CaseBlock '}' // // CaseBlock : // CaseList DefaultCaseClause CaseList // // CaseList : // <empty> | // CaseClause CaseList // // CaseClause : // 'case' Expression ':' OptionalStatements // // DefaultCaseClause : // <empty> | // 'default' ':' OptionalStatements //--------------------------------------------------------------------------------------- private AstNode ParseSwitchStatement() { Context switchCtx = m_currentToken.Clone(); AstNode expr = null; AstNodeList cases = 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); } 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); } GetNextToken(); } } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // parse the switch body cases = new AstNodeList(m_currentToken.Clone(), this); bool defaultStatement = false; m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet); try { while (JSToken.RightCurly != m_currentToken.Token) { SwitchCase caseClause = null; AstNode caseValue = null; Context caseCtx = m_currentToken.Clone(); 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); } // 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 { Block 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, statements); cases.Append(caseClause); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); } if (JSToken.RightCurly == m_currentToken.Token) { statements.Context.UpdateWith(m_currentToken); } caseCtx.UpdateWith(statements.Context); caseClause = new SwitchCase(caseCtx, this, caseValue, 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, expr, cases); 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, expr, cases); }
/// <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, parentCall.Function, newName, 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, parentCall.Function, combinedString, parentCall.Arguments[0].Context); parentCall.Parent.ReplaceChild(parentCall, replacementMember); return true; } } } } return false; }
//--------------------------------------------------------------------------------------- // 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, expression, args, 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, expression, new AstNodeList(CurrentPositionContext(), this), false); 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(m_currentToken.Clone(), 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, expression, (AstNodeList)exc._partiallyComputedNode, 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, expression, args, 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; 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 indentifier, // but it IS a valid identifier format. Throw the error but still // create the constant wrapper so we can output it as-is ReportError(JSError.NoIdentifier, 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.GetIdentifier(), PrimitiveType.String, m_currentToken.Clone(), this); } GetNextToken(); expression = new Member(expression.Context.CombineWith(id.Context), this, expression, id.Context.Code, 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, expression, new AstNodeList(CurrentPositionContext(), this), false); ((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 static string CreateJSFromResourceStrings(ResourceStrings resourceStrings) { var sb = StringBuilderPool.Acquire(); try { // start the var statement using the requested name and open the initializer object literal sb.Append("var "); sb.Append(resourceStrings.Name); sb.Append("={"); // we're going to need to insert commas between each pair, so we'll use a boolean // flag to indicate that we're on the first pair. When we output the first pair, we'll // set the flag to false. When the flag is false, we're about to insert another pair, so // we'll add the comma just before. bool firstItem = true; // loop through all items in the collection foreach (var keyPair in resourceStrings.NameValuePairs) { // if this isn't the first item, we need to add a comma separator if (!firstItem) { sb.Append(','); } else { // next loop is no longer the first item firstItem = false; } // append the key as the name, a colon to separate the name and value, // and then the value // must quote if not valid JS identifier format, or if it is, but it's a keyword // (use strict mode just to be safe) string propertyName = keyPair.Key; if (!JSScanner.IsValidIdentifier(propertyName) || JSScanner.IsKeyword(propertyName, true)) { sb.Append("\""); // because we are using quotes for the delimiters, replace any instances // of a quote character (") with an escaped quote character (\") sb.Append(propertyName.Replace("\"", "\\\"")); sb.Append("\""); } else { sb.Append(propertyName); } sb.Append(':'); // make sure the Value is properly escaped, quoted, and whatever we // need to do to make sure it's a proper JS string. // pass false for whether this string is an argument to a RegExp constructor. // pass false for whether to use W3Strict formatting for character escapes (use maximum browser compatibility) // pass true for ecma strict mode string stringValue = ConstantWrapper.EscapeString( keyPair.Value, false, false, true ); sb.Append(stringValue); } // close the object literal and return the string sb.AppendLine("};"); return(sb.ToString()); } finally { sb.Release(); } }
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 ConstantWrapper Equal(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 true newLiteral = new ConstantWrapper(true, 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 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 (left.IsOkayToCombine && right.IsOkayToCombine) { try { // numeric comparison // +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. 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; }
public override void CleanupNodes() { base.CleanupNodes(); // see if the condition is a constant if (Parser.Settings.EvalLiteralExpressions && Parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { ConstantWrapper constantCondition = Condition as ConstantWrapper; if (constantCondition != null) { // TODO: we'd RATHER eliminate the statement altogether if the condition is always false, // but we'd need to make sure var'd variables and declared functions are properly handled. try { bool isTrue = constantCondition.ToBoolean(); if (isTrue) { // the condition is always true; we should change it to a for(;;) statement. // less bytes than while(1) // check to see if we want to combine a preceding var with a for-statement AstNode initializer = null; if (Parser.Settings.IsModificationAllowed(TreeModifications.MoveVarIntoFor)) { // if the previous statement is a var, we can move it to the initializer // and save even more bytes. The parent should always be a block. If not, // then assume there is no previous. Block parentBlock = Parent as Block; if (parentBlock != null) { int whileIndex = parentBlock.StatementIndex(this); if (whileIndex > 0) { Var previousVar = parentBlock[whileIndex - 1] as Var; if (previousVar != null) { initializer = previousVar; parentBlock.ReplaceChild(previousVar, null); } } } } // create the for using our body and replace ourselves with it ForNode forNode = new ForNode(Context, Parser, initializer, null, null, Body); Parent.ReplaceChild(this, forNode); } else if (constantCondition.IsNotOneOrPositiveZero) { // the condition is always false, so we can replace the condition // with a zero -- only one byte Condition = new ConstantWrapper(0, PrimitiveType.Number, null, Parser); } } catch (InvalidCastException) { // ignore any invalid cast exceptions } } } }
/// <summary> /// Evaluate: (OTHER [op] CONST) [op] CONST /// </summary> /// <param name="thisConstant">second constant</param> /// <param name="otherConstant">first constant</param> /// <param name="leftOperator">first operator</param> 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); } } }
private ConstantWrapper LessThanOrEqual(ConstantWrapper left, ConstantWrapper right) { ConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { if (left.IsStringLiteral && right.IsStringLiteral) { // 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; }
public override void Visit(ConstantWrapper node) { if (node != null) { // check to see if this node is an argument to a RegExp constructor. // if it is, we'll want to not use certain string escapes AstNode previousNode = null; AstNode parentNode = node.Parent; while (parentNode != null) { // is this a call node and the previous node was one of the parameters? CallNode callNode = parentNode as CallNode; if (callNode != null && previousNode == callNode.Arguments) { // are we calling a simple lookup for "RegExp"? Lookup lookup = callNode.Function as Lookup; if (lookup != null && lookup.Name == "RegExp") { // we are -- so all string literals passed within this constructor should not use // standard string escape sequences node.IsParameterToRegExp = true; // we can stop looking break; } } // next up the chain, keeping track of this current node as next iteration's "previous" node previousNode = parentNode; parentNode = parentNode.Parent; } // we only need to process the literals IF we are actually going to do // anything with them (combine duplicates). So if we aren't, don't call // AddLiteral because it hugely inflates the processing time of the application. if (m_parser.Settings.CombineDuplicateLiterals) { // add this literal to the scope's literal collection. // HOWEVER, we do NOT want to add it for consideration of literal combination // if any scope above us is a with-scope -- otherwise the // variable we use to combine the literals might be confused with a // property on the with-object. // AND we don't want to do it if the scope is unknown, for the same reason. // we won't really know if the variable we create will interfere with the // scope resolution of any variables that me in the eval string. ActivationObject thisScope = ScopeStack.Peek(); if (thisScope.IsKnownAtCompileTime && !thisScope.IsInWithScope) { thisScope.AddLiteral(node, thisScope); } } // this node has no children, so don't bother calling the base //base.Visit(node); } }
internal override void AnalyzeNode() { // see if this is a member (we'll need it for a couple checks) Member member = m_func as Member; if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // if this is a member, and it's a debugger object, and it's a constructor.... if (member != null && member.IsDebuggerStatement && m_isConstructor) { // we need to replace our debugger object with a generic Object m_func = new Lookup("Object", m_func.Context, Parser); // and make sure the node list is empty if (m_args != null && m_args.Count > 0) { m_args = new AstNodeList(m_args.Context, Parser); } } } // if this is a constructor and we want to collapse // some of them to literals... if (m_isConstructor && Parser.Settings.CollapseToLiteral) { // see if this is a lookup, and if so, if it's pointing to one // of the two constructors we want to collapse Lookup lookup = m_func as Lookup; if (lookup != null) { if (lookup.Name == "Object" && Parser.Settings.IsModificationAllowed(TreeModifications.NewObjectToObjectLiteral)) { // no arguments -- the Object constructor with no arguments is the exact same as an empty // object literal if (m_args == null || m_args.Count == 0) { // replace our node with an object literal ObjectLiteral objLiteral = new ObjectLiteral(Context, Parser, null, null); if (Parent.ReplaceChild(this, objLiteral)) { // and bail now. No need to recurse -- it's an empty literal return; } } else if (m_args.Count == 1) { // one argument // check to see if it's an object literal. ObjectLiteral objectLiteral = m_args[0] as ObjectLiteral; if (objectLiteral != null) { // the Object constructor with an argument that is a JavaScript object merely returns the // argument. Since the argument is an object literal, it is by definition a JavaScript object // and therefore we can replace the constructor call with the object literal Parent.ReplaceChild(this, objectLiteral); // don't forget to recurse the object now objectLiteral.AnalyzeNode(); // and then bail -- we don't want to process this call // operation any more; we've gotten rid of it return; } } } else if (lookup.Name == "Array" && Parser.Settings.IsModificationAllowed(TreeModifications.NewArrayToArrayLiteral)) { // Array is trickier. // If there are no arguments, then just use []. // if there are multiple arguments, then use [arg0,arg1...argN]. // but if there is one argument and it's numeric, we can't crunch it. // also can't crunch if it's a function call or a member or something, since we won't // KNOW whether or not it's numeric. // // so first see if it even is a single-argument constant wrapper. ConstantWrapper constWrapper = (m_args != null && m_args.Count == 1 ? m_args[0] as ConstantWrapper : null); // if the argument count is not one, then we crunch. // if the argument count IS one, we only crunch if we have a constant wrapper, // AND it's not numeric. if (m_args == null || m_args.Count != 1 || (constWrapper != null && !constWrapper.IsNumericLiteral)) { // create the new array literal object ArrayLiteral arrayLiteral = new ArrayLiteral(Context, Parser, m_args); // replace ourself within our parent if (Parent.ReplaceChild(this, arrayLiteral)) { // recurse arrayLiteral.AnalyzeNode(); // and bail -- we don't want to recurse this node any more return; } } } } } // if we are replacing resource references with strings generated from resource files // and this is a brackets call: lookup[args] ResourceStrings resourceStrings = Parser.ResourceStrings; if (m_inBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = m_func as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // we're going to replace this node with a string constant wrapper // but first we need to make sure that this is a valid lookup. // if the parameter contains anything that would vary at run-time, // then we need to throw an error. // the parser will always have either one or zero nodes in the arguments // arg list. We're not interested in zero args, so just make sure there is one if (m_args.Count == 1) { // must be a constant wrapper ConstantWrapper argConstant = m_args[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.String, Context, Parser); // replace this node with localized string, analyze it, and bail // so we don't anaylze the tree we just replaced Parent.ReplaceChild(this, resourceLiteral); resourceLiteral.AnalyzeNode(); return; } else { // error! must be a constant Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } else { // error! can only be a single constant argument to the string resource object. // the parser will only have zero or one arguments, so this must be zero // (since the parser won't pass multiple args to a [] operator) Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } } // and finally, if this is a backets call and the argument is a constantwrapper that can // be an identifier, just change us to a member node: obj["prop"] to obj.prop. // but ONLY if the string value is "safe" to be an identifier. Even though the ECMA-262 // spec says certain Unicode categories are okay, in practice the various major browsers // all seem to have problems with certain characters in identifiers. Rather than risking // some browsers breaking when we change this syntax, don't do it for those "danger" categories. if (m_inBrackets && m_args != null) { // see if there is a single, constant argument string argText = m_args.SingleConstantArgument; if (argText != null) { // see if we want to replace the name string newName; if (Parser.Settings.HasRenamePairs && Parser.Settings.ManualRenamesProperties && Parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = Parser.Settings.GetNewName(argText))) { // yes -- we are going to replace the name, either as a string literal, or by converting // to a member-dot operation. // See if we can't turn it into a dot-operator. If we can't, then we just want to replace the operator with // a new constant wrapper. Otherwise we'll just replace the operator with a new constant wrapper. if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(newName) && !JSScanner.IsKeyword(newName)) { // the new name is safe to convert to a member-dot operator. // but we don't want to convert the node to the NEW name, because we still need to Analyze the // new member node -- and it might convert the new name to something else. So instead we're // just going to convert this existing string to a member node WITH THE OLD STRING, // and THEN analyze it (which will convert the old string to newName) Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); // this analyze call will convert the old-name member to the newName value replacementMember.AnalyzeNode(); return; } else { // nope; can't convert to a dot-operator. // we're just going to replace the first argument with a new string literal // and continue along our merry way. m_args.ReplaceChild(m_args[0], new ConstantWrapper(newName, PrimitiveType.String, m_args[0].Context, Parser)); } } else if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(argText) && !JSScanner.IsKeyword(argText)) { // not a replacement, but the string literal is a safe identifier. So we will // replace this call node with a Member-dot operation Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); replacementMember.AnalyzeNode(); return; } } } // call the base class to recurse base.AnalyzeNode(); // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!m_inBrackets && !m_isConstructor && (m_args == null || m_args.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && Parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(Context, Parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator Parent.ReplaceChild(this, unary); // don't need to AnalyzeNode on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else if (Parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // if this is a window.eval call, then we need to mark this scope as unknown just as // we would if this was a regular eval call. // (unless, of course, the parser settings say evals are safe) // call AFTER recursing so we know the left-hand side properties have had a chance to // lookup their fields to see if they are local or global if (member != null && string.CompareOrdinal(member.Name, "eval") == 0) { if (member.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } else { CallNode callNode = m_func as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } /* REVIEW: may be too late. lookups may alread have been analyzed and * found undefined * // check to see if this is an assignment to a window["prop"] structure * BinaryOperator binaryOp = Parent as BinaryOperator; * if (binaryOp != null && binaryOp.IsAssign * && m_inBrackets * && m_func.IsWindowLookup * && m_args != null) * { * // and IF the property is a non-empty constant that isn't currently * // a global field... * string propertyName = m_args.SingleConstantArgument; * if (!string.IsNullOrEmpty(propertyName) * && Parser.GlobalScope[propertyName] == null) * { * // we want to also add it to the global fields so it's not undefined * Parser.GlobalScope.DeclareField(propertyName, null, 0); * } * } */ }
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; }
protected virtual void CreateLiteralShortcuts() { if (m_literalMap != null) { // get a reference to the first function scope in the chain // might be this, might be a parent FunctionScope functionScope = null; ActivationObject scope = this; while (scope != null && (functionScope = scope as FunctionScope) == null) { scope = scope.Parent; } // if we didn't find a parent function scope, then don't do any combining // because the literals are globals if (functionScope != null) { // for each value in our literal map foreach (string constantString in m_literalMap.Keys) { LiteralReference literalReference = m_literalMap[constantString]; // if the child scope isn't null, then we don't reference the literal // and only one of our child scopes does, so we don't want to add the // shortcut here. // OR if there are no constant wrappers left in the list, then we've already // replaced them all and there's nothing left to do. // BUT if the child scope is null, either we reference it, or more than // one child references it. So if there are any constant wrappers in the list, // then we want to add the shortcut and replace all the constants if (literalReference.ChildScope == null && literalReference.ConstantWrapperList.Count > 0) { // AND we only want to do it if it will be worthwhile. // (and a constant of length 1 is never worthwhile) int constantLength = constantString.Length; if (constantLength > 1) { int minCount = (constantLength + 7) / (constantLength - 1); if (literalReference.Count > minCount) { // create a special name that won't collide with any other variable names string specialName = string.Format(CultureInfo.InvariantCulture, "[literal:{0}]", ++s_literalCounter); // add a generated var statement at the top of the function block that // is equal to the literal value (just use the first constant wrapper as a model) ConstantWrapper modelConstant = literalReference.ConstantWrapperList[0]; // by default we will use the value of the first instance as the generated variable's value object generatedValue = modelConstant.Value; // BUT.... // if this is a numeric value, then we need to determine whether we should use a // positive or negative version of this value to minimize the number of minus operators in the results if (modelConstant.IsNumericLiteral) { // first we need to go through the existing references and count how many negative values there are var numberOfNegatives = 0; foreach (ConstantWrapper constantWrapper in literalReference.ConstantWrapperList) { // since the model us numeric, we shouldn't have any problems calling the // ToNumber method on the others (which should all also be numeric) if (constantWrapper.ToNumber() < 0) { ++numberOfNegatives; } } // now if more than half of the references are negative, we will want the generated value // to also be negative! Otherwise we want to force it to Positive. var absoluteValue = Math.Abs((double)generatedValue); if (numberOfNegatives > literalReference.ConstantWrapperList.Count / 2) { // force it to negative generatedValue = -absoluteValue; } else { // force it to positive generatedValue = absoluteValue; } } // add the generated variable to the function scope functionScope.FunctionObject.AddGeneratedVar( specialName, new ConstantWrapper( generatedValue, modelConstant.PrimitiveType, modelConstant.Context, Parser), true); // walk the list of constant wrappers backwards (because we'll be removing them // as we go along) and replace each one with a lookup for the generated variable. // Don't forget to analyze the lookup. for (int ndx = literalReference.ConstantWrapperList.Count - 1; ndx >= 0; --ndx) { ConstantWrapper constantWrapper = literalReference.ConstantWrapperList[ndx]; // create the lookup based on the thisliteral context Lookup lookup = new Lookup(specialName, constantWrapper.Context, Parser); // indicate this is generated by our code, not the user lookup.IsGenerated = true; // by default, we're just going to replace the constant with the lookup AstNode replacement = lookup; // if the constant wrapper is a numeric value that is the NEGATIVE of the // combined numeric value (which would happen if the literal was subsequently // combined with a unary minus operator), then we need to change this to a unary-minus // operator on the lookup, not just the lookup. if (constantWrapper.IsNumericLiteral) { // since the constant wrapper is numeric, we shouldn't have any problems // calling ToNumber if ((double)generatedValue == -constantWrapper.ToNumber()) { // it has been negated! Change the replacement to a unary minus operator // with the lookup as its operand replacement = new NumericUnary( constantWrapper.Context, Parser, lookup, JSToken.Minus); } } // replace the this literal with the appropriate node constantWrapper.Parent.ReplaceChild(constantWrapper, replacement); // set up the lookup's outer local field using the scope of the // original constant wrapper lookup.SetOuterLocalField(constantWrapper.EnclosingScope); // and remove it from the list. This is so child scopes don't also try to // add a shortcut -- the list will be empty. literalReference.ConstantWrapperList.RemoveAt(ndx); } } } } } } } }
//--------------------------------------------------------------------------------------- // ParseIfStatement // // IfStatement : // 'if' '(' Expression ')' Statement ElseStatement // // ElseStatement : // <empty> | // 'else' Statement //--------------------------------------------------------------------------------------- private IfNode ParseIfStatement() { Context ifCtx = m_currentToken.Clone(); AstNode condition = null; AstNode trueBranch = null; AstNode falseBranch = null; m_blockType.Add(BlockType.Block); try { // parse condition GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { if (JSToken.LeftParenthesis != m_currentToken.Token) ReportError(JSError.NoLeftParenthesis); GetNextToken(); condition = ParseExpression(); // parse statements if (JSToken.RightParenthesis != m_currentToken.Token) { ifCtx.UpdateWith(condition.Context); ReportError(JSError.NoRightParenthesis); } else ifCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { // make up an if condition if (exc._partiallyComputedNode != null) condition = exc._partiallyComputedNode; else condition = new ConstantWrapper(true, PrimitiveType.Boolean, CurrentPositionContext(), this); if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = null; // really not much to pass up // the if condition was so bogus we do not have a chance to make an If node, give up throw; } else { if (exc._token == JSToken.RightParenthesis) 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. We can know if it's wrapped in parens // if the first character of the context is a paren and it's BEFORE the context of the leftmost // context of the condition. BinaryOperator binOp = condition as BinaryOperator; if (binOp != null && binOp.OperatorToken == JSToken.Assign && condition.Context != null && condition.Context.Code != null && !(condition.Context.Code.StartsWith("(", StringComparison.Ordinal) && condition.Context.IsBefore(binOp.LeftHandSide.Context))) { condition.Context.HandleError(JSError.SuspectAssignment); } m_noSkipTokenSet.Add(NoSkipTokenSet.s_IfBodyNoSkipTokenSet); if (JSToken.Semicolon == m_currentToken.Token) { ForceReportInfo(JSError.SuspectSemicolon); } else if (JSToken.LeftCurly != m_currentToken.Token) { // if the statements aren't withing curly-braces, throw a possible error ReportError(JSError.StatementBlockExpected, ifCtx, true); } try { // parse a Statement, not a SourceElement trueBranch = ParseStatement(false); } catch (RecoveryTokenException exc) { // make up a block for the if part if (exc._partiallyComputedNode != null) trueBranch = exc._partiallyComputedNode; else trueBranch = new Block(CurrentPositionContext(), this); if (IndexOfToken(NoSkipTokenSet.s_IfBodyNoSkipTokenSet, exc) == -1) { // we have to pass the exception to someone else, make as much as you can from the if exc._partiallyComputedNode = new IfNode(ifCtx, this, condition, trueBranch, falseBranch); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_IfBodyNoSkipTokenSet); } // parse else, if any if (JSToken.Else == m_currentToken.Token) { Context elseCtx = m_currentToken.Clone(); GetNextToken(); if (JSToken.Semicolon == m_currentToken.Token) ForceReportInfo(JSError.SuspectSemicolon); // if the statements aren't withing curly-braces, throw a possible error if (JSToken.LeftCurly != m_currentToken.Token && JSToken.If != m_currentToken.Token) { ReportError(JSError.StatementBlockExpected, elseCtx, true); } try { // parse a Statement, not a SourceElement falseBranch = ParseStatement(false); } catch (RecoveryTokenException exc) { // make up a block for the else part if (exc._partiallyComputedNode != null) falseBranch = exc._partiallyComputedNode; else falseBranch = new Block(CurrentPositionContext(), this); exc._partiallyComputedNode = new IfNode(ifCtx, this, condition, trueBranch, falseBranch); throw; } } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new IfNode(ifCtx, this, condition, trueBranch, falseBranch); }
/// <summary> /// Evaluate: CONST [op] (CONST [op] OTHER) /// </summary> /// <param name="thisConstant">first constant</param> /// <param name="otherConstant">second constant</param> /// <param name="leftOperator">second operator</param> 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); } } } } }
//--------------------------------------------------------------------------------------- // ParseForStatement // // ForStatement : // 'for' '(' OptionalExpressionNoIn ';' OptionalExpression ';' OptionalExpression ')' // 'for' '(' 'var' VariableDeclarationListNoIn ';' OptionalExpression ';' OptionalExpression ')' // 'for' '(' LeftHandSideExpression 'in' Expression')' // 'for' '(' 'var' Identifier OptionalInitializerNoIn 'in' Expression')' // // OptionalExpressionNoIn : // <empty> | // ExpressionNoIn // same as Expression but does not process 'in' as an operator // // OptionalInitializerNoIn : // <empty> | // InitializerNoIn // same as initializer but does not process 'in' as an operator //--------------------------------------------------------------------------------------- private AstNode ParseForStatement() { m_blockType.Add(BlockType.Loop); AstNode forNode = null; try { Context forCtx = m_currentToken.Clone(); GetNextToken(); if (JSToken.LeftParenthesis != m_currentToken.Token) ReportError(JSError.NoLeftParenthesis); GetNextToken(); bool isForIn = false, recoveryInForIn = false; AstNode lhs = null, initializer = null, condOrColl = null, increment = null; try { if (JSToken.Var == m_currentToken.Token) { isForIn = true; Var varList = new Var(m_currentToken.Clone(), this); varList.Append(ParseIdentifierInitializer(JSToken.In, (FieldAttributes)0)); // a list of variable initializers is allowed only in a for(;;) while (JSToken.Comma == m_currentToken.Token) { isForIn = false; varList.Append(ParseIdentifierInitializer(JSToken.In, (FieldAttributes)0)); //initializer = new Comma(initializer.context.CombineWith(var.context), initializer, var); } initializer = varList; // if it could still be a for..in, now it's time to get the 'in' if (isForIn) { if (JSToken.In == m_currentToken.Token) { GetNextToken(); condOrColl = ParseExpression(); } else isForIn = false; } } else { if (JSToken.Semicolon != m_currentToken.Token) { bool isLHS; initializer = ParseUnaryExpression(out isLHS, false); if (isLHS && JSToken.In == m_currentToken.Token) { isForIn = true; lhs = initializer; initializer = null; GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { condOrColl = ParseExpression(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = null; throw; } else { if (exc._partiallyComputedNode == null) condOrColl = new ConstantWrapper(true, PrimitiveType.Boolean, CurrentPositionContext(), this); // what could we put here? else condOrColl = exc._partiallyComputedNode; } if (exc._token == JSToken.RightParenthesis) { GetNextToken(); recoveryInForIn = true; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } } else initializer = ParseExpression(initializer, false, isLHS, JSToken.In); } } } catch (RecoveryTokenException exc) { // error is too early abort for exc._partiallyComputedNode = null; throw; } // at this point we know whether or not is a for..in if (isForIn) { if (!recoveryInForIn) { if (JSToken.RightParenthesis != m_currentToken.Token) ReportError(JSError.NoRightParenthesis); forCtx.UpdateWith(m_currentToken); GetNextToken(); } AstNode body = null; // if the statements aren't withing curly-braces, throw a possible error if (JSToken.LeftCurly != m_currentToken.Token) { ReportError(JSError.StatementBlockExpected, forCtx, true); } try { // parse a Statement, not a SourceElement body = ParseStatement(false); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) body = new Block(CurrentPositionContext(), this); else body = exc._partiallyComputedNode; exc._partiallyComputedNode = new ForIn(forCtx, this, (lhs != null ? lhs : initializer), condOrColl, body); throw; } // for (a in b) // lhs = a, initializer = null // for (var a in b) // lhs = null, initializer = var a forNode = new ForIn(forCtx, this, (lhs != null ? lhs : initializer), condOrColl, body); } else { m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { if (JSToken.Semicolon != m_currentToken.Token) { ReportError(JSError.NoSemicolon); if (JSToken.Colon == m_currentToken.Token) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet); try { SkipTokensAndThrow(); } catch (RecoveryTokenException) { if (JSToken.Semicolon == m_currentToken.Token) m_errorToken = null; else throw; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet); } } } GetNextToken(); if (JSToken.Semicolon != m_currentToken.Token) { condOrColl = ParseExpression(); if (JSToken.Semicolon != m_currentToken.Token) ReportError(JSError.NoSemicolon); } GetNextToken(); if (JSToken.RightParenthesis != m_currentToken.Token) increment = ParseExpression(); if (JSToken.RightParenthesis != m_currentToken.Token) ReportError(JSError.NoRightParenthesis); forCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = null; throw; } else { // discard any partial info, just genrate empty condition and increment and keep going exc._partiallyComputedNode = null; if (condOrColl == null) condOrColl = new ConstantWrapper(true, PrimitiveType.Boolean, CurrentPositionContext(), this); } if (exc._token == JSToken.RightParenthesis) { GetNextToken(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } AstNode body = null; // if the statements aren't withing curly-braces, throw a possible error if (JSToken.LeftCurly != m_currentToken.Token) { ReportError(JSError.StatementBlockExpected, forCtx, true); } try { // parse a Statement, not a SourceElement body = ParseStatement(false); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) body = new Block(CurrentPositionContext(), this); else body = exc._partiallyComputedNode; exc._partiallyComputedNode = new ForNode(forCtx, this, initializer, condOrColl, increment, body); throw; } forNode = new ForNode(forCtx, this, initializer, condOrColl, increment, body); } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return forNode; }
public override void Visit(Member node) { if (node != null) { // if we don't even have any resource strings, then there's nothing // we need to do and we can just perform the base operation ResourceStrings resourceStrings = m_parser.ResourceStrings; if (resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = node.Root as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // it is -- we're going to replace this with a string value. // if this member name is a string on the object, we'll replacve it with // the literal. Otherwise we'll replace it with an empty string. // see if the string resource contains this value ConstantWrapper stringLiteral = new ConstantWrapper( resourceStrings[node.Name] ?? string.Empty, PrimitiveType.String, node.Context, m_parser ); node.Parent.ReplaceChild(node, stringLiteral); // analyze the literal stringLiteral.Accept(this); return; } } // if we are replacing property names and we have something to replace if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties && m_parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming)) { // see if this name is a target for replacement string newName = m_parser.Settings.GetNewName(node.Name); if (!string.IsNullOrEmpty(newName)) { // it is -- set the name to the new name node.Name = newName; } } // check the name of the member for reserved words that aren't allowed if (JSScanner.IsKeyword(node.Name, ScopeStack.Peek().UseStrict)) { node.NameContext.HandleError(JSError.KeywordUsedAsIdentifier, true); } // recurse base.Visit(node); } }
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; }
public void Visit(ConstantWrapper node) { // it's a constant, so we don't care }
public override void Visit(CallNode node) { if (node != null) { // see if this is a member (we'll need it for a couple checks) Member member = node.Function as Member; if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // if this is a member, and it's a debugger object, and it's a constructor.... if (member != null && member.IsDebuggerStatement && node.IsConstructor) { // we need to replace our debugger object with a generic Object node.ReplaceChild(node.Function, new Lookup("Object", node.Function.Context, m_parser)); // and make sure the node list is empty if (node.Arguments != null && node.Arguments.Count > 0) { node.ReplaceChild(node.Arguments, new AstNodeList(node.Arguments.Context, m_parser)); } } } // if this is a constructor and we want to collapse // some of them to literals... if (node.IsConstructor && m_parser.Settings.CollapseToLiteral) { // see if this is a lookup, and if so, if it's pointing to one // of the two constructors we want to collapse Lookup lookup = node.Function as Lookup; if (lookup != null) { if (lookup.Name == "Object" && m_parser.Settings.IsModificationAllowed(TreeModifications.NewObjectToObjectLiteral)) { // no arguments -- the Object constructor with no arguments is the exact same as an empty // object literal if (node.Arguments == null || node.Arguments.Count == 0) { // replace our node with an object literal ObjectLiteral objLiteral = new ObjectLiteral(node.Context, m_parser, null, null); if (node.Parent.ReplaceChild(node, objLiteral)) { // and bail now. No need to recurse -- it's an empty literal return; } } else if (node.Arguments.Count == 1) { // one argument // check to see if it's an object literal. ObjectLiteral objectLiteral = node.Arguments[0] as ObjectLiteral; if (objectLiteral != null) { // the Object constructor with an argument that is a JavaScript object merely returns the // argument. Since the argument is an object literal, it is by definition a JavaScript object // and therefore we can replace the constructor call with the object literal node.Parent.ReplaceChild(node, objectLiteral); // don't forget to recurse the object now objectLiteral.Accept(this); // and then bail -- we don't want to process this call // operation any more; we've gotten rid of it return; } } } else if (lookup.Name == "Array" && m_parser.Settings.IsModificationAllowed(TreeModifications.NewArrayToArrayLiteral)) { // Array is trickier. // If there are no arguments, then just use []. // if there are multiple arguments, then use [arg0,arg1...argN]. // but if there is one argument and it's numeric, we can't crunch it. // also can't crunch if it's a function call or a member or something, since we won't // KNOW whether or not it's numeric. // // so first see if it even is a single-argument constant wrapper. ConstantWrapper constWrapper = (node.Arguments != null && node.Arguments.Count == 1 ? node.Arguments[0] as ConstantWrapper : null); // if the argument count is not one, then we crunch. // if the argument count IS one, we only crunch if we have a constant wrapper, // AND it's not numeric. if (node.Arguments == null || node.Arguments.Count != 1 || (constWrapper != null && !constWrapper.IsNumericLiteral)) { // create the new array literal object ArrayLiteral arrayLiteral = new ArrayLiteral(node.Context, m_parser, node.Arguments); // replace ourself within our parent if (node.Parent.ReplaceChild(node, arrayLiteral)) { // recurse arrayLiteral.Accept(this); // and bail -- we don't want to recurse this node any more return; } } } } } // if we are replacing resource references with strings generated from resource files // and this is a brackets call: lookup[args] ResourceStrings resourceStrings = m_parser.ResourceStrings; if (node.InBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = node.Function as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // we're going to replace this node with a string constant wrapper // but first we need to make sure that this is a valid lookup. // if the parameter contains anything that would vary at run-time, // then we need to throw an error. // the parser will always have either one or zero nodes in the arguments // arg list. We're not interested in zero args, so just make sure there is one if (node.Arguments.Count == 1) { // must be a constant wrapper ConstantWrapper argConstant = node.Arguments[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.String, node.Context, m_parser); // replace this node with localized string, analyze it, and bail // so we don't anaylze the tree we just replaced node.Parent.ReplaceChild(node, resourceLiteral); resourceLiteral.Accept(this); return; } else { // error! must be a constant node.Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } else { // error! can only be a single constant argument to the string resource object. // the parser will only have zero or one arguments, so this must be zero // (since the parser won't pass multiple args to a [] operator) node.Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } } // and finally, if this is a backets call and the argument is a constantwrapper that can // be an identifier, just change us to a member node: obj["prop"] to obj.prop. // but ONLY if the string value is "safe" to be an identifier. Even though the ECMA-262 // spec says certain Unicode categories are okay, in practice the various major browsers // all seem to have problems with certain characters in identifiers. Rather than risking // some browsers breaking when we change this syntax, don't do it for those "danger" categories. if (node.InBrackets && node.Arguments != null) { // see if there is a single, constant argument string argText = node.Arguments.SingleConstantArgument; if (argText != null) { // see if we want to replace the name string newName; if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties && m_parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = m_parser.Settings.GetNewName(argText))) { // yes -- we are going to replace the name, either as a string literal, or by converting // to a member-dot operation. // See if we can't turn it into a dot-operator. If we can't, then we just want to replace the operator with // a new constant wrapper. Otherwise we'll just replace the operator with a new constant wrapper. if (m_parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(newName) && !JSScanner.IsKeyword(newName, node.EnclosingScope.UseStrict)) { // the new name is safe to convert to a member-dot operator. // but we don't want to convert the node to the NEW name, because we still need to Analyze the // new member node -- and it might convert the new name to something else. So instead we're // just going to convert this existing string to a member node WITH THE OLD STRING, // and THEN analyze it (which will convert the old string to newName) Member replacementMember = new Member(node.Context, m_parser, node.Function, argText, node.Arguments[0].Context); node.Parent.ReplaceChild(node, replacementMember); // this analyze call will convert the old-name member to the newName value replacementMember.Accept(this); return; } else { // nope; can't convert to a dot-operator. // we're just going to replace the first argument with a new string literal // and continue along our merry way. node.Arguments.ReplaceChild(node.Arguments[0], new ConstantWrapper(newName, PrimitiveType.String, node.Arguments[0].Context, m_parser)); } } else if (m_parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(argText) && !JSScanner.IsKeyword(argText, node.EnclosingScope.UseStrict)) { // not a replacement, but the string literal is a safe identifier. So we will // replace this call node with a Member-dot operation Member replacementMember = new Member(node.Context, m_parser, node.Function, argText, node.Arguments[0].Context); node.Parent.ReplaceChild(node, replacementMember); replacementMember.Accept(this); return; } } } // call the base class to recurse base.Visit(node); // might have changed member = node.Function as Member; // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!node.InBrackets && !node.IsConstructor && (node.Arguments == null || node.Arguments.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && m_parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(node.Context, m_parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator node.Parent.ReplaceChild(node, unary); // don't need to recurse on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else { var isEval = false; var lookup = node.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "eval") == 0 && lookup.VariableField is JSPredefinedField) { // call to predefined eval function isEval = true; } else if (member != null && string.CompareOrdinal(member.Name, "eval") == 0) { // if this is a window.eval call, then we need to mark this scope as unknown just as // we would if this was a regular eval call. // (unless, of course, the parser settings say evals are safe) // call AFTER recursing so we know the left-hand side properties have had a chance to // lookup their fields to see if they are local or global if (member.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() isEval = true; } } else { CallNode callNode = node.Function as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] isEval = true; } } if (isEval) { if (m_parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } } }