Implement CASE operator
This also modifies the behavior of AND/OR operators to strictly return true/false/null whereas previously they could return the value of the first truthy/false operand. The purpose of this change is to maintain expression equivalency before and after minimization.
This commit is contained in:
1 parent
e3ef177842
commit
6cbd59c6ec
8 files changed
+512
-187
No files matched your search
@@ -21,6 +21,7 @@ import * as BI from "./bigint";
|
|||||||
import { espresso, complement, tautology } from "yebool";
|
import { espresso, complement, tautology } from "yebool";
|
||||||
import { Expression } from "../types";
|
import { Expression } from "../types";
|
||||||
import { map } from "./expression-parser";
|
import { map } from "./expression-parser";
|
||||||
|
import { and, evaluateCallback } from "./expression";
|
||||||
|
|
||||||
const ZERO = BI.BigInt(0);
|
const ZERO = BI.BigInt(0);
|
||||||
const ONE = BI.BigInt(1);
|
const ONE = BI.BigInt(1);
|
||||||
@@ -420,20 +421,42 @@ class OrSynth extends BoolExprSynth {
|
|||||||
private exprSynths: BoolExprSynth[];
|
private exprSynths: BoolExprSynth[];
|
||||||
public constructor(...e: BoolExprSynth[]) {
|
public constructor(...e: BoolExprSynth[]) {
|
||||||
super();
|
super();
|
||||||
this.exprSynths = e;
|
this.exprSynths = e.filter((ee) => !(ee instanceof FalseSynth));
|
||||||
|
const unpacked: BoolExprSynth[] = [];
|
||||||
|
this.exprSynths = this.exprSynths.filter((ee) => {
|
||||||
|
if (ee instanceof OrSynth) {
|
||||||
|
unpacked.push(...ee.exprSynths);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
this.exprSynths.push(...unpacked);
|
||||||
}
|
}
|
||||||
public true(context: Context): Minterm[] {
|
public true(context: Context): Minterm[] {
|
||||||
|
if (this.exprSynths.length === 0) return [];
|
||||||
|
if (this.exprSynths.length === 1) return this.exprSynths[0].true(context);
|
||||||
|
if (this.exprSynths.some((e) => e instanceof TrueSynth)) return [[]];
|
||||||
return this.exprSynths.map((e) => e.true(context)).flat();
|
return this.exprSynths.map((e) => e.true(context)).flat();
|
||||||
}
|
}
|
||||||
public false(context: Context): Minterm[] {
|
public false(context: Context): Minterm[] {
|
||||||
|
if (this.exprSynths.length === 0) return [[]];
|
||||||
|
if (this.exprSynths.length === 1) return this.exprSynths[0].false(context);
|
||||||
|
if (
|
||||||
|
this.exprSynths.some(
|
||||||
|
(e) => e instanceof TrueSynth || e instanceof NullSynth
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return [];
|
||||||
return complement(
|
return complement(
|
||||||
this.exprSynths.map((e) => complement(e.false(context))).flat()
|
this.exprSynths.map((e) => complement(e.false(context))).flat()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public null(context: Context): Minterm[] {
|
public null(context: Context): Minterm[] {
|
||||||
|
if (this.exprSynths.length === 0) return [];
|
||||||
|
if (this.exprSynths.length === 1) return this.exprSynths[0].null(context);
|
||||||
|
const n = this.exprSynths.map((e) => e.null(context)).flat();
|
||||||
const t = this.exprSynths.map((e) => e.true(context)).flat();
|
const t = this.exprSynths.map((e) => e.true(context)).flat();
|
||||||
const f = this.exprSynths.map((e) => e.false(context)).flat();
|
return complement([...complement(n), ...t]);
|
||||||
return complement([...t, ...f]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,20 +464,109 @@ class AndSynth extends BoolExprSynth {
|
|||||||
private exprSynths: BoolExprSynth[];
|
private exprSynths: BoolExprSynth[];
|
||||||
public constructor(...e: BoolExprSynth[]) {
|
public constructor(...e: BoolExprSynth[]) {
|
||||||
super();
|
super();
|
||||||
this.exprSynths = e;
|
this.exprSynths = e.filter((ee) => !(ee instanceof TrueSynth));
|
||||||
|
const unpacked: BoolExprSynth[] = [];
|
||||||
|
this.exprSynths = this.exprSynths.filter((ee) => {
|
||||||
|
if (ee instanceof AndSynth) {
|
||||||
|
unpacked.push(...ee.exprSynths);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
this.exprSynths.push(...unpacked);
|
||||||
}
|
}
|
||||||
public true(context: Context): Minterm[] {
|
public true(context: Context): Minterm[] {
|
||||||
|
if (this.exprSynths.length === 0) return [[]];
|
||||||
|
if (this.exprSynths.length === 1) return this.exprSynths[0].true(context);
|
||||||
|
if (
|
||||||
|
this.exprSynths.some(
|
||||||
|
(e) => e instanceof FalseSynth || e instanceof NullSynth
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return [];
|
||||||
return complement(
|
return complement(
|
||||||
this.exprSynths.map((e) => complement(e.true(context))).flat()
|
this.exprSynths.map((e) => complement(e.true(context))).flat()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public false(context: Context): Minterm[] {
|
public false(context: Context): Minterm[] {
|
||||||
|
if (this.exprSynths.length === 0) return [];
|
||||||
|
if (this.exprSynths.length === 1) return this.exprSynths[0].false(context);
|
||||||
|
if (this.exprSynths.some((e) => e instanceof FalseSynth)) return [[]];
|
||||||
return this.exprSynths.map((e) => e.false(context)).flat();
|
return this.exprSynths.map((e) => e.false(context)).flat();
|
||||||
}
|
}
|
||||||
public null(context: Context): Minterm[] {
|
public null(context: Context): Minterm[] {
|
||||||
const t = this.exprSynths.map((e) => e.true(context)).flat();
|
if (this.exprSynths.length === 0) return [];
|
||||||
|
if (this.exprSynths.length === 1) return this.exprSynths[0].null(context);
|
||||||
|
const n = this.exprSynths.map((e) => e.null(context)).flat();
|
||||||
const f = this.exprSynths.map((e) => e.false(context)).flat();
|
const f = this.exprSynths.map((e) => e.false(context)).flat();
|
||||||
return complement([...t, ...f]);
|
return complement([...complement(n), ...f]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CaseSynth extends BoolExprSynth {
|
||||||
|
private exprSynths: BoolExprSynth[];
|
||||||
|
public constructor(e: BoolExprSynth[]) {
|
||||||
|
super();
|
||||||
|
this.exprSynths = e;
|
||||||
|
}
|
||||||
|
public true(context: Context): Minterm[] {
|
||||||
|
const minterms: Minterm[] = [];
|
||||||
|
const cumulative: Minterm[] = [];
|
||||||
|
for (let i = 0; i < this.exprSynths.length; i += 2) {
|
||||||
|
const w = this.exprSynths[i].true(context);
|
||||||
|
const t = this.exprSynths[i + 1].true(context);
|
||||||
|
minterms.push(
|
||||||
|
...complement([...cumulative, ...complement(w), ...complement(t)])
|
||||||
|
);
|
||||||
|
if (i < this.exprSynths.length - 2) {
|
||||||
|
cumulative.push(
|
||||||
|
...complement([
|
||||||
|
...this.exprSynths[i].false(context),
|
||||||
|
...this.exprSynths[i].null(context),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minterms;
|
||||||
|
}
|
||||||
|
public false(context: Context): Minterm[] {
|
||||||
|
const minterms: Minterm[] = [];
|
||||||
|
const cumulative: Minterm[] = [];
|
||||||
|
for (let i = 0; i < this.exprSynths.length; i += 2) {
|
||||||
|
const w = this.exprSynths[i].true(context);
|
||||||
|
const t = this.exprSynths[i + 1].false(context);
|
||||||
|
minterms.push(
|
||||||
|
...complement([...cumulative, ...complement(w), ...complement(t)])
|
||||||
|
);
|
||||||
|
if (i < this.exprSynths.length - 2) {
|
||||||
|
cumulative.push(
|
||||||
|
...complement([
|
||||||
|
...this.exprSynths[i].false(context),
|
||||||
|
...this.exprSynths[i].null(context),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minterms;
|
||||||
|
}
|
||||||
|
public null(context: Context): Minterm[] {
|
||||||
|
const minterms: Minterm[] = [];
|
||||||
|
const cumulative: Minterm[] = [];
|
||||||
|
for (let i = 0; i < this.exprSynths.length; i += 2) {
|
||||||
|
const w = this.exprSynths[i].true(context);
|
||||||
|
const t = this.exprSynths[i + 1].null(context);
|
||||||
|
minterms.push(
|
||||||
|
...complement([...cumulative, ...complement(w), ...complement(t)])
|
||||||
|
);
|
||||||
|
cumulative.push(
|
||||||
|
...complement([
|
||||||
|
...this.exprSynths[i].false(context),
|
||||||
|
...this.exprSynths[i].null(context),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
minterms.push(...complement([...cumulative]));
|
||||||
|
return minterms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,59 +582,122 @@ const SWAPPED_OPS = {
|
|||||||
"<=": ">=",
|
"<=": ">=",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function normalize(expr: Expression): Expression {
|
function normalizeCallback(exp: Expression): Expression {
|
||||||
expr = map(expr, (exp) => {
|
|
||||||
if (!Array.isArray(exp)) return exp;
|
if (!Array.isArray(exp)) return exp;
|
||||||
const op = exp[0];
|
const op = exp[0];
|
||||||
|
|
||||||
if (op === "+") {
|
if (op === "CASE") {
|
||||||
const args = exp.slice(1).map((e) => {
|
const res = [] as [Expression, Expression][];
|
||||||
|
for (let i = 1; i < exp.length; i += 2) {
|
||||||
|
let w = exp[i];
|
||||||
|
if (w instanceof Polynomial) w = JSON.parse(w.toString());
|
||||||
|
if (!Array.isArray(w) && !w) continue;
|
||||||
|
const t = exp[i + 1];
|
||||||
|
if (!Array.isArray(t) || t[0] !== "CASE") {
|
||||||
|
res.push([w, t]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let j = 1; j < t.length; j += 2) res.push([and(w, t[j]), t[j + 1]]);
|
||||||
|
res.push([w, null]);
|
||||||
|
if (!Array.isArray(w) && w) break;
|
||||||
|
}
|
||||||
|
while (res[res.length - 1][1] == null) res.pop();
|
||||||
|
return ["CASE", ...res.flat()];
|
||||||
|
}
|
||||||
|
|
||||||
|
const permutations: Map<number, [Expression, Expression][]> = new Map();
|
||||||
|
for (const [i, e] of exp.entries()) {
|
||||||
|
if (!Array.isArray(e)) continue;
|
||||||
|
if (e[0] !== "CASE") continue;
|
||||||
|
const perms: [Expression, Expression][] = [];
|
||||||
|
for (let j = 1; j < e.length; j += 2) perms.push([e[j], e[j + 1]]);
|
||||||
|
permutations.set(i, perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permutations.size) {
|
||||||
|
let res: [Expression, Expression][] = [[true, exp]];
|
||||||
|
for (const [i, perms] of permutations) {
|
||||||
|
const res2: [Expression, Expression][] = [];
|
||||||
|
for (const [w, t] of perms) {
|
||||||
|
res2.push(
|
||||||
|
...res.map((r) => {
|
||||||
|
const e = (r[1] as Expression[]).slice();
|
||||||
|
e[i] = t;
|
||||||
|
return [and(w, r[0]), e] as [Expression, Expression];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
res = res2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const r of res) r[1] = normalizeCallback(r[1]);
|
||||||
|
|
||||||
|
if (res[0][0] === true) return res[0][1];
|
||||||
|
while (res[res.length - 1][1] == null) res.pop();
|
||||||
|
|
||||||
|
return ["CASE", ...res.flat()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPolynomial(e: Expression): Polynomial {
|
||||||
|
if (e == null) return null;
|
||||||
if (e instanceof Polynomial) return e;
|
if (e instanceof Polynomial) return e;
|
||||||
else if (typeof e === "number") return Polynomial.fromConstant(e);
|
if (typeof e === "number") return Polynomial.fromConstant(e);
|
||||||
else return Polynomial.fromIndeterminate(e);
|
if (typeof e === "string")
|
||||||
});
|
return Polynomial.fromConstant(parseFloat(e) || 0);
|
||||||
|
if (typeof e === "boolean") return Polynomial.fromConstant(+e);
|
||||||
|
return Polynomial.fromIndeterminate(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op === "+") {
|
||||||
|
const args: Polynomial[] = [];
|
||||||
|
for (let i = 1; i < exp.length; ++i) {
|
||||||
|
const p = toPolynomial(exp[i]);
|
||||||
|
if (p == null) return null;
|
||||||
|
args.push(p);
|
||||||
|
}
|
||||||
return (args.reduce(
|
return (args.reduce(
|
||||||
(previousValue, currentValue) => previousValue.add(currentValue),
|
(previousValue, currentValue) => previousValue.add(currentValue),
|
||||||
ADDITIVE_IDENTITY
|
ADDITIVE_IDENTITY
|
||||||
) as unknown) as Expression;
|
) as unknown) as Expression;
|
||||||
} else if (op === "*") {
|
} else if (op === "*") {
|
||||||
const args = exp.slice(1).map((e) => {
|
const args: Polynomial[] = [];
|
||||||
if (e instanceof Polynomial) return e;
|
for (let i = 1; i < exp.length; ++i) {
|
||||||
else if (typeof e === "number") return Polynomial.fromConstant(e);
|
const p = toPolynomial(exp[i]);
|
||||||
else return Polynomial.fromIndeterminate(e);
|
if (p == null) return null;
|
||||||
});
|
args.push(p);
|
||||||
|
}
|
||||||
return (args.reduce(
|
return (args.reduce(
|
||||||
(previousValue, currentValue) => previousValue.multiply(currentValue),
|
(previousValue, currentValue) => previousValue.multiply(currentValue),
|
||||||
MULTIPLICATIVE_IDENTITY
|
MULTIPLICATIVE_IDENTITY
|
||||||
) as unknown) as Expression;
|
) as unknown) as Expression;
|
||||||
} else if (op === "-") {
|
} else if (op === "-") {
|
||||||
const args = exp.slice(1).map((e) => {
|
const args: Polynomial[] = [];
|
||||||
if (e instanceof Polynomial) return e;
|
for (let i = 1; i < exp.length; ++i) {
|
||||||
else if (typeof e === "number") return Polynomial.fromConstant(e);
|
const p = toPolynomial(exp[i]);
|
||||||
else return Polynomial.fromIndeterminate(e);
|
if (p == null) return null;
|
||||||
});
|
args.push(p);
|
||||||
|
}
|
||||||
return (args.reduce((previousValue, currentValue) =>
|
return (args.reduce((previousValue, currentValue) =>
|
||||||
previousValue.subtract(currentValue)
|
previousValue.subtract(currentValue)
|
||||||
) as unknown) as Expression;
|
) as unknown) as Expression;
|
||||||
} else if (op === "/") {
|
} else if (op === "/") {
|
||||||
const args = exp.slice(1).map((e) => {
|
const args: Polynomial[] = [];
|
||||||
if (e instanceof Polynomial) return e;
|
for (let i = 1; i < exp.length; ++i) {
|
||||||
else if (typeof e === "number") return Polynomial.fromConstant(e);
|
const p = toPolynomial(exp[i]);
|
||||||
else return Polynomial.fromIndeterminate(e);
|
if (p == null) return null;
|
||||||
});
|
args.push(p);
|
||||||
|
}
|
||||||
return (args.reduce((previousValue, currentValue) =>
|
return (args.reduce((previousValue, currentValue) =>
|
||||||
previousValue.divide(currentValue)
|
previousValue.divide(currentValue)
|
||||||
) as unknown) as Expression;
|
) as unknown) as Expression;
|
||||||
} else if (["=", "<>", ">", ">=", "<", "<="].includes(op)) {
|
} else if (["=", "<>", ">", ">=", "<", "<="].includes(op)) {
|
||||||
|
if (exp[1] == null || exp[2] == null) return null;
|
||||||
let lhs: Polynomial, rhs: Polynomial;
|
let lhs: Polynomial, rhs: Polynomial;
|
||||||
if (exp[1] instanceof Polynomial) lhs = exp[1];
|
if (exp[1] instanceof Polynomial) lhs = exp[1];
|
||||||
else if (typeof exp[1] === "number")
|
else if (typeof exp[1] === "number") lhs = Polynomial.fromConstant(exp[1]);
|
||||||
lhs = Polynomial.fromConstant(exp[1]);
|
|
||||||
|
|
||||||
if (exp[2] instanceof Polynomial) rhs = exp[2];
|
if (exp[2] instanceof Polynomial) rhs = exp[2];
|
||||||
else if (typeof exp[2] === "number")
|
else if (typeof exp[2] === "number") rhs = Polynomial.fromConstant(exp[2]);
|
||||||
rhs = Polynomial.fromConstant(exp[2]);
|
|
||||||
|
|
||||||
if (lhs || rhs) {
|
if (lhs || rhs) {
|
||||||
if (!lhs) lhs = Polynomial.fromIndeterminate(exp[1]);
|
if (!lhs) lhs = Polynomial.fromIndeterminate(exp[1]);
|
||||||
@@ -561,8 +736,7 @@ export function normalize(expr: Expression): Expression {
|
|||||||
rhs = rhs.multiply(reciprocal);
|
rhs = rhs.multiply(reciprocal);
|
||||||
|
|
||||||
const keys = lhs.terms[0].indeterminates.sortedKeys;
|
const keys = lhs.terms[0].indeterminates.sortedKeys;
|
||||||
let invert =
|
let invert = lhs.terms[0].indeterminates.map.get(keys[0]) < 0 ? -1 : 0;
|
||||||
lhs.terms[0].indeterminates.map.get(keys[0]) < 0 ? -1 : 0;
|
|
||||||
|
|
||||||
for (const t of lhs.terms)
|
for (const t of lhs.terms)
|
||||||
for (const v of t.indeterminates.map.values()) invert += v;
|
for (const v of t.indeterminates.map.values()) invert += v;
|
||||||
@@ -584,20 +758,27 @@ export function normalize(expr: Expression): Expression {
|
|||||||
e instanceof Polynomial ? JSON.parse(e.toString()) : e
|
e instanceof Polynomial ? JSON.parse(e.toString()) : e
|
||||||
);
|
);
|
||||||
|
|
||||||
|
exp = evaluateCallback(exp);
|
||||||
|
|
||||||
return exp;
|
return exp;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
export function normalize(expr: Expression): Expression {
|
||||||
|
expr = map(expr, normalizeCallback);
|
||||||
if (expr instanceof Polynomial) expr = JSON.parse(expr.toString());
|
if (expr instanceof Polynomial) expr = JSON.parse(expr.toString());
|
||||||
|
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toBoolExprSynth(e: Expression | BoolExprSynth): BoolExprSynth {
|
function toBoolExprSynth(e: Expression | BoolExprSynth): BoolExprSynth {
|
||||||
if (e instanceof BoolExprSynth) return e;
|
if (e instanceof BoolExprSynth) return e;
|
||||||
else if (Array.isArray(e)) return new VarSynth(e);
|
if (Array.isArray(e)) {
|
||||||
else if (e == null) return new NullSynth();
|
if (e[0] === "CASE")
|
||||||
else if (e) return new TrueSynth();
|
return new CaseSynth(e.slice(1).map((ee) => toBoolExprSynth(ee)));
|
||||||
else return new FalseSynth();
|
return new VarSynth(e);
|
||||||
|
}
|
||||||
|
if (e == null) return new NullSynth();
|
||||||
|
if (e) return new TrueSynth();
|
||||||
|
return new FalseSynth();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sopToExpression(
|
function sopToExpression(
|
||||||
@@ -608,7 +789,7 @@ function sopToExpression(
|
|||||||
const res: Expression[] = [];
|
const res: Expression[] = [];
|
||||||
for (const s of sop) {
|
for (const s of sop) {
|
||||||
if (!s.length) return true;
|
if (!s.length) return true;
|
||||||
const and: Expression[] = [];
|
const conjs: Expression[] = [];
|
||||||
for (const i of s) {
|
for (const i of s) {
|
||||||
let expr = expressions.get(i >>> 2);
|
let expr = expressions.get(i >>> 2);
|
||||||
if (!(i & 1) !== !(i & 2)) expr = ["NOT", expr];
|
if (!(i & 1) !== !(i & 2)) expr = ["NOT", expr];
|
||||||
@@ -624,10 +805,10 @@ function sopToExpression(
|
|||||||
else if (e[0] === "NOT") expr = e[1];
|
else if (e[0] === "NOT") expr = e[1];
|
||||||
else expr = ["NOT", e];
|
else expr = ["NOT", e];
|
||||||
}
|
}
|
||||||
and.push(expr);
|
conjs.push(expr);
|
||||||
}
|
}
|
||||||
if (and.length > 1) res.push(["AND" as any].concat(and));
|
if (conjs.length > 1) res.push(["AND" as any].concat(conjs));
|
||||||
else res.push(and[0]);
|
else res.push(conjs[0]);
|
||||||
}
|
}
|
||||||
if (res.length > 1) return ["OR" as any].concat(res);
|
if (res.length > 1) return ["OR" as any].concat(res);
|
||||||
return res[0];
|
return res[0];
|
||||||
@@ -825,25 +1006,20 @@ function boolExprSynthToExpression(boolExpr: BoolExprSynth): Expression {
|
|||||||
const { dcSet, isNull } = generateDcSetAndIsNull(variables);
|
const { dcSet, isNull } = generateDcSetAndIsNull(variables);
|
||||||
minterms = sanitizeMinterms(minterms, isNull);
|
minterms = sanitizeMinterms(minterms, isNull);
|
||||||
|
|
||||||
function canRaise(idx: number, set: Set<number>): boolean {
|
const canRaise = (idx: number, set: Set<number>): boolean =>
|
||||||
if (set.size === 1) return true;
|
!isNull.has(idx >> 2) || !(idx & 1) || !set.has(idx ^ 3);
|
||||||
const i = idx >>> 2;
|
|
||||||
const n = isNull.get(i);
|
|
||||||
if (n == null) {
|
|
||||||
for (const [k, v] of isNull) {
|
|
||||||
if (v !== i) continue;
|
|
||||||
if (set.has((k << 2) ^ 1) || set.has((k << 2) ^ 3)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (idx & 1) === 0 || set.has((n << 2) ^ 2) || set.has((n << 2) ^ 3);
|
|
||||||
}
|
|
||||||
minterms = espresso(minterms, dcSet, { canRaise });
|
minterms = espresso(minterms, dcSet, { canRaise });
|
||||||
return sopToExpression(minterms, variables);
|
return sopToExpression(minterms, variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapCallback(exp: Expression): Expression {
|
function mapCallback(exp: Expression | BoolExprSynth): Expression {
|
||||||
if (!Array.isArray(exp)) return exp;
|
if (!Array.isArray(exp)) return exp as Expression;
|
||||||
|
if (exp[0] === "CASE") {
|
||||||
|
exp = exp.slice();
|
||||||
|
for (let i = 1; i < exp.length; i += 2) exp[i] = toBoolExprSynth(exp[i]);
|
||||||
|
return exp;
|
||||||
|
}
|
||||||
|
|
||||||
const op = exp[0];
|
const op = exp[0];
|
||||||
if (op === "IS NULL")
|
if (op === "IS NULL")
|
||||||
return (new IsNullSynth(toBoolExprSynth(exp[1])) as unknown) as Expression;
|
return (new IsNullSynth(toBoolExprSynth(exp[1])) as unknown) as Expression;
|
||||||
@@ -864,16 +1040,53 @@ function mapCallback(exp: Expression): Expression {
|
|||||||
) as unknown) as Expression;
|
) as unknown) as Expression;
|
||||||
}
|
}
|
||||||
for (let i = 1; i < exp.length; ++i) {
|
for (let i = 1; i < exp.length; ++i) {
|
||||||
if (exp[i] instanceof BoolExprSynth)
|
if (exp[i] instanceof BoolExprSynth) {
|
||||||
exp[i] = boolExprSynthToExpression(exp[i]);
|
exp[i] = boolExprSynthToExpression(exp[i]);
|
||||||
|
} else if (Array.isArray(exp[i]) && exp[i][0] === "CASE") {
|
||||||
|
for (let j = 2; j < exp[i].length; j += 2) {
|
||||||
|
if (exp[i][j] instanceof BoolExprSynth)
|
||||||
|
exp[i][j] = boolExprSynthToExpression(exp[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function minimize(expr: Expression): Expression {
|
export function minimize(expr: Expression, boolean = false): Expression {
|
||||||
expr = normalize(expr);
|
expr = normalize(expr);
|
||||||
expr = map(expr, mapCallback);
|
expr = map(expr, mapCallback);
|
||||||
if (expr instanceof BoolExprSynth) return boolExprSynthToExpression(expr);
|
if (Array.isArray(expr) && expr[0] === "CASE") {
|
||||||
|
if (!boolean) {
|
||||||
|
const context: Map<string, number> = new Map();
|
||||||
|
const whens = expr.filter((e, i) => i % 2).map((e) => e.true(context));
|
||||||
|
const variables = new Map(
|
||||||
|
Array.from(context).map(([k, v]) => [v, JSON.parse(k) as Expression])
|
||||||
|
);
|
||||||
|
const { dcSet, isNull } = generateDcSetAndIsNull(variables);
|
||||||
|
const canRaise = (idx: number, set: Set<number>): boolean =>
|
||||||
|
!isNull.has(idx >> 2) || !(idx & 1) || !set.has(idx ^ 3);
|
||||||
|
const res: Expression = ["CASE"];
|
||||||
|
for (let i = 1; i < expr.length; i += 2) {
|
||||||
|
let minterms = sanitizeMinterms(whens[(i - 1) / 2], isNull);
|
||||||
|
minterms = espresso(minterms, dcSet, { canRaise });
|
||||||
|
if (!minterms.length) continue;
|
||||||
|
const w = sopToExpression(minterms, variables);
|
||||||
|
let t = expr[i + 1];
|
||||||
|
if (t instanceof BoolExprSynth) t = boolExprSynthToExpression(t);
|
||||||
|
res.push(w, t);
|
||||||
|
if (w === true) break;
|
||||||
|
dcSet.push(...minterms);
|
||||||
|
}
|
||||||
|
while (res[res.length - 1] == null) res.splice(-2);
|
||||||
|
if (res.length < 3) return null;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
expr = (toBoolExprSynth(expr) as unknown) as Expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boolean) expr = (toBoolExprSynth(expr) as unknown) as Expression;
|
||||||
|
|
||||||
|
if (expr instanceof BoolExprSynth) expr = boolExprSynthToExpression(expr);
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,6 +1101,8 @@ export function unionDiff(
|
|||||||
expr2 = map(expr2, mapCallback);
|
expr2 = map(expr2, mapCallback);
|
||||||
|
|
||||||
if (!expr1) {
|
if (!expr1) {
|
||||||
|
if (Array.isArray(expr2) && expr2[0] === "CASE")
|
||||||
|
expr2 = (toBoolExprSynth(expr2) as unknown) as Expression;
|
||||||
if (expr2 instanceof BoolExprSynth)
|
if (expr2 instanceof BoolExprSynth)
|
||||||
expr2 = boolExprSynthToExpression(expr2);
|
expr2 = boolExprSynthToExpression(expr2);
|
||||||
return [expr2, expr2];
|
return [expr2, expr2];
|
||||||
@@ -921,19 +1136,8 @@ export function unionDiff(
|
|||||||
isNull
|
isNull
|
||||||
);
|
);
|
||||||
|
|
||||||
function canRaise(idx: number, set: Set<number>): boolean {
|
const canRaise = (idx: number, set: Set<number>): boolean =>
|
||||||
if (set.size === 1) return true;
|
!isNull.has(idx >> 2) || !(idx & 1) || !set.has(idx ^ 3);
|
||||||
const i = idx >>> 2;
|
|
||||||
const n = isNull.get(i);
|
|
||||||
if (n == null) {
|
|
||||||
for (const [k, v] of isNull) {
|
|
||||||
if (v !== i) continue;
|
|
||||||
if (set.has((k << 2) ^ 1) || set.has((k << 2) ^ 3)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (idx & 1) === 0 || set.has((n << 2) ^ 2) || set.has((n << 2) ^ 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
const union = espresso(unionMinterms, dcSet, { canRaise });
|
const union = espresso(unionMinterms, dcSet, { canRaise });
|
||||||
const diff = espresso(diffMinterms, dcSet, { canRaise });
|
const diff = espresso(diffMinterms, dcSet, { canRaise });
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ const lang = parsimmon.createLanguage({
|
|||||||
NumberValue: function () {
|
NumberValue: function () {
|
||||||
return parsimmon
|
return parsimmon
|
||||||
.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
|
.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
|
||||||
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
.skip(parsimmon.optWhitespace)
|
.skip(parsimmon.optWhitespace)
|
||||||
.map(Number)
|
.map(Number)
|
||||||
.desc("number");
|
.desc("number");
|
||||||
@@ -223,6 +224,48 @@ const lang = parsimmon.createLanguage({
|
|||||||
(f, args) => ["FUNC", f.toUpperCase()].concat(args)
|
(f, args) => ["FUNC", f.toUpperCase()].concat(args)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
WhenPair: function (r) {
|
||||||
|
return parsimmon.seq(
|
||||||
|
parsimmon
|
||||||
|
.regexp(/when/i)
|
||||||
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
|
.skip(parsimmon.optWhitespace)
|
||||||
|
.desc("WHEN")
|
||||||
|
.then(r.Expression),
|
||||||
|
parsimmon
|
||||||
|
.regexp(/then/i)
|
||||||
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
|
.skip(parsimmon.optWhitespace)
|
||||||
|
.desc("THEN")
|
||||||
|
.then(r.Expression)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
CaseStatement: function (r) {
|
||||||
|
return parsimmon.seqMap(
|
||||||
|
parsimmon
|
||||||
|
.regexp(/case/i)
|
||||||
|
.result("CASE")
|
||||||
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
|
.skip(parsimmon.optWhitespace)
|
||||||
|
.desc("CASE"),
|
||||||
|
r.WhenPair.many(),
|
||||||
|
parsimmon
|
||||||
|
.regexp(/else/i)
|
||||||
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
|
.skip(parsimmon.optWhitespace)
|
||||||
|
.desc("ELSE")
|
||||||
|
.then(r.Expression)
|
||||||
|
.map((e) => [[true, e]])
|
||||||
|
.fallback(null)
|
||||||
|
.skip(
|
||||||
|
parsimmon
|
||||||
|
.regex(/end/i)
|
||||||
|
.notFollowedBy(parsimmon.regexp(/[a-zA-Z0-9_]/))
|
||||||
|
)
|
||||||
|
.skip(parsimmon.optWhitespace),
|
||||||
|
(...arr) => arr.flat(2)
|
||||||
|
);
|
||||||
|
},
|
||||||
Value: function (r) {
|
Value: function (r) {
|
||||||
return parsimmon.alt(
|
return parsimmon.alt(
|
||||||
r.NullValue,
|
r.NullValue,
|
||||||
@@ -230,7 +273,8 @@ const lang = parsimmon.createLanguage({
|
|||||||
r.NumberValue,
|
r.NumberValue,
|
||||||
r.StringValueSql,
|
r.StringValueSql,
|
||||||
r.StringValueJs,
|
r.StringValueJs,
|
||||||
r.FuncValue
|
r.FuncValue,
|
||||||
|
r.CaseStatement
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
ValueExpression: function (r) {
|
ValueExpression: function (r) {
|
||||||
@@ -378,6 +422,17 @@ export function stringify(exp: Expression, level = 0): string {
|
|||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (op === "CASE") {
|
||||||
|
const parts: string[] = ["CASE"];
|
||||||
|
for (let i = 1; i < exp.length - 1; i += 2) {
|
||||||
|
if (!Array.isArray(exp[i]) && exp[i]) {
|
||||||
|
if (exp[i + 1] != null) parts.push("ELSE", stringify(exp[i + 1]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parts.push("WHEN", stringify(exp[i]), "THEN", stringify(exp[i + 1]));
|
||||||
|
}
|
||||||
|
parts.push("END");
|
||||||
|
return parts.join(" ");
|
||||||
} else if (op in opLevels) {
|
} else if (op in opLevels) {
|
||||||
const parts = exp.slice(1).map((e, i) => {
|
const parts = exp.slice(1).map((e, i) => {
|
||||||
return stringify(e, opLevels[exp[0]] + Math.min(i - 1, 0));
|
return stringify(e, opLevels[exp[0]] + Math.min(i - 1, 0));
|
||||||
|
|||||||
+152
-102
@@ -72,124 +72,162 @@ export function likePatternToRegExp(pat: string, esc = "", flags = ""): RegExp {
|
|||||||
return new RegExp(chars.join(""), flags);
|
return new RegExp(chars.join(""), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
function evalExp(e: Expression): Expression {
|
function compare(
|
||||||
if (e[0] === "AND") {
|
a: boolean | number | string,
|
||||||
return reduce(e, (a, b) => {
|
b: boolean | number | string
|
||||||
if (!isArray(a)) return a ? b : a;
|
): number {
|
||||||
if (!isArray(b)) return b ? a : b;
|
if (typeof a === "boolean") a = +a;
|
||||||
return REDUCE_SKIP;
|
if (typeof b === "boolean") b = +b;
|
||||||
});
|
if (typeof a !== typeof b) return typeof a === "string" ? 1 : -1;
|
||||||
} else if (e[0] === "OR") {
|
return a > b ? 1 : a < b ? -1 : 0;
|
||||||
return reduce(e, (a, b) => {
|
}
|
||||||
if (!isArray(a)) return a ? a : b;
|
|
||||||
if (!isArray(b)) return b ? b : a;
|
function toNumber(a: boolean | number | string): number {
|
||||||
return REDUCE_SKIP;
|
switch (typeof a) {
|
||||||
});
|
case "number":
|
||||||
} else if (e[0] === "NOT") {
|
return a;
|
||||||
if (!isArray(e[1])) return !e[1];
|
case "boolean":
|
||||||
else if (e[1][0] === "NOT") return e[1][1];
|
return +a;
|
||||||
} else if (e[0] === "IS NULL") {
|
case "string":
|
||||||
if (isArray(e[1])) return e;
|
return parseFloat(a) || 0;
|
||||||
else if (e[1] == null) return true;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evaluateCallback(exp: Expression): Expression {
|
||||||
|
if (!Array.isArray(exp)) return exp;
|
||||||
|
if (exp[0] === "CASE") {
|
||||||
|
for (let i = 1; i < exp.length; i += 2) {
|
||||||
|
if (Array.isArray(exp[i])) return exp;
|
||||||
|
if (exp[i]) return exp[i + 1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else if (exp[0] === "AND") {
|
||||||
|
for (let i = 1; i < exp.length; ++i)
|
||||||
|
if (!Array.isArray(exp[i]) && exp[i] != null && !exp[i]) return false;
|
||||||
|
const args: Expression[] = [];
|
||||||
|
for (let i = 1; i < exp.length; ++i) {
|
||||||
|
const ee = exp[i];
|
||||||
|
if (ee == null) return null;
|
||||||
|
if (Array.isArray(ee)) {
|
||||||
|
if (ee[0] === "AND") args.push(...ee.slice(1));
|
||||||
|
else args.push(ee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!args.length) return true;
|
||||||
|
if (args.length === 1) args.push(true);
|
||||||
|
return ["AND", ...args];
|
||||||
|
} else if (exp[0] === "OR") {
|
||||||
|
const args: Expression[] = [];
|
||||||
|
for (let i = 1; i < exp.length; ++i) {
|
||||||
|
const ee = exp[i];
|
||||||
|
if (Array.isArray(ee)) {
|
||||||
|
if (ee[0] === "OR") args.push(...ee.slice(1));
|
||||||
|
else args.push(ee);
|
||||||
|
} else if (ee) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!args.length) return exp.some((ee) => ee == null) ? null : false;
|
||||||
|
if (args.length === 1) args.push(false);
|
||||||
|
return ["OR", ...args];
|
||||||
|
} else if (exp[0] === "NOT") {
|
||||||
|
if (exp[1] == null) return null;
|
||||||
|
if (!isArray(exp[1])) return !exp[1];
|
||||||
|
else if (exp[1][0] === "NOT") return exp[1][1];
|
||||||
|
} else if (exp[0] === "IS NULL") {
|
||||||
|
if (isArray(exp[1])) return exp;
|
||||||
|
else if (exp[1] == null) return true;
|
||||||
else return null;
|
else return null;
|
||||||
} else if (e[0] === "IS NOT NULL") {
|
} else if (exp[0] === "IS NOT NULL") {
|
||||||
if (isArray(e[1])) return e;
|
if (isArray(exp[1])) return exp;
|
||||||
else if (e[1] != null) return true;
|
else if (exp[1] != null) return true;
|
||||||
else return null;
|
else return null;
|
||||||
} else if (e[0] === "LIKE") {
|
} else if (exp[0] === "LIKE") {
|
||||||
if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e;
|
if (isArray(exp[1]) || isArray(exp[2]) || isArray(exp[3])) return exp;
|
||||||
else if (
|
else if (
|
||||||
e[1] == null ||
|
exp[1] == null ||
|
||||||
e[2] == null ||
|
exp[2] == null ||
|
||||||
((e as any[]).length >= 4 && e[3] == null)
|
((exp as any[]).length >= 4 && exp[3] == null)
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
let r = regExpCache.get(e as any[]);
|
let r = regExpCache.get(exp as any[]);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
r = likePatternToRegExp(e[2], e[3]);
|
r = likePatternToRegExp(exp[2], exp[3]);
|
||||||
regExpCache.set(e as any[], r);
|
regExpCache.set(exp as any[], r);
|
||||||
}
|
}
|
||||||
return r.test(e[1]);
|
return r.test(exp[1]);
|
||||||
} else if (e[0] === "NOT LIKE") {
|
} else if (exp[0] === "NOT LIKE") {
|
||||||
if (isArray(e[1]) || isArray(e[2]) || isArray(e[3])) return e;
|
if (isArray(exp[1]) || isArray(exp[2]) || isArray(exp[3])) return exp;
|
||||||
else if (
|
else if (
|
||||||
e[1] == null ||
|
exp[1] == null ||
|
||||||
e[2] == null ||
|
exp[2] == null ||
|
||||||
((e as any[]).length >= 4 && e[3] == null)
|
((exp as any[]).length >= 4 && exp[3] == null)
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
let r = regExpCache.get(e as any[]);
|
let r = regExpCache.get(exp as any[]);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
r = likePatternToRegExp(e[2], e[3]);
|
r = likePatternToRegExp(exp[2], exp[3]);
|
||||||
regExpCache.set(e as any[], r);
|
regExpCache.set(exp as any[], r);
|
||||||
}
|
}
|
||||||
return !r.test(e[1]);
|
return !r.test(exp[1]);
|
||||||
} else if (e[0] === "=") {
|
} else if (exp[0] === "=") {
|
||||||
if (isArray(e[1]) || isArray(e[2])) return e;
|
if (exp[1] == null || exp[2] == null) return null;
|
||||||
if (e[1] == null || e[2] == null) return null;
|
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
||||||
return e[1] === e[2];
|
return compare(exp[1], exp[2]) === 0;
|
||||||
} else if (e[0] === "<>") {
|
} else if (exp[0] === "<>") {
|
||||||
if (isArray(e[1]) || isArray(e[2])) return e;
|
if (exp[1] == null || exp[2] == null) return null;
|
||||||
if (e[1] == null || e[2] == null) return null;
|
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
||||||
return e[1] !== e[2];
|
return compare(exp[1], exp[2]) !== 0;
|
||||||
} else if (e[0] === ">") {
|
} else if (exp[0] === ">") {
|
||||||
if (isArray(e[1]) || isArray(e[2])) return e;
|
if (exp[1] == null || exp[2] == null) return null;
|
||||||
if (e[1] == null || e[2] == null) return null;
|
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
||||||
return e[1] > e[2];
|
return compare(exp[1], exp[2]) > 0;
|
||||||
} else if (e[0] === ">=") {
|
} else if (exp[0] === ">=") {
|
||||||
if (isArray(e[1]) || isArray(e[2])) return e;
|
if (exp[1] == null || exp[2] == null) return null;
|
||||||
if (e[1] == null || e[2] == null) return null;
|
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
||||||
return e[1] >= e[2];
|
return compare(exp[1], exp[2]) >= 0;
|
||||||
} else if (e[0] === "<") {
|
} else if (exp[0] === "<") {
|
||||||
if (isArray(e[1]) || isArray(e[2])) return e;
|
if (exp[1] == null || exp[2] == null) return null;
|
||||||
if (e[1] == null || e[2] == null) return null;
|
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
||||||
return e[1] < e[2];
|
return compare(exp[1], exp[2]) < 0;
|
||||||
} else if (e[0] === "<=") {
|
} else if (exp[0] === "<=") {
|
||||||
if (isArray(e[1]) || isArray(e[2])) return e;
|
if (exp[1] == null || exp[2] == null) return null;
|
||||||
if (e[1] == null || e[2] == null) return null;
|
if (isArray(exp[1]) || isArray(exp[2])) return exp;
|
||||||
return e[1] <= e[2];
|
return compare(exp[1], exp[2]) <= 0;
|
||||||
} else if (e[0] === "*") {
|
} else if (exp[0] === "*") {
|
||||||
return reduce(e, (a, b) => {
|
return reduce(exp, (a, b) => {
|
||||||
if (!isArray(a) && !isArray(b)) {
|
|
||||||
if (a == null || b == null) return null;
|
if (a == null || b == null) return null;
|
||||||
return a * b;
|
if (!isArray(a) && !isArray(b)) return toNumber(a) * toNumber(b);
|
||||||
}
|
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (e[0] === "/") {
|
} else if (exp[0] === "/") {
|
||||||
return reduce(e, (a, b, i) => {
|
return reduce(exp, (a, b, i) => {
|
||||||
if (!isArray(a) && !isArray(b)) {
|
|
||||||
if (a == null || b == null) return null;
|
if (a == null || b == null) return null;
|
||||||
return i === 0 ? a / b : a * b;
|
if (!isArray(a) && !isArray(b))
|
||||||
}
|
return i === 0 ? toNumber(a) / toNumber(b) : toNumber(a) * toNumber(b);
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (e[0] === "+") {
|
} else if (exp[0] === "+") {
|
||||||
return reduce(e, (a, b) => {
|
return reduce(exp, (a, b) => {
|
||||||
if (!isArray(a) && !isArray(b)) {
|
|
||||||
if (a == null || b == null) return null;
|
if (a == null || b == null) return null;
|
||||||
return a + b;
|
if (!isArray(a) && !isArray(b)) return toNumber(a) + toNumber(b);
|
||||||
}
|
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (e[0] === "-") {
|
} else if (exp[0] === "-") {
|
||||||
return reduce(e, (a, b, i) => {
|
return reduce(exp, (a, b, i) => {
|
||||||
if (!isArray(a) && !isArray(b)) {
|
|
||||||
if (a == null || b == null) return null;
|
if (a == null || b == null) return null;
|
||||||
return i === 0 ? a - b : a + b;
|
if (!isArray(a) && !isArray(b))
|
||||||
}
|
return i === 0 ? toNumber(a) - toNumber(b) : toNumber(a) + toNumber(b);
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
} else if (e[0] === "||") {
|
} else if (exp[0] === "||") {
|
||||||
return reduce(e, (a, b) => {
|
return reduce(exp, (a, b) => {
|
||||||
if (!isArray(a) && !isArray(b)) {
|
|
||||||
if (a == null || b == null) return null;
|
if (a == null || b == null) return null;
|
||||||
return `${a}${b}`;
|
if (!isArray(a) && !isArray(b)) return `${a}${b}`;
|
||||||
}
|
|
||||||
return REDUCE_SKIP;
|
return REDUCE_SKIP;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return e;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evaluate(
|
export function evaluate(
|
||||||
@@ -236,7 +274,7 @@ export function evaluate(
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evalExp(e);
|
return evaluateCallback(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,35 +319,47 @@ export async function evaluateAsync(
|
|||||||
return v as Expression;
|
return v as Expression;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evalExp(e);
|
return evaluateCallback(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function and(exp1: Expression, exp2: Expression): Expression {
|
export function and(exp1: Expression, exp2: Expression): Expression {
|
||||||
if (!isArray(exp1)) return exp1 ? exp2 : exp1;
|
if (exp1 != null && !exp1) return false;
|
||||||
if (!isArray(exp2)) return exp2 ? exp1 : exp2;
|
if (exp2 != null && !exp2) return false;
|
||||||
|
if (!Array.isArray(exp1) && !Array.isArray(exp2)) {
|
||||||
|
if (exp1 == null || exp2 == null) return null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(exp2) && exp1[0] === "AND") return exp1;
|
||||||
|
if (!Array.isArray(exp1) && exp2[0] === "AND") return exp2;
|
||||||
|
|
||||||
let res: Expression = ["AND"];
|
const res: Expression = ["AND"];
|
||||||
|
|
||||||
if (exp1[0] === "AND") res = res.concat(exp1.slice(1));
|
if (Array.isArray(exp1) && exp1[0] === "AND") res.push(...exp1.slice(1));
|
||||||
else res.push(exp1);
|
else res.push(exp1);
|
||||||
|
|
||||||
if (exp2[0] === "AND") res = res.concat(exp2.slice(1));
|
if (Array.isArray(exp2) && exp2[0] === "AND") res.push(...exp2.slice(1));
|
||||||
else res.push(exp2);
|
else res.push(exp2);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function or(exp1: Expression, exp2: Expression): Expression {
|
export function or(exp1: Expression, exp2: Expression): Expression {
|
||||||
if (!isArray(exp1)) return exp1 ? exp1 : exp2;
|
if (!Array.isArray(exp1) && exp1) return true;
|
||||||
if (!isArray(exp2)) return exp2 ? exp2 : exp1;
|
if (!Array.isArray(exp2) && exp2) return true;
|
||||||
|
if (!Array.isArray(exp1) && !Array.isArray(exp2)) {
|
||||||
|
if (exp1 == null || exp2 == null) return null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(exp2) && exp1[0] === "OR") return exp1;
|
||||||
|
if (!Array.isArray(exp1) && exp2[0] === "OR") return exp2;
|
||||||
|
|
||||||
let res: Expression = ["OR"];
|
const res: Expression = ["OR"];
|
||||||
|
|
||||||
if (exp1[0] === "OR") res = res.concat(exp1.slice(1));
|
if (Array.isArray(exp1) && exp1[0] === "OR") res.push(...exp1.slice(1));
|
||||||
else res.push(exp1);
|
else res.push(exp1);
|
||||||
|
|
||||||
if (exp2[0] === "OR") res = res.concat(exp2.slice(1));
|
if (Array.isArray(exp2) && exp2[0] === "OR") res.push(...exp2.slice(1));
|
||||||
else res.push(exp2);
|
else res.push(exp2);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ export function filterToMongoQuery(exp: Expression): Record<string, unknown> {
|
|||||||
const op = filter[0];
|
const op = filter[0];
|
||||||
|
|
||||||
if (ops[op] === 0) {
|
if (ops[op] === 0) {
|
||||||
|
if (op === "AND") filter = filter.filter((f) => f !== true);
|
||||||
|
if (op === "OR") filter = filter.filter((f) => f !== false);
|
||||||
|
if (filter.length === 2) return recursive(filter[1], negate, res);
|
||||||
|
|
||||||
for (let i = 1; i < filter.length; ++i) {
|
for (let i = 1; i < filter.length; ++i) {
|
||||||
if (!isArray(filter[i]) || ops[filter[i][0]] == null)
|
if (!isArray(filter[i]) || ops[filter[i][0]] == null)
|
||||||
throw new Error(`Invalid expression in ${op} clause`);
|
throw new Error(`Invalid expression in ${op} clause`);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import * as mongodbFunctions from "../mongodb-functions";
|
|||||||
import * as expression from "../common/expression";
|
import * as expression from "../common/expression";
|
||||||
import { QueryOptions, Expression } from "../types";
|
import { QueryOptions, Expression } from "../types";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
|
import { minimize } from "../common/boolean-expression";
|
||||||
|
|
||||||
const CACHE_TTL = 300000;
|
const CACHE_TTL = 300000;
|
||||||
|
|
||||||
@@ -105,6 +106,7 @@ export function query(
|
|||||||
options = options || {};
|
options = options || {};
|
||||||
let q;
|
let q;
|
||||||
filter = expression.evaluate(filter, null, Date.now());
|
filter = expression.evaluate(filter, null, Date.now());
|
||||||
|
filter = minimize(filter, true);
|
||||||
|
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
if (resource === "devices") {
|
if (resource === "devices") {
|
||||||
@@ -204,6 +206,7 @@ export function query(
|
|||||||
export function count(resource: string, filter: Expression): Promise<number> {
|
export function count(resource: string, filter: Expression): Promise<number> {
|
||||||
let q;
|
let q;
|
||||||
filter = expression.evaluate(filter, null, Date.now());
|
filter = expression.evaluate(filter, null, Date.now());
|
||||||
|
filter = minimize(filter, true);
|
||||||
|
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
if (resource === "devices")
|
if (resource === "devices")
|
||||||
|
|||||||
Generated
+3
-3
@@ -10479,9 +10479,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"yebool": {
|
"yebool": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/yebool/-/yebool-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/yebool/-/yebool-1.0.2.tgz",
|
||||||
"integrity": "sha512-PrD+4pRGLSS3c8Z+l4V1EUPGUxtqpCDOobldAijBDAgdRHVtrIcDeZ6BFZDKk6QMmGTCCZhbQYdFKSz5fdBXAw==",
|
"integrity": "sha512-3sgfZ1jSZNWyU5Xu0uIlA98tNl/Aj1sZVt89qZL4IXJVNG2+eWF4hp9NzQrOng6nO/ohvIPnzbIfp8qtBykBfw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsbi": "^3.1.3"
|
"jsbi": "^3.1.3"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@
|
|||||||
"parsimmon": "^1.13.0",
|
"parsimmon": "^1.13.0",
|
||||||
"seedrandom": "^3.0.1",
|
"seedrandom": "^3.0.1",
|
||||||
"yaml": "^1.6.0",
|
"yaml": "^1.6.0",
|
||||||
"yebool": "^1.0.1"
|
"yebool": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^14.0.0",
|
"@rollup/plugin-commonjs": "^14.0.0",
|
||||||
|
|||||||
+13
-4
@@ -24,6 +24,7 @@ import Autocomplete from "./autocomplete-compnent";
|
|||||||
import * as smartQuery from "./smart-query";
|
import * as smartQuery from "./smart-query";
|
||||||
import { filterToMongoQuery } from "../lib/mongodb-functions";
|
import { filterToMongoQuery } from "../lib/mongodb-functions";
|
||||||
import { Expression } from "../lib/types";
|
import { Expression } from "../lib/types";
|
||||||
|
import { normalize } from "../lib/common/boolean-expression";
|
||||||
|
|
||||||
const getAutocomplete = memoize((resource) => {
|
const getAutocomplete = memoize((resource) => {
|
||||||
const labels = smartQuery.getLabels(resource);
|
const labels = smartQuery.getLabels(resource);
|
||||||
@@ -41,6 +42,12 @@ const getAutocomplete = memoize((resource) => {
|
|||||||
return autocomplete;
|
return autocomplete;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function validateQuery(q: Expression): void {
|
||||||
|
q = normalize(q);
|
||||||
|
if (Array.isArray(q) && q[0] === "CASE") q = ["AND", ...q.slice(1)];
|
||||||
|
filterToMongoQuery(q);
|
||||||
|
}
|
||||||
|
|
||||||
function parseFilter(resource, f): Expression {
|
function parseFilter(resource, f): Expression {
|
||||||
let exp;
|
let exp;
|
||||||
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
||||||
@@ -51,11 +58,13 @@ function parseFilter(resource, f): Expression {
|
|||||||
exp = parse(f);
|
exp = parse(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw exception if invalid Mongo query
|
// Throws exception if invalid Mongo query
|
||||||
filterToMongoQuery(
|
validateQuery(
|
||||||
map(exp, (e) => {
|
map(exp, (e) => {
|
||||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
if (Array.isArray(e) && e[0] === "FUNC") {
|
||||||
return smartQuery.unpack(resource, e[2], e[3]);
|
if (e[1] === "Q") return smartQuery.unpack(resource, e[2], e[3]);
|
||||||
|
else if (e[1] === "NOW") return Date.now();
|
||||||
|
}
|
||||||
return e;
|
return e;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user