expr-cls is a minimal, experimental implementation of a high-performance string expression compiler and runtime for Go.
The core idea is to build expressions as chains of strictly-typed Go closures (functions), rather than interpreting bytecode on a virtual machine.
This architecture enables extremely fast compilation and execution, zero allocations during evaluation, and a flexible, extensible environment system.
Environment:
At compile time, you define an environment describing:
- Supported unary and binary operators for the parser (not implemented yet; currently a fixed set is used).
- Complex constructs for the parser (e.g., ternary operator, arbitrary typed literals; planned).
- Overload registrations for unary and binary operators.
- Overload registrations for functions.
- Constant registrations.
- Variable type registrations.
- Variable source type registrations (struct fields as variables).
Extensibility:
You can create separate packages with ready-made environments for domain-specific tasks (e.g., matrices, complex numbers, statistics, geospatial, etc).
See example
for a current example of environment definition and usage.
Performance:
Expressions compile and execute extremely quickly at runtime.
Compiled expressions are strictly typed.
package example_test
import (
"fmt"
exprcls "github.com/guamoko995/expr-cls"
// Using the example environment
_ "github.com/guamoko995/expr-cls/tests/example/def_env"
)
// CompileAndCalcExample demonstrates how to compile and evaluate expressions
// using the expr-cls package.
func Example() {
// Define a data structure containing input variables for our expression.
type InputData struct {
A int
B int
}
// Parse and compile the expression "3 + A * B".
prog, err := exprcls.Compile[InputData, float64]("3 + A * -B + sin(pi/2)")
if err != nil {
fmt.Println("Error:", err)
return
}
// Provide input data for evaluating the expression.
input := InputData{A: 7, B: 10}
// Evaluate the expression using the provided input data.
result := prog(input)
// Print the computed result.
fmt.Println(result)
// Output: -66
}
- Struct fields as variables in expressions (via registration).
- Operator overloading for custom types (operator set is fixed by parser, not extendable yet).
- Function overloading and custom function registration.
- Constant registration.
- Strict typing of compiled expressions.
- Extremely fast compilation and evaluation (see benchmarks below).
- MVP: Some features and built-ins from original expr-lang/expr are disabled or not implemented.
You can find the benchmark source in tests/benchmarks
.
Measures the time and resource usage to compile an expression (benchmark code):
goos: linux
goarch: amd64
pkg: github.com/guamoko995/expr-cls/tests/benchmarks
cpu: AMD Ryzen 5 5600H with Radeon Graphics
BenchmarkCompile/expr-cls-12 661381 1802 ns/op 1344 B/op 31 allocs/op
BenchmarkCompile/expr-12 107227 10928 ns/op 10351 B/op 76 allocs/op
BenchmarkCompile/cel-go-12 33639 36255 ns/op 24404 B/op 355 allocs/op
PASS
ok github.com/guamoko995/expr-cls/tests/benchmarks 3.591s
expr-cls compiles more than 6x faster and with 8x less memory allocation than expr-lang/expr.
Measures the time and resource usage to repeatedly evaluate a compiled expression (benchmark code):
expression: "X+(6.0*Y)"
params:
X=3
Y=5
expr-cls result: 33
expr result: 33
cel-go result: 33
goos: linux
goarch: amd64
pkg: github.com/guamoko995/expr-cls/tests/benchmarks
cpu: AMD Ryzen 5 5600H with Radeon Graphics
BenchmarkСalc/expr-cls-12 132511530 9.015 ns/op 0 B/op 0 allocs/op
BenchmarkСalc/expr-12 6529965 172.0 ns/op 136 B/op 6 allocs/op
BenchmarkСalc/cel-go-12 4484371 267.9 ns/op 368 B/op 6 allocs/op
PASS
ok github.com/guamoko995/expr-cls/tests/benchmarks 3.529s
expr-cls evaluates expressions ~18x faster and with zero allocations.
- Parsing: Expressions are parsed into AST nodes. The set of operators/constructs is currently fixed.
- Environment: Holds operator/function/constant/variable builders. Easily extended. See
example
for practical setup. - Building: AST is compiled into a closure chain (Go functions), not bytecode.
- Evaluation: The compiled closure chain receives strictly-typed input (struct) and returns strictly-typed output.
To see how to register types, constants, functions, and operator overloads, refer to the example
.
- Only struct variable sources supported (for now).
- Operators cannot be extended (only overloaded).
- Some complex constructs (e.g. ternary operator, custom literals) are planned but not implemented.
- Error handling and reporting are minimal.
- More tests and environments are planned.
- Parser extensibility: declarative operator/construct definition is a future goal.
This README summarizes the ideas and experimental architecture of expr-cls.
Feedback and contributions are welcome.