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@4cb8746b
val _noFloat =
    float.verifiedExplain("floating-point values may not be used as array indices")
// _noFloat: parsley.Parsley[Nothing] = parsley.Parsley@9a1e8f1

_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@2822d614
val _noPlus = (char('+') ~> int).verifiedFail { n =>
    Seq(s"the number $n may not be preceeded by \"+\"")
}
// _noPlus: parsley.Parsley[Nothing] = parsley.Parsley@56508214
_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 SpecializedGen.

import parsley.errors.SpecializedGen
val _noPlus = (char('+') ~> int).verifiedWith {
    new SpecializedGen[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@32a714e8
_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@15def607

_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@d8c3ddb
val _noDot = (char('.') ~> ident).preventativeFail { v =>
    Seq(s"accessing field $v is not permitted here")
}
// _noDot: parsley.Parsley[Unit] = parsley.Parsley@36ae23d
_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.