object-scan
Version:
Traverse object hierarchies using matching and callbacks.
111 lines (105 loc) • 3.35 kB
JavaScript
/* compile needles to hierarchical map object */
import parser from './parser.js';
import iterator from './compiler-iterator.js';
import { Ref } from './parser-ref.js';
import { Node } from './node.js';
import { formatNeedle } from '../generic/helper.js';
const applyNeedle = (tower, needle, tree, ctx) => {
iterator(tower, needle, tree, {
onAdd: (cur, parent, v, vParent, next) => {
cur.addNeedle(needle);
if (v instanceof Ref) {
if (v.left === true) {
if (v.isStarRec) {
v.setPointer(cur);
}
v.setNode(new Node('*', ctx));
ctx.links.push(cur, v.node);
next(v.node);
} else {
// eslint-disable-next-line no-param-reassign
v.target = 'target' in vParent ? vParent.target : parent.get(vParent.value);
ctx.links.push(v.target, v.node);
if (v.pointer !== null) {
next(v.pointer);
v.setPointer(null);
}
next(cur);
}
return;
}
const redundantRecursion = (
v.isStarRec
&& v.value === vParent?.value
);
if (redundantRecursion && ctx.strict) {
throw new Error(`Redundant Recursion: "${needle}"`);
}
if (!redundantRecursion) {
let node = cur.get(v.value);
if (node === undefined) {
node = new Node(v.value, ctx);
cur.add(node);
}
next(node);
} else {
// eslint-disable-next-line no-param-reassign
v.target = cur;
}
if (v.isStarRec) {
next(cur);
}
},
onFin: (cur, parent, v, excluded) => {
if (ctx.strict && v.isSimpleStarRec) {
const unnecessary = parent.children.filter(({ value }) => !['', '**'].includes(value));
if (unnecessary.length !== 0) {
throw new Error(`Needle Target Invalidated: "${unnecessary[0].needles[0]}" by "${needle}"`);
}
}
if (ctx.strict && cur.leafNeedles.length !== 0) {
const nLeft = formatNeedle(cur.leafNeedles[0]);
const nRight = formatNeedle(needle);
throw new Error(`Redundant Needle Target: "${nLeft}" vs "${nRight}"`);
}
cur.finish(needle, excluded, ctx.counter);
ctx.counter += 1;
}
});
};
const finalizeTower = (tower, ctx) => {
const { links } = ctx;
while (links.length !== 0) {
const child = links.pop();
const parent = links.pop();
const { children } = parent;
parent.children = [...child.children.filter((c) => !children.includes(c)), ...children];
}
if (ctx.useArraySelector === false) {
tower.setRoots(tower.children
.filter(({ isStarRec, value }) => isStarRec || value === ''));
}
const { nodes } = ctx;
while (nodes.length !== 0) {
const node = nodes.pop();
const { children } = node;
children.reverse();
if (children.some(({ matches }) => matches)) {
node.markMatches();
}
}
};
export const compile = (needles, ctx) => {
ctx.counter = 0;
ctx.links = [];
ctx.nodes = [];
ctx.regex = Object.create(null);
const tower = new Node('*', ctx);
for (let idx = 0; idx < needles.length; idx += 1) {
const needle = needles[idx];
const tree = [parser.parse(needle, ctx)];
applyNeedle(tower, needle, tree, ctx);
}
finalizeTower(tower, ctx);
return tower;
};