cellite

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

cel-lite

CEL expressions for Go programs, with the standard library alone: small enough for TinyGo and WebAssembly, and quick enough to run on every keystroke.

cel-lite runs a subset of the Common Expression Language. An expression means what CEL's language definition says it means; what cel-lite doesn't implement, it refuses by name when the expression is compiled, so nothing quietly means something else. The parsed and checked tree is public, for programs that would rather generate code from an expression than run it.

Why

  • CEL without cel-go's weight. cel-go is the reference implementation, and the right choice when you need all of CEL. It links protobuf, ANTLR and five more modules into a program, though, and one that only asks mode == 'normal' && !pane.zoomed pays for all of them.
  • No dependencies. The module imports the standard library and nothing else, and its tests check that it stays that way.
  • TinyGo and WebAssembly. It builds and runs under TinyGo and Go's js/wasm. Each phase stays within a measured 13.2 KB of stack, so it fits TinyGo's fixed goroutine stacks, and nothing panics on any input.
  • Fast. Compiling an expression takes one or two microseconds, and evaluating it takes tens of nanoseconds, allocating nothing unless it builds a string or calls a helper.

Install

go get github.com/neuroplastio/cel-lite

It needs Go 1.24 or later. The package is cellite.

Example

env, err := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "mode", Type: cellite.String},
	{Name: "pane.zoomed", Type: cellite.Bool},
}})
if err != nil {
	log.Fatal(err)
}
prg, err := env.Compile(`mode == 'normal' && !pane.zoomed`)
if err != nil {
	log.Fatal(err) // a *cellite.Error says where, and why
}
act := env.Activation(map[string]any{"mode": "normal", "pane.zoomed": false})
holds, unknown := prg.Test(act) // true, false

An expression can also read your own structs, lists and maps without copying them, call Go functions you declare, and work with variables whose values aren't known yet. Getting started walks through each.

Compared with cel-go

cel-lite cel-go v0.32
Language a subset of CEL, the rest refused all of CEL, and extensions
Dependencies none 8 modules, 70 packages
Binary, linux/amd64 1.9 MB 10.2 MB
Go, js/wasm 3.1 MB 20.7 MB
TinyGo, wasm 193 KB 3.3 MB, panics as it starts
Compile an expression 0.2–2 µs, 5–22 allocations 7–33 µs, 120–400 allocations
Evaluate it 14–116 ns, up to 2 allocations 45–430 ns, up to 7 allocations

The sizes and dependencies are those of one small program, which compiles and tests one expression, built with -trimpath -ldflags='-s -w' by Go 1.26 and with -no-debug by TinyGo 0.42. A program that prints hello is 1.6 MB, or 2.6 MB in js/wasm, so most of cel-lite's column is Go's own runtime. Under TinyGo, cel-go builds but stops at start-up on reflection TinyGo doesn't implement. The times are the benchmarks' medians; evaluating allocates only to build a string or to call a helper.

What's in the subset

  • Literals, lists and maps; !, -, &&, ||, ? :, ==, !=, <, <=, >, >=, in, +, -, *, / and %; field selection, indexing and has().
  • size, contains, startsWith and endsWith, and the conversions int, uint, double, string, bytes, bool and dyn.
  • Structs you declare, and helpers: Go functions with typed overloads.
  • Unknown variables, with CEL's rules for errors and unknowns through && and ||.

Refused by name: timestamps and durations, regular expressions, the comprehension macros (all, exists, exists_one, map, filter), type values, message construction and optional syntax. The language has the details, and the few places where cel-lite follows CEL's definition rather than cel-go.

Documentation

  • Getting started: install, a first expression, variables and a helper.
  • The language: what the subset holds, what it refuses and how, and where it differs from cel-go.
  • Embedding: environments, types, values, helpers, programs, errors, and the tree.
  • Limits, TinyGo and WebAssembly: the limits, the stack each phase needs, and building for TinyGo and js/wasm.
  • Performance: the benchmarks against cel-go, how they were measured, and how to run them.
  • Package reference on pkg.go.dev, with runnable examples.

Status

v0.1.0, the first release. cel-lite follows semantic versioning: until 1.0, a release that breaks the API raises the minor version (0.1 to 0.2), and a patch release never does. Before 1.0 these may still change: the tree (Expr and its Ops) as the subset grows, the wording of error messages, and the API's edges as programs that embed it ask for things. An expression that compiles keeps its meaning, unless that meaning is found to differ from CEL's definition, which is a bug.

Developing

Go and TinyGo are pinned in mise.toml.

  • mise exec -- make check: gofmt, vet and the tests; the standard library alone; every test under TinyGo on a 32 KB stack, and the stack budget; js/wasm; and the comparison with cel-go in oracle/.
  • make fuzz: a minute each of the fuzz targets: trees built by hand, and generated expressions and source text against cel-go.
  • make bench: compiling and evaluating; oracle/ has cel-go's side.
  • make stack: the stack each phase needs at the limits, under TinyGo.

License

Apache License 2.0: see LICENSE.

Documentation

Overview

Package cellite runs a subset of CEL, the Common Expression Language, with Go's standard library alone, so it builds for js/wasm and under TinyGo. Its parsed and checked tree is public, for programs that turn an expression into Go of their own.

A program declares what an expression may name, compiles it once and evaluates the Program as often as it likes:

env, err := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "pane", Type: cellite.Bool},
	{Name: "pane.id", Type: cellite.String},
}})
prg, err := env.Compile(`pane && pane.id == 'x'`)
act := env.Activation(map[string]any{"pane": true, "pane.id": "x"})
holds, unknown := prg.Test(act)

Compile is Parse, then Check, then Program. Each step is public: Parse gives the syntax tree (an *Expr), Check resolves its names and types every node, and Program plans the checked tree for evaluation.

The guides (getting started, the language, embedding, limits and TinyGo, performance) are in the repository's docs directory: https://github.com/neuroplastio/cel-lite/tree/main/docs.

Meaning

An expression means what CEL's language definition says it means, and what it means in cel-go (v0.32), which the oracle module's tests compare it with. Or it is refused when it is compiled, with an *Error of kind Unsupported that names what is missing: timestamps and durations, regular expressions, macros (all, exists, map, filter), type values, message construction and optional syntax.

Where cel-lite and cel-go differ

Where cel-go strays from the language definition, cel-lite follows the definition, or refuses by name:

  • cel-go types a value read with [] from a dyn (`x['k']`, or an element of an empty literal) with a type parameter that its first use binds: `-x['k']` is a double there, `[x['k'], 'a']` a list(string), `-x['k'] + 1` is refused, and `int(x['k'])` takes only an int when it runs. In the definition the value is dyn. Check refuses such a use (Unsupported, "inferred type"); x.k or dyn(x['k']) keeps the value dyn.
  • A map's keys are bool, int, uint or string (the definition's Values). A map literal whose dyn key turns out a double, a list or a map is an error when it runs; cel-go builds the map, where a NaN key is never found and two NaN keys are two entries.
  • A variable may take a type's name (`list`, `int`), and the name then reads the variable. When the variable is absent, cel-go reads the type instead, a value the expression's checked type doesn't allow: `list` declared a bool gives the type list. cel-lite reports the variable missing, as for any other name.
  • cel-go reads a struct's field by its name with [] when a dyn hides the struct, `dyn(item)['title']`. In the definition [] reads a list or a map; cel-lite gives an error (no such overload).
  • `x in m` with x bytes, as untyped data can give a variable, is false on any map: bytes are never a key. cel-go fails on a map literal, which can't hash them, and says false on a Go map.
  • cel-lite's limits are lower than cel-go's (Limits).

Values

A Value is a CEL value. Lists, maps and structs reach the evaluator through the List, Map and Object interfaces, so a program can hand over its own data without copying it; ValueOf wraps Go's own slices and maps. A struct the program declares (Decls.Structs) is read by field index, which Check resolves, through Object. cel-lite doesn't compare structs: Check refuses == on them, and where a dyn hides them the evaluation does, unless the answer doesn't depend on it. A struct against null compares none, and the value decides: a struct is never null.

Limits

Nothing here panics on any input, since recover doesn't work under TinyGo's wasm. An expression is at most MaxSize bytes, its tree at most MaxDepth (50) deep, and its brackets, calls and conditionals nest at most 16 deep. Within those limits the parser, the checker, the planner and the evaluator each need at most 13.2 KB of stack under TinyGo 0.42 on wasm (`make stack` measures it, against a budget of 16 KB), which leaves most of a TinyGo goroutine's fixed 64 KB to the program around them; the gate runs every test on a 32 KB stack. Integers are int64 and uint64 everywhere, and overflow is an error.

Example

A program declares the variables an expression may read, compiles the expression once, and tests it as often as it likes.

env, err := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "mode", Type: cellite.String},
	{Name: "pane.zoomed", Type: cellite.Bool},
}})
if err != nil {
	panic(err)
}
prg, err := env.Compile(`mode == 'normal' && !pane.zoomed`)
if err != nil {
	panic(err)
}
act := env.Activation(map[string]any{
	"mode":        "normal",
	"pane.zoomed": false,
})
holds, unknown := prg.Test(act)
fmt.Println(holds, unknown)
Output:
true false

Index

Examples

Constants

View Source
const (
	// MaxSize is the longest source Parse reads, in bytes.
	MaxSize = 100_000
	// MaxDepth is how deep an expression's tree may be. && and || chains
	// are built balanced, as cel-go builds them, so a long chain is
	// shallow; `a + b + c` chains are not.
	MaxDepth = 50
)

Limits on what Parse reads. cel-go has its own (100,000 code points, a parse depth of 250); these are lower, and a deeper expression is refused rather than read. The parser, the checker, the planner and the evaluator recurse once per level, and a TinyGo goroutine's stack is a fixed 64 KB, which an overflow corrupts silently: the limits keep each of them within 16 KB there, as `make stack` measures and the tests check. Brackets, calls and conditionals also nest at most 16 deep. A limit rises only with a new measurement.

Variables

View Source
var (
	Dyn    = Type{Kind: DynKind}
	Null   = Type{Kind: NullKind}
	Bool   = Type{Kind: BoolKind}
	Int    = Type{Kind: IntKind}
	Uint   = Type{Kind: UintKind}
	Double = Type{Kind: DoubleKind}
	String = Type{Kind: StringKind}
	Bytes  = Type{Kind: BytesKind}
)

The types that have no parameters.

View Source
var ErrUnknown = errors.New("cellite: unknown")

ErrUnknown is what Eval gives when the value depends on a variable the activation says is unknown (Unknown): partial evaluation.

Functions

func FormatType

func FormatType(t Type) string

FormatType writes a type as CEL does: int, list(string), map(string, int), a struct's name.

func ReservedWords

func ReservedWords() []string

ReservedWords are the words CEL keeps from identifiers: no variable, struct or helper takes one as its name. A field may: `x.if`.

Example

A program that declares names it doesn't choose, such as a table's keys, leaves out the words CEL keeps. A type's name may be a variable's.

reserved := map[string]bool{}
for _, w := range cellite.ReservedWords() {
	reserved[w] = true
}
var vars []cellite.Var
for _, key := range []string{"name", "in", "list", "if"} {
	if !reserved[key] {
		vars = append(vars, cellite.Var{Name: key, Type: cellite.Dyn})
	}
}
_, err := cellite.NewEnv(cellite.Decls{Vars: vars})
fmt.Println(len(vars), err)
Output:
2 <nil>

func StandardFunctions

func StandardFunctions() []string

StandardFunctions are the functions and macros of CEL's standard library, the ones cel-lite implements and the ones it refuses by name: no helper takes one as its name.

func Text

func Text(v Value) (string, error)

Text is CEL's string() of a value: a string as it is; an int, a uint and a bool written out; a double in Go's %g; bytes that are valid UTF-8 as that text. Any other value is an error.

func TypeEqual

func TypeEqual(a, b Type) bool

TypeEqual reports whether a and b are the same type.

func TypeNames

func TypeNames() []string

TypeNames are the names CEL gives its types: no struct or helper takes one as its name. A variable may, and then the name reads the variable, as in cel-go.

Types

type Activation

type Activation interface {
	// Var is variable i's value, or that it is Absent or Unknown.
	Var(i int) (Value, Presence)
}

Activation gives an evaluation its variables' values, by their index in the Env (Env.Vars).

type Decls

