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

时间:2016-05-05 17:36:37

标签: scala parsing exception-handling parser-combinators

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

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抛出异常时,我希望我的Failure解析器优雅地失败(通过返回toInt)。我怎么能这样做?

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

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

2 个答案:

答案 0 :(得分:5)

您可以使用接受部分功能的组合器(受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 }

答案 1 :(得分:0)

如何使用parseAll将来电包裹到Try()

Try(parseAll(positiveInt, input))

scala.util.Try的{​​{1}}方法将在apply中包装任何异常,然后您甚至可以使用Failure[T]将任何.toOption转换为Failure 1}}。

None