diff --git a/lib/util/fixpoint.js b/lib/util/fixpoint.js new file mode 100644 index 0000000..c149fce --- /dev/null +++ b/lib/util/fixpoint.js @@ -0,0 +1,87 @@ +// Based on: +// https://github.com/plug-obp/obp3-algos/blob/1f15cf8c829c7ecd08adb7180aa3c3aefa4f9466/src/main/java/obp3/fixer/Fixer.java +// which is in turn based on: +// 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(); + + const mWorkset = []; // Note: we use a Stack (LIFO), but FIFO queue or any other collection would also work. + + let inactive = true; + + const ensureTransient = node => { + if (mTransient.has(node)) return; + mTransient.set(node, lattice.bottom()); + mParents.set(node, []); + mWorkset.push(node); + } + + const solve = current => { + if (mFixed.get(current) !== undefined) return; + const currentChildren = []; + const alive = [true]; + const requestFunction = node => { + if (!alive[0]) throw new Error("must be alive!"); + const property = mFixed.get(node); + if (property !== undefined) return property; + ensureTransient(node); + currentChildren.push(node); + return mTransient.get(node); + } + const newProperty = eqsFunction(current)(requestFunction); + alive[0] = false; + + const isMaximal = lattice.isMaximal(newProperty); + if (!isMaximal) { + for (const child of currentChildren) { + const parents = mParents.get(child); + if (parents === undefined) { + mParents.set(child, [current]); + } else { + parents.push(current); + } + } + + if (!lattice.equality(mTransient.get(current), newProperty)) { + mTransient.put(current, newProperty); + mWorkset.push(...(mParents.get(current) || [])); + } + } + else { + mFixed.set(current, newProperty); + const oldProperty = mTransient.get(current); + mTransient.delete(current); + if (!lattice.equality(oldProperty, newProperty)) { + mWorkset.push(...(mParents.get(current) || [])); + } + } + } + + const get = node => { + const property = mFixed.get(node); + if (property !== undefined) return property; // return from cache + + if (!inactive) throw new Error("must be inactive!"); + inactive = false; + + ensureTransient(node); + + while (mWorkset.length > 0) { + const current = mWorkset.pop(); // LIFO + solve(current); + } + + for (const [key, value] of mTransient) { + mFixed.set(key, value); + } + mTransient.clear(); + inactive = true; + return mFixed.get(node); + } + + return get; +} diff --git a/tests/fixpoint.js b/tests/fixpoint.js new file mode 100644 index 0000000..8942723 --- /dev/null +++ b/tests/fixpoint.js @@ -0,0 +1,74 @@ +import { fixer } from "../lib/util/fixpoint.js"; + +const booleanLattice = { + isMaximal: (value) => value, + equality: (l, r) => l === r, + bottom: () => false, +}; + +const nullable = grammar => nonterminal => request => { + console.log(nonterminal); + const f = nt => { + return { + Epsilon: () => true, + T: () => false, + NT: () => request(nt.nt), + Seq: () => f(nt.l) && f(nt.r), + Alt: () => f(nt.l) || f(nt.r), + }[nt.kind](); + } + return f(grammar[nonterminal]); +} + +// S → A B C +// A → a A | ε +// B → b B | A C +// C → c C | ε +const exampleGrammar = { + S: {kind: "Seq", + l: {kind: "NT", nt: "A"}, + r: {kind: "Seq", + l: {kind: "NT", nt: "B"}, + r: {kind: "NT", nt: "C"}, + }, + }, + + A: {kind: "Alt", + l: {kind: "Seq", + l: {kind: "T", token: "a"}, + r: {kind: "NT", nt: "A"} + }, + r: {kind: "Epsilon"}, + }, + + B: {kind: "Alt", + l: {kind: "Seq", + l: {kind: "T", token: "b"}, + r: {kind: "NT", nt: "B"} + }, + r: {kind: "Seq", + l: {kind: "NT", nt: "A"}, + r: {kind: "NT", nt: "C"} + }, + }, + + C: {kind: "Alt", + l: {kind: "Seq", + l: {kind: "T", token: "c"}, + r: {kind: "NT", nt: "C"} + }, + r: {kind: "Epsilon"}, + }, +}; +// A is nullable because it has a production A → ε. +// C is nullable because it has a production C → ε. +// 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); + +console.log(isNullable("S")); +console.log(isNullable("A")); +console.log(isNullable("B")); +console.log(isNullable("C")); \ No newline at end of file