Scalexpr is a expression parser and evaluator for Scala. The library has been created to provide a very simple way to parse expression Strings and evaluate them in a type-safe manner.
A sbt script is provided and can be used to build scalexpr. The only dependency is the incredible fastparse library.
On its simplest form, scalexp can be used as:
import br.com.virsox.scalexpr._
import scala.util.{Failure, Success}
val parser = ExpressionParser()
parser.parseIntExpression("10 + 5") match {
case Success(expr) => println(expr.resolve()); assert(expr.resolve() == 15)
case Failure(ex) => println(ex)
}
The library also support more complex expressions containing relational and boolean operators:
val expr1 = parser.parseBooleanExpression("(10 + 5 > 12) && (4 - 3 <= 1)").get
println(expr1.resolve()) // prints true
And the use of variables:
val ctx2: Map[String, Any] = Map("age" -> 19, "salary" -> 32000)
val expr2 = parser.parseBooleanExpression("age == 19 && salary > 40000 ").get
println(expr2.resolve(ctx2)) // prints false
In this case, age
and salary
are parsed as variables that are resolved in the resolve
method based on the
content of the context map.
Finally, the library also supports other data types, including Long
, Double
, String
, and
java.time.Instant
:
import java.time.Instant
val ts = Instant.parse("2015-10-01T12:00:00.000Z")
val ctx3 = Map("id" -> 10L, "name" -> "sensor1", "timestamp" -> ts)
val expr3 = parser.parseBooleanExpression(
"""(id == 10L || name == "sensor1") && timestamp > 2012-01-01T00:00:00.000Z""").get
println(expr3.resolve(ctx3)) // prints true
The library provides a VariableResolver
type class that must be implemented by classes whose objects
can be used as context when resolving expressions:
trait VariableResolver[T] {
def get[B: TypeTag](ctx: T, field: String): Option[B]
def nested(ctx: T, field: String): Option[T]
}
Objects of this type class are passed implictly to the resolve
method and are used to obtain values
based on the variable names. scalexpr provides a default implementation for Map
s with String
keys, but
other implementations can be easily created.
scalexpr provides a DSL that can be used to create Expression
objects independently of the
parsing functionalities. The following snippet shows how the DSL can be used:
import br.com.virsox.scalexpr._
val expr1 = IntConstant(10) + IntConstant(2) * IntConstant(3)
println(expr1.resolve()) // prints 16
val expr2 = (DoubleConstant(6.0) / DoubleConstant(2.0)) > DoubleConstant(1.0)
println(expr2.resolve()) // prints true
val ctx3 = Map("name" -> "John", "age" -> 9)
val expr3 = (StringVar("name") == StringConstant("John")) && (IntVar("age") > IntConstant(10))
println(expr3.resolve(ctx3)) // prints false
The library has been designed to be as straightforward to use as possible and, right now, it already implements most functionalities originally planned. Nevertheless, the following functionalities are still missing:
- Automatic conversion between numeric types
- String functions (toUpperCase, toLowerCase, trim)
- Alternative date representations
Contributions are welcome!