All Downloads are FREE. Search and download functionalities are using the official Maven repository.

parsley.errors.combinator.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 Parsley 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 {
    /** 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`]].
      *
      * @example {{{
      * scala> val greeting = string("hello world") <* char('!')
      * scala> greeting.label("greeting").parse("hello world.")
      * val res0 = Failure((line 1, column 12):
      *   unexpected "."
      *   expected "!"
      *   >hello world.
      *               ^)
      * scala> amend(greeting).label("greeting").parse("hello world.")
      * val res1 = Failure((line 1, column 1):
      *   unexpected "h"
      *   expected greeting
      *   >hello world.
      *    ^)
      * }}}
      *
      * @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 adjusts any error messages generated by the given parser so that they
      * occur at the position recorded on entry to this combinator, but retains the original offset.
      *
      * Similar to [[amend `amend`]], but retains the original offset the error occurred at. This is known
      * as its ''underlying offset'' as opposed to the visual ''presentation offset''. To the reader, the
      * error messages appears as if no input was consumed, but for the purposes of error message merging
      * the error is still deeper. A key thing to note is that two errors can only merge if they are at
      * the same presentation ''and'' underlying offsets: if they are not the deeper of the two ''dominates''.
      *
      * The ability for an error to still dominate others after partial amendment can be useful for allowing
      * it to avoid being lost when merging with errors that are deeper than the presentation offset but
      * shallower than the underlying.
      *
      * @example {{{
      * scala> val greeting = string("hello world") <* char('!')
      * scala> val shortGreeting = string("h") <* (char('i') | string("ey")) <* char('!')
      * // here, the shortGreeting, despite not getting as far into the input is dominating the amended long greeting
      * scala> (amend(atomic(greeting)).label("hello world!") | shortGreeting).parse("hello world.")
      * val res0 = Failure((line 1, column 2):
      *   unexpected "el"
      *   expected "ey" or "i"
      *   >hello world.
      *     ^^)
      * // here it appears to start at the `h` point, but notably dominates the short greeting
      * scala> (amend(atomic(greeting)).label("hello world!") | shortGreeting).parse("hello world.")
      * val res1= Failure((line 1, column 1):
      *   unexpected "h"
      *   expected hello world!
      *   >hello world.
      *    ^)
      * }}}
      *
      * @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 4.4.0
      * @group adj
      */
    def partialAmend[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorAmend(p.internal, partial = true))

    /** 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"
      *     }
      * }
      * }}}
      *
      * '''In reality though, `filterOut` has an `amend` and `entrench` built into it.'''
      *
      * @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 any `entrench` combinators 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 all 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)
    /** This combinator undoes the action of `by` many `entrench` combinators 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 several, but potentially not all entrenchments after the
      * critical section has passed.
      *
      * @param by the number of entrenchments to undo
      * @param p a parser that should no longer be under the affect of an `entrench` combinator
      * @return a parser that parses `p` and ''may'' allows its error messages to be amended if all entrenchments are undone
      * @since 4.4.0
      * @group adj
      */
    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[A](p:parsley\.Parsley[A])* `dislodge`]]
      * @group adj
      */
    def amendThenDislodge[A](p: Parsley[A]): Parsley[A] = amendThenDislodge(Int.MaxValue)(p)
    /** 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 `by` many times 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.4.0
      * @see [[amend `amend`]] and [[dislodge[A](by:Int)* `dislodge`]]
      * @group adj
      */
    def amendThenDislodge[A](by: Int)(p: Parsley[A]): Parsley[A] = dislodge(by)(amend(p))

    // These don't need coverage really, they are basically the same as the ones above
    // $COVERAGE-OFF$
    /** This combinator first tries to partially 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.4.0
      * @see [[partialAmend `partialAmend`]] and [[dislodge[A](p:parsley\.Parsley[A])* `dislodge`]]
      * @group adj
      */
    def partialAmendThenDislodge[A](p: Parsley[A]): Parsley[A] = partialAmendThenDislodge(Int.MaxValue)(p)
    /** This combinator first tries to partially amend the position of any error generated by the given parser,
      * and if the error was entrenched will dislodge it `by` many times 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.4.0
      * @see [[partialAmend `partialAmend`]] and [[dislodge[A](by:Int)* `dislodge`]]
      * @group adj
      */
    def partialAmendThenDislodge[A](by: Int)(p: Parsley[A]): Parsley[A] = dislodge(by)(partialAmend(p))
    // $COVERAGE-ON$

    /** 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 genFilter 15
      * @groupname genFilter Generic Filtering Combinators
      * @groupdesc genFilter
      *     This combinators generalise the combinators from above, which are all special cases of them. Each of these
      *     takes the characteristic predicate or function of the regular variants, but takes an `errGen` object that
      *     can be used to fine-tune the error messages. These offer some flexiblity not offered by the specialised
      *     filtering combinators, but are a little more verbose to use.
      *
      * @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 `atomic(p)`, `p` can ''observably'' consume input even though the wider parser does not consume input due to the `atomic`.
      *       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 implemented in terms of [[filterWith `filterWith`]].
          * @note $autoAmend
          * @group filter
          */
        def filterOut(pred: PartialFunction[A, String]): Parsley[A] = {
            this.filterWith(new VanillaGen[A] {
                override def reason(x: A) = Some(pred(x))
            })(!pred.isDefinedAt(_))
        }

        /** 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
          * @note implemented in terms of [[filterWith `filterWith`]].
          * @group filter
          */
        def guardAgainst(pred: PartialFunction[A, Seq[String]]): Parsley[A] = {
            this.filterWith(new SpecializedGen[A] {
                override def messages(x: A) = pred(x)
            })(!pred.isDefinedAt(_))
        }

        /** 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
          * @note implemented in terms of [[collectWith `collectWith`]].
          * @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.
          * @see [[mapFilterMsg `mapFilterMsg`]], which is similar to `collectMsg`, except uses a `A => Either[Seq[String], B]` function.
          * @note $autoAmend
          * @note implemented in terms of [[collectWith `collectWith`]].
          * @group filter
          */
        def collectMsg[B](msggen: A => Seq[String])(pf: PartialFunction[A, B]): Parsley[B] = {
            this.collectWith(new SpecializedGen[A] {
                override def messages(x: A) = msggen(x)
            })(pf)
        }

        /** This combinator conditionally transforms the result of this parser with a given function, if a `Left` is
          * returned generates an error with its contexts, otherwise results the result inside the `Right`.
          *
          * Like [[Parsley.mapFilter `mapFilter`]], except allows for the error message generated to be
          * specified for invalid parses.
          *
          * @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.filterWithMsg {
          *         case x if x >= Short.MinValue
          *                && x <= Short.MaxValue => Right(x.toShort)
          *         case x => Left(Seq(s"integer literal $n is not within the range -2^16 to +2^16-1"))
          *     }
          * }}}
          *
          * @since 5.0.0
          * @param f the predicate that is tested against the parser result.
          * @return a parser which returns the result of this parser applied to pf, if possible.
          * @see [[parsley.Parsley.mapFilter `mapFilter`]], which is a basic version of this same combinator with no customised error message.
          * @note $autoAmend
          * @note implemented in terms of [[mapFilterWith `mapFilterWith`]].
          * @group filter
          */
        def mapFilterMsg[B](f: A => Either[Seq[String], B]): Parsley[B] = {
            this.mapFilterWith(new SpecializedGen[A] {
                override def messages(x: A) = {
                    val Left(errs) = f(x): @unchecked
                    errs
                }
            })(x => f(x).toOption)
        }

        /** 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
          * @note implemented in terms of [[filterWith `filterWith`]].
          * @group filter
          */
        def unexpectedWhen(pred: PartialFunction[A, String]): Parsley[A] = {
            this.filterWith(new VanillaGen[A] {
                override def unexpected(x: A) = VanillaGen.NamedItem(pred(x))
            })(!pred.isDefinedAt(_))
        }

        /** 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
          * @note implemented in terms of [[filterWith `filterWith`]].
          * @group filter
          */
        def unexpectedWithReasonWhen(pred: PartialFunction[A, (String, String)]): Parsley[A] = {
            this.filterWith(new VanillaGen[A] {
                override def unexpected(x: A) = VanillaGen.NamedItem(pred(x)._1)
                override def reason(x: A) = Some(pred(x)._2)
            })(!pred.isDefinedAt(_))
        }

        /** 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.
          * @param items any further labels to assign to this parser.
          * @return a parser that expects `item` on failure.
          * @since 3.0.0
          * @group rich
          */
        def label(item: String, items: String*): Parsley[A] = {
            require(item.nonEmpty && items.forall(_.nonEmpty), "labels cannot be empty strings")
            new Parsley(new frontend.ErrorLabel(con(p).internal, item, items))
        }

        /** 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)

        /** 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))
        }

        // TODO: check this documentation, I'm not sure it's correct
        /** 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.
          * @group rich
          */
        def hide: Parsley[A] = new Parsley(new frontend.ErrorHide(con(p).internal))

        /** This combinator filters the result of this parser with the given predicate, generating an error with the
          * given error generator if the function returned `false`.
          *
          * Like [[Parsley.filter `filter`]], except allows for the error message generated to be fine-tuned with
          * respect to the parsers result and width of input consumed using an [[ErrorGen `ErrorGen`]] object.
          *
          * @param errGen how to generate error messages based on the result of this parser.
          * @param pred the predicate that is tested against the parser result.
          * @since 4.4.0
          * @group genFilter
          */
        def filterWith(errGen: ErrorGen[A])(pred: A => Boolean): Parsley[A] = combinator.filterWith(con(p))(pred, errGen)

        /** This combinator conditionally transforms the result of this parser with a given partial function, generating an error with the
          * given error generator if the function is not defined on the result of this parser.
          *
          * Like [[Parsley.collect `collect`]], except allows for the error message generated to be fine-tuned with
          * respect to the parsers result and width of input consumed using an [[ErrorGen `ErrorGen`]] object.
          *
          * @param pf the partial function used to both filter the result of this parser and transform it.
          * @param errGen how to generate error messages based on the result of this parser.
          * @since 4.4.0
          * @group genFilter
          */
        def collectWith[B](errGen: ErrorGen[A])(pf: PartialFunction[A, B]): Parsley[B] = combinator.collectWith(con(p))(pf, errGen)

        /** This combinator conditionally transforms the result of this parser with a given function, generating an error with the
          * given error generator if the function returns `None` given the result of this parser.
          *
          * Like [[Parsley.mapFilter `mapFilter`]], except allows for the error message generated to be fine-tuned with
          * respect to the parsers result and width of input consumed using an [[ErrorGen `ErrorGen`]] object.
          *
          * @param f the function used to both filter the result of this parser and transform it.
          * @param errGen how to generate error messages based on the result of this parser.
          * @since 4.4.0
          * @group genFilter
          */
        def mapFilterWith[B](errGen: ErrorGen[A])(f: A => Option[B]): Parsley[B] = combinator.mapFilterWith(con(p))(f, errGen)
    }

    @inline private [parsley] def filterWith[A](p: Parsley[A])(f: A => Boolean, err: ErrorGen[A]): Parsley[A] = {
        new Parsley(new frontend.Filter(p.internal, f, err.internal))
    }

    @inline private [parsley] def collectWith[A, B](p: Parsley[A])(f: PartialFunction[A, B], err: ErrorGen[A]): Parsley[B] = {
        mapFilterWith(p)(f.lift, err)
    }

    @inline private [parsley] def mapFilterWith[A, B](p: Parsley[A])(f: A => Option[B], err: ErrorGen[A]): Parsley[B] = {
        new Parsley(new frontend.MapFilter(p.internal, f, err.internal))
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy