predicate

package
v0.0.0-...-dcf4134 Latest Latest
Warning

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

Go to latest
Published: Jun 10, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package predicate is a small, sandboxed expression-evaluation engine for boolean predicates over named values and host-registered functions.

The package parses a strict subset of Lua expression syntax via gopher-lua's parse package (already vendored for write-path automation), walks the AST against a hard allow-list, translates it to a typed internal IR, and evaluates the IR against caller-supplied bindings.

Lifecycle

env := predicate.NewEnv()
env.DeclareVar("entity", predicate.RecordType{
    "status": predicate.StringType,
})
env.DeclareVar("current_user", predicate.RecordType{})
env.DeclareFunc("has_role", predicate.FuncSig{
    Params: []predicate.Type{predicate.RecordType{}, predicate.StringType},
    Return: predicate.BoolType,
})

prog, err := predicate.Compile(env,
    `entity.status == 'review' and has_role(current_user, 'reviewer')`)
if err != nil { ... }

b := predicate.NewBindings()
b.SetVar("entity", predicate.NewRecord(map[string]predicate.Value{
    "status": predicate.NewString("review"),
}))
b.SetVar("current_user", predicate.NewRecord(map[string]predicate.Value{}))
b.SetFunc("has_role", predicate.FuncFunc(
    func(ctx context.Context, args []predicate.Value) (predicate.Value, error) {
        return predicate.NewBool(true), nil
    },
))

v, err := prog.Eval(ctx, b)
// v.(predicate.Bool).Bool() == true

Concurrency

A *Program is immutable after Compile and is safe to Eval concurrently from multiple goroutines, each with its own Bindings. The Eval call allocates per-invocation visitor state; no caches or memoization live on *Program.

Equality semantics

Predicates use Lua-flavored equality, not Go-flavored. Comparison across most type pairs is a compile-time error; the few mixed-type pairs that compile follow this table:

a is     b is     a == b
-------- -------- --------------------------
nil      nil      true
nil      anything false
bool     bool     Go ==
number   number   float64 == (single numeric type, see below)
string   string   byte-equal (incl. null bytes)

Ordered comparisons (<, <=, >, >=) require two numbers or two strings; strings compare lexicographically (byte-wise).

Numeric model

Numbers are a single type backed by float64, matching Lua 5.1 semantics. Integer literals (1, 0xFF), float literals (1.0, 1.5e-3), and exponential forms (1e10) all parse to the same Number type. Bindings of Go int are promoted to float64 at binding time; values outside the 53-bit integer range round per IEEE 754.

Security model

The walker rejects any AST node not on the allow-list (default-reject branch on every switch). Per-field invariants are enforced beyond node type — e.g. AttrGetExpr.Key must be *StringExpr, rejecting computed attribute access entity[expr]. A compile-time depth budget (default 256) defends against stack overflow from adversarially nested expressions; a per-Eval step budget (default 10_000) defends against runtime exhaustion. Neither budget can be disabled, only raised.

The package does no I/O: no file access, no network, no goroutine spawning. It is a pure function from (Program, Bindings) to a Value.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CompileAll

func CompileAll(env *Env, sources []NamedSource, opts ...CompileOption) ([]*Program, []Issue)

CompileAll compiles every source in turn against env and returns (a) the compiled programs in input order, with a nil entry where compile failed, and (b) one Issue per failed source.

Intended for batch checking at policy-load time so a caller (e.g. an ACL loader in a future PR) can fail-fast on bad rules while keeping the successfully compiled programs ready for use — no double-parse needed (RR-LQE9).

Types

type Bindings

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

Bindings carries the runtime values a predicate evaluates against: concrete Values for each declared variable plus implementations for each declared host function.

Build a Bindings with NewBindings and the SetVar/SetFunc methods; the engine does not expose the underlying maps so callers cannot mutate them mid-evaluation. A *Bindings is safe to reuse across Eval calls but is not safe for concurrent mutation; build once and share the resulting value.

func NewBindings

func NewBindings() *Bindings

NewBindings returns an empty Bindings ready for SetVar / SetFunc.

func (*Bindings) SetFunc

func (b *Bindings) SetFunc(name string, f Func) error

SetFunc binds an implementation to a host function name.

func (*Bindings) SetVar

func (b *Bindings) SetVar(name string, v Value) error

