refactor: replace array-based expressions with typed class hierarchy

Replace the old Lisp-like array expression representation
(["AND", ["PARAM", ...], ...]) with a class hierarchy rooted in
an abstract Expression class (Literal, Parameter, Binary, Unary,
FunctionCall, Conditional). This gives compile-time type safety
to expression construction, evaluation, and pattern matching.

Key changes:
- New lib/common/expression.ts defines the class hierarchy with
  map/evaluate/toString methods
- Rewrite parser as hand-rolled recursive descent, removing the
  parsimmon dependency
- Extend Path with a colon separator to unify parameter identity
  and attributes into a single address space (e.g.
  "Device.WiFi.SSID.1.Name:writable"). The colon field tracks
  how many trailing segments are attribute qualifiers. PathSet
  rewritten with separate param/attr segment indexes
- Flatten device attribute representation in the UI to
  colon-delimited keys mirroring the new path syntax
This commit is contained in:
Zaid Humaimidi committed 2026-03-12 14:58:20 -07:00
1 parent 75908e624f
commit 6f3ce6f7c8
71 files changed
+4045 -3449

No files matched your search

+34 -14
View File
@@ -2,7 +2,7 @@ import test from "node:test";
import assert from "node:assert";
import initSqlJs from "sql.js/dist/sql-asm.js";
import { covers, minimize, unionDiff } from "../lib/common/expression/synth.ts";
import { parse, stringify } from "../lib/common/expression/parser.ts";
import Expression from "../lib/common/expression.ts";
const STRING_VALUES = [null, "", "a", "ab", "ab10", "ab-10"];
const DECIMAL_VALUES = [null, 0, -10, 10];
@@ -86,7 +86,7 @@ void test("minimize", async () => {
for (const c of cases) {
const res1 = await query(c);
const min = stringify(minimize(parse(c), true));
const min = minimize(Expression.parse(c), true).toString();
const res2 = await query(min);
assert.strictEqual(setsEqual(res1, res2), true);
}
@@ -104,9 +104,9 @@ void test("unionDiff", async () => {
for (const [c1, c2] of getPermutations(cases, cases)) {
const res1 = await query(c1);
const res2 = await query(c2);
const [union, diff] = unionDiff(parse(c1), parse(c2));
const res3 = await query(stringify(union));
const res4 = await query(stringify(diff));
const [union, diff] = unionDiff(Expression.parse(c1), Expression.parse(c2));
const res3 = await query(union.toString());
const res4 = await query(diff.toString());
const unionSet = new Set([...res1, ...res2]);
const diffSet = new Set(Array.from(res2).filter((r) => !res1.has(r)));
@@ -117,15 +117,35 @@ void test("unionDiff", async () => {
});
void test("covers", async () => {
assert.strictEqual(covers(false, false), true);
assert.strictEqual(covers(false, parse("decimal > 5 AND decimal < 3")), true);
assert.strictEqual(covers(parse("true"), parse("decimal > 0")), true);
assert.strictEqual(covers(parse("true"), parse("false")), true);
assert.strictEqual(covers(parse("false"), parse("false")), true);
assert.strictEqual(covers(parse("false"), parse("decimal > 0")), false);
assert.strictEqual(covers(parse("decimal >= 0"), parse("decimal > 0")), true);
assert.strictEqual(
covers(parse("decimal > 0"), parse("decimal >= 0")),
covers(Expression.parse("false"), Expression.parse("false")),
true,
);
assert.strictEqual(
covers(
Expression.parse("false"),
Expression.parse("decimal > 5 AND decimal < 3"),
),
true,
);
assert.strictEqual(
covers(Expression.parse("true"), Expression.parse("decimal > 0")),
true,
);
assert.strictEqual(
covers(Expression.parse("true"), Expression.parse("false")),
true,
);
assert.strictEqual(
covers(Expression.parse("false"), Expression.parse("decimal > 0")),
false,
);
assert.strictEqual(
covers(Expression.parse("decimal >= 0"), Expression.parse("decimal > 0")),
true,
);
assert.strictEqual(
covers(Expression.parse("decimal > 0"), Expression.parse("decimal >= 0")),
false,
);
@@ -139,7 +159,7 @@ void test("covers", async () => {
for (const [c1, c2] of cases) {
const res1 = await query(c1);
const res2 = await query(c2);
const coversResult = covers(parse(c1), parse(c2));
const coversResult = covers(Expression.parse(c1), Expression.parse(c2));
const actuallyCovers = Array.from(res2).every((r) => res1.has(r));
assert.strictEqual(