diff options
Diffstat (limited to '')
| -rw-r--r-- | src/parse-expr.ts | 33 | ||||
| -rw-r--r-- | src/utils/adt.ts | 6 | ||||
| -rw-r--r-- | src/utils/parser-comb.ts | 63 |
3 files changed, 95 insertions, 7 deletions
diff --git a/src/parse-expr.ts b/src/parse-expr.ts index 53de485..dbad1a1 100644 --- a/src/parse-expr.ts +++ b/src/parse-expr.ts @@ -1,11 +1,38 @@ -import { Enum, constructors } from "./utils/adt"; +import { Enum, constructors, match } from './utils/adt'; +import * as P from './utils/parser-comb' export type Expr = Enum<{ Call: { name: string, args: Expr[] } Var: { name: string, defaultValue: Expr } Identifier: string LiteralString: string -}>; +}> -export const Expr = constructors<Expr>(); +export const Expr = constructors<Expr>() + +const whitespace = P.regex(/\s*/) +const consumeWhitespace = <A>(p: P.Parser<A>): P.Parser<A> => P.between(whitespace, p, whitespace) + +const identifierParser = P.regex(/^[a-z][a-z0-9]+/i) +const identifierExprParser = P.map(identifierParser, ident => Expr.Identifier(ident)) + +const callParser = P.map(P.zip2( + consumeWhitespace(identifierParser), + P.between(P.string('('), consumeWhitespace(identifierParser), P.string(')')), +), ([name, rest]) => Expr.Call({ name, args: [Expr.Identifier(rest)] })) + +const exprParser: P.Parser<Expr> = P.or([ + callParser, + identifierExprParser, +]) + +export const parse = (input: string): Expr => { + const res = exprParser(input.trim()); + return match(res, { + Ok: ({ value }) => value, + Err: ({ error, input }) => { + throw new Error(`${error}.\n Left input: ${input.slice(0, 20)}...`) + }, + }) +} diff --git a/src/utils/adt.ts b/src/utils/adt.ts index c19398b..e9a6c51 100644 --- a/src/utils/adt.ts +++ b/src/utils/adt.ts @@ -1,11 +1,9 @@ type TagValue<T, N> = T extends Tag<N, infer V> ? V : never export const match = - <R, T extends Tag<string, any>>(pattern: { + <R, T extends Tag<string, any>>(tag: T, pattern: { [key in T['tag'] | '_']?: (v: TagValue<T, key>) => R - }) => - (tag: T): R => - ((pattern as any)[tag.tag] || (pattern._ as any))(tag.value) + }): R => ((pattern as any)[tag.tag] || (pattern._ as any))(tag.value) type Tag<N, V> = { tag: N; value: V } export type Enum<T> = { [N in keyof T]: Tag<N, T[N]> }[keyof T] diff --git a/src/utils/parser-comb.ts b/src/utils/parser-comb.ts new file mode 100644 index 0000000..9cea86f --- /dev/null +++ b/src/utils/parser-comb.ts @@ -0,0 +1,63 @@ +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), + }); + +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 => { + 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.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) + |
