8

I'm new to Scala but I was wondering it it is possible to implement a simple Equation parser in the language.

Say I have a few functions (much like Excel functions):

IF(Cond a=b, val_true, val_false)

MID(String, Start_pos, num_chars) - string extract

LEN(String) - length of a string

OR(cond1, cond2, ... condn)

AND(cond1, cond2, ... condn)

So the idea would be I could pass in a formula at runtime as a string from a user as a command line argument along with any other params say IF(LEN(param1)=4,MID(param1,2,1), MID(param1,0,LEN(param1)))

The idea is to evaluate the function, so if the user provides that above formula and the string "scat" then the output would be "a". If the string "scala" was given then the output would be "scala"...

How easy would this be to implement in Scala? What is the best design approach? I know there are no function pointers (in C I would have parsed the formula string into a collection of func points and gone from there)...

Any advice on how to approach this in efficient Scala style would be appreciated.

Cheers!

NightWolf
  • 7,694
  • 9
  • 74
  • 121
  • 1
    There _are_ the equivalent of function pointers, namely anonymous function literals. – Ptharien's Flame Mar 20 '12 at 01:47
  • The idea would be to pass the the formula and then do what? Return a function implementing the formula, return an object representing the parsed expression? – huynhjl Mar 20 '12 at 01:57
  • Sorry, this is not clear enough. "I could pass in a formula": how? where? at run time? at compile time? as source code? as string? and do you expect to happen when you do pass it? If I were you, I'd rewrite the whole question again. – Daniel C. Sobral Mar 20 '12 at 02:28
  • Thanks for the comments. Have made more clear, essentially I want an expression language that is passed/evaluated in Scala. Just a toy example,we have a language like this at work, but theres no neat evaluator. I thought this would be a good project to introduce Scala to the office and show off some of its features. – NightWolf Mar 20 '12 at 02:53
  • 1
    @NightWolf - did you take a look at javax.script.*? It would let you embed the whole JavaScript by default, and many other scripting languages. I think that would be better that writing your own interpreter. – Rogach Mar 20 '12 at 05:31
  • 2
    Take a look at the SCells spreadsheet example in *Programming in Scala*: http://www.artima.com/pins1ed/the-scells-spreadsheet.html . Basically it uses parser combinators, so you'll have to read up on those as well. – Luigi Plinge Mar 20 '12 at 05:49

1 Answers1

8

This question has motivated to experiment with combinator parsers. Given the following algebraic data types representing a subset of your expressions:

import scala.util.parsing.combinator._
object Expr { type VARS = Map[String, Any] }
import Expr._
sealed trait Expr { def eval(v: VARS) : Any } 

case class If(cond: Cond, ifTrue: Expr, ifFalse: Expr) extends Expr {
  def eval(v: VARS) = 
    if (cond.eval(v)) ifTrue.eval(v) else ifFalse.eval(v)
}
case class Cond(left: Expr, right: Expr) extends Expr {
  def eval(v: VARS) = left.eval(v) == right.eval(v)
}
case class Len(ident: String) extends Expr { 
  def eval(v: VARS) = v(ident).toString.size
}
case class Mid(ident: String, start: Expr, count: Expr) extends Expr {
  def eval(v: VARS) = {
    val s = start.eval(v).asInstanceOf[Int]
    val e = s + count.eval(v).asInstanceOf[Int]
    v(ident).asInstanceOf[String].substring(s, e)
  }
}
case class Ident(ident: String) extends Expr   { def eval(v:VARS) = v(ident) }
case class StringLit(value: String) extends Expr { def eval(v:VARS) = value }
case class Number(value: String) extends Expr  { def eval(v:VARS) = value.toInt }

The following parser definition will parse your given expression and return a Expr object:

class Equation extends JavaTokenParsers {
  def IF: Parser[If] = "IF" ~ "(" ~ booleanExpr ~","~ expr ~","~ expr ~ ")" ^^ {
    case "IF" ~ "(" ~ booleanExpr ~ "," ~ ifTrue ~ "," ~ ifFalse ~ ")" =>
      If(booleanExpr, ifTrue, ifFalse)
  }
  def LEN: Parser[Len] = "LEN" ~> "(" ~> ident <~ ")" ^^ (Len(_))
  def MID: Parser[Mid] = "MID" ~ "(" ~ ident ~ "," ~ expr ~ "," ~ expr ~ ")" ^^ {
    case "MID" ~ "(" ~ ident ~ "," ~ expr1 ~ "," ~ expr2 ~ ")" =>
      Mid(ident, expr1, expr2) 
  }
  def expr: Parser[Expr] = (
    stringLiteral ^^ (StringLit(_))
    | wholeNumber  ^^ (Number(_))
    | LEN
    | MID 
    | IF 
    | ident ^^ (Ident(_))
  )
  def booleanExpr: Parser[Cond] = expr ~ "=" ~ expr ^^ {
    case expr1 ~ "=" ~ expr2 => Cond(expr1, expr2)
  }
}

Then parsing and evaluating the results can be done like this:

val equation = new Equation
val parsed = equation.parseAll(equation.expr,
   """IF(LEN(param1)=4,MID(param1,2,1), MID(param1,0,LEN(param1)))""")
parsed match {
  case equation.Success(expr, _) =>
    println(expr)
    // If(Cond(Len(param1),Number(4)),
    //   Mid(param1,Number(2),Number(1)),
    //   Mid(param1,Number(0),Len(param1)))
    println(expr.eval(Map("param1" -> "scala"))) // prints scala
    println(expr.eval(Map("param1" -> "scat")))  // prints a
  case _ =>
    println("cannot parse")
}

Note that the grammar I provided is just the minimum to make your example parse and there absolutely no error management or type checking. Process wise, I first came up with a grammar without the production ^^ ... that would parse your example, then added the Expr types but without the eval method, then the production ^^ ..., then I finally added the eval methods to the Expr trait and sub-classes.

huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Hi @huynhjl, while executing above code, we are facing issue at below lines of code println(expr.eval(Map("param1" -> "scala"))) // prints scala println(expr.eval(Map("param1" -> "scat"))) // prints a ---------- Error code: Cannot resolve symbol eval can you please suggest on this – Rajashekhar Meesala Feb 20 '23 at 09:53