type Decls struct {
	// Vars are the variables an expression may read, in the order an
	// Activation indexes them.
	Vars []Var
	// Structs are the struct types the variables' values may be or hold.
	Structs []Struct
	// Funcs are the helpers an expression may call, beside CEL's
	// standard functions.
	Funcs []Func
}

Decls is what a program declares to its expressions: variables, the structs their values may be, and helper functions.

type Env

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

Env is the environment expressions are checked and compiled in: a program's Decls, read. It is safe for concurrent use.

func NewEnv

func NewEnv(d Decls) (*Env, error)

NewEnv reads a program's declarations. A name that isn't an identifier (a variable's may be qualified), a reserved word, a struct or helper named as a type, a name declared twice, a type that names an undeclared struct, a helper named as a standard function, and an overload ID used twice are errors. A variable may take a type's name, as in cel-go: the variable wins, so with `list` declared, `list` reads it.

Example (Helper)

A helper is a Go function an expression calls, with typed overloads: a call is resolved when the expression is compiled. Member makes it a method, its receiver the first argument.

upper := func(args []cellite.Value) (cellite.Value, error) {
	return cellite.StringValue(strings.ToUpper(args[0].String())), nil
}
env, err := cellite.NewEnv(cellite.Decls{
	Vars: []cellite.Var{{Name: "name", Type: cellite.String}},
	Funcs: []cellite.Func{{Name: "upper", Overloads: []cellite.Overload{{
		ID:     "upper_string",
		Member: true,
		Args:   []cellite.Type{cellite.String},
		Result: cellite.String,
		Fn:     upper,
	}}}},
})
if err != nil {
	panic(err)
}
prg, _ := env.Compile(`name.upper() == 'ADA'`)
holds, _ := prg.Test(env.Activation(map[string]any{"name": "Ada"}))
fmt.Println(holds, prg.Expr().Args[0].Overload)
Output:
true upper_string
Example (Structs)

A struct the program declares is read by field index, which Check resolves from the field's name; lists and maps of the program's own come in through List and Map.

env, err := cellite.NewEnv(cellite.Decls{
	Structs: []cellite.Struct{{Name: "File", Fields: []cellite.Field{
		{Name: "name", Type: cellite.String},
		{Name: "size", Type: cellite.Int},
	}}},
	Vars: []cellite.Var{
		{Name: "files", Type: cellite.ListOf(cellite.StructOf("File"))},
	},
})
if err != nil {
	panic(err)
}
prg, err := env.Compile(`size(files) > 1 && files[1].size > 1024`)
if err != nil {
	panic(err)
}
fs := files{{name: "a.txt", size: 12}, {name: "b.bin", size: 4096}}
holds, _ := prg.Test(env.Activation(map[string]any{
	"files": cellite.ListValue(fs),
}))
fmt.Println(holds)
Output:
true

func (*Env) Activation

func (e *Env) Activation(vals map[string]any, unknown ...string) Activation

Activation is an activation from a map of the variables' values by name, each read by ValueOf: a variable the map doesn't hold is absent, and one named in unknown is unknown.

func (*Env) Check

func (e *Env) Check(x *Expr) (*Expr, error)

Check resolves a parsed expression's names in the Env and types every node, as cel-go's checker does, and returns the checked tree, a new one: x is left as it is. An undeclared name, types that don't fit, a standard function this package doesn't implement and a malformed tree are errors (*Error). Check has no source, so the Error's Line and Col are 0; Compile fills them in.

Example

Check types every node of the tree Parse reads, and resolves names to variables by index: a program can generate code of its own from it.

env, _ := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "pane.zoomed", Type: cellite.Bool},
	{Name: "title", Type: cellite.String},
}})
x, err := cellite.Parse(`!pane.zoomed && size(title) > 3`)
if err != nil {
	panic(err)
}
y, err := env.Check(x)
if err != nil {
	panic(err)
}
var walk func(x *cellite.Expr, indent string)
walk = func(x *cellite.Expr, indent string) {
	fmt.Printf("%s%s %s", indent, opName(x.Op), cellite.FormatType(x.Type))
	switch x.Op {
	case cellite.OpVar:
		fmt.Printf(" %s (variable %d)", x.Name, x.Ref)
	case cellite.OpCall:
		fmt.Printf(" %s (%s)", x.Name, x.Overload)
	case cellite.OpLit:
		fmt.Printf(" %v", x.Value)
	}
	fmt.Println()
	for _, a := range x.Args {
		walk(a, indent+"  ")
	}
}
walk(y, "")
Output:
&& bool
  ! bool
    var bool pane.zoomed (variable 0)
  > bool
    call int size (size_string)
      var string title (variable 1)
    literal int 3

