From 47786ae792c209e3e1f0b0fa31b6b0e61d072820 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Thu, 22 May 2025 14:31:26 +0200 Subject: [PATCH] fixpoint algorithm: parameterize the Map to use --- lib/util/fixpoint.js | 13 ++++++------ lib/util/rbtree_wrapper.js | 43 ++++++++++++++++++++++++++++++++++++++ tests/fixpoint.js | 12 +++++++---- 3 files changed, 58 insertions(+), 10 deletions(-) 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"));