UNPKG

jora

Version:

JavaScript object query engine

162 lines (145 loc) 4.65 kB
const binary = { 'in': 'in', 'not in': 'notIn', 'has': 'has', 'has no': 'hasNo', 'and': 'and', 'or': 'or', '??': 'nullish', '+': 'add', '-': 'sub', '*': 'mul', '/': 'div', '%': 'mod', '=': 'eq', '!=': 'ne', '<': 'lt', '<=': 'lte', '>': 'gt', '>=': 'gte', '~=': 'match' }; function valueSubset(ctx, values, dict) { if (dict.type === 'Array') { if (dict.elements.length === 0) { ctx.range([dict.range[0] + 1, dict.range[1] - 1], 'value-subset', values, false); return; } const excludeValues = []; for (const { type, range, value } of dict.elements) { if (range && (type === 'Literal' || type === 'Identifier')) { excludeValues.push(value); } } const related = excludeValues.length ? ctx.literalList(excludeValues) : false; for (const { type, range, value } of dict.elements) { if (range) { if (type === 'Literal' || type === 'Identifier' || (type === 'GetProperty' && value === null)) { ctx.range(range, 'value-subset', values, related); } } } } } export function suggest(node, ctx) { switch (node.operator) { case 'in': ctx.range(node.left.range, 'in-value', node.right, null); valueSubset(ctx, node.left, node.right); break; case 'not in': valueSubset(ctx, node.left, node.right); break; case 'has': ctx.range(node.right.range, 'in-value', node.left, null); valueSubset(ctx, node.right, node.left); break; case 'has no': valueSubset(ctx, node.right, node.left); break; case '=': case '!=': ctx.range(node.right.range, 'value', node.left, null); break; } } export function compile(node, ctx) { if (node.operator in binary === false) { ctx.error('Unknown operator "' + node.operator + '"', node); return; } switch (node.operator) { // separate branch since node.right might not to be evaluated (short-circuiting) when: // - node.left is falsy for "and" // - node.left is truthy for "or" case 'and': ctx.put('!'); case 'or': { const tmpVar = ctx.allocateVar(); ctx.put(`${ctx.buildinFn('bool')}(${tmpVar}=`); ctx.node(node.left); ctx.put(`)?${tmpVar}:`); ctx.createScope( () => ctx.node(node.right), (sp) => { ctx.put(')'); return '(' + sp + ','; }, ctx.scope.$ref ); break; } // separate branch since node.right might not to be evaluated (short-circuiting) // when node.left is null or undefined case '??': { const tmpVar = ctx.allocateVar(); // TODO: replace for Nullish coalescing operator (??) instead of ternary operator once drop support for Node.js below 14.0 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing ctx.put(`(${tmpVar}=`); ctx.node(node.left); ctx.put(`,${tmpVar}!==null&&${tmpVar}!==undefined)?${tmpVar}:`); ctx.createScope( () => ctx.node(node.right), (sp) => { ctx.put(')'); return '(' + sp + ','; }, ctx.scope.$ref ); break; } // separate branch since suggest should collect stat for node.right first case 'has no': ctx.put('!'); case 'has': ctx.put(ctx.buildinFn('in')); ctx.put('('); ctx.node(node.right); ctx.put(','); ctx.node(node.left); ctx.put(')'); break; default: ctx.put(ctx.buildinFn(binary[node.operator])); ctx.put('('); ctx.node(node.left); ctx.put(','); ctx.node(node.right); ctx.put(')'); } } export function walk(node, ctx) { ctx.node(node.left); ctx.node(node.right); } export function stringify(node, ctx) { ctx.node(node.left); if (/^[a-z]/i.test(node.operator)) { ctx.put(' '); ctx.put(node.operator); ctx.put(' '); } else { ctx.put(node.operator); } ctx.node(node.right); }