1
void whatever() {
  // ...
  val parser = new MyParser
  val parse = parser.parse(input)
  if (parse successful) {
    semanticAnalysis(parse)
  }
}

void semanticAnalysis(parse: DontKnowTheCorrectType) {
  // ...
}

What type do I have to give to the formal parameter parse? Hovering over parse inside whatever says val parse: parser.ParseResult[parsing.Program], but of course that doesn't work as a parameter type of semanticAnalysis, because the local variable parse is not in scope there.

fredoverflow
  • 256,549
  • 94
  • 388
  • 662

4 Answers4

3

Parse results are path-dependent types because they are results of this specific parser and there's no guarantee they are compatible.

This is why parsers are usually not instantiated the way you use them (new MyParser), but as object.

object MyParser extends RegexParsers { 
   def statements : Parser[List[Statement]] = // ...
} 

def handleResult(res: MyParser.ParseResult[List[Statement]]) = { // ... } 

val res = MyParser.parseAll(MyParser.statements, "/* */")

If you need more dynamic behaviour (or want concurrent parsing, parser combinators aren't thread-safe, ouch), you'll just have to keep the parser object accessible (and stable) wherever you want to use its results.

Sadly, passing a parser and its result around together is not trivial because you run into the prohibition of dependent method types, e.g.

  def fun(p: scala.util.parsing.combinator.Parsers, res: p.ParseResult[_]) = {}

won't compile ("illegal dependent method type"), but there are ways to get around that if you must, like the answer to this question.

Community
  • 1
  • 1
themel
  • 8,825
  • 2
  • 32
  • 31
  • That's gonna be a problem, because I put some state into my parser... I know, that's not purely functional, and I will probably burn in hell for that :) – fredoverflow Aug 21 '12 at 09:53
  • Can you not do `def fun(p: scala.util.parsing.combinator.Parsers)(res: p.ParseResult[_]) = {}`? –  Jul 06 '14 at 11:17
1

You should be able to define semanticAnalysis as:

def semanticAnalysis(parse: MyParser#ParseResult[parsing.Program]) = {
  ...
}

Note the use of # instead of . for the type. There are more details about type projections here, but basically, parser.ParseResult is different for each parser, while MyParser#ParseResult is the same for all parser instances.

But, as themel says, you should probably be using object instead of class.

Community
  • 1
  • 1
Steve
  • 3,038
  • 2
  • 27
  • 46
0

You can rewrite it like this:

def whatever() {
  // ...
  val parser = new MyParser
  def semanticAnalysis(parse: parser.ParseResult) {
    // ...
  }
  val parse = parser.parse(input)
  if (parse successful) {
    semanticAnalysis(parse)
  }
}

If you have this being called from multiple places, then maybe this:

  class SemanticAnalysis(parser: Parser) {
    def apply(parse: parser.ParseResult) {
      // ...
    }
  }

And then

  if (parse successful) {
    new SemanticAnalysis(parser)(parse)
  }
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
0

When I need to use the result of parser combinators in other parts of a program, I extract the success result or error message from the path-dependent ParseResult and put the data into an independent type. It's more verbose than I like, but I want to keep the combinator instance an implementation detail of the parser.

sealed abstract class Result[+T]
case class Success[T](result: T) extends Result[T]
case class Failure(msg: String) extends Result[Nothing]
case class Error(msg: String) extends Result[Nothing]

/** Parse the package declarations in the file. */
def parse(file: String): Result[List[Package]] = {
  val stream = ... // open the file...
  val parser = new MyParser
  val result = parser.parseAll(parser.packages, stream)   
  stream.close()
  result match {
    case parser.Success(packages, _) => Success(packages)
    case parser.Failure(msg, _) => Failure(msg)
    case parser.Error(msg, _) => Error(msg)
  }
}
Jay Lieske
  • 4,788
  • 3
  • 30
  • 41