2

I'm using Li Haoyi's FastParse library. I have several situations where I'd like to provide explicit failure messages.

For example:

  def courseRE[p: P]: P[Regex] =
    P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.|*+[]()-^$").repX(1).!).map { re =>
      try { re.r }
      catch { case e => failure(s"Ill-formed regular expression: ${re}.") }
    }

But there is (apparently) no failure function.

Any suggested work-arounds? Simply throwing an exception doesn't give any context information.

bwbecker
  • 1,031
  • 9
  • 21
  • There is a `Parsed.Failure` that can be composed. Can see it even in Haoyi's lib page here https://com-lihaoyi.github.io/fastparse/ – user2963757 May 12 '22 at 06:12
  • Thanks for the suggestion, @user2963757. I see `Parsed.Failure` all over the documentation, but always as the result of a parsing run and never as a way to inject an error. So I guess I don't understand how to use it to achieve my goals. Can you provide an example? – bwbecker May 12 '22 at 12:44
  • I don't think you should inject. Instead do something like `parse() match {case Parsed.Failure(_, _, extra) => MyException(message=extra)}` – user2963757 May 20 '22 at 11:41

1 Answers1

0

I haven't yet found a good solution. I don't like the solution proposed by @user2963757 because it loses all the information from the parser about what it was looking for, where it was looking, etc.

This is raised a number of times in the FastParse issues list on GitHub. See issue 213, issue 187, issue 243, and pull request 244. There are a few vague suggestions but as far as I can tell the pull request hasn't been acted on (as of 2023-02-09).

The best I've found so far is defining this in an accessible location:

  // Fail with a message.  See https://github.com/com-lihaoyi/fastparse/issues/213
  // The message shows up as "Expected ..."; phrase it appropriately.
  private def Fail[T](expected: String)(implicit ctx: P[_]): P[T] = {
    val res = ctx.freshFailure()
    if (ctx.verboseFailures) ctx.setMsg(ctx.index, () => expected)
    res
  }

To use it:

    P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.|*+[]()-^$").repX(1).!).flatMap(re =>
      Try(re.r)
        .map(Pass(_))
        .getOrElse(Fail("<Well-formed regular expression>"))
    )

Trying to parse "^CS1[1345" yields

Expected <Well-formed regular expression>:1:10, found ""

Notice that the failure message has to be stated in terms of what was expected, not the actual problem. The actual error message thrown by the exception usually doesn't work well in this situation. I'm also not getting the fragment that it found.

Unfortunately, even this message is usually unavailable. For example, parsing a larger piece of my input results in

Expected (courseSpecDef | minUnits | xOf | courseSpec):1:14, found "^CS1[1345 "

I'd like to be able to surface the more exact error of "Unclosed character class" but seemingly can't.

By the way, I looked in the documentation, source code, and the sample parsers (PythonParse and ScalaParse) for examples of the use of the Fail parser. Can't find any. The only one is the one shown in the documentation, which doesn't compose with another parser.

If anyone has a better solution, I'd still love to hear it.

bwbecker
  • 1,031
  • 9
  • 21