public char NextCharOrUnicodeEscape( out char surrogateCharacter, out SyntaxDiagnosticInfo info ) { var ch = this.PeekChar(); Debug.Assert( ch != InvalidCharacter, "Precondition established by all callers; required for correctness of AdvanceChar() call." ); if (ch == '\\') { var ch2 = this.PeekChar(1); if (ch2 == 'U' || ch2 == 'u') { return(this.ScanUnicodeEscape( peek: false, surrogateCharacter: out surrogateCharacter, info: out info )); } } surrogateCharacter = InvalidCharacter; info = null; this.AdvanceChar(); return(ch); }
protected SyntaxDiagnosticInfo[] GetErrors(int leadingTriviaWidth) { if (_errors != null) { if (leadingTriviaWidth > 0) { var array = new SyntaxDiagnosticInfo[_errors.Count]; for (int i = 0; i < _errors.Count; i++) { // fixup error positioning to account for leading trivia array[i] = _errors[i].WithOffset(_errors[i].Offset + leadingTriviaWidth); } return(array); } else { return(_errors.ToArray()); } } else { return(null); } }
private void ScanInterpolatedStringLiteral(bool isVerbatim, ref TokenInfo info) { // We have a string of the form // $" ... " // or, if isVerbatim is true, of possible forms // $@" ... " // @$" ... " // Where the contents contains zero or more sequences // { STUFF } // where these curly braces delimit STUFF in expression "holes". // In order to properly find the closing quote of the whole string, // we need to locate the closing brace of each hole, as strings // may appear in expressions in the holes. So we // need to match up any braces that appear between them. // But in order to do that, we also need to match up any // /**/ comments, ' characters quotes, () parens // [] brackets, and "" strings, including interpolated holes in the latter. SyntaxDiagnosticInfo error = null; bool closeQuoteMissing; ScanInterpolatedStringLiteralTop( null, isVerbatim, ref info, ref error, out closeQuoteMissing ); this.AddError(error); }
internal void ScanInterpolatedStringLiteralTop(ArrayBuilder <Interpolation> interpolations, ref TokenInfo info, out bool closeQuoteMissing) { Debug.Assert(lexer.TextWindow.PeekChar() == '$'); lexer.TextWindow.AdvanceChar(); // $ if (isVerbatim) { Debug.Assert(lexer.TextWindow.PeekChar() == '@'); lexer.TextWindow.AdvanceChar(); // @ } Debug.Assert(lexer.TextWindow.PeekChar() == '"'); lexer.TextWindow.AdvanceChar(); // " ScanInterpolatedStringLiteralContents(interpolations); if (lexer.TextWindow.PeekChar() != '"') { Debug.Assert(IsAtEnd()); if (error == null) { int position = IsAtEnd(true) ? lexer.TextWindow.Position - 1 : lexer.TextWindow.Position; error = lexer.MakeError(position, 1, isVerbatim ? ErrorCode.ERR_UnterminatedStringLit : ErrorCode.ERR_NewlineInConst); } closeQuoteMissing = true; } else { // found the closing quote lexer.TextWindow.AdvanceChar(); // " closeQuoteMissing = false; } info.Kind = SyntaxKind.InterpolatedStringToken; }
private void ScanISLNestedVerbatimString(ref SyntaxDiagnosticInfo error) { Debug.Assert(TextWindow.PeekChar() == '@'); TextWindow.AdvanceChar(); Debug.Assert(TextWindow.PeekChar() == '\"'); TextWindow.AdvanceChar(); // move past quote while (true) { if (IsAtEnd()) { // we'll get an error in the enclosing construct return; } char ch = TextWindow.PeekChar(); TextWindow.AdvanceChar(); if (ch == '\"') { if (TextWindow.PeekChar(1) == '\"') { TextWindow.AdvanceChar(); // move past escaped quote } else { return; } } } }
private static TSyntax UpdateDiagnosticOffset <TSyntax>( TSyntax node, int diagnosticOffsetDelta ) where TSyntax : CSharpSyntaxNode { DiagnosticInfo[] oldDiagnostics = node.GetDiagnostics(); if (oldDiagnostics == null || oldDiagnostics.Length == 0) { return(node); } var numDiagnostics = oldDiagnostics.Length; DiagnosticInfo[] newDiagnostics = new DiagnosticInfo[numDiagnostics]; for (int i = 0; i < numDiagnostics; i++) { DiagnosticInfo oldDiagnostic = oldDiagnostics[i]; SyntaxDiagnosticInfo oldSyntaxDiagnostic = oldDiagnostic as SyntaxDiagnosticInfo; newDiagnostics[i] = oldSyntaxDiagnostic == null ? oldDiagnostic : new SyntaxDiagnosticInfo( oldSyntaxDiagnostic.Offset + diagnosticOffsetDelta, oldSyntaxDiagnostic.Width, (ErrorCode)oldSyntaxDiagnostic.Code, oldSyntaxDiagnostic.Arguments ); } return(node.WithDiagnosticsGreen(newDiagnostics)); }
/// <remarks> /// NOTE: we are specifically diverging from dev11 to improve the user experience. /// Since treating the "async" keyword as an identifier in older language /// versions can never result in a correct program, we instead accept it as the a /// keyword regardless of the language version and produce an error if the version /// is insufficient. /// </remarks> protected TNode CheckFeatureAvailability <TNode>(TNode node, MessageID feature, bool forceWarning = false) where TNode : CSharpSyntaxNode { LanguageVersion availableVersion = this.Options.LanguageVersion; if (feature == MessageID.IDS_FeatureModuleAttrLoc) { // There's a special error code for this feature, so handle it separately. return(availableVersion >= LanguageVersion.CSharp2 ? node : this.AddError(node, ErrorCode.WRN_NonECMAFeature, feature.Localize())); } LanguageVersion requiredVersion = feature.RequiredVersion(); if (availableVersion >= requiredVersion) { return(node); } if (!forceWarning) { return(this.AddError(node, availableVersion.GetErrorCode(), feature.Localize(), (int)requiredVersion)); } SyntaxDiagnosticInfo rawInfo = new SyntaxDiagnosticInfo(availableVersion.GetErrorCode(), feature.Localize(), (int)requiredVersion); return(this.AddError(node, ErrorCode.WRN_ErrorOverride, rawInfo, rawInfo.Code)); }
protected SyntaxDiagnosticInfo[] GetErrors(int leadingTriviaWidth) { if (_errors != null) { if (leadingTriviaWidth > 0) { var array = new SyntaxDiagnosticInfo[_errors.Count]; for (int i = 0; i < _errors.Count; i++) { // fixup error positioning to account for leading trivia array[i] = _errors[i].WithOffset(_errors[i].Offset + leadingTriviaWidth); } return array; } else { return _errors.ToArray(); } } else { return null; } }
private void TrySetUnrecoverableError(SyntaxDiagnosticInfo error) { // only need to record the first error we hit Error ??= error; // No matter what, ensure that we know we hit an error we can't recover from. EncounteredUnrecoverableError = true; }
public char NextUnicodeEscape(out char surrogateCharacter, out SyntaxDiagnosticInfo info) { return(ScanUnicodeEscape( peek: false, surrogateCharacter: out surrogateCharacter, info: out info )); }
private void TrySetRecoverableError(SyntaxDiagnosticInfo error) { // only need to record the first error we hit Error ??= error; // Do not touch 'EncounteredUnrecoverableError'. If we already encountered something unrecoverable, // that doesn't change. And if we haven't hit something unrecoverable then we stay in that mode as this // is a recoverable error. }
private void ScanISLContents(ArrayBuilder <Interpolation> interpolations, ref SyntaxDiagnosticInfo error) { while (true) { if (IsAtEnd()) { // error: end of line before end of string return; } switch (TextWindow.PeekChar()) { case '\"': // found the end of the string return; case '\\': TextWindow.AdvanceChar(); if (IsAtEnd()) { // the caller will complain about unclosed quote return; } else if (TextWindow.PeekChar() == '{') { int interpolationStart = TextWindow.Position; TextWindow.AdvanceChar(); ScanISLHoleBalancedText('}', true, ref error); int end = TextWindow.Position; if (TextWindow.PeekChar() == '}') { TextWindow.AdvanceChar(); } else { if (error == null) { error = MakeError(interpolationStart - 1, 2, ErrorCode.ERR_UnclosedExpressionHole); } } if (interpolations != null) { interpolations.Add(new Interpolation(interpolationStart, end)); } } else { TextWindow.AdvanceChar(); // skip past a single escaped character } continue; default: // found some other character in the string portion TextWindow.AdvanceChar(); continue; } } }
protected void AddError(SyntaxDiagnosticInfo error) { if (this.errors == null) { this.errors = new List <SyntaxDiagnosticInfo>(8); } this.errors.Add(error); }
private InternalSyntaxTrivia AddDiagnostic(InternalSyntaxTrivia token, int tokenIndex) { string errorMessage; if (this.Antlr4Errors.TryGetValue(tokenIndex, out errorMessage)) { SyntaxDiagnosticInfo diagnosticInfo = this.MakeError(token.GetLeadingTriviaWidth(), token.Width, Antlr4RoslynErrorCode.ERR_SyntaxError, errorMessage); return((InternalSyntaxTrivia)token.WithDiagnostics(new DiagnosticInfo[] { diagnosticInfo })); } return(token); }
protected void AddError(SyntaxDiagnosticInfo error) { if (error != null) { if (_errors == null) { _errors = new List <SyntaxDiagnosticInfo>(8); } _errors.Add(error); } }
protected void AddError(SyntaxDiagnosticInfo error) { if (error != null) { if (this.errors == null) { this.errors = new List<SyntaxDiagnosticInfo>(8); } this.errors.Add(error); } }
void ScanISLHoleBracketed(char start, char end, ref SyntaxDiagnosticInfo error) { Debug.Assert(start == TextWindow.PeekChar()); TextWindow.AdvanceChar(); ScanISLHoleBalancedText(end, false, ref error); if (TextWindow.PeekChar() == end) { TextWindow.AdvanceChar(); } else { // an error was given by the caller } }
private void ScanISLNestedString(char quote, ref SyntaxDiagnosticInfo error) { Debug.Assert(TextWindow.PeekChar() == quote); TextWindow.AdvanceChar(); // move past quote while (true) { if (IsAtEnd()) { // we'll get an error in the enclosing construct return; } char ch = TextWindow.PeekChar(); TextWindow.AdvanceChar(); switch (ch) { case '\"': case '\'': if (ch == quote) { return; } break; case '\\': ch = TextWindow.PeekChar(); if (IsAtEnd()) { return; } else if (ch == '{' && quote == '"') { TextWindow.AdvanceChar(); // move past { ScanISLHoleBalancedText('}', true, ref error); if (TextWindow.PeekChar() == '}') { TextWindow.AdvanceChar(); } } else { TextWindow.AdvanceChar(); // move past one escaped character } break; } } }
/// <remarks> /// NOTE: we are specifically diverging from dev11 to improve the user experience. /// Since treating the "async" keyword as an identifier in older language /// versions can never result in a correct program, we instead accept it as a /// keyword regardless of the language version and produce an error if the version /// is insufficient. /// </remarks> protected TNode CheckFeatureAvailability <TNode>(TNode node, MessageID feature, bool forceWarning = false) where TNode : GreenNode { LanguageVersion availableVersion = this.Options.LanguageVersion; if (feature == MessageID.IDS_FeatureModuleAttrLoc) { // There's a special error code for this feature, so handle it separately. return(availableVersion >= LanguageVersion.CSharp2 ? node : this.AddError(node, ErrorCode.WRN_NonECMAFeature, feature.Localize())); } if (IsFeatureEnabled(feature)) { return(node); } var featureName = feature.Localize(); var requiredFeature = feature.RequiredFeature(); if (requiredFeature != null) { if (forceWarning) { SyntaxDiagnosticInfo rawInfo = new SyntaxDiagnosticInfo(ErrorCode.ERR_FeatureIsExperimental, featureName, requiredFeature); return(this.AddError(node, ErrorCode.WRN_ErrorOverride, rawInfo, rawInfo.Code)); } return(this.AddError(node, ErrorCode.ERR_FeatureIsExperimental, featureName, requiredFeature)); } else { var requiredVersion = feature.RequiredVersion(); if (forceWarning) { SyntaxDiagnosticInfo rawInfo = new SyntaxDiagnosticInfo(availableVersion.GetErrorCode(), featureName, new CSharpRequiredLanguageVersion(requiredVersion)); return(this.AddError(node, ErrorCode.WRN_ErrorOverride, rawInfo, rawInfo.Code)); } return(this.AddError(node, availableVersion.GetErrorCode(), featureName, new CSharpRequiredLanguageVersion(requiredVersion))); } }
internal void ScanInterpolatedStringLiteralTop( ArrayBuilder <Interpolation> interpolations, bool isVerbatim, ref TokenInfo info, ref SyntaxDiagnosticInfo error, out bool closeQuoteMissing ) { var subScanner = new InterpolatedStringScanner(this, isVerbatim); subScanner.ScanInterpolatedStringLiteralTop( interpolations, ref info, out closeQuoteMissing ); error = subScanner.error; info.Text = TextWindow.GetText(false); }
private void ScanInterpolatedStringLiteral(ref TokenInfo info) { // We have a string of the form // " ... " // Where the contents contains one or more sequences // \{ STUFF } // where these curly braces delimit STUFF in expression "holes". // In order to properly find the closing quote of the whole string, // we need to locate the closing brace of each hole, as strings // may appear in expressions in the holes. So we // need to match up any braces that appear between them. // But in order to do that, we also need to match up any // /**/ comments, ' characters quotes, () parens // [] brackets, and "" strings, including interpolated holes in the latter. SyntaxDiagnosticInfo error = null; ScanISLTop(null, ref info, ref error); this.AddError(error); info.Text = TextWindow.GetText(false); }
private ExpressionSyntax ParseInterpolatedStringToken() { // We don't want to make the scanner stateful (between tokens) if we can possibly avoid it. // The approach implemented here is // // (1) Scan the whole interpolated string literal as a single token. Now the statefulness of // the scanner (to match { }'s) is limited to its behavior while scanning a single token. // // (2) When the parser gets such a token, here, it spins up another scanner / parser on each of // the holes and builds a tree for the whole thing (resulting in an InterpolatedStringExpressionSyntax). // // (3) The parser discards the original token and replaces it with this tree. (In other words, // it replaces one token with a different set of tokens that have already been parsed) // // (4) On an incremental change, we widen the invalidated region to include any enclosing interpolated // string nonterminal so that we never reuse tokens inside a changed interpolated string. // // This has the secondary advantage that it can reasonably be specified. // // The substitution will end up being invisible to external APIs and clients such as the IDE, as // they have no way to ask for the stream of tokens before parsing. // var originalToken = this.EatToken(); var originalText = originalToken.ValueText; // this is actually the source text Debug.Assert(originalText[0] == '$' || originalText[0] == '@'); var isAltInterpolatedVerbatim = originalText.Length > 2 && originalText[0] == '@'; // @$ var isVerbatim = isAltInterpolatedVerbatim || (originalText.Length > 2 && originalText[1] == '@'); Debug.Assert(originalToken.Kind == SyntaxKind.InterpolatedStringToken); var interpolations = ArrayBuilder <Lexer.Interpolation> .GetInstance(); SyntaxDiagnosticInfo error = null; bool closeQuoteMissing; using (var tempLexer = new Lexer(Text.SourceText.From(originalText), this.Options, allowPreprocessorDirectives: false)) { // compute the positions of the interpolations in the original string literal, and also compute/preserve // lexical errors var info = default(Lexer.TokenInfo); tempLexer.ScanInterpolatedStringLiteralTop(interpolations, isVerbatim, ref info, ref error, out closeQuoteMissing); } // Make a token for the open quote $" or $@" or @$" var openQuoteIndex = isVerbatim ? 2 : 1; Debug.Assert(originalText[openQuoteIndex] == '"'); var openQuoteKind = isVerbatim ? SyntaxKind.InterpolatedVerbatimStringStartToken // $@ or @$ : SyntaxKind.InterpolatedStringStartToken; // $ var openQuoteText = isAltInterpolatedVerbatim ? "@$\"" : isVerbatim ? "$@\"" : "$\""; var openQuote = SyntaxFactory.Token(originalToken.GetLeadingTrivia(), openQuoteKind, openQuoteText, openQuoteText, trailing: null); if (isAltInterpolatedVerbatim) { openQuote = CheckFeatureAvailability(openQuote, MessageID.IDS_FeatureAltInterpolatedVerbatimStrings); } // Make a token for the close quote " (even if it was missing) var closeQuoteIndex = closeQuoteMissing ? originalText.Length : originalText.Length - 1; Debug.Assert(closeQuoteMissing || originalText[closeQuoteIndex] == '"'); var closeQuote = closeQuoteMissing ? SyntaxFactory.MissingToken(SyntaxKind.InterpolatedStringEndToken).TokenWithTrailingTrivia(originalToken.GetTrailingTrivia()) : SyntaxFactory.Token(null, SyntaxKind.InterpolatedStringEndToken, originalToken.GetTrailingTrivia()); var builder = _pool.Allocate <InterpolatedStringContentSyntax>(); if (interpolations.Count == 0) { // In the special case when there are no interpolations, we just construct a format string // with no inserts. We must still use String.Format to get its handling of escapes such as {{, // so we still treat it as a composite format string. var text = Substring(originalText, openQuoteIndex + 1, closeQuoteIndex - 1); if (text.Length > 0) { var token = MakeStringToken(text, text, isVerbatim, SyntaxKind.InterpolatedStringTextToken); builder.Add(SyntaxFactory.InterpolatedStringText(token)); } } else { for (int i = 0; i < interpolations.Count; i++) { var interpolation = interpolations[i]; // Add a token for text preceding the interpolation var text = Substring(originalText, (i == 0) ? (openQuoteIndex + 1) : (interpolations[i - 1].CloseBracePosition + 1), interpolation.OpenBracePosition - 1); if (text.Length > 0) { var token = MakeStringToken(text, text, isVerbatim, SyntaxKind.InterpolatedStringTextToken); builder.Add(SyntaxFactory.InterpolatedStringText(token)); } // Add an interpolation var interp = ParseInterpolation(originalText, interpolation, isVerbatim); builder.Add(interp); } // Add a token for text following the last interpolation var lastText = Substring(originalText, interpolations[interpolations.Count - 1].CloseBracePosition + 1, closeQuoteIndex - 1); if (lastText.Length > 0) { var token = MakeStringToken(lastText, lastText, isVerbatim, SyntaxKind.InterpolatedStringTextToken); builder.Add(SyntaxFactory.InterpolatedStringText(token)); } } interpolations.Free(); var result = SyntaxFactory.InterpolatedStringExpression(openQuote, builder, closeQuote); _pool.Free(builder); if (error != null) { result = result.WithDiagnosticsGreen(new[] { error }); } Debug.Assert(originalToken.ToFullString() == result.ToFullString()); // yield from text equals yield from node return(CheckFeatureAvailability(result, MessageID.IDS_FeatureInterpolatedStrings)); }
private SkippedTokensTriviaSyntax ParserErrorsAsTrivia(List <ParseErrorData> parseErrors, IDictionary <string, SourceText> includes) { // create one syntax token per error // and one syntax token for the main file // these tokens will get as many errors as needed. // We are only includin 1 syntax error per file (1003) and one parse error (9002) var textNode = SyntaxFactory.BadToken(null, _text.ToString(), null); var builder = new SyntaxListBuilder(parseErrors.Count + 1); if (!parseErrors.IsEmpty()) { bool hasSyntaxError = false; bool hasParserError = false; foreach (var e in parseErrors) { bool add = true; if (e.Code == ErrorCode.ERR_SyntaxError) { add = !hasSyntaxError; hasSyntaxError = true; } else if (e.Code == ErrorCode.ERR_ParserError) { add = !hasParserError; hasParserError = true; } if (!add) { continue; } if (e.Node != null) { var node = e.Node; var key = node.SourceFileName; int pos = node.Position; int len = node.FullWidth; if (node.SourceSymbol != null) { var sym = node.SourceSymbol as XSharpToken; key = sym.SourceName; pos = sym.Position; len = sym.FullWidth; } if (len <= 0 || pos < 0) { if (e.Node.Parent != null) { var xNode = e.Node as IXParseTree; pos = xNode.Position; len = xNode.FullWidth; } if (pos < 0) { pos = 0; } if (len <= 0) { len = 1; } } SourceText inc = null; if (key != null && includes.ContainsKey(key)) { inc = includes[key]; if (pos - 1 + len > inc.Length) { len = inc.Length - pos + 1; } } var diag = new SyntaxDiagnosticInfo(pos, len, e.Code, e.Args); if (inc != null) { var incNode = SyntaxFactory.BadToken(null, inc.ToString(), null); incNode = incNode.WithAdditionalDiagnostics(diag); incNode.XNode = e.Node; builder.Add(incNode); } else { textNode = textNode.WithAdditionalDiagnostics(diag); } } else { textNode = textNode.WithAdditionalDiagnostics(new SyntaxDiagnosticInfo(e.Code, e.Args)); } } } else { if (_options.ParseLevel == ParseLevel.Complete) { textNode = textNode.WithAdditionalDiagnostics(new SyntaxDiagnosticInfo(ErrorCode.ERR_ParserError, "Unknown error")); } } builder.Add(textNode); return(_syntaxFactory.SkippedTokensTrivia(builder.ToList <SyntaxToken>())); }
public char NextCharOrUnicodeEscape(out char surrogateCharacter, out SyntaxDiagnosticInfo info) { var ch = this.PeekChar(); Debug.Assert(ch != InvalidCharacter, "Precondition established by all callers; required for correctness of AdvanceChar() call."); if (ch == '\\') { var ch2 = this.PeekChar(1); if (ch2 == 'U' || ch2 == 'u') { return this.ScanUnicodeEscape(peek: false, surrogateCharacter: out surrogateCharacter, info: out info); } } surrogateCharacter = InvalidCharacter; info = null; this.AdvanceChar(); return ch; }
private void ScanFormatSpecifier() { Debug.Assert(lexer.TextWindow.PeekChar() == ':'); lexer.TextWindow.AdvanceChar(); while (true) { char ch = lexer.TextWindow.PeekChar(); if (ch == '\\' && !isVerbatim) { // normal string & char constants can have escapes var pos = lexer.TextWindow.Position; char c2; ch = lexer.ScanEscapeSequence(out c2); if ((ch == '{' || ch == '}') && error == null) { error = lexer.MakeError(pos, 1, ErrorCode.ERR_EscapedCurly, ch); } } else if (ch == '"') { if (isVerbatim && lexer.TextWindow.PeekChar(1) == '"') { lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } else { return; // premature end of string! let caller complain about unclosed interpolation } } else if (ch == '{') { var pos = lexer.TextWindow.Position; lexer.TextWindow.AdvanceChar(); // ensure any { characters are doubled up if (lexer.TextWindow.PeekChar() == '{') { lexer.TextWindow.AdvanceChar(); // { } else if (error == null) { error = lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "{"); } } else if (ch == '}') { if (lexer.TextWindow.PeekChar(1) == '}') { lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } else { return; // end of interpolation } } else if (IsAtEnd()) { return; // premature end; let caller complain } else { lexer.TextWindow.AdvanceChar(); } } }
/// <summary> /// Scan past the hole inside an interpolated string literal, leaving the current character on the '}' (if any) /// </summary> private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool isHole, ref int colonPosition) { while (true) { if (IsAtEnd()) { // the caller will complain return; } char ch = lexer.TextWindow.PeekChar(); switch (ch) { case '#': // preprocessor directives not allowed. if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } lexer.TextWindow.AdvanceChar(); continue; case '$': if (lexer.TextWindow.PeekChar(1) == '"' || lexer.TextWindow.PeekChar(1) == '@' && lexer.TextWindow.PeekChar(2) == '"') { bool isVerbatimSubstring = lexer.TextWindow.PeekChar(1) == '@'; var interpolations = default(ArrayBuilder <Interpolation>); var info = default(TokenInfo); bool wasVerbatim = this.isVerbatim; bool wasAllowNewlines = this.allowNewlines; try { this.isVerbatim = isVerbatimSubstring; this.allowNewlines &= isVerbatim; bool closeQuoteMissing; ScanInterpolatedStringLiteralTop(interpolations, ref info, out closeQuoteMissing); } finally { this.isVerbatim = wasVerbatim; this.allowNewlines = wasAllowNewlines; } continue; } goto default; case ':': // the first colon not nested within matching delimiters is the start of the format string if (isHole) { Debug.Assert(colonPosition == 0); colonPosition = lexer.TextWindow.Position; ScanFormatSpecifier(); return; } goto default; case '}': case ')': case ']': if (ch == endingChar) { return; } if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } goto default; case '"': case '\'': // handle string or character literal inside an expression hole. ScanInterpolatedStringLiteralNestedString(); continue; case '@': if (lexer.TextWindow.PeekChar(1) == '"') { // check for verbatim string inside an expression hole. ScanInterpolatedStringLiteralNestedVerbatimString(); continue; } goto default; case '/': switch (lexer.TextWindow.PeekChar(1)) { case '/': if (isVerbatim && allowNewlines) { lexer.TextWindow.AdvanceChar(); // skip / lexer.TextWindow.AdvanceChar(); // skip / while (!IsAtEnd(false)) { lexer.TextWindow.AdvanceChar(); // skip // comment character } } else { // error: single-line comment not allowed in an interpolated string if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 2, ErrorCode.ERR_SingleLineCommentInExpressionHole); } lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } continue; case '*': // check for and scan /* comment */ ScanInterpolatedStringLiteralNestedComment(); continue; default: lexer.TextWindow.AdvanceChar(); continue; } case '{': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('{', '}'); continue; case '(': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('(', ')'); continue; case '[': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('[', ']'); continue; default: // part of code in the expression hole lexer.TextWindow.AdvanceChar(); continue; } } }
private char ScanUnicodeEscape(bool peek, out char surrogateCharacter, out SyntaxDiagnosticInfo info) { surrogateCharacter = InvalidCharacter; info = null; int start = this.Position; char character = this.PeekChar(); Debug.Assert(character == '\\'); this.AdvanceChar(); character = this.PeekChar(); if (character == 'U') { uint uintChar = 0; this.AdvanceChar(); if (!SyntaxFacts.IsHexDigit(this.PeekChar())) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } else { for (int i = 0; i < 8; i++) { character = this.PeekChar(); if (!SyntaxFacts.IsHexDigit(character)) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } break; } uintChar = (uint)((uintChar << 4) + SyntaxFacts.HexValue(character)); this.AdvanceChar(); } if (uintChar > 0x0010FFFF) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } else { character = GetCharsFromUtf32(uintChar, out surrogateCharacter); } } } else { Debug.Assert(character == 'u' || character == 'x'); int intChar = 0; this.AdvanceChar(); if (!SyntaxFacts.IsHexDigit(this.PeekChar())) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } else { for (int i = 0; i < 4; i++) { char ch2 = this.PeekChar(); if (!SyntaxFacts.IsHexDigit(ch2)) { if (character == 'u') { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } break; } intChar = (intChar << 4) + SyntaxFacts.HexValue(ch2); this.AdvanceChar(); } character = (char)intChar; } } return(character); }
internal void ScanInterpolatedStringLiteralTop(ArrayBuilder<Interpolation> interpolations, ref TokenInfo info, out bool closeQuoteMissing) { Debug.Assert(lexer.TextWindow.PeekChar() == '$'); lexer.TextWindow.AdvanceChar(); // $ if (isVerbatim) { Debug.Assert(lexer.TextWindow.PeekChar() == '@'); lexer.TextWindow.AdvanceChar(); // @ } Debug.Assert(lexer.TextWindow.PeekChar() == '"'); lexer.TextWindow.AdvanceChar(); // " ScanInterpolatedStringLiteralContents(interpolations); if (lexer.TextWindow.PeekChar() != '"') { Debug.Assert(IsAtEnd()); if (error == null) { int position = IsAtEnd(true) ? lexer.TextWindow.Position - 1 : lexer.TextWindow.Position; error = lexer.MakeError(position, 1, isVerbatim ? ErrorCode.ERR_UnterminatedStringLit : ErrorCode.ERR_NewlineInConst); } closeQuoteMissing = true; } else { // found the closing quote lexer.TextWindow.AdvanceChar(); // " closeQuoteMissing = false; } info.Kind = SyntaxKind.InterpolatedStringToken; }
public char NextUnicodeEscape(out char surrogateCharacter, out SyntaxDiagnosticInfo info) { return ScanUnicodeEscape(peek: false, surrogateCharacter: out surrogateCharacter, info: out info); }
private SyntaxToken Create(ref TokenInfo info, SyntaxListBuilder leading, SyntaxListBuilder trailing, SyntaxDiagnosticInfo[] errors) { Debug.Assert(info.Kind != SyntaxKind.IdentifierToken || info.StringValue != null); var leadingNode = SyntaxList.List(leading); var trailingNode = SyntaxList.List(trailing); SyntaxToken token; if (info.RequiresTextForXmlEntity) { token = SyntaxFactory.Token(leadingNode, info.Kind, info.Text, info.StringValue, trailingNode); } else { switch (info.Kind) { case SyntaxKind.IdentifierToken: token = SyntaxFactory.Identifier(info.ContextualKind, leadingNode, info.Text, info.StringValue, trailingNode); break; case SyntaxKind.NumericLiteralToken: switch (info.ValueKind) { case SpecialType.System_Int32: token = SyntaxFactory.Literal(leadingNode, info.Text, info.IntValue, trailingNode); break; case SpecialType.System_UInt32: token = SyntaxFactory.Literal(leadingNode, info.Text, info.UintValue, trailingNode); break; case SpecialType.System_Int64: token = SyntaxFactory.Literal(leadingNode, info.Text, info.LongValue, trailingNode); break; case SpecialType.System_UInt64: token = SyntaxFactory.Literal(leadingNode, info.Text, info.UlongValue, trailingNode); break; case SpecialType.System_Single: token = SyntaxFactory.Literal(leadingNode, info.Text, info.FloatValue, trailingNode); break; case SpecialType.System_Double: token = SyntaxFactory.Literal(leadingNode, info.Text, info.DoubleValue, trailingNode); break; case SpecialType.System_Decimal: token = SyntaxFactory.Literal(leadingNode, info.Text, info.DecimalValue, trailingNode); break; default: throw ExceptionUtilities.UnexpectedValue(info.ValueKind); } break; case SyntaxKind.InterpolatedStringToken: // we do not record a separate "value" for an interpolated string token, as it must be rescanned during parsing. token = SyntaxFactory.Literal(leadingNode, info.Text, info.Kind, info.Text, trailingNode); break; case SyntaxKind.StringLiteralToken: token = SyntaxFactory.Literal(leadingNode, info.Text, info.Kind, info.StringValue, trailingNode); break; case SyntaxKind.CharacterLiteralToken: token = SyntaxFactory.Literal(leadingNode, info.Text, info.CharValue, trailingNode); break; case SyntaxKind.XmlTextLiteralNewLineToken: token = SyntaxFactory.XmlTextNewLine(leadingNode, info.Text, info.StringValue, trailingNode); break; case SyntaxKind.XmlTextLiteralToken: token = SyntaxFactory.XmlTextLiteral(leadingNode, info.Text, info.StringValue, trailingNode); break; case SyntaxKind.XmlEntityLiteralToken: token = SyntaxFactory.XmlEntity(leadingNode, info.Text, info.StringValue, trailingNode); break; case SyntaxKind.EndOfDocumentationCommentToken: case SyntaxKind.EndOfFileToken: token = SyntaxFactory.Token(leadingNode, info.Kind, trailingNode); break; case SyntaxKind.None: token = SyntaxFactory.BadToken(leadingNode, info.Text, trailingNode); break; default: Debug.Assert(SyntaxFacts.IsPunctuationOrKeyword(info.Kind)); token = SyntaxFactory.Token(leadingNode, info.Kind, trailingNode); break; } } if (errors != null && (_options.DocumentationMode >= DocumentationMode.Diagnose || !InDocumentationComment)) { token = token.WithDiagnosticsGreen(errors); } return token; }
private ExpressionSyntax ParseInterpolatedStringToken() { // We don't want to make the scanner stateful (between tokens) if we can possibly avoid it. // The approach implemented here is // // (1) Scan the whole interpolated string literal as a single token. Now the statefulness of // the scanner (to match { }'s) is limited to its behavior while scanning a single token. // // (2) When the parser gets such a token, here, it spins up another scanner / parser on each of // the holes and builds a tree for the whole thing (resulting in an interpolated string nonterminal node). // // (3) The parser discards the original token and replaces it with this tree. (In other words, // it replaces one token with a different set of tokens that have already been parsed) // // (4) On an incremental change, we widen the invalidated region to include any enclosing interpolated // string nonterminal so that we never reuse tokens inside a changed interpolated string. // // This has the secondary advantage that it can reasonably be specified. // // The substitution will end up being invisible to external APIs and clients such as the IDE, as // they have no way to ask for the stream of tokens before parsing. // // Take special not of the handling of trivia. With one exception, what happens in a hole stays // in the hole. That means that the first token in a hole can have leading trivia, even though // it is not the first thing on a line. The single exception occurs when the hole is completely // empty of tokens, but contains some trivia. In that case we move the trivia (from the fake EOF // token inside the hole) to be leading trivia of the following literal part so that it doesn't // get dropped on the floor. // var originalToken = this.EatToken(); Debug.Assert(originalToken.Kind == SyntaxKind.InterpolatedStringToken); var originalText = originalToken.ValueText; var interpolations = ArrayBuilder <Lexer.Interpolation> .GetInstance(); SyntaxDiagnosticInfo error = null; using (var tempLexer = new Lexer(Text.SourceText.From(originalText), this.Options)) { // compute the positions of the interpolations in the original string literal var info = default(Lexer.TokenInfo); tempLexer.ScanISLTop(interpolations, ref info, ref error); } Debug.Assert(interpolations.Count != 0); var builder = this.pool.AllocateSeparated <InterpolatedStringInsertSyntax>(); try { SyntaxToken stringStart = null; SyntaxToken stringEnd = null; for (int i = 0; i < interpolations.Count; i++) { var interpolation = interpolations[i]; var first = i == 0; var last = i == (interpolations.Count - 1); if (first) { // compute stringStart var startText = originalText.Substring(0, interpolation.Start + 1); stringStart = MakeStringToken(startText, SyntaxKind.InterpolatedStringStartToken).WithLeadingTrivia(originalToken.GetLeadingTrivia()); Debug.Assert(stringStart.Kind == SyntaxKind.InterpolatedStringStartToken); } CSharpSyntaxNode additionalTrivia; var interpText = originalText.Substring(interpolation.Start + 1, interpolation.End - interpolation.Start - 1); using (var tempLexer = new Lexer(Text.SourceText.From(interpText), this.Options)) { using (var tempParser = new LanguageParser(tempLexer, null, null)) { var insert = tempParser.ParseInterpolatedStringInsert(); insert = tempParser.ConsumeUnexpectedTokens(insert); // In case the insert is empty, move the leading trivia from its EOF token to the following literal part. additionalTrivia = tempParser.CurrentToken.GetLeadingTrivia(); builder.Add(insert); } } if (last) { // compute stringEnd var endText = originalText.Substring(interpolation.End); stringEnd = MakeStringToken(endText, SyntaxKind.InterpolatedStringEndToken).WithLeadingTrivia(additionalTrivia).WithTrailingTrivia(originalToken.GetTrailingTrivia()); Debug.Assert(stringEnd.Kind == SyntaxKind.InterpolatedStringEndToken); } else { // add an interpolated string mid token for the following }...{ part var midText = originalText.Substring(interpolation.End, interpolations[i + 1].Start - interpolation.End + 1); var stringMid = MakeStringToken(midText, SyntaxKind.InterpolatedStringMidToken).WithLeadingTrivia(additionalTrivia); Debug.Assert(stringMid.Kind == SyntaxKind.InterpolatedStringMidToken); builder.AddSeparator(stringMid); } } interpolations.Free(); var result = SyntaxFactory.InterpolatedString(stringStart, builder.ToList(), stringEnd); if (error != null) { result = result.WithDiagnosticsGreen(new[] { error }); } return(result); } finally { this.pool.Free(builder); } }
private char ScanUnicodeEscape(bool peek, out char surrogateCharacter, out SyntaxDiagnosticInfo info) { surrogateCharacter = InvalidCharacter; info = null; int start = this.Position; char character = this.PeekChar(); Debug.Assert(character == '\\'); this.AdvanceChar(); character = this.PeekChar(); if (character == 'U') { uint uintChar = 0; this.AdvanceChar(); if (!SyntaxFacts.IsHexDigit(this.PeekChar())) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } else { for (int i = 0; i < 8; i++) { character = this.PeekChar(); if (!SyntaxFacts.IsHexDigit(character)) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } break; } uintChar = (uint)((uintChar << 4) + SyntaxFacts.HexValue(character)); this.AdvanceChar(); } if (uintChar > 0x0010FFFF) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } else { character = GetCharsFromUtf32(uintChar, out surrogateCharacter); } } } else { Debug.Assert(character == 'u' || character == 'x'); int intChar = 0; this.AdvanceChar(); if (!SyntaxFacts.IsHexDigit(this.PeekChar())) { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } else { for (int i = 0; i < 4; i++) { char ch2 = this.PeekChar(); if (!SyntaxFacts.IsHexDigit(ch2)) { if (character == 'u') { if (!peek) { info = CreateIllegalEscapeDiagnostic(start); } } break; } intChar = (intChar << 4) + SyntaxFacts.HexValue(ch2); this.AdvanceChar(); } character = (char)intChar; } } return character; }
private void TrySetUnrecoverableError(SyntaxDiagnosticInfo error) { // only need to record the first error we hit Error ??= error; }
/// <summary> /// Converts skippedSyntax node into tokens and adds these as trivia on the target token. /// Also adds the first error (in depth-first preorder) found in the skipped syntax tree to the target token. /// </summary> internal SyntaxToken AddSkippedSyntax(SyntaxToken target, CSharpSyntaxNode skippedSyntax, bool trailing) { var builder = new SyntaxListBuilder(4); // the error in we'll attach to the node SyntaxDiagnosticInfo diagnostic = null; // the position of the error within the skipedSyntax node full tree int diagnosticOffset = 0; int currentOffset = 0; foreach (var node in skippedSyntax.EnumerateNodes()) { SyntaxToken token = node as SyntaxToken; if (token != null) { builder.Add(token.GetLeadingTrivia()); if (token.Width > 0) { // separate trivia from the tokens SyntaxToken tk = token.WithLeadingTrivia(null).WithTrailingTrivia(null); // adjust relative offsets of diagnostics attached to the token: int leadingWidth = token.GetLeadingTriviaWidth(); if (leadingWidth > 0) { var tokenDiagnostics = tk.GetDiagnostics(); for (int i = 0; i < tokenDiagnostics.Length; i++) { var d = (SyntaxDiagnosticInfo)tokenDiagnostics[i]; tokenDiagnostics[i] = new SyntaxDiagnosticInfo(d.Offset - leadingWidth, d.Width, (ErrorCode)d.Code, d.Arguments); } } builder.Add(SyntaxFactory.SkippedTokensTrivia(tk)); } else { // do not create zero-width structured trivia, GetStructure doesn't work well for them var existing = (SyntaxDiagnosticInfo)token.GetDiagnostics().FirstOrDefault(); if (existing != null) { diagnostic = existing; diagnosticOffset = currentOffset; } } builder.Add(token.GetTrailingTrivia()); currentOffset += token.FullWidth; } else if (node.ContainsDiagnostics && diagnostic == null) { // only propagate the first error to reduce noise: var existing = (SyntaxDiagnosticInfo)node.GetDiagnostics().FirstOrDefault(); if (existing != null) { diagnostic = existing; diagnosticOffset = currentOffset; } } } int triviaWidth = currentOffset; var trivia = builder.ToListNode(); // total width of everything preceding the added trivia int triviaOffset; if (trailing) { var trailingTrivia = target.GetTrailingTrivia(); triviaOffset = target.FullWidth; //added trivia is full width (before addition) target = target.WithTrailingTrivia(SyntaxList.Concat(trailingTrivia, trivia)); } else { // Since we're adding triviaWidth before the token, we have to add that much to // the offset of each of its diagnostics. if (triviaWidth > 0) { var targetDiagnostics = target.GetDiagnostics(); for (int i = 0; i < targetDiagnostics.Length; i++) { var d = (SyntaxDiagnosticInfo)targetDiagnostics[i]; targetDiagnostics[i] = new SyntaxDiagnosticInfo(d.Offset + triviaWidth, d.Width, (ErrorCode)d.Code, d.Arguments); } } var leadingTrivia = target.GetLeadingTrivia(); target = target.WithLeadingTrivia(SyntaxList.Concat(trivia, leadingTrivia)); triviaOffset = 0; //added trivia is first, so offset is zero } if (diagnostic != null) { int newOffset = triviaOffset + diagnosticOffset + diagnostic.Offset; target = WithAdditionalDiagnostics(target, new SyntaxDiagnosticInfo(newOffset, diagnostic.Width, (ErrorCode)diagnostic.Code, diagnostic.Arguments) ); } return(target); }
private void ScanInterpolatedStringLiteralContents(ArrayBuilder <Interpolation> interpolations) { while (true) { if (IsAtEnd()) { // error: end of line before end of string return; } switch (lexer.TextWindow.PeekChar()) { case '"': if (isVerbatim && lexer.TextWindow.PeekChar(1) == '"') { lexer.TextWindow.AdvanceChar(); // " lexer.TextWindow.AdvanceChar(); // " continue; } // found the end of the string return; case '}': var pos = lexer.TextWindow.Position; lexer.TextWindow.AdvanceChar(); // } // ensure any } characters are doubled up if (lexer.TextWindow.PeekChar() == '}') { lexer.TextWindow.AdvanceChar(); // } } else if (error == null) { error = lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "}"); } continue; case '{': if (lexer.TextWindow.PeekChar(1) == '{') { lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } else { int openBracePosition = lexer.TextWindow.Position; lexer.TextWindow.AdvanceChar(); int colonPosition = 0; ScanInterpolatedStringLiteralHoleBalancedText('}', true, ref colonPosition); int closeBracePosition = lexer.TextWindow.Position; bool closeBraceMissing = false; if (lexer.TextWindow.PeekChar() == '}') { lexer.TextWindow.AdvanceChar(); } else { closeBraceMissing = true; if (error == null) { error = lexer.MakeError(openBracePosition - 1, 2, ErrorCode.ERR_UnclosedExpressionHole); } } interpolations?.Add(new Interpolation(openBracePosition, colonPosition, closeBracePosition, closeBraceMissing)); } continue; case '\\': if (isVerbatim) { goto default; } var escapeStart = lexer.TextWindow.Position; char c2; char ch = lexer.ScanEscapeSequence(out c2); if ((ch == '{' || ch == '}') && error == null) { error = lexer.MakeError(escapeStart, lexer.TextWindow.Position - escapeStart, ErrorCode.ERR_EscapedCurly, ch); } continue; default: // found some other character in the string portion lexer.TextWindow.AdvanceChar(); continue; } } }
internal void ScanInterpolatedStringLiteralTop(ArrayBuilder<Interpolation> interpolations, bool isVerbatim, ref TokenInfo info, ref SyntaxDiagnosticInfo error, out bool closeQuoteMissing) { var subScanner = new InterpolatedStringScanner(this, isVerbatim); subScanner.ScanInterpolatedStringLiteralTop(interpolations, ref info, out closeQuoteMissing); error = subScanner.error; info.Text = TextWindow.GetText(false); }
private void ScanInterpolatedStringLiteralContents(ArrayBuilder<Interpolation> interpolations) { while (true) { if (IsAtEnd()) { // error: end of line before end of string return; } switch (lexer.TextWindow.PeekChar()) { case '"': if (isVerbatim && lexer.TextWindow.PeekChar(1) == '"') { lexer.TextWindow.AdvanceChar(); // " lexer.TextWindow.AdvanceChar(); // " continue; } // found the end of the string return; case '}': var pos = lexer.TextWindow.Position; lexer.TextWindow.AdvanceChar(); // } // ensure any } characters are doubled up if (lexer.TextWindow.PeekChar() == '}') { lexer.TextWindow.AdvanceChar(); // } } else if (error == null) { error = lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "}"); } continue; case '{': if (lexer.TextWindow.PeekChar(1) == '{') { lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } else { int openBracePosition = lexer.TextWindow.Position; lexer.TextWindow.AdvanceChar(); int colonPosition = 0; ScanInterpolatedStringLiteralHoleBalancedText('}', true, ref colonPosition); int closeBracePosition = lexer.TextWindow.Position; bool closeBraceMissing = false; if (lexer.TextWindow.PeekChar() == '}') { lexer.TextWindow.AdvanceChar(); } else { closeBraceMissing = true; if (error == null) { error = lexer.MakeError(openBracePosition - 1, 2, ErrorCode.ERR_UnclosedExpressionHole); } } interpolations?.Add(new Interpolation(openBracePosition, colonPosition, closeBracePosition, closeBraceMissing)); } continue; case '\\': if (isVerbatim) { goto default; } var escapeStart = lexer.TextWindow.Position; char c2; char ch = lexer.ScanEscapeSequence(out c2); if ((ch == '{' || ch == '}') && error == null) { error = lexer.MakeError(escapeStart, lexer.TextWindow.Position - escapeStart, ErrorCode.ERR_EscapedCurly, ch); } continue; default: // found some other character in the string portion lexer.TextWindow.AdvanceChar(); continue; } } }
private void ScanInterpolatedStringLiteralContents(ArrayBuilder <Interpolation> interpolations) { while (true) { if (IsAtEnd()) { // error: end of line before end of string return; } switch (lexer.TextWindow.PeekChar()) { case '"' when RecoveringFromRunawayLexing(): // When recovering from mismatched delimiters, we consume the next // quote character as the close quote for the interpolated string. In // practice this gets us out of trouble in scenarios we've encountered. // See, for example, https://github.com/dotnet/roslyn/issues/44789 return; case '"': if (isVerbatim && lexer.TextWindow.PeekChar(1) == '"') { lexer.TextWindow.AdvanceChar(); // " lexer.TextWindow.AdvanceChar(); // " continue; } // found the end of the string return; case '}': var pos = lexer.TextWindow.Position; lexer.TextWindow.AdvanceChar(); // } // ensure any } characters are doubled up if (lexer.TextWindow.PeekChar() == '}') { lexer.TextWindow.AdvanceChar(); // } } else if (error == null) { error = lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "}"); } continue; case '{': if (lexer.TextWindow.PeekChar(1) == '{') { lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } else { int openBracePosition = lexer.TextWindow.Position; lexer.TextWindow.AdvanceChar(); int colonPosition = 0; ScanInterpolatedStringLiteralHoleBalancedText('}', true, ref colonPosition); int closeBracePosition = lexer.TextWindow.Position; bool closeBraceMissing = false; if (lexer.TextWindow.PeekChar() == '}') { lexer.TextWindow.AdvanceChar(); } else { closeBraceMissing = true; if (error == null) { error = lexer.MakeError(openBracePosition - 1, 2, ErrorCode.ERR_UnclosedExpressionHole); } } interpolations?.Add(new Interpolation(openBracePosition, colonPosition, closeBracePosition, closeBraceMissing)); } continue; case '\\': if (isVerbatim) { goto default; } var escapeStart = lexer.TextWindow.Position; char c2; char ch = lexer.ScanEscapeSequence(out c2); if ((ch == '{' || ch == '}') && error == null) { error = lexer.MakeError(escapeStart, lexer.TextWindow.Position - escapeStart, ErrorCode.ERR_EscapedCurly, ch); } continue; default: // found some other character in the string portion lexer.TextWindow.AdvanceChar(); continue; } } }
/// <summary> /// Scan past the hole inside an interpolated string literal, leaving the current character on the '}' (if any) /// </summary> private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool isHole, ref int colonPosition) { while (true) { if (IsAtEnd()) { // the caller will complain return; } char ch = lexer.TextWindow.PeekChar(); switch (ch) { case '#': // preprocessor directives not allowed. if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } lexer.TextWindow.AdvanceChar(); continue; case '$': if (lexer.TextWindow.PeekChar(1) == '"' || lexer.TextWindow.PeekChar(1) == '@' && lexer.TextWindow.PeekChar(2) == '"') { bool isVerbatimSubstring = lexer.TextWindow.PeekChar(1) == '@'; var interpolations = default(ArrayBuilder<Interpolation>); var info = default(TokenInfo); bool wasVerbatim = this.isVerbatim; bool wasAllowNewlines = this.allowNewlines; try { this.isVerbatim = isVerbatimSubstring; this.allowNewlines &= isVerbatim; bool closeQuoteMissing; ScanInterpolatedStringLiteralTop(interpolations, ref info, out closeQuoteMissing); } finally { this.isVerbatim = wasVerbatim; this.allowNewlines = wasAllowNewlines; } continue; } goto default; case ':': // the first colon not nested within matching delimiters is the start of the format string if (isHole) { Debug.Assert(colonPosition == 0); colonPosition = lexer.TextWindow.Position; ScanFormatSpecifier(); return; } goto default; case '}': case ')': case ']': if (ch == endingChar) { return; } if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } goto default; case '"': case '\'': // handle string or character literal inside an expression hole. ScanInterpolatedStringLiteralNestedString(ch); continue; case '@': if (lexer.TextWindow.PeekChar(1) == '"') { // check for verbatim string inside an expression hole. ScanInterpolatedStringLiteralNestedVerbatimString(); continue; } goto default; case '/': switch (lexer.TextWindow.PeekChar(1)) { case '/': if (isVerbatim && allowNewlines) { lexer.TextWindow.AdvanceChar(); // skip / lexer.TextWindow.AdvanceChar(); // skip / while (!IsAtEnd(false)) { lexer.TextWindow.AdvanceChar(); // skip // comment character } } else { // error: single-line comment not allowed in an interpolated string if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 2, ErrorCode.ERR_SingleLineCommentInExpressionHole); } lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } continue; case '*': // check for and scan /* comment */ ScanInterpolatedStringLiteralNestedComment(); continue; default: lexer.TextWindow.AdvanceChar(); continue; } case '{': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('{', '}'); continue; case '(': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('(', ')'); continue; case '[': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('[', ']'); continue; default: // part of code in the expression hole lexer.TextWindow.AdvanceChar(); continue; } } }
private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool isHole, ref int colonPosition) { while (true) { if (IsAtEnd()) { // the caller will complain return; } char ch = lexer.TextWindow.PeekChar(); switch (ch) { case '#': // preprocessor directives not allowed. if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } lexer.TextWindow.AdvanceChar(); continue; case '$': if (lexer.TextWindow.PeekChar(1) == '"' || lexer.TextWindow.PeekChar(1) == '@' && lexer.TextWindow.PeekChar(2) == '"') { bool isVerbatimSubstring = lexer.TextWindow.PeekChar(1) == '@'; var interpolations = (ArrayBuilder <Interpolation>)null; var info = default(TokenInfo); bool wasVerbatim = this.isVerbatim; bool wasAllowNewlines = this.allowNewlines; try { this.isVerbatim = isVerbatimSubstring; this.allowNewlines &= isVerbatim; bool closeQuoteMissing; ScanInterpolatedStringLiteralTop(interpolations, ref info, out closeQuoteMissing); } finally { this.isVerbatim = wasVerbatim; this.allowNewlines = wasAllowNewlines; } continue; } goto default; case ':': // the first colon not nested within matching delimiters is the start of the format string if (isHole) { Debug.Assert(colonPosition == 0); colonPosition = lexer.TextWindow.Position; ScanFormatSpecifier(); return; } goto default; case '}': case ')': case ']': if (ch == endingChar) { return; } if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } goto default; case '"' when RecoveringFromRunawayLexing(): // When recovering from mismatched delimiters, we consume the next // quote character as the close quote for the interpolated string. In // practice this gets us out of trouble in scenarios we've encountered. // See, for example, https://github.com/dotnet/roslyn/issues/44789 return; case '"': case '\'': // handle string or character literal inside an expression hole. ScanInterpolatedStringLiteralNestedString(); continue; case '@': if (lexer.TextWindow.PeekChar(1) == '"' && !RecoveringFromRunawayLexing()) { // check for verbatim string inside an expression hole. ScanInterpolatedStringLiteralNestedVerbatimString(); continue; } else if (lexer.TextWindow.PeekChar(1) == '$' && lexer.TextWindow.PeekChar(2) == '"') { lexer.CheckFeatureAvailability(MessageID.IDS_FeatureAltInterpolatedVerbatimStrings); var interpolations = (ArrayBuilder <Interpolation>)null; var info = default(TokenInfo); bool wasVerbatim = this.isVerbatim; bool wasAllowNewlines = this.allowNewlines; try { this.isVerbatim = true; this.allowNewlines = true; bool closeQuoteMissing; ScanInterpolatedStringLiteralTop(interpolations, ref info, out closeQuoteMissing); } finally { this.isVerbatim = wasVerbatim; this.allowNewlines = wasAllowNewlines; } continue; } goto default; case '/': switch (lexer.TextWindow.PeekChar(1)) { case '/': if (isVerbatim && allowNewlines) { lexer.TextWindow.AdvanceChar(); // skip / lexer.TextWindow.AdvanceChar(); // skip / while (!IsAtEnd(false)) { lexer.TextWindow.AdvanceChar(); // skip // comment character } } else { // error: single-line comment not allowed in an interpolated string if (error == null) { error = lexer.MakeError(lexer.TextWindow.Position, 2, ErrorCode.ERR_SingleLineCommentInExpressionHole); } lexer.TextWindow.AdvanceChar(); lexer.TextWindow.AdvanceChar(); } continue; case '*': // check for and scan /* comment */ ScanInterpolatedStringLiteralNestedComment(); continue; default: lexer.TextWindow.AdvanceChar(); continue; } case '{': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('{', '}'); continue; case '(': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('(', ')'); continue; case '[': // TODO: after the colon this has no special meaning. ScanInterpolatedStringLiteralHoleBracketed('[', ']'); continue; default: // part of code in the expression hole lexer.TextWindow.AdvanceChar(); continue; } } }