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;
}