Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reference Manual

This chapter is the compact language reference for CEL expressions. It gathers the core grammar, token categories, type names, literal forms, operators, standard-library operations, collections, closures, and sharp edges in one place.

Concept

CEL is an expression language. Every source form in this chapter produces a value or helps compose one. Use the tutorial chapters for worked prose, and use this chapter when you need the exact grammar or a concise rule table.

Syntax

expression = range_expression .
range_expression = ".." [ or_expression ]
                 | "..=" or_expression
                 | or_expression [ ".." [ or_expression ] | "..=" or_expression ] .
or_expression = and_expression { "||" and_expression } .
and_expression = comparison_expression { "&&" comparison_expression } .
comparison_expression = bitwise_or_expression
    [ ("==" | "!=" | "<" | ">" | "<=" | ">=") bitwise_or_expression ] .
bitwise_or_expression = bitwise_xor_expression { "|" bitwise_xor_expression } .
bitwise_xor_expression = bitwise_and_expression { "^" bitwise_and_expression } .
bitwise_and_expression = bitwise_shift_expression { "&" bitwise_shift_expression } .
bitwise_shift_expression = additive_expression { ("<<" | ">>") additive_expression } .
additive_expression = multiplicative_expression { ("+" | "-") multiplicative_expression } .
multiplicative_expression = cast_expression { ("*" | "/" | "%") cast_expression } .
cast_expression = unary_expression { "as" identifier } .
unary_expression = (("-" | "!") unary_expression) | postfix_expression .
postfix_expression = primary_expression {
    "(" [ argument_list ] ")" | "." (unsuffixed_integer | identifier [ "(" [ argument_list ] ")" ])
} .
argument_list = expression { "," expression } .
primary_expression = literal
                   | identifier [ record_fields ]
                   | tuple_or_group
                   | array_expression
                   | if_expression
                   | closure_expression .
record_fields = "{"
    [ field_initializer { "," field_initializer } [ "," ] ] "}" .
field_initializer = identifier ":" expression .
tuple_or_group = "(" [ expression [ "," [ expression { "," expression } ] ] ] ")" .
array_expression = "[" [ expression { "," expression } ] "]"
                 [ ":" type_expr ] .
type_expr = identifier [ "(" type_expr { "," type_expr } ")" ]
          | "[" type_expr "]"
          | "(" [ type_expr [ "," [ type_expr { "," type_expr } ] ] ] ")" .
if_expression = "if" expression "{" expression "}"
              [ "else" ( "{" expression "}" | if_expression ) ] .
closure_expression = "||" expression
                   | "|" closure_param { "," closure_param } "|" expression .
closure_param = identifier ":" type_expr .

Worked examples

(1, 2).0
if ok && ready { [1, 2] } else { [3, 4] }
|pair: (i32, i32)| pair.0 + pair.1
1 + 2..=3 * 4
true as i32

Token table

CategoryFormsNotes
Identifiersname, value2, user_nameBare names for values, callees, and closure parameters.
Reserved wordsif, else, as, true, falseThese spellings have fixed language roles.
Literal tokensintegers, floats, strings, characters, bytes, byte strings, C stringsUnit () comes from grammar, not from a standalone literal token.
Delimiters( ) [ ] { }Used for grouping, tuples, calls, arrays, and if branches.
Structural punctuation, : .Used for separators, closure parameter types, and tuple indexing.
Operators+ - * / % ! && || == != < <= > >= & | ^ << >> .. ..= asSee the operator tables below.

Type table

KindNames or formsNotes
Built-in scalar typesi8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, StringThese are the named cast targets. Closure annotations also accept registered cloneable leaves and built-in generic types through the configured type resolver.
Other value formschar, byte strings, C strings, unit, tuples, arrays, ranges, closuresExpressions can use these values, but they are not extra built-in scalar type names.
Structural types(), (T,), (T, U), [T], Fn((A, B), R)Compose recursively without registering each aggregate. (T) groups a type. Fn((), R) has no parameters; Fn((T,), R) has one.

Literal table