func (*Env) Compile

func (e *Env) Compile(src string) (*Program, error)

Compile parses, checks and plans an expression. Its errors are *Error, with Line and Col set.

Example

Eval gives the expression's value, of the type Check found.

env, _ := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "title", Type: cellite.String},
	{Name: "count", Type: cellite.Int},
}})
prg, err := env.Compile(`count > 0 ? title + ' (' + string(count) + ')' : title`)
if err != nil {
	panic(err)
}
v, err := prg.Eval(env.Activation(map[string]any{"title": "Inbox", "count": 3}))
if err != nil {
	panic(err)
}
fmt.Println(cellite.FormatType(prg.Type()), v.String())
Output:
string Inbox (3)

func (*Env) Program

func (e *Env) Program(x *Expr) (*Program, error)

Program plans a checked expression, the tree Check returned in this Env. A tree that refers to what the Env doesn't declare, a variable or a field by an index out of range or a helper it hasn't, is an error.

func (*Env) VarIndex

func (e *Env) VarIndex(name string) (int, bool)

VarIndex is the index of the variable declared as name.

func (*Env) Vars

func (e *Env) Vars() []Var

Vars are the variables, in the order declared: Var(i) of an Activation is Vars()[i]. The slice is the Env's own; don't change it.

type Error

type Error struct {
	Kind ErrorKind
	// Pos is the byte offset in the source; Line and Col are where that
	// is, from 1, Col counted in characters.
	Pos, Line, Col int
	// Name is the undeclared name, or the unsupported feature.
	Name string
	Msg  string
}

Error is why Parse, Check or Compile refused an expression.

Example

An expression is refused when it is compiled: a syntax error, an undeclared name, types that don't fit, or CEL that cel-lite doesn't implement, which it names.

env, _ := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "tags", Type: cellite.ListOf(cellite.String)},
}})
for _, src := range []string{
	`tags[0] ==`,
	`tag == 'x'`,
	`tags[0] + 1`,
	`tags.exists(t, t == 'x')`,
} {
	_, err := env.Compile(src)
	var e *cellite.Error
	if errors.As(err, &e) && e.Kind == cellite.Unsupported {
		fmt.Printf("unsupported %q: %v\n", e.Name, err)
		continue
	}
	fmt.Println(err)
}
Output:
1:11: syntax error: unexpected end of input
1:1: undeclared reference to 'tag'
1:9: found no matching overload for '_+_' applied to '(string, int)'
unsupported "macro exists": 1:6: the macro exists() is not supported: comprehensions (all, exists, exists_one, map, filter) are not implemented

func (*Error) Error

func (e *Error) Error() string

Error is the message with its position: "1:7: undeclared reference to 'x'", or "offset 6: …" from Check, which has no source.

type ErrorKind

type ErrorKind uint8

ErrorKind says why an expression was refused.

const (
	// SyntaxError: the source doesn't parse as CEL, or a tree a program
	// built isn't one: a node missing an operand, or one Program is
	// given that refers to what its Env doesn't declare.
	SyntaxError ErrorKind = iota + 1
	// Undeclared: a name the Env doesn't declare, a variable or a
	// function. Error.Name is the name.
	Undeclared
	// TypeError: the types don't fit, such as `'a' + 1`, or a context
	// that must be a bool and isn't.
	TypeError
	// Unsupported: valid CEL that this package doesn't implement, refused
	// by name so that an expression it accepts means what it means in
	// CEL. Error.Name is the feature: "macro all", "function matches".
	Unsupported
)

type EvalError

type EvalError struct {
	Msg string
	// Err is the helper's error, when a helper failed.
	Err error
}

EvalError is an error an expression met while it ran: a missing key, a division by zero, an overflow, or a helper's own error (Err).

