如果抛出异常,我怎样才能让我的解析器优雅地失败?

How can I make my parser fail gracefully if an exception is thrown?

这是我为 positive Ints:

编写一个小型解析器的尝试
import scala.util.parsing.combinator.RegexParsers

object PositiveIntParser extends RegexParsers {

  private def positiveInt: Parser[Int] = """0*[1-9]\d*""".r ^^ { _.toInt }

  def apply(input: String): Option[Int] = parseAll(positiveInt, input) match {
    case Success(result, _) => Some(result)
    case _ => None
  }

}

问题是,如果输入字符串太长,toInt 会抛出 NumberFormatException,这会使我的解析器崩溃:

scala> :load PositiveIntParser.scala
Loading PositiveIntParser.scala...
import scala.util.parsing.combinator.RegexParsers
defined object PositiveIntParser

scala> PositiveIntParser("12")
res0: Option[Int] = Some(12)

scala> PositiveIntParser("-12")
res1: Option[Int] = None

scala> PositiveIntParser("123123123123123123")
java.lang.NumberFormatException: For input string: "123123123123123123"
  at ...

相反,我希望我的 positiveInt 解析器在 toInt 抛出异常时优雅地失败(通过返回 Failure)。我该怎么做?

我想到的一个简单的解决方法是限制我的正则表达式接受的字符串的长度,但这并不令人满意。

我猜 scala.util.parsing.combinator 库已经提供了这个用例的解析器组合器,但我一直找不到...

Try() 包装对 parseAll 的调用怎么样?

Try(parseAll(positiveInt, input))

scala.util.Tryapply 方法将任何异常包装在 Failure[T] 中,然后您甚至可以使用 .toOption 将任何 Failure 转换为None.

Try(parseAll(positiveInt, input)).toOption

您可以使用接受部分函数的组合器(受 how to make scala parser fail 启发):

private def positiveInt: Parser[Int] = """0*[1-9]\d*""".r ^? {
  case x if Try(x.toInt).isSuccess => x.toInt
}

如果你想避免双重转换,你可以创建一个提取器来执行匹配和转换:

object ParsedInt {
  def unapply(str: String): Option[Int] = Try(str.toInt).toOption
}

private def positiveInt: Parser[Int] = """0*[1-9]\d*""".r ^? { case ParsedInt(x) => x }

也可以将肯定性测试移到案例条件中,我发现它比有点复杂的正则表达式更具可读性:

private def positiveInt: Parser[Int] = """\d+""".r ^? { case ParsedInt(x) if x > 0 => x }

根据您的评论,提取也可以在单独的 ^^ 步骤中执行,如下所示:

private def positiveInt: Parser[Int] = """\d+""".r ^^
  { str => Try(str.toInt)} ^? { case util.Success(x) if x > 0 => x }