in src/Bicep.Core/Parsing/Lexer.cs [855:1067]
private TokenType ScanToken()
{
if (textWindow.IsAtEnd())
{
return TokenType.EndOfFile;
}
var nextChar = textWindow.Peek();
textWindow.Advance();
switch (nextChar)
{
case '{':
if (templateStack.Any())
{
// if we're inside a string interpolation hole, and we find an object open brace,
// push it to the stack, so that we can match it up against an object close brace.
// this allows us to determine whether we're terminating an object or closing an interpolation hole.
templateStack.Push(TokenType.LeftBrace);
}
return TokenType.LeftBrace;
case '}':
if (templateStack.Any())
{
var prevTemplateToken = templateStack.Peek();
if (prevTemplateToken != TokenType.LeftBrace)
{
var stringToken = ScanStringSegment(false);
if (stringToken == TokenType.StringRightPiece)
{
templateStack.Pop();
}
return stringToken;
}
}
return TokenType.RightBrace;
case '(':
return TokenType.LeftParen;
case ')':
return TokenType.RightParen;
case '[':
return TokenType.LeftSquare;
case ']':
return TokenType.RightSquare;
case '@':
return TokenType.At;
case ',':
return TokenType.Comma;
case '.':
switch (textWindow.Peek(), textWindow.Peek(1))
{
case ('.', '.'):
textWindow.Advance(2);
return TokenType.Ellipsis;
}
return TokenType.Dot;
case '?':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case '?':
textWindow.Advance();
return TokenType.DoubleQuestion;
}
}
return TokenType.Question;
case ':':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case ':':
textWindow.Advance();
return TokenType.DoubleColon;
}
}
return TokenType.Colon;
case ';':
return TokenType.Semicolon;
case '+':
return TokenType.Plus;
case '-':
return TokenType.Minus;
case '%':
return TokenType.Modulo;
case '*':
return TokenType.Asterisk;
case '/':
return TokenType.Slash;
case '^':
return TokenType.Hat;
case '!':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case '=':
textWindow.Advance();
return TokenType.NotEquals;
case '~':
textWindow.Advance();
return TokenType.NotEqualsInsensitive;
}
}
return TokenType.Exclamation;
case '<':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case '=':
textWindow.Advance();
return TokenType.LessThanOrEqual;
}
}
return TokenType.LeftChevron;
case '>':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case '=':
textWindow.Advance();
return TokenType.GreaterThanOrEqual;
}
}
return TokenType.RightChevron;
case '=':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case '=':
textWindow.Advance();
return TokenType.Equals;
case '~':
textWindow.Advance();
return TokenType.EqualsInsensitive;
case '>':
textWindow.Advance();
return TokenType.Arrow;
}
}
return TokenType.Assignment;
case '&':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case '&':
textWindow.Advance();
return TokenType.LogicalAnd;
}
}
return TokenType.Unrecognized;
case '|':
if (!textWindow.IsAtEnd())
{
switch (textWindow.Peek())
{
case '|':
textWindow.Advance();
return TokenType.LogicalOr;
}
}
return TokenType.Pipe;
case '\'':
// "'''" means we're starting a multiline string.
if (textWindow.Peek(0) == '\'' && textWindow.Peek(1) == '\'')
{
textWindow.Advance(2);
return ScanMultilineString();
}
var token = ScanStringSegment(true);
if (token == TokenType.StringLeftPiece)
{
// if we're beginning a string interpolation statement, we need to keep track of it
templateStack.Push(token);
}
return token;
case '\n':
case '\r':
if (templateStack.Any())
{
// need to re-check the newline token on next pass
textWindow.Rewind();
// do not consume the new line character
// TODO: figure out a way to avoid returning this multiple times for nested interpolation
AddDiagnostic(b => b.UnterminatedStringWithNewLine());
templateStack.Clear();
return TokenType.StringRightPiece;
}
this.ScanNewLine();
return TokenType.NewLine;
default:
if (IsDigit(nextChar))
{
this.ScanNumber();
return TokenType.Integer;
}
if (IsIdentifierStart(nextChar))
{
return this.ScanIdentifier();
}
return TokenType.Unrecognized;
}
}