如果抛出异常,如何使解析器优雅地失败?

jub*_*0bs 1 parsing scala exception-handling parser-combinators

这是我尝试为 Int s 编写一个小解析器:

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
  }

}
Run Code Online (Sandbox Code Playgroud)

问题是,如果输入字符串太长,则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 ...
Run Code Online (Sandbox Code Playgroud)

相反,我希望我的positiveInt解析器FailuretoInt抛出异常时优雅地(通过返回a )失败.我怎样才能做到这一点?

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

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

Sum*_*uma 5

您可以使用组合器接受部分函数(灵感来自如何使scala解析器失败):

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

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

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 }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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