Operators
CEL operators combine existing expression values. Precedence and associativity determine how unparenthesized expressions group. For the full grammar, see the Reference Manual.
Concept
Unary, binary, cast, and postfix operators all participate in one expression grammar. Postfix calls, tuple indices, and named members bind most tightly. Range forms bind most loosely. Logical operators short-circuit, and comparison and range forms do not chain.
Syntax
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 } .
Worked examples
10 + 20 * 5
"cel" + "-" + "lang"
ok && round_ties_even(3.5) > 3.0
1u64 << 3u32
1.5 as i32
1 + 2..3 * 4
Exact rules
Precedence runs from lowest to highest in this order:
- range forms
||&&- comparison operators
- bitwise
| - bitwise
^ - bitwise
& - shifts
<<and>> - additive
+and- - multiplicative
*,/, and% - casts with
as - unary
-and! - postfix calls, tuple indexing, and named members
Within one precedence level, binary operators associate to the left. Comparison expressions consume one comparison operator, and range forms do not chain.
- A call is a postfix
()form with zero or more comma-separated arguments. receiver.nameandreceiver.name()both invoke a unary named operation, ignoring same-named value bindings.receiver.name(a, b)supplies the receiver first, then explicit arguments; each evaluates once in source order. The member label is not a variable reference, even inside a closure.- Grouping changes call semantics:
(receiver.name)()calls the accessor’s result as a value. It is not another spelling ofreceiver.name(). Named members, calls, and.Nshare postfix precedence and chain left to right. Neither named members nor tuple indices support assignment. - Hosts supply member signatures, not a fixed set of receiver types. The checker rejects known incompatible operands and preserves registered result types. Calls without metadata remain best-effort and still check every argument.
- Postfix calls and tuple indices bind more tightly than unary and binary operators.
+accepts homogeneous numeric operands andString + String. Unsigned integer addition wraps. Signed integer addition reportsarithmetic overflowon overflow.- Binary
-accepts homogeneous numeric operands. Unary-accepts only signed integers and floating-point values. Unsigned integer subtraction wraps. Signed binary subtraction and signed unary negation reportarithmetic overflowon overflow. *accepts homogeneous numeric operands. Unsigned integer multiplication wraps. Signed integer multiplication reportsarithmetic overflowon overflow./and%accept homogeneous numeric operands. Floating-point division and remainder use normal floating-point behavior. Integer failures reportdivision by zero.&,|, and^accept homogeneous integer operands only.<<and>>accept integer operands only. The right operand must fit inu32, and the shift count must be in range. Shift failures reportshift overflow.!acceptsboolonly.&&and||acceptbooloperands only and short-circuit.==and!=accept homogeneous numeric operands, homogeneousbool, or homogeneousString.<,<=,>, and>=accept homogeneous numeric operands or homogeneousString.- Range forms bind more loosely than every other operator. The valid forms
are
a..b,a..=b,a..,..b,..=b, and... ..=always requires a right endpoint.- Range endpoints are full expressions.
- Range expressions do not chain, so
1..2..3is not valid. 1 + 2..3 * 4means(1 + 2)..(3 * 4).asconverts a value to one of the sixteen built-in scalar type names:i8,i16,i32,i64,i128,isize,u8,u16,u32,u64,u128,usize,f32,f64,bool, andString.- Integer targets accept integer, floating-point, and
boolsources. Floating-point targets accept integer and floating-point sources.boolandStringtargets accept only their same-type sources. - Integer-to-integer casts check that the source fits in the target.
Floating-point-to-integer casts require a finite, in-range source and
truncate toward zero.
f64 as f32checks finite range before narrowing. truecasts to integer1, andfalsecasts to integer0.- CEL limits cast targets to the sixteen built-in scalar type names.
char, byte-string, C-string, unit, array, tuple, range, and closure values are not additional cast target names.- Number-to-
bool,bool-to-float, andString-to-number orString-to-boolcasts are not valid. - Casts associate from left to right, so
x as T as Uapplies the first cast before the second. - See Conditional expressions for
ifexpressions, and the Reference Manual for a summary.
Edge cases
- Write
a < b && b < c, nota < b < c. - Call argument lists do not accept a trailing comma.
- Negative shift counts, counts larger than
u32, and counts outside the left operand’s width reportshift overflow. - Signed
+, signed binary-, signed unary-, and signed*overflows reportarithmetic overflow. - Integer
/and%reportdivision by zerowhen they fail. - Casts cannot target
char, byte-string, C-string, unit, array, tuple, range, or closure values.