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); } 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; } if (textWindow.IsAtEnd()) { AddDiagnostic(b => b.UnterminatedStringEscapeSequenceAtEof()); return(isAtStartOfString ? TokenType.StringComplete : TokenType.StringRightPiece); } nextChar = textWindow.Peek(); textWindow.Advance(); if (CharacterEscapes.ContainsKey(nextChar) == false) { // the span of the error is the incorrect escape sequence AddDiagnostic(textWindow.GetLookbehindSpan(2), b => b.UnterminatedStringEscapeSequenceUnrecognized(CharacterEscapeSequences)); } } }
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)); } } } }