-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathStringParser.res
More file actions
120 lines (106 loc) · 3.94 KB
/
Copy pathStringParser.res
File metadata and controls
120 lines (106 loc) · 3.94 KB
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
open VJsonUtil
type success<'res> = {str: string, res: 'res}
type failure = {expected: string, remaining: string}
type parser<'res> = string => result<success<'res>, failure>
let map: (parser<'a>, 'a => 'b) => parser<'b> = (parser, f, str) =>
str->parser->Belt.Result.map(({str, res}) => {str: str, res: f(res)})
let opt: parser<'a> => parser<option<'a>> = (parser, str) =>
switch str->parser {
| Ok({str: str2, res}) => Ok({str: str2, res: Some(res)})
| _ => Ok({str: str, res: None})
}
let or_: (parser<'a>, parser<'a>) => parser<'a> = (parser1, parser2, str) =>
switch str->parser1 {
| Ok(res) => Ok(res)
| Error(e1) =>
switch str->parser2 {
| Ok(res) => Ok(res)
// Prefer the error message of whichever one consumed more input
| Error(e2) => Error(e1.remaining->Js.String.length < e2.remaining->Js.String.length ? e1 : e2)
}
}
let and_: (parser<'a>, parser<'b>) => parser<('a, 'b)> = (parser1, parser2, str1) =>
switch str1->parser1 {
| Ok({str: str2, res: res1}) =>
str2->parser2->Belt.Result.map(({str: str3, res: res2}) => {str: str3, res: (res1, res2)})
| Error(e) => Error(e)
}
let left: (parser<'a>, parser<_>) => parser<'a> = (parser1, parser2) =>
parser1->and_(parser2)->map(fst)
let right: (parser<_>, parser<'a>) => parser<'a> = (parser1, parser2) =>
parser1->and_(parser2)->map(snd)
// Parse as many items of the first parser as possible. After each parse, attempt to
// parse a separator. If successful, keep parsing. Otherwise, parse with the end parser.
let manySepEnd: 'a. (parser<'a>, ~sep: parser<_>, ~end_: parser<_>) => parser<array<'a>> = (
parser,
~sep,
~end_,
startStr,
) => {
let results = []
let str = ref(startStr)
let keepGoing = ref(true)
let error = ref(None)
let endLoop = (lastError, ~combine) => {
keepGoing := false
switch str.contents->end_ {
// The results we parsed are only valid if the endParser also matches.
| Ok({str: next}) => str := next
// Otherwise, the last failure is the "real" error.
| Error(e) =>
error :=
Some(
combine
? {...lastError, expected: lastError.expected->concatIfNotIncluded(` OR ${e.expected}`)}
: lastError,
)
}
}
while keepGoing.contents {
switch str.contents->parser {
| Ok({str: next, res}) => {
results->Js.Array2.push(res)->ignore
switch next->sep {
| Ok({str: next2}) => str := next2
| Error(e) => {
str := next
// After a successful item parse, either a separator or an end parse is
// valid, so if both fail, combine their errors
endLoop(e, ~combine=true)
}
}
}
// If the item parse fails, only an end parse is valid, don't combine errors
| Error(e) => endLoop(e, ~combine=false)
}
}
switch error.contents {
| None => Ok({str: str.contents, res: results})
| Some(e) => Error(e)
}
}
let sliceN = (str, from) => str->Js.String2.sliceToEnd(~from)
let literal: string => parser<string> = (expected, str) =>
str->Js.String2.startsWith(expected)
? Ok({str: str->sliceN(expected->Js.String.length), res: expected})
: Error({expected: expected->quote, remaining: str})
let regex: (Js.Re.t, ~index: int=?, string) => parser<string> = (
regex,
// Which index of the returned regex capture array to return
~index=0,
// Used to generate error message if the parser fails
description,
str,
) =>
switch str->Js.String2.match_(regex) {
| Some(arr) =>
Ok({
str: str->sliceN(arr->Belt.Array.get(0)->Belt.Option.getExn->Js.String.length),
res: arr->Belt.Array.get(index)->Belt.Option.getExn,
})
| _ => Error({expected: description, remaining: str})
}
let whitespace: parser<string> = regex(Regexes.whitespace, "whitespace")
let lexeme: parser<'a> => parser<'a> = parser => left(parser, whitespace)
let eof: parser<unit> = str =>
str === "" ? Ok({str: "", res: ()}) : Error({expected: "end of file", remaining: str})