summaryrefslogtreecommitdiff
path: root/src/utils/parser-comb.ts
blob: 3f171b899cf747c3b47ee81101e7c8d7f07dfc72 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { match } from './adt'
import { Result, mapResult, chainResult } from './result'

export type ParseResult<T> = Result<
  { value: T; input: string },
  { error: string; input: 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) })
  }

export const or =
  <T>([parser, ...rest]: Array<Parser<T>>): Parser<T> =>
  input => {
    if (rest.length === 0) return parser(input)
    const result = parser(input)
    return match(result, {
      Ok: () => result,
      Err: _ => or(rest)(input),
    })
  }

export const mapParseResult =
  <T, R>(
    parser: Parser<T>,
    fn: (_: { value: T; input: string }) => { value: R; input: string },
  ): Parser<R> =>
  input =>
    mapResult(parser(input), fn)

export const map = <T, R>(parser: Parser<T>, fn: (_: T) => R): Parser<R> =>
  mapParseResult(parser, ({ value, ...rest }) => ({
    ...rest,
    value: fn(value),
  }))

export const zip2 =
  <A, B>(parserA: Parser<A>, parserB: Parser<B>): Parser<readonly [A, B]> =>
  input => {
    // TODO: refactor please. shit code
    const resa: Result<
      { value: A; input: string },
      { error: string; input: string }
    > = parserA(input)
    return chainResult(resa, ({ value: a, input: inputB }) => {
      const res: Result<
        { value: readonly [A, B]; input: string },
        { error: string; input: string }
      > = map(parserB, b => [a, b] as const)(inputB)
      return res
    })
  }

export const prefixed = <A>(
  parserPrefix: Parser<any>,
  parser: Parser<A>,
): Parser<A> => map(zip2(parserPrefix, parser), ([_, a]) => a)

export const suffixed = <A>(
  parser: Parser<A>,
  parserSuffix: Parser<any>,
): Parser<A> => map(zip2(parser, parserSuffix), ([a, _]) => a)

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 }),
    })

export const optional =
  <A>(parser: Parser<A>): Parser<undefined | A> =>
  input => {
    const result = parser(input)
    return match(result, {
      Ok: _ => result,
      Err: _ => Result.Ok({ value: undefined, input }),
    })
  }