private bool ScanXmlCrefToken()

in src/Compilers/CSharp/Portable/Parser/Lexer.cs [3820:4099]


        private bool ScanXmlCrefToken(ref TokenInfo info)
        {
            Debug.Assert(!this.LocationIs(XmlDocCommentLocation.Start));
            Debug.Assert(!this.LocationIs(XmlDocCommentLocation.Exterior));

            if (this.LocationIs(XmlDocCommentLocation.End))
            {
                info.Kind = SyntaxKind.EndOfDocumentationCommentToken;
                return true;
            }

            int beforeConsumed = TextWindow.Position;
            char consumedChar = TextWindow.NextChar();
            char consumedSurrogate = SlidingTextWindow.InvalidCharacter;

            // This first switch is for special characters.  If we see the corresponding
            // XML entities, we DO NOT want to take these actions.
            switch (consumedChar)
            {
                case '"':
                    if (this.ModeIs(LexerMode.XmlCrefDoubleQuote) || this.ModeIs(LexerMode.XmlNameDoubleQuote))
                    {
                        info.Kind = SyntaxKind.DoubleQuoteToken;
                        return true;
                    }

                    break;

                case '\'':
                    if (this.ModeIs(LexerMode.XmlCrefQuote) || this.ModeIs(LexerMode.XmlNameQuote))
                    {
                        info.Kind = SyntaxKind.SingleQuoteToken;
                        return true;
                    }

                    break;

                case '<':
                    info.Text = TextWindow.GetText(intern: false);
                    this.AddError(XmlParseErrorCode.XML_LessThanInAttributeValue, info.Text); //ErrorCode.WRN_XMLParseError
                    return true;

                case SlidingTextWindow.InvalidCharacter:
                    if (!TextWindow.IsReallyAtEnd())
                    {
                        goto default;
                    }

                    info.Kind = SyntaxKind.EndOfDocumentationCommentToken;
                    return true;

                case '\r':
                case '\n':
                    TextWindow.Reset(beforeConsumed);
                    this.ScanEndOfLine();
                    info.StringValue = info.Text = TextWindow.GetText(intern: false);
                    info.Kind = SyntaxKind.XmlTextLiteralNewLineToken;
                    this.MutateLocation(XmlDocCommentLocation.Exterior);
                    break;

                case '&':
                    TextWindow.Reset(beforeConsumed);
                    if (!TextWindow.TryScanXmlEntity(out consumedChar, out consumedSurrogate))
                    {
                        TextWindow.Reset(beforeConsumed);
                        this.ScanXmlEntity(ref info);
                        info.Kind = SyntaxKind.XmlEntityLiteralToken;
                        return true;
                    }

                    // TryScanXmlEntity advances even when it returns false.
                    break;

                case '{':
                    consumedChar = '<';
                    break;

                case '}':
                    consumedChar = '>';
                    break;

                default:
                    if (SyntaxFacts.IsNewLine(consumedChar))
                    {
                        goto case '\n';
                    }

                    break;
            }

            Debug.Assert(TextWindow.Position > beforeConsumed, "First character or entity has been consumed.");

            // NOTE: None of these cases will be matched if the surrogate is non-zero (UTF-16 rules)
            // so we don't need to check for that explicitly.

            // NOTE: there's a lot of overlap between this switch and the one in
            // ScanSyntaxToken, but we probably don't want to share code because
            // ScanSyntaxToken is really hot code and this switch does some extra
            // work.
            switch (consumedChar)
            {
                //// Single-Character Punctuation/Operators ////
                case '(':
                    info.Kind = SyntaxKind.OpenParenToken;
                    break;
                case ')':
                    info.Kind = SyntaxKind.CloseParenToken;
                    break;
                case '[':
                    info.Kind = SyntaxKind.OpenBracketToken;
                    break;
                case ']':
                    info.Kind = SyntaxKind.CloseBracketToken;
                    break;
                case ',':
                    info.Kind = SyntaxKind.CommaToken;
                    break;
                case '.':
                    info.Kind = SyntaxKind.DotToken;
                    break;
                case '?':
                    info.Kind = SyntaxKind.QuestionToken;
                    break;
                case '&':
                    info.Kind = SyntaxKind.AmpersandToken;
                    break;
                case '*':
                    info.Kind = SyntaxKind.AsteriskToken;
                    break;
                case '|':
                    info.Kind = SyntaxKind.BarToken;
                    break;
                case '^':
                    info.Kind = SyntaxKind.CaretToken;
                    break;
                case '%':
                    info.Kind = SyntaxKind.PercentToken;
                    break;
                case '/':
                    info.Kind = SyntaxKind.SlashToken;
                    break;
                case '~':
                    info.Kind = SyntaxKind.TildeToken;
                    break;

                // NOTE: Special case - convert curly brackets into angle brackets.
                case '{':
                    info.Kind = SyntaxKind.LessThanToken;
                    break;
                case '}':
                    info.Kind = SyntaxKind.GreaterThanToken;
                    break;

                //// Multi-Character Punctuation/Operators ////
                case ':':
                    if (AdvanceIfMatches(':')) info.Kind = SyntaxKind.ColonColonToken;
                    else info.Kind = SyntaxKind.ColonToken;
                    break;
                case '=':
                    if (AdvanceIfMatches('=')) info.Kind = SyntaxKind.EqualsEqualsToken;
                    else info.Kind = SyntaxKind.EqualsToken;
                    break;
                case '!':
                    if (AdvanceIfMatches('=')) info.Kind = SyntaxKind.ExclamationEqualsToken;
                    else info.Kind = SyntaxKind.ExclamationToken;
                    break;
                case '>':
                    if (AdvanceIfMatches('=')) info.Kind = SyntaxKind.GreaterThanEqualsToken;
                    // GreaterThanGreaterThanToken is synthesized in the parser since it is ambiguous (with closing nested type parameter lists)
                    // else if (AdvanceIfMatches('>')) info.Kind = SyntaxKind.GreaterThanGreaterThanToken;
                    else info.Kind = SyntaxKind.GreaterThanToken;
                    break;
                case '<':
                    if (AdvanceIfMatches('=')) info.Kind = SyntaxKind.LessThanEqualsToken;
                    else if (AdvanceIfMatches('<')) info.Kind = SyntaxKind.LessThanLessThanToken;
                    else info.Kind = SyntaxKind.LessThanToken;
                    break;
                case '+':
                    if (AdvanceIfMatches('+')) info.Kind = SyntaxKind.PlusPlusToken;
                    else info.Kind = SyntaxKind.PlusToken;
                    break;
                case '-':
                    if (AdvanceIfMatches('-')) info.Kind = SyntaxKind.MinusMinusToken;
                    else info.Kind = SyntaxKind.MinusToken;
                    break;
            }

            if (info.Kind != SyntaxKind.None)
            {
                Debug.Assert(info.Text == null, "Haven't tried to set it yet.");
                Debug.Assert(info.StringValue == null, "Haven't tried to set it yet.");

                string valueText = SyntaxFacts.GetText(info.Kind);
                string actualText = TextWindow.GetText(intern: false);
                if (!string.IsNullOrEmpty(valueText) && actualText != valueText)
                {
                    info.RequiresTextForXmlEntity = true;
                    info.Text = actualText;
                    info.StringValue = valueText;
                }
            }
            else
            {
                // If we didn't match any of the above cases, then we either have an
                // identifier or an unexpected character.

                TextWindow.Reset(beforeConsumed);

                if (this.ScanIdentifier(ref info) && info.Text.Length > 0)
                {
                    // ACASEY:  All valid identifier characters should be valid in XML attribute values,
                    // but I don't want to add an assert because XML character classification is expensive.
                    // check to see if it is an actual keyword
                    // NOTE: name attribute values don't respect keywords - everything is an identifier.
                    SyntaxKind keywordKind;
                    if (!InXmlNameAttributeValue && !info.IsVerbatim && !info.HasIdentifierEscapeSequence && _cache.TryGetKeywordKind(info.StringValue, out keywordKind))
                    {
                        if (SyntaxFacts.IsContextualKeyword(keywordKind))
                        {
                            info.Kind = SyntaxKind.IdentifierToken;
                            info.ContextualKind = keywordKind;
                            // Don't need to set any special flags to store the original text of an identifier.
                        }
                        else
                        {
                            info.Kind = keywordKind;
                            info.RequiresTextForXmlEntity = info.Text != info.StringValue;
                        }
                    }
                    else
                    {
                        info.ContextualKind = info.Kind = SyntaxKind.IdentifierToken;
                    }
                }
                else
                {
                    if (consumedChar == '@')
                    {
                        // Saw '@', but it wasn't followed by an identifier (otherwise ScanIdentifier would have succeeded).
                        if (TextWindow.PeekChar() == '@')
                        {
                            TextWindow.NextChar();
                            info.Text = TextWindow.GetText(intern: true);
                            info.StringValue = ""; // Can't be null for an identifier.
                        }
                        else
                        {
                            this.ScanXmlEntity(ref info);
                        }
                        info.Kind = SyntaxKind.IdentifierToken;
                        this.AddError(ErrorCode.ERR_ExpectedVerbatimLiteral);
                    }
                    else if (TextWindow.PeekChar() == '&')
                    {
                        this.ScanXmlEntity(ref info);
                        info.Kind = SyntaxKind.XmlEntityLiteralToken;
                        this.AddCrefError(ErrorCode.ERR_UnexpectedCharacter, info.Text);
                    }
                    else
                    {
                        char bad = TextWindow.NextChar();
                        info.Text = TextWindow.GetText(intern: false);

                        // If it's valid in XML, then it was unexpected in cref mode.
                        // Otherwise, it's just bad XML.
                        if (MatchesProductionForXmlChar((uint)bad))
                        {
                            this.AddCrefError(ErrorCode.ERR_UnexpectedCharacter, info.Text);
                        }
                        else
                        {
                            this.AddError(XmlParseErrorCode.XML_InvalidUnicodeChar);
                        }
                    }
                }
            }

            Debug.Assert(info.Kind != SyntaxKind.None || info.Text != null);
            return info.Kind != SyntaxKind.None;
        }