public Token ReadStringLiteral(int firstChar)
{
System.Diagnostics.Debug.Assert(firstChar == '\'' || firstChar == '"' || firstChar == '`');
var contents = new StringBuilder();
int lineTerminatorCount = 0;
int escapeSequenceCount = 0;
// In order to support tagged template literals properly, we need to capture the raw
// text, including escape sequences.
StringBuilder previousInputCapture = null;
if (firstChar == '`')
{
previousInputCapture = this.InputCaptureStringBuilder;
this.InputCaptureStringBuilder = new StringBuilder();
}
int c;
while (true)
{
c = ReadNextChar();
if (c == firstChar)
break;
if (c == -1)
throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Unexpected end of input in string literal.", this.lineNumber, this.Source.Path);
if (IsLineTerminator(c))
{
// Line terminators are only allowed in template literals.
if (firstChar != '`')
throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Unexpected line terminator in string literal.", this.lineNumber, this.Source.Path);
ReadLineTerminator(c);
contents.Append('\n');
}
else if (c == '\\')
{
// Escape sequence or line continuation.
c = ReadNextChar();
if (IsLineTerminator(c))
{
// Line continuation.
ReadLineTerminator(c);
// Keep track of the number of line terminators so the parser can compute
// line numbers correctly.
lineTerminatorCount++;
// Increment the internal line number so errors can be tracked properly.
this.lineNumber++;
this.columnNumber = 1;
}
else
{
// Escape sequence.
switch (c)
{
case 'b':
// Backspace.
contents.Append((char)0x08);
break;
case 'f':
// Form feed.
contents.Append((char)0x0C);
break;
case 'n':
// Line feed.
contents.Append((char)0x0A);
break;
case 'r':
// Carriage return.
contents.Append((char)0x0D);
break;
case 't':
// Horizontal tab.
contents.Append((char)0x09);
break;
case 'v':
// Vertical tab.
contents.Append((char)0x0B);
break;
case 'x':
// ASCII escape.
contents.Append(ReadHexEscapeSequence(2));
break;
case 'u':
// Unicode escape.
c = this.reader.Peek();
if (c == '{')
// ECMAScript 6 hex escape sequence.
contents.Append(ReadExtendedUnicodeSequence());
else
contents.Append(ReadHexEscapeSequence(4));
break;
case '0':
// Null character or octal escape sequence.
c = this.reader.Peek();
if (c >= '0' && c <= '9')
contents.Append(ReadOctalEscapeSequence(firstChar, 0));
else
contents.Append((char)0);
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
// Octal escape sequence.
contents.Append(ReadOctalEscapeSequence(firstChar, c - '0'));
break;
case '8':
case '9':
throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Invalid octal escape sequence.", this.lineNumber, this.Source.Path);
default:
contents.Append((char)c);
break;
}
escapeSequenceCount ++;
}
}
else if (c == '$' && firstChar == '`')
{
// This is a template literal substitution if the next character is '{'
if (this.reader.Peek() == '{')
{
// Yes, this is a substitution!
ReadNextChar();
break;
}
else
{
// Not a substitution.
contents.Append((char)c);
}
}
else
{
contents.Append((char)c);
}
}
// Template literals return a different type of token.
if (firstChar == '`')
{
var rawText = this.InputCaptureStringBuilder.ToString(0, this.InputCaptureStringBuilder.Length - (c == '$' ? 2 : 1));
this.InputCaptureStringBuilder = previousInputCapture;
if (this.InputCaptureStringBuilder != null)
this.InputCaptureStringBuilder.Append(previousInputCapture.ToString());
return new TemplateLiteralToken(contents.ToString(), rawText, substitutionFollows: c == '$');
}
// Return a regular string literal token.
return new StringLiteralToken(contents.ToString(), escapeSequenceCount, lineTerminatorCount);
}