SetVar binds a value to a variable name. Returns an error on empty name or nil value (the typed Nil value is fine; a Go nil interface is not).

type Bool

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

Bool is a concrete-typed boolean value.

func NewBool

func NewBool(b bool) Bool

NewBool constructs a Bool value.

func (Bool) Bool

func (b Bool) Bool() bool

Bool returns the underlying Go bool.

func (Bool) Type

func (Bool) Type() Type

type CompileError

type CompileError struct {
	Line   int
	Col    int
	Reason string
}

CompileError reports a failure to translate the parsed AST into the predicate IR: an unsupported AST node, a per-field invariant violation, an unknown symbol, a type mismatch, or a budget overrun.

func (*CompileError) Error

func (e *CompileError) Error() string

type CompileOption

type CompileOption func(*compileOptions)

CompileOption configures Compile.

func WithMaxDepth

func WithMaxDepth(n int) CompileOption

WithMaxDepth overrides the compile-time depth budget. Clamped to >= 1.

type Env

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

Env declares the variables and functions a predicate may reference. Build one before calling Compile.

Env is mutable until the first Compile that uses it; callers should finish declarations before any compile. Concurrent declares are not safe; declare then share.

func NewEnv

func NewEnv() *Env

NewEnv constructs an empty Env.

func (*Env) DeclareFunc

func (e *Env) DeclareFunc(name string, sig FuncSig) error

DeclareFunc registers a host function name and its signature.

The return type must be a scalar (bool, number, string, nil). Record and list return types are rejected (RR-93UN): the engine's runtime type check does not reach into a returned Record's fields, so a downstream entity.attribute access on a host-returned record could observe a typed field whose runtime type differs from the declared type. Until the type checker is extended to re-validate nested values, host functions return scalars only. (Current use cases — has_role, has_relation, count_relations — all do.)

func (*Env) DeclareVar

func (e *Env) DeclareVar(name string, t Type) error

DeclareVar registers a variable name and its type. Returns an error if name is already declared (as a var or as a func).

type EvalError

type EvalError struct {
	Reason string
}

EvalError reports a failure during Eval — a missing binding, a host function returning the wrong type, or the per-Eval step budget being exhausted.

func (*EvalError) Error

func (e *EvalError) Error() string

type EvalOption

type EvalOption func(*evalOptions)

EvalOption configures a single Eval call. Options stack left-to-right; later options override earlier ones.

func WithStepBudget

func WithStepBudget(n int) EvalOption

WithStepBudget overrides the per-Eval step budget. Must be > 0; values <= 0 are clamped to 1 so a misconfigured caller cannot disable the budget.

type Func

type Func interface {
	Call(ctx context.Context, args []Value) (Value, error)
}

Func is a host-implemented function callable from a predicate. The engine type-checks args against the declared FuncSig at compile time; an implementation receives args in the declared types and must return a Value of the declared return type.

Func is an interface (not a function type) so implementations can carry state and so the engine can pass a context.Context — needed once host functions traverse the store, hit caches, or are cancellable.

type FuncFunc

type FuncFunc func(ctx context.Context, args []Value) (Value, error)

FuncFunc adapts a Go closure into a Func. Use it when the host function has no state of its own.

func (FuncFunc) Call

func (f FuncFunc) Call(ctx context.Context, args []Value) (Value, error)

Call satisfies Func.

type FuncSig

type FuncSig struct {
	Params   []Type
	Variadic Type
	Return   Type
}

FuncSig declares a host function's parameter and return types. A non-nil Variadic indicates the function accepts zero or more extra arguments of that type after the fixed Params.

type Issue

type Issue struct {
	Name string
	Err  error
}

Issue is a single compile failure: the source it came from and the parse / compile error that produced it. Err is one of *ParseError or *CompileError; use errors.As to inspect (RR-8VKE).

type List

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

List is an ordered sequence of values, currently unused by the expression grammar (no list literals) but reachable through host functions whose return type is a list. Reserved here so the surface is stable.

func NewList

func NewList(elems []Value) List

NewList constructs a List. As with NewRecord, the returned List retains the supplied slice by reference — callers must not mutate it after the call (RR-AJS4).

func (List) Elems

func (l List) Elems() []Value

Elems returns the underlying slice. Callers must not mutate.

func (List) Type

func (List) Type() Type

type ListType

type ListType struct{ Elem Type }

ListType is a homogeneous list type descriptor.

