in src/Bicep.Core/Parsing/Lexer.cs [653:779]
private TokenType ScanStringSegment(bool isAtStartOfString)
{
// to handle interpolation, strings are broken down into multiple segments, to detect the portions of string between the 'holes'.
// 'complete' string: a string with no holes (no interpolation), e.g. "'hello'"
// string 'left piece': the portion of an interpolated string up to the first hole, e.g. "'hello$"
// string 'middle piece': the portion of an interpolated string between two holes, e.g. "}hello${"
// string 'right piece': the portion of an interpolated string after the last hole, e.g. "}hello'"
while (true)
{
if (textWindow.IsAtEnd())
{
AddDiagnostic(b => b.UnterminatedString());
return isAtStartOfString ? TokenType.StringComplete : TokenType.StringRightPiece;
}
var nextChar = textWindow.Peek();
if (IsNewLine(nextChar))
{
// do not consume the new line character
AddDiagnostic(b => b.UnterminatedStringWithNewLine());
return isAtStartOfString ? TokenType.StringComplete : TokenType.StringRightPiece;
}
int escapeBeginPosition = textWindow.GetAbsolutePosition();
textWindow.Advance();
if (nextChar == '\'')
{
return isAtStartOfString ? TokenType.StringComplete : TokenType.StringRightPiece;
}
if (nextChar == '$' && !textWindow.IsAtEnd() && textWindow.Peek() == '{')
{
textWindow.Advance();
return isAtStartOfString ? TokenType.StringLeftPiece : TokenType.StringMiddlePiece;
}
if (nextChar != '\\')
{
continue;
}
// an escape sequence was started with \
if (textWindow.IsAtEnd())
{
// the escape was unterminated
AddDiagnostic(b => b.UnterminatedStringEscapeSequenceAtEof());
return isAtStartOfString ? TokenType.StringComplete : TokenType.StringRightPiece;
}
// the escape sequence has a char after the \
// consume it
nextChar = textWindow.Peek();
textWindow.Advance();
if (nextChar == 'u')
{
// unicode escape
if (textWindow.IsAtEnd())
{
// string was prematurely terminated
// reusing the first check in the loop body to produce the diagnostic
continue;
}
nextChar = textWindow.Peek();
if (nextChar != '{')
{
// \u must be followed by {, but it's not
AddDiagnostic(textWindow.GetSpanFromPosition(escapeBeginPosition), b => b.InvalidUnicodeEscape());
continue;
}
textWindow.Advance();
if (textWindow.IsAtEnd())
{
// string was prematurely terminated
// reusing the first check in the loop body to produce the diagnostic
continue;
}
string codePointText = ScanHexNumber(textWindow);
if (textWindow.IsAtEnd())
{
// string was prematurely terminated
// reusing the first check in the loop body to produce the diagnostic
continue;
}
if (string.IsNullOrEmpty(codePointText))
{
// we didn't get any hex digits
AddDiagnostic(textWindow.GetSpanFromPosition(escapeBeginPosition), b => b.InvalidUnicodeEscape());
continue;
}
nextChar = textWindow.Peek();
if (nextChar != '}')
{
// hex digits must be followed by }, but it's not
AddDiagnostic(textWindow.GetSpanFromPosition(escapeBeginPosition), b => b.InvalidUnicodeEscape());
continue;
}
textWindow.Advance();
if (!TryParseCodePoint(codePointText, out _))
{
// code point is not actually valid
AddDiagnostic(textWindow.GetSpanFromPosition(escapeBeginPosition), b => b.InvalidUnicodeEscape());
continue;
}
}
else
{
// not a unicode escape
if (SingleCharacterEscapes.ContainsKey(nextChar) == false)
{
// the span of the error is the incorrect escape sequence
AddDiagnostic(textWindow.GetLookbehindSpan(2), b => b.UnterminatedStringEscapeSequenceUnrecognized(CharacterEscapeSequences));
}
}
}
}