Mathematical expression engine written in Kotlin, running on JVM.

Overview

KFormula

ภาษาไทย

Mathematical expression engine written in Kotlin, running on JVM.

Awesome Kotlin Badge Travis CI Kotlin 1.3.60 MIT License

With KFormula, you can parse simple mathematical expression text and get the evaluated result.

You can add variables/constants, or beyond that, you can define your own function and expose the logic to the engine.

This way, you can make your application to be able to accept mathematical expression from user's input, or even from database.

If the calculation should be made, simply change the expression to get the new calculation logic applied to your application without recompiling!

Install

Gradle:

repositories {
    jcenter()
    mavenCentral()
}

dependencies {
    ...
    implementation 'com.github.vittee.kformula:kformula:1.0.3'
    ...
}

Table Of Contents

Expression

Numeric literal

Only base-10 decimal is supported, no fancy bin/oct/hex/scientific notations.

All values will be stored as BigDecimal instances.

Percentage numeric literal

KFormula support a special numeric ended with % sign.

Literal Value
100% 1.0
50% 0.5

They can be used in percentage operations

Boolean

KFormula operates the expressions as numbers, so true is 1, false is 0

Variable

Variable name must begin with $ or % sign, followed by any unicode characters and underscores/dots.

Example or valid variable names:

$test
$2pi
%discount
$record.value
$ตัวแปร
$変数

Variable name that begin with % sign can be used in percentage operations

Supported operators

Operation Operator
Add +
Subtract -
Multiply *
Divide /
Exponent ^
Modulo mod
Logical OR or
Logical AND and
Logical NOT not or !
Equal = or ==
Not Equal != or <>
Greater than >
Less than <
Greater than or equal >=
Less than or equal <=

Special Operators

IN range

Syntax:

<expr> in <begin>...<end>

Returns true if <expr> is within the range starting from <begin> to <end>

It is equivalent to:

(<expr> >= <begin>) and (<expr> <= <end>)

Example:

5 in 5..20

Returns true

20 in 5..20

Returns true

4 in 5..20     

Returns false

21 in 5..20

Returns false


NOT IN range

Syntax:

not in <begin>...<end>
!in <begin>...<end>

Returns true if <expr> is NOT within the range starting from <begin> to <end>

It is equivalent to:

(<expr> < <begin>) or (<expr> > <end>)

Example:

5 not in 5..20
5 !in 5..20

Returns false

20 not in 5..20
20 !in 5..20

Returns false

4 not in 5..20
4 !in 5..20        

Returns true

21 not in 5..20
21 !in 5..20

Returns true


IN set

Syntax:

<expr> in [<elements>]

Returns true if <expr> is a member of the set specified by <elements>

Example:

5 in [5,10,15,20]

Returns true

20 in [5,10,15,20]

Returns true

4 in [5,10,15,20]     

Returns false

12 in [5,10,15,20]     

Returns false

21 in [5,10,15,20]     

Returns false


NOT IN set

Syntax:

not in [<elements>]
!in [<elements>]

Returns true if <expr> is NOT a member of a set specified by <elements>

Example:

5 not in [5,10,15,20]
5 !in [5,10,15,20]

Returns false

20 not in [5,10,15,20]
20 !in [5,10,15,20]

Returns false

4 not [5,10,15,20]
4 !in [5,10,15,20]      

Returns true

12 not [5,10,15,20]
12 !in [5,10,15,20]      

Returns true

21 not [5,10,15,20]
21 !in [5,10,15,20]

Returns true


Ternary

Syntax #1:

if <condition> [then] <true expression> [else <false expression>]

Syntax #2 (function call-like):

IF(<condition>,<true expression>[,<false expression>])

The <true expression> will be evaluated only if the <condition> is true, otherwise <false expression> will be evaluated (if provided).

If the <false expression> is omitted and the <condition> is false it will return 0

Note: Both the <true expression> and <false expression> will be evaluated lazily.

For the syntax #1, then can be omitted, but not recommended as it would reduce readability.

Example

Assuming that $fee1 is 60 and $fee2 is 30

Syntax #1:

if $weight > 200 then $fee1 else if $weight > 100 then $fee2

Syntax #2:

IF($weight > 200, $fee1, IF($weight > 100, $fee2))

Mixed syntax #1:

IF($weight > 200, $fee1, if $weight > 100 then $fee2)

Mixed syntax #2:

if $weight > 200 then $fee1 else IF($weight > 100, $fee2)

Returns 60 if $weight is greater than 200

Returns 30 if $weight is greater than 100

Returns 0 if $weight is less than or equal 100


Percentage operations

Any variables with name started with % sign and any numbers that ended with % sign are considered as percentage values and can be used in adding and subtracting percentage from a value.

Only + and - work with right hand side percentage value, e.g:

Expression Result
30 + 50% 45
400 - 50% 200
120 + %fifty 180
400 - %discount 300

Assuming that %fifty variable is 0.5 and %discount is 0.25

Note: Any other operations on percentage values will result in normal arithmetic operations and the result is still percentage value.

Usage

Simple usage

The simplest way is to use Formula class, it has built-in functions ready for use.

Kotlin:

