UNPKG

@jmespath-community/jmespath

Version:

Typescript implementation of the JMESPath Community specification

312 lines 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TreeInterpreterInstance = exports.TreeInterpreter = void 0; const Lexer_type_1 = require("./Lexer.type"); const Runtime_1 = require("./Runtime"); const Scope_1 = require("./Scope"); const utils_1 = require("./utils"); class TreeInterpreter { constructor() { this._rootValue = null; this.runtime = new Runtime_1.Runtime(this); this._scope = new Scope_1.ScopeChain(); } withScope(scope) { const interpreter = new TreeInterpreter(); interpreter.runtime._functionTable = this.runtime._functionTable; interpreter._rootValue = this._rootValue; interpreter._scope = this._scope.withScope(scope); return interpreter; } search(node, value) { this._rootValue = value; this._scope = new Scope_1.ScopeChain(); return this.visit(node, value); } visit(node, value) { var _a, _b, _c; switch (node.type) { case 'Field': const identifier = node.name; let result = null; if (value !== null && typeof value === 'object' && !Array.isArray(value)) { result = (_a = value[identifier]) !== null && _a !== void 0 ? _a : null; } return result; case 'LetExpression': { const { bindings, expression } = node; let scope = {}; bindings.forEach(binding => { const reference = this.visit(binding, value); scope = Object.assign(Object.assign({}, scope), reference); }); return this.withScope(scope).visit(expression, value); } case 'Binding': { const { variable, reference } = node; const result = this.visit(reference, value); return { [variable]: result }; } case 'Variable': { const variable = node.name; if (!this._scope.getValue(variable) && !Object.prototype.hasOwnProperty.call(this._scope.currentScopeData, variable)) { throw new Error(`Error referencing undefined variable ${variable}`); } return this._scope.getValue(variable); } case 'IndexExpression': return this.visit(node.right, this.visit(node.left, value)); case 'Subexpression': { const result = this.visit(node.left, value); return result != null ? (_b = this.visit(node.right, result)) !== null && _b !== void 0 ? _b : null : null; } case 'Index': { if (!Array.isArray(value)) { return null; } const index = node.value < 0 ? value.length + node.value : node.value; return (_c = value[index]) !== null && _c !== void 0 ? _c : null; } case 'Slice': { if (!Array.isArray(value) && typeof value !== 'string') { return null; } const { start, stop, step } = this.computeSliceParams(value.length, node); if (typeof value === 'string') { // string slices is implemented by slicing // the corresponding array of codepoints and // converting the result back to a string const chars = [...value]; const sliced = this.slice(chars, start, stop, step); return sliced.join(''); } else { return this.slice(value, start, stop, step); } } case 'Projection': { const { left, right } = node; // projections typically operate on arrays // string slicing produces a 'Projection' whose // first child is an 'IndexExpression' whose // second child is an 'Slice' // we allow execution of the left index-expression // to return a string only if the AST has this // specific shape let allowString = false; if (left.type === 'IndexExpression' && left.right.type === 'Slice') { allowString = true; } const base = this.visit(left, value); if (allowString && typeof base === 'string') { // a projection is really a sub-expression in disguise // we must evaluate the right hand expression return this.visit(right, base); } if (!Array.isArray(base)) { return null; } const collected = []; for (const elem of base) { const current = this.visit(right, elem); if (current !== null) { collected.push(current); } } return collected; } case 'ValueProjection': { const { left, right } = node; const base = this.visit(left, value); if (base === null || typeof base !== 'object' || Array.isArray(base)) { return null; } const collected = []; const values = Object.values(base); for (const elem of values) { const current = this.visit(right, elem); if (current !== null) { collected.push(current); } } return collected; } case 'FilterProjection': { const { left, right, condition } = node; const base = this.visit(left, value); if (!Array.isArray(base)) { return null; } const results = []; for (const elem of base) { const matched = this.visit(condition, elem); if ((0, utils_1.isFalse)(matched)) { continue; } const result = this.visit(right, elem); if (result !== null) { results.push(result); } } return results; } case 'Arithmetic': { const first = this.visit(node.left, value); const second = this.visit(node.right, value); switch (node.operator) { case Lexer_type_1.Token.TOK_PLUS: return (0, utils_1.add)(first, second); case Lexer_type_1.Token.TOK_MINUS: return (0, utils_1.sub)(first, second); case Lexer_type_1.Token.TOK_MULTIPLY: case Lexer_type_1.Token.TOK_STAR: return (0, utils_1.mul)(first, second); case Lexer_type_1.Token.TOK_DIVIDE: return (0, utils_1.divide)(first, second); case Lexer_type_1.Token.TOK_MODULO: return (0, utils_1.mod)(first, second); case Lexer_type_1.Token.TOK_DIV: return (0, utils_1.div)(first, second); default: throw new Error(`Syntax error: unknown arithmetic operator: ${node.operator}`); } } case 'Unary': { const operand = this.visit(node.operand, value); switch (node.operator) { case Lexer_type_1.Token.TOK_PLUS: (0, utils_1.ensureNumbers)(operand); return operand; case Lexer_type_1.Token.TOK_MINUS: (0, utils_1.ensureNumbers)(operand); return -operand; default: throw new Error(`Syntax error: unknown arithmetic operator: ${node.operator}`); } } case 'Comparator': { const first = this.visit(node.left, value); const second = this.visit(node.right, value); // equality is an exact match switch (node.name) { case 'EQ': return (0, utils_1.strictDeepEqual)(first, second); case 'NE': return !(0, utils_1.strictDeepEqual)(first, second); } // ordering operators are only valid for numbers if (typeof first !== 'number' || typeof second !== 'number') { return null; } switch (node.name) { case 'GT': return first > second; case 'GTE': return first >= second; case 'LT': return first < second; case 'LTE': return first <= second; } } case 'Flatten': { const original = this.visit(node.child, value); return Array.isArray(original) ? original.flat() : null; } case 'Root': return this._rootValue; case 'MultiSelectList': { const collected = []; for (const child of node.children) { collected.push(this.visit(child, value)); } return collected; } case 'MultiSelectHash': { const collected = {}; for (const child of node.children) { collected[child.name] = this.visit(child.value, value); } return collected; } case 'OrExpression': { const result = this.visit(node.left, value); if ((0, utils_1.isFalse)(result)) { return this.visit(node.right, value); } return result; } case 'AndExpression': { const result = this.visit(node.left, value); if ((0, utils_1.isFalse)(result)) { return result; } return this.visit(node.right, value); } case 'NotExpression': return (0, utils_1.isFalse)(this.visit(node.child, value)); case 'Literal': return node.value; case 'Pipe': return this.visit(node.right, this.visit(node.left, value)); case 'Function': { const args = []; for (const child of node.children) { args.push(this.visit(child, value)); } return this.runtime.callFunction(node.name, args); } case 'ExpressionReference': return Object.assign({ expref: true }, node.child); case 'Current': case 'Identity': return value; } } computeSliceParams(arrayLength, sliceNode) { let { start, stop, step } = sliceNode; if (step === null) { step = 1; } else if (step === 0) { const error = new Error('Invalid value: slice step cannot be 0'); error.name = 'RuntimeError'; throw error; } start = start === null ? (step < 0 ? arrayLength - 1 : 0) : this.capSliceRange(arrayLength, start, step); stop = stop === null ? (step < 0 ? -1 : arrayLength) : this.capSliceRange(arrayLength, stop, step); return { start, stop, step }; } capSliceRange(arrayLength, actualValue, step) { let nextActualValue = actualValue; if (nextActualValue < 0) { nextActualValue += arrayLength; if (nextActualValue < 0) { nextActualValue = step < 0 ? -1 : 0; } } else if (nextActualValue >= arrayLength) { nextActualValue = step < 0 ? arrayLength - 1 : arrayLength; } return nextActualValue; } slice(collection, start, end, step) { const result = []; if (step > 0) { for (let i = start; i < end; i += step) { result.push(collection[i]); } } else { for (let i = start; i > end; i += step) { result.push(collection[i]); } } return result; } } exports.TreeInterpreter = TreeInterpreter; exports.TreeInterpreterInstance = new TreeInterpreter(); exports.default = exports.TreeInterpreterInstance; //# sourceMappingURL=TreeInterpreter.js.map