/// <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);
        }
Beispiel #4
0
        /// <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);
        }