/// <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) { char ch = _lexer.TextWindow.PeekChar(); // Note: within a hole newlines are always allowed. The restriction on if newlines are allowed or not // is only within a text-portion of the interpolated string. if (IsAtEnd(allowNewline: true)) { // the caller will complain return; } switch (ch) { case '#': // preprocessor directives not allowed. TrySetUnrecoverableError(_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) == '"') { var discarded = default(TokenInfo); _lexer.ScanInterpolatedStringLiteral( isVerbatim: _lexer.TextWindow.PeekChar(1) == '@', ref discarded); 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; } TrySetUnrecoverableError(_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. var nestedStringPosition = _lexer.TextWindow.Position; // Note that this verbatim string may encounter an error (for example if it contains a // new line and we don't allow that). This should be reported to the user, but should // not put us into an unrecoverable position. We can clearly see that this was intended // to be a normal verbatim string, so we can continue on an attempt to understand the // outer interpolated string properly. var discarded = default(TokenInfo); var errorCode = _lexer.ScanVerbatimStringLiteral(ref discarded); if (errorCode is ErrorCode code) { TrySetRecoverableError(_lexer.MakeError(nestedStringPosition, width: 2, code)); } continue; } else if (_lexer.TextWindow.PeekChar(1) == '$' && _lexer.TextWindow.PeekChar(2) == '"') { var discarded = default(TokenInfo); _lexer.ScanInterpolatedStringLiteral(isVerbatim: true, ref discarded); continue; } goto default; case '/': switch (_lexer.TextWindow.PeekChar(1)) { case '/': _lexer.ScanToEndOfLine(); continue; case '*': _lexer.ScanMultiLineComment(out _); 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 ScanInterpolatedStringLiteralNestedVerbatimString() { var discarded = default(TokenInfo); lexer.ScanVerbatimStringLiteral(ref discarded, allowNewlines: allowNewlines); }
/// <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) { char ch = _lexer.TextWindow.PeekChar(); // See if we ran into a disallowed new line. If so, this is recoverable, so just skip past it but // give a good message about the issue. This will prevent a lot of cascading issues with the // remainder of the interpolated string that comes on the following lines. var allowNewLines = _isVerbatim && _allowNewlines; if (!allowNewLines && SyntaxFacts.IsNewLine(ch)) { TrySetRecoverableError(_lexer.MakeError(_lexer.TextWindow.Position, width: 0, ErrorCode.ERR_NewlinesAreNotAllowedInsideANonVerbatimInterpolatedString)); } if (IsAtEnd(allowNewline: true)) { // the caller will complain return; } switch (ch) { case '#': // preprocessor directives not allowed. TrySetUnrecoverableError(_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 = _isVerbatim; bool wasAllowNewlines = _allowNewlines; try { _isVerbatim = isVerbatimSubstring; _allowNewlines &= _isVerbatim; ScanInterpolatedStringLiteralTop(interpolations, ref info, closeQuoteMissing: out _); } finally { _isVerbatim = wasVerbatim; _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; } TrySetUnrecoverableError(_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. var nestedStringPosition = _lexer.TextWindow.Position; // Note that this verbatim string may encounter an error (for example if it contains a // new line and we don't allow that). This should be reported to the user, but should // not put us into an unrecoverable position. We can clearly see that this was intended // to be a normal verbatim string, so we can continue on an attempt to understand the // outer interpolated string properly. var discarded = default(TokenInfo); var errorCode = _lexer.ScanVerbatimStringLiteral(ref discarded, _allowNewlines); if (errorCode is ErrorCode code) { TrySetRecoverableError(_lexer.MakeError(nestedStringPosition, width: 2, code)); } 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 = _isVerbatim; bool wasAllowNewlines = _allowNewlines; try { _isVerbatim = true; _allowNewlines = true; ScanInterpolatedStringLiteralTop(interpolations, ref info, closeQuoteMissing: out _); } finally { _isVerbatim = wasVerbatim; _allowNewlines = wasAllowNewlines; } continue; } goto default; case '/': switch (_lexer.TextWindow.PeekChar(1)) { case '/': if (!_isVerbatim || !_allowNewlines) { // error: single-line comment not allowed in an interpolated string. // report the error but keep going for good error recovery. TrySetRecoverableError(_lexer.MakeError(_lexer.TextWindow.Position, 2, ErrorCode.ERR_SingleLineCommentInExpressionHole)); } _lexer.TextWindow.AdvanceChar(); // skip / _lexer.TextWindow.AdvanceChar(); // skip / while (!IsAtEnd(allowNewline: false)) { _lexer.TextWindow.AdvanceChar(); // skip // comment character } 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; } } }