diff --git a/lib/util/fixpoint.js b/lib/util/fixpoint.js
index c149fce..7014035 100644
--- a/lib/util/fixpoint.js
+++ b/lib/util/fixpoint.js
@@ -4,10 +4,12 @@
// FRANCOIS POTTIER, Lazy Least Fixed Points in ML
// OCAML Code
-export const fixer = (eqsFunction, lattice) => {
- const mFixed = new Map();
- const mTransient = new Map();
- const mParents = new Map();
+// eqsFunction: V -> (V -> P) -> P
+// elemCmp: (V -> V -> Ordering)
+export const fixer = (eqsFunction, dictType, lattice) => {
+ const mFixed = dictType();
+ const mTransient = dictType();
+ const mParents = dictType();
const mWorkset = []; // Note: we use a Stack (LIFO), but FIFO queue or any other collection would also work.
@@ -16,7 +18,6 @@ export const fixer = (eqsFunction, lattice) => {
const ensureTransient = node => {
if (mTransient.has(node)) return;
mTransient.set(node, lattice.bottom());
- mParents.set(node, []);
mWorkset.push(node);
}
@@ -47,7 +48,7 @@ export const fixer = (eqsFunction, lattice) => {
}
if (!lattice.equality(mTransient.get(current), newProperty)) {
- mTransient.put(current, newProperty);
+ mTransient.set(current, newProperty);
mWorkset.push(...(mParents.get(current) || []));
}
}
diff --git a/lib/util/rbtree_wrapper.js b/lib/util/rbtree_wrapper.js
index dbc3fce..b9c1682 100644
--- a/lib/util/rbtree_wrapper.js
+++ b/lib/util/rbtree_wrapper.js
@@ -29,3 +29,46 @@ export class RBTreeWrapper {
return this.tree.keys.map(key => [key, this.tree.get(key)]);
}
}
+
+// Can be used as drop-in replacement for Map
+// Why create a mutable adapter for a purely-functional data structure?
+// Because the builtin Map does not allow custom comparison functions and this one does.
+export class MutableRBTree {
+ constructor(tree, printf = defaultPrintf) {
+ this.tree = tree;
+ this[inspect.custom] = printf;
+ }
+
+ static new(cmp) {
+ return new MutableRBTree(createRBTree(cmp));
+ }
+
+ set(key, value) {
+ this.tree = this.tree.remove(key).insert(key, value);
+ }
+
+ delete(key) {
+ this.tree = this.tree.remove(key);
+ }
+
+ get(key) {
+ return this.tree.get(key);
+ }
+
+ has(key) {
+ return this.tree.get(key) !== undefined;
+ }
+
+ clear() {
+ this.tree = createRBTree(this.tree._compare);
+ }
+
+ *[Symbol.iterator]() {
+ const iter = this.tree.begin;
+ while (iter !== undefined && iter.valid) {
+ yield [iter.key, iter.value];
+ iter.next();
+ }
+ }
+}
+
diff --git a/tests/fixpoint.js b/tests/fixpoint.js
index 8942723..70bd6fc 100644
--- a/tests/fixpoint.js
+++ b/tests/fixpoint.js
@@ -1,4 +1,6 @@
+import { compareStrings } from "../lib/compare/primitives.js";
import { fixer } from "../lib/util/fixpoint.js";
+import { MutableRBTree } from "../lib/util/rbtree_wrapper.js";
const booleanLattice = {
isMaximal: (value) => value,
@@ -7,7 +9,7 @@ const booleanLattice = {
};
const nullable = grammar => nonterminal => request => {
- console.log(nonterminal);
+ console.log(nonterminal);
const f = nt => {
return {
Epsilon: () => true,
@@ -15,7 +17,7 @@ const nullable = grammar => nonterminal => request => {
NT: () => request(nt.nt),
Seq: () => f(nt.l) && f(nt.r),
Alt: () => f(nt.l) || f(nt.r),
- }[nt.kind]();
+ }[nt.kind]();
}
return f(grammar[nonterminal]);
}
@@ -65,8 +67,10 @@ const exampleGrammar = {
// B is nullable because B → A C, and both A and C are nullable.
// S is nullable because S → A B C, and A, B, and C are all nullable.
-
-const isNullable = fixer(nullable(exampleGrammar), booleanLattice);
+const isNullable = fixer(
+ nullable(exampleGrammar), // eqs
+ () => MutableRBTree.new((a,b) => compareStrings(a)(b)), // dict to use
+ booleanLattice);
console.log(isNullable("S"));
console.log(isNullable("A"));