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

+22 -19
View File
@@ -7,7 +7,7 @@ import VersionedMap from "../lib/versioned-map.ts";
import { Attributes } from "../lib/types.ts";
void test("getAliasDeclarations", () => {
const path = Path.parse("a.[aa:10,bb.[aaa:100].cc:1].b");
const path = Path.parse("a.[aa = 10 AND bb.[aaa = 100].cc = 1].b");
const decs = device.getAliasDeclarations(path, 99);
const expected = ["a.*.b", "a.*.aa", "a.*.bb.*.cc", "a.*.bb.*.aaa"];
@@ -32,66 +32,69 @@ void test("unpack", () => {
changes: new Set<string>(),
};
device.set(deviceData, Path.parse("a.1.b"), now, {
device.set(deviceData, "a.1.b", now, {
value: [now, ["b", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.1.c"), now, {
device.set(deviceData, "a.1.c", now, {
value: [now, ["c", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.1.a.1.a"), now, {
device.set(deviceData, "a.1.a.1.a", now, {
value: [now, ["", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.1.a.1.b"), now, {
device.set(deviceData, "a.1.a.1.b", now, {
value: [now, ["b1", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.1.a.1.c"), now, {
device.set(deviceData, "a.1.a.1.c", now, {
value: [now, ["c1", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.1.a.2.a"), now, {
device.set(deviceData, "a.1.a.2.a", now, {
value: [now, ["", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.1.a.2.b"), now, {
device.set(deviceData, "a.1.a.2.b", now, {
value: [now, ["b2", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.1.a.2.c"), now, {
device.set(deviceData, "a.1.a.2.c", now, {
value: [now, ["c2", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.b"), now, {
device.set(deviceData, "a.2.b", now, {
value: [now, ["b", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.c"), now, {
device.set(deviceData, "a.2.c", now, {
value: [now, ["c", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.a.1.a"), now, {
device.set(deviceData, "a.2.a.1.a", now, {
value: [now, ["", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.a.1.b"), now, {
device.set(deviceData, "a.2.a.1.b", now, {
value: [now, ["b1", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.a.1.c"), now, {
device.set(deviceData, "a.2.a.1.c", now, {
value: [now, ["c1", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.a.2.a"), now, {
device.set(deviceData, "a.2.a.2.a", now, {
value: [now, ["", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.a.2.b"), now, {
device.set(deviceData, "a.2.a.2.b", now, {
value: [now, ["c1", "xsd:string"]],
});
device.set(deviceData, Path.parse("a.2.a.2.c"), now, {
device.set(deviceData, "a.2.a.2.c", now, {
value: [now, ["b1", "xsd:string"]],
});
let unpacked: Path[];
unpacked = device.unpack(
deviceData,
Path.parse("a.[b:b,c:c].a.[b:b1,c:c1].a"),
Path.parse("a.[b='b' AND c='c'].a.[b='b1' AND c='c1'].a"),
);
assert.strictEqual(unpacked.length, 2);
assert.strictEqual(unpacked[0].toString(), "a.1.a.1.a");
assert.strictEqual(unpacked[1].toString(), "a.2.a.1.a");
unpacked = device.unpack(deviceData, Path.parse("a.*.a.[b:c1,c:b1].a"));
unpacked = device.unpack(
deviceData,
Path.parse("a.*.a.[b='c1' AND c='b1'].a"),
);
assert.strictEqual(unpacked.length, 1);
assert.strictEqual(unpacked[0].toString(), "a.2.a.2.a");
});