Here is my attempt at writing a small parser for positive 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
}
}
The problem is that, if the input string is too long, toInt
throws an NumberFormatException
, which makes my parser blow up:
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 ...
Instead, I would like my positiveInt
parser to fail gracefully (by returning a Failure
) when toInt
throws an exception. How can I do that?
An easy fix that comes to mind consists in limiting the length of the strings accepted by my regex, but that's unsatisfactory.
I'm guessing that a parser combinator for this use case is already provided by the scala.util.parsing.combinator
library, but I've been unable to find one...
You can use a combinator accepting a partial function (inspired by 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
}
If you want to avoid the double conversion, you can create an extractor to perform the matching and conversion:
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 }
It is also possible to move the positiveness test into the case condition, which I find more readable than a bit intricate regex:
private def positiveInt: Parser[Int] = """\d+""".r ^? { case ParsedInt(x) if x > 0 => x }
As per your comment the extraction can also be performed in a separate ^^
step, as follows:
private def positiveInt: Parser[Int] = """\d+""".r ^^
{ str => Try(str.toInt)} ^? { case util.Success(x) if x > 0 => x }