Last active
July 13, 2021 20:34
-
-
Save caasi/63720bfb95f4105f8864e73e2f0e9031 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
type Option<T> = T | undefined; | |
type Parser<T> = (input: string) => [Option<T>, string]; | |
const alt | |
: <T>(pa: Parser<T>, pb: Parser<T>) => Parser<T> | |
= (pa, pb) => (input) => { | |
const result = pa(input); | |
if (result[0] !== undefined) return result; | |
return pb(input); | |
}; | |
const takeWhile | |
: (f: (x: string) => boolean) => Parser<string> | |
= (f) => (input) => { | |
let i = 0; | |
while(i < input.length) { | |
if (!f(input[i])) break; | |
++i; | |
} | |
if (i === 0) return [, input]; | |
return [input.slice(0, i), input.slice(i)]; | |
}; | |
const range | |
: (begin: string, end: string) => Parser<string> | |
= (begin, end) => (input) => { | |
const b = begin.charCodeAt(0); | |
const e = end.charCodeAt(0); | |
if (isNaN(b) || isNaN(e)) return [, input]; | |
let i = 0; | |
while(i < input.length) { | |
const code = input.charCodeAt(i); | |
if (code < b || e < code) break; | |
++i; | |
} | |
if (i === 0) return [, input]; | |
return [input.slice(0, i), input.slice(i)]; | |
};`` | |
let result: Option<any>; | |
let rest: string = ''; | |
const between | |
: (a: string, b: string) => (c: string) => boolean | |
= (a, b) => (c) => { | |
const code = c.charCodeAt(0); | |
return a.charCodeAt(0) <= code && code <= b.charCodeAt(0); | |
}; | |
const isUpper = between("A", "Z"); | |
const isLower = between("a", "z"); | |
const word = takeWhile((c) => | |
isUpper(c) || isLower(c) | |
); | |
[result, rest] = word("foobar2000"); | |
console.log(result, rest); // ["foobar"], "2000" | |
const digits = range("0", "9"); | |
[result, rest] = digits("1984DEC10"); | |
console.log(result, rest); // ["1984"], "DEC10" | |
const str | |
: (s: string) => Parser<string> | |
= (s) => (input) => { | |
if (input.startsWith(s)) return [s, input.slice(s.length)]; | |
return [, input]; | |
}; | |
const foo = str("foo"); | |
[result, rest] = foo("foobar"); | |
console.log(result, rest); // ["foo"], "bar" | |
const seq | |
: <T, U>(pa: Parser<T>, pb: Parser<U>) => Parser<[T, U]> | |
= (pa, pb) => (input) => { | |
const [a, rest0] = pa(input); | |
if (a === undefined) return [, input]; | |
const [b, rest1] = pb(rest0); | |
if (b === undefined) return [, input]; | |
return [[a, b], rest1]; | |
}; | |
const pure | |
: <T>(x: T) => Parser<T> | |
= (x) => (input) => [x, input]; | |
const bind | |
: <T, U>(pa: Parser<T>, f: (x: T) => Parser<U>) => Parser<U> | |
= (pa, f) => (input) => { | |
const [a, rest] = pa(input); | |
if (a === undefined) return [, input]; | |
const pb = f(a); | |
return pb(rest); | |
}; | |
const seq3 | |
: <T, U, V>(pa: Parser<T>, pb: Parser<U>, pc: Parser<V>) => Parser<[T, U, V]> | |
= (pa, pb, pc) => | |
bind(pa, (a) => | |
bind(pb, (b) => | |
bind(pc, (c) => pure([a, b, c])))); | |
const seq5 | |
: <T, U, V, W, X>(pa: Parser<T>, pb: Parser<U>, pc: Parser<V>, pd: Parser<W>, pe: Parser<X>) => Parser<[T, U, V, W, X]> | |
= (pa, pb, pc, pd, pe) => | |
bind(seq3(pa, pb, pc), ([a, b, c]) => | |
bind(seq(pd, pe), ([d, e]) => pure([a, b, c, d, e]))); | |
const map | |
: <T, U>(pa: Parser<T>, f: (x: T) => U) => Parser<U> | |
= (pa, f) => (input) => { | |
const [a, rest] = pa(input); | |
if (a === undefined) return [, input]; | |
return [f(a), rest]; | |
}; | |
// 您可以用 bind 實現另外一個版本的 map 嗎? | |
const fullname = seq3(word, str(" "), word); | |
[result, rest] = fullname("Isaac Huang"); | |
console.log(result, rest); // [["Isaac", " ", "Huang"]], "" | |
const num | |
: Parser<number> | |
= map(digits, (str) => parseInt(str, 10)); | |
const price1 = map(seq(str("price: "), num), ([_, value]) => value); | |
[result, rest] = price1("price: 1790"); | |
console.log(result, rest); // [1790], "" | |
const zeroOrOne | |
: <T>(pa: Parser<T>) => Parser<[] | [T]> | |
= (pa) => (input) => { | |
const [a, rest] = pa(input); | |
if (a === undefined) return [[], input]; | |
return [[a], rest]; | |
}; | |
const nothing | |
: Parser<null> | |
= map(str("null"), () => null); | |
const numberOrNothing = alt(num, nothing); | |
const price2 = map(seq(str("price: "), numberOrNothing), ([_, value]) => value); | |
[result, rest] = price2("price: 1790"); | |
console.log(result, rest); // [1790], "" | |
[result, rest] = price2("price: null"); | |
console.log(result, rest); // [null], "" | |
const many | |
: <T>(pa: Parser<T>) => Parser<T[]> | |
= (pa) => bind(zeroOrOne(pa), (xs) => | |
xs.length === 0 | |
? pure(xs) | |
: bind(many(pa), (ys) => pure([...xs, ...ys]))); | |
const skip | |
: <T, U>(pa: Parser<T>, pb: Parser<U>) => Parser<T> | |
= (pa, pb) => bind(pa, (a) => bind(pb, (_) => pure(a))); | |
const skipFirst | |
: <T, U>(pa: Parser<T>, pb: Parser<U>) => Parser<U> | |
= (pa, pb) => bind(pa, (_) => bind(pb, (b) => pure(b))); | |
const sepBy | |
: <T, U>(pa: Parser<T>, pb: Parser<U>) => Parser<T[]> | |
= (pa, pb) => bind(zeroOrOne(pa), (xs) => | |
xs.length === 0 | |
? pure(xs) | |
: bind(many(skipFirst(pb, pa)), (ys) => pure([...xs, ...ys]))); | |
const concat = (xs: string[]) => xs.join(""); | |
const spaces = map(many(str(" ")), concat); | |
const productName = map(sepBy(word, spaces), (xs) => xs.join(" ")); | |
[result, rest] = productName("Valheim, 318"); | |
console.log(result, rest); // ["Valheim"], ", 318" | |
[result, rest] = productName("Death Stranding, 1790"); | |
console.log(result, rest); // ["Death Stranding"], ", 1790" | |
const developerName = productName; | |
const comma = skip(str(","), spaces); | |
console.log(comma(", aseuth")); | |
const newline = str("\n"); | |
const price = num; | |
const product = map( | |
seq5(productName, comma, price, comma, developerName), | |
([product, _, price, __, developer]) => ({ product, price, developer }), | |
); | |
const productList = sepBy(product, newline); | |
const csv = `Death Stranding, 1790, Kojima Productions | |
Grand Theft Auto V, 1299, Rockstart North | |
Valheim, 318, Iron Gate AB`; | |
[result, rest] = productList(csv); | |
console.table(result); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment