aboutsummaryrefslogtreecommitdiff
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/utils/adt.ts32
-rw-r--r--src/utils/parser-comb.ts120
-rw-r--r--src/utils/result.ts18
3 files changed, 170 insertions, 0 deletions
diff --git a/src/utils/adt.ts b/src/utils/adt.ts
new file mode 100644
index 0000000..45d9e50
--- /dev/null
+++ b/src/utils/adt.ts
@@ -0,0 +1,32 @@
+type TagValue<T, N> = T extends Tag<N, infer V> ? V : never
+
+export const match = <R, T extends Tag<string, any>>(
+ tag: T,
+ pattern: {
+ [key in T['tag'] | '_']?: (v: TagValue<T, key>) => R
+ },
+): R => ((pattern as any)[tag.tag] || (pattern._ as any))(tag.value)
+
+export const matchString = <R, T extends string = string>(
+ key: T,
+ pattern: {
+ [key in T | '_']?: (key: key) => R
+ },
+): R => ((pattern as any)[key] || (pattern._ as any))(key)
+
+type Tag<N, V> = { tag: N; value: V }
+export type Enum<T> = { [N in keyof T]: Tag<N, T[N]> }[keyof T]
+
+export const constructors = <T extends Tag<string, any>>(): {
+ [N in T['tag']]: TagValue<T, N> extends null | never
+ ? (value?: null | never) => T
+ : (value: TagValue<T, N>) => T
+} =>
+ new Proxy(
+ {},
+ {
+ get(_, k) {
+ return (value: any) => ({ tag: k, value })
+ },
+ },
+ ) as any
diff --git a/src/utils/parser-comb.ts b/src/utils/parser-comb.ts
new file mode 100644
index 0000000..3f171b8
--- /dev/null
+++ b/src/utils/parser-comb.ts
@@ -0,0 +1,120 @@
+import { match } from './adt'
+import { Result, mapResult, chainResult } from './result'
+
+export type ParseResult<T> = Result<
+ { value: T; input: string },
+ { error: string; input: string }
+>
+
+export type Parser<T> = (input: string) => ParseResult<T>
+
+export const regex =
+ (re: RegExp): Parser<string> =>
+ input => {
+ if (input.length === 0) return Result.Err({ error: 'fuckedinput', input })
+ const res = input.match(re)
+ if (!res) return Result.Err({ error: 'fucked', input })
+ return Result.Ok({ value: res[0], input: input.replace(re, '') })
+ }
+
+export const string =
+ (str: string): Parser<string> =>
+ input => {
+ if (input.length === 0) return Result.Err({ error: 'fuckedinput', input })
+ if (!input.startsWith(str))
+ return Result.Err({ error: 'fuckedstring', input })
+ return Result.Ok({ value: str, input: input.slice(str.length) })
+ }
+
+export const or =
+ <T>([parser, ...rest]: Array<Parser<T>>): Parser<T> =>
+ input => {
+ if (rest.length === 0) return parser(input)
+ const result = parser(input)
+ return match(result, {
+ Ok: () => result,
+ Err: _ => or(rest)(input),
+ })
+ }
+
+export const mapParseResult =
+ <T, R>(
+ parser: Parser<T>,
+ fn: (_: { value: T; input: string }) => { value: R; input: string },
+ ): Parser<R> =>
+ input =>
+ mapResult(parser(input), fn)
+
+export const map = <T, R>(parser: Parser<T>, fn: (_: T) => R): Parser<R> =>
+ mapParseResult(parser, ({ value, ...rest }) => ({
+ ...rest,
+ value: fn(value),
+ }))
+
+export const zip2 =
+ <A, B>(parserA: Parser<A>, parserB: Parser<B>): Parser<readonly [A, B]> =>
+ input => {
+ // TODO: refactor please. shit code
+ const resa: Result<
+ { value: A; input: string },
+ { error: string; input: string }
+ > = parserA(input)
+ return chainResult(resa, ({ value: a, input: inputB }) => {
+ const res: Result<
+ { value: readonly [A, B]; input: string },
+ { error: string; input: string }
+ > = map(parserB, b => [a, b] as const)(inputB)
+ return res
+ })
+ }
+
+export const prefixed = <A>(
+ parserPrefix: Parser<any>,
+ parser: Parser<A>,
+): Parser<A> => map(zip2(parserPrefix, parser), ([_, a]) => a)
+
+export const suffixed = <A>(
+ parser: Parser<A>,
+ parserSuffix: Parser<any>,
+): Parser<A> => map(zip2(parser, parserSuffix), ([a, _]) => a)
+
+export const between = <A>(
+ prefix: Parser<any>,
+ parser: Parser<A>,
+ suffix: Parser<any>,
+): Parser<A> => suffixed(prefixed(prefix, parser), suffix)
+
+export const many0 =
+ <A>(parser: Parser<A>): Parser<Array<A>> =>
+ originalInput =>
+ match(parser(originalInput), {
+ Ok: ({ value, input }) => map(many0(parser), ls => [value, ...ls])(input),
+ Err: ({ input }) => Result.Ok({ value: [], input }),
+ })
+
+export const many1 =
+ <A>(parser: Parser<A>): Parser<Array<A>> =>
+ originalInput =>
+ match(parser(originalInput), {
+ Ok: ({ value, input }) => map(many0(parser), ls => [value, ...ls])(input),
+ Err: err => Result.Err(err),
+ })
+
+export const sepBy =
+ <A>(parser: Parser<A>, sepP: Parser<any>): Parser<Array<A>> =>
+ originalInput =>
+ match(parser(originalInput), {
+ Ok: ({ value, input }) =>
+ map(many0(prefixed(sepP, parser)), ls => [value, ...ls])(input),
+ Err: _ => Result.Ok({ value: [], input: originalInput }),
+ })
+
+export const optional =
+ <A>(parser: Parser<A>): Parser<undefined | A> =>
+ input => {
+ const result = parser(input)
+ return match(result, {
+ Ok: _ => result,
+ Err: _ => Result.Ok({ value: undefined, input }),
+ })
+ }
diff --git a/src/utils/result.ts b/src/utils/result.ts
new file mode 100644
index 0000000..39a7fe6
--- /dev/null
+++ b/src/utils/result.ts
@@ -0,0 +1,18 @@
+import { Enum, constructors, match } from './adt'
+
+export type Result<V, E> = Enum<{ Ok: V; Err: E }>
+export const Result = constructors<Result<any, any>>()
+
+export const mapResult = <A, B, E>(
+ res: Result<A, E>,
+ fn: (_: A) => B,
+): Result<B, E> => chainResult(res, a => Result.Ok(fn(a)))
+
+export const chainResult = <A, B, E>(
+ res: Result<A, E>,
+ fn: (_: A) => Result<B, E>,
+): Result<B, E> =>
+ match(res, {
+ Ok: a => fn(a),
+ Err: e => Result.Err(e),
+ })