expr
A fast, type-safe expression language and evaluation engine embeddable directly in Go programs.
Repository Health
Technical Analysis
Expr is a Go-centric expression language for evaluating dynamic conditions, rules, and configuration logic without shipping a full scripting runtime. Instead of tree-walking a generic AST at runtime, Expr statically type-checks expressions against the caller’s real Go structs and maps at compile time, then compiles them to bytecode that runs on a small, purpose-built stack-based virtual machine.
The project is used in production by companies including Google, Uber, GoDaddy, and Argo to power business-rule engines, access-control policies, and dynamic configuration systems where end users (not developers) need to define conditional logic safely. Its combination of compile-time type errors, a side-effect-free execution model, and guaranteed termination make it well suited for evaluating untrusted or user-authored expressions inside a larger Go application.
What You Get
- A compact expression syntax (comparisons, boolean logic, string/array operations, closures like
all,filter,map) for writing dynamic conditions and transformations - Compile-time static type checking against your actual Go structs, maps, and functions via reflection, with source-mapped error messages
- A bytecode compiler and stack-based VM (
vm/) instead of AST tree-walking, plus an optimizer pass that fuses common patterns like filter+map+sum into specialized opcodes - Guaranteed termination and side-effect-free evaluation, making it safe to run expressions written by end users or loaded from untrusted configuration
- Extension points for custom operators, functions, AST visitors/patchers, and timezone-aware date handling without forking the compiler
- A
repl/package and CLI for interactively testing expressions during development
Common Use Cases
- Letting end users define access-control or moderation rules (e.g. “user.Group in [‘admin’,‘moderator’]”) without redeploying code
- Evaluating feature-flag or rollout conditions in progressive-delivery tools (used by Argo Rollouts, keda.sh, WoodpeckerCI)
- Powering business-rule engines for pricing, fraud, or eligibility logic where rules change more often than code
- Filtering, transforming, or scoring collections dynamically (e.g.
all(tweets, len(.Content) <= 240)) inside ETL or data-processing pipelines - Customizing middleware or routing behavior in frameworks and gateways (e.g. WunderGraph Cosmo) via user-supplied expressions
Under The Hood
Architecture
Expr is structured as a classic multi-stage compiler pipeline with clean separation of concerns: parser/ (with its own lexer subpackage) tokenizes and parses expression source into an AST defined in ast/node.go; checker/checker.go performs reflection-based static type checking against the caller-supplied environment using a “nature” type system (checker/nature); optimizer/ applies a series of independent AST-rewrite passes (constant folding, filter/map fusion, array “in” membership, sum/count specialization) each in its own file; compiler/compiler.go then emits bytecode consumed by vm/vm.go, a stack-based virtual machine with opcodes in vm/opcodes.go and typed helpers in vm/runtime. ast/visitor.go’s visitor pattern underlies both the optimizer and the patcher/ package (WithContext, WithTimezone, OperatorOverloading), which rewrite the AST before compilation. Because parser, checker, optimizer, compiler, and patchers all depend directly on the shared ast.Node shape, that type is the highest-blast-radius abstraction in the codebase; the rest of the pipeline is independently testable stage by stage.
Tech Stack
Expr is a zero-runtime-dependency Go module (go.mod declares only go 1.18, no external packages), relying entirely on the standard library (reflect, fmt, errors, time). Reflection-based type introspection in checker/nature is the core integration mechanism, letting arbitrary Go structs and maps serve as expression environments without code generation. CI is driven by GitHub Actions (test.yml, check.yml, build.yml, diff.yml) plus a dedicated fuzz.yml wired into OSS-Fuzz for continuous fuzzing of the parser and compiler, with a coverage script (.github/scripts/coverage.mjs).
Code Quality
The repository carries an extensive test suite (dozens of _test.go files spanning ast, parser, checker, optimizer, compiler, vm, patcher, and a top-level expr_test.go with exhaustive table-driven cases plus bench_test.go for performance regressions). Errors surfaced to callers are explicit, typed values (file.Error, unwrapped via errors.As) rather than silent failures, while internal misuse (e.g. registering a non-function as a custom function) panics loudly instead of failing quietly. Package naming is idiomatic and single-responsibility (ast, vm, checker, optimizer), and continuous fuzzing against a language-implementation target is a notably rigorous quality signal beyond typical unit testing.
API Design
The public surface in expr.go is small and consistent: Compile, Run, and Eval as the three entry points, with a single Option function type (Env, AsBool, Optimize, Function, Patch, etc.) used uniformly for every configuration knob, so adding capabilities never breaks existing call sites. Getting started requires no scaffolding beyond expr.Compile(code, expr.Env(env)) and expr.Run(program, env), and compile-time errors point at the exact offending token in the source expression, which meaningfully lowers the debugging cost of a typical embedded-DSL integration.
Used by 3 apps in this directory
Argo Workflows
Devops · Data Engineering
The most popular Kubernetes-native workflow engine for orchestrating containerized DAGs, ML pipelines, CI/CD, and parallel batch jobs at scale.
Nightingale
Monitoring
Open-source alerting engine that connects to any time-series or log data source and routes alarms to 20+ notification channels with AI-assisted triage.
SigNoz
Monitoring · Analytics
Self-host your entire observability stack — logs, metrics, traces, and LLM monitoring — in one OpenTelemetry-native platform, without the Datadog bill.