FormExampleResult
Integer42, 42u16Unsuffixed integers default to i32. Integer suffixes are i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, and usize.
Floating-point3.5, 6.02e23f32Unsuffixed floating-point literals default to f64. Floating-point suffixes are f32 and f64.
Booleantrue, falseProduces bool.
String"hello"Produces String.
Character'a'Produces char.
Byteb'A'Produces u8.
Byte stringb"bytes"Produces a byte-string value.
C stringc"header"Produces a C-string value.
Unit()Produces the unit value.

Operator precedence table

Level (low to high)FormsAssociativity
1range formsnon-chaining
2||left
3&&left
4==, !=, <, <=, >, >=non-chaining
5|left
6^left
7&left
8<<, >>left
9+, -left
10*, /, %left
11asleft
12unary -, !prefix
13calls, .N, .name, .name(args)left

Operator rules table

FormsAccepted operandsNotes
+homogeneous numeric operands, or String + StringUnsigned integers wrap. Signed integers report arithmetic overflow on overflow. Strings concatenate.
binary -homogeneous numeric operandsUnsigned integers wrap. Signed integers report arithmetic overflow on overflow.
unary -signed integers and floating-point valuesSigned integer overflow reports arithmetic overflow.
*homogeneous numeric operandsUnsigned integers wrap. Signed integers report arithmetic overflow on overflow.
/, %homogeneous numeric operandsInteger failures report division by zero.
&, |, ^homogeneous integer operandsInteger-only bitwise operators.
<<, >>integer left operand and integer right operandThe right operand must fit u32, and the shift count must be in range. Failures report shift overflow.
!boolLogical negation.
&&, ||bool and boolShort-circuit logical operators.
==, !=homogeneous numeric operands, homogeneous bool, or homogeneous StringProduces bool.
<, <=, >, >=homogeneous numeric operands or homogeneous StringProduces bool.
range formshomogeneous numeric endpoints, or no endpoints for ....= requires a right endpoint. Endpoints are full expressions.
asexpression plus built-in scalar target nameInteger targets accept integer, floating-point, and bool sources. f32/f64 targets accept integer and floating-point sources. bool and String targets accept only their same-type sources.
call and .Na valid callee and arguments, or a tuple plus an unsuffixed integer.N applies to tuples only.

Numeric call table

All functions in this table belong to the standard library and are available when the evaluation environment installs it. See Standard library for worked examples and fuller prose.

FunctionAccepted operandsResultNotes
min(a, b), max(a, b)same-type numeric operands: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64same as operandsNo coercion. Successful calls are infallible.
clamp(x, lo, hi)same-type numeric operands from the same 14-type setsame as operandsRequires ordered bounds with lo <= hi; otherwise reports invalid clamp bounds.
abs(x)i8, i16, i32, i64, i128, isize, f32, f64same as operandMinimum signed integer reports arithmetic overflow.
signum(x)i8, i16, i32, i64, i128, isize, f32, f64same as operandNo unsigned support. Successful calls are infallible.
sqrt(x)f32, f64same as operandFloat-only. Inputs below zero yield NaN rather than an error.
floor(x), ceil(x), trunc(x), fract(x), round_ties_even(x)f32, f64same as operandFloat-only. Successful calls are infallible.
  • None of the documented functions are integer-only.
  • sqrt, floor, ceil, trunc, fract, and round_ties_even are float-only.
  • min, max, and clamp support both integers and floats.
  • abs and signum support signed integers and floats, but not unsigned integers.

Collection table

FormSyntaxRules
Unit()The empty parenthesized form is the unit value.
Grouping(expr)Changes grouping without creating a tuple.
Tuple(expr,), (a, b, ...)Positional value form. A 1-tuple requires the trailing comma.
Array[a, b, ...]Unannotated arrays are non-empty, homogeneous, and comma-separated.
Typed array[a, b]: [T], []: [T]The annotation states the complete array type and permits empty arrays.
Nested array[[0], [1]], []: [[i32]]Each element must still have the same array element type.
Tuple array[(0, true)], []: [(i32, bool)]Each positional type participates in homogeneity.
Callable array[]: [Fn((i32,), [i32])]Empty arrays retain full signatures; equality is not required for storage.
Rangea..b, a..=b, a.., ..b, ..=b, ..Endpoint-bearing forms require homogeneous numeric endpoints.

