Skip to content

Instantly share code, notes, and snippets.

@funrep
Created December 31, 2022 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save funrep/ae2e5ec0786d8182bf3b942e436bb621 to your computer and use it in GitHub Desktop.
Save funrep/ae2e5ec0786d8182bf3b942e436bb621 to your computer and use it in GitHub Desktop.
// mål att parsa basic datalog:
// fact(A, B).
// rule(x, y) :- fact(A, y).
// en feedback på readme:en är att flytta installation lite längre upp för
// att det känns som ganska standard istället för längst ner i de flesta paket.
import { Helpers } from '@honungsburk/kombo';
// import { Done, Loop, spaces, Trailing, Unit } from '@honungsburk/kombo/dist/cjs/Advanced';
// import { loop, oneOf, oneOfMany, Parser, run, sequence, Step, succeed, symbol, variable } from '@honungsburk/kombo/dist/cjs/Simple';
import { spaces, Trailing } from '@honungsburk/kombo/Advanced';
import { oneOf, Parser, run, sequence, succeed, variable } from '@honungsburk/kombo/Simple';
import { assert } from 'console';
// det är nog snarare problem med min typescript setup än kombo, men automkompletar jag importsen så får
// jag med `/dist/cjls` som ses ovan
type Datalog = Def[];
type Symbol = string;
interface Def {
type: string;
name: Symbol;
head: Value[];
body?: Expr[];
}
interface Expr {
type: string;
name: Symbol;
params: Value[];
}
interface Value {
type: string;
value: Symbol;
}
class Literal implements Value {
type = 'Literal';
value: Symbol;
constructor(value: Symbol) {
this.value = value;
}
}
class Variable implements Value {
type = 'Variable';
value: Symbol;
constructor(value: Symbol) {
this.value = value;
}
}
class RuleDef implements Def {
type = 'RuleDef';
name: Symbol;
head: Variable[];
body: Expr[];
constructor(name: Symbol, head: Variable[], body: Expr[]) {
this.name = name;
this.head = head;
this.body = body;
}
}
class FactDef implements Def {
type = 'RuleDef';
name: Symbol;
head: Literal[];
constructor(name: Symbol, head: Literal[]) {
this.name = name;
this.head = head;
}
}
const literal: Parser<Literal> =
variable({ start: Helpers.isUpper, inner: Helpers.isUpper, reserved: new Set()})
.map((s: string) => { return new Literal(s) });
// jag började med lösningen nedan men eftersom symbol/token är Parser<false>
// så funkar det inte, hade vart najs med en `string: Parser<string>` primitive
// const upperCharacters =
// Array.from({length:26},(_,k)=>k+1)
// .map((v) => String.fromCharCode(64 + v));
// const literal: Parser<Literal> =
// oneOfMany(...upperCharacters.map((char) => symbol(char)))
// .map((char) => { return { type: 'Literal', value: char} });
// insåg jag nog kunde gjort en string: Parser<string> = (s: string) => upperCharacters.includes(s) ? succeed(s) : problem('not an upper character')
// hade vart najs med ett sånt exempel i README och dokumentation tror jag
const variableP: Parser<Variable> =
variable({ start: Helpers.isLower, inner: Helpers.isLower, reserved: new Set()})
.map((s: string) => { return new Variable(s) });
const symbolP: Parser<Symbol> =
variable({ start: Helpers.isLower, inner: Helpers.isLower, reserved: new Set()})
.map((s: string) => { return s });
function atomParser<T>(arg: Parser<T>): Parser<{ name: Symbol, args: T[]}> {
return symbolP
.andThen((name: any) =>
sequence({
start: '(',
seperator: ',',
end: ')',
spaces: spaces,
item: arg,
trailing: Trailing.Forbidden
}).map((args: { toArray: () => any; }) => { return { name, args: args.toArray() }}));
}
const factDef: Parser<FactDef> =
succeed((atom: { name: string; args: Literal[]; }) => { return new FactDef(atom.name, atom.args) })
.apply(atomParser<Literal>(literal));
const expr: Parser<Expr> =
atomParser<Value>(oneOf(literal, variableP))
.map((atom: { name: any; args: any; }) => { return { type: 'Expr', name: atom.name, params: atom.args } });
const ruleDef: Parser<RuleDef> =
atomParser<Variable>(variableP)
.andThen((atomHead: { name: string; args: Variable[]; }) =>
succeed((body: { toArray: () => Expr[]; }) => new RuleDef(atomHead.name, atomHead.args, body.toArray()))
.skip(spaces)
.apply(sequence({
start: ':-',
seperator: ',',
end: '',
spaces: spaces,
item: expr,
trailing: Trailing.Forbidden
}))
);
// hade lite problem med type-checking och loop, så spara den som referens.
//
// const defHelp = (defs: Def[]): Parser<Step<Def[], Def[]>> => {
// return oneOf(
// succeed((def: Def) => {
// defs.push(def);
// return new (Loop as any)(defs); // inte hundra varför jag måste köra `as any` här
// })
// .apply(oneOfMany(factDef, ruleDef))
// .skip(spaces),
// succeed(Unit).map(() => new (Done as any)(defs)) // samma här
// );
// };
// const program: Parser<Datalog> = loop([])(defHelp as any); // och här
const program: Parser<Datalog> =
succeed((program: { toArray: () => any; }) => program.toArray())
.apply(sequence({
start: '',
seperator: '.',
end: '',
spaces: spaces,
item: oneOf(factDef, ruleDef),
trailing: Trailing.Optional
}));
function parseDatalog(input: string): Datalog | null {
const result = run(program)(input);
if (result.kind === 'Err') {
console.log(result.value);
return null;
}
return result.value;
}
// tests
function equal(a: any, b: any): boolean {
const res = JSON.stringify(a) === JSON.stringify(b);
if (!res) {
console.log(a);
console.log(b);
}
return res;
}
const p0 = 'fact(A, b)';
const r0 = { type: 'Expr', name: 'fact', params: [new Literal('A'), new Variable('b')]};
assert(equal(run(expr)(p0).value, r0), 'failed to parse expr');
const p1 = 'fact(A, B)';
const r1 = new FactDef('fact', [new Literal('A'), new Literal('B')]);
assert(equal(run(factDef)(p1).value, r1), 'failed to parse fact')
const p2 = 'rule(a) :- fact(a, B)';
const r2 = new RuleDef('fact', [new Variable('a')], [{ type: 'Expr', name: 'fact', params: [new Variable('a'), new Literal('B')]}]);
// verkar inte kunna parsa `:-` , tar sequence bara in chars för start, seperator, och end?
assert(equal(run(ruleDef)(p2).value, r2), 'failed to parse rule')
const p3 = `
fact(A, B).
fact(B, C).
rule(a) :- fact(a, B).
rule(c) :- rule(A), fact(B, c).
`
const r3 = [
r1,
new FactDef('fact', [new Literal('B'), new Literal('C')]),
r2,
new RuleDef('rule', [new Variable('c')], [
{ type: 'Expr', name: 'rule', params: [new Literal('A')]},
{ type: 'Expr', name: 'fact', params: [new Literal('B'), new Variable('c')]}
])
]
// parsar bara en regel, kanske måste ha en riktigt start och end för att använda sequence
assert(equal(parseDatalog(p3), r3), 'failed to parse program')
@honungsburk
Copy link

honungsburk commented Dec 31, 2022

Nice! Skapade ett issue

@honungsburk
Copy link

Jag ser att end är empty string. Har inte verifierat men känns fel.

const ruleDef: Parser<RuleDef> =
    atomParser<Variable>(variableP)
    	.andThen((atomHead: { name: string; args: Variable[]; }) =>
            succeed((body: { toArray: () => Expr[]; }) => new RuleDef(atomHead.name, atomHead.args, body.toArray()))
            .skip(spaces)
            .apply(sequence({
                    start: ':-',
                    seperator: ',',
                    end: '',
                    spaces: spaces,
                    item: expr,
                    trailing: Trailing.Forbidden
    		    }))
    	);

@honungsburk
Copy link

jag började med lösningen nedan men eftersom symbol/token är Parser
så funkar det inte, hade vart najs med en string: Parser<string> primitive

Nått sånt kanske funkar:

const string = (string: string): P.Parser<string> => {
  return P.succeed(string)
    .skip(P.symbol(string))
};

@honungsburk
Copy link

Har du ett fullt repo du skulle kunna ladda upp? Det hade varit super för å se varför type checking inte funkar some den ska.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment