CEL (Common Expression Language)
wireform-cel is a conformant Haskell implementation of Google’s
Common Expression Language (CEL).
CEL is a non-Turing-complete, side-effect-free, strongly- and
dynamically-typed expression language used for policy, validation, and
filtering: IAM conditions, Envoy/xDS, protobuf field validation, and more.
The package provides the lexer, a recursive-descent parser, the dynamic
runtime Value model, and an evaluator with the full standard library of
operators, conversions, functions, and comprehension macros.
Unlike most packages in the wireform monorepo, wireform-cel depends only on
Hackage libraries (no other wireform format packages beyond wireform-core),
so it builds standalone. It is the engine that
wireform-protovalidate is built on.
Key features
Section titled “Key features”- Complete grammar: ternary
?:,||/&&, relations andin, arithmetic, unary-/!, member selection / indexing / calls, list and map literals, andName{...}message-literal syntax. Full literal lexis covers decimal/hex integers, theuunsigned suffix, floats, single/double and triple-quoted strings, raw (r"...") strings, bytes (b"...") literals, and the entire escape-sequence set with surrogate/range validation (including backtick-escaped identifiers). - Number-line numeric semantics:
1 == 1u == 1.0with cross-type ordering,NaNthat is never equal and always unordered, heterogeneous equality, and overflow-checked arithmetic. - Error-absorbing logic: commutative
&&/||that absorb errors per the CEL spec. - Comprehension macros:
has,all,exists,exists_one,map(3- and 4-argument), andfilter, plus the two-variablemacros2formsall/exists/existsOne/transformList/transformMap, with comprehension scoping. - Standard library:
size,type,dyn, the conversions (int/uint/double/string/bool/bytes/duration/timestamp), the string functions (contains,startsWith,endsWith,matches), and theTimestamp/Durationaccessors (getFullYear,getMonth,getDate,getHours, …,getDayOfWeek,getDayOfYear). Named IANA timezones are supported via thetzlibrary, alongsideUTCand fixed±HH:MMoffsets.
Quick start
Section titled “Quick start”{-# LANGUAGE OverloadedStrings #-}import CEL
main :: IO ()main = do -- Self-contained expressions: print (run emptyEnv "1 + 2 * 3") -- Right (VInt 7) print (run emptyEnv "[1, 2, 3].map(x, x * x)") -- Right (VList [VInt 1,VInt 4,VInt 9]) print (run emptyEnv "'foobar'.matches('o+b')") -- Right (VBool True)
-- Bind variables into the environment: let env = bindAll [("user", VMap (celMapFromList [("age", VInt 30)]))] emptyEnv print (run env "user.age >= 18") -- Right (VBool True)compile parses to an Expr you can evaluate repeatedly with evaluate;
run is the parse-and-evaluate convenience. Errors are returned as
Left CelError.
Compile-time compilation
Section titled “Compile-time compilation”For CEL that is known at compile time there is no need to parse at runtime.
CEL.TH offers two levels; a CEL syntax error becomes a compile error in
both:
[cel| … |]/compileCelparse at compile time and splice the resultingExpras a baked-in constant. No runtime parse;evaluatewalks the AST once.[celFn| … |]/compileCelFngo further and emit the program as Haskell: every CEL node becomes a direct call to aCEL.Evalcombinator, producing anEnv -> Either CelError Valueclosure with no AST walk and no per-node dispatch at runtime. GHC optimizes it like any other Haskell.
{-# LANGUAGE QuasiQuotes #-}import CELimport CEL.TH (cel, celFn)
program :: Exprprogram = [cel| [1, 2, 3].map(x, x * x) |] -- parsed at compile time
-- fully compiled to Haskell; reads variables from the environment:predicate :: Env -> Either CelError Valuepredicate = [celFn| this.size() >= 3 && this.startsWith('x') |]The runtime evaluator is built from the same per-node combinators, so
compileExpr (a reusable Expr -> Env -> Either CelError Value) and the
compile-time path share one definition of the language semantics.
Conformance
Section titled “Conformance”The default test suite (test/) pairs Test.CEL.Conformance (example-based
tests taken from the worked examples in the language definition) with
Test.CEL.Properties (Hedgehog properties for arithmetic, ordering, and
size).
An opt-in suite (wireform-cel-conformance) runs the official
cel-spec
tests/simple/testdata/*.textproto suite, following the monorepo’s
TOML_TEST_SUITE / YAML_TEST_SUITE pattern. Point CEL_SPEC_DIR at a
checkout and run it:
git clone https://github.com/google/cel-specCEL_SPEC_DIR=$PWD/cel-spec cabal test wireform-cel:wireform-cel-conformanceOver the core language files (everything except the extension libraries, the protobuf-enum file, and the type-deduction/unknown-tracking files) the current result is:
TOTAL pass=1124 skip=128 fail=0All 128 skips are protocol-buffer-message cases (construction, field access,
wrapper/Any/Struct conversions). Protobuf message values and the
optional static type-checking phase are the documented gaps; CEL is
dynamically typed, so evaluation does not depend on the type-checker.
Notable modules
Section titled “Notable modules”| Module | Purpose |
|---|---|
CEL | Umbrella module: run, compile, evaluate, emptyEnv, bind, bindAll, Value, CelError |
CEL.Value | The dynamic runtime Value model (VInt, VUInt, VDouble, VString, VList, VMap, …) and celMapFromList |
CEL.Syntax | The Expr AST |
CEL.Parser | Lexer + recursive-descent parser |
CEL.Eval | Per-node combinator evaluator (compileExpr) |
CEL.Stdlib | Standard-library operators, conversions, string/regex functions, timestamp/duration accessors (named IANA timezones via tz) |
CEL.Environment | The Env of variable bindings |
CEL.Error | The CelError type |
CEL.TH | Compile-time compilation: [cel| … |] / compileCel (baked Expr) and [celFn| … |] / compileCelFn (compile-to-Haskell) |