Creating an instance of an object using Scala combinator parsing

Viewed 37

I have this parser:

import scala.util.parsing.combinator.JavaTokenParsers

class RequestMappingParser extends JavaTokenParsers {
  def requestMapping: Parser[Any] = "@RequestMapping(" ~ commaDelimitedSeq ~ ")"
  def commaDelimitedSeq: Parser[Any] = repsep(keyValue, ",")
  def keyValue: Parser[Any] = key ~ "=" ~ value
  def key: Parser[Any] = "value" | "method"
  def value: Parser[Any] = """[^)]*""".r
}

I have this simple class:

class MethodRequestMapping(val value: String, val method: String)

My parser can parse this string:

"@RequestMapping(value = \"/ex/foos\", method = RequestMethod.GET)"

and return a result of Parser[Any]. I would like it to return a result of Parser[MethodRequestMapping].

How do I do this? I know I have to do something like this:

def requestMapping: Parser[MethodRequestMapping] = "@RequestMapping(" ~ commaDelimitedSeq ~ ")" ^^ {
  // Some sort of pattern matching and creation of MethodRequestMapping
  // ???
}

But what goes in the ???? Or is it better to do it some entirely different way?

1 Answers

First of all, half the combinatorics you use are created as Parser[String], and it's your type annotation that upcast them to Parser[Any] (which is allowed because of covariance, but also completely useless in this case).

So you actually have something like:

class RequestMappingParser extends JavaTokenParsers {
  def requestMapping: Parser[String ~ List[String ~ String ~ String] ~ String] = "@RequestMapping(" ~ commaDelimitedSeq ~ ")"
  def commaDelimitedSeq: Parser[List[String ~ String ~ String]] = repsep(keyValue, ",")
  def keyValue: Parser[String ~ String ~ String] = key ~ "=" ~ value
  def key: Parser[String] = "value" | "method"
  def value: Parser[String] = """[^)]*""".r
}

Then we could use map or ^^ to adjust types - before it was difficult because we had Any there, but if we won't get rid of type, we will be able to pattern-match easily

class RequestMappingParser extends JavaTokenParsers {
  // result of this parser will be ethier "value" or "method"
  def key: Parser[String] = "value" | "method"
  def value: Parser[String] = """[^),]*""".r // you had an error here, you were missing "," to separate items
  // here we'll map String ~ String ~ String to tuple - we'll drop unused "=" in the process
  def keyValue: Parser[(String, String)] = (key ~ "=" ~ value).map {
    case k ~ _ ~ v => k -> v
  }
  // repsep wil return List[(String, String)], which we'll map to Map
  def commaDelimitedSeq: Parser[Map[String, String]] = repsep(keyValue, ",").map(_.toMap)
  // this would give us String ~ Map[String, String] ~ String, but we don't need
  // these String constants. Also we can map things into out final result
  def requestMapping: Parser[MethodRequestMapping] = ("@RequestMapping(" ~ commaDelimitedSeq ~ ")").map {
    case _ ~ map ~ _ =>
      new MethodRequestMapping(value = map("value"), method = map("method"))
  }
}
val parser = new RequestMappingParser()
val parsed = "@RequestMapping(value = \"/ex/foos\", method = RequestMethod.GET)"
val result = parser.parse(parser.requestMapping, parsed).get
// result.value == "\"/ex/foos\""
// result.method == "RequestMethod.GET"

That said - this parser combinations library is discouraged, mostly because of being slow. For production it is suggested to use something like FastParse, which has faster implementation and more features.

Related