Skip to content

Instantly share code, notes, and snippets.

@caasi
Last active July 13, 2021 20:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caasi/63720bfb95f4105f8864e73e2f0e9031 to your computer and use it in GitHub Desktop.
Save caasi/63720bfb95f4105f8864e73e2f0e9031 to your computer and use it in GitHub Desktop.
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