type NamedSource

type NamedSource struct {
	Name   string
	Source string
}

NamedSource pairs a predicate source string with a stable name the caller can use to identify the source in lint output.

type Nil

type Nil struct{}

Nil is the predicate engine's nil value. Distinct from Go's nil and from a missing binding.

func NewNil

func NewNil() Nil

NewNil constructs a Nil value.

func (Nil) Type

func (Nil) Type() Type

Type returns NilType.

type Number

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

Number is a concrete numeric value, backed by float64. There is no separate integer type — see doc.go ("Numeric model").

func NewNumber

func NewNumber(f float64) Number

NewNumber constructs a Number value from a float64.

func NewNumberFromInt

func NewNumberFromInt(i int) Number

NewNumberFromInt constructs a Number value from a Go int, with the integer promoted to float64.

func (Number) Float

func (n Number) Float() float64

Float returns the underlying float64.

func (Number) Type

func (Number) Type() Type

type ParseError

type ParseError struct {
	Line int
	Col  int
	Msg  string
}

ParseError reports a failure inside gopher-lua's parser. Line and Col are best-effort: they reflect the position the parser reported, adjusted for the synthetic "return " prefix we prepend.

func (*ParseError) Error

func (e *ParseError) Error() string

type Program

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

Program is a compiled predicate, ready for repeated evaluation.

A Program is immutable after Compile. It carries no mutable state, no caches, and no per-instance memoization. Multiple goroutines may call Eval on the same Program concurrently with their own Bindings.

func Compile

func Compile(env *Env, source string, opts ...CompileOption) (prog *Program, err error)

Compile parses source as a single Lua expression, walks the AST against the predicate engine's allow-list, and type-checks against env. The returned *Program is safe for concurrent evaluation.

A nil env is rejected. The source must be a single expression (statements, multi-return-value, and leading `return` are all rejected with a *CompileError naming the failure mode).

func (*Program) Eval

func (p *Program) Eval(ctx context.Context, b *Bindings, opts ...EvalOption) (Value, error)

Eval evaluates the program against bindings and returns the result value or an *EvalError. Safe to call concurrently with distinct bindings (see doc.go).

The context is threaded through to any host function the program invokes. A nil bindings argument is treated as empty; refer to any declared variable or function and Eval returns an *EvalError.

func (*Program) ResultType

func (p *Program) ResultType() Type

ResultType returns the static type of the program's top-level expression.

type Record

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

Record is a named-field bundle, the value-form of a Lua table used for entity-shape access (entity.status). Field access happens at eval time through the AttrGet IR op.

func NewRecord

func NewRecord(fields map[string]Value) Record

NewRecord constructs a Record from a map. The returned Record retains the supplied map by reference — callers must not mutate it after the call (RR-AJS4). Pass a freshly built map if the caller intends to keep working with one of its own.

func (Record) Get

func (r Record) Get(name string) (Value, bool)

Get returns the field value and a present flag.

func (Record) Type

func (Record) Type() Type

type RecordType

type RecordType map[string]Type

Record is a named-field type descriptor. Used both as a static declaration and as a runtime Value (see value.go). The fields map declares attribute name → type for an entity-like structure.

type String

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

String is a concrete string value. Lua strings are byte-strings; we preserve any bytes the caller binds, including embedded null bytes.

func NewString

func NewString(s string) String

NewString constructs a String value.

func (String) String

func (s String) String() string

String returns the underlying Go string.

func (String) Type

func (String) Type() Type

type Type

type Type interface {
	// contains filtered or unexported methods
}

Type is the static type a Value can carry. The type system is deliberately tiny: it only needs to discriminate the cases the expression grammar can express.

var (
	BoolType   Type = primitiveType{"bool"}
	NumberType Type = primitiveType{"number"}
	StringType Type = primitiveType{"string"}
	NilType    Type = primitiveType{"nil"}
	// AnyType only appears in host-function signatures: it accepts
	// any Value. Use sparingly — it short-circuits the type checker.
	AnyType Type = primitiveType{"any"}
)

Public type descriptors callers use when declaring an env.

type Value

type Value interface {
	Type() Type
	// contains filtered or unexported methods
}

Value is the sealed sum type the evaluator operates on. The unexported sealedValue method prevents external packages from inventing new variants; everything that round-trips through the engine must be one of the constructors below.

Jump to

Keyboard shortcuts

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