aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/parse-expr.ts26
-rw-r--r--src/utils/parser-comb.ts23
-rw-r--r--tests/parse-expr.spec.ts46
3 files changed, 82 insertions, 13 deletions
diff --git a/src/parse-expr.ts b/src/parse-expr.ts
index dbad1a1..ec387d6 100644
--- a/src/parse-expr.ts
+++ b/src/parse-expr.ts
@@ -5,6 +5,7 @@ export type Expr = Enum<{
Call: { name: string, args: Expr[] }
Var: { name: string, defaultValue: Expr }
Identifier: string
+ Chain: { exprs: Expr[] }
LiteralString: string
}>
@@ -12,24 +13,33 @@ 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 comma = consumeWhitespace(P.string(','))
+const parens = <A>(p: P.Parser<A>): P.Parser<A> => P.between(P.string('('), p, P.string(')'))
+const identifierParser = P.regex(/^[a-z][a-z0-9_-]*/i)
-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(
+const callExprParser = (input: string) => P.map(P.zip2(
consumeWhitespace(identifierParser),
- P.between(P.string('('), consumeWhitespace(identifierParser), P.string(')')),
-), ([name, rest]) => Expr.Call({ name, args: [Expr.Identifier(rest)] }))
+ parens(consumeWhitespace(P.sepBy(exprParser, comma))),
+), ([name, args]) => Expr.Call({ name, args }))(input)
const exprParser: P.Parser<Expr> = P.or([
- callParser,
+ callExprParser,
identifierExprParser,
])
-export const parse = (input: string): Expr => {
- const res = exprParser(input.trim());
+const multiExpr = P.many1(exprParser)
+
+export const parse = (input: string): Array<Expr> => {
+ const res = multiExpr(input.trim());
return match(res, {
- Ok: ({ value }) => value,
+ 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/parser-comb.ts b/src/utils/parser-comb.ts
index 9cea86f..49f633e 100644
--- a/src/utils/parser-comb.ts
+++ b/src/utils/parser-comb.ts
@@ -17,12 +17,14 @@ export type ParseResult<T> = Result<{ value: T, input: string }, { error: 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) });
}
@@ -61,3 +63,24 @@ export const suffixed = <A>(parser: Parser<A>, parserSuffix: Parser<any>): Parse
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 }),
+ })
+
diff --git a/tests/parse-expr.spec.ts b/tests/parse-expr.spec.ts
index 00097a4..875af63 100644
--- a/tests/parse-expr.spec.ts
+++ b/tests/parse-expr.spec.ts
@@ -1,9 +1,45 @@
-import { parse } from '../src/parse-expr'
-import * as P from '../src/utils/parser-comb'
+import { Expr, parse } from '../src/parse-expr'
describe('parser', () => {
- it('should die', () => {
- const res = parse('hello(test)')
- expect(res).toEqual(['hello', '(test, 1)'])
+ it('should parse function call', () => {
+ expect(parse('hello()')).toEqual([Expr.Call({ name: 'hello', args: [] })])
+ expect(parse('hello ( wow , foo ) ')).toEqual([
+ Expr.Call({
+ name: 'hello',
+ args: [Expr.Identifier('wow'), Expr.Identifier('foo')],
+ }),
+ ])
+ expect(parse('hello(wow,foo)')).toEqual([
+ Expr.Call({
+ name: 'hello',
+ args: [Expr.Identifier('wow'), Expr.Identifier('foo')],
+ }),
+ ])
+ expect(parse('hello(wow,foo, coolio)')).toEqual([
+ Expr.Call({
+ name: 'hello',
+ args: [
+ Expr.Identifier('wow'),
+ Expr.Identifier('foo'),
+ Expr.Identifier('coolio'),
+ ],
+ }),
+ ])
+ expect(parse('hello(wow)')).toEqual([
+ Expr.Call({ name: 'hello', args: [Expr.Identifier('wow')] }),
+ ])
+ })
+
+ it('should parse sequential function calls', () => {
+ expect(parse('hello(world) foo-doo(bar, baz)')).toEqual([
+ Expr.Call({
+ name: 'hello',
+ args: [Expr.Identifier('world')],
+ }),
+ Expr.Call({
+ name: 'foo-doo',
+ args: [Expr.Identifier('bar'), Expr.Identifier('baz')],
+ }),
+ ])
})
})