Spaces:
Sleeping
Sleeping
| /** | |
| * @fileoverview Utility functions to locate the source text of each code unit in the value of a string literal or template token. | |
| * @author Francesco Trotta | |
| */ | |
| ; | |
| /** | |
| * Represents a code unit produced by the evaluation of a JavaScript common token like a string | |
| * literal or template token. | |
| */ | |
| class CodeUnit { | |
| constructor(start, source) { | |
| this.start = start; | |
| this.source = source; | |
| } | |
| get end() { | |
| return this.start + this.length; | |
| } | |
| get length() { | |
| return this.source.length; | |
| } | |
| } | |
| /** | |
| * An object used to keep track of the position in a source text where the next characters will be read. | |
| */ | |
| class TextReader { | |
| constructor(source) { | |
| this.source = source; | |
| this.pos = 0; | |
| } | |
| /** | |
| * Advances the reading position of the specified number of characters. | |
| * @param {number} length Number of characters to advance. | |
| * @returns {void} | |
| */ | |
| advance(length) { | |
| this.pos += length; | |
| } | |
| /** | |
| * Reads characters from the source. | |
| * @param {number} [offset=0] The offset where reading starts, relative to the current position. | |
| * @param {number} [length=1] Number of characters to read. | |
| * @returns {string} A substring of source characters. | |
| */ | |
| read(offset = 0, length = 1) { | |
| const start = offset + this.pos; | |
| return this.source.slice(start, start + length); | |
| } | |
| } | |
| const SIMPLE_ESCAPE_SEQUENCES = { | |
| __proto__: null, | |
| b: "\b", | |
| f: "\f", | |
| n: "\n", | |
| r: "\r", | |
| t: "\t", | |
| v: "\v", | |
| }; | |
| /** | |
| * Reads a hex escape sequence. | |
| * @param {TextReader} reader The reader should be positioned on the first hexadecimal digit. | |
| * @param {number} length The number of hexadecimal digits. | |
| * @returns {string} A code unit. | |
| */ | |
| function readHexSequence(reader, length) { | |
| const str = reader.read(0, length); | |
| const charCode = parseInt(str, 16); | |
| reader.advance(length); | |
| return String.fromCharCode(charCode); | |
| } | |
| /** | |
| * Reads a Unicode escape sequence. | |
| * @param {TextReader} reader The reader should be positioned after the "u". | |
| * @returns {string} A code unit. | |
| */ | |
| function readUnicodeSequence(reader) { | |
| const regExp = /\{(?<hexDigits>[\dA-F]+)\}/iuy; | |
| regExp.lastIndex = reader.pos; | |
| const match = regExp.exec(reader.source); | |
| if (match) { | |
| const codePoint = parseInt(match.groups.hexDigits, 16); | |
| reader.pos = regExp.lastIndex; | |
| return String.fromCodePoint(codePoint); | |
| } | |
| return readHexSequence(reader, 4); | |
| } | |
| /** | |
| * Reads an octal escape sequence. | |
| * @param {TextReader} reader The reader should be positioned after the first octal digit. | |
| * @param {number} maxLength The maximum number of octal digits. | |
| * @returns {string} A code unit. | |
| */ | |
| function readOctalSequence(reader, maxLength) { | |
| const [octalStr] = reader.read(-1, maxLength).match(/^[0-7]+/u); | |
| reader.advance(octalStr.length - 1); | |
| const octal = parseInt(octalStr, 8); | |
| return String.fromCharCode(octal); | |
| } | |
| /** | |
| * Reads an escape sequence or line continuation. | |
| * @param {TextReader} reader The reader should be positioned on the backslash. | |
| * @returns {string} A string of zero, one or two code units. | |
| */ | |
| function readEscapeSequenceOrLineContinuation(reader) { | |
| const char = reader.read(1); | |
| reader.advance(2); | |
| const unitChar = SIMPLE_ESCAPE_SEQUENCES[char]; | |
| if (unitChar) { | |
| return unitChar; | |
| } | |
| switch (char) { | |
| case "x": | |
| return readHexSequence(reader, 2); | |
| case "u": | |
| return readUnicodeSequence(reader); | |
| case "\r": | |
| if (reader.read() === "\n") { | |
| reader.advance(1); | |
| } | |
| // fallthrough | |
| case "\n": | |
| case "\u2028": | |
| case "\u2029": | |
| return ""; | |
| case "0": | |
| case "1": | |
| case "2": | |
| case "3": | |
| return readOctalSequence(reader, 3); | |
| case "4": | |
| case "5": | |
| case "6": | |
| case "7": | |
| return readOctalSequence(reader, 2); | |
| default: | |
| return char; | |
| } | |
| } | |
| /** | |
| * Reads an escape sequence or line continuation and generates the respective `CodeUnit` elements. | |
| * @param {TextReader} reader The reader should be positioned on the backslash. | |
| * @returns {Generator<CodeUnit>} Zero, one or two `CodeUnit` elements. | |
| */ | |
| function* mapEscapeSequenceOrLineContinuation(reader) { | |
| const start = reader.pos; | |
| const str = readEscapeSequenceOrLineContinuation(reader); | |
| const end = reader.pos; | |
| const source = reader.source.slice(start, end); | |
| switch (str.length) { | |
| case 0: | |
| break; | |
| case 1: | |
| yield new CodeUnit(start, source); | |
| break; | |
| default: | |
| yield new CodeUnit(start, source); | |
| yield new CodeUnit(start, source); | |
| break; | |
| } | |
| } | |
| /** | |
| * Parses a string literal. | |
| * @param {string} source The string literal to parse, including the delimiting quotes. | |
| * @returns {CodeUnit[]} A list of code units produced by the string literal. | |
| */ | |
| function parseStringLiteral(source) { | |
| const reader = new TextReader(source); | |
| const quote = reader.read(); | |
| reader.advance(1); | |
| const codeUnits = []; | |
| for (;;) { | |
| const char = reader.read(); | |
| if (char === quote) { | |
| break; | |
| } | |
| if (char === "\\") { | |
| codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); | |
| } else { | |
| codeUnits.push(new CodeUnit(reader.pos, char)); | |
| reader.advance(1); | |
| } | |
| } | |
| return codeUnits; | |
| } | |
| /** | |
| * Parses a template token. | |
| * @param {string} source The template token to parse, including the delimiting sequences `` ` ``, `${` and `}`. | |
| * @returns {CodeUnit[]} A list of code units produced by the template token. | |
| */ | |
| function parseTemplateToken(source) { | |
| const reader = new TextReader(source); | |
| reader.advance(1); | |
| const codeUnits = []; | |
| for (;;) { | |
| const char = reader.read(); | |
| if (char === "`" || (char === "$" && reader.read(1) === "{")) { | |
| break; | |
| } | |
| if (char === "\\") { | |
| codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); | |
| } else { | |
| let unitSource; | |
| if (char === "\r" && reader.read(1) === "\n") { | |
| unitSource = "\r\n"; | |
| } else { | |
| unitSource = char; | |
| } | |
| codeUnits.push(new CodeUnit(reader.pos, unitSource)); | |
| reader.advance(unitSource.length); | |
| } | |
| } | |
| return codeUnits; | |
| } | |
| module.exports = { parseStringLiteral, parseTemplateToken }; | |