3
votes

I'm just learning the Scala parser combinator library. I've experimented a working parser that parses some arithmetic expressions with an abstract syntax tree. So when I call

phrase(expr)(tokens)

and my parser parses all the input then gives me an evaluation. But how can I have a step by step evaluation?

Say

3 + 4 * 7

it prints

3 + 28

then

31

in separate lines.

I've scan through the apis but the docs there are not very helpful ... Thanks for help.

2

2 Answers

5
votes

Here's a really simple implementation of what you are trying to do:

First, we define an expression hierarchy. You will need to tailor this to your specific problem.

trait Expr {
  def eval: Int
}
case class IntLeaf(n: Int) extends Expr {
  def eval = n
  override def toString = "%d".format(n)
}
case class Sum(a: Expr, b: Expr) extends Expr {
  def eval = a.eval + b.eval
  override def toString = "(%s + %s)".format(a, b)
}

Then, a function that combines only the bottom-most branch.

def combineLeaves(e: Expr): Expr = {
  e match {
    case IntLeaf(n) => IntLeaf(n)
    case Sum(IntLeaf(a), IntLeaf(b)) => IntLeaf(a + b)
    case Sum(a, b) => Sum(combineLeaves(a), combineLeaves(b))
  }
}

Then, a function that combines the tree one level at a time, printing as it goes.

def printEval(e: Expr) {
  println(e)
  e match {
    case IntLeaf(n) =>
    case _ => printEval(combineLeaves(e))
  }
}

Now, the Parser. Again, you'll have to tailor this to your data.

object ArithmeticParser extends RegexParsers {
  private def int: Parser[IntLeaf] = regex(new Regex("""\d+""")).map(s => IntLeaf(s.toInt))
  private def sum: Parser[Sum] = ("(" ~> expr ~ "+" ~ expr <~ ")").map { case (a ~ _ ~ b) => Sum(a, b) }
  private def expr = int | sum
  def parse(str: String): ParseResult[Expr] = parseAll(expr, str)
  def apply(str: String): Expr = ArithmeticParser.parse(str) match {
    case ArithmeticParser.Success(result: Expr, _) => result
    case _ => sys.error("Could not parse the input string: " + str)
  }

}

And here's how you use it:

scala> printEval(ArithmeticParser("((1 + 7) + ((3 + 9) + 5))"))
((1 + 7) + ((3 + 9) + 5))
(8 + (12 + 5))
(8 + 17)
25
2
votes

Parser combinators never give you any evaluation. Using parser combinators you parse the input string and build some data structure representing it. Evaluation is another step where you somehow process the data structure and perform required simplifications. Therefore, having your expression 3+4*7, in the parse phase you can build the following abstract syntax tree:

   +
  / \
 3   *   
    / \
   4   7

and then, in the evaluation phase, recursively walk the tree, and for each non-leaf node apply the node operation to the results of evaluation of its left and right subtree.

If the documentation is not helpful, you can consult the parser combinators chapter of Programming in Scala, the first edition of which is available for free.

I also recently wrote a blogpost on parser combinators in Scala, where I discuss a scenario somewhat similar to yours.