val code = "1+1"
val fx = Formula()
val program = fx.compile(code)
val result = program.eval()
println("result is $result")

Java:

final String code = "1+1";
final Formula fx = new Formula();
final RootExpr program = fx.compile(code);
final BigDecimal result = program.eval();
System.out.println("result is " + result.toPlainString())

Adding constant

Kotlin:

val fx = Formula().apply {
    addConstant("VALUE", 10)
}

Java:

final Formula fx = new Formula();

fx.addConstant("VALUE", 10);

Adding variable

Kotlin:

val fx = Formula().apply {
    addVariable("\$test", 300)
    addVariable("%fifty", 0.5)
}

Java:

final Formula fx = new Formula();

fx.addVariable("$test", 300);
fx.addVariable("%fifty", 0.5);

Adding external/dynamic variable

It is possible to use a runtime variable in your formula. To do so, you can retreive the runtime variable with a callback function, then use it by "$external" (Kotlin) or "$external" (Java).

Kotlin:

val fx = Formula().apply { 
    addExternalVariable("\$external") {
        getValue().toBigDecimal()
    }
}

Java (with Lambda):

final Formula fx = new Formula();

fx.addExternalVariable("$external", s -> BigDecimal.valueOf(getValue()));

Built-in functions

abs

Returns the absolute value.

Syntax:

abs(value)

Example:

abs(-10)

Returns 10

sum

Returns the sum of all values.

Syntax:

sum(...<values>)

Example:

sum(1,2,3,4,5)

Returns 15

average

Returns the average of all values.

Syntax:

average(...<values>)

Example:

average(1,2,3,4,5)

Returns 3

floor

Returns value rounded down to the nearest integer.

Syntax:

floor(<value>)

Example:

floor(5.321)

Returns 5

ceil

Returns value rounded up to the nearest integer.

Syntax:

ceil(<value>)

Example:

ceil(5.321)

Returns 6

round

Returns value rounded to precision.

Syntax:

round(<value>, [precision=0])

Example:

round(5.321)

Returns 5

round(5.566)

Returns 6

round(5.566, 1)

Returns 5.6

min

Returns the minimum value.

Syntax:

min(...<values>)

Example:

min(5,1,4,2,3)

Returns 1

max

Returns the maximum value.

Syntax:

max(...<values>)

Example:

max(5,1,4,2,3)

Returns 5

clamp

Clamps the value within the inclusive lower and upper bounds.

Syntax:

clamp(<value>, <lower>, <upper>)

Example:

clamp(5, 10, 20)

Returns 5

clamp(25, 10, 20)

Returns 20

clamp(15, 10, 20)

Returns 15

sqrt

Returns the square root of a number.

Syntax:

sqrt(<value>)

Example:

sqrt(9)

Returns 3

sqrt(2)

Returns 1.414213562373095

Adding function

Function can be added to the Formula instance by calling addFunction method.

Kotlin addFunction method definition:

fun addFunction(name: String, vararg signatures: String, handler: FunctionCallHandler)

Java addFunction method definition:

public void addFunction(String name, String[] signatures, FunctionCallHandler handler)
Function without parameter

Kotlin:

val fx = Formula().apply {
    addFunction("one") {
        1.toBigDecimal()
    }
}

Java (with Lambda):

final Formula fx = new Formula();

fx.addFunction("one", new String[]{}, args -> {
    return BigDecimal.valueOf(1);
});

Expression:

one()

Evaluate to 1

one() + one()

Evaluate to 2

Function with parameters

To add a function with parameters, just specify a list of parameter names via the addFunction method.

The parameter can then be accessed by name via args parameter in handler.

Kotlin:

val fx = Formula().apply {
    addFunction("add", "a", "b") { args ->
        args["a"] + args["b"]
    }
}

Java (with Lambda):

final Formula fx = new Formula();

fx.addFunction("add", new String[]{"a", "b"}, args -> {
    BigDecimal a = args.get("a").eval();
    BigDecimal b = args.get("b").eval();

    return a.add(b)
});

Example:

add(1,2)

Returns 3

add(1,add(2,4))

Returns 7

Default parameters

Parameters can have default values by specifying parameter names using <name>=<value> format, e.g. param2=100

Kotlin:

val fx = Formula().apply {
    addFunction("add", "a", "b=1") { args ->
        args["a"] + args["b"]
    }
}

Java (with Lambda):

final Formula fx = new Formula();

fx.addFunction("add", new String[]{"a", "b=1"}, args -> {
    BigDecimal a = args.get("a").eval();
    BigDecimal b = args.get("b").eval();

    return a.add(b)
});

Example:

add(1)

Returns 2

add(1,2)

Returns 3

Variadic parameters

Sometimes, the number of parameters is unknown, you can prefix the parameter name with ... to make it variadic. (Also known as Rest parameters)

Kotlin:

val fx = Formula().apply {
    addFunction("accumulate", "init", "...all") { args ->
        val all = args["all"].rest.eval()
        args["init"] + all.reduce { sum, v -> sum.add(v) }
    }
}

Java (with Lambda):

final Formula fx = new Formula();

fx.addFunction("accumulate", new String[]{"init", "...all"}, args -> {
    BigDecimal init = args.get("init").eval();

    List<Expr> exprs = args.get("all").getRest();

    return exprs.stream().map(Expr::eval).reduce(init, (sum, v) -> sum.add(v));
});

Extending

TBD

You might also like...
Kotlin jvm + android packages for bdk-ffi

bdk-kotlin This project builds .jar and .aar packages for the jvm and android platforms that provide Kotlin language bindings for the bdk library. The

Kotlin SDK for Jellyfin supporting Android and the JVM.

Jellyfin Kotlin SDK Part of the Jellyfin Project The Jellyfin Kotlin SDK is a library implementing the Jellyfin API to easily access servers. It is cu

Asynchronous Spring Initializr API wrapper for Kotlin/JVM

initializr-kt Asynchronous Spring Initializr API wrapper for Kotlin/JVM. This library provides the simplest DSL for initializing Spring Boot projects

A simple, lightweight, non-bloated redis client for kotlin and other JVM languages

rekt is a lightweight, non-bloated redis client, primarily written for the kotlin programming language, while also supporting other JVM-based languages, such as Java, Scala, and obviously way more.

Depenject - a lightweight, minimalistic dependency injection library for Kotlin/JVM.

depenject depenject is a lightweight, minimalistic dependency injection library for Kotlin/JVM. Our goal is similar to flavor's to simplify the usage

StarkNet SDK for JVM languages (java, kotlin, scala)

☕ starknet jvm ☕ StarkNet SDK for JVM languages: Java Kotlin Scala Clojure Groovy Table of contents Documentation Example usages Making synchronous re

Archimedes's implementation for the Java Virtual Machine (JVM)

Archimedes Give me a place to stand, and I shall move the earth. Archimedes's implementation for the Java Virtual Machine (JVM) Building From Source T

Utility library dedicated for functional & non-functional codebases to simplify modelling of success and failure responses for the JVM languages 🔀

Expressible Utility library, part of the panda-lang SDK, dedicated for functional codebases that require enhanced response handling. Express yourself

JVM Open Asset Import Library (Assimp)

assimp JVM porting of Assimp This port is being written trying to stick as much as possible close to the C version in order to: minimize maintenance t

Owner
Wittawas Nakkasem
Wittawas Nakkasem
Simulate the running route of each player on the playground, and can be timed with a stopwatch

PathView (Simulate the running route of each player on the playground, and can be timed with a stopwatch) Generally speaking, high frequency and dense

Old Driver 5 Jun 11, 2022
Running Axon Server in Testcontainers tests.

Axon Server TestContainer Running axon server in testcontainers tests. Idea Run an Axon Server docker container from within your (junit) tests. Usage

holixon 3 Nov 21, 2022
Learn how to make an app designed for single-screen devices shine when running on foldable and dual-screen devices

dcberlin21-workshop Make your app shine om foldable devices with the samples we have here. Related links SDK open-source code SDK samples (Kotlin) App

Cesar Valiente 3 Oct 26, 2021
A sample skeleton backend app built using Spring Boot kotlin, Expedia Kotlin Graphql, Reactive Web that can be deployed to Google App Engine Flexible environmennt

spring-kotlin-gql-gae This is a sample skeleton of a backend app that was built using: Spring Boot(Kotlin) Reactive Web Sprinng Data R2DBC with MYSQL

Dario Mungoi 7 Sep 17, 2022
Image Processing Engine with GUI

Image Processing Engine with GUI Imperial College London Department of Computing Third Year Software Engineer Group Project Supervisor: Dr. Pancham Sh

null 1 Jan 14, 2022
A Hello World and Template for the KorGe game engine

Korge Hello World and Template This is a Hello World and Template for the KorGe game engine. Using gradle with kotlin-dsl. You can open this project i

Kiet 0 May 1, 2022
Candroid Browser is a replacement web browser for Candroid. It is designed to replace the AOSP browser, but not Google Chrome. It will be based on a privacy friendly WebKit engine fork.

Candroid Browser Candroid Browser is a replacement web browser for Candroid. It is designed to replace the AOSP browser, but not Google Chrome. It wil

Sean P. Myrick V19.1.7.2 3 Dec 22, 2022
My own approach to what I think an Android MVVM project with Clean Architecture should look like with Dagger-Hilt as Dependency Injector engine

MVVM Project Hilt Introducción Este proyecto es mi visión particular, ni mejor ni peor (sólo una más) que cualquier otra aproximación a lo que yo enti

Antonio Fdez. Alabarce 7 Dec 16, 2022
Create libraries for all types of Kotlin projects: android, JVM, Multiplatform, Gradle plugins, and so on.

JavierSC Kotlin template Create libraries for all types of Kotlin projects: android, JVM, Multiplatform, Gradle plugins, and so on. Features Easy to p

Javier Segovia Córdoba 2 Dec 14, 2022
An implementation of MediatR on JVM for Spring using Kotlin coroutines

Kpring MediatR In this project, an attempt has been made to implement the mediator pattern on the JVM with simplicity using Kotlin with native corouti

Mahdi Bohloul 4 Aug 6, 2022