diff --git a/dist/demo/2024_07/equality_saturation.js b/dist/demo/2024_07/equality_saturation.js
new file mode 100644
index 0000000..3918c74
--- /dev/null
+++ b/dist/demo/2024_07/equality_saturation.js
@@ -0,0 +1 @@
+"use strict";
diff --git a/dist/demo/2024_07/rewrite.js b/dist/demo/2024_07/rewrite.js
index 54bbac9..04b1575 100644
--- a/dist/demo/2024_07/rewrite.js
+++ b/dist/demo/2024_07/rewrite.js
@@ -1,27 +1,135 @@
"use strict";
-const match = (pattern, structure) => {
- if (structure[0] !== pattern[0])
- return false;
- const a = Array.isArray(pattern[1])
- ? match(pattern[1], structure[1])
- : { [pattern[1]]: structure[1] };
- if (!a)
- return false;
- const b = Array.isArray(pattern[2])
- ? match(pattern[2], structure[2])
- : { [pattern[2]]: structure[2] };
- if (!b)
- return false;
- return {
- ...a,
- ...b,
- };
-};
-const subst = (matches, [op, a, b]) => [
- op,
- Array.isArray(a) ? subst(matches, a) : matches[a],
- Array.isArray(b) ? subst(matches, b) : matches[b],
+const variable = ({ name, pred, match }) => ({
+ kind: "variable",
+ name,
+ pred,
+ match,
+ withPred: (newPred) => variable({ name, pred: newPred, match }),
+ withMatch: (newMatch) => variable({ name, pred, match: newMatch }),
+});
+const v = ([name]) => variable({ name, pred: () => true, match: (matches) => matches[name] });
+const isNumber = (n) => typeof n === "number";
+const MY_RULES = [
+ {
+ from: ["=", ["+", v `a`, v `b`], v `c`],
+ to: ["=", v `a`, ["-", v `c`, v `b`]],
+ },
+ {
+ from: ["=", ["-", v `a`, v `b`], v `c`],
+ to: ["=", v `a`, ["+", v `c`, v `b`]],
+ },
+ {
+ from: ["=", v `a`, v `b`],
+ to: ["=", v `b`, v `a`],
+ },
+ {
+ from: ["+", v `a`, v `b`],
+ to: ["+", v `b`, v `a`],
+ },
+ {
+ from: ["+", v `a`.withPred(isNumber), v `b`.withPred(isNumber)],
+ to: v `a + b`.withMatch(({ a, b }) => a + b),
+ },
+ {
+ from: ["+", v `a`, v `a`],
+ to: ["*", 2, v `a`],
+ },
+ {
+ from: ["*", v `a`, v `b`],
+ to: ["*", v `b`, v `a`],
+ },
+ {
+ from: ["=", ["*", v `a`, v `b`], v `c`],
+ to: ["=", v `a`, ["/", v `c`, v `b`]],
+ },
+ {
+ from: ["=", ["/", v `a`, v `b`], v `c`],
+ to: ["=", v `a`, ["*", v `c`, v `b`]],
+ },
];
+const MATCH_FAIL = "MATCH_FAIL";
+const match = (pattern, structure, matchesSoFar = {}) => {
+ if (pattern.kind === "variable") {
+ if (!pattern.pred(structure, matchesSoFar))
+ return MATCH_FAIL;
+ const prevMatch = pattern.match(matchesSoFar);
+ if (prevMatch && JSON.stringify(prevMatch) !== JSON.stringify(structure))
+ return MATCH_FAIL;
+ return { [pattern.name]: structure };
+ }
+ else if (Array.isArray(pattern)) {
+ if (!Array.isArray(structure))
+ return MATCH_FAIL;
+ let matches = {};
+ for (let i = 0; i < pattern.length; i++) {
+ const p = pattern[i];
+ const s = structure[i];
+ const m = match(p, s, matches);
+ if (m === MATCH_FAIL)
+ return MATCH_FAIL;
+ matches = { ...matches, ...m };
+ }
+ return matches;
+ }
+ else {
+ if (pattern !== structure)
+ return MATCH_FAIL;
+ return {};
+ }
+};
+const getRecursiveMatches = (pattern, structure, res = new Map()) => {
+ res.set(structure, match(pattern, structure));
+ if (Array.isArray(structure))
+ for (const substructure of structure)
+ getRecursiveMatches(pattern, substructure, res);
+ return res;
+};
+const SUBST_FAIL = "SUBST_FAIL";
+const subst = (matches, structure) => {
+ if (structure.kind === "variable")
+ return structure.match(matches) ?? SUBST_FAIL;
+ else if (Array.isArray(structure)) {
+ const ms = structure.map((s) => subst(matches, s));
+ if (ms.some((m) => m === SUBST_FAIL))
+ return SUBST_FAIL;
+ return ms;
+ }
+ else
+ return structure;
+};
+const APPLY_FAIL = "APPLY_FAIL";
+const applyRule = ({ from, to, success, failure }, structure) => {
+ const m = match(from, structure);
+ if (m === MATCH_FAIL) {
+ if (failure)
+ failure(MATCH_FAIL);
+ return APPLY_FAIL;
+ }
+ const s = subst(m, to);
+ if (s === SUBST_FAIL) {
+ if (failure)
+ failure(SUBST_FAIL);
+ return APPLY_FAIL;
+ }
+ if (success)
+ success();
+ return s;
+};
+const tryAllRulesRecursively = (rules, structure) => {
+ return [
+ ...(Array.isArray(structure)
+ ? structure.flatMap((s, i) => tryAllRulesRecursively(rules, s).map((res) => structure.with(i, res)))
+ : []),
+ ...rules
+ .map((r) => {
+ const res = applyRule(r, structure);
+ if (res === APPLY_FAIL)
+ return APPLY_FAIL;
+ return res;
+ })
+ .filter((subRes) => subRes !== APPLY_FAIL),
+ ];
+};
const rewriteStep = (input, rules) => {
const neue = [
...(Array.isArray(input[1])
@@ -31,44 +139,83 @@ const rewriteStep = (input, rules) => {
? rewriteStep(input[2], rules).map((res) => [input[0], input[1], res])
: []),
];
- for (const { from, to } of rules) {
- const m = match(from, input);
- if (!m)
+ for (const rule of rules) {
+ const res = applyRule(rule, input);
+ if (res === APPLY_FAIL)
continue;
- neue.push(subst(m, to));
+ neue.push(res);
}
return neue;
};
-const rules = [
- {
- from: ["=", ["+", "a", "b"], "c"],
- to: ["=", "a", ["-", "c", "b"]],
- },
- {
- from: ["=", ["-", "a", "b"], "c"],
- to: ["=", "a", ["+", "c", "b"]],
- },
- {
- from: ["=", "a", "b"],
- to: ["=", "b", "a"],
- },
+//const myInput = ["=", ["-", "w", 2], 3];
+const myInput1 = ["=", `w`, ["-", `r`, `l`]];
+const myInput2 = ["=", `c`, ["+", `l`, ["/", `w`, 2]]];
+// GOAL:
+// input: l, r, w = r - l, c = l + w/2
+// output:
+// - given: solution for l and r, given w and c.
+const isVar = (x) => x === "w" || x === "r" || x === "l" || x === "c";
+const sToRule = (s) => {
+ const m = match(["=", v `lhs`.withPred(isVar), v `rhs`], s);
+ if (m === MATCH_FAIL)
+ return [];
+ return [
+ {
+ m_name: m.lhs,
+ from: v `lhs`.withPred((x) => x === m.lhs),
+ to: m.rhs,
+ success: () => console.log("SUCCESSFULLY APPLIED", m.lhs),
+ failure: (fa) => console.log("FAIL APPLIED", fa, m.lhs),
+ },
+ ];
+};
+console.log("tryAllRulesRecursively 1", tryAllRulesRecursively(MY_RULES, "a"), tryAllRulesRecursively(MY_RULES, ["a"]));
+console.log("tryAllRulesRecursively 2", tryAllRulesRecursively(MY_RULES, ["+", "a", "b"]));
+console.log("sToRule 1", sToRule(["=", v `x`, ["+", 1, 2]]));
+console.log("sToRule 2", sToRule(["=", ["sin", v `x`], ["+", 1, 2]]));
+console.log("sToRule 3", tryAllRulesRecursively([
{
- from: ["+", "a", "b"],
- to: ["+", "b", "a"],
+ from: v `lhs`.withPred((x) => x.kind === "variable" && x.name === "x"),
+ to: 22,
},
- /* next: {
- from: ["-", { name: "a", pred: isNum }, { name: "b", isNum }],
- to: { args: ["a", "b"], calc: (a, b)=>a-b },
- },*/
-];
-const mySet = new Set();
-const see = (ob) => mySet.add(JSON.stringify(ob));
-const isSeen = (ob) => mySet.has(JSON.stringify(ob));
-const myInput = ["=", ["-", "w", 2], 3];
-const toTry = [myInput];
-while (toTry.length > 0) {
- const i = toTry.pop();
- see(i);
- toTry.push(...rewriteStep(i, rules).filter((res) => !isSeen(res)));
+], ["sin", v `x`]), tryAllRulesRecursively(sToRule(["=", v `x`, ["+", 1, 2]]), [
+ "+",
+ 33,
+ ["-", 1, v `x`],
+]));
+const mySet1 = new Set();
+const mySet2 = new Set();
+const see = (set, ob) => set.add(JSON.stringify(ob));
+const isSeen = (set, ob) => set.has(JSON.stringify(ob));
+const rules = [...MY_RULES];
+const toTry1 = [myInput1];
+const toTry2 = [myInput2];
+for (let i = 0; i < 12; i++) {
+ if (toTry1.length === 0 && toTry2.length === 0)
+ break;
+ if (toTry1.length > 0) {
+ const i = toTry1.pop();
+ see(mySet1, i);
+ const newStructuresToTry = tryAllRulesRecursively(rules, i).filter((res) => !isSeen(mySet1, res));
+ toTry1.push(...newStructuresToTry);
+ rules.push(...newStructuresToTry.flatMap(sToRule));
+ }
+ if (toTry2.length > 0) {
+ const i = toTry2.pop();
+ see(mySet2, i);
+ const newStructuresToTry = tryAllRulesRecursively(rules, i).filter((res) => !isSeen(mySet2, res));
+ toTry2.push(...newStructuresToTry);
+ rules.push(...newStructuresToTry.flatMap(sToRule));
+ }
+ console.log("hi", toTry1.length);
}
-console.log([...mySet.values()].map(JSON.parse));
+console.log("go go go", [...mySet1.values()].map(JSON.parse), [...mySet2.values()].map(JSON.parse), rules);
+// console.log(
+// "SELECT",
+// [...mySet.values()]
+// .map(JSON.parse)
+// .filter(
+// ([[_1, v1], [_2, v2]]) => v1.kind === "variable" && v2.kind === "variable"
+// )
+// .map(([[_1, v1], [_2, v2]]) => [v1.name, v2.name])
+// );
diff --git a/dist/demo/2024_08/e_graph.html b/dist/demo/2024_08/e_graph.html
new file mode 100644
index 0000000..d64bca7
--- /dev/null
+++ b/dist/demo/2024_08/e_graph.html
@@ -0,0 +1 @@
+
diff --git a/dist/demo/2024_08/e_graph.js b/dist/demo/2024_08/e_graph.js
new file mode 100644
index 0000000..496a503
--- /dev/null
+++ b/dist/demo/2024_08/e_graph.js
@@ -0,0 +1,85 @@
+// e init (tree) => e-graph
+// e match (pattern, e-graph) => e-node[]
+// e sub (result, e-node, e-graph) => e-graph
+
+const node = (value, ...children) => {
+ const res = {
+ isNode: true,
+ value,
+ children: children,
+ parent: null,
+ };
+ for (const child of children) child.parent = res;
+ return res;
+};
+const vari = (v, ...children) => {
+ const res = {
+ isNode: true,
+ var: v,
+ children: children,
+ parent: null,
+ };
+ for (const child of children) child.parent = res;
+ return res;
+};
+const nodeEq = (n1, n2) => {
+ if (n1.value !== n2.value) return false;
+ else if (n1.children.length !== n2.children.length) return false;
+ else return n1.children.every((v, i) => nodeEq(v, n2.children[i]));
+};
+
+const a = node("f", node(1), node(2));
+const b = node("f", node(1), node(2));
+
+console.log("nodeEq!", nodeEq(a, b));
+
+const eClassFromNode = (node, parentENode = null) => {
+ const eNode = { isENode: true, value: node.value };
+ eNode.children = node.children.map((n) => eClassFromNode(n, eNode));
+ return {
+ isEClass: true,
+ eNodes: [eNode],
+ parents: [parentENode],
+ };
+};
+
+console.log("eClassFromNode!", eClassFromNode(a));
+
+const eClassMatches = (patternNode, eClass) => {
+ return eClass.eNodes.flatMap((en) => eNodeMatches(patternNode, en));
+};
+const eNodeMatches = (patternNode, eNode) => {
+ if (patternNode.var === undefined && eNode.value !== patternNode.value)
+ return [];
+ else if (patternNode.children.length !== eNode.children.length) return [];
+ else {
+ const childrenMatches = eNode.children.map((ec, i) =>
+ eClassMatches(patternNode.children[i], ec)
+ );
+ return [
+ ...gogo(
+ childrenMatches,
+ patternNode.var ? { [patternNode.var]: eNode.value } : {}
+ ),
+ ];
+ }
+};
+
+const gogo = function* (childrenMatches, match) {
+ if (childrenMatches.length === 0) {
+ yield { ...match };
+ return;
+ }
+ for (const matches1 of childrenMatches[0]) {
+ for (const matches2 of gogo(childrenMatches.slice(1))) {
+ yield { ...match, ...matches1, ...matches2 };
+ }
+ }
+};
+
+console.log(
+ "eClassMatches!",
+ eClassMatches(vari("go", vari("1"), node(2)), eClassFromNode(a))
+);
+
+// Aside: the implicit lifting language could maybe really help simplify this.
diff --git a/dist/demo/2024_08/egg_port.js b/dist/demo/2024_08/egg_port.js
new file mode 100644
index 0000000..aa87728
--- /dev/null
+++ b/dist/demo/2024_08/egg_port.js
@@ -0,0 +1,69 @@
+import { makeSet, find, union } from "./union_find.js";
+// map canonical e-nodes to e-class ids
+// i.e. e-node 𝑛 ∈ 𝑀 [𝑎] ⇐⇒ 𝐻 [canonicalize(𝑛)] = find(𝑎)
+const hashcons = new Map();
+const parents = new Map();
+const worklist = new Set();
+const mk_enode = (op, children) => ({
+ op,
+ children,
+ hash: op + children.map((c) => "c" + c.id).join(),
+});
+// TODO: fix canonicalize to respect hashcons
+const canonicalize = (eNode) => mk_enode(eNode.op, eNode.children.map(find));
+const lookup = (eNode) => hashcons.get(eNode.hash);
+// eNode -> eClassId
+const add = (eNode) => {
+ const en = canonicalize(eNode);
+ if (lookup(en))
+ return lookup(en);
+ else {
+ const eClassId = makeSet();
+ for (const child of eNode.children)
+ parents.set(child, parents.get(child)?.set(en, eClassId) ?? new Map([[en, eClassId]]));
+ hashcons.set(en.hash, eClassId);
+ return eClassId;
+ }
+};
+const merge = (eClassId1, eClassId2) => {
+ if (find(eClassId1) === find(eClassId2))
+ return find(eClassId1);
+ const newEClassId = union(eClassId1, eClassId2);
+ worklist.add(newEClassId);
+ return newEClassId;
+};
+const rebuild = () => {
+ while (worklist.size > 0) {
+ const todo = [...worklist];
+ worklist.clear();
+ for (const eClassId of todo)
+ repair(find(eClassId));
+ }
+};
+const repair = (eClassId) => {
+ for (const [peNode, peClassId] of parents.get(eClassId) ?? []) {
+ hashcons.delete(peNode.hash);
+ const newPeNode = canonicalize(peNode);
+ hashcons.set(newPeNode.hash, peClassId);
+ }
+ const newParents = new Map();
+ for (const [peNode, peClassId] of parents.get(eClassId) ?? []) {
+ const newPeNode = canonicalize(peNode);
+ if (newParents.has(newPeNode)) {
+ merge(peClassId, newParents.get(newPeNode));
+ }
+ newParents.set(newPeNode, find(peClassId));
+ }
+ parents.set(eClassId, newParents);
+};
+//const ematch = (pattern) => [subts, eClass][]
+// To apply a rewrite l → 𝑟 to an e-graph,
+// ematch finds tuples (𝜎,𝑐) where e-class 𝑐 represents l[𝜎].
+// Then, for each tuple, merge(𝑐, add(𝑟 [𝜎])) adds 𝑟 [𝜎] to the e-graph
+// and unifies it with the matching e-class c.
+add(mk_enode("1", []));
+add(mk_enode("2", []));
+add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")]));
+add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")]));
+rebuild();
+console.log(hashcons);
diff --git a/dist/demo/2024_08/js.js b/dist/demo/2024_08/js.js
new file mode 100644
index 0000000..f20dc26
--- /dev/null
+++ b/dist/demo/2024_08/js.js
@@ -0,0 +1,50 @@
+// source: https://github.com/manubb/union-find
+export const makeSet = () => {
+ const singleton = {
+ rank: 0,
+ };
+ singleton.parent = singleton;
+ return singleton;
+};
+export const find = (node) => {
+ if (node.parent !== node) {
+ node.parent = find(node.parent);
+ }
+ return node.parent;
+};
+export const union = (node1, node2) => {
+ const root1 = find(node1);
+ const root2 = find(node2);
+ if (root1 !== root2) {
+ if (root1.rank < root2.rank) {
+ root1.parent = root2;
+ }
+ else {
+ root2.parent = root1;
+ if (root1.rank === root2.rank)
+ root1.rank += 1;
+ }
+ }
+};
+/*
+MIT License
+
+Copyright (c) 2018 Manuel Baclet
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.*/
diff --git a/dist/demo/2024_08/union_find.js b/dist/demo/2024_08/union_find.js
new file mode 100644
index 0000000..7afaa29
--- /dev/null
+++ b/dist/demo/2024_08/union_find.js
@@ -0,0 +1,56 @@
+// source: https://github.com/manubb/union-find
+
+let idCounter = 0;
+export const makeSet = () => {
+ const singleton = {
+ rank: 0,
+ id: idCounter++,
+ };
+ singleton.parent = singleton;
+
+ return singleton;
+};
+
+export const find = (node) => {
+ if (node.parent !== node) {
+ node.parent = find(node.parent);
+ }
+
+ return node.parent;
+};
+
+export const union = (node1, node2) => {
+ const root1 = find(node1);
+ const root2 = find(node2);
+ if (root1 !== root2) {
+ if (root1.rank < root2.rank) {
+ root1.parent = root2;
+ } else {
+ root2.parent = root1;
+ if (root1.rank === root2.rank) root1.rank += 1;
+ }
+ }
+};
+
+/*
+MIT License
+
+Copyright (c) 2018 Manuel Baclet
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.*/
diff --git a/src/demo/2024_07/rewrite.ts b/src/demo/2024_07/rewrite.ts
index b1d6bd6..6db6973 100644
--- a/src/demo/2024_07/rewrite.ts
+++ b/src/demo/2024_07/rewrite.ts
@@ -1,25 +1,134 @@
-const match = (pattern, structure) => {
- if (structure[0] !== pattern[0]) return false;
- const a = Array.isArray(pattern[1])
- ? match(pattern[1], structure[1])
- : { [pattern[1]]: structure[1] };
- if (!a) return false;
- const b = Array.isArray(pattern[2])
- ? match(pattern[2], structure[2])
- : { [pattern[2]]: structure[2] };
- if (!b) return false;
- return {
- ...a,
- ...b,
- };
-};
+const variable = ({ name, pred, match }: any) => ({
+ kind: "variable",
+ name,
+ pred,
+ match,
+
+ withPred: (newPred) => variable({ name, pred: newPred, match }),
+ withMatch: (newMatch) => variable({ name, pred, match: newMatch }),
+});
+const v = ([name]: TemplateStringsArray) =>
+ variable({ name, pred: () => true, match: (matches) => matches[name] });
-const subst = (matches, [op, a, b]) => [
- op,
- Array.isArray(a) ? subst(matches, a) : matches[a],
- Array.isArray(b) ? subst(matches, b) : matches[b],
+const isNumber = (n) => typeof n === "number";
+
+const MY_RULES = [
+ {
+ from: ["=", ["+", v`a`, v`b`], v`c`],
+ to: ["=", v`a`, ["-", v`c`, v`b`]],
+ },
+ {
+ from: ["=", ["-", v`a`, v`b`], v`c`],
+ to: ["=", v`a`, ["+", v`c`, v`b`]],
+ },
+ {
+ from: ["=", v`a`, v`b`],
+ to: ["=", v`b`, v`a`],
+ },
+ {
+ from: ["+", v`a`, v`b`],
+ to: ["+", v`b`, v`a`],
+ },
+ {
+ from: ["+", v`a`.withPred(isNumber), v`b`.withPred(isNumber)],
+ to: v`a + b`.withMatch(({ a, b }) => a + b),
+ },
+ {
+ from: ["+", v`a`, v`a`],
+ to: ["*", 2, v`a`],
+ },
+ {
+ from: ["*", v`a`, v`b`],
+ to: ["*", v`b`, v`a`],
+ },
+ {
+ from: ["=", ["*", v`a`, v`b`], v`c`],
+ to: ["=", v`a`, ["/", v`c`, v`b`]],
+ },
+ {
+ from: ["=", ["/", v`a`, v`b`], v`c`],
+ to: ["=", v`a`, ["*", v`c`, v`b`]],
+ },
];
+const MATCH_FAIL = "MATCH_FAIL";
+const match = (pattern, structure, matchesSoFar = {}) => {
+ if (pattern.kind === "variable") {
+ if (!pattern.pred(structure, matchesSoFar)) return MATCH_FAIL;
+ const prevMatch = pattern.match(matchesSoFar);
+ if (prevMatch && JSON.stringify(prevMatch) !== JSON.stringify(structure))
+ return MATCH_FAIL;
+
+ return { [pattern.name]: structure };
+ } else if (Array.isArray(pattern)) {
+ if (!Array.isArray(structure)) return MATCH_FAIL;
+ let matches = {};
+ for (let i = 0; i < pattern.length; i++) {
+ const p = pattern[i];
+ const s = structure[i];
+ const m = match(p, s, matches);
+ if (m === MATCH_FAIL) return MATCH_FAIL;
+ matches = { ...matches, ...m };
+ }
+ return matches;
+ } else {
+ if (pattern !== structure) return MATCH_FAIL;
+ return {};
+ }
+};
+const getRecursiveMatches = (pattern, structure, res = new Map()) => {
+ res.set(structure, match(pattern, structure));
+ if (Array.isArray(structure))
+ for (const substructure of structure)
+ getRecursiveMatches(pattern, substructure, res);
+ return res;
+};
+
+const SUBST_FAIL = "SUBST_FAIL";
+const subst = (matches, structure) => {
+ if (structure.kind === "variable")
+ return structure.match(matches) ?? SUBST_FAIL;
+ else if (Array.isArray(structure)) {
+ const ms = structure.map((s) => subst(matches, s));
+ if (ms.some((m) => m === SUBST_FAIL)) return SUBST_FAIL;
+ return ms;
+ } else return structure;
+};
+
+const APPLY_FAIL = "APPLY_FAIL";
+const applyRule = ({ from, to, success, failure }, structure) => {
+ const m = match(from, structure);
+
+ if (m === MATCH_FAIL) {
+ if (failure) failure(MATCH_FAIL);
+ return APPLY_FAIL;
+ }
+ const s = subst(m, to);
+ if (s === SUBST_FAIL) {
+ if (failure) failure(SUBST_FAIL);
+ return APPLY_FAIL;
+ }
+ if (success) success();
+ return s;
+};
+
+const tryAllRulesRecursively = (rules, structure) => {
+ return [
+ ...(Array.isArray(structure)
+ ? structure.flatMap((s, i) =>
+ tryAllRulesRecursively(rules, s).map((res) => structure.with(i, res))
+ )
+ : []),
+ ...rules
+ .map((r) => {
+ const res = applyRule(r, structure);
+ if (res === APPLY_FAIL) return APPLY_FAIL;
+ return res;
+ })
+ .filter((subRes) => subRes !== APPLY_FAIL),
+ ];
+};
+
const rewriteStep = (input, rules) => {
const neue = [
...(Array.isArray(input[1])
@@ -29,48 +138,113 @@ const rewriteStep = (input, rules) => {
? rewriteStep(input[2], rules).map((res) => [input[0], input[1], res])
: []),
];
- for (const { from, to } of rules) {
- const m = match(from, input);
- if (!m) continue;
- neue.push(subst(m, to));
+ for (const rule of rules) {
+ const res = applyRule(rule, input);
+ if (res === APPLY_FAIL) continue;
+ neue.push(res);
}
return neue;
};
-const rules = [
- {
- from: ["=", ["+", "a", "b"], "c"],
- to: ["=", "a", ["-", "c", "b"]],
- },
- {
- from: ["=", ["-", "a", "b"], "c"],
- to: ["=", "a", ["+", "c", "b"]],
- },
- {
- from: ["=", "a", "b"],
- to: ["=", "b", "a"],
- },
- {
- from: ["+", "a", "b"],
- to: ["+", "b", "a"],
- },
- /* next: {
- from: ["-", { name: "a", pred: isNum }, { name: "b", isNum }],
- to: { args: ["a", "b"], calc: (a, b)=>a-b },
- },*/
-];
+//const myInput = ["=", ["-", "w", 2], 3];
+const myInput1 = ["=", `w`, ["-", `r`, `l`]];
+const myInput2 = ["=", `c`, ["+", `l`, ["/", `w`, 2]]];
-const mySet = new Set();
-const see = (ob) => mySet.add(JSON.stringify(ob));
-const isSeen = (ob) => mySet.has(JSON.stringify(ob));
+// GOAL:
+// input: l, r, w = r - l, c = l + w/2
+// output:
+// - given: solution for l and r, given w and c.
+
+const isVar = (x) => x === "w" || x === "r" || x === "l" || x === "c";
+const sToRule = (s) => {
+ const m = match(["=", v`lhs`.withPred(isVar), v`rhs`], s);
+ if (m === MATCH_FAIL) return [];
+ return [
+ {
+ m_name: m.lhs,
+ from: v`lhs`.withPred((x) => x === m.lhs),
+ to: m.rhs,
+ success: () => console.log("SUCCESSFULLY APPLIED", m.lhs),
+ failure: (fa) => console.log("FAIL APPLIED", fa, m.lhs),
+ },
+ ];
+};
-const myInput = ["=", ["-", "w", 2], 3];
+console.log(
+ "tryAllRulesRecursively 1",
+ tryAllRulesRecursively(MY_RULES, "a"),
+ tryAllRulesRecursively(MY_RULES, ["a"])
+);
-const toTry = [myInput];
-while (toTry.length > 0) {
- const i = toTry.pop();
- see(i);
- toTry.push(...rewriteStep(i, rules).filter((res) => !isSeen(res)));
+console.log(
+ "tryAllRulesRecursively 2",
+ tryAllRulesRecursively(MY_RULES, ["+", "a", "b"])
+);
+
+console.log("sToRule 1", sToRule(["=", v`x`, ["+", 1, 2]]));
+console.log("sToRule 2", sToRule(["=", ["sin", v`x`], ["+", 1, 2]]));
+
+console.log(
+ "sToRule 3",
+ tryAllRulesRecursively(
+ [
+ {
+ from: v`lhs`.withPred((x) => x.kind === "variable" && x.name === "x"),
+ to: 22,
+ },
+ ],
+ ["sin", v`x`]
+ ),
+ tryAllRulesRecursively(sToRule(["=", v`x`, ["+", 1, 2]]), [
+ "+",
+ 33,
+ ["-", 1, v`x`],
+ ])
+);
+
+const mySet1 = new Set();
+const mySet2 = new Set();
+const see = (set, ob) => set.add(JSON.stringify(ob));
+const isSeen = (set, ob) => set.has(JSON.stringify(ob));
+
+const rules = [...MY_RULES];
+const toTry1 = [myInput1];
+const toTry2 = [myInput2];
+for (let i = 0; i < 12; i++) {
+ if (toTry1.length === 0 && toTry2.length === 0) break;
+ if (toTry1.length > 0) {
+ const i = toTry1.pop();
+ see(mySet1, i);
+ const newStructuresToTry = tryAllRulesRecursively(rules, i).filter(
+ (res) => !isSeen(mySet1, res)
+ );
+ toTry1.push(...newStructuresToTry);
+ rules.push(...newStructuresToTry.flatMap(sToRule));
+ }
+ if (toTry2.length > 0) {
+ const i = toTry2.pop();
+ see(mySet2, i);
+ const newStructuresToTry = tryAllRulesRecursively(rules, i).filter(
+ (res) => !isSeen(mySet2, res)
+ );
+ toTry2.push(...newStructuresToTry);
+ rules.push(...newStructuresToTry.flatMap(sToRule));
+ }
+ console.log("hi", toTry1.length);
}
-console.log([...mySet.values()].map(JSON.parse));
+console.log(
+ "go go go",
+ [...mySet1.values()].map(JSON.parse),
+ [...mySet2.values()].map(JSON.parse),
+ rules
+);
+// console.log(
+// "SELECT",
+// [...mySet.values()]
+// .map(JSON.parse)
+// .filter(
+// ([[_1, v1], [_2, v2]]) => v1.kind === "variable" && v2.kind === "variable"
+// )
+// .map(([[_1, v1], [_2, v2]]) => [v1.name, v2.name])
+// );
diff --git a/src/demo/2024_08/e_graph.html b/src/demo/2024_08/e_graph.html
new file mode 100644
index 0000000..d64bca7
--- /dev/null
+++ b/src/demo/2024_08/e_graph.html
@@ -0,0 +1 @@
+
diff --git a/src/demo/2024_08/e_graph.js b/src/demo/2024_08/e_graph.js
new file mode 100644
index 0000000..496a503
--- /dev/null
+++ b/src/demo/2024_08/e_graph.js
@@ -0,0 +1,85 @@
+// e init (tree) => e-graph
+// e match (pattern, e-graph) => e-node[]
+// e sub (result, e-node, e-graph) => e-graph
+
+const node = (value, ...children) => {
+ const res = {
+ isNode: true,
+ value,
+ children: children,
+ parent: null,
+ };
+ for (const child of children) child.parent = res;
+ return res;
+};
+const vari = (v, ...children) => {
+ const res = {
+ isNode: true,
+ var: v,
+ children: children,
+ parent: null,
+ };
+ for (const child of children) child.parent = res;
+ return res;
+};
+const nodeEq = (n1, n2) => {
+ if (n1.value !== n2.value) return false;
+ else if (n1.children.length !== n2.children.length) return false;
+ else return n1.children.every((v, i) => nodeEq(v, n2.children[i]));
+};
+
+const a = node("f", node(1), node(2));
+const b = node("f", node(1), node(2));
+
+console.log("nodeEq!", nodeEq(a, b));
+
+const eClassFromNode = (node, parentENode = null) => {
+ const eNode = { isENode: true, value: node.value };
+ eNode.children = node.children.map((n) => eClassFromNode(n, eNode));
+ return {
+ isEClass: true,
+ eNodes: [eNode],
+ parents: [parentENode],
+ };
+};
+
+console.log("eClassFromNode!", eClassFromNode(a));
+
+const eClassMatches = (patternNode, eClass) => {
+ return eClass.eNodes.flatMap((en) => eNodeMatches(patternNode, en));
+};
+const eNodeMatches = (patternNode, eNode) => {
+ if (patternNode.var === undefined && eNode.value !== patternNode.value)
+ return [];
+ else if (patternNode.children.length !== eNode.children.length) return [];
+ else {
+ const childrenMatches = eNode.children.map((ec, i) =>
+ eClassMatches(patternNode.children[i], ec)
+ );
+ return [
+ ...gogo(
+ childrenMatches,
+ patternNode.var ? { [patternNode.var]: eNode.value } : {}
+ ),
+ ];
+ }
+};
+
+const gogo = function* (childrenMatches, match) {
+ if (childrenMatches.length === 0) {
+ yield { ...match };
+ return;
+ }
+ for (const matches1 of childrenMatches[0]) {
+ for (const matches2 of gogo(childrenMatches.slice(1))) {
+ yield { ...match, ...matches1, ...matches2 };
+ }
+ }
+};
+
+console.log(
+ "eClassMatches!",
+ eClassMatches(vari("go", vari("1"), node(2)), eClassFromNode(a))
+);
+
+// Aside: the implicit lifting language could maybe really help simplify this.
diff --git a/src/demo/2024_08/egg_port.ts b/src/demo/2024_08/egg_port.ts
new file mode 100644
index 0000000..cdb50d0
--- /dev/null
+++ b/src/demo/2024_08/egg_port.ts
@@ -0,0 +1,88 @@
+import { SetMap } from "../../lib/structure/data.js";
+import { makeSet, find, union } from "./union_find.js";
+
+type EClassId = { id: number };
+type ENode = {
+ op: string;
+ children: EClassId[];
+ hash: string;
+};
+
+// map canonical e-nodes to e-class ids
+// i.e. e-node 𝑛 ∈ 𝑀 [𝑎] ⇐⇒ 𝐻 [canonicalize(𝑛)] = find(𝑎)
+const hashcons = new Map();
+const parents = new Map>();
+const worklist = new Set();
+
+const mk_enode = (op: string, children: EClassId[]): ENode => ({
+ op,
+ children,
+ hash: op + children.map((c) => "c" + c.id).join(),
+});
+
+// TODO: fix canonicalize to respect hashcons
+const canonicalize = (eNode: ENode) =>
+ mk_enode(eNode.op, eNode.children.map(find));
+const lookup = (eNode: ENode): EClassId | undefined => hashcons.get(eNode.hash);
+
+// eNode -> eClassId
+const add = (eNode: ENode): EClassId => {
+ const en = canonicalize(eNode);
+ if (lookup(en)) return lookup(en)!;
+ else {
+ const eClassId = makeSet();
+ for (const child of eNode.children)
+ parents.set(
+ child,
+ parents.get(child)?.set(en, eClassId) ?? new Map([[en, eClassId]])
+ );
+ hashcons.set(en.hash, eClassId);
+ return eClassId;
+ }
+};
+const merge = (eClassId1, eClassId2) => {
+ if (find(eClassId1) === find(eClassId2)) return find(eClassId1);
+ const newEClassId = union(eClassId1, eClassId2);
+ worklist.add(newEClassId);
+ return newEClassId;
+};
+
+const rebuild = () => {
+ while (worklist.size > 0) {
+ const todo = [...worklist];
+ worklist.clear();
+ for (const eClassId of todo) repair(find(eClassId));
+ }
+};
+const repair = (eClassId) => {
+ for (const [peNode, peClassId] of parents.get(eClassId) ?? []) {
+ hashcons.delete(peNode.hash);
+ const newPeNode = canonicalize(peNode);
+ hashcons.set(newPeNode.hash, peClassId);
+ }
+
+ const newParents = new Map();
+ for (const [peNode, peClassId] of parents.get(eClassId) ?? []) {
+ const newPeNode = canonicalize(peNode);
+ if (newParents.has(newPeNode)) {
+ merge(peClassId, newParents.get(newPeNode));
+ }
+ newParents.set(newPeNode, find(peClassId));
+ }
+ parents.set(eClassId, newParents);
+};
+
+//const ematch = (pattern) => [subts, eClass][]
+
+// To apply a rewrite l → 𝑟 to an e-graph,
+// ematch finds tuples (𝜎,𝑐) where e-class 𝑐 represents l[𝜎].
+// Then, for each tuple, merge(𝑐, add(𝑟 [𝜎])) adds 𝑟 [𝜎] to the e-graph
+// and unifies it with the matching e-class c.
+
+add(mk_enode("1", []));
+add(mk_enode("2", []));
+add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")]));
+add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")]));
+rebuild();
+
+console.log(hashcons);
diff --git a/src/demo/2024_08/union_find.js b/src/demo/2024_08/union_find.js
new file mode 100644
index 0000000..7afaa29
--- /dev/null
+++ b/src/demo/2024_08/union_find.js
@@ -0,0 +1,56 @@
+// source: https://github.com/manubb/union-find
+
+let idCounter = 0;
+export const makeSet = () => {
+ const singleton = {
+ rank: 0,
+ id: idCounter++,
+ };
+ singleton.parent = singleton;
+
+ return singleton;
+};
+
+export const find = (node) => {
+ if (node.parent !== node) {
+ node.parent = find(node.parent);
+ }
+
+ return node.parent;
+};
+
+export const union = (node1, node2) => {
+ const root1 = find(node1);
+ const root2 = find(node2);
+ if (root1 !== root2) {
+ if (root1.rank < root2.rank) {
+ root1.parent = root2;
+ } else {
+ root2.parent = root1;
+ if (root1.rank === root2.rank) root1.rank += 1;
+ }
+ }
+};
+
+/*
+MIT License
+
+Copyright (c) 2018 Manuel Baclet
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.*/