func (*EvalError) Error

func (e *EvalError) Error() string

Error is the message: "division by zero", "no such key: a", or what a helper's error says.

func (*EvalError) Unwrap

func (e *EvalError) Unwrap() error

Unwrap is the helper's error.

type Expr

type Expr struct {
	Op Op
	// Pos is the node's byte offset in the source: an operator's for an
	// operation, else where the node starts.
	Pos int
	// Type is the node's type, set by Check.
	Type Type
	// Value is a literal's value (OpLit).
	Value any
	// Name is a variable's or an identifier's name (OpVar, OpIdent), the
	// field or key selected (OpSelect, OpHas), or the function called
	// (OpCall).
	Name string
	// Ref is a variable's index in the Env's Vars (OpVar), or a field's
	// index in its Struct's Fields (OpSelect on a struct); set by Check.
	Ref int
	// Overload names what a call runs, set by Check: a standard
	// function's overload as cel-go names it ("size_string",
	// "string_to_int64"), or a helper's Overload.ID. It is empty when
	// more than one overload fits the argument types, and the one to run
	// is decided by the values.
	Overload string
	// Member is set on a method call, `x.f(y)`: the receiver is Args[0].
	Member bool
	// Args are the node's operands, in the order its Op gives.
	Args []*Expr
}

Expr is a node of an expression: what Parse reads and Check types. It is plain data, so a program that generates code from an expression walks it with a switch on Op. Parse and Check make a tree's nodes in a few chunks: a program may change a node or give it new Args, and appending to a node's Args copies them.

func Parse

func Parse(src string) (*Expr, error)

Parse reads a CEL expression into its tree, unchecked: names are OpIdent until Check resolves them. A syntax error and a feature this package doesn't implement are errors (*Error). Each byte of src that isn't UTF-8 reads as U+FFFD, as in cel-go, and positions count in that text.

type Field

type Field struct {
	Name string
	Type Type
}

Field is a struct's field.

type Func

type Func struct {
	Name      string
	Overloads []Overload
}

Func is a helper function, with one or more overloads. A call is checked against them when an expression is compiled.

type Kind

type Kind uint8

Kind is what sort of value a Type describes.

const (
	// NullKind is null's. It is the zero Kind, so the zero Value is null.
	NullKind Kind = iota
	// DynKind is any value, checked when the expression runs. A Type must
	// say it: the zero Type is null's, which NewEnv refuses as a
	// variable's or a field's type.
	DynKind
	// The kinds of CEL's scalar values, its lists and its maps: bool,
	// int (int64), uint (uint64), double (float64), string, bytes, list
	// and map.
	BoolKind
	IntKind
	UintKind
	DoubleKind
	StringKind
	BytesKind
	ListKind
	MapKind
	// StructKind is a struct the program declares (Decls.Structs), named
	// by Type.Name.
	StructKind
)

type List

type List interface {
	Len() int
	// At is element i, 0 <= i < Len().
	At(i int) Value
}

List is a list a program supplies without copying it into Values, such as a slice of its structs.

type Map

type Map interface {
	Len() int
	// Get is the value at key, which is a Value of any kind: a map with
	// string keys is asked for an int, and says it doesn't hold it.
	Get(key Value) (Value, bool)
	// Range calls f on each entry until f returns false.
	Range(f func(k, v Value) bool)
}

Map is a map a program supplies. Its keys are bools, ints, uints or strings.

type Object

type Object interface {
	// Field is field i, its index in the Struct's Fields: Check resolves
	// a field's name to it (Expr.Ref). For a field of a struct type that
	// isn't set, an Object of the struct's zero value reads as cel-go
	// reads a native type's nil pointer and protobuf an unset message:
	// its fields are their zero values, and it is never null. NullValue
	// makes it equal null, and selecting from it an error.
	Field(i int) Value
}

Object is a value of a struct the program declares (Decls.Structs).

type Op

type Op uint8

Op is what an expression node does.

