@jmespath-community/jmespath
Version:
Typescript implementation of the JMESPath Community specification
326 lines • 12.4 kB
JavaScript
;
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