From f7ad8b4c6e64ac24dde029b547d21a62572b55db Mon Sep 17 00:00:00 2001 From: vezwork Date: Thu, 8 Aug 2024 16:54:36 -0400 Subject: [PATCH] Rough library version of use_e build and evaluation --- dist/demo/2024_08/archive/e_graph.js | 115 +++++++++++ dist/demo/2024_08/archive/egg_port.js | 69 +++++++ dist/demo/2024_08/archive/use_e_basic.js | 37 ++++ dist/demo/2024_08/e_graph.js | 227 +++++++++++++-------- dist/demo/2024_08/e_graph2.js | 182 ----------------- dist/demo/2024_08/use_e.js | 218 +++++++++++--------- src/demo/2024_08/archive/e_graph.js | 115 +++++++++++ src/demo/2024_08/{ => archive}/egg_port.ts | 0 src/demo/2024_08/archive/idea.txt | 17 ++ src/demo/2024_08/archive/use_e_basic.js | 37 ++++ src/demo/2024_08/e_graph.js | 227 +++++++++++++-------- src/demo/2024_08/e_graph2.js | 182 ----------------- src/demo/2024_08/use_e.js | 218 +++++++++++--------- 13 files changed, 924 insertions(+), 720 deletions(-) create mode 100644 dist/demo/2024_08/archive/e_graph.js create mode 100644 dist/demo/2024_08/archive/egg_port.js create mode 100644 dist/demo/2024_08/archive/use_e_basic.js delete mode 100644 dist/demo/2024_08/e_graph2.js create mode 100644 src/demo/2024_08/archive/e_graph.js rename src/demo/2024_08/{ => archive}/egg_port.ts (100%) create mode 100644 src/demo/2024_08/archive/idea.txt create mode 100644 src/demo/2024_08/archive/use_e_basic.js delete mode 100644 src/demo/2024_08/e_graph2.js diff --git a/dist/demo/2024_08/archive/e_graph.js b/dist/demo/2024_08/archive/e_graph.js new file mode 100644 index 0000000..3a2fc6c --- /dev/null +++ b/dist/demo/2024_08/archive/e_graph.js @@ -0,0 +1,115 @@ +// e init (tree) => e-graph +// e match (pattern, e-graph) => e-node[] +// e sub (result, e-node, e-graph) => e-graph + +let nodeIdCounter = 0; +const nodes = new Map(); + +const node = (value, ...children) => { + const hash = value + children.map((c) => ":" + c.id).join(""); + if (nodes.has(hash)) return nodes.get(hash); + const res = { + isNode: true, + value, + children: children, + parents: [], + id: nodeIdCounter++, + hash, + }; + nodes.set(hash, res); + for (const child of children) child.parents.push(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) => n1.hash === n2.hash; + +const a = node("f", node(1), node(2)); +const b = node("f", node(1), node(2)); + +console.log("nodeEq!", nodeEq(a, b)); +console.log("nodes!", nodes); + +let idCounter = 0; +const eClasses = new Set(); +const eNodes = new Map(); + +const eNode = (value, ...children) => { + const hash = value + children.map((c) => ":" + c.id).join(""); + if (eNodes.has(hash)) return nodes.get(hash); + const res = { + isENode: true, + value, + children: children, + parents: [], + id: nodeIdCounter++, + hash, + }; + nodes.set(hash, res); + for (const child of children) child.parents.push(res); + return res; +}; + +const eNodes = new Map(); +let eClassIdCounter = 0; +const eClassFromNode = (node, parentENode = null) => { + const eNode = { isENode: true, value: node.value }; + eNode.children = node.children.map((n) => eClassFromNode(n, eNode)); + const id = node.value + eNode.children.map((c) => "c" + c.id).join(); + eNodes.set(id, eNode); + return { + isEClass: true, + eNodes: [eNode], + parents: [parentENode], + id: eClassIdCounter++, + }; +}; + +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/archive/egg_port.js b/dist/demo/2024_08/archive/egg_port.js new file mode 100644 index 0000000..aa87728 --- /dev/null +++ b/dist/demo/2024_08/archive/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/archive/use_e_basic.js b/dist/demo/2024_08/archive/use_e_basic.js new file mode 100644 index 0000000..468cd08 --- /dev/null +++ b/dist/demo/2024_08/archive/use_e_basic.js @@ -0,0 +1,37 @@ +import { + node, + vari, + merge, + printEClasses, + runRules, + e, + checkEq, + unhash, + eClassMatches, +} from "./e_graph.js"; +import { setFromId, find } from "./union_find.js"; + +const rules = [ + { + from: [node("a")], + to: ({}, ec) => merge(ec, e("b")), + }, + { + from: [node("b")], + to: ({}, ec) => merge(ec, e("a")), + }, +]; + +e("+", e("a")); +e("+", e("b")); + +printEClasses(); + +runRules(rules); + +printEClasses(); + +console.log( + "CHECK", + checkEq(node("a"), node("+", node("-", node("b"), node("c")), node(0))) +); diff --git a/dist/demo/2024_08/e_graph.js b/dist/demo/2024_08/e_graph.js index 3a2fc6c..d77aed2 100644 --- a/dist/demo/2024_08/e_graph.js +++ b/dist/demo/2024_08/e_graph.js @@ -1,115 +1,182 @@ -// e init (tree) => e-graph -// e match (pattern, e-graph) => e-node[] -// e sub (result, e-node, e-graph) => e-graph - -let nodeIdCounter = 0; -const nodes = new Map(); - -const node = (value, ...children) => { - const hash = value + children.map((c) => ":" + c.id).join(""); - if (nodes.has(hash)) return nodes.get(hash); - const res = { - isNode: true, - value, - children: children, - parents: [], - id: nodeIdCounter++, - hash, - }; - nodes.set(hash, res); - for (const child of children) child.parents.push(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; +import { map, first, skip, withIndex } from "../../lib/structure/Iterable.js"; +import { hash, makeHashcons } from "./hashcons.js"; +import { + find, + items, + union, + makeSet, + sets, + parents, + setFromId, +} from "./union_find.js"; + +const worklist = []; + +export const merge = (id1, id2) => { + if (find(id1) === find(id2)) return find(id1); + const newId = union(id1, id2); + + worklist.push(newId); + + return newId; }; -const nodeEq = (n1, n2) => n1.hash === n2.hash; -const a = node("f", node(1), node(2)); -const b = node("f", node(1), node(2)); +const canonicalize = (eNode) => + makeENode(eNode.value, ...eNode.children.map(find)); +const hashcons = makeHashcons(); +const makeENode = (value, ...children) => ({ + value, + children, +}); +const add = (eNode) => { + eNode = canonicalize(eNode); + if (hashcons.has(eNode)) return hashcons.get(eNode); -console.log("nodeEq!", nodeEq(a, b)); -console.log("nodes!", nodes); + const eClassId = makeSet(eNode); -let idCounter = 0; -const eClasses = new Set(); -const eNodes = new Map(); + for (const child of eNode.children) parents(child).set(eNode, eClassId); -const eNode = (value, ...children) => { - const hash = value + children.map((c) => ":" + c.id).join(""); - if (eNodes.has(hash)) return nodes.get(hash); - const res = { - isENode: true, + hashcons.set(eNode, eClassId); + return eClassId; +}; + +export const e = (value, ...children) => add(makeENode(value, ...children)); + +export const unhash = (str) => { + const [value, ...classIds] = str.split(":"); + return makeENode( value, - children: children, - parents: [], - id: nodeIdCounter++, - hash, - }; - nodes.set(hash, res); - for (const child of children) child.parents.push(res); - return res; + ...classIds.map(Number).map((id) => setFromId.get(id)) + ); }; -const eNodes = new Map(); -let eClassIdCounter = 0; -const eClassFromNode = (node, parentENode = null) => { - const eNode = { isENode: true, value: node.value }; - eNode.children = node.children.map((n) => eClassFromNode(n, eNode)); - const id = node.value + eNode.children.map((c) => "c" + c.id).join(); - eNodes.set(id, eNode); - return { - isEClass: true, - eNodes: [eNode], - parents: [parentENode], - id: eClassIdCounter++, - }; +const rebuild = () => { + while (worklist.length > 0) { + const todo = worklist.map(find); + worklist.length = 0; + for (const eClass of todo) repair(eClass); + } }; +const repair = (eClass) => { + for (let [peNode, peClass] of parents(eClass)) { + peNode = unhash(peNode); + hashcons.remove(peNode); + find(peClass).items.delete(hash(peNode)); + peNode = canonicalize(peNode); + find(peClass).items.add(hash(peNode)); + hashcons.set(peNode, find(peClass)); + } -console.log("eClassFromNode!", eClassFromNode(a)); + const newParents = makeHashcons(); + for (let [peNode, peClass] of parents(eClass)) { + peNode = unhash(peNode); + peNode = canonicalize(peNode); + if (newParents.has(peNode)) merge(peClass, newParents.get(peNode)); + newParents.set(peNode, find(peClass)); + } + find(eClass).parents = newParents; +}; -const eClassMatches = (patternNode, eClass) => { - return eClass.eNodes.flatMap((en) => eNodeMatches(patternNode, en)); +const printENode = ({ value, children }) => + value + + (children.length === 0 ? "" : "(" + children.map(printEClassId) + ")"); +const printEClassId = (eClass) => find(eClass).id + ":"; +const printEClass = (eClass) => + printEClassId(eClass) + "{" + [...items(eClass)].join(", ") + "}"; + +export const printEClasses = () => + console.log([...sets].map(printEClass).join("\n")); + +export const eClassMatches = (patternNode, eClass) => { + if (eClass === undefined) return []; + return [...items(eClass)] + .map(unhash) + .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 if ( + patternNode.var === undefined && + patternNode.children.length !== eNode.children.length + ) + return []; else { - const childrenMatches = eNode.children.map((ec, i) => - eClassMatches(patternNode.children[i], ec) + const childrenMatches = patternNode.children.map((p, i) => + eClassMatches(p, eNode.children[i]) ); return [ - ...gogo( + ...objectCombos( childrenMatches, - patternNode.var ? { [patternNode.var]: eNode.value } : {} + patternNode.var ? { [patternNode.var]: find(hashcons.get(eNode)) } : {} ), ]; } }; -const gogo = function* (childrenMatches, match) { +const objectCombos = function* (childrenMatches, match) { if (childrenMatches.length === 0) { yield { ...match }; return; } for (const matches1 of childrenMatches[0]) { - for (const matches2 of gogo(childrenMatches.slice(1))) { + for (const matches2 of objectCombos(childrenMatches.slice(1))) { yield { ...match, ...matches1, ...matches2 }; } } }; -console.log( - "eClassMatches!", - eClassMatches(vari("go", vari("1"), node(2)), eClassFromNode(a)) -); +// // TODO: make matches with the same var require the same value + +export const node = (value, ...children) => ({ + value, + children, +}); +export const vari = (v, ...children) => ({ + var: v, + children, +}); + +const oneFromEach = function* (arrays) { + if (arrays.length === 0) { + yield []; + return; + } + for (const item of arrays[0]) { + for (const array of oneFromEach(arrays.slice(1))) { + yield [item, ...array]; + } + } +}; +const objFromObjs = (objs) => objs.reduce((prev, cur) => ({ ...prev, ...cur })); + +const readRule = ({ from, to }) => + [...sets] + .map((c) => ({ + c, + matches: from.map((pattern) => eClassMatches(pattern, c)), + })) + .map(({ c, matches }) => ({ c, to, matchCombos: oneFromEach(matches) })); +const enactRule = (r) => + r.map(({ c, to, matchCombos }) => [ + ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), + ]); +// const runRule = ({ from, to }) => +// [...sets] +// .map((c) => ({ +// c, +// matches: from.map((pattern) => eClassMatches(pattern, c)), +// })) +// .map(({ c, matches }) => ({ c, matchCombos: oneFromEach(matches) })) +// .map(({ c, matchCombos }) => [ +// ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), +// ]); +export const runRules = (rules) => { + rules.map(readRule).map(enactRule); + rebuild(); +}; -// Aside: the implicit lifting language could maybe really help simplify this. +export const checkEq = (...patterns) => + [...sets] + .map((c) => patterns.map((pattern) => eClassMatches(pattern, c))) + .some((matches) => matches.every((match) => match.length > 0)); diff --git a/dist/demo/2024_08/e_graph2.js b/dist/demo/2024_08/e_graph2.js deleted file mode 100644 index bdb4e1c..0000000 --- a/dist/demo/2024_08/e_graph2.js +++ /dev/null @@ -1,182 +0,0 @@ -import { map, first, skip, withIndex } from "../../lib/structure/Iterable.js"; -import { hash, makeHashcons } from "./hashcons.js"; -import { - find, - items, - union, - makeSet, - sets, - parents, - setFromId, -} from "./union_find.js"; - -const worklist = []; - -export const merge = (id1, id2) => { - if (find(id1) === find(id2)) return find(id1); - const newId = union(id1, id2); - - worklist.push(newId); - - return newId; -}; - -const canonicalize = (eNode) => - makeENode(eNode.value, ...eNode.children.map(find)); -const hashcons = makeHashcons(); -const makeENode = (value, ...children) => ({ - value, - children, -}); -const add = (eNode) => { - eNode = canonicalize(eNode); - if (hashcons.has(eNode)) return hashcons.get(eNode); - - const eClassId = makeSet(eNode); - - for (const child of eNode.children) parents(child).set(eNode, eClassId); - - hashcons.set(eNode, eClassId); - return eClassId; -}; - -export const e = (value, ...children) => add(makeENode(value, ...children)); - -export const unhash = (str) => { - const [value, ...classIds] = str.split(":"); - return makeENode( - value, - ...classIds.map(Number).map((id) => setFromId.get(id)) - ); -}; - -const rebuild = () => { - while (worklist.length > 0) { - const todo = worklist.map(find); - worklist.length = 0; - for (const eClass of todo) repair(eClass); - } -}; -const repair = (eClass) => { - for (let [peNode, peClass] of parents(eClass)) { - peNode = unhash(peNode); - hashcons.remove(peNode); - find(peClass).items.delete(hash(peNode)); - peNode = canonicalize(peNode); - find(peClass).items.add(hash(peNode)); - hashcons.set(peNode, find(peClass)); - } - - const newParents = makeHashcons(); - for (let [peNode, peClass] of parents(eClass)) { - peNode = unhash(peNode); - peNode = canonicalize(peNode); - if (newParents.has(peNode)) merge(peClass, newParents.get(peNode)); - newParents.set(peNode, find(peClass)); - } - find(eClass).parents = newParents; -}; - -const printENode = ({ value, children }) => - value + - (children.length === 0 ? "" : "(" + children.map(printEClassId) + ")"); -const printEClassId = (eClass) => find(eClass).id + ":"; -const printEClass = (eClass) => - printEClassId(eClass) + "{" + [...items(eClass)].join(", ") + "}"; - -export const printEClasses = () => - console.log([...sets].map(printEClass).join("\n")); - -const eClassMatches = (patternNode, eClass) => { - if (eClass === undefined) return []; - return [...items(eClass)] - .map(unhash) - .flatMap((en) => eNodeMatches(patternNode, en)); -}; -const eNodeMatches = (patternNode, eNode) => { - if (patternNode.var === undefined && eNode.value !== patternNode.value) - return []; - else if ( - patternNode.var === undefined && - patternNode.children.length !== eNode.children.length - ) - return []; - else { - const childrenMatches = patternNode.children.map((p, i) => - eClassMatches(p, eNode.children[i]) - ); - return [ - ...objectCombos( - childrenMatches, - patternNode.var ? { [patternNode.var]: find(hashcons.get(eNode)) } : {} - ), - ]; - } -}; - -const objectCombos = function* (childrenMatches, match) { - if (childrenMatches.length === 0) { - yield { ...match }; - return; - } - for (const matches1 of childrenMatches[0]) { - for (const matches2 of objectCombos(childrenMatches.slice(1))) { - yield { ...match, ...matches1, ...matches2 }; - } - } -}; - -// // TODO: make matches with the same var require the same value - -export const node = (value, ...children) => ({ - value, - children, -}); -export const vari = (v, ...children) => ({ - var: v, - children, -}); - -const oneFromEach = function* (arrays) { - if (arrays.length === 0) { - yield []; - return; - } - for (const item of arrays[0]) { - for (const array of oneFromEach(arrays.slice(1))) { - yield [item, ...array]; - } - } -}; -const objFromObjs = (objs) => objs.reduce((prev, cur) => ({ ...prev, ...cur })); - -const readRule = ({ from, to }) => - [...sets] - .map((c) => ({ - c, - matches: from.map((pattern) => eClassMatches(pattern, c)), - })) - .map(({ c, matches }) => ({ c, to, matchCombos: oneFromEach(matches) })); -const enactRule = (r) => - r.map(({ c, to, matchCombos }) => [ - ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), - ]); -// const runRule = ({ from, to }) => -// [...sets] -// .map((c) => ({ -// c, -// matches: from.map((pattern) => eClassMatches(pattern, c)), -// })) -// .map(({ c, matches }) => ({ c, matchCombos: oneFromEach(matches) })) -// .map(({ c, matchCombos }) => [ -// ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), -// ]); -export const runRules = (rules) => { - rules.map(readRule).map(enactRule); - rebuild(); -}; - -export const checkEq = (...patterns) => - [...sets] - .map((c) => patterns.map((pattern) => eClassMatches(pattern, c))) - .some((matches) => matches.every((match) => match.length > 0)); diff --git a/dist/demo/2024_08/use_e.js b/dist/demo/2024_08/use_e.js index f3f96fa..9149792 100644 --- a/dist/demo/2024_08/use_e.js +++ b/dist/demo/2024_08/use_e.js @@ -7,96 +7,100 @@ import { e, checkEq, unhash, -} from "./e_graph2.js"; + eClassMatches, +} from "./e_graph.js"; import { setFromId, find } from "./union_find.js"; -// const rules = [ -// { -// from: [node("a")], -// to: ({}, ec) => merge(ec, e("b")), -// }, -// { -// from: [node("b")], -// to: ({}, ec) => merge(ec, e("a")), -// }, -// ]; - -// e("+", e("a")); -// e("+", e("b")); +const processToRule = (v, lookup) => + v.var + ? lookup[v.var] + : e(v.value, ...v.children.map((c) => processToRule(c, lookup))); + +const makeRule = ({ from, to }) => { + const fromfrom = Array.isArray(from) ? from : [from]; + const toto = Array.isArray(to) + ? (lookup) => + merge(processToRule(to[0], lookup), processToRule(to[1], lookup)) + : (lookup, eClass) => merge(eClass, processToRule(to, lookup)); + return { + from: fromfrom, + to: toto, + }; +}; +const nodeEq = (...c) => c; +const addRule = (r) => rules.push(makeRule(r)); -// printEClasses(); +const rules = []; -// runRules(rules); +const definitions = {}; +const define = (key, f) => { + definitions[key] = f; +}; -// printEClasses(); +const isCommutative = (op) => + addRule({ + from: node(op, vari("a"), vari("b")), + to: node(op, vari("b"), vari("a")), + }); +const isInverse = (op1, op2) => { + addRule({ + from: nodeEq(vari("a"), node(op1, vari("b"), vari("c"))), + to: nodeEq(vari("b"), node(op2, vari("a"), vari("c"))), + }); + addRule({ + from: nodeEq(vari("a"), node(op2, vari("b"), vari("c"))), + to: nodeEq(vari("b"), node(op1, vari("a"), vari("c"))), + }); +}; -// console.log( -// "CHECK", -// checkEq(node("a"), node("+", node("-", node("b"), node("c")), node(0))) -// ); - -const rules = [ - { - from: [node("+", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("-", c, b)), - }, - { - from: [node("-", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("+", c, b)), - }, - { - from: [node("+", vari("a"), vari("b"))], - to: ({ a, b }, eClass) => merge(eClass, e("+", b, a)), - }, - { - from: [node("*", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("/", c, b)), - }, - { - from: [node("/", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("*", c, b)), - }, - { - from: [node("*", vari("a"), vari("b"))], - to: ({ a, b }, eClass) => merge(eClass, e("*", b, a)), - }, - - // { - // from: [node("+", node("+", vari("a"), vari("b")), vari("c"))], - // to: ({ a, b, c }, eClass) => merge(eClass, e("+", a, e("+", b, c))), - // }, - - // { - // from: [vari("a")], - // to: ({ a }) => merge(a, e("+", a, e(0))), - // }, -]; - -e("l"); -e("r"); -merge(e("w"), e("-", e("r"), e("l"))); -merge(e("c"), e("+", e("l"), e("/", e("w"), e(2)))); - -printEClasses(); - -runRules(rules); -runRules(rules); -runRules(rules); -runRules(rules); -runRules(rules); - -printEClasses(); - -const values = new Map(); +const todo = []; +let values = new Map(); const setValue = (key, value) => values.set(find(e(key)), value); -setValue(2, 2); -setValue("w", 4); -setValue("r", 5); - -const todo = [find(e("w")), find(e("r"))]; +const printValue = (key) => + console.log("printValue: " + key + " = " + values.get(find(e(key)))); + +const eq = (...args) => + merge( + ...args.map((arg) => { + if (typeof arg === "number") { + setValue(arg, arg); + todo.push(find(e(arg))); + } + return ["string", "number"].includes(typeof arg) ? e(arg) : arg; + }) + ); +const op = (value, ...children) => + e( + value, + ...children.map((arg) => { + if (typeof arg === "number") { + setValue(arg, arg); + todo.push(find(e(arg))); + } + return ["string", "number"].includes(typeof arg) ? e(arg) : arg; + }) + ); + +const build = () => { + //printEClasses(); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + + const newValues = new Map(); + for (const [k, v] of values) { + newValues.set(find(k), v); + } + values = newValues; + //printEClasses(); +}; const evalC = (clas) => { - const parentValues = [...clas.parents] + const parentValues = [...find(clas).parents] .filter(([n, c]) => values.get(find(c)) === undefined) .map(([n, c]) => ({ c, @@ -106,28 +110,46 @@ const evalC = (clas) => { .filter(({ vs }) => !vs.some((v) => v === undefined)); if (parentValues.length > 0) { const { c, op, vs } = parentValues[0]; - values.set( - find(c), - { - "-": vs[0] - vs[1], - "+": vs[0] + vs[1], - "*": vs[0] * vs[1], - "/": vs[0] / vs[1], - }[op] - ); - console.log(op, vs, values.get(find(c))); + values.set(find(c), definitions[op](...vs)); + //console.log(find(c).id, op, vs, values.get(find(c))); todo.push(find(c)); } }; -while (todo.length > 0) { - const next = [...todo]; - todo.length = 0; - for (const nex of next) { - evalC(nex); +const evaluate = () => { + while (todo.length > 0) { + const next = [...todo]; + todo.length = 0; + for (const nex of next) { + evalC(nex); + } } -} +}; + +define("+", (a, b) => a + b); +isCommutative("+"); +define("-", (a, b) => a - b); +isInverse("+", "-"); + +define("*", (a, b) => a * b); +isCommutative("*"); +define("/", (a, b) => a / b); +isInverse("*", "/"); -console.log([...values].map(([k, v]) => k.id + " : " + v).join("\n")); +eq("width", op("-", "right", "left")); +eq("center", op("+", "left", op("/", "width", 2))); +eq("width", 4); +eq("left", 10); + +build(); +evaluate(); + +printValue("left"); +printValue("width"); +printValue("center"); +printValue("right"); + +// printEClasses(); +// console.log([...values].map(([k, v]) => k.id + " : " + v).join("\n")); // console.log("CHECK", checkEq(node("r"), node("+", node("w"), node("l")))); diff --git a/src/demo/2024_08/archive/e_graph.js b/src/demo/2024_08/archive/e_graph.js new file mode 100644 index 0000000..3a2fc6c --- /dev/null +++ b/src/demo/2024_08/archive/e_graph.js @@ -0,0 +1,115 @@ +// e init (tree) => e-graph +// e match (pattern, e-graph) => e-node[] +// e sub (result, e-node, e-graph) => e-graph + +let nodeIdCounter = 0; +const nodes = new Map(); + +const node = (value, ...children) => { + const hash = value + children.map((c) => ":" + c.id).join(""); + if (nodes.has(hash)) return nodes.get(hash); + const res = { + isNode: true, + value, + children: children, + parents: [], + id: nodeIdCounter++, + hash, + }; + nodes.set(hash, res); + for (const child of children) child.parents.push(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) => n1.hash === n2.hash; + +const a = node("f", node(1), node(2)); +const b = node("f", node(1), node(2)); + +console.log("nodeEq!", nodeEq(a, b)); +console.log("nodes!", nodes); + +let idCounter = 0; +const eClasses = new Set(); +const eNodes = new Map(); + +const eNode = (value, ...children) => { + const hash = value + children.map((c) => ":" + c.id).join(""); + if (eNodes.has(hash)) return nodes.get(hash); + const res = { + isENode: true, + value, + children: children, + parents: [], + id: nodeIdCounter++, + hash, + }; + nodes.set(hash, res); + for (const child of children) child.parents.push(res); + return res; +}; + +const eNodes = new Map(); +let eClassIdCounter = 0; +const eClassFromNode = (node, parentENode = null) => { + const eNode = { isENode: true, value: node.value }; + eNode.children = node.children.map((n) => eClassFromNode(n, eNode)); + const id = node.value + eNode.children.map((c) => "c" + c.id).join(); + eNodes.set(id, eNode); + return { + isEClass: true, + eNodes: [eNode], + parents: [parentENode], + id: eClassIdCounter++, + }; +}; + +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/archive/egg_port.ts similarity index 100% rename from src/demo/2024_08/egg_port.ts rename to src/demo/2024_08/archive/egg_port.ts diff --git a/src/demo/2024_08/archive/idea.txt b/src/demo/2024_08/archive/idea.txt new file mode 100644 index 0000000..00974b9 --- /dev/null +++ b/src/demo/2024_08/archive/idea.txt @@ -0,0 +1,17 @@ +fn + := js`(a,b) => a+b` +rule +(a b) ===> +(b a) + +fn - := js`(a,b) => a-b` +rule -(a b) = c <==> a = +(b c) + +fn * := js`(a,b) => a*b` +rule *(a b) ===> *(b a) + +fn / := js`(a,b) => a/b` +rule /(a b) = c <==> a = *(b c) + + +const width = -(left right) +const center = +(left /(width 2)) +const left = 2 +const *(10, width) = 40 \ No newline at end of file diff --git a/src/demo/2024_08/archive/use_e_basic.js b/src/demo/2024_08/archive/use_e_basic.js new file mode 100644 index 0000000..468cd08 --- /dev/null +++ b/src/demo/2024_08/archive/use_e_basic.js @@ -0,0 +1,37 @@ +import { + node, + vari, + merge, + printEClasses, + runRules, + e, + checkEq, + unhash, + eClassMatches, +} from "./e_graph.js"; +import { setFromId, find } from "./union_find.js"; + +const rules = [ + { + from: [node("a")], + to: ({}, ec) => merge(ec, e("b")), + }, + { + from: [node("b")], + to: ({}, ec) => merge(ec, e("a")), + }, +]; + +e("+", e("a")); +e("+", e("b")); + +printEClasses(); + +runRules(rules); + +printEClasses(); + +console.log( + "CHECK", + checkEq(node("a"), node("+", node("-", node("b"), node("c")), node(0))) +); diff --git a/src/demo/2024_08/e_graph.js b/src/demo/2024_08/e_graph.js index 3a2fc6c..d77aed2 100644 --- a/src/demo/2024_08/e_graph.js +++ b/src/demo/2024_08/e_graph.js @@ -1,115 +1,182 @@ -// e init (tree) => e-graph -// e match (pattern, e-graph) => e-node[] -// e sub (result, e-node, e-graph) => e-graph - -let nodeIdCounter = 0; -const nodes = new Map(); - -const node = (value, ...children) => { - const hash = value + children.map((c) => ":" + c.id).join(""); - if (nodes.has(hash)) return nodes.get(hash); - const res = { - isNode: true, - value, - children: children, - parents: [], - id: nodeIdCounter++, - hash, - }; - nodes.set(hash, res); - for (const child of children) child.parents.push(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; +import { map, first, skip, withIndex } from "../../lib/structure/Iterable.js"; +import { hash, makeHashcons } from "./hashcons.js"; +import { + find, + items, + union, + makeSet, + sets, + parents, + setFromId, +} from "./union_find.js"; + +const worklist = []; + +export const merge = (id1, id2) => { + if (find(id1) === find(id2)) return find(id1); + const newId = union(id1, id2); + + worklist.push(newId); + + return newId; }; -const nodeEq = (n1, n2) => n1.hash === n2.hash; -const a = node("f", node(1), node(2)); -const b = node("f", node(1), node(2)); +const canonicalize = (eNode) => + makeENode(eNode.value, ...eNode.children.map(find)); +const hashcons = makeHashcons(); +const makeENode = (value, ...children) => ({ + value, + children, +}); +const add = (eNode) => { + eNode = canonicalize(eNode); + if (hashcons.has(eNode)) return hashcons.get(eNode); -console.log("nodeEq!", nodeEq(a, b)); -console.log("nodes!", nodes); + const eClassId = makeSet(eNode); -let idCounter = 0; -const eClasses = new Set(); -const eNodes = new Map(); + for (const child of eNode.children) parents(child).set(eNode, eClassId); -const eNode = (value, ...children) => { - const hash = value + children.map((c) => ":" + c.id).join(""); - if (eNodes.has(hash)) return nodes.get(hash); - const res = { - isENode: true, + hashcons.set(eNode, eClassId); + return eClassId; +}; + +export const e = (value, ...children) => add(makeENode(value, ...children)); + +export const unhash = (str) => { + const [value, ...classIds] = str.split(":"); + return makeENode( value, - children: children, - parents: [], - id: nodeIdCounter++, - hash, - }; - nodes.set(hash, res); - for (const child of children) child.parents.push(res); - return res; + ...classIds.map(Number).map((id) => setFromId.get(id)) + ); }; -const eNodes = new Map(); -let eClassIdCounter = 0; -const eClassFromNode = (node, parentENode = null) => { - const eNode = { isENode: true, value: node.value }; - eNode.children = node.children.map((n) => eClassFromNode(n, eNode)); - const id = node.value + eNode.children.map((c) => "c" + c.id).join(); - eNodes.set(id, eNode); - return { - isEClass: true, - eNodes: [eNode], - parents: [parentENode], - id: eClassIdCounter++, - }; +const rebuild = () => { + while (worklist.length > 0) { + const todo = worklist.map(find); + worklist.length = 0; + for (const eClass of todo) repair(eClass); + } }; +const repair = (eClass) => { + for (let [peNode, peClass] of parents(eClass)) { + peNode = unhash(peNode); + hashcons.remove(peNode); + find(peClass).items.delete(hash(peNode)); + peNode = canonicalize(peNode); + find(peClass).items.add(hash(peNode)); + hashcons.set(peNode, find(peClass)); + } -console.log("eClassFromNode!", eClassFromNode(a)); + const newParents = makeHashcons(); + for (let [peNode, peClass] of parents(eClass)) { + peNode = unhash(peNode); + peNode = canonicalize(peNode); + if (newParents.has(peNode)) merge(peClass, newParents.get(peNode)); + newParents.set(peNode, find(peClass)); + } + find(eClass).parents = newParents; +}; -const eClassMatches = (patternNode, eClass) => { - return eClass.eNodes.flatMap((en) => eNodeMatches(patternNode, en)); +const printENode = ({ value, children }) => + value + + (children.length === 0 ? "" : "(" + children.map(printEClassId) + ")"); +const printEClassId = (eClass) => find(eClass).id + ":"; +const printEClass = (eClass) => + printEClassId(eClass) + "{" + [...items(eClass)].join(", ") + "}"; + +export const printEClasses = () => + console.log([...sets].map(printEClass).join("\n")); + +export const eClassMatches = (patternNode, eClass) => { + if (eClass === undefined) return []; + return [...items(eClass)] + .map(unhash) + .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 if ( + patternNode.var === undefined && + patternNode.children.length !== eNode.children.length + ) + return []; else { - const childrenMatches = eNode.children.map((ec, i) => - eClassMatches(patternNode.children[i], ec) + const childrenMatches = patternNode.children.map((p, i) => + eClassMatches(p, eNode.children[i]) ); return [ - ...gogo( + ...objectCombos( childrenMatches, - patternNode.var ? { [patternNode.var]: eNode.value } : {} + patternNode.var ? { [patternNode.var]: find(hashcons.get(eNode)) } : {} ), ]; } }; -const gogo = function* (childrenMatches, match) { +const objectCombos = function* (childrenMatches, match) { if (childrenMatches.length === 0) { yield { ...match }; return; } for (const matches1 of childrenMatches[0]) { - for (const matches2 of gogo(childrenMatches.slice(1))) { + for (const matches2 of objectCombos(childrenMatches.slice(1))) { yield { ...match, ...matches1, ...matches2 }; } } }; -console.log( - "eClassMatches!", - eClassMatches(vari("go", vari("1"), node(2)), eClassFromNode(a)) -); +// // TODO: make matches with the same var require the same value + +export const node = (value, ...children) => ({ + value, + children, +}); +export const vari = (v, ...children) => ({ + var: v, + children, +}); + +const oneFromEach = function* (arrays) { + if (arrays.length === 0) { + yield []; + return; + } + for (const item of arrays[0]) { + for (const array of oneFromEach(arrays.slice(1))) { + yield [item, ...array]; + } + } +}; +const objFromObjs = (objs) => objs.reduce((prev, cur) => ({ ...prev, ...cur })); + +const readRule = ({ from, to }) => + [...sets] + .map((c) => ({ + c, + matches: from.map((pattern) => eClassMatches(pattern, c)), + })) + .map(({ c, matches }) => ({ c, to, matchCombos: oneFromEach(matches) })); +const enactRule = (r) => + r.map(({ c, to, matchCombos }) => [ + ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), + ]); +// const runRule = ({ from, to }) => +// [...sets] +// .map((c) => ({ +// c, +// matches: from.map((pattern) => eClassMatches(pattern, c)), +// })) +// .map(({ c, matches }) => ({ c, matchCombos: oneFromEach(matches) })) +// .map(({ c, matchCombos }) => [ +// ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), +// ]); +export const runRules = (rules) => { + rules.map(readRule).map(enactRule); + rebuild(); +}; -// Aside: the implicit lifting language could maybe really help simplify this. +export const checkEq = (...patterns) => + [...sets] + .map((c) => patterns.map((pattern) => eClassMatches(pattern, c))) + .some((matches) => matches.every((match) => match.length > 0)); diff --git a/src/demo/2024_08/e_graph2.js b/src/demo/2024_08/e_graph2.js deleted file mode 100644 index bdb4e1c..0000000 --- a/src/demo/2024_08/e_graph2.js +++ /dev/null @@ -1,182 +0,0 @@ -import { map, first, skip, withIndex } from "../../lib/structure/Iterable.js"; -import { hash, makeHashcons } from "./hashcons.js"; -import { - find, - items, - union, - makeSet, - sets, - parents, - setFromId, -} from "./union_find.js"; - -const worklist = []; - -export const merge = (id1, id2) => { - if (find(id1) === find(id2)) return find(id1); - const newId = union(id1, id2); - - worklist.push(newId); - - return newId; -}; - -const canonicalize = (eNode) => - makeENode(eNode.value, ...eNode.children.map(find)); -const hashcons = makeHashcons(); -const makeENode = (value, ...children) => ({ - value, - children, -}); -const add = (eNode) => { - eNode = canonicalize(eNode); - if (hashcons.has(eNode)) return hashcons.get(eNode); - - const eClassId = makeSet(eNode); - - for (const child of eNode.children) parents(child).set(eNode, eClassId); - - hashcons.set(eNode, eClassId); - return eClassId; -}; - -export const e = (value, ...children) => add(makeENode(value, ...children)); - -export const unhash = (str) => { - const [value, ...classIds] = str.split(":"); - return makeENode( - value, - ...classIds.map(Number).map((id) => setFromId.get(id)) - ); -}; - -const rebuild = () => { - while (worklist.length > 0) { - const todo = worklist.map(find); - worklist.length = 0; - for (const eClass of todo) repair(eClass); - } -}; -const repair = (eClass) => { - for (let [peNode, peClass] of parents(eClass)) { - peNode = unhash(peNode); - hashcons.remove(peNode); - find(peClass).items.delete(hash(peNode)); - peNode = canonicalize(peNode); - find(peClass).items.add(hash(peNode)); - hashcons.set(peNode, find(peClass)); - } - - const newParents = makeHashcons(); - for (let [peNode, peClass] of parents(eClass)) { - peNode = unhash(peNode); - peNode = canonicalize(peNode); - if (newParents.has(peNode)) merge(peClass, newParents.get(peNode)); - newParents.set(peNode, find(peClass)); - } - find(eClass).parents = newParents; -}; - -const printENode = ({ value, children }) => - value + - (children.length === 0 ? "" : "(" + children.map(printEClassId) + ")"); -const printEClassId = (eClass) => find(eClass).id + ":"; -const printEClass = (eClass) => - printEClassId(eClass) + "{" + [...items(eClass)].join(", ") + "}"; - -export const printEClasses = () => - console.log([...sets].map(printEClass).join("\n")); - -const eClassMatches = (patternNode, eClass) => { - if (eClass === undefined) return []; - return [...items(eClass)] - .map(unhash) - .flatMap((en) => eNodeMatches(patternNode, en)); -}; -const eNodeMatches = (patternNode, eNode) => { - if (patternNode.var === undefined && eNode.value !== patternNode.value) - return []; - else if ( - patternNode.var === undefined && - patternNode.children.length !== eNode.children.length - ) - return []; - else { - const childrenMatches = patternNode.children.map((p, i) => - eClassMatches(p, eNode.children[i]) - ); - return [ - ...objectCombos( - childrenMatches, - patternNode.var ? { [patternNode.var]: find(hashcons.get(eNode)) } : {} - ), - ]; - } -}; - -const objectCombos = function* (childrenMatches, match) { - if (childrenMatches.length === 0) { - yield { ...match }; - return; - } - for (const matches1 of childrenMatches[0]) { - for (const matches2 of objectCombos(childrenMatches.slice(1))) { - yield { ...match, ...matches1, ...matches2 }; - } - } -}; - -// // TODO: make matches with the same var require the same value - -export const node = (value, ...children) => ({ - value, - children, -}); -export const vari = (v, ...children) => ({ - var: v, - children, -}); - -const oneFromEach = function* (arrays) { - if (arrays.length === 0) { - yield []; - return; - } - for (const item of arrays[0]) { - for (const array of oneFromEach(arrays.slice(1))) { - yield [item, ...array]; - } - } -}; -const objFromObjs = (objs) => objs.reduce((prev, cur) => ({ ...prev, ...cur })); - -const readRule = ({ from, to }) => - [...sets] - .map((c) => ({ - c, - matches: from.map((pattern) => eClassMatches(pattern, c)), - })) - .map(({ c, matches }) => ({ c, to, matchCombos: oneFromEach(matches) })); -const enactRule = (r) => - r.map(({ c, to, matchCombos }) => [ - ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), - ]); -// const runRule = ({ from, to }) => -// [...sets] -// .map((c) => ({ -// c, -// matches: from.map((pattern) => eClassMatches(pattern, c)), -// })) -// .map(({ c, matches }) => ({ c, matchCombos: oneFromEach(matches) })) -// .map(({ c, matchCombos }) => [ -// ...map(map(matchCombos, objFromObjs), (o) => to(o, c)), -// ]); -export const runRules = (rules) => { - rules.map(readRule).map(enactRule); - rebuild(); -}; - -export const checkEq = (...patterns) => - [...sets] - .map((c) => patterns.map((pattern) => eClassMatches(pattern, c))) - .some((matches) => matches.every((match) => match.length > 0)); diff --git a/src/demo/2024_08/use_e.js b/src/demo/2024_08/use_e.js index f3f96fa..9149792 100644 --- a/src/demo/2024_08/use_e.js +++ b/src/demo/2024_08/use_e.js @@ -7,96 +7,100 @@ import { e, checkEq, unhash, -} from "./e_graph2.js"; + eClassMatches, +} from "./e_graph.js"; import { setFromId, find } from "./union_find.js"; -// const rules = [ -// { -// from: [node("a")], -// to: ({}, ec) => merge(ec, e("b")), -// }, -// { -// from: [node("b")], -// to: ({}, ec) => merge(ec, e("a")), -// }, -// ]; - -// e("+", e("a")); -// e("+", e("b")); +const processToRule = (v, lookup) => + v.var + ? lookup[v.var] + : e(v.value, ...v.children.map((c) => processToRule(c, lookup))); + +const makeRule = ({ from, to }) => { + const fromfrom = Array.isArray(from) ? from : [from]; + const toto = Array.isArray(to) + ? (lookup) => + merge(processToRule(to[0], lookup), processToRule(to[1], lookup)) + : (lookup, eClass) => merge(eClass, processToRule(to, lookup)); + return { + from: fromfrom, + to: toto, + }; +}; +const nodeEq = (...c) => c; +const addRule = (r) => rules.push(makeRule(r)); -// printEClasses(); +const rules = []; -// runRules(rules); +const definitions = {}; +const define = (key, f) => { + definitions[key] = f; +}; -// printEClasses(); +const isCommutative = (op) => + addRule({ + from: node(op, vari("a"), vari("b")), + to: node(op, vari("b"), vari("a")), + }); +const isInverse = (op1, op2) => { + addRule({ + from: nodeEq(vari("a"), node(op1, vari("b"), vari("c"))), + to: nodeEq(vari("b"), node(op2, vari("a"), vari("c"))), + }); + addRule({ + from: nodeEq(vari("a"), node(op2, vari("b"), vari("c"))), + to: nodeEq(vari("b"), node(op1, vari("a"), vari("c"))), + }); +}; -// console.log( -// "CHECK", -// checkEq(node("a"), node("+", node("-", node("b"), node("c")), node(0))) -// ); - -const rules = [ - { - from: [node("+", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("-", c, b)), - }, - { - from: [node("-", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("+", c, b)), - }, - { - from: [node("+", vari("a"), vari("b"))], - to: ({ a, b }, eClass) => merge(eClass, e("+", b, a)), - }, - { - from: [node("*", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("/", c, b)), - }, - { - from: [node("/", vari("a"), vari("b")), vari("c")], - to: ({ a, b, c }) => merge(a, e("*", c, b)), - }, - { - from: [node("*", vari("a"), vari("b"))], - to: ({ a, b }, eClass) => merge(eClass, e("*", b, a)), - }, - - // { - // from: [node("+", node("+", vari("a"), vari("b")), vari("c"))], - // to: ({ a, b, c }, eClass) => merge(eClass, e("+", a, e("+", b, c))), - // }, - - // { - // from: [vari("a")], - // to: ({ a }) => merge(a, e("+", a, e(0))), - // }, -]; - -e("l"); -e("r"); -merge(e("w"), e("-", e("r"), e("l"))); -merge(e("c"), e("+", e("l"), e("/", e("w"), e(2)))); - -printEClasses(); - -runRules(rules); -runRules(rules); -runRules(rules); -runRules(rules); -runRules(rules); - -printEClasses(); - -const values = new Map(); +const todo = []; +let values = new Map(); const setValue = (key, value) => values.set(find(e(key)), value); -setValue(2, 2); -setValue("w", 4); -setValue("r", 5); - -const todo = [find(e("w")), find(e("r"))]; +const printValue = (key) => + console.log("printValue: " + key + " = " + values.get(find(e(key)))); + +const eq = (...args) => + merge( + ...args.map((arg) => { + if (typeof arg === "number") { + setValue(arg, arg); + todo.push(find(e(arg))); + } + return ["string", "number"].includes(typeof arg) ? e(arg) : arg; + }) + ); +const op = (value, ...children) => + e( + value, + ...children.map((arg) => { + if (typeof arg === "number") { + setValue(arg, arg); + todo.push(find(e(arg))); + } + return ["string", "number"].includes(typeof arg) ? e(arg) : arg; + }) + ); + +const build = () => { + //printEClasses(); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + runRules(rules); + + const newValues = new Map(); + for (const [k, v] of values) { + newValues.set(find(k), v); + } + values = newValues; + //printEClasses(); +}; const evalC = (clas) => { - const parentValues = [...clas.parents] + const parentValues = [...find(clas).parents] .filter(([n, c]) => values.get(find(c)) === undefined) .map(([n, c]) => ({ c, @@ -106,28 +110,46 @@ const evalC = (clas) => { .filter(({ vs }) => !vs.some((v) => v === undefined)); if (parentValues.length > 0) { const { c, op, vs } = parentValues[0]; - values.set( - find(c), - { - "-": vs[0] - vs[1], - "+": vs[0] + vs[1], - "*": vs[0] * vs[1], - "/": vs[0] / vs[1], - }[op] - ); - console.log(op, vs, values.get(find(c))); + values.set(find(c), definitions[op](...vs)); + //console.log(find(c).id, op, vs, values.get(find(c))); todo.push(find(c)); } }; -while (todo.length > 0) { - const next = [...todo]; - todo.length = 0; - for (const nex of next) { - evalC(nex); +const evaluate = () => { + while (todo.length > 0) { + const next = [...todo]; + todo.length = 0; + for (const nex of next) { + evalC(nex); + } } -} +}; + +define("+", (a, b) => a + b); +isCommutative("+"); +define("-", (a, b) => a - b); +isInverse("+", "-"); + +define("*", (a, b) => a * b); +isCommutative("*"); +define("/", (a, b) => a / b); +isInverse("*", "/"); -console.log([...values].map(([k, v]) => k.id + " : " + v).join("\n")); +eq("width", op("-", "right", "left")); +eq("center", op("+", "left", op("/", "width", 2))); +eq("width", 4); +eq("left", 10); + +build(); +evaluate(); + +printValue("left"); +printValue("width"); +printValue("center"); +printValue("right"); + +// printEClasses(); +// console.log([...values].map(([k, v]) => k.id + " : " + v).join("\n")); // console.log("CHECK", checkEq(node("r"), node("+", node("w"), node("l"))));