anyexpr

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 13, 2026 License: MIT Imports: 8 Imported by: 0

README

anyexpr

Go Reference CI Go Report Card codecov

A generic expression compilation and evaluation library for Go, built on expr-lang.

Why anyexpr?

expr-lang is a powerful, fast, and safe expression language for Go — it provides the parser, compiler, and virtual machine that make expression evaluation possible. anyexpr doesn't replace any of that.

What anyexpr adds is an opinionated workflow on top: a typed generic compiler, a library of common string and pattern matching functions, named expression sources, and a compile-once-run-many execution model. It's designed to reduce the boilerplate when you need to build an end-to-end pipeline for evaluating user-authored filter expressions, routing rules, or matching logic against structured data.

If you need a Go expression language, use expr-lang. If you're building a system where users write filter/match expressions and you want a batteries-included workflow around compilation, validation, and evaluation — anyexpr provides that layer.

Install

go get github.com/rhyselsmore/anyexpr

Quick Start

Define your environment struct, create a compiler, compile an expression, and evaluate it:

type Email struct {
    From    string
    Subject string
    Body    string
}

// Create a compiler parameterised on your environment type.
compiler, err := anyexpr.NewCompiler[Email]()
if err != nil {
    log.Fatal(err)
}

// Compile an expression. Field names and function calls are validated
// against the environment type at compile time.
prog, err := compiler.Compile(
    anyexpr.NewSource("invoice-filter",
        `has(Subject, "invoice") && ends(From, "stripe.com")`))
if err != nil {
    log.Fatal(err)
}

// Evaluate against a value. Programs are safe for concurrent use.
matched, err := prog.Match(Email{
    From:    "billing@stripe.com",
    Subject: "Your January Invoice",
    Body:    "...",
})
// matched == true

Features

  • Typed compilation — expr-lang validates expression field names and types against your struct at compile time. anyexpr wraps this with Go generics (Compiler[T], Program[T]) so the environment type flows through your Go code as well — no any casts or manual expr.Env() calls.
  • Built-in functionshas, starts, ends, eq, re, glob, extract, email_domain, and more. Case-insensitive by default, with x-prefixed case-sensitive variants (xhas, xstarts, etc.).
  • Compile once, run many — compiled programs are immutable and safe for concurrent use across goroutines.
  • Extensible — register custom functions with WithFunction, or override built-ins with ReplaceFunction.
  • Safe — expressions run in a sandboxed evaluator with no I/O, no imports, and no side effects.

Built-in Functions

Function Signature Description
has (s, substr) bool Case-insensitive substring match
xhas (s, substr) bool Case-sensitive substring match
starts (s, prefix) bool Case-insensitive prefix match
xstarts (s, prefix) bool Case-sensitive prefix match
ends (s, suffix) bool Case-insensitive suffix match
xends (s, suffix) bool Case-sensitive suffix match
eq (a, b) bool Case-insensitive equality
re (s, pattern) bool Case-insensitive regex match
xre (s, pattern) bool Case-sensitive regex match
glob (s, pattern) bool Case-insensitive glob match
lower (s) string Lowercase
upper (s) string Uppercase
trim (s) string Trim whitespace
replace (s, old, new) string Replace all occurrences
split (s, sep) []string Split on delimiter
words (s) []string Split on whitespace
lines (s) []string Split on newlines
extract (s, pattern) string First regex match
email_domain (addr) string Domain from email address
len (v) int Length of string, array, slice, or map

See doc/reference.md for full documentation and examples.

Custom Functions

compiler, err := anyexpr.NewCompiler[MyEnv](
    anyexpr.WithFunction("reverse", func(params ...any) (any, error) {
        s := params[0].(string)
        runes := []rune(s)
        for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
            runes[i], runes[j] = runes[j], runes[i]
        }
        return string(runes), nil
    }),
)

Eval

Match returns a bool. For expressions that return other types, use Eval:

prog, _ := compiler.Compile(
    anyexpr.NewSource("get-domain", `email_domain(Email)`))

result, err := prog.Eval(env) // result is "example.com"

Validation

Check expressions without compiling them into programs:

err := compiler.Check([]*anyexpr.Source{
    anyexpr.NewSource("rule-1", `has(Name, "alice")`),
    anyexpr.NewSource("rule-2", `starts(Name, "b")`),
})

Rules Engine

