UNPKG

@jmespath-community/jmespath

Version:

Typescript implementation of the JMESPath Community specification

326 lines 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Lexer = exports.basicTokens = void 0; const strings_1 = require("./utils/strings"); const Lexer_type_1 = require("./Lexer.type"); const index_1 = require("./utils/index"); exports.basicTokens = { '(': Lexer_type_1.Token.TOK_LPAREN, ')': Lexer_type_1.Token.TOK_RPAREN, '*': Lexer_type_1.Token.TOK_STAR, ',': Lexer_type_1.Token.TOK_COMMA, '.': Lexer_type_1.Token.TOK_DOT, ':': Lexer_type_1.Token.TOK_COLON, '@': Lexer_type_1.Token.TOK_CURRENT, ']': Lexer_type_1.Token.TOK_RBRACKET, '{': Lexer_type_1.Token.TOK_LBRACE, '}': Lexer_type_1.Token.TOK_RBRACE, '+': Lexer_type_1.Token.TOK_PLUS, '%': Lexer_type_1.Token.TOK_MODULO, '\u2212': Lexer_type_1.Token.TOK_MINUS, '\u00d7': Lexer_type_1.Token.TOK_MULTIPLY, '\u00f7': Lexer_type_1.Token.TOK_DIVIDE, }; const operatorStartToken = { '!': true, '<': true, '=': true, '>': true, '&': true, '|': true, '/': true, }; const skipChars = { '\t': true, '\n': true, '\r': true, ' ': true, }; class StreamLexer { constructor() { this._current = 0; this._enable_legacy_literals = false; } tokenize(stream, options) { const tokens = []; this._current = 0; this._enable_legacy_literals = (options === null || options === void 0 ? void 0 : options.enable_legacy_literals) || false; let start; let identifier; let token; while (this._current < stream.length) { if ((0, index_1.isAlpha)(stream[this._current])) { start = this._current; identifier = this.consumeUnquotedIdentifier(stream); tokens.push({ start, type: Lexer_type_1.Token.TOK_UNQUOTEDIDENTIFIER, value: identifier, }); } else if (exports.basicTokens[stream[this._current]] !== undefined) { tokens.push({ start: this._current, type: exports.basicTokens[stream[this._current]], value: stream[this._current], }); this._current += 1; } else if (stream[this._current] === '$') { start = this._current; if (this._current + 1 < stream.length && (0, index_1.isAlpha)(stream[this._current + 1])) { this._current += 1; identifier = this.consumeUnquotedIdentifier(stream); tokens.push({ start, type: Lexer_type_1.Token.TOK_VARIABLE, value: identifier, }); } else { tokens.push({ start: start, type: Lexer_type_1.Token.TOK_ROOT, value: stream[this._current], }); this._current += 1; } } else if (stream[this._current] === '-') { if (this._current + 1 < stream.length && (0, index_1.isNum)(stream[this._current + 1])) { const token = this.consumeNumber(stream); token && tokens.push(token); } else { const token = { start: this._current, type: Lexer_type_1.Token.TOK_MINUS, value: '-', }; tokens.push(token); this._current += 1; } } else if ((0, index_1.isNum)(stream[this._current])) { token = this.consumeNumber(stream); tokens.push(token); } else if (stream[this._current] === '[') { token = this.consumeLBracket(stream); tokens.push(token); } else if (stream[this._current] === '"') { start = this._current; identifier = this.consumeQuotedIdentifier(stream); tokens.push({ start, type: Lexer_type_1.Token.TOK_QUOTEDIDENTIFIER, value: identifier, }); } else if (stream[this._current] === `'`) { start = this._current; identifier = this.consumeRawStringLiteral(stream); tokens.push({ start, type: Lexer_type_1.Token.TOK_LITERAL, value: identifier, }); } else if (stream[this._current] === '`') { start = this._current; const literal = this.consumeLiteral(stream); tokens.push({ start, type: Lexer_type_1.Token.TOK_LITERAL, value: literal, }); } else if (operatorStartToken[stream[this._current]] !== undefined) { token = this.consumeOperator(stream); token && tokens.push(token); } else if (skipChars[stream[this._current]] !== undefined) { this._current += 1; } else { const error = new Error(`Syntax error: unknown character: ${stream[this._current]}`); error.name = 'LexerError'; throw error; } } return tokens; } consumeUnquotedIdentifier(stream) { const start = this._current; this._current += 1; while (this._current < stream.length && (0, index_1.isAlphaNum)(stream[this._current])) { this._current += 1; } return stream.slice(start, this._current); } consumeQuotedIdentifier(stream) { const start = this._current; this._current += 1; const maxLength = stream.length; while (stream[this._current] !== '"' && this._current < maxLength) { let current = this._current; if (stream[current] === '\\' && (stream[current + 1] === '\\' || stream[current + 1] === '"')) { current += 2; } else { current += 1; } this._current = current; } this._current += 1; const [value, ok] = this.parseJSON(stream.slice(start, this._current)); if (!ok) { const error = new Error(`syntax: unexpected end of JSON input`); error.name = 'LexerError'; throw error; } return value; } consumeRawStringLiteral(stream) { const start = this._current; this._current += 1; const maxLength = stream.length; while (stream[this._current] !== `'` && this._current < maxLength) { let current = this._current; if (stream[current] === '\\' && (stream[current + 1] === '\\' || stream[current + 1] === `'`)) { current += 2; } else { current += 1; } this._current = current; } this._current += 1; const literal = stream.slice(start + 1, this._current - 1); return (0, strings_1.replace)((0, strings_1.replace)(literal, `\\\\`, `\\`), `\\'`, `'`); } consumeNumber(stream) { const start = this._current; this._current += 1; const maxLength = stream.length; while ((0, index_1.isNum)(stream[this._current]) && this._current < maxLength) { this._current += 1; } const value = parseInt(stream.slice(start, this._current), 10); return { start, value, type: Lexer_type_1.Token.TOK_NUMBER }; } consumeLBracket(stream) { const start = this._current; this._current += 1; if (stream[this._current] === '?') { this._current += 1; return { start, type: Lexer_type_1.Token.TOK_FILTER, value: '[?' }; } if (stream[this._current] === ']') { this._current += 1; return { start, type: Lexer_type_1.Token.TOK_FLATTEN, value: '[]' }; } return { start, type: Lexer_type_1.Token.TOK_LBRACKET, value: '[' }; } consumeOrElse(stream, peek, token, orElse) { const start = this._current; this._current += 1; if (this._current < stream.length && stream[this._current] === peek) { this._current += 1; return { start: start, type: orElse, value: stream.slice(start, this._current), }; } return { start: start, type: token, value: stream[start] }; } consumeOperator(stream) { const start = this._current; const startingChar = stream[start]; switch (startingChar) { case '!': return this.consumeOrElse(stream, '=', Lexer_type_1.Token.TOK_NOT, Lexer_type_1.Token.TOK_NE); case '<': return this.consumeOrElse(stream, '=', Lexer_type_1.Token.TOK_LT, Lexer_type_1.Token.TOK_LTE); case '>': return this.consumeOrElse(stream, '=', Lexer_type_1.Token.TOK_GT, Lexer_type_1.Token.TOK_GTE); case '=': return this.consumeOrElse(stream, '=', Lexer_type_1.Token.TOK_ASSIGN, Lexer_type_1.Token.TOK_EQ); case '&': return this.consumeOrElse(stream, '&', Lexer_type_1.Token.TOK_EXPREF, Lexer_type_1.Token.TOK_AND); case '|': return this.consumeOrElse(stream, '|', Lexer_type_1.Token.TOK_PIPE, Lexer_type_1.Token.TOK_OR); case '/': return this.consumeOrElse(stream, '/', Lexer_type_1.Token.TOK_DIVIDE, Lexer_type_1.Token.TOK_DIV); } } consumeLiteral(stream) { this._current += 1; const start = this._current; const maxLength = stream.length; while (stream[this._current] !== '`' && this._current < maxLength) { let current = this._current; if (stream[current] === '\\' && (stream[current + 1] === '\\' || stream[current + 1] === '`')) { current += 2; } else { current += 1; } this._current = current; } let literalString = stream.slice(start, this._current).trimStart(); literalString = literalString.replace('\\`', '`'); let literal = null; let ok = false; // attempts to detect and parse valid JSON if (this.looksLikeJSON(literalString)) { [literal, ok] = this.parseJSON(literalString); } // invalid JSON values should be converted to quoted // JSON strings during the JEP-12 deprecation period. if (!ok && this._enable_legacy_literals) { [literal, ok] = this.parseJSON(`"${literalString}"`); } if (!ok) { const error = new Error(`Syntax error: unexpected end of JSON input or invalid format for a JSON literal: ${stream[this._current]}`); error.name = 'LexerError'; throw error; } this._current += 1; return literal; } looksLikeJSON(literalString) { const startingChars = '[{"'; const jsonLiterals = ['true', 'false', 'null']; const numberLooking = '-0123456789'; if (literalString === '') { return false; } if (startingChars.includes(literalString[0])) { return true; } if (jsonLiterals.includes(literalString)) { return true; } if (numberLooking.includes(literalString[0])) { // eslint-disable-next-line @typescript-eslint/naming-convention const [_, ok] = this.parseJSON(literalString); return ok; } return false; } parseJSON(text) { try { const json = JSON.parse(text); return [json, true]; } catch (_a) { return [null, false]; } } } exports.Lexer = new StreamLexer(); exports.default = exports.Lexer; //# sourceMappingURL=Lexer.js.map