const (
	// OpLit is a literal: Value is nil (null), a bool, an int64, a
	// uint64, a float64, a string or a []byte.
	OpLit Op = iota + 1
	// OpIdent is a name as Parse read it, before Check resolves it. A
	// checked expression has none.
	OpIdent
	// OpVar is a declared variable: Name, and Ref its index in the Env.
	// A qualified name (`pane.id`) that the Env declares is one OpVar.
	OpVar
	// OpSelect is Args[0].Name: a struct's field, whose index in its
	// Struct's Fields Check sets as Ref, or a map's key.
	OpSelect
	// OpHas is has(Args[0].Name): whether a map holds the key.
	OpHas
	// OpIndex is Args[0][Args[1]]: a list's element or a map's value.
	OpIndex
	// OpCall calls the function Name. A method call (Member) has its
	// receiver as Args[0]. Check sets Overload.
	OpCall
	// OpCond is Args[0] ? Args[1] : Args[2].
	OpCond
	// OpAnd, OpOr and OpNot are &&, || and !. && and || hold on false
	// and true from either side, whatever the other side gives.
	OpAnd
	OpOr
	OpNot
	// OpNeg is unary minus.
	OpNeg
	// The comparisons: Args[0] op Args[1].
	OpEq
	OpNe
	OpLt
	OpLe
	OpGt
	OpGe
	// OpIn is Args[0] in Args[1]: a list's element or a map's key.
	OpIn
	// The arithmetic: Args[0] op Args[1].
	OpAdd
	OpSub
	OpMul
	OpDiv
	OpMod
	// OpList is a list literal: [Args...].
	OpList
	// OpMap is a map literal: Args holds each key, then its value.
	OpMap
)

type Overload

type Overload struct {
	// ID names the overload in a checked expression (Expr.Overload), for
	// a program that generates code from it. It is unique in the Env.
	ID string
	// Member makes it a method: `x.f(y)`, with the receiver as Args[0].
	Member bool
	// Args are the parameters' types, and Result the type it returns.
	// Dyn takes, or gives, a value of any type.
	Args   []Type
	Result Type
	// Fn runs it. Its arguments are the values, not errors or unknowns,
	// which a call never passes on. An error it returns is the
	// expression's (EvalError.Err).
	Fn func(args []Value) (Value, error)
}

Overload is one signature of a helper and the Go function that runs it.

type Presence

type Presence uint8

Presence says whether an activation has a variable's value.

const (
	// Present: the value is given.
	Present Presence = iota
	// Absent: reading the variable is an error, as reading an attribute
	// that isn't there is in CEL.
	Absent
	// Unknown: the value isn't known yet. An expression that depends on
	// it is unknown; one that doesn't, such as `false && x`, is not.
	Unknown
)

type Program

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

Program is a compiled expression. It is immutable and safe for concurrent use.

func (*Program) Eval

func (p *Program) Eval(a Activation) (Value, error)

Eval runs the program on the activation's variables. It gives the value, or an *EvalError, or ErrUnknown when the value depends on a variable the activation calls unknown.

func (*Program) Expr

func (p *Program) Expr() *Expr

Expr is the checked expression.

func (*Program) Test

func (p *Program) Test(a Activation) (holds, unknown bool)

Test runs a program whose type is bool: whether it holds (it is true), and whether it is unknown. An error is neither; so is a value that isn't a bool. It doesn't allocate.

Example

A variable the activation calls unknown makes unknown what depends on it, and only that: partial evaluation.

env, _ := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "ready", Type: cellite.Bool},
	{Name: "user.admin", Type: cellite.Bool},
}})
prg, _ := env.Compile(`ready && user.admin`)
for _, ready := range []bool{false, true} {
	act := env.Activation(map[string]any{"ready": ready}, "user.admin")
	holds, unknown := prg.Test(act)
	fmt.Println(ready, holds, unknown)
}
Output:
false false false
true false true

func (*Program) Type

func (p *Program) Type() Type

Type is the expression's type.

type Struct

type Struct struct {
	Name   string
	Fields []Field
}

Struct is a struct type. A value of it is an Object, whose Field(i) gives Fields[i].

type Type

type Type struct {
	Kind Kind
	// Name is a struct's name; empty for every other kind.
	Name string
	// Elem is a list's element type, or a map's value type.
	Elem *Type
	// Key is a map's key type.
	Key *Type
}