The anyexpr/rules subpackage builds on top of anyexpr with a typed when/then rule evaluation engine:

  • Typed actions — declare actions as struct fields with Action[V, E]. Values are type-checked at compile time.
  • Compile-time validation — expression errors, unknown actions, type mismatches, and cardinality violations are caught before evaluation.
  • Typed results — read results through struct fields, not string keys. Full provenance (which rule set each value).
  • Skip expressions — conditionally skip rules with a second expression, with configurable evaluation order.
  • Tracing — opt-in per-rule tracing with timing and outcomes.
  • Selectors — filter rules by tags, names, or expressions.
  • Registry — CRUD for rule definitions with on-demand compilation.
  • Testing — validate expressions, test rules in isolation, write assertions in the expression language.
  • Dispatch — route evaluation results to named handlers gated by expressions, with plans, strategies, and structured logging.

See the rules README for full documentation.

Acknowledgements

anyexpr is built entirely on expr-lang by Anton Medvedev. All expression parsing, compilation, and evaluation is handled by expr — anyexpr is a convenience layer on top. If you find this library useful, check out expr-lang and consider giving it a star.

License

MIT

Documentation

Overview

Package anyexpr is a generic expression compilation and evaluation library.

It wraps expr-lang with a typed compiler, a library of built-in string and pattern matching functions, and a compile-once-run-many execution model.

The typical workflow is:

  1. Create a Compiler parameterised on your environment struct.
  2. Compile expression strings into Program values using Compiler.Compile.
  3. Evaluate programs against environment values using Program.Match or Program.Eval.

Expressions are validated at compile time against the fields and methods of the environment type, catching typos and type errors before evaluation.

Example
package main

import (
	"fmt"
	"log"

	"github.com/rhyselsmore/anyexpr"
)

func main() {
	type Email struct {
		From    string
		Subject string
		Body    string
	}

	compiler, err := anyexpr.NewCompiler[Email]()
	if err != nil {
		log.Fatal(err)
	}

	src := anyexpr.NewSource("invoice-filter",
		`has(Subject, "invoice") && ends(From, "stripe.com")`)

	prog, err := compiler.Compile(src)
	if err != nil {
		log.Fatal(err)
	}

	msg := Email{
		From:    "billing@stripe.com",
		Subject: "Your January Invoice",
		Body:    "...",
	}

	matched, err := prog.Match(msg)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(matched)
}
Output:
true
Example (Check)
package main

import (
	"fmt"
	"log"

	"github.com/rhyselsmore/anyexpr"
)

func main() {
	type Env struct {
		Name string
	}

	compiler, err := anyexpr.NewCompiler[Env]()
	if err != nil {
		log.Fatal(err)
	}

	sources := []*anyexpr.Source{
		anyexpr.NewSource("rule-1", `has(Name, "alice")`),
		anyexpr.NewSource("rule-2", `starts(Name, "b")`),
	}

	if err := compiler.Check(sources); err != nil {
		log.Fatal(err)
	}
	fmt.Println("all valid")
}
Output:
all valid
Example (CustomFunction)
package main

import (
	"fmt"
	"log"

	"github.com/rhyselsmore/anyexpr"
)

