From e01cb693bc5737792e2b37abfd98d2d8f81bac4d Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Thu, 10 Aug 2023 22:45:00 +0530 Subject: feat: adds simple evaluator --- src/eval.ts | 40 +++++++++++++++++++++++++++++ src/parse-expr.ts | 66 ------------------------------------------------ src/parser.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ src/utils/adt.ts | 5 ++++ src/utils/parser-comb.ts | 15 ++--------- src/utils/result.ts | 14 ++++++++++ 6 files changed, 127 insertions(+), 79 deletions(-) create mode 100644 src/eval.ts delete mode 100644 src/parse-expr.ts create mode 100644 src/parser.ts create mode 100644 src/utils/result.ts (limited to 'src') diff --git a/src/eval.ts b/src/eval.ts new file mode 100644 index 0000000..1e18457 --- /dev/null +++ b/src/eval.ts @@ -0,0 +1,40 @@ +import { Expr } from "./parser"; +import { match, matchString } from "./utils/adt"; + +export type Dependencies = { + addClass(id: string, classes: string): Promise + removeClass(id: string, classes: string): Promise + // requestGetCss(url: string): Promise + // getVarable(name: string, def?: string): Promise + // updateVariable(id: string, varName: string, value: string): Promise + // calculate ?? +} + +export const evalExpr = async (expr: Expr, deps: Dependencies): Promise => + match, Expr>(expr, { + Call: async ({ name, args }) => { + await matchString(name, { + 'add-class': async () => { + const id = await evalExpr(args[0], deps) + const classes = await evalExpr(args[1], deps) + if (id && classes) { + await deps.addClass(id, classes) + } + }, + 'remove-class': async () => { + const id = await evalExpr(args[0], deps) + const classes = await evalExpr(args[1], deps) + if (id && classes) { + await deps.removeClass(id, classes) + } + }, + _: () => Promise.reject(new Error('not supposed to be here')), + }) + return undefined + }, + LiteralString: async s => s, + Identifier: async s => s, + VarIdentifier: async s => s, + _: async _ => undefined, + }) + diff --git a/src/parse-expr.ts b/src/parse-expr.ts deleted file mode 100644 index 1c5c08f..0000000 --- a/src/parse-expr.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Enum, constructors, match } from './utils/adt' -import * as P from './utils/parser-comb' - -export type Expr = Enum<{ - Call: { name: string; args: Expr[] } - Identifier: string - VarIdentifier: string - LiteralString: string -}> - -export const Expr = constructors() - -const whitespace = P.regex(/^\s*/) -const consumeWhitespace = (p: P.Parser): P.Parser => - P.between(whitespace, p, whitespace) -const comma = consumeWhitespace(P.string(',')) -const parens = (p: P.Parser): P.Parser => - P.between(P.string('('), p, P.string(')')) -const identifierParser = P.regex(/^[a-z][a-z0-9_-]*/i) -const varIdentifierParser = P.regex(/^--[a-z][a-z0-9-]*/i) -const singleQuote = P.string("'") -const doubleQuote = P.string('"') - -const identifierExprParser = P.map(identifierParser, Expr.Identifier) -const varIdentifierExprParser = P.map(varIdentifierParser, Expr.VarIdentifier) - -const callExprParser = (input: string) => - P.map( - P.zip2( - consumeWhitespace(identifierParser), - parens(consumeWhitespace(P.sepBy(exprParser, comma))) - ), - ([name, args]) => Expr.Call({ name, args }) - )(input) - -const stringLiteralParser: P.Parser = P.map( - P.or([ - P.between(singleQuote, P.regex(/^[^']*/), singleQuote), - P.between(doubleQuote, P.regex(/^[^"]*/), doubleQuote), - ]), - Expr.LiteralString -) - -const exprParser: P.Parser = P.or([ - stringLiteralParser, - varIdentifierExprParser, - callExprParser, - identifierExprParser, -]) - -const multiExprParser = P.many1(exprParser) - -export const parse = (input: string): Array => { - const res = multiExprParser(input.trim()) - return match(res, { - Ok: ({ value, input }) => { - if (input) { - throw new Error(`Input not consumed completely here brosky: "${input}"`) - } - return value - }, - Err: ({ error, input }) => { - throw new Error(`${error}.\n Left input: ${input.slice(0, 20)}...`) - }, - }) -} diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..1c5c08f --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,66 @@ +import { Enum, constructors, match } from './utils/adt' +import * as P from './utils/parser-comb' + +export type Expr = Enum<{ + Call: { name: string; args: Expr[] } + Identifier: string + VarIdentifier: string + LiteralString: string +}> + +export const Expr = constructors() + +const whitespace = P.regex(/^\s*/) +const consumeWhitespace = (p: P.Parser): P.Parser => + P.between(whitespace, p, whitespace) +const comma = consumeWhitespace(P.string(',')) +const parens = (p: P.Parser): P.Parser => + P.between(P.string('('), p, P.string(')')) +const identifierParser = P.regex(/^[a-z][a-z0-9_-]*/i) +const varIdentifierParser = P.regex(/^--[a-z][a-z0-9-]*/i) +const singleQuote = P.string("'") +const doubleQuote = P.string('"') + +const identifierExprParser = P.map(identifierParser, Expr.Identifier) +const varIdentifierExprParser = P.map(varIdentifierParser, Expr.VarIdentifier) + +const callExprParser = (input: string) => + P.map( + P.zip2( + consumeWhitespace(identifierParser), + parens(consumeWhitespace(P.sepBy(exprParser, comma))) + ), + ([name, args]) => Expr.Call({ name, args }) + )(input) + +const stringLiteralParser: P.Parser = P.map( + P.or([ + P.between(singleQuote, P.regex(/^[^']*/), singleQuote), + P.between(doubleQuote, P.regex(/^[^"]*/), doubleQuote), + ]), + Expr.LiteralString +) + +const exprParser: P.Parser = P.or([ + stringLiteralParser, + varIdentifierExprParser, + callExprParser, + identifierExprParser, +]) + +const multiExprParser = P.many1(exprParser) + +export const parse = (input: string): Array => { + const res = multiExprParser(input.trim()) + return match(res, { + Ok: ({ value, input }) => { + if (input) { + throw new Error(`Input not consumed completely here brosky: "${input}"`) + } + return 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 e9a6c51..7234f67 100644 --- a/src/utils/adt.ts +++ b/src/utils/adt.ts @@ -5,6 +5,11 @@ export const match = [key in T['tag'] | '_']?: (v: TagValue) => R }): R => ((pattern as any)[tag.tag] || (pattern._ as any))(tag.value) +export const matchString = + (key: T, pattern: { + [key in T | '_']?: (key: key) => R + }): R => ((pattern as any)[key] || (pattern._ as any))(key) + type Tag = { tag: N; value: V } export type Enum = { [N in keyof T]: Tag }[keyof T] diff --git a/src/utils/parser-comb.ts b/src/utils/parser-comb.ts index 49f633e..e0665f6 100644 --- a/src/utils/parser-comb.ts +++ b/src/utils/parser-comb.ts @@ -1,16 +1,5 @@ -import { Enum, constructors, match } from './adt'; - -export type Result = Enum<{ Ok: V, Err: E }> -export const Result = constructors>() - -export const mapResult = (res: Result, fn: (_: A) => B): Result => - chainResult(res, a => Result.Ok(fn(a))) - -export const chainResult = (res: Result, fn: (_: A) => Result): Result => - match(res, { - Ok: a => fn(a), - Err: e => Result.Err(e), - }); +import { match } from './adt'; +import { Result, mapResult, chainResult } from './result'; export type ParseResult = Result<{ value: T, input: string }, { error: string, input: string }>; diff --git a/src/utils/result.ts b/src/utils/result.ts new file mode 100644 index 0000000..c0120b8 --- /dev/null +++ b/src/utils/result.ts @@ -0,0 +1,14 @@ +import { Enum, constructors, match } from "./adt"; + +export type Result = Enum<{ Ok: V, Err: E }> +export const Result = constructors>() + +export const mapResult = (res: Result, fn: (_: A) => B): Result => + chainResult(res, a => Result.Ok(fn(a))) + +export const chainResult = (res: Result, fn: (_: A) => Result): Result => + match(res, { + Ok: a => fn(a), + Err: e => Result.Err(e), + }); + -- cgit v1.3.1