summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2023-08-10 18:18:46 +0530
committerAkshay Nair <phenax5@gmail.com>2023-08-10 18:18:46 +0530
commitdb97b1a9c42fe945ca37256f5ee18d52c6aa32b4 (patch)
tree1da3da4a99192be8ad10b2931d447c2cc3f45036 /src
parent6a04167058b59b54b183104a1cf1b1b2cf8d7e9a (diff)
downloadcss-everything-db97b1a9c42fe945ca37256f5ee18d52c6aa32b4.tar.gz
css-everything-db97b1a9c42fe945ca37256f5ee18d52c6aa32b4.zip
feat: adds parser for simple expressions
Diffstat (limited to 'src')
-rw-r--r--src/parse-expr.ts33
-rw-r--r--src/utils/adt.ts6
-rw-r--r--src/utils/parser-comb.ts63
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)
+