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), | |
| ); | |
| } | |