Skip to content

Repository files navigation

expr4k

A small, safe, typed expression language for the JVM and the browser, written in Kotlin Multiplatform.

Evaluate expressions like age >= 18 && country in ["UK", "IE"] against a context — with no code injection, type errors caught before evaluation, and clear messages that point at the source.

Status: working, pre-release. The full pipeline is in with a public Expr API — lexer, parser, static type checker and tree-walking evaluator, all tested, with a REPL/CLI. The same code compiles to JVM and JS (Kotlin Multiplatform), and a browser playground runs the library compiled to JS. Not yet published to Maven Central.

Why

Rule engines, feature-flag conditions and search filters keep needing to "evaluate this little expression, safely". The usual options (SpEL, MVEL, JEXL) are heavy, reflection-based, or unsafe. expr4k aims to be small, safe by construction (no arbitrary code execution), typed, and pleasant to embed.

Example

// compile once (lex + parse + type-check), evaluate many times
val expr = Expr.compile("age >= 18 && country in ['UK', 'IE']")
val ok: Boolean = expr.evalBoolean(mapOf("age" to 21, "country" to "UK"))   // true

Pass a schema to turn type mismatches into compile-time errors instead of runtime ones:

Expr.compile("age && true", mapOf("age" to Type.NUMBER))
// throws TypeException: operator '&&' expects a boolean but got number at 1:1

Every failure — lexing, parsing, type checking or evaluation — is an Expr4kException carrying the source position it refers to. A compiled Expr is immutable and safe to cache and share across threads.

CLI

A small REPL and one-shot evaluator ship alongside the library:

./gradlew runCli --args="2 + 3 * 4"    # one-shot → 14.0

./gradlew runCli                       # REPL
> :let age 21
age = 21.0 : number
> age >= 18 && age < 65
true : boolean
> :help                                # :let, :type, :vars, :quit

Design

A hand-written pipeline, no parser generator — owning the parser is the point:

source ──▶ Lexer ──▶ tokens ──▶ parser ──▶ typed AST ──▶ type check ──▶ evaluator ──▶ value

Benchmark

./gradlew :benchmark:jmh runs the JMH microbenchmarks (in the :benchmark module — a small JVM module that consumes the library's JVM target). Compiling a predicate once and reusing it is far cheaper than re-parsing on every evaluation:

approach ns / eval throughput
compile once, eval many ~95 ~10.5M / sec
compile on every eval ~1,590 ~0.63M / sec

So reusing a compiled Expr is roughly 17x faster per evaluation (JMH average time, 2 forks × 5 iterations). Figures are machine-dependent — run it yourself.

Building

./gradlew build

Requires a JDK; the Gradle wrapper fetches the rest.

Licence

MIT.

About

A small, safe, typed expression language in Kotlin Multiplatform: a hand-written lexer, parser, static type checker and tree-walking evaluator behind a one-line API. The case study runs the library live in the browser, compiled to JavaScript.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages