Skip to content

Instantly share code, notes, and snippets.

@ulve
Last active April 12, 2018 18:33
Show Gist options
  • Save ulve/052bc1363ef7e85a66275f5e86b68874 to your computer and use it in GitHub Desktop.
Save ulve/052bc1363ef7e85a66275f5e86b68874 to your computer and use it in GitHub Desktop.
En enkel parser combinator för typescript
// Okej nu behöver vi lite typer
interface Success<T> {
kind: "success";
val: [T[], string];
}
interface Failure {
kind: "failure";
message: string;
}
type Result<T> = Success<T> | Failure;
interface Parser<T> {
runWith(s: string): Result<T>;
andThen(p2: Parser<T>): Parser<T>;
orElse(p2: Parser<T>): Parser<T>;
map<U>(f: (s: T[]) => U): Parser<U>;
}
class Parpar<T> implements Parser<T> {
private fn = null;
constructor(c: string | ((s: string) => Result<T>)) {
if (typeof c === "function") {
this.fn = c;
} else {
this.fn = (str: string): Result<string> => {
if (str === "" || str === undefined || str === null) {
return { kind: "failure", message: "Ingen data" };
} else if (str[0] === c) {
return { kind: "success", val: [[c], str.substring(1)] };
} else {
return {
kind: "failure",
message: `Försökte hitta ett ${c} men fick istälet ett ${str[0]}`
};
}
};
}
}
runWith(s: string): Result<T> {
return this.fn(s);
}
andThen(p2: Parser<T>): Parser<T> {
let fn: (s: string) => Result<T> = (str: string): Result<T> => {
const result1: Result<T> = this.runWith(str);
switch (result1.kind) {
case "success":
const result2 = p2.runWith(result1.val[1]);
switch (result2.kind) {
case "success":
return {
kind: "success",
val: [[...result1.val[0], ...result2.val[0]], result2.val[1]]
};
case "failure":
return { kind: "failure", message: result2.message };
}
break;
case "failure":
return { kind: "failure", message: result1.message };
}
};
return new Parpar(fn);
}
orElse(p2: Parser<T>): Parser<T> {
let fn: (s: string) => Result<T> = (str: string): Result<T> => {
const result1: Result<T> = this.runWith(str);
switch (result1.kind) {
case "success":
return result1;
case "failure":
return p2.runWith(str);
}
};
return new Parpar(fn);
}
map<U>(f: (s: T[]) => U): Parser<U> {
let fn: (s: string) => Result<U> = s => {
const result1 = this.runWith(s);
switch (result1.kind) {
case "success":
let v = f(result1.val[0]);
return {
kind: "success",
val: [[v], result1.val[1]]
};
case "failure":
return {
kind: "failure",
message: result1.message
};
};
};
return new Parpar<U>(fn)
}
}
const A = new Parpar("A");
const B = new Parpar("B");
const aOrB = A.orElse(B);
const r = aOrB.runWith("BC");
const aAndB = A.andThen(B);
const r2 = aAndB.runWith("ABC");
const aAndThenBAndThenAOrB = A.andThen(B).andThen(aOrB);
const r3 = aAndThenBAndThenAOrB.map(s => s.join(",")).runWith("ABBA");
const one = new Parpar<string>("1");
const two = new Parpar<string>("2");
const three = new Parpar<string>("3");
const oneToThree = one.orElse(two).orElse(three)
const r5 = oneToThree.andThen(oneToThree).andThen(oneToThree)
.map(s => s.map(s2 => parseInt(s2)).reduce((a, j) => a+j))
.runWith("323")
r5
const r4 = one.andThen(one).andThen(one).map((s:string[]) => s.join(","))
const oneI = new Parpar<string>("1").map(s => parseInt(s[0]));
const twoI = new Parpar<string>("2").map(s => parseInt(s[0]));
const threeI = new Parpar<string>("3").map(s => parseInt(s[0]));
const ott = oneI.orElse(twoI).orElse(threeI);
const fofo = ott.andThen(ott).andThen(ott).map(s => s.reduce((a, j) => a+j)).runWith("123")
fofo
// En enkel parser
// tar in en sträng och returnerar en boolean som berättar om det gick bra.
// Returnerar även det som är kvar av strängen sen vår parser ätit upp den
const A_parser = (str: string) : [boolean, string] => {
if(str === "" || str === undefined || str === null) {
return [false, ""]
} else if(str[0] === 'A') {
return [true, str.substring(1)]
} else {
return [false, str]
}
}
const a = A_parser("ABC")
a
const b = A_parser("BCD")
b
const c =A_parser("")
c
// En lite svårare parser
// den tar ett variabelt tecken som man kan leta efter och den
// genererar även felmmedelanden
const p_parser = (c: string, str: string) : [string, string] => {
if(str === "" || str === undefined || str === null) {
return ["Fanns ingen data", ""]
} else if(str[0] === c) {
return [`hittade ${c}`, str.substring(1)]
} else {
return [`Försökte hitta ett ${c} men fick istälet ett ${str[0]}`, str]
}
}
const a2 = p_parser("A", "ABC")
a2
const b2 = p_parser("A", "BCD")
b2
const c2 = p_parser("A", undefined)
c2
// Okej nu behöver vi lite typer
interface Success {
kind: "success";
val: [string[], string];
}
interface Failure {
kind: "failure";
message: string
}
type Result = Success | Failure
const pchar = (c: string, str: string) : Result => {
if(str === "" || str === undefined || str === null) {
return { kind: "failure", message: "Ingen data" }
} else if(str[0] === c) {
return { kind: "success", val: [[c], str.substring(1)] }
} else {
return { kind: "failure", message: `Försökte hitta ett ${c} men fick istälet ett ${str[0]}` }
}
}
const a3 = pchar("A", "ACD")
a3
const b3 = pchar("A", "KATT")
b3
const c3 = pchar("A", null)
c3
// Okej nu riktigt avancerat. Vi returnerar en funktion som vi kan köra
// istället för ett resultat!
const cchar = (c: string) => (str: string) : Result => {
if(str === "" || str === undefined || str === null) {
return { kind: "failure", message: "Ingen data" }
} else if(str[0] === c) {
return { kind: "success", val: [[c], str.substring(1)] }
} else {
return { kind: "failure", message: `Försökte hitta ett ${c} men fick istälet ett ${str[0]}` }
}
}
// Kolla nu har vi ett snyggt sätt att göra egna sådana här parsers.
// vi har en funktion som retunerar parsers på bokstäver man skickar in
const matchA = cchar("A")
const a4 = matchA("ABC")
// Okej nu blir det lite mer trolleri
// Vi gör något med parsertypen!
interface Parser {
fn: (s: string) => Result;
}
const mchar = (c: string): Parser => {
return {fn: (str: string) : Result => {
if(str === "" || str === undefined || str === null) {
return { kind: "failure", message: "Ingen data" }
} else if(str[0] === c) {
return { kind: "success", val: [[c], str.substring(1)] }
} else {
return { kind: "failure", message: `Försökte hitta ett ${c} men fick istälet ett ${str[0]}` }
}
}
}
}
const matchB : Parser = mchar("B")
matchB
//const a5 = matchB("BCD")
//a5
// Jaha det här var ju en funktion. Men det är ju vad vi vill i hemlighet
// Vi gör en run-funktion som kan köra dom här funktionerna
const runParser = (parser: Parser, str: string) : Result => {
return parser.fn(str)
}
const a6 = runParser(matchB, "BCD")
a6
// Det var en parser. Den kan hitta bokstäver. Det är inte så superhäftigt ändå
// vad är dom här combinatorerna som folk pratar så mycket om
// en kombinator är något som kan lägga ihop saker. tar två inparametrar av en typ
// och returnerar en av samma typ
// 1 + 1 = 2
// [1, 2] :: [3, 4]
// osv
// vi skulle haft något sådant för parsers!
const pA = mchar("A")
const pB = mchar("B")
// Här gör vi en som först kör en parser om den lyckas kör den nästa parser
// om båda lyckas får vi tillbaks resultatet
// om någon misslyckas så får vi en failure
const andThen = (p1: Parser, p2: Parser): Parser => {
return {
fn: (str: string): Result => {
const result1: Result = runParser(p1, str)
switch(result1.kind) {
case "success":
const result2 = runParser(p2, result1.val[1])
switch(result2.kind) {
case "success":
return {kind: "success", val: [[...result1.val[0], ...result2.val[0]], result2.val[1]]}
case "failure":
return {kind: "failure", message: result2.message}
};
break;
case "failure":
return {kind: "failure", message: result1.message}
}
}
}
}
const aThenB = andThen(pA, pB)
const a7 = runParser(aThenB, "ABC")
a7
// Gu så bra. Om vi bara kunde göra en som valde mellan två olika parsers
const orElse = (p1: Parser, p2: Parser) : Parser => {
return {
fn: (str: string): Result => {
const result1: Result = runParser(p1, str)
switch(result1.kind) {
case "success":
return result1
case "failure":
return runParser(p2, str)
}
}
}
}
const aOrB = orElse(pA, pB)
const a8 = runParser(aOrB, "AAA")
a8
// Kolla nu kan man sätta ihop dom till längre kjedjor! Om vi bara hade ett riktigt språk
// med infixopperatorer så man kunde skriva pA .>>. pB .>>. (pA <|> pB) istället
const aThenBThenAOrB = andThen(andThen(pA, pB), aOrB)
const a9 = runParser(aThenBThenAOrB, "ABC")
a9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment