Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

expression evaluator in scala (with maybe placeholders?)

Tags:

regex

scala

I am reading something like this from my configuration file :

metric1.critical = "<2000 || >20000"
metric1.okay = "=1"
metric1.warning = "<=3000"
metric2.okay = ">0.9 && < 1.1 "
metric3.warning ="( >0.9 && <1.5) || (<500 &&>200)"

and I have a

metric1.value =  //have some value

My aim is to basically evaluate

    if(metric1.value<2000 || metric1.value > 20000)
     metric1.setAlert("critical");
    else if(metric1.value=1)
     metric.setAlert("okay");
    //and so on

I am not really good with regex so I am going to try not to use it. I am coding in Scala and wanted to know if any existing library can help with this. Maybe i need to put placeholders to fill in the blanks and then evaluate the expression? But how do I evaluate the expression most efficiently and with less overhead?

EDIT: In java how we have expression evaluator Libraries i was hoping i could find something similar for my code . Maybe I can add placeholders in the config file like "?" these to substitute my metric1.value (read variables) and then use an evaluator? OR Can someone suggest a good regex for this? Thanks in advance!

like image 360
joanOfArc Avatar asked Dec 26 '22 03:12

joanOfArc


2 Answers

This sounds like you want to define your own syntax using a parser combinator library.

There is a parser combinator built into the scala class library. Since the scala library has been modularized, it is now a separate project that lives at https://github.com/scala/scala-parser-combinators.

Update: everybody looking for a parser combinator library that is conceptually similar to scala-parser-combinators should take a look at fastparse. It is very fast, and does not use macros. So it can serve as a drop-in replacement for scala-parser-combinators.

There are some examples on how to use it in Programming in Scala, Chapter 33, "Combinator Parsing".

Here is a little grammar, ast and evaluator to get you started. This is missing a lot of things such as whitespace handling, operator priority etc. You should also not use strings for encoding the different comparison operators. But I think with this and the chapter from Programming in Scala you should be able to come up with something that suits your needs.

import scala.util.parsing.combinator.{JavaTokenParsers, PackratParsers}

sealed abstract class AST
sealed abstract class BooleanExpression extends AST
case class BooleanOperation(op: String, lhs: BooleanExpression, rhs:BooleanExpression) extends BooleanExpression
case class Comparison(op:String, rhs:Constant) extends BooleanExpression
case class Constant(value: Double) extends AST

object ConditionParser extends JavaTokenParsers with PackratParsers {

  val booleanOperator : PackratParser[String] = literal("||") | literal("&&")
  val comparisonOperator : PackratParser[String] = literal("<=") | literal(">=") | literal("==") | literal("!=") | literal("<") | literal(">")
  val constant : PackratParser[Constant] = floatingPointNumber.^^ { x => Constant(x.toDouble) }
  val comparison : PackratParser[Comparison] = (comparisonOperator ~ constant) ^^ { case op ~ rhs => Comparison(op, rhs) }
  lazy val p1 : PackratParser[BooleanExpression] = booleanOperation | comparison
  val booleanOperation = (p1 ~ booleanOperator ~ p1) ^^ { case lhs ~ op ~ rhs => BooleanOperation(op, lhs, rhs) }
}

object Evaluator {

  def evaluate(expression:BooleanExpression, value:Double) : Boolean = expression match {
    case Comparison("<=", Constant(c)) => value <= c
    case Comparison(">=", Constant(c)) => value >= c
    case Comparison("==", Constant(c)) => value == c
    case Comparison("!=", Constant(c)) => value != c
    case Comparison("<", Constant(c)) => value < c
    case Comparison(">", Constant(c)) => value > c
    case BooleanOperation("||", a, b) => evaluate(a, value) || evaluate(b, value)
    case BooleanOperation("&&", a, b) => evaluate(a, value) && evaluate(b, value)
  }
}

object Test extends App {

  def parse(text:String) : BooleanExpression = ConditionParser.parseAll(ConditionParser.p1, text).get

  val texts = Seq(
    "<2000",
    "<2000||>20000",
    "==1",
    "<=3000",
    ">0.9&&<1.1")

  val xs = Seq(0.0, 1.0, 100000.0)

  for {
    text <- texts
    expression = parse(text)
    x <- xs
    result = Evaluator.evaluate(expression, x)
  } {
    println(s"$text $expression $x $result")
  }
}
like image 58
Rüdiger Klaehn Avatar answered Feb 03 '23 20:02

Rüdiger Klaehn


Scala has built in Interpreter library which you can use. The library provides functionalities similar to eval() in many other languages. You can pass Scala code snippet as String to the .interpret method and it will evaluate it.

import scala.tools.nsc.{ Interpreter, Settings }

val settings = new Settings   
settings.usejavacp.value = true
val in = new Interpreter(settings) 
val lowerCritical = "<2000" // set the value from config
val value = 200   
in.interpret(s"$value $lowerCritical")                    //> res0: Boolean = true
val value1 = 20000                               //> value1  : Int = 20000
in.interpret(s"$value1 $lowerCritical")          //> res1: Boolean = false
like image 24
mohit Avatar answered Feb 03 '23 20:02

mohit