Created
December 31, 2022 13:59
-
-
Save funrep/ae2e5ec0786d8182bf3b942e436bb621 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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') |
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
Nått sånt kanske funkar: