Error Message Patterns

The combinators discussed in parsley.errors.combinator are useful for changing the contents of an error message, but they do not account for any contextual obligations a more carefully crafted error might have. Ensuring that error messages are valid in the context in which they occurred is the job of the Verified Errors and Preventative Errors parsing design patterns. These are both encoded in parsley as combinators within parsley.errors.patterns.

The Scaladoc for this page can be found at parsley.errors.patterns.

Verified Errors

When a hand-tailored reason for a syntax error relies on some future input being present, a verified error can be used to ensure these obligations are met. These errors, called error widgets, will have the following properties:

The "problematic parser" referred to above is what will be parsed to try and verify that the requirements for the error messages are met. Any widget that meets these five properties is likely already a verified error, however, they can be satisfied by the combinators enabled by importing parsley.errors.patterns.VerifiedErrors -- this is the focus of this page.

Each of the verifiedX combinators try a parse something, and if it succeeds will generate an error based on the result. If it could not be parsed, they will generate an empty error. Each different combinator will generate an error with different content. As examples (in isolation of surrounding content):

import parsley.errors.patterns.VerifiedErrors
import parsley.character.char

// assume that a `lexer` is available
val float = lexer.nonlexeme.floating.decimal
// float: parsley.Parsley[BigDecimal] = parsley.Parsley@66f6199
val _noFloat =
    float.verifiedExplain("floating-point values may not be used as array indices")
// _noFloat: parsley.Parsley[Nothing] = parsley.Parsley@2b7aaedb

_noFloat.parse("hello")
// res0: parsley.Result[String, Nothing] = Failure((line 1, column 1):
//   unknown parse error
//   >hello
//    )
_noFloat.parse("3.142")
// res1: parsley.Result[String, Nothing] = Failure((line 1, column 1):
//   unexpected "3.142"
//   floating-point values may not be used as array indices
//   >3.142
//    ^^^^^)

val int = lexer.nonlexeme.unsigned.decimal
// int: parsley.Parsley[BigInt] = parsley.Parsley@515ec88e
val _noPlus = (char('+') ~> int).verifiedFail { n =>
    Seq(s"the number $n may not be preceeded by \"+\"")
}
// _noPlus: parsley.Parsley[Nothing] = parsley.Parsley@b9381b1
_noPlus.parse("+10")
// res2: parsley.Result[String, Nothing] = Failure((line 1, column 1):
//   the number 10 may not be preceeded by "+"
//   >+10
//    ^^^)

Occasionally, there may need to be more fine-grained control over the errors. In these cases, the most generic version of the combinator is verifiedWith, which takes an ErrorGen object. For instance, perhaps the focus of the last error above should only be the +. In which case, the caret must be adjusted by an ErrorGen object -- this can either be VanillaGen or SpecialisedGen.

import parsley.errors.SpecialisedGen
val _noPlus = (char('+') ~> int).verifiedWith {
    new SpecialisedGen[BigInt] {
        def messages(n: BigInt): Seq[String] =
            Seq("a number may not be preceeded by \"+\"")
        override def adjustWidth(x: BigInt, width: Int) = 1
    }
}
// _noPlus: parsley.Parsley[Nothing] = parsley.Parsley@6bc1d375
_noPlus.parse("+10")
// res3: parsley.Result[String, Nothing] = Failure((line 1, column 1):
//   a number may not be preceeded by "+"
//   >+10
//    ^)

In the above, the width parameter would have been the original determined size, which would have been 3 based on the other error message. A VanillaGen would also have the ability to change the unexpected message:

import parsley.errors.VanillaGen
val _noFloat = float.verifiedWith {
    new VanillaGen[BigDecimal] {
        override def reason(x: BigDecimal): Option[String] =
            Some("floats may not be array indices")
        override def unexpected(x: BigDecimal): VanillaGen.UnexpectedItem = {
            VanillaGen.NamedItem("floating-point number")
        }
    }
}
// _noFloat: parsley.Parsley[Nothing] = parsley.Parsley@54c2c2ec

_noFloat.parse("3.142")
// res4: parsley.Result[String, Nothing] = Failure((line 1, column 1):
//   unexpected floating-point number
//   floats may not be array indices
//   >3.142
//    ^^^^^)

Preventative Errors

The verified error pattern can only be applied as part of a chain of alternatives. However, if the alternatives are spread far apart, this can make the pattern cumbersome to use. Instead, a preventative error seeks to rule out bad inputs not as a last resort, but eagerly as soon as it might become possible to do so. Widgets following this pattern have the following properties:

Similarly to verified errors, preventative errors can have many forms but the most common are embodied by the combinators available by importing parsley.errors.patterns.PreventativeErrors.

Unlike, verifiedX, preventativeX will try and parse something, and succeed if that fails. This makes it similar to notFollowedBy, but that alone does not have all the desired properties. As an example:

import parsley.errors.patterns.PreventativeErrors

val ident = lexer.nonlexeme.names.identifier
// ident: parsley.Parsley[String] = parsley.Parsley@4b0602a8
val _noDot = (char('.') ~> ident).preventativeFail { v =>
    Seq(s"accessing field $v is not permitted here")
}
// _noDot: parsley.Parsley[Unit] = parsley.Parsley@71e977a7
_noDot.parse("hi")
// res5: parsley.Result[String, Unit] = Success(())
_noDot.parse(".foo")
// res6: parsley.Result[String, Unit] = Failure((line 1, column 1):
//   accessing field foo is not permitted here
//   >.foo
//    ^^^^)

There are also vanilla variants too (and one for ErrorGen). However, these also allow for additional optional labels to be provided to describe valid alternatives that would have been successful from this point. This is useful, since parsers that follow from this point will not be parsed and cannot contribute their own labels. It is for this reason, that the verified error pattern is more effective if it possible to use.