diff options
| author | Akshay Nair <phenax5@gmail.com> | 2023-08-10 21:27:42 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2023-08-10 21:27:42 +0530 |
| commit | 3854a42db888a58f0452bfb23b6e17df5bf8ad39 (patch) | |
| tree | bc19fbfaaa637467844a99baf8565cb340691346 | |
| parent | db97b1a9c42fe945ca37256f5ee18d52c6aa32b4 (diff) | |
| download | css-everything-3854a42db888a58f0452bfb23b6e17df5bf8ad39.tar.gz css-everything-3854a42db888a58f0452bfb23b6e17df5bf8ad39.zip | |
feat: multi expression parser done
| -rw-r--r-- | src/parse-expr.ts | 26 | ||||
| -rw-r--r-- | src/utils/parser-comb.ts | 23 | ||||
| -rw-r--r-- | tests/parse-expr.spec.ts | 46 |
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')], + }), + ]) }) }) |
