/// <summary> /// Parse a <see cref="GenericMethodDecl"/> using alternate type argument delimiters. /// </summary> public static GenericMethodDecl ParseAlt(Parser parser, CodeObject parent, ParseFlags flags) { // Verify that this alternate form is inside a doc comment (subroutines will look for the appropriate // delimiters according to the parser state) in addition to passing other verifications as above. // If it doesn't seem to match the proper pattern, abort so that other types can try parsing it. if (parser.InDocComment && ((parent is TypeDecl && parser.HasUnusedIdentifier) || parser.HasUnusedTypeRefAndIdentifier) && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, flags) && parser.LastPeekedTokenText == ParseTokenStart) { return(new GenericMethodDecl(parser, parent, false, flags)); } return(null); }
/// <summary> /// Parse a <see cref="GenericMethodDecl"/>. /// </summary> public static new GenericMethodDecl Parse(Parser parser, CodeObject parent, ParseFlags flags) { // If our parent is a TypeDecl, verify that we have an unused identifier (a Dot operator is possible // for explicit interface implementations, but is handled by MethodDecl, which then calls the constructor // below). Otherwise, require a possible return type in addition to the identifier. Also verify that // we seem to match a type argument list pattern followed by a '('. // If it doesn't seem to match the proper pattern, abort so that other types can try parsing it. if (((parent is TypeDecl && parser.HasUnusedIdentifier) || parser.HasUnusedTypeRefAndIdentifier) && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenArgumentEnd, flags) && parser.LastPeekedTokenText == ParseTokenStart) { return(new GenericMethodDecl(parser, parent, false, flags)); } return(null); }
/// <summary> /// Parse a list of type parameters. /// </summary> public static ChildList <TypeParameter> ParseList(Parser parser, CodeObject parent) { ChildList <TypeParameter> parameters = null; if (parser.TokenText == ParseTokenStart || (parser.InDocComment && parser.TokenText == ParseTokenAltStart && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, ParseFlags.None))) { string argumentEnd = (parser.TokenText == ParseTokenAltStart ? ParseTokenAltEnd : ParseTokenEnd); parent.MoveAllComments(parser.LastToken); // Move any skipped comments to the parent parser.NextToken(); // Move past '<' // Create a string of possible terminators (assuming 1 char terminators for now) string terminators = argumentEnd + MethodDeclBase.ParseTokenStart + ConstraintClause.ParseTokenSeparator + Statement.ParseTokenTerminator; while (parser.Token != null && (parser.TokenText.Length != 1 || terminators.IndexOf(parser.TokenText[0]) < 0)) { TypeParameter typeParameter = new TypeParameter(parser, parent); if (typeParameter.Name != null) { if (parameters == null) { parameters = new ChildList <TypeParameter>(parent); } parameters.Add(typeParameter); if (parser.TokenText == ParseTokenSeparator) { parser.NextToken(); // Move past ',' } } else { parser.NextToken(); // Move past bad token (non-identifier) } } parser.NextToken(); // Move past '>' } return(parameters); }
/// <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); }
protected static bool PeekConditional(Parser parser, CodeObject parent, int colonCount, ParseFlags flags) { // Unfortunately, determining if the '?' is definitely part of a '?:' pair as opposed to a nullable type declaration // isn't easy - in fact, it's the single hardest thing to parse in the entire language. Nicely formatted code always // has a space before it in the first case, and not in the second, but code is often poorly formatted. The only way // to be sure how to parse it is to peek ahead looking for the matching ':'. We can parse in a very simplified manner // for efficiency, just keeping track of '()', '[]', '{}' pairs, aborting if we hit a ';' anywhere, or a ',' that's // not in a nested scope, or finally if we find the matching ':' (not in a nested scope). If we're in the '?' clause // of a nested Conditional without parens, then we need to find an extra ':' for each nested level (colonCount). // One more thing - we have to handle '<>' with generic arguments in order to avoid aborting on a possible ',' // inside them, but we also have to avoid any confusion with LessThan/GreatherThan operators. bool firstToken = true; Stack <string> stack = new Stack <string>(8); while (true) { Token next = parser.PeekNextToken(); if (next == null) { break; } check: if (next.IsSymbol) { // Abort if any invalid symbols appear immediately after the '?' if (firstToken) { firstToken = false; if (">)]};,".Contains(next.Text)) { break; } } // If we have a '<', skip over any possible generic type parameters so that we don't abort on a ',' if (next.Text == TypeRefBase.ParseTokenArgumentStart) { TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenArgumentEnd, flags); next = parser.LastPeekedToken; } // Keep track of nested parens, brackets (new arrays), braces (initializers or generics in doc comments) string nextText = next.Text; if (nextText == ParseTokenStartGroup || nextText == NewArray.ParseTokenStart || nextText == Initializer.ParseTokenStart) { stack.Push(nextText); } else if (nextText == ParseTokenEndGroup) { // If we hit an unexpected ')', abort if (stack.Count == 0 || stack.Peek() != ParseTokenStartGroup) { break; } stack.Pop(); } else if (nextText == NewArray.ParseTokenEnd) { // If we hit an unexpected ']', abort if (stack.Count == 0 || stack.Peek() != NewArray.ParseTokenStart) { break; } stack.Pop(); } else if (nextText == Initializer.ParseTokenEnd) { // If we hit an unexpected '}', abort if (stack.Count == 0 || stack.Peek() != Initializer.ParseTokenStart) { break; } stack.Pop(); } else if (nextText == ParseToken1) { // If we found a '?', recursively call this routine to process it (in order to // differentiate between a nested nullable type or another Conditional). if (!PeekConditional(parser, parent, 1, flags)) { // If it wasn't a Conditional, get the last token and check it next = parser.LastPeekedToken; goto check; } } else if (stack.Count == 0) { // We're not inside any nested parens/brackets/braces/angle brackets. Check for certain symbols: // Terminate on a ',' or ';' (we ignore if nested because of anonymous method bodies) if (nextText == ParseTokenSeparator || nextText == Statement.ParseTokenTerminator) { break; } // Process a ':' if (nextText == ParseToken2) { // Assume we have a valid Conditional if the expected number of colons has been found if (--colonCount == 0) { return(true); } } } } else if (next.Text == NewOperator.ParseToken) { // If we found a 'new', treat the following as a type (in order to avoid any trailing '?' of // a nullable type from being treated as a nested conditional). TypeRefBase.PeekType(parser, parser.PeekNextToken(), true, flags | ParseFlags.Type); // Whether it worked or not, pick up with the last peeked token next = parser.LastPeekedToken; firstToken = false; goto check; } firstToken = false; } return(false); }