-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathAdvanced.elm
More file actions
1378 lines (1030 loc) · 32.1 KB
/
Advanced.elm
File metadata and controls
1378 lines (1030 loc) · 32.1 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
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
module Parser.Advanced exposing
( Parser, run, DeadEnd, inContext, Token(..)
, int, float, number, symbol, keyword, variable, end
, succeed, (|=), (|.), lazy, andThen, problem
, oneOf, map, backtrackable, commit, token
, sequence, Trailing(..), loop, Step(..)
, spaces, lineComment, multiComment, Nestable(..)
, getChompedString, chompIf, chompWhile, chompUntil, chompUntilEndOr, mapChompedString
, withIndent, getIndent
, getPosition, getRow, getCol, getOffset, getSource
)
{-|
# Parsers
@docs Parser, run, DeadEnd, inContext, Token
* * *
**Everything past here works just like in the
[`Parser`](/packages/elm/parser/latest/Parser) module, except that `String`
arguments become `Token` arguments, and you need to provide a `Problem` for
certain scenarios.**
* * *
# Building Blocks
@docs int, float, number, symbol, keyword, variable, end
# Pipelines
@docs succeed, (|=), (|.), lazy, andThen, problem
# Branches
@docs oneOf, map, backtrackable, commit, token
# Loops
@docs sequence, Trailing, loop, Step
# Whitespace
@docs spaces, lineComment, multiComment, Nestable
# Chompers
@docs getChompedString, chompIf, chompWhile, chompUntil, chompUntilEndOr, mapChompedString
# Indentation
@docs withIndent, getIndent
# Positions
@docs getPosition, getRow, getCol, getOffset, getSource
-}
import Char
import Elm.Kernel.Parser
import Set
-- INFIX OPERATORS
infix left 5 (|=) = keeper
infix left 6 (|.) = ignorer
{- NOTE: the (|.) operator binds tighter to slightly reduce the amount
of recursion in pipelines. For example:
func
|. a
|. b
|= c
|. d
|. e
With the same precedence:
(ignorer (ignorer (keeper (ignorer (ignorer func a) b) c) d) e)
With higher precedence:
keeper (ignorer (ignorer func a) b) (ignorer (ignorer c d) e)
So the maximum call depth goes from 5 to 3.
-}
-- PARSERS
{-| An advanced `Parser` gives two ways to improve your error messages:
- `problem` — Instead of all errors being a `String`, you can create a
custom type like `type Problem = BadIndent | BadKeyword String` and track
problems much more precisely.
- `context` — Error messages can be further improved when precise
problems are paired with information about where you ran into trouble. By
tracking the context, instead of saying “I found a bad keyword” you can say
“I found a bad keyword when parsing a list” and give folks a better idea of
what the parser thinks it is doing.
I recommend starting with the simpler [`Parser`][parser] module though, and
when you feel comfortable and want better error messages, you can create a type
alias like this:
```elm
import Parser.Advanced
type alias MyParser a =
Parser.Advanced.Parser Context Problem a
type Context = Definition String | List | Record
type Problem = BadIndent | BadKeyword String
```
All of the functions from `Parser` should exist in `Parser.Advanced` in some
form, allowing you to switch over pretty easily.
[parser]: /packages/elm/parser/latest/Parser
-}
type Parser context problem value =
Parser (State context -> PStep context problem value)
type PStep context problem value
= Good Bool value (State context)
| Bad Bool (Bag context problem)
type alias State context =
{ src : String
, offset : Int
, indent : Int
, context : List (Located context)
, row : Int
, col : Int
}
type alias Located context =
{ row : Int
, col : Int
, context : context
}
-- RUN
{-| This works just like [`Parser.run`](/packages/elm/parser/latest/Parser#run).
The only difference is that when it fails, it has much more precise information
for each dead end.
-}
run : Parser c x a -> String -> Result (List (DeadEnd c x)) a
run (Parser parse) src =
case parse { src = src, offset = 0, indent = 1, context = [], row = 1, col = 1} of
Good _ value _ ->
Ok value
Bad _ bag ->
Err (bagToList bag [])
-- PROBLEMS
{-| Say you are parsing a function named `viewHealthData` that contains a list.
You might get a `DeadEnd` like this:
```elm
{ row = 18
, col = 22
, problem = UnexpectedComma
, contextStack =
[ { row = 14
, col = 1
, context = Definition "viewHealthData"
}
, { row = 15
, col = 4
, context = List
}
]
}
```
We have a ton of information here! So in the error message, we can say that “I
ran into an issue when parsing a list in the definition of `viewHealthData`. It
looks like there is an extra comma.” Or maybe something even better!
Furthermore, many parsers just put a mark where the problem manifested. By
tracking the `row` and `col` of the context, we can show a much larger region
as a way of indicating “I thought I was parsing this thing that starts over
here.” Otherwise you can get very confusing error messages on a missing `]` or
`}` or `)` because “I need more indentation” on something unrelated.
**Note:** Rows and columns are counted like a text editor. The beginning is `row=1`
and `col=1`. The `col` increments as characters are chomped. When a `\n` is chomped,
`row` is incremented and `col` starts over again at `1`.
-}
type alias DeadEnd context problem =
{ row : Int
, col : Int
, problem : problem
, contextStack : List { row : Int, col : Int, context : context }
}
type Bag c x
= Empty
| AddRight (Bag c x) (DeadEnd c x)
| Append (Bag c x) (Bag c x)
fromState : State c -> x -> Bag c x
fromState s x =
AddRight Empty (DeadEnd s.row s.col x s.context)
fromInfo : Int -> Int -> x -> List (Located c) -> Bag c x
fromInfo row col x context =
AddRight Empty (DeadEnd row col x context)
bagToList : Bag c x -> List (DeadEnd c x) -> List (DeadEnd c x)
bagToList bag list =
case bag of
Empty ->
list
AddRight bag1 x ->
bagToList bag1 (x :: list)
Append bag1 bag2 ->
bagToList bag1 (bagToList bag2 list)
-- PRIMITIVES
{-| Just like [`Parser.succeed`](Parser#succeed)
-}
succeed : a -> Parser c x a
succeed a =
Parser <| \s ->
Good False a s
{-| Just like [`Parser.problem`](Parser#problem) except you provide a custom
type for your problem.
-}
problem : x -> Parser c x a
problem x =
Parser <| \s ->
Bad False (fromState s x)
-- MAPPING
{-| Just like [`Parser.map`](Parser#map)
-}
map : (a -> b) -> Parser c x a -> Parser c x b
map func (Parser parse) =
Parser <| \s0 ->
case parse s0 of
Good p a s1 ->
Good p (func a) s1
Bad p x ->
Bad p x
map2 : (a -> b -> value) -> Parser c x a -> Parser c x b -> Parser c x value
map2 func (Parser parseA) (Parser parseB) =
Parser <| \s0 ->
case parseA s0 of
Bad p x ->
Bad p x
Good p1 a s1 ->
case parseB s1 of
Bad p2 x ->
Bad (p1 || p2) x
Good p2 b s2 ->
Good (p1 || p2) (func a b) s2
{-| Just like the [`(|=)`](Parser#|=) from the `Parser` module.
-}
keeper : Parser c x (a -> b) -> Parser c x a -> Parser c x b
keeper parseFunc parseArg =
map2 (<|) parseFunc parseArg
{-| Just like the [`(|.)`](Parser#|.) from the `Parser` module.
-}
ignorer : Parser c x keep -> Parser c x ignore -> Parser c x keep
ignorer keepParser ignoreParser =
map2 always keepParser ignoreParser
-- AND THEN
{-| Just like [`Parser.andThen`](Parser#andThen)
-}
andThen : (a -> Parser c x b) -> Parser c x a -> Parser c x b
andThen callback (Parser parseA) =
Parser <| \s0 ->
case parseA s0 of
Bad p x ->
Bad p x
Good p1 a s1 ->
let
(Parser parseB) =
callback a
in
case parseB s1 of
Bad p2 x ->
Bad (p1 || p2) x
Good p2 b s2 ->
Good (p1 || p2) b s2
-- LAZY
{-| Just like [`Parser.lazy`](Parser#lazy)
-}
lazy : (() -> Parser c x a) -> Parser c x a
lazy thunk =
Parser <| \s ->
let
(Parser parse) =
thunk ()
in
parse s
-- ONE OF
{-| Just like [`Parser.oneOf`](Parser#oneOf)
-}
oneOf : List (Parser c x a) -> Parser c x a
oneOf parsers =
Parser <| \s -> oneOfHelp s Empty parsers
oneOfHelp : State c -> Bag c x -> List (Parser c x a) -> PStep c x a
oneOfHelp s0 bag parsers =
case parsers of
[] ->
Bad False bag
Parser parse :: remainingParsers ->
case parse s0 of
Good _ _ _ as step ->
step
Bad p x as step ->
if p then
step
else
oneOfHelp s0 (Append bag x) remainingParsers
-- LOOP
{-| Just like [`Parser.Step`](Parser#Step)
-}
type Step state a
= Loop state
| Done a
{-| Just like [`Parser.loop`](Parser#loop)
-}
loop : state -> (state -> Parser c x (Step state a)) -> Parser c x a
loop state callback =
Parser <| \s ->
loopHelp False state callback s
loopHelp : Bool -> state -> (state -> Parser c x (Step state a)) -> State c -> PStep c x a
loopHelp p state callback s0 =
let
(Parser parse) =
callback state
in
case parse s0 of
Good p1 step s1 ->
case step of
Loop newState ->
loopHelp (p || p1) newState callback s1
Done result ->
Good (p || p1) result s1
Bad p1 x ->
Bad (p || p1) x
-- BACKTRACKABLE
{-| Just like [`Parser.backtrackable`](Parser#backtrackable)
-}
backtrackable : Parser c x a -> Parser c x a
backtrackable (Parser parse) =
Parser <| \s0 ->
case parse s0 of
Bad _ x ->
Bad False x
Good _ a s1 ->
Good False a s1
{-| Just like [`Parser.commit`](Parser#commit)
-}
commit : a -> Parser c x a
commit a =
Parser <| \s -> Good True a s
-- SYMBOL
{-| Just like [`Parser.symbol`](Parser#symbol) except you provide a `Token` to
clearly indicate your custom type of problems:
comma : Parser Context Problem ()
comma =
symbol (Token "," ExpectingComma)
-}
symbol : Token x -> Parser c x ()
symbol =
token
-- KEYWORD
{-| Just like [`Parser.keyword`](Parser#keyword) except you provide a `Token`
to clearly indicate your custom type of problems:
let_ : Parser Context Problem ()
let_ =
symbol (Token "let" ExpectingLet)
Note that this would fail to chomp `letter` because of the subsequent
characters. Use `token` if you do not want that last letter check.
-}
keyword : Token x -> Parser c x ()
keyword (Token kwd expecting) =
let
progress =
not (String.isEmpty kwd)
in
Parser <| \s ->
let
(newOffset, newRow, newCol) =
isSubString kwd s.offset s.row s.col s.src
in
if newOffset == -1 || 0 <= isSubChar (\c -> Char.isAlphaNum c || c == '_') newOffset s.src then
Bad False (fromState s expecting)
else
Good progress ()
{ src = s.src
, offset = newOffset
, indent = s.indent
, context = s.context
, row = newRow
, col = newCol
}
-- TOKEN
{-| With the simpler `Parser` module, you could just say `symbol ","` and
parse all the commas you wanted. But now that we have a custom type for our
problems, we actually have to specify that as well. So anywhere you just used
a `String` in the simpler module, you now use a `Token Problem` in the advanced
module:
type Problem
= ExpectingComma
| ExpectingListEnd
comma : Token Problem
comma =
Token "," ExpectingComma
listEnd : Token Problem
listEnd =
Token "]" ExpectingListEnd
You can be creative with your custom type. Maybe you want a lot of detail.
Maybe you want looser categories. It is a custom type. Do what makes sense for
you!
-}
type Token x = Token String x
{-| Just like [`Parser.token`](Parser#token) except you provide a `Token`
specifying your custom type of problems.
-}
token : Token x -> Parser c x ()
token (Token str expecting) =
let
progress =
not (String.isEmpty str)
in
Parser <| \s ->
let
(newOffset, newRow, newCol) =
isSubString str s.offset s.row s.col s.src
in
if newOffset == -1 then
Bad False (fromState s expecting)
else
Good progress ()
{ src = s.src
, offset = newOffset
, indent = s.indent
, context = s.context
, row = newRow
, col = newCol
}
-- INT
{-| Just like [`Parser.int`](Parser#int) where you have to handle negation
yourself. The only difference is that you provide a two potential problems:
int : x -> x -> Parser c x Int
int expecting invalid =
number
{ int = Ok identity
, hex = Err invalid
, octal = Err invalid
, binary = Err invalid
, float = Err invalid
, invalid = invalid
, expecting = expecting
}
You can use problems like `ExpectingInt` and `InvalidNumber`.
-}
int : x -> x -> Parser c x Int
int expecting invalid =
number
{ int = Ok identity
, hex = Err invalid
, octal = Err invalid
, binary = Err invalid
, float = Err invalid
, invalid = invalid
, expecting = expecting
}
-- FLOAT
{-| Just like [`Parser.float`](Parser#float) where you have to handle negation
yourself. The only difference is that you provide a two potential problems:
float : x -> x -> Parser c x Float
float expecting invalid =
number
{ int = Ok toFloat
, hex = Err invalid
, octal = Err invalid
, binary = Err invalid
, float = Ok identity
, invalid = invalid
, expecting = expecting
}
You can use problems like `ExpectingFloat` and `InvalidNumber`.
-}
float : x -> x -> Parser c x Float
float expecting invalid =
number
{ int = Ok toFloat
, hex = Err invalid
, octal = Err invalid
, binary = Err invalid
, float = Ok identity
, invalid = invalid
, expecting = expecting
}
-- NUMBER
{-| Just like [`Parser.number`](Parser#number) where you have to handle
negation yourself. The only difference is that you provide all the potential
problems.
-}
number
: { int : Result x (Int -> a)
, hex : Result x (Int -> a)
, octal : Result x (Int -> a)
, binary : Result x (Int -> a)
, float : Result x (Float -> a)
, invalid : x
, expecting : x
}
-> Parser c x a
number c =
Parser <| \s ->
if isAsciiCode 0x30 {- 0 -} s.offset s.src then
let
zeroOffset = s.offset + 1
baseOffset = zeroOffset + 1
in
if isAsciiCode 0x78 {- x -} zeroOffset s.src then
finalizeInt c.invalid c.hex baseOffset (consumeBase16 baseOffset s.src) s
else if isAsciiCode 0x6F {- o -} zeroOffset s.src then
finalizeInt c.invalid c.octal baseOffset (consumeBase 8 baseOffset s.src) s
else if isAsciiCode 0x62 {- b -} zeroOffset s.src then
finalizeInt c.invalid c.binary baseOffset (consumeBase 2 baseOffset s.src) s
else
finalizeFloat c.invalid c.expecting c.int c.float (zeroOffset, 0) s
else
finalizeFloat c.invalid c.expecting c.int c.float (consumeBase 10 s.offset s.src) s
consumeBase : Int -> Int -> String -> (Int, Int)
consumeBase =
Elm.Kernel.Parser.consumeBase
consumeBase16 : Int -> String -> (Int, Int)
consumeBase16 =
Elm.Kernel.Parser.consumeBase16
finalizeInt : x -> Result x (Int -> a) -> Int -> (Int, Int) -> State c -> PStep c x a
finalizeInt invalid handler startOffset (endOffset, n) s =
case handler of
Err x ->
Bad True (fromState s x)
Ok toValue ->
if startOffset == endOffset
then Bad (s.offset < startOffset) (fromState s invalid)
else Good True (toValue n) (bumpOffset endOffset s)
bumpOffset : Int -> State c -> State c
bumpOffset newOffset s =
{ src = s.src
, offset = newOffset
, indent = s.indent
, context = s.context
, row = s.row
, col = s.col + (newOffset - s.offset)
}
finalizeFloat : x -> x -> Result x (Int -> a) -> Result x (Float -> a) -> (Int, Int) -> State c -> PStep c x a
finalizeFloat invalid expecting intSettings floatSettings intPair s =
let
intOffset = Tuple.first intPair
floatOffset = consumeDotAndExp intOffset s.src
in
if floatOffset < 0 then
Bad True (fromInfo s.row (s.col - (floatOffset + s.offset)) invalid s.context)
else if s.offset == floatOffset then
Bad False (fromState s expecting)
else if intOffset == floatOffset then
finalizeInt invalid intSettings s.offset intPair s
else
case floatSettings of
Err x ->
Bad True (fromState s invalid)
Ok toValue ->
case String.toFloat (String.slice s.offset floatOffset s.src) of
Nothing -> Bad True (fromState s invalid)
Just n -> Good True (toValue n) (bumpOffset floatOffset s)
--
-- On a failure, returns negative index of problem.
--
consumeDotAndExp : Int -> String -> Int
consumeDotAndExp offset src =
if isAsciiCode 0x2E {- . -} offset src then
consumeExp (chompBase10 (offset + 1) src) src
else
consumeExp offset src
--
-- On a failure, returns negative index of problem.
--
consumeExp : Int -> String -> Int
consumeExp offset src =
if isAsciiCode 0x65 {- e -} offset src || isAsciiCode 0x45 {- E -} offset src then
let
eOffset = offset + 1
expOffset =
if isAsciiCode 0x2B {- + -} eOffset src || isAsciiCode 0x2D {- - -} eOffset src then
eOffset + 1
else
eOffset
newOffset = chompBase10 expOffset src
in
if expOffset == newOffset then
-newOffset
else
newOffset
else
offset
chompBase10 : Int -> String -> Int
chompBase10 =
Elm.Kernel.Parser.chompBase10
-- END
{-| Just like [`Parser.end`](Parser#end) except you provide the problem that
arises when the parser is not at the end of the input.
-}
end : x -> Parser c x ()
end x =
Parser <| \s ->
if String.length s.src == s.offset then
Good False () s
else
Bad False (fromState s x)
-- CHOMPED STRINGS
{-| Just like [`Parser.getChompedString`](Parser#getChompedString)
-}
getChompedString : Parser c x a -> Parser c x String
getChompedString parser =
mapChompedString always parser
{-| Just like [`Parser.mapChompedString`](Parser#mapChompedString)
-}
mapChompedString : (String -> a -> b) -> Parser c x a -> Parser c x b
mapChompedString func (Parser parse) =
Parser <| \s0 ->
case parse s0 of
Bad p x ->
Bad p x
Good p a s1 ->
Good p (func (String.slice s0.offset s1.offset s0.src) a) s1
-- CHOMP IF
{-| Just like [`Parser.chompIf`](Parser#chompIf) except you provide a problem
in case a character cannot be chomped.
-}
chompIf : (Char -> Bool) -> x -> Parser c x ()
chompIf isGood expecting =
Parser <| \s ->
let
newOffset = isSubChar isGood s.offset s.src
in
-- not found
if newOffset == -1 then
Bad False (fromState s expecting)
-- newline
else if newOffset == -2 then
Good True ()
{ src = s.src
, offset = s.offset + 1
, indent = s.indent
, context = s.context
, row = s.row + 1
, col = 1
}
-- found
else
Good True ()
{ src = s.src
, offset = newOffset
, indent = s.indent
, context = s.context
, row = s.row
, col = s.col + 1
}
-- CHOMP WHILE
{-| Just like [`Parser.chompWhile`](Parser#chompWhile)
-}
chompWhile : (Char -> Bool) -> Parser c x ()
chompWhile isGood =
Parser <| \s ->
chompWhileHelp isGood s.offset s.row s.col s
chompWhileHelp : (Char -> Bool) -> Int -> Int -> Int -> State c -> PStep c x ()
chompWhileHelp isGood offset row col s0 =
let
newOffset = isSubChar isGood offset s0.src
in
-- no match
if newOffset == -1 then
Good (s0.offset < offset) ()
{ src = s0.src
, offset = offset
, indent = s0.indent
, context = s0.context
, row = row
, col = col
}
-- matched a newline
else if newOffset == -2 then
chompWhileHelp isGood (offset + 1) (row + 1) 1 s0
-- normal match
else
chompWhileHelp isGood newOffset row (col + 1) s0
-- CHOMP UNTIL
{-| Just like [`Parser.chompUntil`](Parser#chompUntil) except you provide a
`Token` in case you chomp all the way to the end of the input without finding
what you need.
-}
chompUntil : Token x -> Parser c x ()
chompUntil (Token str expecting) =
Parser <| \s ->
let
(newOffset, newRow, newCol) =
findSubString str s.offset s.row s.col s.src
in
if newOffset == -1 then
Bad False (fromInfo newRow newCol expecting s.context)
else
Good (s.offset < newOffset) ()
{ src = s.src
, offset = newOffset
, indent = s.indent
, context = s.context
, row = newRow
, col = newCol
}
{-| Just like [`Parser.chompUntilEndOr`](Parser#chompUntilEndOr)
-}
chompUntilEndOr : String -> Parser c x ()
chompUntilEndOr str =
Parser <| \s ->
let
(newOffset, newRow, newCol) =
Elm.Kernel.Parser.findSubString str s.offset s.row s.col s.src
adjustedOffset =
if newOffset < 0 then String.length s.src else newOffset
in
Good (s.offset < adjustedOffset) ()
{ src = s.src
, offset = adjustedOffset
, indent = s.indent
, context = s.context
, row = newRow
, col = newCol
}
-- CONTEXT
{-| This is how you mark that you are in a certain context. For example, here
is a rough outline of some code that uses `inContext` to mark when you are
parsing a specific definition:
import Char
import Parser.Advanced exposing (..)
import Set
type Context
= Definition String
| List
definition : Parser Context Problem Expr
definition =
functionName
|> andThen definitionBody
definitionBody : String -> Parser Context Problem Expr
definitionBody name =
inContext (Definition name) <|
succeed (Function name)
|= arguments
|. symbol (Token "=" ExpectingEquals)
|= expression
functionName : Parser c Problem String
functionName =
variable
{ start = Char.isLower
, inner = Char.isAlphaNum
, reserved = Set.fromList ["let","in"]
, expecting = ExpectingFunctionName
}
First we parse the function name, and then we parse the rest of the definition.
Importantly, we call `inContext` so that any dead end that occurs in
`definitionBody` will get this extra context information. That way you can say
things like, “I was expecting an equals sign in the `view` definition.” Context!
-}
inContext : context -> Parser context x a -> Parser context x a
inContext context (Parser parse) =
Parser <| \s0 ->
case parse (changeContext (Located s0.row s0.col context :: s0.context) s0) of
Good p a s1 ->
Good p a (changeContext s0.context s1)
Bad _ _ as step ->
step
changeContext : List (Located c) -> State c -> State c
changeContext newContext s =
{ src = s.src
, offset = s.offset
, indent = s.indent
, context = newContext
, row = s.row
, col = s.col
}
-- INDENTATION
{-| Just like [`Parser.getIndent`](Parser#getIndent)
-}
getIndent : Parser c x Int
getIndent =