-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathParser.elm
358 lines (262 loc) · 7.55 KB
/
Parser.elm
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
module Parser
exposing
( Parser
, State
, Problem(..)
, lazy
, succeed
, fail
, andThen
, map
, map2
, (|=)
, (|*)
, oneOf
, oneOfBacktrack
, item
, satisfy
, run
)
{-| General parser combinator library for Elm. It also incldues an HTML parser!
# Parsing
@docs Parser, run
# Chaining
@docs map, map2, (|=), (|*), andThen, oneOf, oneOfBacktrack
# Error Handling
@docs Problem, State
# Advanced
@docs lazy, succeed, fail
# Low Level
@docs item, satisfy
-}
import String
import List
import Parser.Internal as Internal exposing (Status(..))
import Misc
-- TYPES
{-| A parser. Is a function that will parse a string into the type of `value`
-}
type alias Parser value =
Internal.Parser Problem value
{-| The curren status of a parse operation
-}
type alias Status value =
Internal.Status Problem value
{-| The curren state of a parser
-}
type alias State =
Internal.State
{-| Possible errors that can be encountered while parsing
-}
type Problem
= BadOneOf (List Problem)
| BadInt
| Bad String
| ExpectedSymbol String
-- PRIMATIVES
{-| Lift a value into a successful parse operation
-}
succeed : value -> Parser value
succeed value =
\state ->
Pass state value
{-| Fails a parse operation with a problem
-}
fail : Problem -> Parser value
fail problem =
\state ->
Fail state problem
{-| Used to declare a parser in an anonymous function, allowing you to defined
resusive grammers
-}
lazy : (() -> Parser value) -> Parser value
lazy wrapper =
\state ->
wrapper () <| state
-- MAP
{-| Transform the result of a parser
-}
map : (valueA -> valueB) -> Parser valueA -> Parser valueB
map func parser =
\state ->
case parser state of
Pass nextState value ->
succeed (func value) nextState
Fail failState problem ->
fail problem failState
{-| Transform the reuslt of two parser
-}
map2 :
(valueA -> valueB -> valueC)
-> Parser valueA
-> Parser valueB
-> Parser valueC
map2 func parserA parserB =
\state ->
case parserA state of
Pass stateA valueA ->
case parserB stateA of
Pass stateB valueB ->
succeed (func valueA valueB) stateB
Fail failState problem ->
fail problem failState
Fail failState problem ->
fail problem failState
-- ANDTHEN
{-| Transfrom a parser, based on the result of the previous one
-}
andThen :
(resultA -> Parser resultB)
-> Parser resultA
-> Parser resultB
andThen func parser =
\state ->
case parser state of
Pass nextState result ->
(func result) nextState
Fail failState problem ->
fail problem failState
-- MULTIPLE PARSERS
{-| Test a list of parser on a state, without backtracking if a parser fails.
In the case of
let
state =
{ source = "bonasdf"
, offset = 0
, row = 0
, col = 0
}
in
oneOf [word "bonjour", lower] state
The parser will fail because it starts to correctly parse "bonjour", but fails
part way. Becuase there is no backtracking, and does not revert to `lower` after
`word "bonjour" fails.
-}
oneOf : List (Parser result) -> Parser result
oneOf parsers =
\state ->
oneOfHelper state parsers []
{-| Helper for oneOf
-}
oneOfHelper :
State
-> List (Parser result)
-> List Problem
-> Status result
oneOfHelper state parsers problems =
case parsers of
[] ->
fail (BadOneOf <| List.reverse problems) state
parser :: rest ->
case parser state of
(Pass _ _) as status ->
status
(Fail { row, col } problem) as status ->
{- Check that this current parser hasn't progressed before
attempting the next one
-}
if state.row == row && state.col == col then
oneOfHelper state rest (problem :: problems)
else
status
{-| Test a list of parser on a state, WITH backtracking if a parser fails. This
means if the first parser fails, it WILL to back to the beginning and try the
next parser
|
-}
oneOfBacktrack : List (Parser result) -> Parser result
oneOfBacktrack parsers =
\state ->
oneOfBacktrackHelper state parsers []
{-| Helper for oneOfBacktrack
-}
oneOfBacktrackHelper :
State
-> List (Parser result)
-> List Problem
-> Status result
oneOfBacktrackHelper state parsers problems =
case parsers of
[] ->
fail (BadOneOf <| List.reverse problems) state
parser :: rest ->
case parser state of
(Pass _ _) as status ->
status
Fail _ problem ->
oneOfBacktrackHelper state rest (problem :: problems)
-- BASIC PARSE OPERATIONS
{-| Parses a single character, always successful as long at the offset is valid
-}
item : Parser Char
item =
\({ source, offset, row, col } as state) ->
let
nextOffset =
offset + 1
sliver =
String.uncons <| String.slice offset nextOffset source
in
case sliver of
Just ( c, _ ) ->
let
( nextRow, nextCol ) =
if c == '\n' then
( row + 1, 0 )
else
( row, col + 1 )
in
succeed c
{ state
| offset = nextOffset
, row = nextRow
, col = nextCol
}
Nothing ->
fail (Bad "I didn't expect the input string to end") state
{-| Tests a the parsed character with the function provided, if it return false,
then fail with the provided problem
-}
satisfy : (Char -> Bool) -> Problem -> Parser Char
satisfy test problem =
\state ->
case item state of
Pass nextState c ->
if test c then
succeed c nextState
else
-- if test fails, keep the original state
fail problem state
Fail failState _ ->
fail problem failState
-- PIPELINE
{-| Given a parserA that contians a function and parserB that is regular run
both. Apply the result from parserB to the function from parserA
-}
(|=) : Parser (a -> b) -> Parser a -> Parser b
(|=) parserFunc parserArg =
map2 Misc.applyFunc parserFunc parserArg
{-| Given a parserA and parserB, run both but only keep the result from parserB
-}
(|*) : Parser keep -> Parser ignore -> Parser keep
(|*) parserIgnore parserKeep =
map2 always parserIgnore parserKeep
-- RUN
{-| Wrap a source string into an initial state
-}
initialState : String -> State
initialState source =
{ source = source
, offset = 0
, row = 1
, col = 1
}
{-| Wrap a source string into a default state
-}
run : Parser a -> String -> Result ( State, Problem ) a
run parser source =
case (parser <| initialState source) of
Pass _ a ->
Ok a
Fail state problem ->
Err ( state, problem )