feel

package module
v0.0.0-...-ced9a64 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

feel

CI

A standalone FEEL (Friendly Enough Expression Language) engine for Go — the lexer, parser, type checker and compiler that lower FEEL expressions into reusable, allocation-light Go closures.

FEEL is the expression language defined by the OMG DMN (Decision Model and Notation) standard. This package is the FEEL front-end extracted from temis so it can be reused on its own, e.g. to evaluate business rules, decision-table cells, or user-authored expressions in any Go program.

Highlights

  • Compile once, evaluate many. Parsing, type checking and lowering happen up front; the result is a CompiledExpr closure that evaluates on the hot path with minimal allocation.
  • Decimal numbers, never float64. Arithmetic uses apd decimals, so results match FEEL / DMN semantics (no binary floating-point surprises).
  • Three-valued logic and pervasive null propagation, per the spec.
  • A type checker that reports positioned findings before you evaluate.
  • A catalog of built-in functions — conversion, boolean, string, list, numeric, date/time, range, temporal, sort and context functions.
  • Unary tests for decision-table input cells (> 10, [1..5], "Winter", "Spring", …).
  • Execution limits (recursion depth, iteration and list-size caps) to keep evaluation of untrusted expressions bounded.
  • Pure Go, one dependency (apd/v3); no cgo.

Install

go get github.com/pblumer/feel

Requires Go 1.24 or newer.

Quick start

package main

import (
	"fmt"

	"github.com/pblumer/feel"
	"github.com/pblumer/feel/value"
)

func main() {
	// Declare the variables the expression may reference.
	env := feel.NewEnv("Season", "Guest Count")

	// Parse + type-check + compile into a reusable closure.
	expr, err := feel.CompileString(
		`if Season = "Winter" and Guest Count > 8 then "Spareribs" else "Salad"`,
		env,
	)
	if err != nil {
		panic(err)
	}

	// Evaluate: bind values by name and run the closure.
	out, err := expr(env.NewScope(map[string]value.Value{
		"Season":      value.Str("Winter"),
		"Guest Count": value.NumberFromInt64(10),
	}))
	if err != nil {
		panic(err)
	}
	fmt.Println(out) // Spareribs
}

A runnable version lives in example_test.go.

Packages

Import path Purpose
github.com/pblumer/feel The engine: lexer, parser, AST, type system, type checker and compiler. Entry points: CompileString, CompileStringWith, Parse, Compile, NewEnv, Typecheck.
github.com/pblumer/feel/value The runtime value model: the Value interface and its kinds (null, bool, number, string, temporal types, list, context, range, function) plus FEEL-conformant equality, ordering and arithmetic.
github.com/pblumer/feel/builtins The built-in function catalog, bound at compile time.

Building values

Bind inputs with the constructors in the value package:

value.Str("Winter")          // string
value.NumberFromInt64(10)    // number (decimal)
value.MustNumber("3.14")     // number from a decimal string
value.BoolOf(true)           // boolean
value.NewList(a, b, c)       // list
value.NewContext().Put(...)  // context (map)

Unary tests

Decision-table input cells are FEEL unary tests — implicit predicates over the cell's input value, referenced as ?:

env := feel.NewEnv(feel.InputVar) // declare "?" (feel.InputVar)
test, err := feel.CompileUnaryTest(`> 10`, env)
if err != nil {
	panic(err)
}
ok, err := feel.Matches(test, env.NewScope(map[string]value.Value{
	feel.InputVar: value.NumberFromInt64(15), // feel.InputVar == "?"
}))
// ok == true

Execution limits

feel.DefaultLimits() returns sensible caps (recursion depth, iteration count, list size). Build a scope with env.NewScopeWithLimits(values, limits) — or share one *feel.EvalState across several evaluations with env.NewScopeShared — to bound evaluation of untrusted input.

Conformance

FEEL is specified by the OMG DMN standard (FEEL is Chapter 10 of the spec). The recognised way to demonstrate conformance is the DMN Technology Compatibility Kit (TCK) — the community-maintained suite of DMN models with input/expected-output cases that vendors run and publish on a compatibility matrix.

This engine is the FEEL core of temis, a DMN 1.5 decision engine that runs the full TCK and passes 3,430 / 3,495 cases (98.1%) — see the temis TCK submission and documented exceptions.

