Hi There!

I'm Dan Schlegel, an Associate Professor in the Computer Science Department at SUNY Oswego

Recursive Descent Parser in Scala

import scala.util.matching.Regex

// Unambiguous grammar
// S -> E$
// E -> T + E | T
// T -> Const | Var

// Easier to parse grammar
// S -> E$
// E -> Terminal E2
// E2 -> + E
// E2 -> NIL
// Terminal -> Const
// Terminal -> Var

sealed trait S {
  def eval(env: Main.Environment): Int
}

sealed trait Terminal extends S

case class E(left: Terminal, right: Option[E2]) extends S {
  def eval(env: Main.Environment): Int = {
    val leftVal: Int = left.eval(env)
    right match {
      case Some(r) => leftVal + r.eval(env)
      case None => leftVal
    }
  }
}
case class E2(l: E) extends S {
  def eval(env: Main.Environment): Int = l.eval(env)
}
case class Var(n: String) extends Terminal {
  def eval(env: Main.Environment): Int = env(n)
}
case class Const(v: Int) extends Terminal {
  def eval(env: Main.Environment): Int = v
}

class RecursiveDescent(input: String) {
  private val constRegex: Regex = "^[0-9]+".r
  private val varRegex: Regex = "^[A-Za-z]+".r

  private var index = 0

  private def remainingInput: String =
    input.substring(index)

  def parseS(): S = {
    val result = parseE()

    if (index != input.length) {
      throw new IllegalArgumentException(
        s"Unexpected input at position $index: '${input.substring(index)}'"
      )
    }

    result
  }

  private def parseE(): E = E(parseTerminal(), parseE2())

  private def parseE2(): Option[E2] = {
    if (index < input.length && input(index) == '+'){
      index+=1; // Advance past +
      Some(E2(parseE()))
    }
    else{
      None
    }
  }

  private def parseConst(): Option[Const] = {
    constRegex.findFirstIn(remainingInput) match {
      case Some(const) =>
        index += const.length
        Some(Const(const.toInt))

      case None => None
    }
  }

  private def parseVar(): Option[Var] = {
    varRegex.findFirstIn(remainingInput) match {
      case Some(varName) =>
        index += varName.length
        Some(Var(varName))

      case None => None
    }
  }

  private def parseTerminal(): Terminal =
    parseConst() // Check for const
      .orElse(parseVar()) // If not there, check for var
      .getOrElse { // If not there, fail
        throw new IllegalArgumentException(
          s"Expected constant or variable at position $index in '$input'"
        )
      }
}

object Main {
  type Environment = String => Int

  def main(args: Array[String]): Unit = {
    val env: Environment = { case "x" => 5 case "y" => 7 }

    val rd = new RecursiveDescent("x+x+7+y")
    val exp2rd: S = rd.parseS()

    println(exp2rd)
    println(exp2rd.eval(env))
  }
}