func main() {
	type Env struct {
		Value string
	}

	compiler, err := anyexpr.NewCompiler[Env](
		anyexpr.WithFunction("reverse", func(params ...any) (any, error) {
			s := params[0].(string)
			runes := []rune(s)
			for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
				runes[i], runes[j] = runes[j], runes[i]
			}
			return string(runes), nil
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	prog, err := compiler.Compile(
		anyexpr.NewSource("palindrome", `eq(Value, reverse(Value))`))
	if err != nil {
		log.Fatal(err)
	}

	matched, _ := prog.Match(Env{Value: "racecar"})
	fmt.Println(matched)
}
Output:
true
Example (Eval)
package main

import (
	"fmt"
	"log"

	"github.com/rhyselsmore/anyexpr"
)

func main() {
	type Email struct {
		From  string
		Email string
	}

	compiler, err := anyexpr.NewCompiler[Email]()
	if err != nil {
		log.Fatal(err)
	}

	prog, err := compiler.Compile(
		anyexpr.NewSource("extract-domain", `email_domain(Email)`))
	if err != nil {
		log.Fatal(err)
	}

	result, err := prog.Eval(Email{
		From:  "Alice",
		Email: "alice@example.com",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result)
}
Output:
example.com

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrDuplicateFunction is returned when a function name is registered
	// more than once on the same compiler.
	ErrDuplicateFunction = errors.New("anyexpr: duplicate function registration")

	// ErrBuiltinConflict is returned when WithFunction is called with a
	// name that matches a built-in function. Use ReplaceFunction instead.
	ErrBuiltinConflict = errors.New("anyexpr: function name conflicts with built-in")

	// ErrNotBuiltin is returned when ReplaceFunction is called with a
	// name that is not a known built-in.
	ErrNotBuiltin = errors.New("anyexpr: name is not a built-in function")

	// ErrCompile is returned when an expression fails to parse or type-check.
	ErrCompile = errors.New("anyexpr: compilation failed")

	// ErrTypeMismatch is returned when Match is called on an expression
	// that does not return bool.
	ErrTypeMismatch = errors.New("anyexpr: expression return type mismatch")
)

Functions

This section is empty.

Types

type CheckOpt

type CheckOpt func(*checkConfig)

CheckOpt configures a Check call.

type CompileOpt

type CompileOpt func(*compileConfig)

CompileOpt configures a single Compile call.

type Compiler

type Compiler[T any] struct {
	// contains filtered or unexported fields
}

Compiler compiles expression sources into programs. It is parameterised on T, the type of the environment passed to expressions at evaluation time. A Compiler is immutable after construction and safe for concurrent use.

func NewCompiler

func NewCompiler[T any](opts ...CompilerOpt) (*Compiler[T], error)

NewCompiler creates a new Compiler. Options configure custom functions and built-in overrides. Returns an error if any option fails validation (duplicate names, conflicts).

func (*Compiler[T]) Check

func (c *Compiler[T]) Check(sources []*Source, opts ...CheckOpt) error

Check validates one or more sources without producing programs. It returns the first error encountered, annotated with the source name.

func (*Compiler[T]) Compile

func (c *Compiler[T]) Compile(src *Source, opts ...CompileOpt) (*Program[T], error)

Compile compiles a single Source into a Program.

type CompilerOpt

type CompilerOpt func(*compilerConfig) error

CompilerOpt configures a Compiler.

func ReplaceFunction

func ReplaceFunction(name string, fn any) CompilerOpt

ReplaceFunction overrides a built-in function. Returns an error at compiler construction if the name is not a known built-in.

func WithFunction

func WithFunction(name string, fn any) CompilerOpt

WithFunction registers a custom function available in all expressions compiled by this compiler. The function signature must be compatible with expr-lang's function binding.

Returns an error at compiler construction if:

  • the name has already been registered
  • the name conflicts with a built-in (use ReplaceFunction instead)

type EvalOpt

type EvalOpt func(*evalConfig)

EvalOpt configures a single Eval call.

func WithEvalTrace

func WithEvalTrace(w io.Writer) EvalOpt

WithEvalTrace writes evaluation trace output to w.

type MatchOpt

type MatchOpt func(*matchConfig)

MatchOpt configures a single Match call.

func WithMatchTrace

func WithMatchTrace(w io.Writer) MatchOpt

WithMatchTrace writes evaluation trace output to w.

type Program

type Program[T any] struct {
	// contains filtered or unexported fields
}

Program is a compiled expression. It is immutable and safe for concurrent use. Programs are only produced by Compiler.Compile.

func (*Program[T]) Eval

func (p *Program[T]) Eval(env T, opts ...EvalOpt) (any, error)

Eval evaluates the expression against env and returns the raw result.

func (*Program[T]) Match

func (p *Program[T]) Match(env T, opts ...MatchOpt) (bool, error)

Match evaluates the expression against env and returns true/false. Returns ErrTypeMismatch if the expression does not return a bool.

func (*Program[T]) Name

func (p *Program[T]) Name() string

Name returns the source name provided at compilation.

func (*Program[T]) Source

func (p *Program[T]) Source() string

Source returns the original expression string.

type Source

type Source struct {
	// contains filtered or unexported fields
}

Source is the input to compilation. It pairs a name with an expression.

func NewSource

func NewSource(name, expr string, opts ...SourceOpt) *Source

NewSource creates a new Source. The name is used in error messages, logging, and tracing. The expr is the expression string to compile.

func (*Source) Expr

func (s *Source) Expr() string

Expr returns the expression string.

func (*Source) Name

func (s *Source) Name() string

Name returns the source name.

type SourceOpt

type SourceOpt func(*sourceConfig)

SourceOpt configures a Source. Reserved for future use.

Directories

Path Synopsis
Package rules is a typed rule evaluation engine built on anyexpr.
Package rules is a typed rule evaluation engine built on anyexpr.
dispatch
Package dispatch routes evaluation results to named handler functions.
Package dispatch routes evaluation results to named handler functions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL