UNPKG

@jmespath-community/jmespath

Version:

Typescript implementation of the JMESPath Community specification

459 lines 19.1 kB
"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