Search code examples
scalaparser-combinators

Combining a Parser using andThen to create another Parser of different type


I'm trying to parse a data using combinator parsers to return a Parser[java.util.Date]

I do that in two phases, first, I parse a year using a simpleYear Parser, then I tried to plug in the result of this simple parser into andThen, I would then manipulate this input to have as output of this andThen a ParseResult[Date]:

Sadly I get the following error from the compiler at the line of declaration:

       type mismatch; found : 
parser.DateParser.Input ⇒ parser.DateParser.ParseResult[java.util.Date] (which 
     expands to) 
    scala.util.parsing.input.Reader[Char] ⇒ parser.DateParser.ParseResult[java.util. required: parser.DateParser.Parser[java.util.Date]

here is the code:

object DateParser extends RegexParsers {
        val formatter: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd")
        def year = """\d{4}""".r
        def month: Parser[String] = 
        def day = """[0-2]\d""".r | """3[01]""".r
        def month = """0\d""".r | """1[0-2]""".r
        def simpleDate: Parser[String] =
            (year ~ "-" ~ month ~ "-" ~ day) ^^
                { case y ~ "-" ~ m ~ "-" ~ d => y + "-" + m + "-" + d }

        def date: Parser[Date] = simpleDate andThen {
            case Success(s, in) =>
                val x: ParseResult[Date] = try {
                    Success(formatter.parse(s), in)
                } catch {
                    case pe: ParseException => Failure("date format invalid", in)
                }
                x
            case f: Failure => f
        }
}

It seems that the scala compiler can't do an implicit conversion itself of the type of date into a Parser[Date](maybe because of the try/catch?)

Is there some other way to do what I want to do?


Solution

  • Parser[T] is a subclass of a function Input => ParseResult[T] and the method andThen you are using comes from the function. You are passing to it a function ParseResult[String] => ParseResult[Date], so you get back Input => ParseResult[Date], which doesn't match the type Parser[Date], and that's why you get this error.

    But you can simply wrap a function of type Input => ParseResult[T] in a Parser method to get a Parser[T]. So you can define date like this:

    def date: Parser[Date] = Parser(simpleDate andThen {
      // Cleaned up `Success` case a bit
      case Success(s, in) =>
        try Success(formatter.parse(s), in)
        catch {
          case pe: ParseException => Failure("date format invalid", in)
        }
      // It's better to use `NoSuccess` instead of `Failure`, 
      // to cover the `Error` case as well.
      case f: NoSuccess => f
    })
    

    That said, it's not the best/cleanest method. As you want to call a function on the parser result to modify it somehow, you can use the Parser's methods map and flatMap or their equivalents (map equivalent to ^^, flatMap equivalent to into and >>). Those have the same idea as map and flatMap of other Scala classes such as Try or Future.

    In this case you have to account for the possibility of failure, so you have to use flatMap. A definition of date using flatMap may look like this:

    def date: Parser[Date] = simpleDate >> (s =>
      try success(formatter.parse(s))
      catch { case pe: ParseException => failure("date format invalid") })
    

    Also, you may want (if you hadn't already done it yourself) to set formatter to be non-lenient: formatter.setLenient(false). Otherwise it will do things like parsing 2000-02-31 as the 2nd of March!