Closure table

FormMeaningNotes
|| exprzero-parameter closureThe body is one expression.
|x: T| exprone typed parameterT resolves through the configured type environment and may recursively contain tuples or arrays.
|x: T, y: U| exprmultiple typed parametersParameters are comma-separated and ordered left to right.
|pair: (i32, i32)| exprone tuple-typed parameterTuple parameters are still one parameter and use .N inside the body.
nested tuple type|value: (i32, (f64, bool))| exprTuple types may nest recursively.
(|x: f64| x)(1.0)immediate invocationCEL checks exact parameter/result shapes; arguments evaluate once, left to right. Unknown callable signatures fail explicitly. Host call operations retain precedence.

Exact rules

  • Calls accept zero or more comma-separated arguments and do not accept a trailing comma.
  • Tuple indexing uses .N with an unsuffixed integer and may be chained.
  • Unannotated arrays are non-empty and homogeneous. A complete type annotation permits an empty array and must match the element type exactly.
  • Tuples and arrays are distinct forms and compose recursively, including registered leaves, source unit, and callable values.
  • Rust interoperability uses Array<T>, source-ordered canonical lists (Nil/Cons witnesses), and Callable<Args, R> (FnOf witness). Array<Array<T>> converts to Vec<Array<T>>, not allocation-preserving deep native vectors. See collections.
  • Arbitrary erased external values require complete shape checks. PreparedSegment and prepared_segment::PreparedRawSegment validate at preparation, not each invocation. Clone, equality, and formatting are explicit independent capabilities.
  • Native Rust operations can serve interpreted CEL through typed adapters. A macro-compiled CEL backend does not yet exist.
  • Comparison expressions consume one comparison operator.
  • Range forms do not chain.
  • expr.name and expr.name() invoke the same host-provided unary operation. expr.name(a, b) passes the receiver first, followed by explicit arguments. Each operand evaluates once in source order. A value binding named name does not hide a dot operation, and the member label is not a variable reference.
  • (expr.name)() calls the operation’s result normally; it fails if the result is not callable. Dot operations, ordinary calls, and tuple indexing share postfix precedence and may be chained.
  • TypeName { field: expression, ... } constructs a host-registered record. Constructor names belong to the type namespace, separate from value names. Field labels are not variable references. Every schema field is required exactly once; unknown, duplicate, missing, and incorrectly typed fields are errors. Field expressions evaluate once in written order, independent of schema order. An empty schema permits TypeName {}; a non-empty field list permits a trailing comma.
  • Field types match exactly: an f64 field accepts 1.0, not the i32 literal 1. The static checker uses installed record schemas and operation signatures; known custom types retain their identities instead of unifying with unrelated types. Unknown constructors are errors. Unresolved inputs and calls without metadata remain best-effort, but all their child expressions are still checked.
  • Unparenthesized record constructors are forbidden throughout an if condition. Parentheses, call arguments, tuple elements, and array elements start unrestricted expression positions: write if (Sample { value: 3.0 }.value) > 0.0 { ... }. The restriction propagates through operators, ranges, and closure bodies.
  • Members are reads/operations, not assignment targets. CEL has no member assignment.
  • CEL does not use bracket postfix indexing such as expr[0].
  • CEL does not define map literals, object literals, or a ternary operator.
  • A closure body resolves its own parameters and does not capture free variables from the enclosing expression.
  • A one-element tuple type requires (T,); (T) is grouping.

Edge cases

  • Signed +, signed binary -, signed unary -, and signed * overflow report arithmetic overflow.
  • Integer / and % failures report division by zero.
  • Negative, too-large, or non-u32-fitting shift counts report shift overflow.
  • a < b < c is not valid; write a < b && b < c.
  • 1..2..3 is not valid.
  • ..= is not valid without a right endpoint.
  • [] is not valid.
  • [(0, 1), (true, 2)] is not valid because element shapes differ.