These productions encode precedence and left associativity directly. Comparisons
and ranges do not chain; logical operators may combine separate comparisons.
A closure is a complete expression, not a primary operand. Its body consumes the
remaining expression inside its delimiter boundary. Group a closure before applying
an operator or postfix suffix to it. Arguments and collection elements are complete
expressions and accept bare closures.
A call suffix directly after a named member dispatches that member. Parenthesize
the member before the call suffix to call its result instead.
value_identifier excludes if, else, as, true, and false.
These are the named cast targets. Closure annotations also accept registered cloneable leaves and built-in generic types through the configured type resolver.
Other value forms
char, byte strings, C strings, unit, tuples, arrays, ranges, closures
Expressions 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.
Unsigned integers wrap. Signed integers report arithmetic overflow on overflow. Strings concatenate.
binary -
homogeneous numeric operands
Unsigned integers wrap. Signed integers report arithmetic overflow on overflow.
unary -
signed integers and floating-point values
Signed integer overflow reports arithmetic overflow.
*
homogeneous numeric operands
Unsigned integers wrap. Signed integers report arithmetic overflow on overflow.
/, %
homogeneous numeric operands
Integer failures report division by zero.
&, |, ^
homogeneous integer operands
Integer-only bitwise operators.
<<, >>
integer left operand and integer right operand
The right operand must fit u32, and the shift count must be in range. Failures report shift overflow.
!
bool
Logical negation.
&&, ||
bool and bool
Short-circuit logical operators.
==, !=
homogeneous numeric operands, homogeneous bool, or homogeneous String
Produces bool.
<, <=, >, >=
homogeneous numeric operands or homogeneous String
Produces bool.
range forms
homogeneous numeric endpoints, or no endpoints for ..
..= requires a right endpoint. Group closures and nested ranges used as endpoints.
as
expression plus built-in scalar target name
Integer 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 .N
a valid callee and arguments, or a tuple plus an unsuffixed integer
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.
round resolves halfway cases away from zero; round_ties_even selects the even neighbor.
sin(x), cos(x), tan(x)
f32, f64
same as operand
Angles in radians. Infinite inputs yield NaN.
asin(x), acos(x), atan(x)
f32, f64
same as operand
Results in radians. asin and acos yield NaN outside [-1, 1].
atan2(y, x)
same-type f32 or f64
same as operands
Quadrant-aware angle in radians; vertical coordinate first.
sin_cos(x)
f32, f64
(f32, f32) or (f64, f64)
CEL tuple ordered as sine then cosine; angle in radians.
to_radians(x), to_degrees(x)
f32, f64
same as operand
Angle conversion without wrapping.
hypot(x, y)
same-type f32 or f64
same as operands
Vector length.
cbrt(x)
f32, f64
same as operand
Real cube root, including negative inputs.
powi(x, exponent)
f32 or f64 base, i32 exponent
same as base
Supports negative integer exponents.
powf(x, exponent)
same-type f32 or f64
same as operands
Floating-point power.
exp(x), exp2(x)
f32, f64
same as operand
Powers of e and 2.
ln(x), log2(x), log10(x)
f32, f64
same as operand
Logarithms in bases e, 2, and 10; zeros yield negative infinity and negative inputs yield NaN.
log(x, base)
same-type f32 or f64
same as operands
Arbitrary-base logarithm with Rust’s floating-point domain behavior.
recip(x)
f32, f64
same as operand
Reciprocal; signed zeros yield signed infinities.
mul_add(x, multiplier, addend)
same-type f32 or f64
same as operands
Fused multiply-add with one rounding step.
rem_euclid(x, divisor)
same-type f32 or f64
same as operands
Nonnegative remainder; rounding can produce abs(divisor). Zero divisor yields NaN.
copysign(magnitude, sign)
same-type f32 or f64
same as operands
Copies the second operand’s sign bit onto the first operand’s magnitude.
Both op(x, ...) and x.op(...) select these registered operations.
Float calls retain Rust’s method semantics, including NaN and infinity;
they do not introduce evaluation errors for mathematical domain violations.
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.
Comparison expressions consume one comparison operator.
Range forms do not chain.
Closures require parentheses as operator operands, including range endpoints,
cast inputs, and postfix receivers. Complete-expression positions, including
call arguments, tuple elements, array elements, and branch bodies, accept bare closures.
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 {}. Field lists do not permit trailing commas.
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.