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