From 7e78336141deb721c440b4b5b5b41e5b96dbc1cd Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Mon, 15 Apr 2019 02:12:45 -0700 Subject: [PATCH] Implement evaluateAsync for expressions --- lib/common/expression-parser.ts | 15 ++ lib/common/expression.ts | 279 +++++++++++++++++++------------- 2 files changed, 183 insertions(+), 111 deletions(-) diff --git a/lib/common/expression-parser.ts b/lib/common/expression-parser.ts index 4da1afa..1ae017a 100644 --- a/lib/common/expression-parser.ts +++ b/lib/common/expression-parser.ts @@ -36,6 +36,21 @@ export function map(exp, callback): Expression { return callback(clone || exp); } +export async function mapAsync(exp, callback): Promise { + if (!Array.isArray(exp)) return callback(exp); + + let clone; + for (let i = 1; i < exp.length; ++i) { + const sub = await mapAsync(exp[i], callback); + if (sub !== exp[i]) { + clone = clone || exp.slice(); + clone[i] = sub; + } + } + + return callback(clone || exp); +} + function binaryLeft(operatorsParser, nextParser): parsimmon.Parser<{}> { return parsimmon.seqMap( nextParser, diff --git a/lib/common/expression.ts b/lib/common/expression.ts index 43a934f..7e1a48a 100644 --- a/lib/common/expression.ts +++ b/lib/common/expression.ts @@ -1,11 +1,11 @@ -import { map, parseLikePattern } from "./expression-parser"; +import { map, mapAsync, parseLikePattern } from "./expression-parser"; import { booleanCnf } from "./expression-cnf"; import { naiveDpll } from "./sat-solver"; import { Expression } from "../types"; const isArray = Array.isArray; -const regExpCache = new WeakMap(); +const regExpCache = new WeakMap(); const REDUCE_SKIP = {}; function reduce(exp, callback): Expression { @@ -55,6 +55,126 @@ export function likePatternToRegExp(pat, esc = "", flags = ""): RegExp { return new RegExp(chars.join(""), flags); } +function evalExp(e: Expression): Expression { + if (e[0] === "AND") { + return reduce(e, (a, b) => { + if (!isArray(a)) return a ? b : a; + if (!isArray(b)) return b ? a : b; + return REDUCE_SKIP; + }); + } else if (e[0] === "OR") { + return reduce(e, (a, b) => { + if (!isArray(a)) return a ? a : b; + if (!isArray(b)) return b ? b : a; + return REDUCE_SKIP; + }); + } else if (e[0] === "NOT") { + if (!isArray(e[1])) return !e[1]; + else if (e[1][0] === "NOT") return e[1][1]; + } else if (e[0] === "IS NULL") { + if (isArray(e[1])) return e; + else if (e[1] == null) return true; + else return null; + } else if (e[0] === "IS NOT NULL") { + if (isArray(e[1])) return e; + else if (e[1] != null) return true; + else return null; + } else if (e[0] === "LIKE") { + if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e; + else if ( + e[1] == null || + e[2] == null || + ((e as any[]).length >= 4 && e[3] == null) + ) + return null; + const r = regExpCache.get(e as any[]); + if (!r) { + likePatternToRegExp(e[2], e[3]); + regExpCache.set(e as any[], r); + } + return r.test(e[1]); + } else if (e[0] === "NOT LIKE") { + if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e; + else if ( + e[1] == null || + e[2] == null || + ((e as any[]).length >= 4 && e[3] == null) + ) + return null; + const r = regExpCache.get(e as any[]); + if (!r) { + likePatternToRegExp(e[2], e[3]); + regExpCache.set(e as any[], r); + } + return !r.test(e[1]); + } else if (e[0] === "=") { + if (isArray(e[1]) || isArray(e[2])) return e; + if (e[1] == null || e[2] == null) return null; + return e[1] === e[2]; + } else if (e[0] === "<>") { + if (isArray(e[1]) || isArray(e[2])) return e; + if (e[1] == null || e[2] == null) return null; + return e[1] !== e[2]; + } else if (e[0] === ">") { + if (isArray(e[1]) || isArray(e[2])) return e; + if (e[1] == null || e[2] == null) return null; + return e[1] > e[2]; + } else if (e[0] === ">=") { + if (isArray(e[1]) || isArray(e[2])) return e; + if (e[1] == null || e[2] == null) return null; + return e[1] >= e[2]; + } else if (e[0] === "<") { + if (isArray(e[1]) || isArray(e[2])) return e; + if (e[1] == null || e[2] == null) return null; + return e[1] < e[2]; + } else if (e[0] === "<=") { + if (isArray(e[1]) || isArray(e[2])) return e; + if (e[1] == null || e[2] == null) return null; + return e[1] <= e[2]; + } else if (e[0] === "*") { + return reduce(e, (a, b) => { + if (!isArray(a) && !isArray(b)) { + if (a == null || b == null) return null; + return a * b; + } + return REDUCE_SKIP; + }); + } else if (e[0] === "/") { + return reduce(e, (a, b, i) => { + if (!isArray(a) && !isArray(b)) { + if (a == null || b == null) return null; + return i === 0 ? a / b : a * b; + } + return REDUCE_SKIP; + }); + } else if (e[0] === "+") { + return reduce(e, (a, b) => { + if (!isArray(a) && !isArray(b)) { + if (a == null || b == null) return null; + return a + b; + } + return REDUCE_SKIP; + }); + } else if (e[0] === "-") { + return reduce(e, (a, b, i) => { + if (!isArray(a) && !isArray(b)) { + if (a == null || b == null) return null; + return i === 0 ? a - b : a + b; + } + return REDUCE_SKIP; + }); + } else if (e[0] === "||") { + return reduce(e, (a, b) => { + if (!isArray(a) && !isArray(b)) { + if (a == null || b == null) return null; + return `${a}${b}`; + } + return REDUCE_SKIP; + }); + } + return e; +} + export function evaluate( exp, obj, @@ -63,14 +183,6 @@ export function evaluate( ): string | number | boolean | null; export function evaluate(exp, obj?, now?: number, cb?: Function): Expression; export function evaluate(exp, obj?, now?: number, cb?: Function): Expression { - function getRegExp(pat, esc): RegExp { - const k = `${esc || ""}:${pat}`; - let c = regExpCache.get(exp); - if (!c) regExpCache.set(exp, (c = {})); - if (!c[k]) c[k] = likePatternToRegExp(pat, esc); - return c[k]; - } - return map(exp, e => { if (cb) e = cb(e); if (!isArray(e)) return e; @@ -96,108 +208,53 @@ export function evaluate(exp, obj?, now?: number, cb?: Function): Expression { if (typeof v === "object") v = v.value ? v.value[0] : null; return v; } - } else if (e[0] === "AND") { - return reduce(e, (a, b) => { - if (!isArray(a)) return a ? b : a; - if (!isArray(b)) return b ? a : b; - return REDUCE_SKIP; - }); - } else if (e[0] === "OR") { - return reduce(e, (a, b) => { - if (!isArray(a)) return a ? a : b; - if (!isArray(b)) return b ? b : a; - return REDUCE_SKIP; - }); - } else if (e[0] === "NOT") { - if (!isArray(e[1])) return !e[1]; - else if (e[1][0] === "NOT") return e[1][1]; - } else if (e[0] === "IS NULL") { - if (isArray(e[1])) return e; - else if (e[1] == null) return true; - else return null; - } else if (e[0] === "IS NOT NULL") { - if (isArray(e[1])) return e; - else if (e[1] != null) return true; - else return null; - } else if (e[0] === "LIKE") { - if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e; - else if (e[1] == null || e[2] == null || (e.length >= 4 && e[3] == null)) - return null; - - const r = getRegExp(e[2], e[3]); - return r.test(e[1]); - } else if (e[0] === "NOT LIKE") { - if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e; - else if (e[1] == null || e[2] == null || (e.length >= 4 && e[3] == null)) - return null; - const r = getRegExp(e[2], e[3]); - return !r.test(e[1]); - } else if (e[0] === "=") { - if (isArray(e[1]) || isArray(e[2])) return e; - if (e[1] == null || e[2] == null) return null; - return e[1] === e[2]; - } else if (e[0] === "<>") { - if (isArray(e[1]) || isArray(e[2])) return e; - if (e[1] == null || e[2] == null) return null; - return e[1] !== e[2]; - } else if (e[0] === ">") { - if (isArray(e[1]) || isArray(e[2])) return e; - if (e[1] == null || e[2] == null) return null; - return e[1] > e[2]; - } else if (e[0] === ">=") { - if (isArray(e[1]) || isArray(e[2])) return e; - if (e[1] == null || e[2] == null) return null; - return e[1] >= e[2]; - } else if (e[0] === "<") { - if (isArray(e[1]) || isArray(e[2])) return e; - if (e[1] == null || e[2] == null) return null; - return e[1] < e[2]; - } else if (e[0] === "<=") { - if (isArray(e[1]) || isArray(e[2])) return e; - if (e[1] == null || e[2] == null) return null; - return e[1] <= e[2]; - } else if (e[0] === "*") { - return reduce(e, (a, b) => { - if (!isArray(a) && !isArray(b)) { - if (a == null || b == null) return null; - return a * b; - } - return REDUCE_SKIP; - }); - } else if (e[0] === "/") { - return reduce(e, (a, b, i) => { - if (!isArray(a) && !isArray(b)) { - if (a == null || b == null) return null; - return i === 0 ? a / b : a * b; - } - return REDUCE_SKIP; - }); - } else if (e[0] === "+") { - return reduce(e, (a, b) => { - if (!isArray(a) && !isArray(b)) { - if (a == null || b == null) return null; - return a + b; - } - return REDUCE_SKIP; - }); - } else if (e[0] === "-") { - return reduce(e, (a, b, i) => { - if (!isArray(a) && !isArray(b)) { - if (a == null || b == null) return null; - return i === 0 ? a - b : a + b; - } - return REDUCE_SKIP; - }); - } else if (e[0] === "||") { - return reduce(e, (a, b) => { - if (!isArray(a) && !isArray(b)) { - if (a == null || b == null) return null; - return `${a}${b}`; - } - return REDUCE_SKIP; - }); } - return e; + return evalExp(e); + }); +} + +export async function evaluateAsync( + exp, + obj, + now: number, + cb?: Function +): Promise; +export async function evaluateAsync( + exp, + obj?, + now?: number, + cb?: Function +): Promise; +export async function evaluateAsync( + exp, + obj?, + now?: number, + cb?: Function +): Promise { + return mapAsync(exp, async e => { + if (cb) e = await cb(e); + if (!isArray(e)) return e; + + if (e[0] === "FUNC") { + if (e[1] === "NOW") { + if (now) return now; + } else if (e[1] === "UPPER") { + if (e[2] == null) return null; + if (!isArray(e[2])) return `${e[2]}`.toUpperCase(); + } else if (e[1] === "LOWER") { + if (e[2] == null) return null; + if (!isArray(e[2])) return `${e[2]}`.toLowerCase(); + } + } else if (e[0] === "PARAM") { + if (e[1] == null) return null; + if (obj && !isArray(e[1])) { + let v = obj[e[1]]; + if (v == null) return null; + if (typeof v === "object") v = v.value ? v.value[0] : null; + return v; + } + } + return evalExp(e); }); }