@jmespath-community/jmespath
Version:
Typescript implementation of the JMESPath Community specification
459 lines • 19.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
const Lexer_1 = __importDefault(require("./Lexer"));
const Lexer_type_1 = require("./Lexer.type");
const bindingPower = {
[Lexer_type_1.Token.TOK_EOF]: 0,
[Lexer_type_1.Token.TOK_VARIABLE]: 0,
[Lexer_type_1.Token.TOK_UNQUOTEDIDENTIFIER]: 0,
[Lexer_type_1.Token.TOK_QUOTEDIDENTIFIER]: 0,
[Lexer_type_1.Token.TOK_RBRACKET]: 0,
[Lexer_type_1.Token.TOK_RPAREN]: 0,
[Lexer_type_1.Token.TOK_COMMA]: 0,
[Lexer_type_1.Token.TOK_RBRACE]: 0,
[Lexer_type_1.Token.TOK_NUMBER]: 0,
[Lexer_type_1.Token.TOK_CURRENT]: 0,
[Lexer_type_1.Token.TOK_EXPREF]: 0,
[Lexer_type_1.Token.TOK_ROOT]: 0,
[Lexer_type_1.Token.TOK_ASSIGN]: 1,
[Lexer_type_1.Token.TOK_PIPE]: 1,
[Lexer_type_1.Token.TOK_OR]: 2,
[Lexer_type_1.Token.TOK_AND]: 3,
[Lexer_type_1.Token.TOK_EQ]: 5,
[Lexer_type_1.Token.TOK_GT]: 5,
[Lexer_type_1.Token.TOK_LT]: 5,
[Lexer_type_1.Token.TOK_GTE]: 5,
[Lexer_type_1.Token.TOK_LTE]: 5,
[Lexer_type_1.Token.TOK_NE]: 5,
[Lexer_type_1.Token.TOK_MINUS]: 6,
[Lexer_type_1.Token.TOK_PLUS]: 6,
[Lexer_type_1.Token.TOK_DIV]: 7,
[Lexer_type_1.Token.TOK_DIVIDE]: 7,
[Lexer_type_1.Token.TOK_MODULO]: 7,
[Lexer_type_1.Token.TOK_MULTIPLY]: 7,
[Lexer_type_1.Token.TOK_FLATTEN]: 9,
[Lexer_type_1.Token.TOK_STAR]: 20,
[Lexer_type_1.Token.TOK_FILTER]: 21,
[Lexer_type_1.Token.TOK_DOT]: 40,
[Lexer_type_1.Token.TOK_NOT]: 45,
[Lexer_type_1.Token.TOK_LBRACE]: 50,
[Lexer_type_1.Token.TOK_LBRACKET]: 55,
[Lexer_type_1.Token.TOK_LPAREN]: 60,
};
class TokenParser {
constructor() {
this.index = 0;
this.tokens = [];
}
parse(expression, options) {
this.loadTokens(expression, options || { enable_legacy_literals: false });
this.index = 0;
const ast = this.expression(0);
if (this.lookahead(0) !== Lexer_type_1.Token.TOK_EOF) {
const token = this.lookaheadToken(0);
this.errorToken(token, `Syntax error: unexpected token type: ${token.type}, value: ${token.value}`);
}
return ast;
}
loadTokens(expression, options) {
this.tokens = [
...Lexer_1.default.tokenize(expression, options),
{ type: Lexer_type_1.Token.TOK_EOF, value: '', start: expression.length },
];
}
expression(rbp) {
const leftToken = this.lookaheadToken(0);
this.advance();
let left = this.nud(leftToken);
let currentTokenType = this.lookahead(0);
while (rbp < bindingPower[currentTokenType]) {
this.advance();
left = this.led(currentTokenType, left);
currentTokenType = this.lookahead(0);
}
return left;
}
lookahead(offset) {
return this.tokens[this.index + offset].type;
}
lookaheadToken(offset) {
return this.tokens[this.index + offset];
}
advance() {
this.index += 1;
}
nud(token) {
switch (token.type) {
case Lexer_type_1.Token.TOK_VARIABLE:
return { type: 'Variable', name: token.value };
case Lexer_type_1.Token.TOK_LITERAL:
return { type: 'Literal', value: token.value };
case Lexer_type_1.Token.TOK_UNQUOTEDIDENTIFIER: {
if (TokenParser.isKeyword(token, 'let') && this.lookahead(0) === Lexer_type_1.Token.TOK_VARIABLE) {
return this.parseLetExpression();
}
else {
return { type: 'Field', name: token.value };
}
}
case Lexer_type_1.Token.TOK_QUOTEDIDENTIFIER:
if (this.lookahead(0) === Lexer_type_1.Token.TOK_LPAREN) {
throw new Error('Syntax error: quoted identifier not allowed for function names.');
}
else {
return { type: 'Field', name: token.value };
}
case Lexer_type_1.Token.TOK_NOT: {
const child = this.expression(bindingPower.Not);
return { type: 'NotExpression', child };
}
case Lexer_type_1.Token.TOK_MINUS: {
const child = this.expression(bindingPower.Minus);
return {
type: 'Unary',
operator: token.type,
operand: child,
};
}
case Lexer_type_1.Token.TOK_PLUS: {
const child = this.expression(bindingPower.Plus);
return {
type: 'Unary',
operator: token.type,
operand: child,
};
}
case Lexer_type_1.Token.TOK_STAR: {
const left = { type: 'Identity' };
const right = this.lookahead(0) === Lexer_type_1.Token.TOK_RBRACKET ? left : this.parseProjectionRHS(bindingPower.Star);
return { type: 'ValueProjection', left, right };
}
case Lexer_type_1.Token.TOK_FILTER:
return this.led(token.type, { type: 'Identity' });
case Lexer_type_1.Token.TOK_LBRACE:
return this.parseMultiselectHash();
case Lexer_type_1.Token.TOK_FLATTEN: {
const left = {
type: 'Flatten',
child: { type: 'Identity' },
};
const right = this.parseProjectionRHS(bindingPower.Flatten);
return { type: 'Projection', left, right };
}
case Lexer_type_1.Token.TOK_LBRACKET: {
if (this.lookahead(0) === Lexer_type_1.Token.TOK_NUMBER || this.lookahead(0) === Lexer_type_1.Token.TOK_COLON) {
const right = this.parseIndexExpression();
return this.projectIfSlice({ type: 'Identity' }, right);
}
if (this.lookahead(0) === Lexer_type_1.Token.TOK_STAR && this.lookahead(1) === Lexer_type_1.Token.TOK_RBRACKET) {
this.advance();
this.advance();
const right = this.parseProjectionRHS(bindingPower.Star);
return {
left: { type: 'Identity' },
right,
type: 'Projection',
};
}
return this.parseMultiselectList();
}
case Lexer_type_1.Token.TOK_CURRENT:
return { type: Lexer_type_1.Token.TOK_CURRENT };
case Lexer_type_1.Token.TOK_ROOT:
return { type: Lexer_type_1.Token.TOK_ROOT };
case Lexer_type_1.Token.TOK_EXPREF: {
const child = this.expression(bindingPower.Expref);
return { type: 'ExpressionReference', child };
}
case Lexer_type_1.Token.TOK_LPAREN: {
const args = [];
let expression = this.expression(0);
args.push(expression);
this.match(Lexer_type_1.Token.TOK_RPAREN);
return args[0];
}
default:
this.errorToken(token);
}
}
led(tokenName, left) {
switch (tokenName) {
case Lexer_type_1.Token.TOK_DOT: {
const rbp = bindingPower.Dot;
if (this.lookahead(0) !== Lexer_type_1.Token.TOK_STAR) {
const right = this.parseDotRHS(rbp);
return { type: 'Subexpression', left, right };
}
this.advance();
const right = this.parseProjectionRHS(rbp);
return { type: 'ValueProjection', left, right };
}
case Lexer_type_1.Token.TOK_PIPE: {
const right = this.expression(bindingPower.Pipe);
return { type: 'Pipe', left, right };
}
case Lexer_type_1.Token.TOK_OR: {
const right = this.expression(bindingPower.Or);
return { type: 'OrExpression', left, right };
}
case Lexer_type_1.Token.TOK_AND: {
const right = this.expression(bindingPower.And);
return { type: 'AndExpression', left, right };
}
case Lexer_type_1.Token.TOK_LPAREN: {
if (left.type !== 'Field') {
throw new Error('Syntax error: expected a Field node');
}
const name = left.name;
const args = this.parseCommaSeparatedExpressionsUntilToken(Lexer_type_1.Token.TOK_RPAREN);
const node = { name, type: 'Function', children: args };
return node;
}
case Lexer_type_1.Token.TOK_FILTER: {
const condition = this.expression(0);
this.match(Lexer_type_1.Token.TOK_RBRACKET);
const right = this.lookahead(0) === Lexer_type_1.Token.TOK_FLATTEN ? { type: 'Identity' } : this.parseProjectionRHS(bindingPower.Filter);
return { type: 'FilterProjection', left, right, condition };
}
case Lexer_type_1.Token.TOK_FLATTEN: {
const leftNode = { type: 'Flatten', child: left };
const right = this.parseProjectionRHS(bindingPower.Flatten);
return { type: 'Projection', left: leftNode, right };
}
case Lexer_type_1.Token.TOK_ASSIGN: {
const leftNode = left;
const right = this.expression(0);
return {
type: 'Binding',
variable: leftNode.name,
reference: right,
};
}
case Lexer_type_1.Token.TOK_EQ:
case Lexer_type_1.Token.TOK_NE:
case Lexer_type_1.Token.TOK_GT:
case Lexer_type_1.Token.TOK_GTE:
case Lexer_type_1.Token.TOK_LT:
case Lexer_type_1.Token.TOK_LTE:
return this.parseComparator(left, tokenName);
case Lexer_type_1.Token.TOK_PLUS:
case Lexer_type_1.Token.TOK_MINUS:
case Lexer_type_1.Token.TOK_MULTIPLY:
case Lexer_type_1.Token.TOK_STAR:
case Lexer_type_1.Token.TOK_DIVIDE:
case Lexer_type_1.Token.TOK_MODULO:
case Lexer_type_1.Token.TOK_DIV:
return this.parseArithmetic(left, tokenName);
case Lexer_type_1.Token.TOK_LBRACKET: {
const token = this.lookaheadToken(0);
if (token.type === Lexer_type_1.Token.TOK_NUMBER || token.type === Lexer_type_1.Token.TOK_COLON) {
const right = this.parseIndexExpression();
return this.projectIfSlice(left, right);
}
this.match(Lexer_type_1.Token.TOK_STAR);
this.match(Lexer_type_1.Token.TOK_RBRACKET);
const right = this.parseProjectionRHS(bindingPower.Star);
return { type: 'Projection', left, right };
}
default:
return this.errorToken(this.lookaheadToken(0));
}
}
static isKeyword(token, keyword) {
return token.type === Lexer_type_1.Token.TOK_UNQUOTEDIDENTIFIER && token.value === keyword;
}
match(tokenType) {
if (this.lookahead(0) === tokenType) {
this.advance();
return;
}
else {
const token = this.lookaheadToken(0);
this.errorToken(token, `Syntax error: expected ${tokenType}, got: ${token.type}`);
}
}
errorToken(token, message = '') {
const error = new Error(message || `Syntax error: invalid token (${token.type}): "${token.value}"`);
error.name = 'ParserError';
throw error;
}
parseIndexExpression() {
if (this.lookahead(0) === Lexer_type_1.Token.TOK_COLON || this.lookahead(1) === Lexer_type_1.Token.TOK_COLON) {
return this.parseSliceExpression();
}
const value = Number(this.lookaheadToken(0).value);
this.advance();
this.match(Lexer_type_1.Token.TOK_RBRACKET);
return { type: 'Index', value };
}
projectIfSlice(left, right) {
const indexExpr = {
type: 'IndexExpression',
left,
right,
};
if (right.type === 'Slice') {
return {
left: indexExpr,
right: this.parseProjectionRHS(bindingPower.Star),
type: 'Projection',
};
}
return indexExpr;
}
parseSliceExpression() {
const parts = [null, null, null];
let index = 0;
let current = this.lookaheadToken(0);
while (current.type != Lexer_type_1.Token.TOK_RBRACKET && index < 3) {
if (current.type === Lexer_type_1.Token.TOK_COLON) {
index++;
if (index === 3) {
this.errorToken(this.lookaheadToken(0), 'Syntax error, too many colons in slice expression');
}
this.advance();
}
else if (current.type === Lexer_type_1.Token.TOK_NUMBER) {
const part = this.lookaheadToken(0).value;
parts[index] = part;
this.advance();
}
else {
const next = this.lookaheadToken(0);
this.errorToken(next, `Syntax error, unexpected token: ${next.value}(${next.type})`);
}
current = this.lookaheadToken(0);
}
this.match(Lexer_type_1.Token.TOK_RBRACKET);
const [start, stop, step] = parts;
return { type: 'Slice', start, stop, step };
}
parseLetExpression() {
const separated = this.parseCommaSeparatedExpressionsUntilKeyword('in');
const expression = this.expression(0);
const bindings = separated.map(binding => binding);
return {
type: 'LetExpression',
bindings: bindings,
expression: expression,
};
}
parseCommaSeparatedExpressionsUntilKeyword(keyword) {
return this.parseCommaSeparatedExpressionsUntil(() => {
return TokenParser.isKeyword(this.lookaheadToken(0), keyword);
}, () => {
this.advance();
});
}
parseCommaSeparatedExpressionsUntilToken(token) {
return this.parseCommaSeparatedExpressionsUntil(() => {
return this.lookahead(0) === token;
}, () => {
return this.match(token);
});
}
parseCommaSeparatedExpressionsUntil(isEndToken, matchEndToken) {
const args = [];
let expression;
while (!isEndToken()) {
expression = this.expression(0);
if (this.lookahead(0) === Lexer_type_1.Token.TOK_COMMA) {
this.match(Lexer_type_1.Token.TOK_COMMA);
}
args.push(expression);
}
matchEndToken();
return args;
}
parseComparator(left, comparator) {
const right = this.expression(bindingPower[comparator]);
return { type: 'Comparator', name: comparator, left, right };
}
parseArithmetic(left, operator) {
const right = this.expression(bindingPower[operator]);
return { type: 'Arithmetic', operator: operator, left, right };
}
parseDotRHS(rbp) {
const lookahead = this.lookahead(0);
const exprTokens = [Lexer_type_1.Token.TOK_UNQUOTEDIDENTIFIER, Lexer_type_1.Token.TOK_QUOTEDIDENTIFIER, Lexer_type_1.Token.TOK_STAR];
if (exprTokens.includes(lookahead)) {
return this.expression(rbp);
}
if (lookahead === Lexer_type_1.Token.TOK_LBRACKET) {
this.match(Lexer_type_1.Token.TOK_LBRACKET);
return this.parseMultiselectList();
}
if (lookahead === Lexer_type_1.Token.TOK_LBRACE) {
this.match(Lexer_type_1.Token.TOK_LBRACE);
return this.parseMultiselectHash();
}
const token = this.lookaheadToken(0);
this.errorToken(token, `Syntax error, unexpected token: ${token.value}(${token.type})`);
}
parseProjectionRHS(rbp) {
if (bindingPower[this.lookahead(0)] < 10) {
return { type: 'Identity' };
}
if (this.lookahead(0) === Lexer_type_1.Token.TOK_LBRACKET) {
return this.expression(rbp);
}
if (this.lookahead(0) === Lexer_type_1.Token.TOK_FILTER) {
return this.expression(rbp);
}
if (this.lookahead(0) === Lexer_type_1.Token.TOK_DOT) {
this.match(Lexer_type_1.Token.TOK_DOT);
return this.parseDotRHS(rbp);
}
const token = this.lookaheadToken(0);
this.errorToken(token, `Syntax error, unexpected token: ${token.value}(${token.type})`);
}
parseMultiselectList() {
const expressions = [];
while (this.lookahead(0) !== Lexer_type_1.Token.TOK_RBRACKET) {
const expression = this.expression(0);
expressions.push(expression);
if (this.lookahead(0) === Lexer_type_1.Token.TOK_COMMA) {
this.match(Lexer_type_1.Token.TOK_COMMA);
if (this.lookahead(0) === Lexer_type_1.Token.TOK_RBRACKET) {
throw new Error('Syntax error: unexpected token Rbracket');
}
}
}
this.match(Lexer_type_1.Token.TOK_RBRACKET);
return { type: 'MultiSelectList', children: expressions };
}
parseMultiselectHash() {
const pairs = [];
const identifierTypes = [Lexer_type_1.Token.TOK_UNQUOTEDIDENTIFIER, Lexer_type_1.Token.TOK_QUOTEDIDENTIFIER];
let keyToken;
let keyName;
let value;
// tslint:disable-next-line: prettier
for (;;) {
keyToken = this.lookaheadToken(0);
if (!identifierTypes.includes(keyToken.type)) {
throw new Error(`Syntax error: expecting an identifier token, got: ${keyToken.type}`);
}
keyName = keyToken.value;
this.advance();
this.match(Lexer_type_1.Token.TOK_COLON);
value = this.expression(0);
pairs.push({ value, type: 'KeyValuePair', name: keyName });
if (this.lookahead(0) === Lexer_type_1.Token.TOK_COMMA) {
this.match(Lexer_type_1.Token.TOK_COMMA);
}
else if (this.lookahead(0) === Lexer_type_1.Token.TOK_RBRACE) {
this.match(Lexer_type_1.Token.TOK_RBRACE);
break;
}
}
return { type: 'MultiSelectHash', children: pairs };
}
}
exports.Parser = new TokenParser();
exports.default = exports.Parser;
//# sourceMappingURL=Parser.js.map