/// <summary> /// Deep-clone the code object. /// </summary> public override CodeObject Clone() { Unrecognized clone = (Unrecognized)base.Clone(); clone._expressions = ChildListHelpers.Clone(_expressions, clone); return(clone); }
/// <summary> /// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence /// operator is encountered. /// </summary> /// <param name="parser">The parser object.</param> /// <param name="parent">The parent object.</param> /// <param name="isTopLevel">True if EOL comments can be associated with the expression during parsing - generally /// true if the parent is a statement or expression list, but with some exceptions.</param> /// <param name="terminator">Optional terminating characters (null if none).</param> /// <param name="flags">Parsing flags.</param> /// <returns>The parsed <see cref="Expression"/>.</returns> public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel, string terminator, ParseFlags flags) { // Save the starting token of the expression for later Token startingToken = parser.Token; // Start a new Unused list in the parser parser.PushUnusedList(); // Parse an expression, which can be in one of the following formats: // - An identifier token, optionally followed by an operator (which is parsed only if precedence rules determine it should be) // - An operator (which will itself look for previous and/or following expressions when parsed) // - An open paren, expression, close paren sequence (handled by the installed parse-point above), optionally followed by an operator // Any other sequence will cause parsing of the expression to cease. // The expression will be terminated by any of ';,}]', or other specified terminator. // Create a string of possible terminators (assuming 1 char terminators for now) string terminators = Statement.ParseTokenTerminator + ParseTokenSeparator + Block.ParseTokenEnd + Index.ParseTokenEnd + terminator; // Keep a reference to the last token so we can move any skipped non-EOL comments to the expression later Token lastToken = parser.LastToken; // Loop until EOF or we find a terminator, or for directive expressions stop if we find a comment or a token on a new line. // NOTE: Keep this logic in sync with the 'if' statement further down in the loop that checks for termination. while (parser.TokenText != null && (parser.TokenText.Length != 1 || terminators.IndexOf(parser.TokenText[0]) < 0) && (!parser.InDirectiveExpression || ((parser.LastToken.TrailingComments == null || parser.LastToken.TrailingComments.Count == 0) && !parser.Token.IsFirstOnLine))) { process_next: bool skipTerminationCheck = false; // Process the current token (will process operators) CodeObject obj = parser.ProcessToken(parent, flags | ParseFlags.Expression); if (obj != null) { // If we got something, save it for later. // Don't move any EOL comments here - they should have already been processed. if (obj is CompilerDirective) { // If we have a compiler directive, and there's a preceeding unused object, add it there CodeObject lastUnusedCodeObject = parser.LastUnusedCodeObject; if (lastUnusedCodeObject != null && !(lastUnusedCodeObject is CompilerDirective)) { lastUnusedCodeObject.AttachAnnotation((CompilerDirective)obj, AnnotationFlags.IsPostfix); } else { parser.AddUnused(obj); // Add the object to the unused list skipTerminationCheck = true; } } else { obj.ParseUnusedAnnotations(parser, parent, true); // Parse any annotations from the Unused list parser.AddUnused(obj); // Add the object to the unused list } } // Stop if EOF or we find a terminator, or for directive expressions stop if we find a comment or a token on a new line. // NOTE: Keep this logic in sync with that in the condition of the parent 'while' loop. if (parser.TokenText == null || (parser.TokenText.Length == 1 && terminators.IndexOf(parser.TokenText[0]) >= 0) || (parser.InDirectiveExpression && ((parser.LastToken.TrailingComments != null && parser.LastToken.TrailingComments.Count != 0) || parser.Token.IsFirstOnLine))) { // Don't abort here on a '{' terminator if we're in a doc comment and we appear to have type arguments using // braces (as opposed to an Initializer after a NewObject). Go process the next object immediately instead. if (parser.InDocComment && parser.TokenText == TypeRefBase.ParseTokenAltArgumentStart && parser.HasUnusedIdentifier && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, flags)) { goto process_next; } break; } // If the current token is the start of a compiler directive, check for special situations in which we want to skip // the termination check below. This allows the directive to be attached to preceeding code objects such as literals // or operators, while not attaching to simple name or type expressions which might be part of a namespace or type header. if (parser.TokenText == CompilerDirective.ParseToken) { CodeObject lastUnusedCodeObject = parser.LastUnusedCodeObject; if (lastUnusedCodeObject is Literal || lastUnusedCodeObject is Operator) { skipTerminationCheck = true; // Also, capture any pending trailing comments if (obj != null) { obj.MoveCommentsAsPost(parser.LastToken); } } } // If we don't have a specific terminating character, then we're parsing a sub-expression and we should stop when we // get to an invalid operator, or an operator of greater precedence. Skip this check if we just parsed a compiler // directive and didn't have a preceeding code object to attach it to, or if we're about to parse a compiler directive // and we have an unused code object that we'd like to attach it to. if (terminator == null && !skipTerminationCheck) { // Check for '{' when used inside a doc comment in a generic type constructor or generic method instance if (parser.InDocComment && parser.TokenText == TypeRefBase.ParseTokenAltArgumentStart && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, flags)) { continue; } // Check if the current token represents a valid operator Parser.OperatorInfo operatorInfo = parser.GetOperatorInfoForToken(); // If the current token doesn't look like a valid operator, we're done with the expression if (operatorInfo == null) { break; } // We have an operator - check if our parent is also an operator if (parent is Operator) { // Special cases for Types: Some operator symbols are overloaded and can also be part // of a type name. We must detect these here, and continue processing in these cases, // skipping the operator precedence checks below that terminate the current expression. // Check for '[' when used in an array type name if (parser.TokenText == TypeRefBase.ParseTokenArrayStart && TypeRefBase.PeekArrayRanks(parser)) { continue; } // Check for '<' when used in a generic type constructor or generic method instance if (parser.TokenText == TypeRefBase.ParseTokenArgumentStart && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenArgumentEnd, flags)) { continue; } // Do NOT check for '?' used for nullable types, because it applies to the entire // expression on the left, so we DO want to terminate processing. // Determine the precedence of the parent operator // NOTE: See the bottom of Operator.cs for a quick-reference of operator precedence. int parentPrecedence = ((Operator)parent).GetPrecedence(); // Stop parsing if the parent operator has higher precedence if (parentPrecedence < operatorInfo.Precedence) { break; } // If the parent has the same precedence, stop parsing if the operator is left-associative if (parentPrecedence == operatorInfo.Precedence && operatorInfo.LeftAssociative) { break; } } } } // Get the expression Expression expression = parser.RemoveLastUnusedExpression(); if (expression != null) { // Attach any skipped non-EOL comments from the front of the expression, but only if we're a top-level expression // (otherwise, comments that preceed a sub-expression will get attached to an outer expression instead). This // prevents lost comments in places such as between a 'return' and the expression that follows. if (isTopLevel) { expression.MoveAllComments(lastToken); } // If this is a top-level expression or if the next token is a close paren, move any trailing comments on the last // token of the expression as post comments. This prevents lost comments in places such as when some trailing parts of // an 'if' conditional expression are commented-out, or the trailing parts of any sub-expression before a close paren. if ((isTopLevel || parser.TokenText == ParseTokenEndGroup) && parser.LastToken.HasTrailingComments && !parser.InDirectiveExpression) { expression.MoveCommentsAsPost(parser.LastToken); } } // Flush remaining unused objects as Unrecognized objects while (parser.HasUnused) { Expression preceedingUnused = parser.RemoveLastUnusedExpression(true); if (preceedingUnused != null) { if (expression == null) { expression = new Unrecognized(false, parser.InDocComment, preceedingUnused); } else if (expression is Unrecognized && !expression.HasParens) { ((Unrecognized)expression).AddLeft(preceedingUnused); } else { expression = new Unrecognized(false, parser.InDocComment, preceedingUnused, expression); } } else { // If we have no expression to put them on, then parse any preceeding compiler directives into a temp object for later retrieval if (expression == null) { expression = new TempExpr(); } expression.ParseUnusedAnnotations(parser, parent, true); break; } } if (expression is Unrecognized) { ((Unrecognized)expression).UpdateMessage(); } parser.Unused.Clear(); // Restore the previous Unused list in the parser parser.PopUnusedList(); if (expression != null) { // Get any EOL comments if (parser.LastToken.HasTrailingComments) { expression.MoveEOLComment(parser.LastToken); } // Set the parent starting token to the beginning of the expression parser.ParentStartingToken = startingToken; } return(expression); }