Documentation
¶
Overview ¶
Package expr is a zero-dependency expression evaluator built on top of go/parser. It accepts the subset of Go expression syntax useful for conditions, templates, and parameter interpolation: identifiers, selectors, index expressions, arithmetic, comparisons, logical operators, calls to registered functions, and pipelines (`a | f(x)` compiles as `f(a, x)`).
expr is intentionally small and adds no external dependencies.
Evaluating an expression ¶
Compile parses an expression once and returns a *Program that can be run against many inputs. Programs are immutable and safe for concurrent evaluation.
p, err := expr.Compile("upper(user.name)",
expr.WithBuiltins(),
expr.WithFunctions(map[string]any{"upper": strings.ToUpper}),
)
v, err := p.Run(ctx, env)
Environments ¶
env may be a map[string]any, a struct, or a pointer to a struct. Identifiers resolve to map keys, struct fields, or bound methods. Callables stored in env are also invocable (see Program.Run).
Templates ¶
NewTemplate pre-compiles a `${...}` interpolator:
t, err := expr.NewTemplate("Hello ${user.name}!", expr.WithBuiltins())
out, err := t.Render(ctx, env)
Index ¶
- Constants
- Variables
- func Builtins() map[string]any
- func CollectionFuncs() map[string]any
- func IsTruthy(value any) bool
- func MathFuncs() map[string]any
- func StringFuncs() map[string]any
- type Func
- type Option
- func WithBuiltins() Option
- func WithEvalBudget(n int) Option
- func WithFieldTags(names ...string) Option
- func WithFunctions(funcs map[string]any) Option
- func WithStructTags(names ...string) Option
- func WithTemplateDelimiters(open, close string) Option
- func WithTemplateFormatter(fn func(v any) (string, bool)) Option
- type Program
- type Template
- type TemplateSegment
Constants ¶
const MaxEvalDepth = 256
MaxEvalDepth bounds the recursion depth of Program.Run. Expressions whose AST nests deeper return ErrEvaluate. 256 is enough for any hand-written expression and keeps the Go stack well under 1 MiB.
const MaxSourceLength = 64 * 1024
MaxSourceLength is the maximum number of bytes Compile will accept. Longer inputs return ErrCompile without invoking the Go parser, which protects against adversarial nesting depths that could exhaust the parser's own stack.
Variables ¶
var ErrCompile = errors.New("expr: compile error")
ErrCompile wraps parse failures so callers can match with errors.Is.
var ErrEvaluate = errors.New("expr: evaluate error")
ErrEvaluate wraps runtime failures so callers can match with errors.Is.
Functions ¶
func Builtins ¶
Builtins returns a fresh copy of the default builtin function set made available to every expression. The returned map is owned by the caller and safe to mutate.
The defaults are chosen to be small, deterministic, side-effect free, and useful for typical condition/template work:
len(v) rune count for strings; element count for
slice/array/map/chan
string(v) stringified form of v
int(v) numeric conversion to int64; strings parse strictly
as base-10 integers
float(v) numeric conversion to float64; strings parse strictly
bool(v) truthiness check (matches IsTruthy)
contains(h, n) substring for strings, element membership for
slices/arrays (using loose numeric equality), or
key presence for string-keyed maps
has(m, k) true if map m has key k; errors if m is not a map
keys(m) sorted string keys of a map
entries(m) sorted key-value pairs of a string-keyed map; each
element is map[string]any{"key": k, "value": v}
lower(s), upper(s) case conversion
sprintf(fmt, ...) fmt.Sprintf-style formatting with cycle guards
if(cond, then, else) is not in this map: it is a special form (always available, lazily evaluated) — see higher_order.go. Opt-in extension sets live in MathFuncs, StringFuncs, and CollectionFuncs.
func CollectionFuncs ¶ added in v1.1.0
CollectionFuncs returns the opt-in list helper set. Like Builtins, the returned map is a fresh copy owned by the caller. Register it with WithFunctions.
first(xs), last(xs) first/last element; nil for empty or nil lists
sum(xs) numeric sum; int64 when every element is
integral, float64 otherwise; empty → 0
slice(xs, i, j) elements [i, j) of a list, or the rune range
of a string; negative indices count from the
end and out-of-range bounds clamp
sort(xs) ascending copy; all numbers (numeric order) or
all strings (lexicographic); mixed types error
reverse(xs) reversed copy; never mutates input
func IsTruthy ¶
IsTruthy reports whether a Go value should be treated as truthy in expr conditionals. The rules are:
- nil: false
- bool: itself
- numeric: non-zero is truthy
- string: non-empty
- slices, arrays, maps: non-empty is truthy
- chan, func, interface, pointer: non-nil is truthy
- anything else: truthy
This is also exposed as the bool() builtin. Note that string content is not inspected; bool("false") is true because the string is non-empty. Callers who need to parse boolean strings should do so explicitly.
func MathFuncs ¶ added in v1.1.0
MathFuncs returns the opt-in numeric helper set. Like Builtins, the returned map is a fresh copy owned by the caller. Register it with WithFunctions:
expr.Compile(src, expr.WithBuiltins(), expr.WithFunctions(expr.MathFuncs()))
The groups are separate from Builtins so a minimal sandbox stays minimal: hosts opt in to exactly the surface they want.
min(a, ...), max(a, ...) smallest/largest argument; int64 when every
argument is integral, float64 otherwise
abs(n) absolute value; int64 in, int64 out
floor(v), ceil(v), round(v) float64 results; integers pass through
func StringFuncs ¶ added in v1.1.0
StringFuncs returns the opt-in string helper set. Like Builtins, the returned map is a fresh copy owned by the caller. Register it with WithFunctions.
trim(s) strings.TrimSpace split(s, sep) list of substrings join(xs, sep) concatenate a list of strings replace(s, old, new) strings.ReplaceAll startsWith(s, prefix) strings.HasPrefix endsWith(s, suffix) strings.HasSuffix
Types ¶
type Func ¶
Func is a native dispatcher signature. Functions registered with this type skip the reflect-based call path entirely: the evaluator invokes them directly with the evaluated argument list and the current context. Use this form for hot builtins or any user function where an extra allocation per call matters.
If the function needs arity or type validation it must do it itself; the dispatcher performs no checks.
type Option ¶
type Option func(*compileConfig)
Option configures a Compile or NewTemplate call. Options are applied in order, so a later WithFunctions overrides an earlier WithBuiltins for any shared name.
func WithBuiltins ¶
func WithBuiltins() Option
WithBuiltins registers the standard builtin function set returned by Builtins. Pair it with WithFunctions to extend or override individual entries; options apply in the order they are passed, so a later WithFunctions wins over an earlier WithBuiltins for any shared name.
func WithEvalBudget ¶ added in v1.1.0
WithEvalBudget bounds the total work a single Run may perform. Each AST node evaluated — including every per-element re-evaluation of a higher-order form's predicate — consumes one unit; when the budget is exhausted, Run fails with an ErrEvaluate-wrapped error.
MaxSourceLength and MaxEvalDepth bound memory and stack, but not CPU: nested higher-order forms multiply, so a ~60-byte expression like map(xs, map(xs, map(xs, it))) over a 10k-element list is 10^12 predicate evaluations. Context deadlines cap wall-clock time but still let one expression burn a core for the full timeout; a budget makes hostile-input behavior deterministic and cheap to reject.
n <= 0 means unlimited (the default). The budget counts evaluator steps, not time spent inside registered functions — bound those separately (see docs/guides/sandboxing.md).
func WithFieldTags ¶ added in v0.0.2
WithFieldTags is an alias for WithStructTags.
func WithFunctions ¶
WithFunctions registers the given functions as callable identifiers in the compiled expression. Entries merge into (and override) whatever is already registered by earlier options.
Functions may take any Go types as arguments; expr converts evaluated values to the declared parameter types at call time. Return signatures of `T`, `(T, error)`, and `()` are supported. Variadic functions are also supported.
Invalid registrations — a nil entry, a value that is not a Go function, or a function with an unsupported signature (more than two return values, or a second return that is not error) — cause Compile to fail with ErrCompile. Surfacing the mistake at load time is the point of the Compile/Run split; before this check the error would hide until the expression first called the bad entry.
func WithStructTags ¶ added in v0.0.2
WithStructTags enables struct field lookup by the named struct tags. Tags are checked in the order provided before falling back to the Go exported field name. Tag options after a comma are ignored, so `json:"name,omitempty"` resolves as `name` and `json:",omitempty"` falls back to the Go field name.
The option is opt-in; without it, struct fields resolve only by Go field name, preserving expr's default behavior. `expr:"-"` hides a field when the expr tag is configured; duplicate resolved names return an ambiguity error at evaluation time.
func WithTemplateDelimiters ¶ added in v1.2.0
WithTemplateDelimiters replaces the default `${` / `}` expression delimiters for a NewTemplate call. The opener must end with at least one `{` and the closer must be the matching run of `}`:
tmpl, err := expr.NewTemplate(src, expr.WithTemplateDelimiters("${{", "}}"))
A GitHub-Actions-style `${{ expr }}` opener avoids collisions with shell parameter expansion and JavaScript template literals, so text like `echo ${HOME}` passes through as a literal. The `$$` escape applies only when the opener starts with `$`.
The option applies only to NewTemplate; passing it to Compile fails with ErrCompile.
func WithTemplateFormatter ¶ added in v1.2.0
WithTemplateFormatter installs a custom value renderer for a NewTemplate call. It runs first for every interpolated result, including nil and strings; returning false falls through to the default rendering chain (nil to empty, strings pass through, composites to JSON, everything else fmt-style).
expr.WithTemplateFormatter(func(v any) (string, bool) {
if t, ok := v.(time.Time); ok {
return t.Format(time.Stamp), true
}
return "", false
})
The option applies only to NewTemplate; passing it to Compile fails with ErrCompile.
type Program ¶
type Program struct {
// contains filtered or unexported fields
}
Program is a compiled expression. Programs are immutable and safe for concurrent evaluation across goroutines.
func Compile ¶
Compile parses an expression once for repeated evaluation. The returned Program is immutable and safe for concurrent use. Input longer than MaxSourceLength is rejected without calling the parser.
Functions are registered via Options and baked into the returned Program. JSON-style array and object literals ([1, 2, 3], {"k": v}) are always accepted; see docs/reference/spec.md for the exact rules.
func (*Program) Identifiers ¶ added in v1.1.0
Identifiers returns the sorted, de-duplicated set of top-level identifier names the expression references — the names Run will try to resolve through the environment. Hosts can use it to validate an expression against a known env shape at load time, to track dependencies for cache invalidation, or to decide which values are worth computing before a Run.
Excluded from the result:
- the literals true, false, and nil
- `it` and `index` where they are bound by an iterating higher-order form (map, filter, flatMap, any, all, find, count, sortBy)
- the named element binding of a three-arg form (the `o` in `filter(orders, o, o.paid)`), both the binding identifier itself and references to it inside the body
- names registered via WithFunctions / WithBuiltins, which resolve without the env
- special-form names (map, filter, try, if, ...) in call position, which are always available
The analysis is static and therefore best-effort in one corner: env entries can shadow registered functions and special forms at Run time, so an excluded name may still be read from the env when a host deliberately shadows it. Every name that can only resolve through the env is always included.
func (*Program) Run ¶
Run evaluates the program against env. env may be a map[string]any, a struct, or a pointer to a struct — identifier lookups resolve to map keys, struct fields, or bound methods (in that order of preference). Identifiers not found in env are then looked up against the functions registered via WithFunctions / WithBuiltins. The literals true, false, and nil are recognized directly. Any unsupported syntax node (slice expressions, type assertions, function literals, channel operations, etc.) returns an error wrapping ErrEvaluate.
Env values that are themselves Go functions are callable from the expression: `f(x)` first looks up `f` in env, and if it finds a function value it is invoked through the same reflect-based dispatch used for WithFunctions-registered functions. Prefer registering functions via Options when possible — those go through a faster prepared path and participate in "did you mean…" diagnostics — but putting callables in env is a useful escape hatch for per-request closures or hosts that want to rebind helpers on every Run.
expr checks ctx.Err() at the top of every AST node, so any pure-expression evaluation exits within one node of cancellation. Registered functions whose first parameter is context.Context receive ctx automatically; well-behaved callees can then cancel their own work. expr does not forcibly terminate user code that ignores ctx — Go provides no mechanism to kill a goroutine, and recovering from a blocked callee would leak it. Passing a nil ctx falls back to context.Background.
type Template ¶
type Template struct {
// contains filtered or unexported fields
}
Template is a pre-compiled `${...}` string interpolator. Each expression inside the template is compiled once by NewTemplate and re-evaluated per call.
Parsing uses go/scanner to find the closing `}` of each expression, so expression bodies may contain braces inside string, rune, and comment tokens as well as nested composite literals like `map[string]any{"k": 1}` without tripping the template parser.
Escaping: `$$` is always rewritten to a literal `$`. `$${name}` therefore emits the literal text `${name}`. A bare `$` that is not followed by `$` or `{` is emitted verbatim, so `$5` or `$foo` pass through unchanged.
The `${` / `}` delimiters can be replaced per template with WithTemplateDelimiters; the `$$` escape applies only when the configured opener starts with `$`.
func NewTemplate ¶
NewTemplate parses raw and pre-compiles every `${...}` expression with the given Options. Strings without any `${...}` are accepted and become constant templates that return raw unchanged from Template.Render.
func (*Template) Render ¶
Render evaluates each `${...}` expression against env and concatenates the results with the surrounding literal text. env follows the same rules as Program.Run: it may be a map[string]any, a struct, or a pointer to a struct. Templates with no expressions return the raw source unchanged without invoking any script.
Value rendering rules for each `${...}` result (a formatter installed with WithTemplateFormatter runs first and can override any of them):
- nil renders as the empty string. Optional fields that resolve to nil silently produce no output rather than the literal "<nil>". This matches the convention used by Jinja, Liquid, Handlebars, and most text-oriented interpolators, and means callers do not need a null-coalescing operator for the common case of optional values.
- string values pass through unchanged.
- maps, slices, arrays, and structs render as compact JSON with HTML escaping disabled, so `${config}` interpolates as `{"retries":3}` rather than Go's map syntax. A composite that marshals to a JSON string (time.Time, custom marshalers) renders as the string itself, unquoted. Values JSON cannot represent (cycles, channels, funcs) fall back to the fmt-style formatting used for scalars.
- Everything else is formatted with fmt.Sprintf("%v", v).
The nil-to-empty rule means a template cannot distinguish "value was nil" from "value was the empty string" in its output. Callers that need that distinction should wrap the expression in something that returns a sentinel.
func (*Template) Segments ¶ added in v1.2.0
func (t *Template) Segments() []TemplateSegment
Segments returns the parsed segments of the template in source order: literal runs and compiled expressions, each carrying its offset and line:column position in the raw source. Hosts use it for syntax highlighting, per-expression variable extraction (via TemplateSegment.Program's Identifiers method), and live validation, without re-implementing the template scanner.
type TemplateSegment ¶ added in v1.2.0
type TemplateSegment struct {
Literal string
Source string
Offset int
Line int
Column int
// Program is the compiled expression for expression segments,
// nil for literal segments. Hosts can call Identifiers() on it
// for per-segment variable extraction, editor hints, or live
// validation.
Program *Program
}
TemplateSegment is the public projection of one parsed template segment, exposed by Template.Segments. A segment is either a literal run of text (Literal non-empty, Source empty) or an interpolated expression (Source holds the expression body).
Offset is the byte offset of the segment's start in the raw template: the first byte of the literal text, or the first byte of the opening delimiter for expression segments. Line and Column are the 1-based line and byte-based column of that offset. For literal segments containing `$$` escapes, Literal holds the decoded text, which may be shorter than the raw source it spans.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
expr
command
|
|
|
examples
|
|
|
basic
command
|
|
|
compile_once
command
|
|
|
env_shapes
command
Env shapes: one expression read against a struct, a map, and a pointer-to-struct wrapped view.
|
Env shapes: one expression read against a struct, a map, and a pointer-to-struct wrapped view. |
|
funcs
command
|
|
|
higher_order
command
Higher-order builtins: map, filter, any, all, find, count.
|
Higher-order builtins: map, filter, any, all, find, count. |
|
higher_order_patterns
command
Higher-order patterns: validation bag, summary object, filter+map projection, named bindings, flatMap, sortBy, and entries.
|
Higher-order patterns: validation bag, summary object, filter+map projection, named bindings, flatMap, sortBy, and entries. |
|
register_functions
command
Registering Go functions with WithFunctions.
|
Registering Go functions with WithFunctions. |
|
sandboxing
command
Sandboxing: compile an untrusted policy expression, run it with a deadline, and catch both eval errors and cancellation.
|
Sandboxing: compile an untrusted policy expression, run it with a deadline, and catch both eval errors and cancellation. |
|
structs
command
|
|
|
template
command
|
|
|
templates_in_anger
command
Templates in anger: compile once, render many, with a registered `join` helper so variable-length lists render as human-readable text instead of JSON, and a formatter that overrides nil rendering.
|
Templates in anger: compile once, render many, with a registered `join` helper so variable-length lists render as human-readable text instead of JSON, and a formatter that overrides nil rendering. |
|
internal
|
|
|
jsonlit
Package jsonlit rewrites JSON-style array and object literals into the Go composite-literal syntax that go/parser.ParseExpr accepts.
|
Package jsonlit rewrites JSON-style array and object literals into the Go composite-literal syntax that go/parser.ParseExpr accepts. |
|
optaccess
Package optaccess rewrites the optional-access operators `?.` and `?[` into calls on internal sentinel functions that the evaluator dispatches as special forms.
|
Package optaccess rewrites the optional-access operators `?.` and `?[` into calls on internal sentinel functions that the evaluator dispatches as special forms. |
|
require
Package require provides a minimal subset of the testify/require API so that tests in this module can run without a third-party dependency.
|
Package require provides a minimal subset of the testify/require API so that tests in this module can run without a third-party dependency. |