Kotlin multiplatform expressions evaluator

This is a kotlin multiplatform runtime infix expressions evaluator.
Overview
Operators
The library supports the following operators and special symbols:
+, -, *, / - mathematical operators
% - modulo. Returns the remainder of a division, after one number is divided by another
^ - exponentiation. a^b means a raised to the power of b
&&, ||, ! - logical 'and', 'or', 'not' operators
==, != - equality operators
<, >, <=, >= - comparison operators
a ? b : c - ternary operator
Constants
The library supports the following constants:
pi - PI
e - e
- any custom constants.
Functions
The library supports the following functions:
Variables
Any symbols other than constants and function calls are resolved as variables during evaluation.
How to get
Gradle
The library is distributed via maven central repositories.
Kotlin DSL
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.murzagalin:multiplatform-expressions-evaluator:x.y.z")
}
Groovy
repositories {
mavenCentral()
}
dependencies {
implementation "io.github.murzagalin:multiplatform-expressions-evaluator:x.y.z"
}
Kotlin
Usage
import com.github.murzagalin.evaluator.Evaluator
val evaluator = Evaluator()
evaluator.evaluateDouble()
evaluator.evaluateBoolean(
,
mapOf( to , to )
)
evaluator.evaluateDouble(
,
mapOf( to , to )
)
evaluator.evaluateDouble(
,
mapOf( to )
)
evaluator.evaluateDouble()
evaluator.evaluateDouble()
Custom functions
The library supports custom functions with any number of arguments.
Supported argument and return types are Double and Boolean
Functions with constant number of arguments
For the example I will explain how to create a function that represents the general form of normal distribution probability density function:

The function will have the following syntax:
normal_dist(x, m, sigma).
The parameter m is the mean of the distribution, while the parameter sigma is its standard deviation.
We define a function which is named "normal_dist" and has 3 arguments:
Note: the library checks if the number of arguments in an expression is equal to 3, otherwise it throws an exception. But you have to check the types of the arguments by yourself.
Functions getAsDouble(index, lazyMessage) and getAsBoolean(index, lazyMessage) return an element at position index, and throw IllegalArgumentException with the message returned from lazyMessage if it has a wrong type
Then we add this function to the evaluator:
import com.github.murzagalin.evaluator.DefaultFunctions
import com.github.murzagalin.evaluator.Evaluator
fun main() {
val evaluator = Evaluator(functions = DefaultFunctions.ALL + NormalDist)
print(evaluator.evaluateDouble("normal_dist(12, 9, 3)"))
}
Functions with variable number of arguments
The process of creating functions with variable number of arguments is pretty much the same. The difference is how we define the function.
As an example I will create a function mult(a1, a2, ..., an) which is defined as a1 * a2 * ... * an:
object Mult: Function("mult", 2..Int.MAX_VALUE) {
override fun invoke(vararg args: Any): Any {
require(args.all { it is Double }) { "$name function requires all arguments to be numbers" }
args.fold() { acc, x -> acc * (x ) }
}
}
Note: we define minimum and maximum number of arguments as a range. It is also possible to define them separately:
Function("mult", 2, Int.MAX_VALUE)
Then we add this function to the evaluator:
import com.github.murzagalin.evaluator.DefaultFunctions
import com.github.murzagalin.evaluator.Evaluator
fun main() {
val evaluator = Evaluator(functions = DefaultFunctions.ALL + Mult)
print(evaluator.evaluateDouble("mult(2, 3, 4)"))
}
Custom constants
The library supports custom constants. For the example I will show you how to add a golden ratio constant.
We will define the constant named phi with the value 1.6180339887:
import com.github.murzagalin.evaluator.Constant
import com.github.murzagalin.evaluator.DefaultConstants
import com.github.murzagalin.evaluator.Evaluator
fun main() {
val PHI = Constant("phi", 1.6180339887)
val evaluator = Evaluator(constants = DefaultConstants.ALL + PHI)
print(evaluator.evaluateDouble("x * phi", mapOf("x" to 2)))
}
Expressions preprocessing
By default, the library does the following steps to evaluate an expression:
- Tokenizing - splitting the expression into a list of units (operations, numbers, constants, function calls, etc.)
- Converting the expression from infix notation to abstract syntax tree.
- Evaluating the abstract syntax tree.
In case you have an expression with variables, it might make sense to preprocess the expression (do steps 1 and 2 in advance) to improve the performance:
import com.github.murzagalin.evaluator.Evaluator
fun main() {
val evaluator = Evaluator()
val preprocessedExpression = evaluator.preprocessExpression("1 + x + y^2")
val result = evaluator.evaluateDouble(preprocessedExpression, mapOf("x" to 2, "y" to 4))
}
JVM
TBD
JS
TBD
IOS
TBD
License
This library is available for free under Apache 2.0 license.
Copyright (c) 2021 Azamat Murzagalin.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance the License.
You may obtain a copy of the License at
http:
Unless applicable law agreed to writing, software
distributed under the License distributed an BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
See the License the specific language governing permissions
limitations under the License.