meta-log-db
Version:
Native database package for Meta-Log (ProLog, DataLog, R5RS)
207 lines (175 loc) • 5.36 kB
text/typescript
import { Fact, DatalogRule, DatalogProgram } from '../types/index.js';
/**
* Fixed-point computation for DataLog
*/
export class FixedPoint {
/**
* Compute fixed point of a DataLog program
*/
static compute(program: DatalogProgram): Fact[] {
let facts = new Set<string>(this.factsToStrings(program.facts));
let previousSize = 0;
let iterations = 0;
const maxIterations = 1000;
while (facts.size !== previousSize && iterations < maxIterations) {
previousSize = facts.size;
// Apply all rules
for (const rule of program.rules) {
const newFacts = this.applyRule(rule, Array.from(facts).map(s => this.stringToFact(s)));
for (const fact of newFacts) {
facts.add(this.factToString(fact));
}
}
iterations++;
}
return Array.from(facts).map(s => this.stringToFact(s));
}
/**
* Apply a rule to generate new facts
*/
private static applyRule(rule: DatalogRule, facts: Fact[]): Fact[] {
const newFacts: Fact[] = [];
// Match body predicates against facts
const bodyMatches = this.matchBody(rule.body, facts);
// Generate head facts for each match
for (const match of bodyMatches) {
const headFact = this.instantiateHead(rule.head, match);
if (headFact) {
newFacts.push(headFact);
}
}
return newFacts;
}
/**
* Match body predicates against facts
*/
private static matchBody(body: string[], facts: Fact[]): Map<string, any>[] {
if (body.length === 0) {
return [new Map()];
}
const [firstPred, ...restPreds] = body;
const firstMatches = this.matchPredicate(firstPred, facts);
const allMatches: Map<string, any>[] = [];
for (const match of firstMatches) {
const restMatches = this.matchBody(restPreds, facts);
for (const restMatch of restMatches) {
const merged = this.mergeMatches(match, restMatch);
if (merged) {
allMatches.push(merged);
}
}
}
return allMatches;
}
/**
* Match a predicate against facts
*/
private static matchPredicate(predicate: string, facts: Fact[]): Map<string, any>[] {
const matches: Map<string, any>[] = [];
const parsed = this.parsePredicate(predicate);
for (const fact of facts) {
if (fact.predicate === parsed.predicate) {
const match = this.matchArgs(parsed.args, fact.args);
if (match) {
matches.push(match);
}
}
}
return matches;
}
/**
* Match arguments (with variable binding)
*/
private static matchArgs(patternArgs: string[], factArgs: any[]): Map<string, any> | null {
if (patternArgs.length !== factArgs.length) {
return null;
}
const bindings = new Map<string, any>();
for (let i = 0; i < patternArgs.length; i++) {
const pattern = patternArgs[i];
const fact = factArgs[i];
if (pattern.startsWith('?')) {
// Variable
const existing = bindings.get(pattern);
if (existing !== undefined && existing !== fact) {
return null;
}
bindings.set(pattern, fact);
} else if (pattern !== fact.toString()) {
// Constant doesn't match
return null;
}
}
return bindings;
}
/**
* Instantiate head with bindings
*/
private static instantiateHead(head: string, bindings: Map<string, any>): Fact | null {
const parsed = this.parsePredicate(head);
const args: any[] = [];
for (const arg of parsed.args) {
if (arg.startsWith('?')) {
const value = bindings.get(arg);
if (value === undefined) {
return null;
}
args.push(value);
} else {
args.push(arg);
}
}
return { predicate: parsed.predicate, args };
}
/**
* Parse predicate string
*/
private static parsePredicate(predStr: string): { predicate: string; args: string[] } {
const match = predStr.match(/^(\w+)\((.*)\)$/);
if (match) {
const predicate = match[1];
const argsStr = match[2];
const args = argsStr ? argsStr.split(',').map(s => s.trim()) : [];
return { predicate, args };
}
return { predicate: predStr, args: [] };
}
/**
* Merge two match bindings
*/
private static mergeMatches(match1: Map<string, any>, match2: Map<string, any>): Map<string, any> | null {
const merged = new Map(match1);
for (const [key, value] of match2) {
const existing = merged.get(key);
if (existing !== undefined && existing !== value) {
return null;
}
merged.set(key, value);
}
return merged;
}
/**
* Convert fact to string for set operations
*/
private static factToString(fact: Fact): string {
return `${fact.predicate}(${fact.args.join(',')})`;
}
/**
* Convert facts to strings
*/
private static factsToStrings(facts: Fact[]): string[] {
return facts.map(f => this.factToString(f));
}
/**
* Convert string back to fact
*/
private static stringToFact(str: string): Fact {
const match = str.match(/^(\w+)\((.*)\)$/);
if (match) {
const predicate = match[1];
const args = match[2] ? match[2].split(',').map(s => s.trim()) : [];
return { predicate, args };
}
return { predicate: str, args: [] };
}
}