Last active
April 12, 2018 18:33
-
-
Save ulve/052bc1363ef7e85a66275f5e86b68874 to your computer and use it in GitHub Desktop.
En enkel parser combinator för typescript
This file contains 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
// 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 |
This file contains 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
// 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