-
Notifications
You must be signed in to change notification settings - Fork 15
/
combinator.scala
571 lines (544 loc) · 33.4 KB
/
combinator.scala
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
/* SPDX-FileCopyrightText: © 2021 Parsley Contributors <https://github.com/j-mie6/Parsley/graphs/contributors>
* SPDX-License-Identifier: BSD-3-Clause
*/
package parsley.errors
import parsley.Parsley
import parsley.internal.deepembedding.{frontend, singletons}
import parsley.internal.errors.{CaretWidth, FlexibleCaret, RigidCaret}
/** This module contains combinators that can be used to directly influence error messages of parsers.
*
* Error messages are, by default, not ''particularly'' descriptive. However, the combinators in this
* module can be used to improve the generation of error messages by providing labels for expected
* items, explanations for why things went wrong, custom error messages, custom unexpected error messages,
* as well as correcting the offsets that error messages actually occurred at.
*
* @since 3.0.0
*
* @group combinators
*
* @groupprio fail 0
* @groupname fail Failure Combinators
* @groupdesc fail
* These combinator immediately fail the parser, with a more bespoke message.
*
* @groupprio adj 10
* @groupname adj Error Adjustment Combinators
* @groupdesc adj
* These combinators can affect at what position an error is caused at. They are
* opposites: where `amend` will ensure an error message is said to have generated
* at the position on entry to the combinator, `entrench` will resist these changes.
*
* @groupprio ext 5
* @groupname ext Error Extension Combinators
* @groupdesc ext
* These are implicit classes that, when in scope, enable additional combinators on
* parsers that interact with the error system in some way.
*/
object combinator {
private [parsley] def empty(caretWidth: Int): Parsley[Nothing] = new Parsley(singletons.Empty(caretWidth))
/** This combinator consumes no input and fails immediately with the given error messages.
*
* Produces a ''specialised'' error message where all the lines of the error are the
* given `msgs` in order of appearance.
*
* @example {{{
* val failing = fail("hello,", "this is an error message", "broken across multiple lines")
* }}}
*
* @param msg0 the first message in the error message.
* @param msgs the remaining messages that will make up the error message.
* @return a parser that fails producing an error message consisting of all the given messages.
* @since 3.0.0
* @group fail
*/
def fail(msg0: String, msgs: String*): Parsley[Nothing] = fail(new FlexibleCaret(1), msg0, msgs: _*)
/** This combinator consumes no input and fails immediately with the given error messages.
*
* Produces a ''specialised'' error message where all the lines of the error are the
* given `msgs` in order of appearance.
*
* @example {{{
* val failing = fail("hello,", "this is an error message", "broken across multiple lines")
* }}}
*
* @param caretWidth the size of the caret for this error: should ideally match the width of the cause of the error.
* @param msg0 the first message in the error message.
* @param msgs the remaining messages that will make up the error message.
* @return a parser that fails producing an error message consisting of all the given messages.
* @since 4.0.0
* @group fail
*/
def fail(caretWidth: Int, msg0: String, msgs: String*): Parsley[Nothing] = fail(new RigidCaret(caretWidth), msg0, msgs: _*)
private def fail(caretWidth: CaretWidth, msg0: String, msgs: String*): Parsley[Nothing] = new Parsley(new singletons.Fail(caretWidth, (msg0 +: msgs): _*))
/** This combinator consumes no input and fails immediately, setting the unexpected component
* to the given item.
*
* Produces a ''trivial'' error message where the unexpected component of the error is
* replaced with the given item `item`.
*
* @since 3.0.0
* @param item the unexpected message for the error generated.
* @return a parser that fails producing an error with `item` as the unexpected token.
* @group fail
*/
def unexpected(item: String): Parsley[Nothing] = unexpected(new FlexibleCaret(1), item)
/** This combinator consumes no input and fails immediately, setting the unexpected component
* to the given item.
*
* Produces a ''trivial'' error message where the unexpected component of the error is
* replaced with the given item `item`.
*
* @since 4.0.0
* @param caretWidth the size of the caret for this error: should ideally match the width of the cause of the error (the unexpected item).
* @param item the unexpected message for the error generated.
* @return a parser that fails producing an error with `item` as the unexpected token.
* @group fail
*/
def unexpected(caretWidth: Int, item: String): Parsley[Nothing] = unexpected(new RigidCaret(caretWidth), item)
private def unexpected(caretWidth: CaretWidth, item: String): Parsley[Nothing] = new Parsley(new singletons.Unexpected(item, caretWidth))
/** This combinator adjusts any error messages generated by the given parser so that they
* occur at the position recorded on entry to this combinator (effectively as if no
* input were consumed).
*
* This is useful if validation work is done
* on the output of a parser that may render it invalid, but the error should point to the
* beginning of the structure. This combinators effect can be cancelled with [[entrench `entrench`]].
*
* @param p a parser whose error messages should be adjusted.
* @return a parser that parses `p` but ensures any errors generated occur as if no input were consumed.
* @since 3.1.0
* @group adj
*/
def amend[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorAmend(p.internal, partial = false))
/** This combinator prevents the action of any enclosing `amend` on the errors generated by the given
* parser.
*
* Sometimes, the error adjustments performed by [[amend `amend`]] should only affect errors generated
* within a certain part of a parser and not the whole thing; in this case, `entrench` can be used
* to protect sub-parsers from having their errors adjusted, providing a much more fine-grained
* scope for error adjustment.
*
* @example In this example, the `ident` parser should not allow keywords, and these error messages
* should be generated from the start of the identifier, not the end. However any errors generated
* ''within'' the identifier itself should remain at their regular offsets.
*
* {{{
* val ident = amend {
* entrench(stringOfSome(letter)).filterOut {
* case v if keywords.contains(v) => s"keyword $v cannot be an identifier"
* }
* }
* }}}
*
* @param p a parser whose error messages should not be adjusted by any surrounding [[amend `amend`]].
* @return a parser that parses `p` but ensures any error messages are generated normally.
* @since 3.1.0
* @group adj
*/
def entrench[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorEntrench(p.internal))
/** This combinator undoes the action of the `entrench` combinator on the given parser.
*
* Entrenchment is important for preventing the incorrect amendment of certain parts of sub-errors
* for a parser, but it may be then undesireable to block further amendments from elsewhere in the
* parser. This combinator can be used to cancel and entrenchment after the critical section has
* passed.
*
* @param p a parser that should no longer be under the affect of an `entrench` combinator
* @return a parser that parses `p` and allows its error messages to be amended.
* @since 4.2.0
* @group adj
*/
def dislodge[A](p: Parsley[A]): Parsley[A] = dislodge(Int.MaxValue)(p)
private def dislodge[A](by: Int)(p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorDislodge(by, p.internal))
/** This combinator first tries to amend the position of any error generated by the given parser,
* and if the error was entrenched will dislodge it instead.
*
* @param p a parser whose error messages should be amended unless its been entrenched.
* @return a parser that parses `p` but ensures any errors generated occur as if no input were consumed.
* @since 4.2.0
* @see [[amend `amend`]] and `[[dislodge `dislodge`]]
* @group adj
*/
def amendThenDislodge[A](p: Parsley[A]): Parsley[A] = dislodge(amend(p))
// TODO: test, document, and expose :)
private [parsley] def partialAmend[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorAmend(p.internal, partial = true))
private [parsley] def partialAmendThenDislodge[A](p: Parsley[A]): Parsley[A] = dislodge(partialAmend(p))
/** This combinator marks any errors within the given parser as being ''lexical errors''.
*
* When an error is marked as a ''lexical error'', it sets a flag within the error that is
* passed to [[parsley.errors.ErrorBuilder.unexpectedToken `ErrorBuilder.unexpectedToken`]]: this
* should be used to prevent `Lexer`-based token extraction from being performed on an error,
* since lexing errors cannot be the result of unexpected tokens.
*
* @param p the parser that serves as a token.
* @return a parser that parses `p` but ensures any error messages are marked as lexical errors.
* @since 4.0.0
* @group adj
*/
def markAsToken[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorLexical(p.internal))
/** This class exposes helpful combinators that are specialised for generating more helpful errors messages.
*
* This extension class operates on values that are convertible to parsers. It enables the use of
* error combinators, which can be used for data validation, error annotation, or immediate failing.
*
* @constructor This constructor should not be called manually, it is designed to be used via Scala's implicit resolution.
* @param p the value that this class is enabling methods on.
* @param con a conversion that allows values convertible to parsers to be used.
* @tparam P the type of base value that this class is used on (the conversion to `Parsley`) is summoned automatically.
* @version 3.0.0
* @group ext
*
* @groupprio rich 0
* @groupname rich Error Enrichment Combinators
* @groupdesc rich
* These combinators add additional information - or refine the existing information within - to
* an error message that has been generated within the scope of the parser they have been called on.
* These are a very basic, but effective, way of improving the quality of error messages generated
* by Parsley.
*
* @groupprio filter 10
* @groupname filter Filtering Combinators
* @groupdesc filter
* These combinators perform filtering on a parser, with particular emphasis on generating meaningful
* error messages if the filtering fails. This is particularly useful for data validation within the
* parser, as very instructive error messages describing what went wrong can be generated. These combinators
* often filter using a `PartialFunction`: this may be because they combine filtering with mapping (in which
* case, the error message is provided separately), or the function may produce a `String`.
*
* In these cases, the partial function is producing the error messages: if the input to the function is
* defined, this means that it is invalid and the filtering will fail using the message obtained from the
* succesful partial function invocation.
*
* @groupprio fail 20
*
* @define observably
* *a parser is said to ''observably'' consume input when error messages generated by a parser `p` occur at a deeper
* offset than `p` originally started at. While this sounds like it is the same as "having consumed input" for the
* purposes of backtracking, they are disjoint concepts:
*
* 1. in `attempt(p)`, `p` can ''observably'' consume input even though the wider parser does not consume input due to the `attempt`.
* 1. in `amend(p)`, `p` can consume input and may not backtrack even though the consumption is not ''observable'' in the error
* message due to the `amend`.
*
* @define autoAmend
* when this combinator fails (and not this parser itself), it will generate errors rooted at the start of the
* parse (as if [[parsley.errors.combinator$.amend `amend`]] had been used) and the caret will span the entire
* successful parse of this parser.
*
* @define partialAmend
* this combinator will generate error messages rooted at the start of the previously successful parse of this
* parser, but only in terms of their position: the actual error is generated at the end of the parse, which
* means it takes priority over sibling errors. This is because the error concerns the whole parse (for caret)
* and morally starts where this parser started (as it caused the failure), however, if it had full `amend`-like
* behaviour these errors would often disappear.
*/
implicit final class ErrorMethods[P, +A](p: P)(implicit con: P => Parsley[A]) {
/** This combinator filters the result of this parser using the given partial-predicate, succeeding only when the predicate is undefined.
*
* First, parse this parser. If it succeeds then take its result `x` and test if `pred.isDefinedAt(x)` is true. If it is
* false, the parser succeeds, returning `x`. Otherwise, `pred(x)` will yield a reason `reason` and the parser will
* fail with `reason` provided to the generated error message à la [[explain `explain`]].
*
* This is useful for performing data validation, but where a definitive reason can be given for the failure. In this instance,
* the rest of the error message is generated as normal, with the expected and unexpected components still given, along with
* any other generated reasons.
*
* @example {{{
* scala> import parsley.character.letter
* scala> val keywords = Set("if", "then", "else")
* scala> val ident = stringOfSome(letter).filterOut {
* case v if keywords.contains(v) => s"keyword $v cannot be an identifier"
* }
* scala> ident.parse("hello")
* val res0 = Success("hello")
* scala> ident.parse("if")
* val res1 = Failure(..)
* }}}
*
* @since 3.0.0
* @param pred the predicate that is tested against the parser result, which also generates errors.
* @return a parser that returns the result of this parser if it fails the predicate.
* @see [[parsley.Parsley.filterNot `filterNot`]], which is a basic version of this same combinator with no customised reason.
* @see [[guardAgainst `guardAgainst`]], which is similar to `filterOut`, except it generates a ''specialised'' error as opposed to just a reason.
* @note $autoAmend
* @group filter
*/
def filterOut(pred: PartialFunction[A, String]): Parsley[A] = new Parsley(new frontend.FilterOut(con(p).internal, pred))
/** This combinator filters the result of this parser using the given partial-predicate, succeeding only when the predicate is undefined.
*
* First, parse this parser. If it succeeds then take its result `x` and test of `pred.isDefinedAt(x)` is true. If it is false,
* the parser succeeds, returning `x`. Otherwise `pred(x)` will yield an error message `msg` and the parser will fail, producing
* a ''specialised'' error only consisting of the message `msg` à la [[fail(caretWidth:Int,msg0:String,msgs:String*)* `fail`]].
*
* This is useful for performing data validation, but where failure is not tied to the grammar but some other property of
* the results. For instance, with the identifier example given for `filterOut`, it is reasonable to suggest that an identifier
* was expected, and a keyword is not a valid identifier: i.e. these components still make sense. Where `guardAgainst` shines,
* however, is in scenarios where the expected alternatives, or the unexpected component itself distract from the cause of the
* error, or are irrelevant in some way. This might be because `guardAgainst` is checking some property of the data that is
* ''possible'' to encode in the grammar, but otherwise ''impractical'', either because it is hard to maintain or generates
* poor error messages for the user.
*
* @example Suppose we are parsing a data-format for graphs, and a restriction has been placed that ensures that the
* numeric identifiers of each declared node must be ordered. This has, for whatever reason, been specified
* as a syntactic property of the data. This is possible to encode using context-sensitive parsing (since each
* new node can only be parsed according to the previous one), but is fairly difficult and impractical. Instead,
* when all the declarations have been read, a `guardAgainst` can be used to prevent mis-ordering:
* {{{
* val node = integer
* val nodes = many(node).guardAgainst {
* case ns if ns.nonEmpty
* && ns.zip(ns.tail).exists { case (x, y) => x == y } =>
* val Some((x, _)) = ns.zip(ns.tail).find { case (x, y) => x == y }
* Seq(s"node $x has been declared twice")
* case ns if ns.nonEmpty
* && ns.zip(ns.tail).exists { case (x, y) => x > y } =>
* val Some((x, y)) = ns.zip(ns.tail).find { case (x, y) => x > y }
* Seq(s"nodes $x and $y are declared in the wrong order", "all nodes should be ordered")
* }
* }}}
*
* @since 4.0.0
* @param pred the predicate that is tested against the parser result, which also generates errors.
* @return a parser that returns the result of this parser if it fails the predicate.
* @see [[parsley.Parsley.filterNot `filterNot`]], which is a basic version of this same combinator with no customised error message.
* @see [[filterOut `filterOut`]], which is similar to `guardAgainst`, except it generates a reason for failure and not a ''specialised'' error.
* @see [[[collectMsg[B](msggen:A=>Seq[String])* `collectMsg`]]], which is similar to `guardAgainst`, but can also transform the data on success.
* @note $autoAmend
* @group filter
*/
def guardAgainst(pred: PartialFunction[A, Seq[String]]): Parsley[A] = new Parsley(new frontend.GuardAgainst(con(p).internal, pred))
/** This combinator applies a partial function `pf` to the result of this parser if its result is defined for `pf`, failing if it is not.
*
* First, parse this parser. If it succeeds, test whether its result `x` is in the domain of the partial function `pf`. If it is defined for
* `pf`, return `pf(x)`. Otherwise, if the result was undefined then fail producing a ''specialised'' error message with `msg`. Equivalent
* to a `guardAgainst` (whose `msggen` ignores its argument) followed by a `map`.
*
* @example A good example of this combinator in use is for handling overflow in numeric literals.
* {{{
* val integer: Parsley[BigInt] = ...
* // this should be amended/entrenched for best results
* val int16: Parsley[Short] =
* integer.collectMsg("integer literal should within the range -2^16 to +2^16-1") {
* case x if x >= Short.MinValue
* && x <= Short.MaxValue => x.toShort
* }
* }}}
*
* @since 3.0.0
* @param msg0 the first error message to use if the filtering fails.
* @param msgs the remaining error messages to use if the filtering fails.
* @param pf the partial function used to both filter the result of this parser and transform it.
* @return a parser which returns the result of this parser applied to pf, if possible.
* @see [[parsley.Parsley.collect `collect`]], which is a basic version of this same combinator with no customised error message.
* @see [[guardAgainst `guardAgainst`]], which is similar to `collectMsg`, except it does not transform the data.
* @note $autoAmend
* @group filter
*/
def collectMsg[B](msg0: String, msgs: String*)(pf: PartialFunction[A, B]): Parsley[B] = this.collectMsg(_ => msg0 +: msgs)(pf)
/** This combinator applies a partial function `pf` to the result of this parser if its result is defined for `pf`, failing if it is not.
*
* First, parse this parser. If it succeeds, test whether its result `x` is in the domain of the partial function `pf`. If it is defined for
* `pf`, return `pf(x)`. Otherwise, if the result was undefined then fail producing a ''specialised'' error message with `msggen(x)`. Equivalent
* to a `guardAgainst` followed by a `map`.
*
* @example A good example of this combinator in use is for handling overflow in numeric literals.
* {{{
* val integer: Parsley[BigInt] = ...
* // this should be amended/entrenched for best results
* val int16: Parsley[Short] =
* integer.collectMsg(n => Seq(s"integer literal $n is not within the range -2^16 to +2^16-1")) {
* case x if x >= Short.MinValue
* && x <= Short.MaxValue => x.toShort
* }
* }}}
*
* @since 4.0.0
* @param msggen a function that generates the error messages to use if the filtering fails.
* @param pf the partial function used to both filter the result of this parser and transform it.
* @return a parser which returns the result of this parser applied to pf, if possible.
* @see [[parsley.Parsley.collect `collect`]], which is a basic version of this same combinator with no customised error message.
* @see [[guardAgainst `guardAgainst`]], which is similar to `collectMsg`, except it does not transform the data.
* @note $autoAmend
* @group filter
*/
def collectMsg[B](msggen: A => Seq[String])(pf: PartialFunction[A, B]): Parsley[B] = {
this.guardAgainst{case x if !pf.isDefinedAt(x) => msggen(x)}.map(pf)
}
private def _unexpectedWhen(pred: PartialFunction[A, (String, Option[String])]): Parsley[A] = {
new Parsley(new frontend.UnexpectedWhen(con(p).internal, pred))
}
/** This combinator filters the result of this parser using the given partial-predicate, succeeding only when the predicate is undefined.
*
* First, parse this parser. If it succeeds then take its result `x` and test if `pred.isDefinedAt(x)` is true. If it is
* false, the parser succeeds, returning `x`. Otherwise, `pred(x)` will yield a unexpected label and the parser will
* fail using [[combinator.unexpected(caretWidth:Int,item:String)* `unexpected`]] and that label.
*
* This is useful for performing data validation, but where a the failure results in the entire token being unexpected. In this instance,
* the rest of the error message is generated as normal, with the expected components still given, along with
* any generated reasons.
*
* @example {{{
* scala> import parsley.character.letter
* scala> val keywords = Set("if", "then", "else")
* scala> val ident = stringOfSome(letter).unexpectedWhen {
* case v if keywords.contains(v) => s"keyword $v"
* }
* scala> ident.parse("hello")
* val res0 = Success("hello")
* scala> ident.parse("if")
* val res1 = Failure(..)
* }}}
*
* @since 3.0.0
* @param pred the predicate that is tested against the parser result, which also generates errors.
* @return a parser that returns the result of this parser if it fails the predicate.
* @see [[parsley.Parsley.filterNot `filterNot`]], which is a basic version of this same combinator with no unexpected message.
* @see [[filterOut `filterOut`]], which is a variant that produces a reason for failure as opposed to an unexpected message.
* @see [[guardAgainst `guardAgainst`]], which is similar to `unexpectedWhen`, except it generates a ''specialised'' error instead.
* @see [[unexpectedWithReasonWhen `unexpectedWithReasonWhen`]], which is similar, but also has a reason associated.
* @note $autoAmend
* @group filter
*/
def unexpectedWhen(pred: PartialFunction[A, String]): Parsley[A] = this._unexpectedWhen {
case x if pred.isDefinedAt(x) => (pred(x), None)
}
/** This combinator filters the result of this parser using the given partial-predicate, succeeding only when the predicate is undefined.
*
* First, parse this parser. If it succeeds then take its result `x` and test if `pred.isDefinedAt(x)` is true. If it is
* false, the parser succeeds, returning `x`. Otherwise, `pred(x)` will yield a unexpected label and the parser will
* fail using [[combinator.unexpected(caretWidth:Int,item:String)* `unexpected`]] and that label as well as a reason.
*
* This is useful for performing data validation, but where a the failure results in the entire token being unexpected. In this instance,
* the rest of the error message is generated as normal, with the expected components still given, along with
* any generated reasons.
*
* @example {{{
* scala> import parsley.character.letter
* scala> val keywords = Set("if", "then", "else")
* scala> val ident = stringOfSome(letter).unexpectedWhenWithReason {
* case v if keywords.contains(v) => (s"keyword $v", "keywords cannot be identifiers")
* }
* scala> ident.parse("hello")
* val res0 = Success("hello")
* scala> ident.parse("if")
* val res1 = Failure(..)
* }}}
*
* @param pred the predicate that is tested against the parser result, which also generates errors.
* @return a parser that returns the result of this parser if it fails the predicate.
* @see [[parsley.Parsley.filterNot `filterNot`]], which is a basic version of this same combinator with no unexpected message or reason.
* @see [[filterOut `filterOut`]], which is a variant that just produces a reason for failure with no unexpected message.
* @see [[guardAgainst `guardAgainst`]], which is similar to `unexpectedWhen`, except it generates a ''specialised'' error instead.
* @see [[unexpectedWhen `unexpectedWhen`]], which is similar, but with no associated reason.
* @since 4.2.0
* @group filter
*/
def unexpectedWithReasonWhen(pred: PartialFunction[A, (String, String)]): Parsley[A] = this._unexpectedWhen {
case x if pred.isDefinedAt(x) =>
val (unex, reason) = pred(x)
(unex, Some(reason))
}
/** This combinator changes the expected component of any errors generated by this parser.
*
* When this parser fails having not ''observably''* consumed input, the expected component of the generated
* error message is set to be the given `item`.
*
* $observably
* @param item the name to give to the expected component of any qualifying errors.
* @return a parser that expects `item` on failure.
* @since 3.0.0
* @group rich
*/
def label(item: String): Parsley[A] = if (item.isEmpty) this.hide else this.labels(item)//new Parsley(new frontend.ErrorLabel(con(p).internal, item))
/** This combinator changes the expected component of any errors generated by this parser.
*
* This is just an alias for the `label` combinator.
*
* ''Known as `<?>` in Haskell.''
*
* @since 3.0.0
* @see [[label `label`]]
* @group rich
*/
def ?(item: String): Parsley[A] = this.label(item)
private [parsley] def labels(items: String*): Parsley[A] = {
require(items.forall(_.nonEmpty), "Labels cannot be empty strings")
new Parsley(new frontend.ErrorLabel(con(p).internal, items))
}
/** This combinator adds a reason to error messages generated by this parser.
*
* When this parser fails having not ''observably''* consumed input, this combinator adds
* `reason` to the error message, which should justify why the error occured. Unlike error
* labels, which may persist if more progress is made having not consumed input, reasons
* are not carried forward in the error message, and are lost.
*
* $observably
* @param reason the reason why a parser failed.
* @return a parser that produces the given reason for failure if it fails.
* @since 3.0.0
* @group rich
*/
def explain(reason: String): Parsley[A] = {
require(reason.nonEmpty, "Reasons cannot be empty strings")
new Parsley(new frontend.ErrorExplain(con(p).internal, reason))
}
/** This combinator hides the expected component of errors generated by this parser.
*
* When this parser fails having not ''observably''* consumed input, this combinator
* hides any error labels assigned to the expected item by any `label` combinators,
* or indeed the base raw labels produced by the input consuming combinators themselves.
*
* This can be useful, say, for hiding whitespace labels, which are not normally useful
* information to include in an error message for whitespace insensitive grammars.
*
* $observably
* @since 3.0.0
* @return a parser that does not produce an expected component on failure.
* @see [[label `label`]]
* @group rich
*/
def hide: Parsley[A] = this.labels()
// $COVERAGE-OFF$
/** This combinator parses this parser and then fails, using the result of this parser to customise the error message.
*
* Similar to `fail`, but first parses this parser: if it succeeded, then its result `x` is used to form the error
* message for the `fail` combinator by calling `msggen(x)`. If this parser fails, however, its error message will
* be generated instead.
*
* @param msggen the generator function for error message, creating a message based on the result of this parser.
* @return a parser that always fails, with the given generator used to produce the error message if this parser succeeded.
* @note $partialAmend
* @group fail
* @deprecated this combinator has not proven to be particularly useful, and will be replaced by a more appropriate,
* not exactly the same, `verifiedFail` combinator.
*/
@deprecated("This combinator will be removed in 5.0.0, without direct replacement", "4.2.0")
def !(msggen: A => String): Parsley[Nothing] = partialAmendThenDislodge {
parsley.position.internalOffsetSpan(entrench(con(p))).flatMap { case (os, x, oe) =>
combinator.fail(oe - os, msggen(x))
}
}
/** This combinator parses this parser and then fails, using the result of this parser to customise the unexpected component
* of the error message.
*
* Similar to `unexpected`, but first parses this parser: if it succeeded, then its result `x` is used to form
* the unexpected component of the generated error by calling `msggen(x)`. If this parser fails, however,
* its error message will be returned untouched.
*
* @param msggen the generator function for error message, creating a message based on the result of this parser.
* @return a parser that always fails, with the given generator used to produce an unexpected message if this parser succeeded.
* @note $partialAmend
* @group fail
* @deprecated this combinator has not proven to be particularly useful and will be removed in 5.0.0. There is a similar, but not
* exact replacement called `verifiedUnexpected`.
* @since 4.2.0
*/
@deprecated("This combinator will be removed in 5.0.0, without direct replacement", "4.2.0")
def unexpected(msggen: A => String): Parsley[Nothing] = partialAmendThenDislodge {
parsley.position.internalOffsetSpan(entrench(con(p))).flatMap { case (os, x, oe) =>
combinator.unexpected(oe - os, msggen(x))
}
}
// $COVERAGE-ON$
}
}