Skip to content

Commit

Permalink
feat(souffle): Add Soufflé rules
Browse files Browse the repository at this point in the history
  • Loading branch information
byakuren-hijiri committed Apr 28, 2024
1 parent 7482218 commit 82ccbd5
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 6 deletions.
79 changes: 73 additions & 6 deletions src/internals/souffle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,60 @@ export class Relation {
}
}

/**
* Represents an atom of a Soufflé rule: https://souffle-lang.github.io/rules#atom
*/
export type RuleAtom = {
name: string;
arguments: string[];
};

/**
* Head of the rule: https://souffle-lang.github.io/rules#multiple-heads.
*/
export type RuleHead = RuleAtom[];

/**
* Body of a rule which is present as a conjunction of (negated) atoms/constraints/disjunctions:
* https://souffle-lang.github.io/rules#conjunction.
*/
export type RuleBodyEntry = { kind: "atom"; value: RuleAtom; negated: boolean };

/**
* Represents a single Datalog rule in a Souffle program.
*/
export class Rule {
private heads: RuleHead;
private body: RuleBodyEntry[];

/**
* Constructs a Datalog rule with the given heads and body entries.
* See: https://souffle-lang.github.io/rules for more information.
* @param head Heads of the rule.
* @param bodyEntries Entries that represent a body of a rule.
*/
constructor(heads: RuleHead, ...bodyEntries: RuleBodyEntry[]) {
this.heads = heads;
this.body = bodyEntries;
}

/**
* Emits the Datalog rule as a string suitable for inclusion in a Souffle program.
* @returns The formatted Datalog rule.
*/
public emit(): string {
const formatAtom = (atom: RuleAtom) =>
`${atom.name}(${atom.arguments.join(", ")})`;
const formatHead = (heads: RuleHead) =>
heads.map((head) => formatAtom(head)).join(", ");
const formatBodyEntry = (entry: RuleBodyEntry) =>
`${entry.negated ? "!" : ""}${formatAtom(entry.value)}`;
const headsStr = formatHead(this.heads);
const bodyStr = this.body.map(formatBodyEntry).join(", ");
return `${headsStr} :-\n ${bodyStr}.`;
}
}

/**
* Manages multiple Soufflé relations.
*/
Expand All @@ -64,6 +118,11 @@ export class SouffleProgram {
*/
private relations = new Map<RelationName, Relation>();

/**
* Soufflé rules defined in the program.
*/
private rules: Rule[] = [];

/**
* Adds a new relation to the context.
* @param name The unique name of the relation.
Expand Down Expand Up @@ -96,15 +155,23 @@ export class SouffleProgram {
}

/**
* Compiles all relation declarations and their facts into a single Soufflé Datalog program.
* Adds a new rule to the Souffle program.
* @param rule The rule to add to the program.
*/
public addRule(rule: Rule) {
this.rules.push(rule);
}

/**
* Compiles all relation declarations, their facts, and rules into a single Soufflé Datalog program.
* @returns A string containing the formatted Datalog program.
*/
public emit(): string {
return Array.from(this.relations.values())
.reduce((acc, relation) => {
return acc + relation.emit() + "\n";
}, "")
.trim();
const relationsOutput = Array.from(this.relations.values())
.map((relation) => relation.emit())
.join("\n");
const rulesOutput = this.rules.map((rule) => rule.emit()).join("\n");
return `${relationsOutput}\n${rulesOutput}`.trim();
}

/**
Expand Down
32 changes: 32 additions & 0 deletions test/souffle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Relation,
Rule,
SouffleProgram,
SouffleExecutor,
} from "../src/internals/souffle";
Expand Down Expand Up @@ -56,6 +57,21 @@ describe("Souffle Datalog tests", () => {
"utf8",
);
});

it("should compile and emit the rules correctly", () => {
const program = new SouffleProgram();
program.addRelation("TestRelation", ["x", "number"]);
program.addRule(
new Rule([{ name: "out", arguments: ["x"] }], {
kind: "atom",
value: { name: "TestRelation", arguments: ["x"] },
negated: false,
}),
);
const output = program.emit();
expect(output).toContain(".decl TestRelation(x:number)");
expect(output).toContain("out(x) :-\n TestRelation(x).");
});
});

describe("SouffleExecutor class", () => {
Expand All @@ -66,5 +82,21 @@ describe("Souffle Datalog tests", () => {
const success = await executor.execute(program);
expect(success).toBe(true);
});

it("should handle rules in the execution", async () => {
const program = new SouffleProgram();
program.addRelation("TestRelation", ["x", "number"]);
program.addFact("TestRelation", 42);
program.addRule(
new Rule([{ name: "output", arguments: ["x"] }], {
kind: "atom",
value: { name: "TestRelation", arguments: ["x"] },
negated: false,
}),
);
const executor = new SouffleExecutor(soufflePath, factDir, outputDir);
const success = await executor.execute(program);
expect(success).toBe(true);
});
});
});

0 comments on commit 82ccbd5

Please sign in to comment.