UNPKG

@haz3y0ne/parsexl

Version:

Parses Excel formulas into a clean, well-typed abstract syntax tree you can analyse or evaluate in TypeScript.

82 lines (66 loc) 2.34 kB
// src/ast/normalizeFilters.ts import type { ASTNode } from "../types"; /** * Rebuilds FILTER(array, include[, ifEmpty]) * into FILTER({array}, {include[]}[, {ifEmpty}]) * and flattens boolean-AND masks: (A=1)*(B>1) → [A=1, B>1] */ const normalised = new WeakSet<ASTNode>(); // guards the main walk const clonedMemo = new WeakMap<ASTNode, ASTNode>(); // memoised deep-clone export function normalizeFilters(node: ASTNode): ASTNode { if (normalised.has(node)) return node; normalised.add(node); if (node.type === "FILTER" && node.args.length >= 2) { const [arrayArg, includeRaw, ifEmptyRaw] = node.args; const includeClauses = flattenMultiplication(includeRaw).map(deepClone); const rebuilt: ASTNode[] = [ { type: "array", name: "array", args: [deepClone(arrayArg)], loc: arrayArg.loc, }, { type: "include", name: "include", args: includeClauses, loc: (includeClauses[0] ?? deepClone(includeRaw)).loc, }, ]; if (ifEmptyRaw) { rebuilt.push({ type: "ifEmpty", name: "ifEmpty", args: [deepClone(ifEmptyRaw)], loc: ifEmptyRaw.loc, }); } node.args = rebuilt; } // recurse for (const child of node.args) normalizeFilters(child); return node; } /* ───────────────────────── helpers ─────────────────────────────────────── */ /** Depth-first clone that memoises already-cloned nodes (breaks cycles). */ function deepClone(n: ASTNode): ASTNode { const found = clonedMemo.get(n); if (found) return found; const copy: ASTNode = { ...n, args: [] }; clonedMemo.set(n, copy); if (n.args) copy.args = n.args.map(deepClone); return copy; } /** Flatten a “*” chain; guarded so a truly cyclic tree cannot loop. */ function flattenMultiplication(n: ASTNode | undefined): ASTNode[] { const seen = new WeakSet<ASTNode>(); function inner(node: ASTNode | undefined): ASTNode[] { if (!node || seen.has(node)) return []; seen.add(node); if (node.type === "*" && node.args?.length === 2) { return [...inner(node.args[0]), ...inner(node.args[1])]; } return [node]; } return inner(n); }