Spaces:
Running
Running
; | |
Object.defineProperty(exports, '__esModule', { | |
value: true, | |
}); | |
exports.Lexer = void 0; | |
exports.isPunctuatorTokenKind = isPunctuatorTokenKind; | |
var _syntaxError = require('../error/syntaxError.js'); | |
var _ast = require('./ast.js'); | |
var _blockString = require('./blockString.js'); | |
var _characterClasses = require('./characterClasses.js'); | |
var _tokenKind = require('./tokenKind.js'); | |
/** | |
* Given a Source object, creates a Lexer for that source. | |
* A Lexer is a stateful stream generator in that every time | |
* it is advanced, it returns the next token in the Source. Assuming the | |
* source lexes, the final Token emitted by the lexer will be of kind | |
* EOF, after which the lexer will repeatedly return the same EOF token | |
* whenever called. | |
*/ | |
class Lexer { | |
/** | |
* The previously focused non-ignored token. | |
*/ | |
/** | |
* The currently focused non-ignored token. | |
*/ | |
/** | |
* The (1-indexed) line containing the current token. | |
*/ | |
/** | |
* The character offset at which the current line begins. | |
*/ | |
constructor(source) { | |
const startOfFileToken = new _ast.Token( | |
_tokenKind.TokenKind.SOF, | |
0, | |
0, | |
0, | |
0, | |
); | |
this.source = source; | |
this.lastToken = startOfFileToken; | |
this.token = startOfFileToken; | |
this.line = 1; | |
this.lineStart = 0; | |
} | |
get [Symbol.toStringTag]() { | |
return 'Lexer'; | |
} | |
/** | |
* Advances the token stream to the next non-ignored token. | |
*/ | |
advance() { | |
this.lastToken = this.token; | |
const token = (this.token = this.lookahead()); | |
return token; | |
} | |
/** | |
* Looks ahead and returns the next non-ignored token, but does not change | |
* the state of Lexer. | |
*/ | |
lookahead() { | |
let token = this.token; | |
if (token.kind !== _tokenKind.TokenKind.EOF) { | |
do { | |
if (token.next) { | |
token = token.next; | |
} else { | |
// Read the next token and form a link in the token linked-list. | |
const nextToken = readNextToken(this, token.end); // @ts-expect-error next is only mutable during parsing. | |
token.next = nextToken; // @ts-expect-error prev is only mutable during parsing. | |
nextToken.prev = token; | |
token = nextToken; | |
} | |
} while (token.kind === _tokenKind.TokenKind.COMMENT); | |
} | |
return token; | |
} | |
} | |
/** | |
* @internal | |
*/ | |
exports.Lexer = Lexer; | |
function isPunctuatorTokenKind(kind) { | |
return ( | |
kind === _tokenKind.TokenKind.BANG || | |
kind === _tokenKind.TokenKind.DOLLAR || | |
kind === _tokenKind.TokenKind.AMP || | |
kind === _tokenKind.TokenKind.PAREN_L || | |
kind === _tokenKind.TokenKind.PAREN_R || | |
kind === _tokenKind.TokenKind.SPREAD || | |
kind === _tokenKind.TokenKind.COLON || | |
kind === _tokenKind.TokenKind.EQUALS || | |
kind === _tokenKind.TokenKind.AT || | |
kind === _tokenKind.TokenKind.BRACKET_L || | |
kind === _tokenKind.TokenKind.BRACKET_R || | |
kind === _tokenKind.TokenKind.BRACE_L || | |
kind === _tokenKind.TokenKind.PIPE || | |
kind === _tokenKind.TokenKind.BRACE_R | |
); | |
} | |
/** | |
* A Unicode scalar value is any Unicode code point except surrogate code | |
* points. In other words, the inclusive ranges of values 0x0000 to 0xD7FF and | |
* 0xE000 to 0x10FFFF. | |
* | |
* SourceCharacter :: | |
* - "Any Unicode scalar value" | |
*/ | |
function isUnicodeScalarValue(code) { | |
return ( | |
(code >= 0x0000 && code <= 0xd7ff) || (code >= 0xe000 && code <= 0x10ffff) | |
); | |
} | |
/** | |
* The GraphQL specification defines source text as a sequence of unicode scalar | |
* values (which Unicode defines to exclude surrogate code points). However | |
* JavaScript defines strings as a sequence of UTF-16 code units which may | |
* include surrogates. A surrogate pair is a valid source character as it | |
* encodes a supplementary code point (above U+FFFF), but unpaired surrogate | |
* code points are not valid source characters. | |
*/ | |
function isSupplementaryCodePoint(body, location) { | |
return ( | |
isLeadingSurrogate(body.charCodeAt(location)) && | |
isTrailingSurrogate(body.charCodeAt(location + 1)) | |
); | |
} | |
function isLeadingSurrogate(code) { | |
return code >= 0xd800 && code <= 0xdbff; | |
} | |
function isTrailingSurrogate(code) { | |
return code >= 0xdc00 && code <= 0xdfff; | |
} | |
/** | |
* Prints the code point (or end of file reference) at a given location in a | |
* source for use in error messages. | |
* | |
* Printable ASCII is printed quoted, while other points are printed in Unicode | |
* code point form (ie. U+1234). | |
*/ | |
function printCodePointAt(lexer, location) { | |
const code = lexer.source.body.codePointAt(location); | |
if (code === undefined) { | |
return _tokenKind.TokenKind.EOF; | |
} else if (code >= 0x0020 && code <= 0x007e) { | |
// Printable ASCII | |
const char = String.fromCodePoint(code); | |
return char === '"' ? "'\"'" : `"${char}"`; | |
} // Unicode code point | |
return 'U+' + code.toString(16).toUpperCase().padStart(4, '0'); | |
} | |
/** | |
* Create a token with line and column location information. | |
*/ | |
function createToken(lexer, kind, start, end, value) { | |
const line = lexer.line; | |
const col = 1 + start - lexer.lineStart; | |
return new _ast.Token(kind, start, end, line, col, value); | |
} | |
/** | |
* Gets the next token from the source starting at the given position. | |
* | |
* This skips over whitespace until it finds the next lexable token, then lexes | |
* punctuators immediately or calls the appropriate helper function for more | |
* complicated tokens. | |
*/ | |
function readNextToken(lexer, start) { | |
const body = lexer.source.body; | |
const bodyLength = body.length; | |
let position = start; | |
while (position < bodyLength) { | |
const code = body.charCodeAt(position); // SourceCharacter | |
switch (code) { | |
// Ignored :: | |
// - UnicodeBOM | |
// - WhiteSpace | |
// - LineTerminator | |
// - Comment | |
// - Comma | |
// | |
// UnicodeBOM :: "Byte Order Mark (U+FEFF)" | |
// | |
// WhiteSpace :: | |
// - "Horizontal Tab (U+0009)" | |
// - "Space (U+0020)" | |
// | |
// Comma :: , | |
case 0xfeff: // <BOM> | |
case 0x0009: // \t | |
case 0x0020: // <space> | |
case 0x002c: | |
// , | |
++position; | |
continue; | |
// LineTerminator :: | |
// - "New Line (U+000A)" | |
// - "Carriage Return (U+000D)" [lookahead != "New Line (U+000A)"] | |
// - "Carriage Return (U+000D)" "New Line (U+000A)" | |
case 0x000a: | |
// \n | |
++position; | |
++lexer.line; | |
lexer.lineStart = position; | |
continue; | |
case 0x000d: | |
// \r | |
if (body.charCodeAt(position + 1) === 0x000a) { | |
position += 2; | |
} else { | |
++position; | |
} | |
++lexer.line; | |
lexer.lineStart = position; | |
continue; | |
// Comment | |
case 0x0023: | |
// # | |
return readComment(lexer, position); | |
// Token :: | |
// - Punctuator | |
// - Name | |
// - IntValue | |
// - FloatValue | |
// - StringValue | |
// | |
// Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | } | |
case 0x0021: | |
// ! | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.BANG, | |
position, | |
position + 1, | |
); | |
case 0x0024: | |
// $ | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.DOLLAR, | |
position, | |
position + 1, | |
); | |
case 0x0026: | |
// & | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.AMP, | |
position, | |
position + 1, | |
); | |
case 0x0028: | |
// ( | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.PAREN_L, | |
position, | |
position + 1, | |
); | |
case 0x0029: | |
// ) | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.PAREN_R, | |
position, | |
position + 1, | |
); | |
case 0x002e: | |
// . | |
if ( | |
body.charCodeAt(position + 1) === 0x002e && | |
body.charCodeAt(position + 2) === 0x002e | |
) { | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.SPREAD, | |
position, | |
position + 3, | |
); | |
} | |
break; | |
case 0x003a: | |
// : | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.COLON, | |
position, | |
position + 1, | |
); | |
case 0x003d: | |
// = | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.EQUALS, | |
position, | |
position + 1, | |
); | |
case 0x0040: | |
// @ | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.AT, | |
position, | |
position + 1, | |
); | |
case 0x005b: | |
// [ | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.BRACKET_L, | |
position, | |
position + 1, | |
); | |
case 0x005d: | |
// ] | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.BRACKET_R, | |
position, | |
position + 1, | |
); | |
case 0x007b: | |
// { | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.BRACE_L, | |
position, | |
position + 1, | |
); | |
case 0x007c: | |
// | | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.PIPE, | |
position, | |
position + 1, | |
); | |
case 0x007d: | |
// } | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.BRACE_R, | |
position, | |
position + 1, | |
); | |
// StringValue | |
case 0x0022: | |
// " | |
if ( | |
body.charCodeAt(position + 1) === 0x0022 && | |
body.charCodeAt(position + 2) === 0x0022 | |
) { | |
return readBlockString(lexer, position); | |
} | |
return readString(lexer, position); | |
} // IntValue | FloatValue (Digit | -) | |
if ((0, _characterClasses.isDigit)(code) || code === 0x002d) { | |
return readNumber(lexer, position, code); | |
} // Name | |
if ((0, _characterClasses.isNameStart)(code)) { | |
return readName(lexer, position); | |
} | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
code === 0x0027 | |
? 'Unexpected single quote character (\'), did you mean to use a double quote (")?' | |
: isUnicodeScalarValue(code) || isSupplementaryCodePoint(body, position) | |
? `Unexpected character: ${printCodePointAt(lexer, position)}.` | |
: `Invalid character: ${printCodePointAt(lexer, position)}.`, | |
); | |
} | |
return createToken(lexer, _tokenKind.TokenKind.EOF, bodyLength, bodyLength); | |
} | |
/** | |
* Reads a comment token from the source file. | |
* | |
* ``` | |
* Comment :: # CommentChar* [lookahead != CommentChar] | |
* | |
* CommentChar :: SourceCharacter but not LineTerminator | |
* ``` | |
*/ | |
function readComment(lexer, start) { | |
const body = lexer.source.body; | |
const bodyLength = body.length; | |
let position = start + 1; | |
while (position < bodyLength) { | |
const code = body.charCodeAt(position); // LineTerminator (\n | \r) | |
if (code === 0x000a || code === 0x000d) { | |
break; | |
} // SourceCharacter | |
if (isUnicodeScalarValue(code)) { | |
++position; | |
} else if (isSupplementaryCodePoint(body, position)) { | |
position += 2; | |
} else { | |
break; | |
} | |
} | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.COMMENT, | |
start, | |
position, | |
body.slice(start + 1, position), | |
); | |
} | |
/** | |
* Reads a number token from the source file, either a FloatValue or an IntValue | |
* depending on whether a FractionalPart or ExponentPart is encountered. | |
* | |
* ``` | |
* IntValue :: IntegerPart [lookahead != {Digit, `.`, NameStart}] | |
* | |
* IntegerPart :: | |
* - NegativeSign? 0 | |
* - NegativeSign? NonZeroDigit Digit* | |
* | |
* NegativeSign :: - | |
* | |
* NonZeroDigit :: Digit but not `0` | |
* | |
* FloatValue :: | |
* - IntegerPart FractionalPart ExponentPart [lookahead != {Digit, `.`, NameStart}] | |
* - IntegerPart FractionalPart [lookahead != {Digit, `.`, NameStart}] | |
* - IntegerPart ExponentPart [lookahead != {Digit, `.`, NameStart}] | |
* | |
* FractionalPart :: . Digit+ | |
* | |
* ExponentPart :: ExponentIndicator Sign? Digit+ | |
* | |
* ExponentIndicator :: one of `e` `E` | |
* | |
* Sign :: one of + - | |
* ``` | |
*/ | |
function readNumber(lexer, start, firstCode) { | |
const body = lexer.source.body; | |
let position = start; | |
let code = firstCode; | |
let isFloat = false; // NegativeSign (-) | |
if (code === 0x002d) { | |
code = body.charCodeAt(++position); | |
} // Zero (0) | |
if (code === 0x0030) { | |
code = body.charCodeAt(++position); | |
if ((0, _characterClasses.isDigit)(code)) { | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
`Invalid number, unexpected digit after 0: ${printCodePointAt( | |
lexer, | |
position, | |
)}.`, | |
); | |
} | |
} else { | |
position = readDigits(lexer, position, code); | |
code = body.charCodeAt(position); | |
} // Full stop (.) | |
if (code === 0x002e) { | |
isFloat = true; | |
code = body.charCodeAt(++position); | |
position = readDigits(lexer, position, code); | |
code = body.charCodeAt(position); | |
} // E e | |
if (code === 0x0045 || code === 0x0065) { | |
isFloat = true; | |
code = body.charCodeAt(++position); // + - | |
if (code === 0x002b || code === 0x002d) { | |
code = body.charCodeAt(++position); | |
} | |
position = readDigits(lexer, position, code); | |
code = body.charCodeAt(position); | |
} // Numbers cannot be followed by . or NameStart | |
if (code === 0x002e || (0, _characterClasses.isNameStart)(code)) { | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
`Invalid number, expected digit but got: ${printCodePointAt( | |
lexer, | |
position, | |
)}.`, | |
); | |
} | |
return createToken( | |
lexer, | |
isFloat ? _tokenKind.TokenKind.FLOAT : _tokenKind.TokenKind.INT, | |
start, | |
position, | |
body.slice(start, position), | |
); | |
} | |
/** | |
* Returns the new position in the source after reading one or more digits. | |
*/ | |
function readDigits(lexer, start, firstCode) { | |
if (!(0, _characterClasses.isDigit)(firstCode)) { | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
start, | |
`Invalid number, expected digit but got: ${printCodePointAt( | |
lexer, | |
start, | |
)}.`, | |
); | |
} | |
const body = lexer.source.body; | |
let position = start + 1; // +1 to skip first firstCode | |
while ((0, _characterClasses.isDigit)(body.charCodeAt(position))) { | |
++position; | |
} | |
return position; | |
} | |
/** | |
* Reads a single-quote string token from the source file. | |
* | |
* ``` | |
* StringValue :: | |
* - `""` [lookahead != `"`] | |
* - `"` StringCharacter+ `"` | |
* | |
* StringCharacter :: | |
* - SourceCharacter but not `"` or `\` or LineTerminator | |
* - `\u` EscapedUnicode | |
* - `\` EscapedCharacter | |
* | |
* EscapedUnicode :: | |
* - `{` HexDigit+ `}` | |
* - HexDigit HexDigit HexDigit HexDigit | |
* | |
* EscapedCharacter :: one of `"` `\` `/` `b` `f` `n` `r` `t` | |
* ``` | |
*/ | |
function readString(lexer, start) { | |
const body = lexer.source.body; | |
const bodyLength = body.length; | |
let position = start + 1; | |
let chunkStart = position; | |
let value = ''; | |
while (position < bodyLength) { | |
const code = body.charCodeAt(position); // Closing Quote (") | |
if (code === 0x0022) { | |
value += body.slice(chunkStart, position); | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.STRING, | |
start, | |
position + 1, | |
value, | |
); | |
} // Escape Sequence (\) | |
if (code === 0x005c) { | |
value += body.slice(chunkStart, position); | |
const escape = | |
body.charCodeAt(position + 1) === 0x0075 // u | |
? body.charCodeAt(position + 2) === 0x007b // { | |
? readEscapedUnicodeVariableWidth(lexer, position) | |
: readEscapedUnicodeFixedWidth(lexer, position) | |
: readEscapedCharacter(lexer, position); | |
value += escape.value; | |
position += escape.size; | |
chunkStart = position; | |
continue; | |
} // LineTerminator (\n | \r) | |
if (code === 0x000a || code === 0x000d) { | |
break; | |
} // SourceCharacter | |
if (isUnicodeScalarValue(code)) { | |
++position; | |
} else if (isSupplementaryCodePoint(body, position)) { | |
position += 2; | |
} else { | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
`Invalid character within String: ${printCodePointAt( | |
lexer, | |
position, | |
)}.`, | |
); | |
} | |
} | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
'Unterminated string.', | |
); | |
} // The string value and lexed size of an escape sequence. | |
function readEscapedUnicodeVariableWidth(lexer, position) { | |
const body = lexer.source.body; | |
let point = 0; | |
let size = 3; // Cannot be larger than 12 chars (\u{00000000}). | |
while (size < 12) { | |
const code = body.charCodeAt(position + size++); // Closing Brace (}) | |
if (code === 0x007d) { | |
// Must be at least 5 chars (\u{0}) and encode a Unicode scalar value. | |
if (size < 5 || !isUnicodeScalarValue(point)) { | |
break; | |
} | |
return { | |
value: String.fromCodePoint(point), | |
size, | |
}; | |
} // Append this hex digit to the code point. | |
point = (point << 4) | readHexDigit(code); | |
if (point < 0) { | |
break; | |
} | |
} | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
`Invalid Unicode escape sequence: "${body.slice( | |
position, | |
position + size, | |
)}".`, | |
); | |
} | |
function readEscapedUnicodeFixedWidth(lexer, position) { | |
const body = lexer.source.body; | |
const code = read16BitHexCode(body, position + 2); | |
if (isUnicodeScalarValue(code)) { | |
return { | |
value: String.fromCodePoint(code), | |
size: 6, | |
}; | |
} // GraphQL allows JSON-style surrogate pair escape sequences, but only when | |
// a valid pair is formed. | |
if (isLeadingSurrogate(code)) { | |
// \u | |
if ( | |
body.charCodeAt(position + 6) === 0x005c && | |
body.charCodeAt(position + 7) === 0x0075 | |
) { | |
const trailingCode = read16BitHexCode(body, position + 8); | |
if (isTrailingSurrogate(trailingCode)) { | |
// JavaScript defines strings as a sequence of UTF-16 code units and | |
// encodes Unicode code points above U+FFFF using a surrogate pair of | |
// code units. Since this is a surrogate pair escape sequence, just | |
// include both codes into the JavaScript string value. Had JavaScript | |
// not been internally based on UTF-16, then this surrogate pair would | |
// be decoded to retrieve the supplementary code point. | |
return { | |
value: String.fromCodePoint(code, trailingCode), | |
size: 12, | |
}; | |
} | |
} | |
} | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
`Invalid Unicode escape sequence: "${body.slice(position, position + 6)}".`, | |
); | |
} | |
/** | |
* Reads four hexadecimal characters and returns the positive integer that 16bit | |
* hexadecimal string represents. For example, "000f" will return 15, and "dead" | |
* will return 57005. | |
* | |
* Returns a negative number if any char was not a valid hexadecimal digit. | |
*/ | |
function read16BitHexCode(body, position) { | |
// readHexDigit() returns -1 on error. ORing a negative value with any other | |
// value always produces a negative value. | |
return ( | |
(readHexDigit(body.charCodeAt(position)) << 12) | | |
(readHexDigit(body.charCodeAt(position + 1)) << 8) | | |
(readHexDigit(body.charCodeAt(position + 2)) << 4) | | |
readHexDigit(body.charCodeAt(position + 3)) | |
); | |
} | |
/** | |
* Reads a hexadecimal character and returns its positive integer value (0-15). | |
* | |
* '0' becomes 0, '9' becomes 9 | |
* 'A' becomes 10, 'F' becomes 15 | |
* 'a' becomes 10, 'f' becomes 15 | |
* | |
* Returns -1 if the provided character code was not a valid hexadecimal digit. | |
* | |
* HexDigit :: one of | |
* - `0` `1` `2` `3` `4` `5` `6` `7` `8` `9` | |
* - `A` `B` `C` `D` `E` `F` | |
* - `a` `b` `c` `d` `e` `f` | |
*/ | |
function readHexDigit(code) { | |
return code >= 0x0030 && code <= 0x0039 // 0-9 | |
? code - 0x0030 | |
: code >= 0x0041 && code <= 0x0046 // A-F | |
? code - 0x0037 | |
: code >= 0x0061 && code <= 0x0066 // a-f | |
? code - 0x0057 | |
: -1; | |
} | |
/** | |
* | Escaped Character | Code Point | Character Name | | |
* | ----------------- | ---------- | ---------------------------- | | |
* | `"` | U+0022 | double quote | | |
* | `\` | U+005C | reverse solidus (back slash) | | |
* | `/` | U+002F | solidus (forward slash) | | |
* | `b` | U+0008 | backspace | | |
* | `f` | U+000C | form feed | | |
* | `n` | U+000A | line feed (new line) | | |
* | `r` | U+000D | carriage return | | |
* | `t` | U+0009 | horizontal tab | | |
*/ | |
function readEscapedCharacter(lexer, position) { | |
const body = lexer.source.body; | |
const code = body.charCodeAt(position + 1); | |
switch (code) { | |
case 0x0022: | |
// " | |
return { | |
value: '\u0022', | |
size: 2, | |
}; | |
case 0x005c: | |
// \ | |
return { | |
value: '\u005c', | |
size: 2, | |
}; | |
case 0x002f: | |
// / | |
return { | |
value: '\u002f', | |
size: 2, | |
}; | |
case 0x0062: | |
// b | |
return { | |
value: '\u0008', | |
size: 2, | |
}; | |
case 0x0066: | |
// f | |
return { | |
value: '\u000c', | |
size: 2, | |
}; | |
case 0x006e: | |
// n | |
return { | |
value: '\u000a', | |
size: 2, | |
}; | |
case 0x0072: | |
// r | |
return { | |
value: '\u000d', | |
size: 2, | |
}; | |
case 0x0074: | |
// t | |
return { | |
value: '\u0009', | |
size: 2, | |
}; | |
} | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
`Invalid character escape sequence: "${body.slice( | |
position, | |
position + 2, | |
)}".`, | |
); | |
} | |
/** | |
* Reads a block string token from the source file. | |
* | |
* ``` | |
* StringValue :: | |
* - `"""` BlockStringCharacter* `"""` | |
* | |
* BlockStringCharacter :: | |
* - SourceCharacter but not `"""` or `\"""` | |
* - `\"""` | |
* ``` | |
*/ | |
function readBlockString(lexer, start) { | |
const body = lexer.source.body; | |
const bodyLength = body.length; | |
let lineStart = lexer.lineStart; | |
let position = start + 3; | |
let chunkStart = position; | |
let currentLine = ''; | |
const blockLines = []; | |
while (position < bodyLength) { | |
const code = body.charCodeAt(position); // Closing Triple-Quote (""") | |
if ( | |
code === 0x0022 && | |
body.charCodeAt(position + 1) === 0x0022 && | |
body.charCodeAt(position + 2) === 0x0022 | |
) { | |
currentLine += body.slice(chunkStart, position); | |
blockLines.push(currentLine); | |
const token = createToken( | |
lexer, | |
_tokenKind.TokenKind.BLOCK_STRING, | |
start, | |
position + 3, // Return a string of the lines joined with U+000A. | |
(0, _blockString.dedentBlockStringLines)(blockLines).join('\n'), | |
); | |
lexer.line += blockLines.length - 1; | |
lexer.lineStart = lineStart; | |
return token; | |
} // Escaped Triple-Quote (\""") | |
if ( | |
code === 0x005c && | |
body.charCodeAt(position + 1) === 0x0022 && | |
body.charCodeAt(position + 2) === 0x0022 && | |
body.charCodeAt(position + 3) === 0x0022 | |
) { | |
currentLine += body.slice(chunkStart, position); | |
chunkStart = position + 1; // skip only slash | |
position += 4; | |
continue; | |
} // LineTerminator | |
if (code === 0x000a || code === 0x000d) { | |
currentLine += body.slice(chunkStart, position); | |
blockLines.push(currentLine); | |
if (code === 0x000d && body.charCodeAt(position + 1) === 0x000a) { | |
position += 2; | |
} else { | |
++position; | |
} | |
currentLine = ''; | |
chunkStart = position; | |
lineStart = position; | |
continue; | |
} // SourceCharacter | |
if (isUnicodeScalarValue(code)) { | |
++position; | |
} else if (isSupplementaryCodePoint(body, position)) { | |
position += 2; | |
} else { | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
`Invalid character within String: ${printCodePointAt( | |
lexer, | |
position, | |
)}.`, | |
); | |
} | |
} | |
throw (0, _syntaxError.syntaxError)( | |
lexer.source, | |
position, | |
'Unterminated string.', | |
); | |
} | |
/** | |
* Reads an alphanumeric + underscore name from the source. | |
* | |
* ``` | |
* Name :: | |
* - NameStart NameContinue* [lookahead != NameContinue] | |
* ``` | |
*/ | |
function readName(lexer, start) { | |
const body = lexer.source.body; | |
const bodyLength = body.length; | |
let position = start + 1; | |
while (position < bodyLength) { | |
const code = body.charCodeAt(position); | |
if ((0, _characterClasses.isNameContinue)(code)) { | |
++position; | |
} else { | |
break; | |
} | |
} | |
return createToken( | |
lexer, | |
_tokenKind.TokenKind.NAME, | |
start, | |
position, | |
body.slice(start, position), | |
); | |
} | |