aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/parser/index.ts38
-rw-r--r--src/parser/utils.ts29
-rw-r--r--src/types.ts9
-rw-r--r--tests/parser.spec.ts79
4 files changed, 108 insertions, 47 deletions
diff --git a/src/parser/index.ts b/src/parser/index.ts
index c61364d..3943848 100644
--- a/src/parser/index.ts
+++ b/src/parser/index.ts
@@ -2,9 +2,11 @@ import { flow, pipe } from 'fp-ts/function'
import { chain, left, orElse, right } from 'fp-ts/lib/Either'
import {
delimited,
+ digits,
many0,
many1,
mapTo,
+ matchChar,
oneOf,
optional,
or,
@@ -18,7 +20,9 @@ import {
tuple3,
whitespaces0,
} from './utils'
-import { Expr, ListExpr } from '../types'
+import { Expr, ListExpr, Literal } from '../types'
+import {getOrElse, map} from 'fp-ts/lib/Option'
+import {snd} from 'fp-ts/lib/Tuple'
const start = mapTo(symbol('^'), _ => Expr.Start())
const end = mapTo(symbol('$'), _ => Expr.End())
@@ -76,6 +80,37 @@ const objectProperty = (input: string) =>
),
)
+const unsignedNum: Parser<number> = mapTo(pair(digits, optional(pair(matchChar('.'), digits))), ([int, decimal]) =>
+ pipe(
+ decimal,
+ map(snd),
+ getOrElse(() => '0'),
+ n => parseFloat(`${int}.${n}`),
+ )
+)
+
+const numberLiteral: Parser<Literal> = mapTo(
+ pair(optional(oneOf(['+', '-'])), unsignedNum),
+ ([signO, n]) =>
+ pipe(
+ signO,
+ getOrElse(() => '+'),
+ sign => (sign === '-' ? -1 : 1) * n,
+ Literal.Number,
+ )
+ ,
+)
+
+const booleanLiteral: Parser<Literal> = mapTo(oneOf(['true', 'false']), b =>
+ Literal.Boolean(b === 'true' ? true : false),
+)
+
+const literalP: Parser<Literal> = delimited(
+ whitespaces0,
+ or([booleanLiteral, numberLiteral]),
+ whitespaces0,
+)
+
const expressionP: Parser<Expr> = (input: string) =>
pipe(
input,
@@ -91,6 +126,7 @@ const expressionP: Parser<Expr> = (input: string) =>
anyBool,
truthy,
falsey,
+ mapTo(literalP, Expr.Literal),
]),
wrapQuantifiers,
wrapAlt,
diff --git a/src/parser/utils.ts b/src/parser/utils.ts
index 841d77c..908405e 100644
--- a/src/parser/utils.ts
+++ b/src/parser/utils.ts
@@ -10,7 +10,7 @@ import {
orElseW,
} from 'fp-ts/Either'
import { none, some, Option } from 'fp-ts/Option'
-import { mapFst, mapSnd, snd } from 'fp-ts/Tuple'
+import { fst, mapFst, mapSnd, snd } from 'fp-ts/Tuple'
import { eq } from '../utils'
export type char = string
@@ -46,7 +46,7 @@ export const many1 = <T>(parser: Parser<T>): Parser<Array<T>> =>
chain(([res, inp]) =>
res.length > 0
? right([res, inp])
- : left([`many1 failed to parse at ${inp}`, inp]),
+ : left([`many1 failed to parse at '${inp}'`, inp]),
),
),
)
@@ -66,21 +66,16 @@ const recoverInput =
)
export const prefixed = <T>(a: Parser<any>, b: Parser<T>): Parser<T> =>
- recoverInput(flow(a, chain(flow(snd, b))))
+ recoverInput(mapTo(pair(a, b), snd))
export const suffixed = <T>(a: Parser<T>, b: Parser<any>): Parser<T> =>
- recoverInput(
- flow(
- a,
- chain(([out, inp]) => pipe(inp, b, map(mapFst(_ => out)))),
- ),
- )
+ recoverInput(mapTo(pair(a, b), fst))
export const delimited = <T>(
p: Parser<any>,
a: Parser<T>,
s: Parser<any>,
-): Parser<T> => recoverInput(suffixed(prefixed(p, a), s))
+): Parser<T> => suffixed(prefixed(p, a), s)
export const satifyChar =
(f: (c: char) => boolean): Parser<char> =>
@@ -92,9 +87,9 @@ export const satifyChar =
export const digit = satifyChar(c => /^[0-9]$/g.test(c))
-export const integer: Parser<number> = flow(
+export const digits: Parser<string> = flow(
many1(digit),
- map(mapFst(ds => parseInt(ds.join(''), 10))),
+ map(mapFst(ds => ds.join(''))),
)
export const or = <T>(parsers: Parser<T>[]): Parser<T> => {
@@ -111,13 +106,6 @@ export const or = <T>(parsers: Parser<T>[]): Parser<T> => {
export const matchChar = (ch: char): Parser<char> => satifyChar(eq(ch))
-export const space = matchChar(' ')
-export const newline = matchChar('\n')
-export const tab = matchChar('\t')
-
-export const whitespace = or([space, newline, tab])
-export const whitespaces0 = many0(whitespace)
-
export const matchString =
(s: string): Parser<string> =>
(input: string) =>
@@ -127,6 +115,9 @@ export const matchString =
export const oneOf = (xs: string[]): Parser<string> => or(xs.map(matchString))
+export const whitespace = oneOf([' ', '\n', '\t'])
+export const whitespaces0 = many0(whitespace)
+
export const symbol = (s: string): Parser<string> =>
delimited(whitespaces0, matchString(s), whitespaces0)
diff --git a/src/types.ts b/src/types.ts
index d30413d..ac577a1 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,6 +3,13 @@ import { constructors, Union } from "./utils"
type _ = never
+export type Literal = Union<{
+ String: string,
+ Number: number,
+ Boolean: boolean,
+}>
+export const Literal = constructors<Literal>()
+
export type Expr = Union<{
Start: _,
End: _,
@@ -19,8 +26,8 @@ export type Expr = Union<{
Falsey: _,
Group: { exprs: Expr[] },
PropertyMatch: { name: string, exprs: Expr[] },
+ Literal: Literal,
}>
-
export const Expr = constructors<Expr>()
export type ListExpr = [
diff --git a/tests/parser.spec.ts b/tests/parser.spec.ts
index 9bc891e..c935165 100644
--- a/tests/parser.spec.ts
+++ b/tests/parser.spec.ts
@@ -1,31 +1,30 @@
import { left, right } from 'fp-ts/Either'
import {
- integer,
+ digits,
whitespace,
whitespaces0,
delimited,
- many1,
- satifyChar,
} from '../src/parser/utils'
-import { parser, propertyName } from '../src/parser'
+import { parser } from '../src/parser'
import { none, some } from 'fp-ts/Option'
-import { Expr } from '../src/types'
+import { Expr, Literal } from '../src/types'
+import { jlog } from '../src/utils'
-const plog = (x: any) => console.log(JSON.stringify(x, null, 2))
+const wrap = (l: any) => right([[none, l, none], ''])
-describe('Foobar', () => {
+describe('Parser', () => {
it('should do shit', () => {
- expect(integer('12901')).toEqual(right([12901, '']))
- expect(integer('12901asas')).toEqual(right([12901, 'asas']))
+ expect(digits('12901')).toEqual(right(['12901', '']))
+ expect(digits('12901asas')).toEqual(right(['12901', 'asas']))
expect(whitespace(' ')).toEqual(right([' ', '']))
expect(whitespace('\n')).toEqual(right(['\n', '']))
expect(whitespace('a')).toEqual(left(['unable to match', 'a']))
- expect(delimited(whitespaces0, integer, whitespaces0)(' 20 ')).toEqual(
- right([20, '']),
+ expect(delimited(whitespaces0, digits, whitespaces0)(' 20 ')).toEqual(
+ right(['20', '']),
)
- expect(delimited(whitespaces0, integer, whitespaces0)(' 2 0 ')).toEqual(
- right([2, '0 ']),
+ expect(delimited(whitespaces0, digits, whitespaces0)(' 2 0 ')).toEqual(
+ right(['2', '0 ']),
)
})
@@ -87,20 +86,48 @@ describe('Foobar', () => {
it('object proprtyu', () => {
expect(parser(/ [name \s\T] [age \n] /.source)).toEqual(
- right([
- [
- none,
- [
- Expr.PropertyMatch({
- name: 'name',
- exprs: [Expr.AnyString(), Expr.Truthy()],
- }),
- Expr.PropertyMatch({ name: 'age', exprs: [Expr.AnyNumber()] }),
- ],
- none,
- ],
- '',
+ wrap([
+ Expr.PropertyMatch({
+ name: 'name',
+ exprs: [Expr.AnyString(), Expr.Truthy()],
+ }),
+ Expr.PropertyMatch({ name: 'age', exprs: [Expr.AnyNumber()] }),
]),
)
})
+
+ it('literals', () => {
+ // unsigned numbers
+ expect(parser(/ 0.105 /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Number(0.105))]),
+ )
+ expect(parser(/ 9 /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Number(9))]),
+ )
+ expect(parser(/ 23 /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Number(23))]),
+ )
+
+ // signed numbers
+ expect(parser(/ +23.025 /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Number(23.025))]),
+ )
+ expect(parser(/ -23.025 /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Number(-23.025))]),
+ )
+ expect(parser(/ +23 /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Number(23))]),
+ )
+ expect(parser(/ -23 /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Number(-23))]),
+ )
+
+ // boolean
+ expect(parser(/ true /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Boolean(true))]),
+ )
+ expect(parser(/ false /.source)).toEqual(
+ wrap([Expr.Literal(Literal.Boolean(false))]),
+ )
+ })
})