A caveat on scope: the TCK runs at the DMN-model level — it evaluates .dmn files and compares outputs, exercising FEEL through a DMN engine (temis' dmn package, which is not part of this module). So that 98.1% certifies the full temis engine, not this library in isolation. What this module carries is temis' own FEEL unit and fuzz suite, including the wp41_* tests written during the TCK-hardening work — the same semantics, encoded as package-level tests that run under go test ./....

If you need a standalone conformance signal for this library, the natural next step is a thin harness that feeds the TCK's FEEL-specific cases (compliance-level-3/*-feel-*) straight to CompileString without a DMN wrapper. That is not included here yet.

Provenance & license

Extracted from temis. Licensed under the Apache License 2.0.

Documentation

Overview

Package feel implements the FEEL expression language: the lexer, parser, AST, type system, type checker and the compiler that lowers expressions into reusable Go closures (CompiledExpr).

The compiler follows the two-phase principle: expensive work happens once at compile time, while the resulting closure evaluates with minimal allocation on the hot path. Numbers are decimal (never float64); see ADR-0007.

The lexer (token.go, lexer.go) and the parser (ast.go, parser.go) are implemented; the type system, compiler and built-ins follow in WP-05ff.

Example

Compile a FEEL expression once, then evaluate it against different inputs.

package main

import (
	"fmt"

	"github.com/pblumer/feel"
	"github.com/pblumer/feel/value"
)

func main() {
	// Declare the variables the expression may reference.
	env := feel.NewEnv("Season", "Guest Count")

	// Parse + type-check + compile into a reusable Go closure.
	expr, err := feel.CompileString(
		`if Season = "Winter" and Guest Count > 8 then "Spareribs" else "Salad"`,
		env,
	)
	if err != nil {
		panic(err)
	}

	// Evaluate: bind values by name and run the closure.
	out, err := expr(env.NewScope(map[string]value.Value{
		"Season":      value.Str("Winter"),
		"Guest Count": value.NumberFromInt64(10),
	}))
	if err != nil {
		panic(err)
	}
	fmt.Println(out)

}
Output:
Spareribs

Index

Examples

Constants

View Source
const DefaultMaxCallDepth = 256

DefaultMaxCallDepth bounds nested user-function (BKM / function literal) calls, turning unbounded recursion into a runtime error instead of a stack overflow (ADR-0008).

View Source
const DefaultMaxParseDepth = 10_000

DefaultMaxParseDepth bounds the syntactic nesting the parser will descend into, turning pathologically deep input (e.g. millions of prefix `-` or nested `(`/`[`) into a *ParseError instead of a fatal stack overflow that would crash the whole process (audit finding K1, ADR-0008). It sits far above any realistic DMN model, whose FEEL nesting is in the low tens.

View Source
const InputVar = "?"

InputVar is the name of the implicit input value inside a decision-table unary test. In FEEL a cell like "< 18" means "? < 18", where ? is the value being tested. The decision-table compiler (WP-09) binds this variable per input column.

Variables

View Source
var (
	TNumber              = &Type{Kind: value.KindNumber}
	TString              = &Type{Kind: value.KindString}
	TBoolean             = &Type{Kind: value.KindBool}
	TDate                = &Type{Kind: value.KindDate}
	TTime                = &Type{Kind: value.KindTime}
	TDateTime            = &Type{Kind: value.KindDateTime}
	TDaysTimeDuration    = &Type{Kind: value.KindDaysTimeDuration}
	TYearsMonthsDuration = &Type{Kind: value.KindYearsMonthsDuration}
	TNull                = &Type{Kind: value.KindNull}
)

Built-in scalar type singletons. nil is used directly for Any.

Functions

func CoerceArg

func CoerceArg(v value.Value, t *Type) (value.Value, bool)

CoerceArg coerces a call argument to a formal parameter's declared type. Unlike CoerceToType it distinguishes a genuine non-conformance (ok=false) from a valid coercion, so an invocation can evaluate to null as a whole — the function is "not invoked" — rather than binding a silently-nulled argument (DMN §10.4, TCK 0082/0085). A null argument conforms to every type.

func CoerceToType

func CoerceToType(v value.Value, t *Type) value.Value

CoerceToType applies FEEL's item-definition coercion of a value to a declared type (DMN §10.3.2.9.4): a conforming value is kept; a singleton list whose sole element conforms is unwrapped to that element; anything else becomes null. A nil *Type (Any) imposes nothing.

func ConformsToType

func ConformsToType(v value.Value, t *Type) bool

ConformsToType reports whether v is a member of the FEEL type t. null is a member of every type; a nil *Type is Any and accepts anything; lists and contexts are checked element- and field-wise against any declared element or field types (DMN §10.3.2.9.4).

func ConstValue

func ConstValue(src string) (value.Value, bool)

ConstValue returns the value of src when it is a single constant literal — a number, string, boolean, null or @-temporal — and false otherwise. It lets a caller evaluate a constant cell (e.g. a decision-table output) without a scope, for example to test it against an allowed-values constraint (WP-31).

func Matches

func Matches(test CompiledExpr, s *Scope) (bool, error)

Matches evaluates a compiled unary test against scope and reports whether it matched (evaluated to true). A null or non-boolean result is not a match.

func NullExpr

func NullExpr(s *Scope) (value.Value, error)

NullExpr is a CompiledExpr that always yields null. It fills omitted arguments of a call so the callee always receives a full argument list.

Types

type Arg

type Arg struct {
	Name  string
	Value Expr
}

Arg is a function-call argument. Name is empty for positional arguments.

func (Arg) String

func (a Arg) String() string

type AtLit

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

AtLit is a temporal literal such as @"2024-01-01"; Value holds the content.

func (AtLit) Pos

func (n AtLit) Pos() Position

func (*AtLit) String

func (n *AtLit) String() string

type BetweenExpr

type BetweenExpr struct {
	X, Low, High Expr
	// contains filtered or unexported fields
}

BetweenExpr is `X between Low and High`.

func (BetweenExpr) Pos

func (n BetweenExpr) Pos() Position

func (*BetweenExpr) String

func (n *BetweenExpr) String() string

type BinaryExpr

type BinaryExpr struct {
	Op   string
	X, Y Expr
	// contains filtered or unexported fields
}

BinaryExpr is an infix operation (arithmetic, boolean or comparison).

func (BinaryExpr) Pos

func (n BinaryExpr) Pos() Position

func (*BinaryExpr) String

func (n *BinaryExpr) String() string

type BoolLit

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

BoolLit is a true/false literal.

func (BoolLit) Pos

func (n BoolLit) Pos() Position

func (*BoolLit) String

func (n *BoolLit) String() string

type CallExpr

type CallExpr struct {
	Fn   Expr
	Args []Arg
	// contains filtered or unexported fields
}

CallExpr is a function call `Fn(args...)`.

func (CallExpr) Pos

func (n CallExpr) Pos() Position

func (*CallExpr) String

func (n *CallExpr) String() string

type CmpTest

type CmpTest struct {
	Op string
	Y  Expr
	// contains filtered or unexported fields
}

CmpTest is an operator-prefixed positive unary test on the right-hand side of `in`, e.g. the `<= 10` in `x in <= 10` or the explicit `= 10` in `x in = 10`. It compares the in-value against Y with Op (one of < <= > >= = !=). It only occurs inside InExpr.Tests.

func (CmpTest) Pos

func (n CmpTest) Pos() Position

func (*CmpTest) String

func (n *CmpTest) String() string

type CompileError

type CompileError struct {
	Msg  string
	Line int
	Col  int
}

CompileError is a compile-time failure (unknown variable, unsupported construct, malformed literal) with its source position.

func (*CompileError) Error

func (e *CompileError) Error() string

type CompiledExpr

type CompiledExpr func(*Scope) (value.Value, error)

CompiledExpr is a compiled FEEL expression: a pure Go closure that evaluates against a Scope. It performs no AST walk or reflection in the hot path (ADR-0004) and is immutable, so it may be evaluated concurrently.

func BoxedFilter

func BoxedFilter(coll CompiledExpr, matchSrc string, env *Env, funcs map[string]*Func) (CompiledExpr, error)

BoxedFilter compiles a boxed filter: coll is the already-compiled collection and matchSrc the FEEL predicate text, compiled against env extended with the implicit element variable item (its context keys resolve directly, e.g. `age > 18`). A numeric predicate selects by index (WP-26).

func CallFunc

func CallFunc(f *Func, args []CompiledExpr) CompiledExpr

CallFunc returns a CompiledExpr that calls the statically known function f with the given compiled arguments (already arranged in f's parameter order), under the recursion-depth limit. It is the entry point boxed invocations use to call a business knowledge model.

func CallValue

func CallValue(callee CompiledExpr, args []CompiledExpr) CompiledExpr

CallValue returns a CompiledExpr that evaluates callee to a function value and calls it with the compiled positional arguments. A callee that is not a function yields null.

func Compile

func Compile(expr Expr, env *Env) (CompiledExpr, error)

Compile lowers an AST into a CompiledExpr, resolving variable names to slots via env. It returns the first CompileError encountered, if any.

func CompileString

func CompileString(src string, env *Env) (CompiledExpr, error)

CompileString parses and compiles src in one step. It supplies the built-in registry as the parser's name oracle so multi-word builtin names whose fragments include keywords (e.g. "index of") assemble correctly.

func CompileStringRefs

func CompileStringRefs(src string, env *Env) (CompiledExpr, []string, error)

CompileStringRefs is CompileString that also returns which of env's variable names the expression references (its free variables drawn from env). It lets a caller learn an expression's dependencies without a separate AST walk — used by package dmn's CompiledExpression to report references (full-FEEL flow mappings). The returned names are sorted and unique.

func CompileStringWith

func CompileStringWith(src string, env *Env, funcs map[string]*Func) (CompiledExpr, error)

CompileStringWith is CompileString with user-defined functions in scope. The parser's name oracle covers both the built-ins and the function names, so a multi-word function name (e.g. a BKM named "Rate Table") assembles correctly.

func CompileUnaryTest

func CompileUnaryTest(src string, env *Env) (CompiledExpr, error)

CompileUnaryTest compiles a decision-table input-entry cell into a boolean CompiledExpr over env. env must define InputVar ("?"); a cell may also refer to other decision variables (e.g. "< limit"). An empty cell or "-" always matches.

func CompileUnaryTestWith

func CompileUnaryTestWith(src string, env *Env, funcs map[string]*Func) (CompiledExpr, error)

CompileUnaryTestWith is CompileUnaryTest with user-defined functions (BKMs) in scope, so a cell that calls one (e.g. "> discount(?)") resolves it as a known function rather than an unknown name. A nil map behaves like CompileUnaryTest.

func CompileWith

func CompileWith(expr Expr, env *Env, funcs map[string]*Func) (CompiledExpr, error)

CompileWith is Compile with a set of user-defined functions (BKMs, boxed function definitions) in scope, resolved by name when an expression calls or references them (WP-23/WP-24). A nil map behaves like Compile.

func ForOne

func ForOne(coll, body CompiledExpr) CompiledExpr

ForOne builds a boxed `for` over a single iterator: coll yields the domain (list/range/single value, per the iteration rules) and body — compiled against an env with the iterator variable appended as its trailing slot — runs for each element, collecting the results into a list (WP-26).

func FuncValue

func FuncValue(params []string, body CompiledExpr) CompiledExpr

FuncValue returns a CompiledExpr that yields a first-class function value capturing the scope it is evaluated in (closure over enclosing variables). The body must have been compiled against an Env whose trailing slots are params, in order; calling the value binds the arguments to those slots (missing → null, surplus ignored) and runs the body under the recursion-depth limit.

func IfThenElse

func IfThenElse(cond, then, els CompiledExpr) CompiledExpr

IfThenElse builds a FEEL conditional: then runs on the boolean true; a genuine non-boolean condition (e.g. a string) makes the whole conditional null (DMN 10.3.2.5, TCK 1150); false and null take the else branch (the common null-safe form). It is the shared runtime of the literal `if` and the boxed <conditional> (WP-26).

func QuantifyOne

func QuantifyOne(some bool, coll, pred CompiledExpr) CompiledExpr

QuantifyOne builds a boxed `some` (some=true) or `every` (some=false) over a single iterator, applying FEEL's three-valued semantics: some is true on any satisfied element, every false on any unsatisfied one, with unknowns otherwise yielding null (WP-26). pred is compiled against an env with the iterator variable appended as its trailing slot.

type ContextEntry

type ContextEntry struct {
	Key    string
	KeyPos Position
	Value  Expr
}

ContextEntry is a single key/value pair of a context literal.

type ContextLit

type ContextLit struct {
	Entries []ContextEntry
	// contains filtered or unexported fields
}

ContextLit is a context literal { k: v, ... } with ordered entries.

func (ContextLit) Pos

func (n ContextLit) Pos() Position

func (*ContextLit) String

func (n *ContextLit) String() string

type Env

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

Env is the compile-time symbol table mapping variable names to slot indices. It defines the slot layout that a matching Scope must follow.

func NewEnv

func NewEnv(names ...string) *Env

NewEnv returns an Env with the given variable names assigned slots in order.

func (*Env) Append

func (e *Env) Append(name string) *Env

Append returns a new Env with name bound to a fresh trailing slot, shadowing any existing binding of the same name. Unlike Derive it always allocates a new slot, so it pairs with Scope.Extend to introduce iteration and filter variables that may shadow an outer variable of the same name.

func (*Env) Derive

func (e *Env) Derive(extra ...string) *Env

Derive returns a new Env with extra names appended after the existing slots. It is used to add the implicit unary-test input "?" to a decision env without disturbing the existing slot indices.

func (*Env) Has

func (e *Env) Has(name string) bool

Has reports whether name is a bound variable. It lets an Env act as a parser NameSet oracle so a variable whose name embeds a keyword or a hyphen (e.g. "Date-Time") assembles as one name instead of a subtraction (WP-41.15).

func (*Env) Names

func (e *Env) Names() []string

Names returns the variable names in slot order.

func (*Env) NewScope

func (e *Env) NewScope(values map[string]value.Value) *Scope

NewScope builds a runtime Scope from named input values, placing each into its env slot. Names absent from values (or with a nil value) become null. This is the single map→slots boundary; everything past it is index-based.

func (*Env) NewScopeShared

func (e *Env) NewScopeShared(values map[string]value.Value, st *EvalState) *Scope

NewScopeShared builds a runtime Scope like NewScopeWithLimits but carries the caller-supplied execution state st instead of allocating a fresh one, so all scopes of a single evaluation share one state (and one allocation).

func (*Env) NewScopeWithLimits

func (e *Env) NewScopeWithLimits(values map[string]value.Value, lim Limits) *Scope

NewScopeWithLimits is NewScope with explicit resource limits enforced for the evaluation rooted at the returned scope (ADR-0008, WP-34).

func (*Env) WithTypes

func (e *Env) WithTypes(types map[string]*Type) *Env

WithTypes returns e with the given user-type resolver attached (for `instance of`). The map is shared, not copied; callers must not mutate it afterwards.

type EvalState

type EvalState = evalState

EvalState is the mutable per-evaluation execution state (the resource counters). It is exported as an alias so a graph evaluator can build one with NewEvalState and share it across every decision's scope via NewScopeShared, allocating it once per evaluation instead of once per decision and bounding the budgets over the whole evaluation (which is what "per-evaluation limits" means).

func NewEvalState

func NewEvalState(lim Limits) *EvalState

NewEvalState builds shared per-evaluation execution state enforcing lim.

type Expr

type Expr interface {
	Pos() Position
	String() string
	// contains filtered or unexported methods
}

Expr is a node in the FEEL abstract syntax tree. Every node carries its source position for diagnostics and renders to a compact S-expression via String, which is the basis for the parser's table tests.

func Parse

func Parse(src string) (Expr, error)

Parse lexes and parses a FEEL expression into an AST. Multi-word names are assembled greedily from plain fragments.

func ParseWithNames

func ParseWithNames(src string, names NameSet) (expr Expr, err error)

ParseWithNames parses src using names as the oracle for multi-word name assembly (may be nil).

type FilterExpr

type FilterExpr struct {
	X      Expr
	Filter Expr
	// contains filtered or unexported fields
}

FilterExpr is `X[Filter]`.

func (FilterExpr) Pos

func (n FilterExpr) Pos() Position

func (*FilterExpr) String

func (n *FilterExpr) String() string

type ForExpr

type ForExpr struct {
	Iterators []Iterator
	Return    Expr
	// contains filtered or unexported fields
}

ForExpr is `for it1, it2, ... return Return` (iterators are cartesian).

func (ForExpr) Pos

func (n ForExpr) Pos() Position

func (*ForExpr) String

func (n *ForExpr) String() string

type Func

type Func struct {
	Name   string
	Params []string
	Body   CompiledExpr
	// ParamTypes, when non-nil for a slot, is the formal parameter's declared type:
	// an argument that does not conform (after singleton-list unwrapping) makes the
	// whole invocation null (the function is "not invoked"). Shorter than Params or
	// nil entries mean an unconstrained (Any) parameter.
	ParamTypes []*Type
	// ResultType, when set, coerces the body's result to the declared return type
	// (DMN §10.3.2.9.4): a non-conforming result — or a singleton list around a
	// conforming element — is coerced, otherwise null.
	ResultType *Type
	// Native, when set, is called instead of Body with the positional argument
	// values (already padded to len(Params) with null). It lets a higher layer
	// supply a callable whose behaviour is not a compiled FEEL body — e.g. package
	// dmn registering a decision service as an invocable function (DMN §10.4).
	Native func(args []value.Value) (value.Value, error)
}

Func is a user-defined FEEL function: a business knowledge model's encapsulated logic, or a function(...) literal compiled as a named callable.

Body is compiled against an Env whose trailing slots are the formal Params (in order); calling the function runs Body in a scope whose those slots hold the argument values. Body is assigned after compilation so a function may reference itself (recursion) or sibling functions (mutual recursion) by name.

type FunctionDefExpr

type FunctionDefExpr struct {
	Params   []Param
	External bool
	Body     Expr
	// contains filtered or unexported fields
}

FunctionDefExpr is `function(params) body` or `function(params) external body`.

func (FunctionDefExpr) Pos

func (n FunctionDefExpr) Pos() Position

func (*FunctionDefExpr) String

func (n *FunctionDefExpr) String() string

type IfExpr

type IfExpr struct {
	Cond, Then, Else Expr
	// contains filtered or unexported fields
}

IfExpr is `if Cond then Then else Else` (else is mandatory in FEEL).

func (IfExpr) Pos

func (n IfExpr) Pos() Position

func (*IfExpr) String

func (n *IfExpr) String() string

type InExpr

type InExpr struct {
	X     Expr
	Tests []Expr
	// contains filtered or unexported fields
}

InExpr is `X in (t1, t2, ...)` or `X in t`.

func (InExpr) Pos

func (n InExpr) Pos() Position

func (*InExpr) String

func (n *InExpr) String() string

type InstanceOfExpr

type InstanceOfExpr struct {
	X    Expr
	Type string
	// contains filtered or unexported fields
}

InstanceOfExpr is `X instance of Type`.

func (InstanceOfExpr) Pos

func (n InstanceOfExpr) Pos() Position

func (*InstanceOfExpr) String

func (n *InstanceOfExpr) String() string

type IntervalLit

type IntervalLit struct {
	LowClosed  bool
	Low        Expr
	High       Expr
	HighClosed bool
	// contains filtered or unexported fields
}

IntervalLit is a range literal whose endpoints may be open or closed, e.g. [1..10], (1..10], ]1..10[.

func (IntervalLit) Pos

func (n IntervalLit) Pos() Position

func (*IntervalLit) String

func (n *IntervalLit) String() string

type Iterator

type Iterator struct {
	Name    string
	NamePos Position
	In      Expr
}

Iterator is one `name in domain` clause of a for/some/every expression.

func (Iterator) String

func (i Iterator) String() string

type Kind

type Kind int

Kind enumerates the lexical token categories produced by the lexer.

Per docs/30-feel-spec.md §2 the lexer does not assemble multi-word FEEL names: it emits one Name token per identifier fragment (and distinct tokens for keywords), leaving longest-match name assembly to the parser (WP-04).

const (
	EOF Kind = iota
	// Error marks an invalid lexeme. Its Value carries a human-readable message
	// and its Text the offending source. The lexer always makes progress after
	// an Error so tokenisation terminates on any input.
	Error

	// Literals and identifiers.
	Number // 42, 3.14, .5, 1.2e10
	String // "abc", with escapes resolved into Value
	At     // @"2024-01-01" temporal literal; Value holds the quoted content
	Name   // an identifier fragment

	// Keywords.
	And
	Or
	Not
	If
	Then
	Else
	For
	In
	Return
	Some
	Every
	Satisfies
	Between
	Instance
	Of
	Function
	External
	True
	False
	Null

	// Operators.
	Plus  // +
	Minus // -
	Star  // *
	Slash // /
	Pow   // **
	Eq    // =
	Neq   // !=
	Lt    // <
	Lte   // <=
	Gt    // >
	Gte   // >=

	// Punctuation.
	LParen   // (
	RParen   // )
	LBracket // [
	RBracket // ]
	LBrace   // {
	RBrace   // }
	Comma    // ,
	Colon    // :
	Dot      // .
	DotDot   // ..
)

Token kinds.

func (Kind) String

func (k Kind) String() string

String returns a stable name for the kind.

type Lexer

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

Lexer turns FEEL source into a stream of tokens. It never panics: malformed input yields Error tokens and the lexer always advances, so Tokenize terminates on any input (docs/30-feel-spec.md §2, WP-03 acceptance criterion).

func New

func New(src string) *Lexer

New returns a Lexer over src.

func (*Lexer) Next

func (l *Lexer) Next() Token

Next returns the next token. At end of input it repeatedly returns EOF.

type LimitError

type LimitError struct {
	Limit string // which limit: "call depth", "iterations", "list size"
	Max   int
}

LimitError reports that an evaluation exceeded a configured resource limit (ADR-0008). The execution-edge classifier maps it to a distinct code so a limit breach is distinguishable from other runtime failures.

func (*LimitError) Error

func (e *LimitError) Error() string

type Limits

type Limits struct {
	MaxCallDepth  int // nested user-function (BKM / function literal) calls
	MaxIterations int // total iteration steps across all comprehensions
	MaxListSize   int // element count of any single produced list
}

Limits configures the per-evaluation resource bounds. A zero field means that dimension is unbounded; DefaultLimits supplies safe non-zero defaults.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits returns the resource limits applied when none are configured. They are generous enough not to affect normal models yet bound hostile input (deep recursion, runaway comprehensions, huge lists).

type ListLit

type ListLit struct {
	Elements []Expr
	// contains filtered or unexported fields
}

ListLit is a list literal [e1, e2, ...].

func (ListLit) Pos

func (n ListLit) Pos() Position

func (*ListLit) String

func (n *ListLit) String() string

type NameRef

type NameRef struct {
	Name  string
	Parts []string
	// contains filtered or unexported fields
}

NameRef is a (possibly multi-word) name reference. Parts holds the original fragments; Name is them joined by single spaces.

func (NameRef) Pos

func (n NameRef) Pos() Position

func (*NameRef) String

func (n *NameRef) String() string

type NameSet

type NameSet interface {
	// Has reports whether name (fragments joined by single spaces) is known.
	Has(name string) bool
}

NameSet is an optional oracle of known names. When supplied to the parser it enables longest-match assembly of multi-word names, including names that contain keywords (e.g. the builtin "date and time"). Without it, the parser greedily merges consecutive plain name fragments, which covers all names that do not embed a keyword (see ADR / docs/30-feel-spec.md §2).

type NullLit

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

NullLit is the null literal.

func (NullLit) Pos

func (n NullLit) Pos() Position

func (*NullLit) String

func (n *NullLit) String() string

type NumberLit

type NumberLit struct {
	Text string
	// contains filtered or unexported fields
}

NumberLit is a numeric literal. The source text is kept verbatim; decimal parsing happens in WP-05.

func (NumberLit) Pos

func (n NumberLit) Pos() Position

func (*NumberLit) String

func (n *NumberLit) String() string

type Param

type Param struct {
	Name string
	Type string // optional type reference, empty if unspecified
}

Param is a formal parameter of a function definition.

func (Param) String

func (p Param) String() string

type ParseError

type ParseError struct {
	Msg  string
	Line int
	Col  int
}

ParseError is a syntax error with its source position.

func (*ParseError) Error

func (e *ParseError) Error() string

type PathExpr

type PathExpr struct {
	X    Expr
	Name string
	// contains filtered or unexported fields
}

PathExpr is member access `X.Name`.

func (PathExpr) Pos

func (n PathExpr) Pos() Position

func (*PathExpr) String

func (n *PathExpr) String() string

type Position

type Position struct {
	Line int
	Col  int
}

Position is a 1-based source location (line and column in runes).

type QuantifiedExpr

type QuantifiedExpr struct {
	Quant     string // "some" or "every"
	Iterators []Iterator
	Satisfies Expr
	// contains filtered or unexported fields
}

QuantifiedExpr is `some|every it1, ... satisfies Satisfies`.

func (QuantifiedExpr) Pos

func (n QuantifiedExpr) Pos() Position

func (*QuantifiedExpr) String

func (n *QuantifiedExpr) String() string

type Scope

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

Scope holds a compiled expression's variables as a flat slot array. Compiled variable accesses are resolved to slot indices at compile time (ADR-0004, architecture §5.2), so evaluation is an array index rather than a map lookup. A Scope is read-only during evaluation and therefore safe to share across goroutines.

A Scope may carry an opaque trace sink (WithTrace). The execution core treats it as an untyped value and never inspects it; a consumer that wants an explanation attaches its own recorder and type-asserts it back where it records (e.g. the decision-table evaluator). The default scope carries no sink (nil), so non-traced evaluation stays allocation-free.

func (*Scope) BindInput

func (s *Scope) BindInput(v value.Value)

BindInput rebinds the implicit-input slot of a scope returned by WithInput to v. It overwrites the trailing slot in place; use only on a WithInput scope.

func (*Scope) Extend

func (s *Scope) Extend(extra ...value.Value) *Scope

Extend returns a new Scope with extra values appended after the existing slots, matching an Env produced by Derive. The receiver is left unchanged, so a base scope can be extended repeatedly with different values.

func (*Scope) Trace

func (s *Scope) Trace() any

Trace returns the scope's opaque trace sink, or nil when tracing is off.

func (*Scope) WithInput

func (s *Scope) WithInput() *Scope

WithInput returns a scope with one extra trailing slot holding a decision-table unary test's implicit input ("?"), which the unary-test env (Env.Derive with InputVar) places last. The slot starts null; BindInput rebinds it. The scope is freshly allocated and confined to a single evaluation, so a decision table reuses one such scope across all input columns — rebinding the slot per column via BindInput — instead of allocating a scope per column. Rebinding in place is safe precisely because the scope is not shared: it is created here and only the evaluating goroutine reads it, synchronously, between rebinds.

func (*Scope) WithTrace

func (s *Scope) WithTrace(sink any) *Scope

WithTrace returns a shallow copy of the scope carrying the given trace sink. The variable slots are shared (they are read-only), so this is cheap and is done once per traced evaluation at the root scope.

type StringLit

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

StringLit is a string literal with escapes already resolved into Value.

func (StringLit) Pos

func (n StringLit) Pos() Position

func (*StringLit) String

func (n *StringLit) String() string

type Token

type Token struct {
	Kind Kind
	// Text is the exact source lexeme (raw, including quotes for strings).
	Text string
	// Value holds decoded content for String and At tokens (escapes resolved),
	// or the error message for Error tokens. It is empty otherwise.
	Value string
	Line  int
	Col   int
}

Token is a single lexical token with its source position (1-based line and column, measured in runes).

func Tokenize

func Tokenize(src string) []Token

Tokenize lexes the whole input and returns all tokens including the final EOF.

type Type

type Type struct {
	Kind   value.Kind
	Elem   *Type            // element type for a list (nil = unknown element)
	Fields map[string]*Type // field types for a context (nil = open/unknown)
}

Type is a FEEL type used by the static type checker (WP-30) and the `instance of` operator. A nil *Type means Any — an unknown or unconstrained type that the checker never flags and that conforms to (and from) every type.

Concrete scalar types are the package singletons (TNumber, TString, …). A List carries its element type in Elem (nil = list of Any); a Context carries its known field types in Fields.

func BuiltinType

func BuiltinType(name string) (*Type, bool)

BuiltinType resolves a FEEL built-in type name (optionally namespace-prefixed, e.g. "feel:number", and ignoring any generic parameter) to its Type. The second result is false for a name that is not a built-in type (Any, or a user-defined item-definition type the caller must resolve itself).

func ContextOf

func ContextOf(fields map[string]*Type) *Type

ContextOf returns a context type with the given field types.

func ListOf

func ListOf(elem *Type) *Type

ListOf returns the list type with the given element type (nil elem = list of Any).

func (*Type) String

func (t *Type) String() string

String renders the type in canonical FEEL form.

type TypeEnv

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

TypeEnv maps variable names to their declared types for static checking. A name that is absent (or mapped to nil) is Any and is never flagged.

func NewTypeEnv

func NewTypeEnv() *TypeEnv

NewTypeEnv returns an empty type environment.

func (*TypeEnv) Set

func (e *TypeEnv) Set(name string, t *Type) *TypeEnv

Set binds name to type t and returns the env for chaining.

type TypeError

type TypeError struct {
	Msg  string
	Line int
	Col  int
}

TypeError is a static type-check finding with its source position (within the expression text).

func Typecheck

func Typecheck(expr Expr, env *TypeEnv) []TypeError

Typecheck statically infers types over expr against env and returns the provable type mismatches it finds. It is deliberately conservative: an operand whose type is unknown (Any) is never flagged, so a well-typed model and a model the checker cannot reason about both produce no findings — only a definite, statically-provable clash is reported. Evaluation still follows FEEL's null semantics regardless; these findings are advisory.

func TypecheckString

func TypecheckString(src string, env *TypeEnv, funcs map[string]*Func) []TypeError

TypecheckString parses src (using the same name oracle as compilation, so multi-word built-in and function names assemble) and type-checks it against env. A source that does not parse yields no findings — the compile path reports the syntax error separately.

func (TypeError) Error

func (e TypeError) Error() string

type UnaryExpr

type UnaryExpr struct {
	Op string
	X  Expr
	// contains filtered or unexported fields
}

UnaryExpr is a prefix operation; Op is currently always "-".

func (UnaryExpr) Pos

func (n UnaryExpr) Pos() Position

func (*UnaryExpr) String

func (n *UnaryExpr) String() string

Directories

Path Synopsis
Package builtins is the registry of FEEL built-in functions.
Package builtins is the registry of FEEL built-in functions.
Package value is the FEEL/DMN runtime value model: the Value interface and its concrete kinds (null, boolean, number, string, the temporal types, list, context, range and function), together with equality, ordering and arithmetic that follow FEEL semantics — most importantly decimal numbers (never float64, ADR-0007) and pervasive null propagation.
Package value is the FEEL/DMN runtime value model: the Value interface and its concrete kinds (null, boolean, number, string, the temporal types, list, context, range and function), together with equality, ordering and arithmetic that follow FEEL semantics — most importantly decimal numbers (never float64, ADR-0007) and pervasive null propagation.

Jump to

Keyboard shortcuts

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