public void Visit(JsConstantWrapper node) { if (node != null) { node.Index = NextOrderIndex; } }
public override void Visit(JsConstantWrapper node) { if (node != null) { // measure if (node.PrimitiveType == JsPrimitiveType.Boolean) { if (m_measure) { // if we are converting true/false literals to !0/!1, then // a logical-not doesn't add or subtract anything. But if we aren't, // we need to add/subtract the difference in the length between the // "true" and "false" strings if (!m_parser.Settings.MinifyCode || !m_parser.Settings.IsModificationAllowed(JsTreeModifications.BooleanLiteralsToNotOperators)) { // converting true to false adds a character, false to true subtracts m_delta += node.ToBoolean() ? 1 : -1; } } else { // convert - just flip the boolean value node.Value = !node.ToBoolean(); } } else { // just the same typical operation as most other nodes for other types TypicalHandler(node); } } }
public void Visit(JsConstantWrapper node) { if (node != null) { // allow string, number, true, false, and null. switch (node.PrimitiveType) { case JsPrimitiveType.Boolean: m_writer.Write((bool)node.Value ? "true" : "false"); break; case JsPrimitiveType.Null: m_writer.Write("null"); break; case JsPrimitiveType.Number: OutputNumber((double)node.Value, node.Context); break; case JsPrimitiveType.String: case JsPrimitiveType.Other: // string -- or treat it like a string OutputString(node.Value.ToString()); break; } } }
private static bool IsMinificationHint(JsConstantWrapper node) { var isHint = false; if (node.PrimitiveType == JsPrimitiveType.String) { // try splitting on commas and removing empty items var sections = node.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var section in sections) { // valid hints are: // name:nomunge don't automatically rename the field defined in this scope named "name" // if name is missing (colon is the first character) or "*", then don't rename ANY // fields defined in the current scope. var ndxColon = section.IndexOf(':'); if (ndxColon >= 0) { // make sure this is a "nomunge" hint. If it is, then the entire node is treated as a hint and // will be removed from the AST. if (string.Compare(section.Substring(ndxColon + 1).Trim(), "nomunge", StringComparison.OrdinalIgnoreCase) == 0) { // it is. isHint = true; // get the name that we don't want to munge. Null means all. Convert "*" // to null. var identifier = section.Substring(0, ndxColon).Trim(); if (string.IsNullOrEmpty(identifier) || string.CompareOrdinal(identifier, "*") == 0) { identifier = null; } // get the current scope and iterate over all the fields within it // looking for just the ones that are defined here (outer is null) var currentScope = node.EnclosingScope; foreach (var field in currentScope.NameTable.Values) { if (field.OuterField == null) { // if the identifier is null or matches exactly, mark it as not crunchable if (identifier == null || string.CompareOrdinal(identifier, field.Name) == 0) { field.CanCrunch = false; } } } } } } } return(isHint); }
public bool IsSingleConstantArgument(string argumentValue) { if (m_list.Count == 1) { JsConstantWrapper constantWrapper = m_list[0] as JsConstantWrapper; if (constantWrapper != null && string.CompareOrdinal(constantWrapper.Value.ToString(), argumentValue) == 0) { return(true); } } return(false); }
public override void Visit(JsConstantWrapper node) { if (node != null) { // no children, so don't bother calling the base. if (node.PrimitiveType == JsPrimitiveType.Boolean && m_parser.Settings.IsModificationAllowed(JsTreeModifications.BooleanLiteralsToNotOperators)) { node.Parent.ReplaceChild(node, new JsUnaryOperator(node.Context, m_parser) { Operand = new JsConstantWrapper(node.ToBoolean() ? 0 : 1, JsPrimitiveType.Number, node.Context, m_parser), OperatorToken = JsToken.LogicalNot }); } } }
public override void Visit(JsConstantWrapper node) { // by default this node has nothing to do and no children to recurse. // but if this node's parent is a block, then this is an expression statement // consisting of a single string literal. Normally we would ignore these -- if // they occured at the top of the block they would be DirectivePrologues. So because // this exists, it must not be at the top. But we still want to check it for the nomunge // hints and respect them if that's what it is. if (node != null && node.Parent is JsBlock) { // if this is a hint, process it as such. if (IsMinificationHint(node)) { // and then remove it. We can do that here, because blocks are processed // in reverse order. node.Parent.ReplaceChild(node, null); } } }
private JsConstantWrapper Plus(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (left.IsStringLiteral || right.IsStringLiteral) { // one or both are strings -- this is a strng concat operation newLiteral = StringConcat(left, right); } else { // neither are strings -- this is a numeric addition operation newLiteral = NumericAddition(left, right); } return newLiteral; }
private static bool IsMinificationHint(JsConstantWrapper node) { var isHint = false; if (node.PrimitiveType == JsPrimitiveType.String) { // try splitting on commas and removing empty items var sections = node.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var section in sections) { // valid hints are: // name:nomunge don't automatically rename the field defined in this scope named "name" // if name is missing (colon is the first character) or "*", then don't rename ANY // fields defined in the current scope. var ndxColon = section.IndexOf(':'); if (ndxColon >= 0) { // make sure this is a "nomunge" hint. If it is, then the entire node is treated as a hint and // will be removed from the AST. if (string.Compare(section.Substring(ndxColon + 1).Trim(), "nomunge", StringComparison.OrdinalIgnoreCase) == 0) { // it is. isHint = true; // get the name that we don't want to munge. Null means all. Convert "*" // to null. var identifier = section.Substring(0, ndxColon).Trim(); if (string.IsNullOrEmpty(identifier) || string.CompareOrdinal(identifier, "*") == 0) { identifier = null; } // get the current scope and iterate over all the fields within it // looking for just the ones that are defined here (outer is null) var currentScope = node.EnclosingScope; foreach (var field in currentScope.NameTable.Values) { if (field.OuterField == null) { // if the identifier is null or matches exactly, mark it as not crunchable if (identifier == null || string.CompareOrdinal(identifier, field.Name) == 0) { field.CanCrunch = false; } } } } } } } return isHint; }
public void Visit(JsConstantWrapper node) { // we're good }
/// <summary> /// We have determined that our right-hand operand is another binary operator, and its /// left-hand operand is a constant that can be combined with our left-hand operand. /// Now we want to set the left-hand operand of that other operator to the newly- /// combined constant value, and then rotate it up -- replace our binary operator /// with this newly-modified binary operator, and then attempt to re-evaluate it. /// </summary> /// <param name="binaryOp">the binary operator that is our right-hand operand</param> /// <param name="newLiteral">the newly-combined literal</param> /// <param name="node"></param> private void RotateFromRight(JsBinaryOperator node, JsBinaryOperator binaryOp, JsConstantWrapper newLiteral) { // replace our node with the binary operator binaryOp.Operand1 = newLiteral; node.Parent.ReplaceChild(node, binaryOp); // and just for good measure.. revisit the node that's taking our place, since // we just changed a constant value. Assuming the other operand is a constant, too. JsConstantWrapper otherConstant = binaryOp.Operand2 as JsConstantWrapper; if (otherConstant != null) { EvalThisOperator(binaryOp, newLiteral, otherConstant); } }
//--------------------------------------------------------------------------------------- // MemberExpression // // Accessor : // <empty> | // Arguments Accessor // '[' Expression ']' Accessor | // '.' Identifier Accessor | // // Don't have this function throwing an exception without checking all the calling sites. // There is state in instance variable that is saved on the calling stack in some function // (i.e ParseFunction and ParseClass) and you don't want to blow up the stack //--------------------------------------------------------------------------------------- private JsAstNode MemberExpression(JsAstNode expression, List<JsContext> newContexts) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); try { switch (m_currentToken.Token) { case JsToken.LeftParenthesis: JsAstNodeList args = null; RecoveryTokenException callError = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken); try { args = ParseExpressionList(JsToken.RightParenthesis); } catch (RecoveryTokenException exc) { args = (JsAstNodeList)exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1) callError = exc; // thrown later on } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken); } expression = new JsCallNode(expression.Context.CombineWith(args.Context), this) { Function = expression, Arguments = args, InBrackets = false }; if (null != newContexts && newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); if (!(expression is JsCallNode)) { expression = new JsCallNode(newContexts[newContexts.Count - 1], this) { Function = expression, Arguments = new JsAstNodeList(CurrentPositionContext(), this) }; } else { expression.Context = newContexts[newContexts.Count - 1]; } ((JsCallNode)expression).IsConstructor = true; newContexts.RemoveAt(newContexts.Count - 1); } if (callError != null) { callError._partiallyComputedNode = expression; throw callError; } GetNextToken(); break; case JsToken.LeftBracket: m_noSkipTokenSet.Add(NoSkipTokenSet.s_BracketToken); try { // // ROTOR parses a[b,c] as a call to a, passing in the arguments b and c. // the correct parse is a member lookup on a of c -- the "b,c" should be // a single expression with a comma operator that evaluates b but only // returns c. // So we'll change the default behavior from parsing an expression list to // parsing a single expression, but returning a single-item list (or an empty // list if there is no expression) so the rest of the code will work. // //args = ParseExpressionList(JSToken.RightBracket); GetNextToken(); args = new JsAstNodeList(CurrentPositionContext(), this); JsAstNode accessor = ParseExpression(); if (accessor != null) { args.Append(accessor); } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BracketToken, exc) == -1) { if (exc._partiallyComputedNode != null) { exc._partiallyComputedNode = new JsCallNode(expression.Context.CombineWith(m_currentToken.Clone()), this) { Function = expression, Arguments = (JsAstNodeList)exc._partiallyComputedNode, InBrackets = true }; } else { exc._partiallyComputedNode = expression; } throw; } else args = (JsAstNodeList)exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken); } expression = new JsCallNode(expression.Context.CombineWith(m_currentToken.Clone()), this) { Function = expression, Arguments = args, InBrackets = true }; // there originally was code here in the ROTOR sources that checked the new context list and // changed this member call to a constructor call, effectively combining the two. I believe they // need to remain separate. // remove the close bracket token GetNextToken(); break; case JsToken.AccessField: JsConstantWrapper id = null; JsContext nameContext = m_currentToken.Clone(); GetNextToken(); if (JsToken.Identifier != m_currentToken.Token) { string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { // don't report an error here -- it's actually okay to have a property name // that is a keyword which is okay to be an identifier. For instance, // jQuery has a commonly-used method named "get" to make an ajax request //ForceReportInfo(JSError.KeywordUsedAsIdentifier); id = new JsConstantWrapper(identifier, JsPrimitiveType.String, m_currentToken.Clone(), this); } else if (JsScanner.IsValidIdentifier(m_currentToken.Code)) { // it must be a keyword, because it can't technically be an identifier, // but it IS a valid identifier format. Throw a warning but still // create the constant wrapper so we can output it as-is ReportError(JsError.KeywordUsedAsIdentifier, m_currentToken.Clone(), true); id = new JsConstantWrapper(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this); } else { ReportError(JsError.NoIdentifier); SkipTokensAndThrow(expression); } } else { id = new JsConstantWrapper(m_scanner.Identifier, JsPrimitiveType.String, m_currentToken.Clone(), this); } GetNextToken(); expression = new JsMember(expression.Context.CombineWith(id.Context), this) { Root = expression, Name = id.Context.Code, NameContext = nameContext.CombineWith(id.Context) }; break; default: if (null != newContexts) { while (newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); expression = new JsCallNode(newContexts[newContexts.Count - 1], this) { Function = expression, Arguments = new JsAstNodeList(CurrentPositionContext(), this) }; ((JsCallNode)expression).IsConstructor = true; newContexts.RemoveAt(newContexts.Count - 1); } } return expression; } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_MemberExprNoSkipTokenSet, exc) != -1) expression = exc._partiallyComputedNode; else { Debug.Assert(exc._partiallyComputedNode == expression); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); } } }
//--------------------------------------------------------------------------------------- // ParseWhileStatement // // WhileStatement : // 'while' '(' Expression ')' Statement //--------------------------------------------------------------------------------------- private JsWhileNode ParseWhileStatement() { JsContext whileCtx = m_currentToken.Clone(); JsAstNode condition = null; JsAstNode body = null; m_blockType.Add(BlockType.Loop); try { GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) { ReportError(JsError.NoLeftParenthesis); } GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { condition = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); whileCtx.UpdateWith(condition.Context); } else whileCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { // abort the while there is really no much to do here exc._partiallyComputedNode = null; throw; } else { // make up a condition if (exc._partiallyComputedNode != null) condition = exc._partiallyComputedNode; else condition = new JsConstantWrapper(false, JsPrimitiveType.Boolean, CurrentPositionContext(), this); if (JsToken.RightParenthesis == m_currentToken.Token) GetNextToken(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // if this is an assignment, throw a warning in case the developer // meant to use == instead of = // but no warning if the condition is wrapped in parens. var binOp = condition as JsBinaryOperator; if (binOp != null && binOp.OperatorToken == JsToken.Assign) { condition.Context.HandleError(JsError.SuspectAssignment); } // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. body = ParseStatement(false, true); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) body = exc._partiallyComputedNode; else body = new JsBlock(CurrentPositionContext(), this); exc._partiallyComputedNode = new JsWhileNode(whileCtx, this) { Condition = condition, Body = JsAstNode.ForceToBlock(body) }; throw; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new JsWhileNode(whileCtx, this) { Condition = condition, Body = JsAstNode.ForceToBlock(body) }; }
private JsConstantWrapper Divide(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (left.IsOkayToCombine && right.IsOkayToCombine && m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { try { double leftValue = left.ToNumber(); double rightValue = right.ToNumber(); double result = leftValue / rightValue; if (JsConstantWrapper.NumberIsOkayToCombine(result)) { newLiteral = new JsConstantWrapper(result, JsPrimitiveType.Number, null, m_parser); } else { if (!left.IsNumericLiteral && JsConstantWrapper.NumberIsOkayToCombine(leftValue)) { left.Parent.ReplaceChild(left, new JsConstantWrapper(leftValue, JsPrimitiveType.Number, left.Context, m_parser)); } if (!right.IsNumericLiteral && JsConstantWrapper.NumberIsOkayToCombine(rightValue)) { right.Parent.ReplaceChild(right, new JsConstantWrapper(rightValue, JsPrimitiveType.Number, right.Context, m_parser)); } } } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } } return newLiteral; }
private JsConstantWrapper BitwiseXor(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { try { Int32 lValue = left.ToInt32(); Int32 rValue = right.ToInt32(); newLiteral = new JsConstantWrapper(Convert.ToDouble(lValue ^ rValue), JsPrimitiveType.Number, null, m_parser); } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } } return newLiteral; }
/// <summary> /// replace the node with a literal. If the node was wrapped in a grouping operator /// before (parentheses around it), then we can get rid of the parentheses too, since /// we are replacing the node with a single literal entity. /// </summary> /// <param name="node">node to replace</param> /// <param name="newLiteral">literal to replace the node with</param> private static void ReplaceNodeWithLiteral(JsAstNode node, JsConstantWrapper newLiteral) { var grouping = node.Parent as JsGroupingOperator; if (grouping != null) { // because we are replacing the operator with a literal, the parentheses // the grouped this operator are now superfluous. Replace them, too grouping.Parent.ReplaceChild(grouping, newLiteral); } else { // just replace the node with the literal node.Parent.ReplaceChild(node, newLiteral); } }
/// <summary> /// Return true if the result isn't an overflow condition /// </summary> /// <param name="result">result constant</param> /// <returns>true is not an overflow; false if it is</returns> private static bool NoOverflow(JsConstantWrapper result) { return !result.IsInfinity; }
/// <summary> /// Return true is not an overflow or underflow, for multiplication operations /// </summary> /// <param name="left">left operand</param> /// <param name="right">right operand</param> /// <param name="result">result</param> /// <returns>true if result not overflow or underflow; false if it is</returns> private static bool NoMultiplicativeOverOrUnderFlow(JsConstantWrapper left, JsConstantWrapper right, JsConstantWrapper result) { // check for overflow bool okayToProceed = !result.IsInfinity; // if we still might be good, check for possible underflow if (okayToProceed) { // if the result is zero, we might have an underflow. But if one of the operands // was zero, then it's okay. // Inverse: if neither operand is zero, then a zero result is not okay okayToProceed = !result.IsZero || (left.IsZero || right.IsZero); } return okayToProceed; }
private static string ComputeJoin(JsArrayLiteral arrayLiteral, JsConstantWrapper separatorNode) { // if the separator node is null, then the separator is a single comma character. // otherwise it's just the string value of the separator. var separator = separatorNode == null ? "," : separatorNode.ToString(); var sb = new StringBuilder(); for (var ndx = 0; ndx < arrayLiteral.Elements.Count; ++ndx) { // add the separator between items (if we have one) if (ndx > 0 && !string.IsNullOrEmpty(separator)) { sb.Append(separator); } // the element is a constant wrapper (we wouldn't get this far if it wasn't), // but we've overloaded the virtual ToString method on ConstantWrappers to convert the // constant value to a string value. sb.Append(arrayLiteral.Elements[ndx].ToString()); } return sb.ToString(); }
private JsConstantWrapper UnsignedRightShift(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { try { // left-hand value is a 32-bit signed integer UInt32 lvalue = left.ToUInt32(); // mask only the bottom 5 bits of the right-hand value int rvalue = (int)(right.ToUInt32() & 0x1F); // convert the result to a double double result = Convert.ToDouble(lvalue >> rvalue); newLiteral = new JsConstantWrapper(result, JsPrimitiveType.Number, null, m_parser); } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } } return newLiteral; }
private JsAstNode ParseSwitchStatement() { JsContext switchCtx = m_currentToken.Clone(); JsAstNode expr = null; JsAstNodeList cases = null; var braceOnNewLine = false; JsContext braceContext = null; m_blockType.Add(BlockType.Switch); try { // read switch(expr) GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) ReportError(JsError.NoLeftParenthesis); GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet); try { expr = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); } GetNextToken(); if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.NoLeftCurly); } braceOnNewLine = m_foundEndOfLine; braceContext = m_currentToken.Clone(); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1 && IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1) { // give up exc._partiallyComputedNode = null; throw; } else { if (exc._partiallyComputedNode == null) expr = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); else expr = exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) != -1) { if (exc._token == JsToken.RightParenthesis) GetNextToken(); if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.NoLeftCurly); } braceOnNewLine = m_foundEndOfLine; braceContext = m_currentToken.Clone(); GetNextToken(); } } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // parse the switch body cases = new JsAstNodeList(CurrentPositionContext(), this); bool defaultStatement = false; m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet); try { while (JsToken.RightCurly != m_currentToken.Token) { JsSwitchCase caseClause = null; JsAstNode caseValue = null; var caseCtx = m_currentToken.Clone(); JsContext colonContext = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_CaseNoSkipTokenSet); try { if (JsToken.Case == m_currentToken.Token) { // get the case GetNextToken(); caseValue = ParseExpression(); } else if (JsToken.Default == m_currentToken.Token) { // get the default if (defaultStatement) { // we report an error but we still accept the default ReportError(JsError.DupDefault, true); } else { defaultStatement = true; } GetNextToken(); } else { // This is an error, there is no case or default. Assume a default was missing and keep going defaultStatement = true; ReportError(JsError.BadSwitch); } if (JsToken.Colon != m_currentToken.Token) { ReportError(JsError.NoColon); } else { colonContext = m_currentToken.Clone(); } // read the statements inside the case or default GetNextToken(); } catch (RecoveryTokenException exc) { // right now we can only get here for the 'case' statement if (IndexOfToken(NoSkipTokenSet.s_CaseNoSkipTokenSet, exc) == -1) { // ignore the current case or default exc._partiallyComputedNode = null; throw; } else { caseValue = exc._partiallyComputedNode; if (exc._token == JsToken.Colon) { GetNextToken(); } } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_CaseNoSkipTokenSet); } m_blockType.Add(BlockType.Block); try { var statements = new JsBlock(m_currentToken.Clone(), this); m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); try { while (JsToken.RightCurly != m_currentToken.Token && JsToken.Case != m_currentToken.Token && JsToken.Default != m_currentToken.Token) { try { // parse a Statement, not a SourceElement statements.Append(ParseStatement(false)); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) { statements.Append(exc._partiallyComputedNode); exc._partiallyComputedNode = null; } if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1) { throw; } } } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1) { caseClause = new JsSwitchCase(caseCtx, this) { CaseValue = caseValue, ColonContext = colonContext, Statements = statements }; cases.Append(caseClause); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); } caseCtx.UpdateWith(statements.Context); caseClause = new JsSwitchCase(caseCtx, this) { CaseValue = caseValue, ColonContext = colonContext, Statements = statements }; cases.Append(caseClause); } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1) { //save what you can a rethrow switchCtx.UpdateWith(CurrentPositionContext()); exc._partiallyComputedNode = new JsSwitch(switchCtx, this) { Expression = expr, BraceContext = braceContext, Cases = cases, BraceOnNewLine = braceOnNewLine }; throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet); } switchCtx.UpdateWith(m_currentToken); GetNextToken(); } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new JsSwitch(switchCtx, this) { Expression = expr, BraceContext = braceContext, Cases = cases, BraceOnNewLine = braceOnNewLine }; }
private JsConstantWrapper StrictNotEqual(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { JsPrimitiveType leftType = left.PrimitiveType; if (leftType == right.PrimitiveType) { // the values are the same type switch (leftType) { case JsPrimitiveType.Null: // null !== null is false newLiteral = new JsConstantWrapper(false, JsPrimitiveType.Boolean, null, m_parser); break; case JsPrimitiveType.Boolean: // compare boolean values newLiteral = new JsConstantWrapper(left.ToBoolean() != right.ToBoolean(), JsPrimitiveType.Boolean, null, m_parser); break; case JsPrimitiveType.String: // compare string ordinally if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new JsConstantWrapper(string.CompareOrdinal(left.ToString(), right.ToString()) != 0, JsPrimitiveType.Boolean, null, m_parser); } break; case JsPrimitiveType.Number: try { // compare the values // +0 and -0 are treated as "equal" in C#, so we don't need to test them separately. // and NaN is always unequal to everything else, including itself. if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new JsConstantWrapper(left.ToNumber() != right.ToNumber(), JsPrimitiveType.Boolean, null, m_parser); } } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } break; } } else { // if they aren't the same type, they are not equal newLiteral = new JsConstantWrapper(true, JsPrimitiveType.Boolean, null, m_parser); } } return newLiteral; }
//--------------------------------------------------------------------------------------- // ParseWithStatement // // WithStatement : // 'with' '(' Expression ')' Statement //--------------------------------------------------------------------------------------- private JsWithNode ParseWithStatement() { JsContext withCtx = m_currentToken.Clone(); JsAstNode obj = null; JsBlock block = null; m_blockType.Add(BlockType.Block); try { GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) ReportError(JsError.NoLeftParenthesis); GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { obj = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { withCtx.UpdateWith(obj.Context); ReportError(JsError.NoRightParenthesis); } else withCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { // give up exc._partiallyComputedNode = null; throw; } else { if (exc._partiallyComputedNode == null) obj = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); else obj = exc._partiallyComputedNode; withCtx.UpdateWith(obj.Context); if (exc._token == JsToken.RightParenthesis) GetNextToken(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. JsAstNode statement = ParseStatement(false, true); // but make sure we save it as a block block = statement as JsBlock; if (block == null) { block = new JsBlock(statement.Context, this); block.Append(statement); } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) { block = new JsBlock(CurrentPositionContext(), this); } else { block = exc._partiallyComputedNode as JsBlock; if (block == null) { block = new JsBlock(exc._partiallyComputedNode.Context, this); block.Append(exc._partiallyComputedNode); } } exc._partiallyComputedNode = new JsWithNode(withCtx, this) { WithObject = obj, Body = block }; throw; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new JsWithNode(withCtx, this) { WithObject = obj, Body = block }; }
public void Visit(JsConstantWrapper node) { if (node != null) { var symbol = StartSymbol(node); var isNoIn = m_noIn; m_noIn = false; switch (node.PrimitiveType) { case JsPrimitiveType.Boolean: Output(node.ToBoolean() ? "true" : "false"); break; case JsPrimitiveType.Null: Output("null"); break; case JsPrimitiveType.Number: if (node.Context == null || !node.Context.HasCode || (!node.MayHaveIssues && m_settings.IsModificationAllowed(JsTreeModifications.MinifyNumericLiterals))) { // apply minification to the literal to get it as small as possible Output(NormalizeNumber(node.ToNumber(), node.Context)); } else { // context is not null but we don't want to minify numeric literals. // just use the original literal from the context. Output(node.Context.Code); } break; case JsPrimitiveType.Other: Output(node.Value.ToString()); break; case JsPrimitiveType.String: if (node.Context == null || !node.Context.HasCode) { // escape the string value because we don't have a raw context value // to show anyways Output(InlineSafeString(EscapeString(node.Value.ToString()))); } else if (!m_settings.IsModificationAllowed(JsTreeModifications.MinifyStringLiterals)) { // we don't want to modify the strings at all! Output(node.Context.Code); } else if (node.MayHaveIssues || (m_settings.AllowEmbeddedAspNetBlocks && node.StringContainsAspNetReplacement)) { // we'd rather show the raw string, but make sure it's safe for inlining Output(InlineSafeString(node.Context.Code)); } else { // we'd rather show the escaped string Output(InlineSafeString(EscapeString(node.Value.ToString()))); } break; } MarkSegment(node, null, node.Context); SetContextOutputPosition(node.Context); m_startOfStatement = false; m_noIn = isNoIn; EndSymbol(symbol); } }
public void Visit(JsConstantWrapper node) { // not applicable; terminate }
/// <summary> /// If the new literal is a string literal, then we need to check to see if our /// parent is a CallNode. If it is, and if the string literal can be an identifier, /// we'll replace it with a Member-Dot operation. /// </summary> /// <param name="newLiteral">newLiteral we intend to replace this binaryop node with</param> /// <returns>true if we replaced the parent callnode with a member-dot operation</returns> /// <param name="node"></param> private bool ReplaceMemberBracketWithDot(JsBinaryOperator node, JsConstantWrapper newLiteral) { if (newLiteral.IsStringLiteral) { // see if this newly-combined string is the sole argument to a // call-brackets node. If it is and the combined string is a valid // identifier (and not a keyword), then we can replace the call // with a member operator. // remember that the parent of the argument won't be the call node -- it // will be the ast node list representing the arguments, whose parent will // be the node list. JsCallNode parentCall = (node.Parent is JsAstNodeList ? node.Parent.Parent as JsCallNode : null); if (parentCall != null && parentCall.InBrackets) { // get the newly-combined string string combinedString = newLiteral.ToString(); // see if this new string is the target of a replacement operation string newName; if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties && m_parser.Settings.IsModificationAllowed(JsTreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = m_parser.Settings.GetNewName(combinedString))) { // yes, it is. Now see if the new name is safe to be converted to a dot-operation. if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember) && JsScanner.IsSafeIdentifier(newName) && !JsScanner.IsKeyword(newName, parentCall.EnclosingScope.UseStrict)) { // we want to replace the call with operator with a new member dot operation, and // since we won't be analyzing it (we're past the analyze phase, we're going to need // to use the new string value JsMember replacementMember = new JsMember(parentCall.Context, m_parser) { Root = parentCall.Function, Name = newName, NameContext = parentCall.Arguments[0].Context }; parentCall.Parent.ReplaceChild(parentCall, replacementMember); return true; } else { // nope, can't be changed to a dot-operator for whatever reason. // just replace the value on this new literal. The old operation will // get replaced with this new literal newLiteral.Value = newName; // and make sure it's type is string newLiteral.PrimitiveType = JsPrimitiveType.String; } } else if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember)) { // our parent is a call-bracket -- now we just need to see if the newly-combined // string can be an identifier if (JsScanner.IsSafeIdentifier(combinedString) && !JsScanner.IsKeyword(combinedString, parentCall.EnclosingScope.UseStrict)) { // yes -- replace the parent call with a new member node using the newly-combined string JsMember replacementMember = new JsMember(parentCall.Context, m_parser) { Root = parentCall.Function, Name = combinedString, NameContext = parentCall.Arguments[0].Context }; parentCall.Parent.ReplaceChild(parentCall, replacementMember); return true; } } } } return false; }
//--------------------------------------------------------------------------------------- // ParseDoStatement // // DoStatement: // 'do' Statement 'while' '(' Expression ')' //--------------------------------------------------------------------------------------- private JsDoWhile ParseDoStatement() { var doCtx = m_currentToken.Clone(); JsContext whileContext = null; JsContext terminatorContext = null; JsAstNode body = null; JsAstNode 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, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. body = ParseStatement(false, true); } catch (RecoveryTokenException exc) { // make up a block for the do while if (exc._partiallyComputedNode != null) body = exc._partiallyComputedNode; else body = new JsBlock(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 JsDoWhile(doCtx.UpdateWith(CurrentPositionContext()), this) { Body = JsAstNode.ForceToBlock(body), Condition = new JsConstantWrapper(false, JsPrimitiveType.Boolean, CurrentPositionContext(), this) }; throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet); } if (JsToken.While != m_currentToken.Token) { ReportError(JsError.NoWhile); } whileContext = m_currentToken.Clone(); doCtx.UpdateWith(whileContext); 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 JsConstantWrapper(false, JsPrimitiveType.Boolean, CurrentPositionContext(), this); if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new JsDoWhile(doCtx, this) { Body = JsAstNode.ForceToBlock(body), WhileContext = whileContext, Condition = 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. terminatorContext = m_currentToken.Clone(); GetNextToken(); } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } // if this is an assignment, throw a warning in case the developer // meant to use == instead of = // but no warning if the condition is wrapped in parens. var binOp = condition as JsBinaryOperator; if (binOp != null && binOp.OperatorToken == JsToken.Assign) { condition.Context.HandleError(JsError.SuspectAssignment); } return new JsDoWhile(doCtx, this) { Body = JsAstNode.ForceToBlock(body), WhileContext = whileContext, Condition = condition, TerminatingContext = terminatorContext }; }
public void Visit(JsConstantWrapper node) { // it's a constant, so we don't care }
//--------------------------------------------------------------------------------------- // ParseIfStatement // // IfStatement : // 'if' '(' Expression ')' Statement ElseStatement // // ElseStatement : // <empty> | // 'else' Statement //--------------------------------------------------------------------------------------- private JsIfNode ParseIfStatement() { JsContext ifCtx = m_currentToken.Clone(); JsAstNode condition = null; JsAstNode trueBranch = null; JsAstNode falseBranch = null; JsContext elseCtx = 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 JsConstantWrapper(true, JsPrimitiveType.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. var binOp = condition as JsBinaryOperator; if (binOp != null && binOp.OperatorToken == JsToken.Assign) { condition.Context.HandleError(JsError.SuspectAssignment); } m_noSkipTokenSet.Add(NoSkipTokenSet.s_IfBodyNoSkipTokenSet); if (JsToken.Semicolon == m_currentToken.Token) { m_currentToken.HandleError(JsError.SuspectSemicolon); } else if (JsToken.LeftCurly != m_currentToken.Token) { // if the statements aren't withing curly-braces, throw a possible error ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. trueBranch = ParseStatement(false, true); } catch (RecoveryTokenException exc) { // make up a block for the if part if (exc._partiallyComputedNode != null) trueBranch = exc._partiallyComputedNode; else trueBranch = new JsBlock(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 JsIfNode(ifCtx, this) { Condition = condition, TrueBlock = JsAstNode.ForceToBlock(trueBranch) }; throw; } } finally { if (trueBranch != null) { ifCtx.UpdateWith(trueBranch.Context); } m_noSkipTokenSet.Remove(NoSkipTokenSet.s_IfBodyNoSkipTokenSet); } // parse else, if any if (JsToken.Else == m_currentToken.Token) { elseCtx = m_currentToken.Clone(); GetNextToken(); if (JsToken.Semicolon == m_currentToken.Token) { m_currentToken.HandleError(JsError.SuspectSemicolon); } else if (JsToken.LeftCurly != m_currentToken.Token && JsToken.If != m_currentToken.Token) { // if the statements aren't withing curly-braces (or start another if-statement), throw a possible error ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. falseBranch = ParseStatement(false, true); } catch (RecoveryTokenException exc) { // make up a block for the else part if (exc._partiallyComputedNode != null) falseBranch = exc._partiallyComputedNode; else falseBranch = new JsBlock(CurrentPositionContext(), this); exc._partiallyComputedNode = new JsIfNode(ifCtx, this) { Condition = condition, TrueBlock = JsAstNode.ForceToBlock(trueBranch), ElseContext = elseCtx, FalseBlock = JsAstNode.ForceToBlock(falseBranch) }; throw; } finally { if (falseBranch != null) { ifCtx.UpdateWith(falseBranch.Context); } } } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new JsIfNode(ifCtx, this) { Condition = condition, TrueBlock = JsAstNode.ForceToBlock(trueBranch), ElseContext = elseCtx, FalseBlock = JsAstNode.ForceToBlock(falseBranch) }; }
private JsAstNode ParseLeftHandSideExpression(bool isMinus) { JsAstNode ast = null; bool skipToken = true; List<JsContext> newContexts = null; TryItAgain: // new expression while (JsToken.New == m_currentToken.Token) { if (null == newContexts) newContexts = new List<JsContext>(4); newContexts.Add(m_currentToken.Clone()); GetNextToken(); } JsToken token = m_currentToken.Token; switch (token) { // primary expression case JsToken.Identifier: ast = new JsLookup(m_currentToken.Clone(), this) { Name = m_scanner.Identifier }; break; case JsToken.ConditionalCommentStart: // skip past the start to the next token GetNextToken(); if (m_currentToken.Token == JsToken.ConditionalCompilationVariable) { // we have /*@id ast = new JsConstantWrapperPP(m_currentToken.Clone(), this) { VarName = m_currentToken.Code, ForceComments = true }; GetNextToken(); if (m_currentToken.Token == JsToken.ConditionalCommentEnd) { // skip past the closing comment GetNextToken(); } else { // we ONLY support /*@id@*/ in expressions right now. If there's not // a closing comment after the ID, then we don't support it. // throw an error, skip to the end of the comment, then ignore it and start // looking for the next token. CCTooComplicated(null); goto TryItAgain; } } else if (m_currentToken.Token == JsToken.ConditionalCommentEnd) { // empty conditional comment! Ignore. GetNextToken(); goto TryItAgain; } else { // we DON'T have "/*@IDENT". We only support "/*@IDENT @*/", so since this isn't // and id, throw the error, skip to the end of the comment, and ignore it // by looping back and looking for the NEXT token. m_currentToken.HandleError(JsError.ConditionalCompilationTooComplex); // skip to end of conditional comment while (m_currentToken.Token != JsToken.EndOfFile && m_currentToken.Token != JsToken.ConditionalCommentEnd) { GetNextToken(); } GetNextToken(); goto TryItAgain; } break; case JsToken.This: ast = new JsThisLiteral(m_currentToken.Clone(), this); break; case JsToken.StringLiteral: ast = new JsConstantWrapper(m_scanner.StringLiteralValue, JsPrimitiveType.String, m_currentToken.Clone(), this) { MayHaveIssues = m_scanner.LiteralHasIssues }; break; case JsToken.IntegerLiteral: case JsToken.NumericLiteral: { JsContext numericContext = m_currentToken.Clone(); double doubleValue; if (ConvertNumericLiteralToDouble(m_currentToken.Code, (token == JsToken.IntegerLiteral), out doubleValue)) { // conversion worked fine // check for some boundary conditions var mayHaveIssues = m_scanner.LiteralHasIssues; if (doubleValue == double.MaxValue) { ReportError(JsError.NumericMaximum, numericContext, true); } else if (isMinus && -doubleValue == double.MinValue) { ReportError(JsError.NumericMinimum, numericContext, true); } // create the constant wrapper from the value ast = new JsConstantWrapper(doubleValue, JsPrimitiveType.Number, numericContext, this) { MayHaveIssues = mayHaveIssues }; } else { // if we went overflow or are not a number, then we will use the "Other" // primitive type so we don't try doing any numeric calcs with it. if (double.IsInfinity(doubleValue)) { // overflow // and if we ARE an overflow, report it ReportError(JsError.NumericOverflow, numericContext, true); } // regardless, we're going to create a special constant wrapper // that simply echos the input as-is ast = new JsConstantWrapper(m_currentToken.Code, JsPrimitiveType.Other, numericContext, this) { MayHaveIssues = true }; } break; } case JsToken.True: ast = new JsConstantWrapper(true, JsPrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JsToken.False: ast = new JsConstantWrapper(false, JsPrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JsToken.Null: ast = new JsConstantWrapper(null, JsPrimitiveType.Null, m_currentToken.Clone(), this); break; case JsToken.ConditionalCompilationVariable: ast = new JsConstantWrapperPP(m_currentToken.Clone(), this) { VarName = m_currentToken.Code, ForceComments = false }; break; case JsToken.DivideAssign: // normally this token is not allowed on the left-hand side of an expression. // BUT, this might be the start of a regular expression that begins with an equals sign! // we need to test to see if we can parse a regular expression, and if not, THEN // we can fail the parse. case JsToken.Divide: // could it be a regexp? ast = ScanRegularExpression(); if (ast != null) { // yup -- we're done here break; } // nope -- go to the default branch goto default; // expression case JsToken.LeftParenthesis: { var groupingOp = new JsGroupingOperator(m_currentToken.Clone(), this); ast = groupingOp; GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenExpressionNoSkipToken); try { // parse an expression groupingOp.Operand = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); } else { // add the closing paren to the expression context ast.Context.UpdateWith(m_currentToken); } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_ParenExpressionNoSkipToken, exc) == -1) throw; else groupingOp.Operand = exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenExpressionNoSkipToken); } } break; // array initializer case JsToken.LeftBracket: JsContext listCtx = m_currentToken.Clone(); GetNextToken(); JsAstNodeList list = new JsAstNodeList(CurrentPositionContext(), this); var hasTrailingCommas = false; while (JsToken.RightBracket != m_currentToken.Token) { if (JsToken.Comma != m_currentToken.Token) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); try { var expression = ParseExpression(true); list.Append(expression); if (JsToken.Comma != m_currentToken.Token) { if (JsToken.RightBracket != m_currentToken.Token) { ReportError(JsError.NoRightBracket); } break; } else { // we have a comma -- skip it after adding it as a terminator // on the previous expression var commaContext = m_currentToken.Clone(); expression.IfNotNull(e => e.TerminatingContext = commaContext); 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) { hasTrailingCommas = true; list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this)); // throw a cross-browser warning about trailing commas commaContext.HandleError(JsError.ArrayLiteralTrailingComma); break; } } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) list.Append(exc._partiallyComputedNode); if (IndexOfToken(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet, exc) == -1) { listCtx.UpdateWith(CurrentPositionContext()); exc._partiallyComputedNode = new JsArrayLiteral(listCtx, this) { Elements = list, MayHaveIssues = true }; throw; } else { if (JsToken.RightBracket == m_currentToken.Token) break; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); } } else { // comma -- missing array item in the list var commaContext = m_currentToken.Clone(); list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this) { TerminatingContext = commaContext }); // 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) { hasTrailingCommas = true; list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this)); // throw a cross-browser warning about trailing commas commaContext.HandleError(JsError.ArrayLiteralTrailingComma); break; } } } listCtx.UpdateWith(m_currentToken); ast = new JsArrayLiteral(listCtx, this) { Elements = list, MayHaveIssues = hasTrailingCommas }; break; // object initializer case JsToken.LeftCurly: JsContext objCtx = m_currentToken.Clone(); GetNextToken(); var propertyList = new JsAstNodeList(CurrentPositionContext(), this); if (JsToken.RightCurly != m_currentToken.Token) { for (; ; ) { JsObjectLiteralField field = null; JsAstNode value = null; bool getterSetter = false; string ident; switch (m_currentToken.Token) { case JsToken.Identifier: field = new JsObjectLiteralField(m_scanner.Identifier, JsPrimitiveType.String, m_currentToken.Clone(), this); break; case JsToken.StringLiteral: field = new JsObjectLiteralField(m_scanner.StringLiteralValue, JsPrimitiveType.String, m_currentToken.Clone(), this) { MayHaveIssues = m_scanner.LiteralHasIssues }; break; case JsToken.IntegerLiteral: case JsToken.NumericLiteral: { double doubleValue; if (ConvertNumericLiteralToDouble(m_currentToken.Code, (m_currentToken.Token == JsToken.IntegerLiteral), out doubleValue)) { // conversion worked fine field = new JsObjectLiteralField( doubleValue, JsPrimitiveType.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 JsObjectLiteralField( m_currentToken.Code, JsPrimitiveType.Other, m_currentToken.Clone(), this); } break; } case JsToken.Get: case JsToken.Set: if (PeekToken() == JsToken.Colon) { // the field is either "get" or "set" and isn't the special Mozilla getter/setter field = new JsObjectLiteralField(m_currentToken.Code, JsPrimitiveType.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 ? JsFunctionType.Getter : JsFunctionType.Setter), m_currentToken.Clone() ); JsFunctionObject funcExpr = value as JsFunctionObject; if (funcExpr != null) { // getter/setter is just the literal name with a get/set flag field = new JsGetterSetter( funcExpr.Name, isGet, funcExpr.IdContext.Clone(), this ); } else { ReportError(JsError.FunctionExpressionExpected); } } break; default: // NOT: identifier token, string, number, or getter/setter. // see if it's a token that COULD be an identifierName. ident = m_scanner.Identifier; if (JsScanner.IsValidIdentifier(ident)) { // BY THE SPEC, if it's a valid identifierName -- which includes reserved words -- then it's // okay for object literal syntax. However, reserved words here won't work in all browsers, // so if it is a reserved word, let's throw a low-sev cross-browser warning on the code. if (JsKeyword.CanBeIdentifier(m_currentToken.Token) == null) { ReportError(JsError.ObjectLiteralKeyword, m_currentToken.Clone(), true); } field = new JsObjectLiteralField(ident, JsPrimitiveType.String, m_currentToken.Clone(), this); } else { // throw an error but use it anyway, since that's what the developer has going on ReportError(JsError.NoMemberIdentifier, m_currentToken.Clone(), true); field = new JsObjectLiteralField(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this); } break; } if (field != null) { if (!getterSetter) { GetNextToken(); } m_noSkipTokenSet.Add(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet); try { if (!getterSetter) { // get the value if (JsToken.Colon != m_currentToken.Token) { ReportError(JsError.NoColon, true); value = ParseExpression(true); } else { field.ColonContext = m_currentToken.Clone(); GetNextToken(); value = ParseExpression(true); } } // put the pair into the list of fields var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context)); var property = new JsObjectLiteralProperty(propCtx, this) { Name = field, Value = value }; propertyList.Append(property); if (JsToken.RightCurly == m_currentToken.Token) { break; } else { if (JsToken.Comma == m_currentToken.Token) { // skip the comma after adding it to the property as a terminating context property.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone()); GetNextToken(); // if the next token is the right-curly brace, then we ended // the list with a comma, which is perfectly fine if (m_currentToken.Token == JsToken.RightCurly) { break; } } else { if (m_foundEndOfLine) { ReportError(JsError.NoRightCurly); } else ReportError(JsError.NoComma, true); SkipTokensAndThrow(); } } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) { // the problem was in ParseExpression trying to determine value value = exc._partiallyComputedNode; var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context)); var property = new JsObjectLiteralProperty(propCtx, this) { Name = field, Value = value }; propertyList.Append(property); } if (IndexOfToken(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new JsObjectLiteral(objCtx, this) { Properties = propertyList }; throw; } else { if (JsToken.Comma == m_currentToken.Token) GetNextToken(); if (JsToken.RightCurly == m_currentToken.Token) break; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet); } } } } objCtx.UpdateWith(m_currentToken); ast = new JsObjectLiteral(objCtx, this) { Properties = propertyList }; break; // function expression case JsToken.Function: ast = ParseFunction(JsFunctionType.Expression, m_currentToken.Clone()); skipToken = false; break; case JsToken.AspNetBlock: ast = new JsAspNetBlockNode(m_currentToken.Clone(), this) { AspNetBlockText = m_currentToken.Code }; break; default: string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { ast = new JsLookup(m_currentToken.Clone(), this) { Name = identifier }; } else { ReportError(JsError.ExpressionExpected); SkipTokensAndThrow(); } break; } // can be a CallExpression, that is, followed by '.' or '(' or '[' if (skipToken) GetNextToken(); return MemberExpression(ast, newContexts); }
//--------------------------------------------------------------------------------------- // ParseExpressionList // // Given a starting this.currentToken '(' or '[', parse a list of expression separated by // ',' until matching ')' or ']' //--------------------------------------------------------------------------------------- private JsAstNodeList ParseExpressionList(JsToken terminator) { JsContext listCtx = m_currentToken.Clone(); GetNextToken(); JsAstNodeList list = new JsAstNodeList(listCtx, this); if (terminator != m_currentToken.Token) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet); try { JsAstNode item; if (JsToken.Comma == m_currentToken.Token) { item = new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this); list.Append(item); } else if (terminator == m_currentToken.Token) { break; } else { item = ParseExpression(true); list.Append(item); } if (terminator == m_currentToken.Token) { break; } else { if (JsToken.Comma == m_currentToken.Token) { item.IfNotNull(n => n.TerminatingContext = m_currentToken.Clone()); } else { if (terminator == JsToken.RightParenthesis) { // in ASP+ it's easy to write a semicolon at the end of an expression // not realizing it is going to go inside a function call // (ie. Response.Write()), so make a special check here if (JsToken.Semicolon == m_currentToken.Token) { if (JsToken.RightParenthesis == PeekToken()) { ReportError(JsError.UnexpectedSemicolon, true); GetNextToken(); break; } } ReportError(JsError.NoRightParenthesisOrComma); } else { ReportError(JsError.NoRightBracketOrComma); } SkipTokensAndThrow(); } } } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) list.Append(exc._partiallyComputedNode); if (IndexOfToken(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = list; throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet); } GetNextToken(); } } listCtx.UpdateWith(m_currentToken); return list; }
private JsConstantWrapper StringConcat(JsConstantWrapper left, JsConstantWrapper right) { JsConstantWrapper newLiteral = null; // if we don't want to combine adjacent string literals, then we know we don't want to do // anything here. if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineAdjacentStringLiterals)) { // if either one of the operands is not a string literal, then check to see if we allow // evaluation of numeric expression; if not, then no-go. IF they are both string literals, // then it doesn't matter what the numeric flag says. if ((left.IsStringLiteral && right.IsStringLiteral) || m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { // if either value is a floating-point number (a number, not NaN, not Infinite, not an Integer), // then we won't do the string concatenation because different browsers may have subtle differences // in their double-to-string conversion algorithms. // so if neither is a numeric literal, or if one or both are, if they are both integer literals // in the range that we can EXACTLY represent them in a double, then we can proceed. // NaN, +Infinity and -Infinity are also acceptable if (left.IsOkayToCombine && right.IsOkayToCombine) { newLiteral = new JsConstantWrapper(left.ToString() + right.ToString(), JsPrimitiveType.String, null, m_parser); } } } return newLiteral; }
private JsAstNode ParseForStatement() { m_blockType.Add(BlockType.Loop); JsAstNode forNode = null; try { JsContext forCtx = m_currentToken.Clone(); GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) { ReportError(JsError.NoLeftParenthesis); } GetNextToken(); bool isForIn = false, recoveryInForIn = false; JsAstNode lhs = null, initializer = null, condOrColl = null, increment = null; JsContext operatorContext = null; JsContext separator1Context = null; JsContext separator2Context = null; try { if (JsToken.Var == m_currentToken.Token || JsToken.Let == m_currentToken.Token || JsToken.Const == m_currentToken.Token) { isForIn = true; JsDeclaration declaration; if (m_currentToken.Token == JsToken.Var) { declaration = new JsVar(m_currentToken.Clone(), this); } else { declaration = new JsLexicalDeclaration(m_currentToken.Clone(), this) { StatementToken = m_currentToken.Token }; } declaration.Append(ParseIdentifierInitializer(JsToken.In)); // a list of variable initializers is allowed only in a for(;;) while (JsToken.Comma == m_currentToken.Token) { isForIn = false; declaration.Append(ParseIdentifierInitializer(JsToken.In)); //initializer = new Comma(initializer.context.CombineWith(var.context), initializer, var); } initializer = declaration; // if it could still be a for..in, now it's time to get the 'in' // TODO: for ES6 might be 'of' if (isForIn) { if (JsToken.In == m_currentToken.Token || (m_currentToken.Token == JsToken.Identifier && string.CompareOrdinal(m_currentToken.Code, "of") == 0)) { operatorContext = m_currentToken.Clone(); 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 || (m_currentToken.Token == JsToken.Identifier && string.CompareOrdinal(m_currentToken.Code, "of") == 0))) { isForIn = true; operatorContext = m_currentToken.Clone(); 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 JsConstantWrapper(true, JsPrimitiveType.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(); } JsAstNode body = null; // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. body = ParseStatement(false, true); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) body = new JsBlock(CurrentPositionContext(), this); else body = exc._partiallyComputedNode; exc._partiallyComputedNode = new JsForIn(forCtx, this) { Variable = (lhs != null ? lhs : initializer), OperatorContext = operatorContext, Collection = condOrColl, Body = JsAstNode.ForceToBlock(body), }; throw; } // for (a in b) // lhs = a, initializer = null // for (var a in b) // lhs = null, initializer = var a forNode = new JsForIn(forCtx, this) { Variable = (lhs != null ? lhs : initializer), OperatorContext = operatorContext, Collection = condOrColl, Body = JsAstNode.ForceToBlock(body), }; } else { m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { if (JsToken.Semicolon == m_currentToken.Token) { separator1Context = m_currentToken.Clone(); } else { 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_useCurrentForNext = false; } 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); } } separator2Context = m_currentToken.Clone(); 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 JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); } 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. var binOp = condOrColl as JsBinaryOperator; if (binOp != null && binOp.OperatorToken == JsToken.Assign) { condOrColl.Context.HandleError(JsError.SuspectAssignment); } JsAstNode body = null; // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. body = ParseStatement(false, true); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) body = new JsBlock(CurrentPositionContext(), this); else body = exc._partiallyComputedNode; exc._partiallyComputedNode = new JsForNode(forCtx, this) { Initializer = initializer, Separator1Context = separator1Context, Condition = condOrColl, Separator2Context = separator2Context, Incrementer = increment, Body = JsAstNode.ForceToBlock(body) }; throw; } forNode = new JsForNode(forCtx, this) { Initializer = initializer, Separator1Context = separator1Context, Condition = condOrColl, Separator2Context = separator2Context, Incrementer = increment, Body = JsAstNode.ForceToBlock(body) }; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return forNode; }