Type is a CEL type: a kind, with a list's element, a map's key and value, or a struct's name. Build one with the variables below, ListOf, MapOf and StructOf.

func ListOf

func ListOf(elem Type) Type

ListOf is the type of a list of elem.

func MapOf

func MapOf(key, val Type) Type

MapOf is the type of a map from key to val.

func StructOf

func StructOf(name string) Type

StructOf is the type of the struct the program declares as name.

type Value

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

Value is a CEL value: what an activation gives, what a helper takes and returns, and what Eval gives. It is small and passed by value: making one from a string or a number, or reading one, doesn't allocate. The zero Value is null.

func BoolValue

func BoolValue(b bool) Value

BoolValue is a bool.

func BytesValue

func BytesValue(b []byte) Value

BytesValue is bytes. The Value keeps its own copy.

func DoubleValue

func DoubleValue(f float64) Value

DoubleValue is a double.

func IntValue

func IntValue(i int64) Value

IntValue is an int.

func ListValue

func ListValue(l List) Value

ListValue is a list.

func MapValue

func MapValue(m Map) Value

MapValue is a map.

func NullValue

func NullValue() Value

NullValue is null.

func ObjectValue

func ObjectValue(name string, o Object) Value

ObjectValue is a value of the struct the program declares as name.

func StringValue

func StringValue(s string) Value

StringValue is a string.

func UintValue

func UintValue(u uint64) Value

UintValue is a uint.

func ValueOf

func ValueOf(x any) Value

ValueOf is a Go value as CEL reads it, without copying: nil; a bool; a string or []byte; a Go integer of any size, as an int if signed and a uint if not; a float32 or float64; a Value; a List, a Map; a slice of any, string, int64, int, float64, bool or Value; a map from string to any, string, int64, int, bool, float64 or Value. Anything else is reported, by the evaluation that reads it, as an error: a struct value goes in through ObjectValue.

Example

ValueOf reads a Go value as CEL does, without copying: here a map from a decoded document, read with a dyn variable.

env, _ := cellite.NewEnv(cellite.Decls{Vars: []cellite.Var{
	{Name: "doc", Type: cellite.Dyn},
}})
prg, _ := env.Compile(`doc.kind == 'pod' && 'app' in doc.labels`)
doc := map[string]any{
	"kind":   "pod",
	"labels": map[string]any{"app": "web"},
}
holds, _ := prg.Test(env.Activation(map[string]any{"doc": doc}))
fmt.Println(holds)
Output:
true

func (Value) Bool

func (v Value) Bool() bool

Bool is a bool's value; false for any other kind.

func (Value) Bytes

func (v Value) Bytes() []byte

Bytes is bytes' value, a copy; nil for any other kind.

func (Value) Double

func (v Value) Double() float64

Double is a double's value; 0 for any other kind.

func (Value) Int

func (v Value) Int() int64

Int is an int's value; 0 for any other kind.

func (Value) Interface

func (v Value) Interface() any

Interface is the value as Go has it: nil, bool, int64, uint64, float64, string or []byte; a list ValueOf made from a slice is that slice, any other list a []any; a map ValueOf made from a Go map is that map, a map literal a map[string]any when its keys are all strings and a map[any]any when not, any other Map itself; a struct value its Object.

func (Value) Kind

func (v Value) Kind() Kind

Kind is the value's kind.

func (Value) List

func (v Value) List() List

List is a list's value; nil for any other kind.

func (Value) Map

func (v Value) Map() Map

Map is a map's value; nil for any other kind.

func (Value) Object

func (v Value) Object() Object

Object is a struct value's Object, and StructName its struct's name; nil and "" for any other kind.

func (Value) String

func (v Value) String() string

String is a string's value; for any other kind, its kind in angle brackets ("<int>"), as reflect.Value's String is. Text gives CEL's string() of a value.

func (Value) StructName

func (v Value) StructName() string

StructName is the name of a struct value's struct; "" for any other kind.

func (Value) Uint

func (v Value) Uint() uint64

Uint is a uint's value; 0 for any other kind.

type Var

type Var struct {
	Name string
	Type Type
}

Var is a variable. Its name may be qualified, `pane.id`: an expression that reads `pane.id` reads this variable, even where `pane` is another.

Jump to

Keyboard shortcuts

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