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/parser.ts | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/parser.ts (limited to 'src/parser.ts') 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)}...`) + }, + }) +} -- cgit v1.3.1