Skip to main content

cel_parser/
lib.rs

1//! A recursive descent parser for CEL (Common Expression Language) expressions.
2//!
3//! This crate provides a parser that can parse CEL expressions into executable segments.
4//! The parser follows the CEL grammar specification and provides detailed error reporting
5//! with source location information.
6//!
7//! # Canonical typed execution
8//!
9//! Arrays, tuples, unit, registered leaves, and `Fn` signatures compose
10//! recursively without registering each aggregate shape. [`ResolvedType`] and
11//! [`Ty`] retain complete shapes; shared physical carrier IDs are not identities.
12//! Preparation validates concrete witnesses before the body executes:
13//!
14//! ```
15//! use cel_parser::{CELParser, OpLookup};
16//! use cel_runtime::{ArrayOf, Cons, IntoCStackList, IntoRustTuple, Leaf, Nil};
17//! type Pair = Cons<Leaf<i32>, Cons<Leaf<bool>, Nil>>;
18//! let body = CELParser::new(OpLookup::new()).parse_str("[(7, true)]").unwrap();
19//! let mut prepared = body.prepare::<Nil, ArrayOf<Pair>>().unwrap();
20//! let pairs = prepared.call(&().into_c_stack_list()).unwrap().into_vec();
21//! assert_eq!(pairs.into_iter().next().unwrap().into_rust_tuple(), (7, true));
22//! ```
23//!
24//! Repeated prepared calls do not repeat recursive type checks. Arbitrary
25//! erased host imports remain checked external boundaries. Cloning borrowed
26//! argument reads, comparing values, and formatting values require their own
27//! capabilities; merely moving a value into a container does not. Source
28//! `Fn((A,), R)` names one parameter and `Fn((), R)` names none. The typed
29//! Rust carrier is [`cel_runtime::Callable`], witnessed by [`cel_runtime::FnOf`].
30//! `cel-rs-macros` validates source; no native macro backend is implemented.
31//!
32//! # Error Handling
33//!
34//! Parse errors are returned as [`ParseError`], which carries a `proc_macro2::Span` for precise diagnostics.
35//! Convert to [`CELError`] (via `From`) when the error must be stored or sent across thread boundaries.
36//! All errors result from malformed input (syntax errors, type mismatches, undefined identifiers).
37//!
38//! # Grammar
39//!
40//! For a prose reference manual with the same grammar, see the standalone
41//! [CEL book](../cel-book/index.html) and its
42//! [reference chapter](../cel-book/reference.html).
43//!
44//! ```text
45//! expression = closure_expression | range_expression.
46//! range_expression = ".." [ or_expression ]
47//!                  | "..=" or_expression
48//!                  | or_expression [ ".." [ or_expression ] | "..=" or_expression ].
49//! or_expression = and_expression { "||" and_expression }.
50//! and_expression = comparison_expression { "&&" comparison_expression }.
51//! comparison_expression = bitwise_or_expression [ comparison_operator bitwise_or_expression ].
52//! bitwise_or_expression = bitwise_xor_expression { "|" bitwise_xor_expression }.
53//! bitwise_xor_expression = bitwise_and_expression { "^" bitwise_and_expression }.
54//! bitwise_and_expression = bitwise_shift_expression { "&" bitwise_shift_expression }.
55//! bitwise_shift_expression = additive_expression { ("<<" | ">>") additive_expression }.
56//! additive_expression = multiplicative_expression { ("+" | "-") multiplicative_expression }.
57//! multiplicative_expression = cast_expression { ("*" | "/" | "%") cast_expression }.
58//! cast_expression = unary_expression { "as" identifier }.
59//! unary_expression = ("-" | "!") unary_expression | postfix_expression.
60//! postfix_expression = primary_expression { postfix_suffix }.
61//! postfix_suffix = "(" [ parameter_list ] ")" | "." (unsuffixed_integer | identifier).
62//! comparison_operator = "==" | "!=" | "<" | ">" | "<=" | ">=".
63//! primary_expression = literal | value_identifier [ record_fields ] | tuple_or_group
64//!                    | array_expression | if_expression.
65//! record_fields = "{" [ identifier ":" expression { "," identifier ":" expression } ] "}".
66//! tuple_or_group = "(" [ expression ["," [ expression { "," expression } ]] ] ")".
67//! array_expression = "[" [ expression { "," expression } ] "]" [ ":" type_expression ].
68//! if_expression = "if" expression "{" expression "}" [ "else" ( "{" expression "}" | if_expression ) ].
69//! closure_expression = ("||" | "|" closure_param { "," closure_param } "|") expression.
70//! closure_param = identifier ":" type_expression.
71//! parameter_list = expression { "," expression }.
72//!
73//! type_expression = identifier [ "(" [ type_expression { "," type_expression } ] ")" ]
74//!                  | "[" type_expression "]"
75//!                  | "(" [ type_expression ["," [ type_expression { "," type_expression } ]] ] ")".
76//!
77//! literal_pattern = ["-"] literal.
78//! ```
79//!
80//! The productions encode operator precedence and left associativity; comparisons and ranges
81//! do not chain. A closure is a complete expression, not a primary operand. Its body consumes
82//! the remaining expression in its current delimiter boundary. Group a closure before applying
83//! an operator or postfix suffix to it: `(|x: i32| x + 1)(2)`. Arguments and collection elements
84//! are complete expressions and accept bare closures. Postfix calls directly following a named
85//! member dispatch that member; grouping the member first calls its result instead.
86//!
87//! Expand these productions in ordinary and header modes. Header mode omits `record_fields`
88//! from its primary production, so its following `{` belongs to the host body. Groups, argument
89//! lists, arrays, record field values and branch bodies enter ordinary mode; closure bodies
90//! inherit their enclosing mode. A condition enters header mode. The first token distinguishes
91//! closures (`|` or `||`) from range expressions. Each precedence level selects its continuation
92//! from the current token and returns at a lower-precedence operator or expression boundary.
93//! Boundaries include closing delimiters, commas, body braces and end of input.
94//! `value_identifier` excludes the five reserved words.
95//!
96//! Registered records use a constructor namespace independent of value bindings and evaluate
97//! fields once in written order. All schema fields are required exactly once with exact types.
98//! An `if` condition (or an embedded host header) reserves its body brace: write
99//! `if (Point { x: 0.0 }).x == 0.0 { 1 } else { 2 }` to explicitly allow a record there.
100//! Delimited argument lists, arrays, field values, and branch bodies parse ordinarily.
101//!
102//! `literal_pattern` is a separate entry point, not reachable from `expression` — it exists for
103//! grammars that embed CEL literals in pattern position (e.g. adam-lang's `conditional_branch`)
104//! and need Rust's own `LiteralPattern` rule: a bare literal, or one directly negated by a
105//! leading `-` (no `!`, no chained `--`, no arbitrary unary/postfix operand) — see
106//! <https://doc.rust-lang.org/reference/patterns.html#literal-patterns>.
107//!
108//! `type_expression` is the single, reusable recursive type grammar shared by array type
109//! ascriptions (`[1, 2]: [i32]`), closure parameters (`|x: RangeInclusive(f64)| ...`), and
110//! adam-lang cell/source/out type annotations (`cell x: (i32, f64);`). A bare identifier
111//! resolves through the configured [`TypeResolver`] (array/closure contexts) or a host
112//! `TypeRegistry` (adam-lang cell contexts); `Name(args)` names a parameterized/generic type
113//! (e.g. `RangeInclusive(f64)`, the type produced by CEL's own `..=` range operator) resolved
114//! against the same [`TypeResolver`]; `[T]` composes nested array types (`[i32]`, `[[i32]]`);
115//! and parenthesized tuple syntax names a (possibly nested) tuple type. Every context that
116//! accepts `type_expression` documents its own narrower support boundary where one exists.
117//! CEL types recursively compose registered cloneable leaves, tuples, arrays, and callables:
118//! arrays can contain tuples or callables, and tuples and callable signatures can contain
119//! further compound types. `Fn((A, B), R)` names a callable with two parameters and result
120//! `R`; use `Fn((A,), R)` for one parameter and `Fn((), R)` for none.
121//! Adam-lang shares this recursive resolution for persistent cells, including registered
122//! generic leaves. Adam requires cloning and value equality before storing a cell:
123//! callable values remain valid CEL values, but a callable without equality cannot become
124//! an Adam cell. Compound host access uses explicit canonical representation witnesses;
125//! registered primitive and foreign leaf cells retain their concrete Rust access APIs.
126//!
127//! # Examples
128//!
129//! ```rust
130//! use cel_parser::{CELParser, OpLookup};
131//!
132//! let mut segment = CELParser::new(OpLookup::new()).parse_str("10u32 + 20u32 * 5u32").unwrap();
133//! let result = segment.call0::<u32>();
134//! assert!(result.is_ok());
135//! assert_eq!(result.unwrap(), 110); // 10 + 20 * 5 = 10 + 100
136//! ```
137//!
138//! ## Basic Usage
139//!
140//! ```rust
141//! use cel_parser::CELParser;
142//! use cel_parser::OpLookup;
143//! use proc_macro2::TokenStream;
144//! use std::str::FromStr;
145//!
146//! let input = TokenStream::from_str("10").unwrap();
147//! let mut parser = CELParser::new(OpLookup::new());
148//! let result = parser.parse_tokens(input.into_iter());
149//! assert!(result.is_ok());
150//! ```
151//!
152//! ## Receiver Operations
153//!
154//! `receiver.name` and `receiver.name()` both invoke a unary named operation directly.
155//! `receiver.name(arg1, arg2)` passes the receiver first, then the explicit arguments;
156//! execution evaluates each operand once in source order. Value bindings named `name` do
157//! not hide dot operations. Grouping changes call behavior: `(receiver.name)()` calls the
158//! operation's result through the ordinary callable lookup instead.
159//!
160//! Host libraries supply these operations through [`OpLookup`]; the parser imposes no
161//! domain-specific receiver types. AST parsing preserves the spelling and the member's name
162//! span without resolving the operation:
163//!
164//! ```rust
165//! use cel_parser::{AstContext, Expr, OpLookup, Parser, format_expr};
166//! let source = "a.center().x";
167//! let expr = Parser::<AstContext>::new(OpLookup::new()).parse_str_ast(source).unwrap();
168//! assert!(matches!(&expr, Expr::Member { args: None, .. }));
169//! assert_eq!(format_expr(&expr, source, 0), source);
170//! ```
171//!
172//! ## Metadata-aware static checking
173//!
174//! [`ty::check_expr_with_type_resolver`] takes the host's [`OpLookup`] as its fourth argument.
175//! It checks record fields against schemas and receiver operations against signatures without
176//! invoking emitters. Known custom leaves retain [`Ty::Registered`] descriptors; unrelated
177//! known types never unify. Unknown constructors are diagnostic, while genuinely unresolved
178//! values and custom calls without metadata remain best-effort. Every child is still checked,
179//! and constructor/field/member labels never enter the value resolver. No integer-to-float
180//! coercion occurs. [`ty::check_expr`] remains the primitive convenience entry with an empty lookup.
181//!
182//! ```rust
183//! use cel_parser::{AstContext, OpLookup, OperationSignature, Parser, ResolvedLeafType, Ty};
184//! use cel_runtime::ValueType;
185//!
186//! let scalar = ResolvedLeafType::new("f64", ValueType::leaf::<f64>());
187//! let mut lookup = OpLookup::new();
188//! lookup.register_operation(OperationSignature {
189//!     name: "twice",
190//!     operands: vec![scalar.clone().into()],
191//!     result: scalar.into(),
192//!     emit: Box::new(|s, _| s.op1(|value: f64| value * 2.0)),
193//! }).unwrap();
194//! let expr = Parser::<AstContext>::new(OpLookup::new())
195//!     .parse_str_ast("3.0.twice() + true").unwrap();
196//! let (_, diagnostics) = cel_parser::ty::check_expr_with_type_resolver(
197//!     &expr, &|_| Ty::Any, cel_parser::builtin_type_resolver().as_ref(), &lookup,
198//! );
199//! assert_eq!(diagnostics.len(), 1);
200//! ```
201//!
202//! ## Array Literals
203//!
204//! A `[...]` literal constructs one canonical [`cel_runtime::Array`] owning every element.
205//! Checked host calls return [`cel_runtime::OwnedValue`]; prepared calls use exact
206//! recursive witnesses directly.
207//! Unannotated non-empty arrays keep their existing inference, while postfix annotations let
208//! callers state complete recursive array, tuple, callable, and custom-leaf shapes
209//! explicitly. Elements must share one complete recursive runtime type — a heterogeneous literal
210//! such as `[1i32, 2.0f64]`, an unannotated empty literal `[]`, or a trailing comma is a parse
211//! error — and the evaluated array converts to the corresponding `Vec<T>` without moving or
212//! reallocating its elements:
213//!
214//! ```rust
215//! use cel_parser::{CELParser, OpLookup};
216//!
217//!
218//! let mut segment = CELParser::new(OpLookup::new())
219//!     .parse_str("[0, 1, 2]: [i32]")
220//!     .unwrap();
221//! let array = segment.call0::<cel_runtime::OwnedValue>().unwrap().try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>().unwrap();
222//! assert_eq!(array.into_vec(), vec![0, 1, 2]);
223//!
224//! let mut segment = CELParser::new(OpLookup::new()).parse_str("[]: [i32]").unwrap();
225//! let array = segment.call0::<cel_runtime::OwnedValue>().unwrap().try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>().unwrap();
226//! assert!(array.into_vec().is_empty());
227//! ```
228//!
229//! Tuple arrays and callable arrays use canonical witnesses rather than external erased wrappers.
230//! Singleton tuple types retain their trailing comma. `Fn((A, B), R)` has two parameters,
231//! `Fn((A,), R)` has one, and `Fn((), R)` has none:
232//!
233//! ```
234//! use cel_parser::{CELParser, OpLookup};
235//! use cel_runtime::{ArrayOf, Cons, FnOf, IntoCStackList, Leaf, Nil};
236//! type F = FnOf<Cons<Leaf<i32>, Nil>, ArrayOf<Leaf<i32>>>;
237//! let body = CELParser::new(OpLookup::new())
238//!     .parse_str("[]: [Fn((i32,), [i32])]").unwrap();
239//! let values = body.prepare::<Nil, ArrayOf<F>>().unwrap()
240//!     .call(&().into_c_stack_list()).unwrap();
241//! assert!(values.is_empty());
242//! ```
243//!
244//! Hosts may also resolve custom registry-backed leaf names by constructing the parser with
245//! [`CELParser::with_type_resolver`]:
246//!
247//! ```rust
248//! use cel_parser::{CELParser, OpLookup, ResolvedLeafType};
249//! use cel_runtime::ValueType;
250//!
251//! #[derive(Clone)]
252//! struct Custom;
253//!
254//! let resolver = [(
255//!     "Custom",
256//!     ResolvedLeafType::new("Custom", ValueType::leaf::<Custom>()),
257//! )];
258//! let mut segment = CELParser::with_type_resolver(OpLookup::new(), resolver)
259//!     .parse_str("[]: [Custom]")
260//!     .unwrap();
261//! let array = segment.call0::<cel_runtime::OwnedValue>().unwrap().try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<Custom>>>().unwrap();
262//! assert!(array.into_vec().is_empty());
263//! ```
264//!
265//! Nested literals accept recursive array annotations; each inner value uses the
266//! same canonical array ABI with a complete element witness:
267//!
268//! ```rust
269//! use cel_parser::{CELParser, OpLookup};
270//!
271//!
272//! let mut segment = CELParser::new(OpLookup::new())
273//!     .parse_str("[[0], [1]]: [[i32]]")
274//!     .unwrap();
275//! let array = segment.call0::<cel_runtime::OwnedValue>().unwrap().try_into_typed::<cel_runtime::ArrayOf<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>>().unwrap();
276//! let rows = array.into_vec();
277//! assert_eq!(rows.len(), 2);
278//! assert_eq!(rows[0].as_slice(), &[0]);
279//! ```
280//!
281//! Exact annotation failures stay precise:
282//!
283//! ```rust
284//! use cel_parser::{CELParser, OpLookup};
285//!
286//! let err = match CELParser::new(OpLookup::new()).parse_str("[0, 1]: [f64]") {
287//!     Ok(_) => panic!("the annotation should reject i32 elements"),
288//!     Err(err) => err,
289//! };
290//! assert_eq!(
291//!     err.message(),
292//!     "array element 0 has type i32, expected f64"
293//! );
294//!
295//! let err = match CELParser::new(OpLookup::new()).parse_str("[0]: [Nope]") {
296//!     Ok(_) => panic!("the unknown leaf name should be rejected"),
297//!     Err(err) => err,
298//! };
299//! assert_eq!(err.message(), "unknown type `Nope`");
300//! ```
301//!
302//! ### Known limitations
303//!
304//! - An unannotated empty literal (`[]`) is a parse error: with no element there is nothing to
305//!   infer the array's element type from (<https://github.com/stlab/cel-rs/issues/212>).
306//! - A type-mismatch diagnostic from the compiling path ([`CELParser`], which type-checks
307//!   elements against their compiled runtime types) spans the whole `[...]` literal and names the
308//!   offending element only in its message text (`array element 1 has type ...`). The static
309//!   [`ty::check_expr`] checker, which runs over an [`AstContext`] tree instead, already reports
310//!   the offending element's own span (<https://github.com/stlab/cel-rs/issues/215>).
311//!
312//! ## Error Formatting
313//!
314//! ```rust
315//! use annotate_snippets::Renderer;
316//! use cel_parser::CELParser;
317//! use cel_parser::OpLookup;
318//! use proc_macro2::TokenStream;
319//! use std::str::FromStr;
320//!
321//! let line = line!() + 1;
322//! let source = r#"
323//!   10 20
324//! "#; // Invalid: missing operator
325//! let input = TokenStream::from_str(source).unwrap();
326//! let mut parser = CELParser::new(OpLookup::new());
327//!
328//! if let Err(e) = parser.parse_tokens(input.into_iter()) {
329//!     // Format error starting at line 1
330//!     println!("{}", e.format_rustc_style(source, file!(), line, &Renderer::plain()));
331//! }
332//! ```
333
334pub mod ast;
335mod compile;
336pub use compile::compile_expr;
337mod error;
338mod fmt;
339pub mod lex_lexer;
340pub mod op_table;
341pub mod registered_ops;
342pub use registered_ops::{
343    OperationEmitter, OperationSignature, RecordEmitter, RecordField, RecordSchema,
344};
345pub mod parser_context;
346pub mod trivia;
347pub mod ty;
348pub mod type_expr;
349
350pub use ast::{AstContext, ClosureParam, Expr, ExprSpan, Literal, LogicalOp, RecordInit};
351pub use error::{
352    CELError, FormatRustcStyle, ParseError, SourceSpan, SpanContext, SpanLabel, format_multi_span,
353};
354pub use fmt::{
355    format_expr, format_expr_header, format_expr_header_with_layout, format_expr_restricted,
356    format_expr_with_layout,
357};
358pub use op_table::{OpLookup, OperandTypes, builtin_operand_types};
359pub use parser_context::{DynSegmentContext, ParserContext};
360pub use proc_macro2::LineColumn;
361pub use trivia::Comment;
362pub use ty::Ty;
363pub use type_expr::{
364    ResolvedArrayType, ResolvedLeafType, ResolvedType, TypeExpr, TypeResolver,
365    builtin_type_resolver, resolve_builtin_named_type,
366};
367
368use lex_lexer::{LexLexer, Literal as CelLiteral, Token, TokenStreamIter};
369
370use cel_runtime::DynSegment;
371use proc_macro2::{Delimiter, Span, TokenStream};
372#[cfg(test)]
373use std::any::TypeId;
374use std::collections::HashMap;
375use std::iter::Peekable;
376use std::str::FromStr;
377
378/// Parser result type.
379///
380/// # Examples
381/// See [`Parser::parse_str`] for a fallible parsing result.
382pub type Result<T> = std::result::Result<T, ParseError>;
383
384/// Pushes a literal value from `token` onto `output`.
385///
386/// # Errors
387///
388/// Returns `Err` if the literal type is unsupported or if a suffixed numeric
389/// literal cannot be parsed.
390fn push_literal_token<C: ParserContext>(output: &mut C, lit: CelLiteral) -> Result<()> {
391    match lit {
392        CelLiteral::Int(integer) => {
393            let span = integer.span();
394            match integer.suffix() {
395                "" | "i32" => output.push_literal(
396                    integer.base10_parse::<i32>().map_err(|e| {
397                        ParseError::new(
398                            format!("invalid i32 literal `{integer}`: {e}"),
399                            integer.span(),
400                        )
401                    })?,
402                    span,
403                ),
404                "u8" => output.push_literal(
405                    integer.base10_parse::<u8>().map_err(|e| {
406                        ParseError::new(
407                            format!("invalid u8 literal `{integer}`: {e}"),
408                            integer.span(),
409                        )
410                    })?,
411                    span,
412                ),
413                "u16" => output.push_literal(
414                    integer.base10_parse::<u16>().map_err(|e| {
415                        ParseError::new(
416                            format!("invalid u16 literal `{integer}`: {e}"),
417                            integer.span(),
418                        )
419                    })?,
420                    span,
421                ),
422                "u32" => output.push_literal(
423                    integer.base10_parse::<u32>().map_err(|e| {
424                        ParseError::new(
425                            format!("invalid u32 literal `{integer}`: {e}"),
426                            integer.span(),
427                        )
428                    })?,
429                    span,
430                ),
431                "u64" => output.push_literal(
432                    integer.base10_parse::<u64>().map_err(|e| {
433                        ParseError::new(
434                            format!("invalid u64 literal `{integer}`: {e}"),
435                            integer.span(),
436                        )
437                    })?,
438                    span,
439                ),
440                "u128" => output.push_literal(
441                    integer.base10_parse::<u128>().map_err(|e| {
442                        ParseError::new(
443                            format!("invalid u128 literal `{integer}`: {e}"),
444                            integer.span(),
445                        )
446                    })?,
447                    span,
448                ),
449                "usize" => output.push_literal(
450                    integer.base10_parse::<usize>().map_err(|e| {
451                        ParseError::new(
452                            format!("invalid usize literal `{integer}`: {e}"),
453                            integer.span(),
454                        )
455                    })?,
456                    span,
457                ),
458                "i8" => output.push_literal(
459                    integer.base10_parse::<i8>().map_err(|e| {
460                        ParseError::new(
461                            format!("invalid i8 literal `{integer}`: {e}"),
462                            integer.span(),
463                        )
464                    })?,
465                    span,
466                ),
467                "i16" => output.push_literal(
468                    integer.base10_parse::<i16>().map_err(|e| {
469                        ParseError::new(
470                            format!("invalid i16 literal `{integer}`: {e}"),
471                            integer.span(),
472                        )
473                    })?,
474                    span,
475                ),
476                "i64" => output.push_literal(
477                    integer.base10_parse::<i64>().map_err(|e| {
478                        ParseError::new(
479                            format!("invalid i64 literal `{integer}`: {e}"),
480                            integer.span(),
481                        )
482                    })?,
483                    span,
484                ),
485                "i128" => output.push_literal(
486                    integer.base10_parse::<i128>().map_err(|e| {
487                        ParseError::new(
488                            format!("invalid i128 literal `{integer}`: {e}"),
489                            integer.span(),
490                        )
491                    })?,
492                    span,
493                ),
494                "isize" => output.push_literal(
495                    integer.base10_parse::<isize>().map_err(|e| {
496                        ParseError::new(
497                            format!("invalid isize literal `{integer}`: {e}"),
498                            integer.span(),
499                        )
500                    })?,
501                    span,
502                ),
503                suffix => {
504                    return Err(ParseError::new(
505                        format!("invalid integer literal suffix: `{suffix}`"),
506                        integer.span(),
507                    ));
508                }
509            };
510        }
511        CelLiteral::Float(float) => {
512            let span = float.span();
513            match float.suffix() {
514                "" | "f64" => output.push_literal(
515                    float.base10_parse::<f64>().map_err(|e| {
516                        ParseError::new(format!("invalid f64 literal `{float}`: {e}"), float.span())
517                    })?,
518                    span,
519                ),
520                "f32" => output.push_literal(
521                    float.base10_parse::<f32>().map_err(|e| {
522                        ParseError::new(format!("invalid f32 literal `{float}`: {e}"), float.span())
523                    })?,
524                    span,
525                ),
526                suffix => {
527                    return Err(ParseError::new(
528                        format!("invalid float literal suffix: `{suffix}`"),
529                        float.span(),
530                    ));
531                }
532            };
533        }
534        CelLiteral::Str(string) => {
535            let span = string.span();
536            output.push_literal(string.value(), span);
537        }
538        CelLiteral::Bool(lit_bool) => {
539            let span = lit_bool.span();
540            output.push_literal(lit_bool.value, span);
541        }
542        CelLiteral::Char(ch) => {
543            let span = ch.span();
544            output.push_literal(ch.value(), span);
545        }
546        CelLiteral::Byte(byte) => {
547            let span = byte.span();
548            output.push_literal(byte.value(), span);
549        }
550        CelLiteral::ByteStr(byte_str) => {
551            let span = byte_str.span();
552            output.push_literal(byte_str.value(), span);
553        }
554        CelLiteral::CStr(c_str) => {
555            let span = c_str.span();
556            output.push_literal(c_str.value(), span);
557        }
558        other => {
559            return Err(ParseError::new(
560                format!("unsupported literal: {other:?}"),
561                other.span(),
562            ));
563        }
564    }
565    Ok(())
566}
567
568/// Validates that `lit` is a literal `cel_parser` can represent as a value: a recognized numeric
569/// suffix, and (for a suffixed or unsuffixed integer/float) a value in range for its width.
570/// Reuses the same literal-compiling checks the grammar itself applies to a literal token,
571/// against a throwaway context, for a caller (e.g. adam-lang's CST parser, which records a
572/// literal token without compiling it into a value) that needs to reject the same literals the
573/// runtime parser would, without needing a [`ParserContext`] of its own to push into.
574///
575/// # Errors
576///
577/// Returns `Err` for an unrecognized numeric suffix, or a numeric value out of range for its
578/// width.
579///
580/// - Complexity: Includes literal-token cloning and value parsing for the token's text.
581///
582/// # Examples
583/// ```
584/// let literal: syn::Lit = syn::parse_str("42i32").unwrap();
585/// cel_parser::validate_literal(&literal).unwrap();
586/// ```
587pub fn validate_literal(lit: &CelLiteral) -> Result<()> {
588    push_literal_token(&mut DynSegmentContext::new_context(), lit.clone())
589}
590
591/// Converts one syntax literal into retained expression syntax without executing code.
592///
593/// Preserves the token's source span and concrete numeric width using the same validation
594/// as expression parsing.
595///
596/// # Errors
597/// Rejects unsupported literal kinds, invalid suffixes, and out-of-range values.
598/// - Complexity: O(n) literal bytes.
599///
600/// # Examples
601/// ```
602/// let token: syn::Lit = syn::parse_str("42i32")?;
603/// assert!(matches!(cel_parser::literal_expr(&token)?,
604///     cel_parser::Expr::Literal { value: cel_parser::Literal::I32(42), .. }));
605/// # Ok::<(), Box<dyn std::error::Error>>(())
606/// ```
607pub fn literal_expr(lit: &CelLiteral) -> Result<Expr> {
608    let mut context = AstContext::new_context();
609    push_literal_token(&mut context, lit.clone())?;
610    Ok(context.into_expr())
611}
612
613/// Resolves a closure annotation through the configured registry without erasing its shape.
614///
615/// # Errors
616/// Returns annotation resolution errors or unrepresentable layout errors.
617/// - Complexity: O(number of type nodes).
618fn closure_param_type_from_type_expr(
619    type_expr: &TypeExpr,
620    resolver: &dyn TypeResolver,
621) -> Result<cel_runtime::ValueType> {
622    type_expr
623        .resolve(resolver)?
624        .into_value_type()
625        .map_err(|error| {
626            let span = type_expr.span();
627            ParseError::new_range(error.to_string(), span.start, span.end)
628        })
629}
630
631/// A recursive descent parser for expressions, generic over the [`ParserContext`] it emits
632/// into.
633///
634/// [`CELParser`] is a type alias for `Parser<DynSegmentContext>` and remains the concrete type
635/// most callers use; it behaves identically to how `CELParser` always has, before this type
636/// became generic.
637///
638/// # Examples
639///
640/// ## Basic Usage
641///
642/// ```rust
643/// use cel_parser::OpLookup;
644/// use cel_parser::CELParser;
645/// use proc_macro2::TokenStream;
646/// use std::str::FromStr;
647///
648/// let input = TokenStream::from_str("10").unwrap();
649/// let mut parser = CELParser::new(OpLookup::new());
650/// let result = parser.parse_tokens(input.into_iter());
651/// assert!(result.is_ok());
652/// ```
653///
654/// ## Error Formatting
655///
656/// ```rust
657/// use annotate_snippets::Renderer;
658/// use cel_parser::OpLookup;
659/// use cel_parser::CELParser;
660/// use proc_macro2::TokenStream;
661/// use std::str::FromStr;
662///
663/// let line = line!() + 1;
664/// let source = r#"
665///   10 + 20 30
666/// "#; // Invalid: missing operator
667/// let input = TokenStream::from_str(source).unwrap();
668/// let mut parser = CELParser::new(OpLookup::new());
669///
670/// if let Err(e) = parser.parse_tokens(input.into_iter()) {
671///     // Format error starting at line 1
672///     println!("{}", e.format_rustc_style(source, file!(), line, &Renderer::plain()));
673/// }
674/// ```
675pub struct Parser<C: ParserContext> {
676    tokens: Option<Peekable<LexLexer>>,
677    context: C,
678    op_lookup: OpLookup,
679    type_resolver: Box<dyn TypeResolver>,
680    last_span: Span,
681    /// Net count of `Delimiter::Brace`/`Delimiter::Bracket`/`Delimiter::Parenthesis` tokens
682    /// consumed since the last [`set_tokens`](Self::set_tokens)/
683    /// [`set_lex_tokens`](Self::set_lex_tokens) call that remain unmatched: incremented for an
684    /// `OpenDelim`, decremented for a `CloseDelim`. See
685    /// [`unbalanced_delimiter_count`](Self::unbalanced_delimiter_count).
686    unbalanced_delimiters: i32,
687    restriction: ExpressionRestriction,
688}
689
690/// Selects whether a header reserves an identifier's following brace for its body.
691///
692/// Explicit delimiters establish ordinary expression boundaries; unary, binary, and closure
693/// expressions inherit the surrounding restriction.
694///
695/// # Examples
696/// ```
697/// use cel_parser::{CELParser, ExpressionRestriction, OpLookup};
698/// let mut parser = CELParser::new(OpLookup::new());
699/// parser.set_tokens("true {}".parse::<proc_macro2::TokenStream>().unwrap().into_iter());
700/// parser.parse_expression_restricted(ExpressionRestriction::NoStructLiteral).unwrap();
701/// ```
702#[derive(Clone, Copy, Debug, PartialEq, Eq)]
703pub enum ExpressionRestriction {
704    /// Allows ordinary record literals.
705    None,
706    /// Leaves an unparenthesized identifier's following brace to the enclosing grammar.
707    NoStructLiteral,
708}
709
710/// A recursive descent parser that executes directly into a [`DynSegment`].
711///
712/// This is the parser every existing caller uses; behavior is unchanged from before [`Parser`]
713/// became generic over [`ParserContext`].
714///
715/// # Examples
716/// See [`Parser::parse_str`] for parsing and execution.
717pub type CELParser = Parser<DynSegmentContext>;
718
719impl<C: ParserContext> Parser<C> {
720    /// Creates a new CEL parser with the given operation lookup.
721    ///
722    /// No tokens are set at construction; use [`set_tokens`](Self::set_tokens),
723    /// [`parse_tokens_ctx`](Self::parse_tokens_ctx), or [`parse_str_ctx`](Self::parse_str_ctx)
724    /// to parse.
725    ///
726    /// # Arguments
727    ///
728    /// * `op_lookup` - Operation lookup for resolving operators and identifiers
729    ///
730    /// # Examples
731    /// See [`Self::parse_str_ctx`] for constructing and using a parser.
732    pub fn new(op_lookup: OpLookup) -> Self {
733        Self::with_boxed_type_resolver(op_lookup, type_expr::builtin_type_resolver())
734    }
735
736    /// Creates a new CEL parser with the given operation lookup and leaf type resolver.
737    ///
738    /// Existing callers that need only the built-in CEL scalar types should continue to use
739    /// [`new`](Self::new); this constructor is for hosts that want additional named CEL types
740    /// without coupling `cel-parser` to their own registry implementation.
741    ///
742    /// # Examples
743    ///
744    /// ```rust
745    /// use cel_parser::{CELParser, OpLookup, ResolvedLeafType};
746    /// use cel_runtime::ValueType;
747    ///
748    /// #[derive(Clone)]
749    /// struct Celsius(i32);
750    ///
751    /// let resolver = [(
752    ///     "Celsius",
753    ///     ResolvedLeafType::new("Celsius", ValueType::leaf::<Celsius>()),
754    /// )];
755    /// let mut segment = CELParser::with_type_resolver(OpLookup::new(), resolver)
756    ///     .parse_str("[]: [Celsius]")
757    ///     .unwrap();
758    /// let array = segment.call0::<cel_runtime::OwnedValue>().unwrap().try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<Celsius>>>().unwrap();
759    /// assert!(array.into_vec().is_empty());
760    /// ```
761    pub fn with_type_resolver<R>(op_lookup: OpLookup, type_resolver: R) -> Self
762    where
763        R: TypeResolver + 'static,
764    {
765        Self::with_boxed_type_resolver(op_lookup, Box::new(type_resolver))
766    }
767
768    /// Creates a parser that uniquely owns its compile-time type resolver.
769    fn with_boxed_type_resolver(op_lookup: OpLookup, type_resolver: Box<dyn TypeResolver>) -> Self {
770        Parser {
771            tokens: None,
772            context: C::new_context(),
773            op_lookup,
774            type_resolver,
775            last_span: Span::call_site(),
776            unbalanced_delimiters: 0,
777            restriction: ExpressionRestriction::None,
778        }
779    }
780
781    /// Replaces this parser's named-type resolver.
782    ///
783    /// Every subsequent parse resolves annotation type names through `type_resolver` instead of
784    /// the one this parser was built with.
785    ///
786    /// # Examples
787    ///
788    /// ```rust
789    /// use cel_parser::{CELParser, OpLookup, ResolvedLeafType};
790    /// use cel_runtime::ValueType;
791    ///
792    /// #[derive(Clone)]
793    /// struct Celsius(i32);
794    ///
795    /// let mut parser = CELParser::new(OpLookup::new());
796    /// assert!(parser.parse_str("[]: [Celsius]").is_err());
797    ///
798    /// parser.set_type_resolver([(
799    ///     "Celsius",
800    ///     ResolvedLeafType::new("Celsius", ValueType::leaf::<Celsius>()),
801    /// )]);
802    /// assert!(parser.parse_str("[]: [Celsius]").is_ok());
803    /// ```
804    pub fn set_type_resolver<R>(&mut self, type_resolver: R)
805    where
806        R: TypeResolver + 'static,
807    {
808        self.type_resolver = Box::new(type_resolver);
809    }
810
811    /// Resolves `expr` through this parser's configured leaf type resolver.
812    ///
813    /// # Errors
814    ///
815    /// Returns `Err` if some named leaf in `expr` is not recognized.
816    ///
817    /// # Examples
818    /// See [`Self::parse_type_expr_str`] for resolving parsed type syntax.
819    pub fn resolve_type_expr(&self, expr: &TypeExpr) -> Result<ResolvedType> {
820        expr.resolve(self.type_resolver.as_ref())
821    }
822
823    /// Sets the token stream for parsing, resetting internal state.
824    ///
825    /// Call before [`is_expression`](Self::is_expression) or use
826    /// [`parse_tokens_ctx`](Self::parse_tokens_ctx) which sets tokens and parses in one step.
827    ///
828    /// # Examples
829    /// See [`Self::parse_tokens_ctx`] for setting and consuming a token cursor.
830    pub fn set_tokens(&mut self, tokens: TokenStreamIter) {
831        self.tokens = Some(LexLexer::new(tokens).peekable());
832        self.context = C::new_context();
833        self.last_span = Span::call_site();
834        self.unbalanced_delimiters = 0;
835    }
836
837    /// Sets the token stream from an existing [`LexLexer`] iterator for inline expression parsing.
838    ///
839    /// Resets the context. Use together with [`parse_expression_ctx`](Self::parse_expression_ctx)
840    /// and [`take_lex_tokens`](Self::take_lex_tokens) to share a token stream between adam-lang and
841    /// [`CELParser`].
842    ///
843    /// # Examples
844    /// See [`Self::parse_tokens_ctx`] for installing an already lexed cursor.
845    pub fn set_lex_tokens(&mut self, tokens: std::iter::Peekable<lex_lexer::LexLexer>) {
846        self.tokens = Some(tokens);
847        self.context = C::new_context();
848        self.last_span = Span::call_site();
849        self.unbalanced_delimiters = 0;
850    }
851
852    /// Returns the net number of opening delimiters this parser has consumed since the last
853    /// [`set_tokens`](Self::set_tokens)/[`set_lex_tokens`](Self::set_lex_tokens) call, that
854    /// remain unmatched by a corresponding closing delimiter.
855    ///
856    /// Always `0` after a successful top-level parse: every delimiter this grammar opens (an
857    /// `if`-expression's branch braces, a tuple/group literal's or call's parens), it also
858    /// closes. Positive after a parse that returned `Err` from partway through a production that
859    /// had already consumed one or more opening delimiters before failing — e.g. an
860    /// `if`-expression whose then-branch fails to parse consumes the branch's opening `{` before
861    /// erroring out, never reaching the matching `}`.
862    ///
863    /// An embedding caller that shares its own token stream with this parser (via
864    /// [`set_lex_tokens`](Self::set_lex_tokens)/[`take_lex_tokens`](Self::take_lex_tokens)), and
865    /// tracks its own nesting depth over that same stream, cannot otherwise tell a delimiter this
866    /// parser left dangling apart from one still genuinely open in the caller's own grammar,
867    /// since both use the same `Delimiter` kinds. Reading this count immediately after reclaiming
868    /// the stream — regardless of whether the parse succeeded or failed — and folding it into the
869    /// caller's own depth counter keeps that counter consistent with the physical nesting left
870    /// behind in the stream.
871    ///
872    /// # Examples
873    /// See [`Self::parse_tokens_ctx`] for the balanced-input case.
874    pub fn unbalanced_delimiter_count(&self) -> i32 {
875        self.unbalanced_delimiters
876    }
877
878    /// Parses one `expression` from the current token stream and returns the built context.
879    ///
880    /// Unlike [`parse_str_ctx`](Self::parse_str_ctx), this method does not require
881    /// end-of-stream, allowing adam-lang to parse an expression embedded within a larger token
882    /// stream.
883    ///
884    /// # Errors
885    ///
886    /// Returns an error if the input does not contain a valid `expression`.
887    ///
888    /// - Complexity: O(n) in the number of tokens in the expression.
889    ///
890    /// # Examples
891    /// See [`Self::parse_tokens_ctx`] for parsing from an installed cursor.
892    pub fn parse_expression_ctx(&mut self) -> Result<C> {
893        self.parse_expression_ctx_restricted(ExpressionRestriction::None)
894    }
895
896    /// Parses one expression under `restriction`, restoring the previous mode on every result.
897    ///
898    /// Does not require end-of-stream, so the enclosing grammar retains its body brace.
899    ///
900    /// - Complexity: O(n) in the expression's token count.
901    ///
902    /// # Errors
903    /// Returns an error for a malformed expression or a context's semantic rejection.
904    ///
905    /// # Examples
906    /// See [`ExpressionRestriction`].
907    pub fn parse_expression_ctx_restricted(
908        &mut self,
909        restriction: ExpressionRestriction,
910    ) -> Result<C> {
911        if !self.with_restriction(restriction, |p| p.is_expression())? {
912            return Err(self.error_at("expression expected"));
913        }
914        Ok(std::mem::replace(&mut self.context, C::new_context()))
915    }
916
917    /// Runs a grammar production in a temporary mode, restoring it on success and error.
918    ///
919    /// - Complexity: the invoked production's complexity.
920    fn with_restriction<T>(
921        &mut self,
922        restriction: ExpressionRestriction,
923        parse: impl FnOnce(&mut Self) -> Result<T>,
924    ) -> Result<T> {
925        let previous = std::mem::replace(&mut self.restriction, restriction);
926        let result = parse(self);
927        self.restriction = previous;
928        result
929    }
930
931    /// `ordinary_expression = expression.` Explicit delimiters lift header restrictions.
932    ///
933    /// # Errors
934    /// Returns the nested expression's syntax or context error.
935    /// - Complexity: O(n) in the expression's token count.
936    fn is_ordinary_expression(&mut self) -> Result<bool> {
937        self.with_restriction(ExpressionRestriction::None, |p| p.is_expression())
938    }
939
940    /// Parses one `literal_pattern` from the current token stream and returns the built context.
941    ///
942    /// Like [`parse_expression_ctx`](Self::parse_expression_ctx), this does not require
943    /// end-of-stream, letting a caller (e.g. adam-lang's `conditional_branch`) parse just the
944    /// pattern before continuing with its own grammar.
945    ///
946    /// # Errors
947    ///
948    /// Returns an error if the input does not contain a valid `literal_pattern`.
949    ///
950    /// # Examples
951    /// ```
952    /// use cel_parser::{CELParser, OpLookup};
953    /// use proc_macro2::TokenStream;
954    /// let mut parser = CELParser::new(OpLookup::new());
955    /// parser.set_tokens("7".parse::<TokenStream>().unwrap().into_iter());
956    /// let context = parser.parse_literal_pattern_ctx().unwrap();
957    /// assert_eq!(context.into_inner().call0::<i32>().unwrap(), 7);
958    /// parser.set_tokens("8".parse::<TokenStream>().unwrap().into_iter());
959    /// assert_eq!(parser.parse_literal_pattern().unwrap().call0::<i32>().unwrap(), 8);
960    /// ```
961    pub fn parse_literal_pattern_ctx(&mut self) -> Result<C> {
962        if !self.is_literal_pattern()? {
963            return Err(self.error_at("literal pattern expected"));
964        }
965        Ok(std::mem::replace(&mut self.context, C::new_context()))
966    }
967
968    /// Compiles a fully independent nested context — used for a closure literal's body — by
969    /// swapping `self.context` out for a fresh one, running `f` against it, then swapping the
970    /// original context back in and returning the finished nested one.
971    ///
972    /// Unlike [`parse_expression_ctx`](Self::parse_expression_ctx), this does not reset
973    /// `self.tokens`/`self.op_lookup`/`self.last_span` — it's for compiling a sub-expression in the
974    /// middle of an already-in-progress outer parse, not starting a fresh top-level parse.
975    ///
976    /// # Errors
977    ///
978    /// Returns `Err` if `f` does, or if `f` returns `Ok(false)` (no expression found) — in both
979    /// cases the outer context is still restored before returning.
980    ///
981    /// - Complexity: whatever `f`'s own parse cost is.
982    pub(crate) fn parse_nested_context<F>(&mut self, f: F) -> Result<C>
983    where
984        F: FnOnce(&mut Self) -> Result<bool>,
985    {
986        let outer = std::mem::replace(&mut self.context, C::new_context());
987        let outcome = f(self);
988        let nested = std::mem::replace(&mut self.context, outer);
989        match outcome {
990            Ok(true) => Ok(nested),
991            Ok(false) => Err(self.error_at("expected expression")),
992            Err(e) => Err(e),
993        }
994    }
995
996    /// Returns the remaining token stream after expression parsing.
997    ///
998    /// Call after [`parse_expression_ctx`](Self::parse_expression_ctx) to recover the
999    /// shared [`LexLexer`] for continued adam-lang parsing.
1000    ///
1001    /// # Examples
1002    /// See [`Self::parse_tokens_ctx`] for recovering the remaining cursor.
1003    pub fn take_lex_tokens(&mut self) -> Option<std::iter::Peekable<lex_lexer::LexLexer>> {
1004        self.tokens.take()
1005    }
1006
1007    /// Parses a token stream into a context value.
1008    ///
1009    /// Sets the token source, runs the expression grammar, and returns the context on success.
1010    ///
1011    /// # Errors
1012    ///
1013    /// Returns an error if the input does not contain a valid CEL expression.
1014    ///
1015    /// # Examples
1016    /// ```
1017    /// use cel_parser::{CELParser, OpLookup, lex_lexer::LexLexer};
1018    /// use proc_macro2::TokenStream;
1019    /// let mut parser = CELParser::new(OpLookup::new());
1020    /// parser.set_tokens("42".parse::<TokenStream>().unwrap().into_iter());
1021    /// assert!(parser.is_expression().unwrap());
1022    /// assert_eq!(parser.unbalanced_delimiter_count(), 0);
1023    /// assert!(parser.take_lex_tokens().unwrap().peek().is_none());
1024    /// let tokens = "42".parse::<TokenStream>().unwrap();
1025    /// parser.set_lex_tokens(LexLexer::new(tokens.into_iter()).peekable());
1026    /// let context = parser.parse_expression_ctx().unwrap();
1027    /// assert_eq!(context.into_inner().call0::<i32>().unwrap(), 42);
1028    /// assert!(parser.take_lex_tokens().unwrap().peek().is_none());
1029    /// let tokens = "43".parse::<TokenStream>().unwrap();
1030    /// let context = parser.parse_tokens_ctx(tokens.into_iter()).unwrap();
1031    /// assert_eq!(context.into_inner().call0::<i32>().unwrap(), 43);
1032    /// ```
1033    pub fn parse_tokens_ctx(&mut self, tokens: TokenStreamIter) -> Result<C> {
1034        self.set_tokens(tokens);
1035        if !self.is_expression()? {
1036            return Err(self.error_at("expression expected"));
1037        }
1038        if self.peek_token().is_some() {
1039            return Err(self.error_at("unexpected token"));
1040        }
1041        Ok(std::mem::replace(&mut self.context, C::new_context()))
1042    }
1043
1044    /// Parses a string into a context value.
1045    ///
1046    /// Tokenizes the string then parses; equivalent to
1047    /// `parse_tokens_ctx(TokenStream::from_str(s)?.into_iter())`.
1048    ///
1049    /// # Errors
1050    ///
1051    /// Returns an error on lex failure or if the input does not contain a valid CEL expression.
1052    ///
1053    /// # Examples
1054    /// ```
1055    /// use cel_parser::{CELParser, OpLookup};
1056    /// let context = CELParser::new(OpLookup::new()).parse_str_ctx("40 + 2").unwrap();
1057    /// assert_eq!(context.into_inner().call0::<i32>().unwrap(), 42);
1058    /// ```
1059    pub fn parse_str_ctx(&mut self, s: &str) -> Result<C> {
1060        let input = TokenStream::from_str(s).map_err(|e| ParseError::from_lex_error(s, e))?;
1061        self.parse_tokens_ctx(input.into_iter())
1062    }
1063
1064    /// Parses one complete `type_expr` from the current token stream.
1065    ///
1066    /// Unlike [`parse_type_expr_str`](Self::parse_type_expr_str), this method does not require
1067    /// ownership of the whole source string; it consumes only as many tokens as the type
1068    /// expression itself needs from the current stream.
1069    ///
1070    /// # Errors
1071    ///
1072    /// Returns an error if the current token stream does not begin with a valid type expression.
1073    ///
1074    /// # Examples
1075    /// See [`Self::parse_type_expr_str`] for parsing from strings and installed cursors.
1076    pub fn parse_type_expr(&mut self) -> Result<TypeExpr> {
1077        self.parse_type_expression()
1078    }
1079
1080    /// Parses one complete `type_expr` from `tokens`.
1081    ///
1082    /// # Errors
1083    ///
1084    /// Returns an error if `tokens` does not contain a valid complete type expression.
1085    ///
1086    /// # Examples
1087    /// See [`Self::parse_type_expr_str`] for parsing a complete token stream.
1088    pub fn parse_type_expr_tokens(&mut self, tokens: TokenStreamIter) -> Result<TypeExpr> {
1089        self.set_tokens(tokens);
1090        let expr = self.parse_type_expr()?;
1091        if self.peek_token().is_some() {
1092            return Err(self.error_at("unexpected token"));
1093        }
1094        Ok(expr)
1095    }
1096
1097    /// Parses one complete `type_expr` from `s`.
1098    ///
1099    /// # Errors
1100    ///
1101    /// Returns an error on lex failure or if `s` does not contain a valid complete type
1102    /// expression.
1103    ///
1104    /// # Examples
1105    /// ```
1106    /// use cel_parser::{CELParser, OpLookup};
1107    /// use proc_macro2::TokenStream;
1108    /// let mut parser = CELParser::new(OpLookup::new());
1109    /// let syntax = parser.parse_type_expr_str("[i32]").unwrap();
1110    /// assert_eq!(parser.resolve_type_expr(&syntax).unwrap().display_name(), "[i32]");
1111    /// let tokens = "u32".parse::<TokenStream>().unwrap();
1112    /// let syntax = parser.parse_type_expr_tokens(tokens.into_iter()).unwrap();
1113    /// assert_eq!(parser.resolve_type_expr(&syntax).unwrap().display_name(), "u32");
1114    /// parser.set_tokens("bool".parse::<TokenStream>().unwrap().into_iter());
1115    /// let syntax = parser.parse_type_expr().unwrap();
1116    /// assert_eq!(parser.resolve_type_expr(&syntax).unwrap().display_name(), "bool");
1117    /// parser.set_tokens("i64".parse::<TokenStream>().unwrap().into_iter());
1118    /// let syntax = parser.parse_type_expression().unwrap();
1119    /// assert_eq!(parser.resolve_type_expr(&syntax).unwrap().display_name(), "i64");
1120    /// ```
1121    pub fn parse_type_expr_str(&mut self, s: &str) -> Result<TypeExpr> {
1122        let input = TokenStream::from_str(s).map_err(|e| ParseError::from_lex_error(s, e))?;
1123        self.parse_type_expr_tokens(input.into_iter())
1124    }
1125
1126    /// Returns a mutable reference to the operation lookup.
1127    ///
1128    /// This allows customization of the operations available during parsing,
1129    /// such as adding new scopes for custom operations or identifiers.
1130    ///
1131    /// # Examples
1132    ///
1133    /// ```rust
1134    /// use cel_parser::op_table::OpLookup;
1135    /// use cel_parser::CELParser;
1136    /// use cel_runtime::DynSegment;
1137    /// use proc_macro2::TokenStream;
1138    /// use std::any::TypeId;
1139    /// use std::str::FromStr;
1140    ///
1141    /// let input = TokenStream::from_str("10 + 20").unwrap();
1142    /// let mut lookup = OpLookup::new();
1143    /// lookup.push_scope(|name, segment, num_operands, _span| {
1144    ///     let matches = {
1145    ///         let top = segment.peek_stack_infos(num_operands);
1146    ///         name == "+" && top.len() == 2 && top[0].value_type.type_id() == TypeId::of::<i32>()
1147    ///     };
1148    ///     if matches {
1149    ///         segment.op2(|a: i32, b: i32| a + b + 1)?; // Custom addition
1150    ///         Ok(true)
1151    ///     } else {
1152    ///         Ok(false)
1153    ///     }
1154    /// });
1155    /// let mut parser = CELParser::new(lookup);
1156    /// parser.set_tokens(input.into_iter());
1157    /// ```
1158    pub fn op_lookup_mut(&mut self) -> &mut OpLookup {
1159        &mut self.op_lookup
1160    }
1161
1162    /// Advances past the current token, recording its span in `last_span`.
1163    ///
1164    /// - Postcondition: [`unbalanced_delimiter_count`](Self::unbalanced_delimiter_count) is
1165    ///   incremented if the consumed token is an `OpenDelim`, decremented if it is a
1166    ///   `CloseDelim` (of `Delimiter::Brace`, `Delimiter::Bracket`, or `Delimiter::Parenthesis`;
1167    ///   `Delimiter::None` never appears in a token stream this parser is given, since it only
1168    ///   ever arises from `proc_macro2`'s macro-hygiene groups, not from parsing source text).
1169    ///
1170    /// # Panics
1171    ///
1172    /// Panics if no token stream has been set or if there is no current token.
1173    fn advance(&mut self) {
1174        use lex_lexer::HasSpan;
1175        let token = self
1176            .tokens
1177            .as_mut()
1178            .expect("tokens set")
1179            .next()
1180            .expect("token required to advance");
1181        match &token {
1182            Token::OpenDelim {
1183                delimiter: Delimiter::Brace | Delimiter::Bracket | Delimiter::Parenthesis,
1184                ..
1185            } => self.unbalanced_delimiters += 1,
1186            Token::CloseDelim {
1187                delimiter: Delimiter::Brace | Delimiter::Bracket | Delimiter::Parenthesis,
1188                ..
1189            } => self.unbalanced_delimiters -= 1,
1190            _ => {}
1191        }
1192        self.last_span = token.span();
1193    }
1194
1195    /// Returns the span of the next token without consuming it, or `None` if exhausted.
1196    fn peek_span(&mut self) -> Option<Span> {
1197        self.peek_token().map(|token| {
1198            use lex_lexer::HasSpan;
1199            token.span()
1200        })
1201    }
1202
1203    /// Peeks at the current token without consuming it.
1204    ///
1205    /// Returns `None` if there are no more tokens.
1206    fn peek_token(&mut self) -> Option<&Token> {
1207        self.tokens.as_mut().expect("tokens set").peek()
1208    }
1209
1210    /// Builds a [`ParseError`] at the current token's span (or call_site if no token).
1211    fn error_at(&mut self, message: &str) -> ParseError {
1212        let span = match self.peek_token() {
1213            Some(token) => {
1214                use lex_lexer::HasSpan;
1215                token.span()
1216            }
1217            None => Span::call_site(),
1218        };
1219        ParseError::new(message, span)
1220    }
1221
1222    /// Consumes and returns `true` if the next token is punctuation matching `target`.
1223    fn is_punctuation(&mut self, target: &str) -> bool {
1224        match self.peek_token() {
1225            Some(Token::Punct { op, .. }) if op == target => {
1226                self.advance();
1227                true
1228            }
1229            _ => false,
1230        }
1231    }
1232
1233    /// Consumes and returns `true` if the next token is an identifier matching `keyword`.
1234    fn is_keyword(&mut self, keyword: &str) -> bool {
1235        match self.peek_token() {
1236            Some(Token::Identifier(ident)) if ident == keyword => {
1237                self.advance();
1238                true
1239            }
1240            _ => false,
1241        }
1242    }
1243
1244    /// Consumes and returns the next token's text if it is an identifier.
1245    ///
1246    /// # Errors
1247    ///
1248    /// Returns an error with `message` if the next token is not an identifier.
1249    fn expect_identifier(&mut self, message: &str) -> Result<String> {
1250        match self.peek_token() {
1251            Some(Token::Identifier(ident)) => {
1252                let name = ident.to_string();
1253                self.advance();
1254                Ok(name)
1255            }
1256            _ => Err(self.error_at(message)),
1257        }
1258    }
1259
1260    /// Consumes and returns `true` if the next token is an opening parenthesis `(`.
1261    fn is_open_paren(&mut self) -> bool {
1262        if matches!(
1263            self.peek_token(),
1264            Some(Token::OpenDelim {
1265                delimiter: Delimiter::Parenthesis,
1266                ..
1267            })
1268        ) {
1269            self.advance();
1270            true
1271        } else {
1272            false
1273        }
1274    }
1275
1276    /// Consumes and returns `true` if the next token is a closing parenthesis `)`.
1277    fn is_close_paren(&mut self) -> bool {
1278        if matches!(
1279            self.peek_token(),
1280            Some(Token::CloseDelim {
1281                delimiter: Delimiter::Parenthesis,
1282                ..
1283            })
1284        ) {
1285            self.advance();
1286            true
1287        } else {
1288            false
1289        }
1290    }
1291
1292    /// Consumes and returns `true` if the next token is a closing bracket `]`.
1293    fn is_close_bracket(&mut self) -> bool {
1294        if matches!(
1295            self.peek_token(),
1296            Some(Token::CloseDelim {
1297                delimiter: Delimiter::Bracket,
1298                ..
1299            })
1300        ) {
1301            self.advance();
1302            true
1303        } else {
1304            false
1305        }
1306    }
1307
1308    /// `expression = closure_expression | range_expression.`
1309    ///
1310    /// # Examples
1311    /// See [`Self::parse_tokens_ctx`] for recognizing an expression on the current cursor.
1312    pub fn is_expression(&mut self) -> Result<bool> {
1313        if self.is_punctuation("||") {
1314            self.is_closure_expression(true)
1315        } else if self.is_punctuation("|") {
1316            self.is_closure_expression(false)
1317        } else {
1318            self.is_range_expression()
1319        }
1320    }
1321
1322    /// `or_expression = and_expression { "||" and_expression }.`
1323    ///
1324    /// # Errors
1325    ///
1326    /// Returns an error if the RHS is missing after `||`, if the RHS does not
1327    /// produce a `bool`, or if any sub-expression returns an error.
1328    fn is_or_expression(&mut self) -> Result<bool> {
1329        let start_span = self.peek_span();
1330        if self.is_and_expression()? {
1331            while self.is_punctuation("||") {
1332                let mut rhs_fragment = self.context.new_fragment();
1333                std::mem::swap(&mut self.context, &mut rhs_fragment);
1334                if !self.is_and_expression()? {
1335                    return Err(self.error_at("expected and_expression"));
1336                }
1337                std::mem::swap(&mut self.context, &mut rhs_fragment);
1338                self.context.apply_logical(
1339                    "||",
1340                    rhs_fragment,
1341                    start_span.expect("production has token at start"),
1342                    self.last_span,
1343                )?;
1344            }
1345            Ok(true)
1346        } else {
1347            Ok(false)
1348        }
1349    }
1350
1351    /// `and_expression = comparison_expression { "&&" comparison_expression }.`
1352    ///
1353    /// # Errors
1354    ///
1355    /// Returns an error if the RHS is missing after `&&`, if the RHS does not
1356    /// produce a `bool`, or if any sub-expression returns an error.
1357    fn is_and_expression(&mut self) -> Result<bool> {
1358        let start_span = self.peek_span();
1359        if self.is_comparison_expression()? {
1360            while self.is_punctuation("&&") {
1361                let mut rhs_fragment = self.context.new_fragment();
1362                std::mem::swap(&mut self.context, &mut rhs_fragment);
1363                if !self.is_comparison_expression()? {
1364                    return Err(self.error_at("expected comparison_expression"));
1365                }
1366                std::mem::swap(&mut self.context, &mut rhs_fragment);
1367                self.context.apply_logical(
1368                    "&&",
1369                    rhs_fragment,
1370                    start_span.expect("production has token at start"),
1371                    self.last_span,
1372                )?;
1373            }
1374            Ok(true)
1375        } else {
1376            Ok(false)
1377        }
1378    }
1379
1380    /// `comparison_expression = bitwise_or_expression
1381    ///     [ ("==" | "!=" | "<" | ">" | "<=" | ">=") bitwise_or_expression ].`
1382    fn is_comparison_expression(&mut self) -> Result<bool> {
1383        let start_span = self.peek_span();
1384        if self.is_bitwise_or_expression()? {
1385            // Longer operators first: must check "==" before "=", "<=" before "<", etc.
1386            let op_name = if self.is_punctuation("==") {
1387                Some("==")
1388            } else if self.is_punctuation("!=") {
1389                Some("!=")
1390            } else if self.is_punctuation("<=") {
1391                Some("<=")
1392            } else if self.is_punctuation(">=") {
1393                Some(">=")
1394            } else if self.is_punctuation("<") {
1395                Some("<")
1396            } else if self.is_punctuation(">") {
1397                Some(">")
1398            } else {
1399                None
1400            };
1401
1402            if let Some(op_name) = op_name {
1403                if !self.is_bitwise_or_expression()? {
1404                    return Err(self.error_at("expected bitwise_or_expression"));
1405                }
1406                self.context.apply_op(
1407                    &self.op_lookup,
1408                    op_name,
1409                    2,
1410                    start_span.expect("production has token at start"),
1411                    self.last_span,
1412                )?;
1413            }
1414            Ok(true)
1415        } else {
1416            Ok(false)
1417        }
1418    }
1419
1420    /// `range_expression = or_expression [ ".." [ or_expression ] | "..=" or_expression ]
1421    ///                   | ".." [ or_expression ]
1422    ///                   | "..=" or_expression.`
1423    ///
1424    /// Left-factored so every alternative is chosen by one concrete leading token rather
1425    /// than by first deciding whether an optional `or_expression` is present: the three
1426    /// alternatives start with `or_expression`'s own FIRST set, the literal `".."`, or the
1427    /// literal `"..="` respectively — pairwise disjoint (`..`/`..=` can never be the first
1428    /// token of an `or_expression`), so picking among them needs exactly one token, and none
1429    /// of them opens with a bracketed, possibly-empty non-terminal.
1430    ///
1431    /// `..`'s right operand is optional wherever it appears (covering, across the three
1432    /// alternatives, `Range`/`RangeFrom`/`RangeTo`/`RangeFull`); `..=`'s right operand is
1433    /// never optional (covering `RangeInclusive`/`RangeToInclusive`) — there is no
1434    /// inclusive-from-only range in Rust, and no such form is registered in the op-table for
1435    /// it to dispatch to, so a bare `..=`, or a left operand followed by `..=` and nothing
1436    /// after, is a parse error, not a valid empty match.
1437    ///
1438    /// Operands are `or_expression` — matching Rust's own precedence, where `..`/`..=` bind
1439    /// *looser* than `||` (and everything below it: `&&`, comparisons, bitwise ops,
1440    /// arithmetic). So `1 + 2..3 * 4` still groups as `(1 + 2)..(3 * 4)` (arithmetic is well
1441    /// inside `or_expression`'s own chain), and `a == b..c == d` groups the *whole*
1442    /// comparisons as the two endpoints: `(a == b)..(c == d)`, not `a == (b..c) == d`.
1443    fn is_range_expression(&mut self) -> Result<bool> {
1444        let start_span = self.peek_span();
1445
1446        if self.is_punctuation("..=") {
1447            if !self.is_or_expression()? {
1448                return Err(self.error_at("expected or_expression"));
1449            }
1450            self.context.apply_op(
1451                &self.op_lookup,
1452                "range_to_inclusive",
1453                1,
1454                start_span.expect("production has token at start"),
1455                self.last_span,
1456            )?;
1457            return Ok(true);
1458        }
1459
1460        if self.is_punctuation("..") {
1461            if self.is_or_expression()? {
1462                self.context.apply_op(
1463                    &self.op_lookup,
1464                    "range_to",
1465                    1,
1466                    start_span.expect("production has token at start"),
1467                    self.last_span,
1468                )?;
1469            } else {
1470                self.context.apply_op(
1471                    &self.op_lookup,
1472                    "range_full",
1473                    0,
1474                    start_span.expect("production has token at start"),
1475                    self.last_span,
1476                )?;
1477            }
1478            return Ok(true);
1479        }
1480
1481        if self.is_or_expression()? {
1482            if self.is_punctuation("..=") {
1483                if !self.is_or_expression()? {
1484                    return Err(self.error_at("expected or_expression"));
1485                }
1486                self.context.apply_op(
1487                    &self.op_lookup,
1488                    "range_inclusive",
1489                    2,
1490                    start_span.expect("production has token at start"),
1491                    self.last_span,
1492                )?;
1493            } else if self.is_punctuation("..") {
1494                if self.is_or_expression()? {
1495                    self.context.apply_op(
1496                        &self.op_lookup,
1497                        "range",
1498                        2,
1499                        start_span.expect("production has token at start"),
1500                        self.last_span,
1501                    )?;
1502                } else {
1503                    self.context.apply_op(
1504                        &self.op_lookup,
1505                        "range_from",
1506                        1,
1507                        start_span.expect("production has token at start"),
1508                        self.last_span,
1509                    )?;
1510                }
1511            }
1512            Ok(true)
1513        } else {
1514            Ok(false)
1515        }
1516    }
1517
1518    /// `bitwise_or_expression = bitwise_xor_expression { "|" bitwise_xor_expression }.`
1519    fn is_bitwise_or_expression(&mut self) -> Result<bool> {
1520        let start_span = self.peek_span();
1521        if self.is_bitwise_xor_expression()? {
1522            while self.is_punctuation("|") {
1523                if !self.is_bitwise_xor_expression()? {
1524                    return Err(self.error_at("expected bitwise_xor_expression"));
1525                }
1526                self.context.apply_op(
1527                    &self.op_lookup,
1528                    "|",
1529                    2,
1530                    start_span.expect("production has token at start"),
1531                    self.last_span,
1532                )?;
1533            }
1534            Ok(true)
1535        } else {
1536            Ok(false)
1537        }
1538    }
1539
1540    /// `bitwise_xor_expression = bitwise_and_expression { "^" bitwise_and_expression }.`
1541    fn is_bitwise_xor_expression(&mut self) -> Result<bool> {
1542        let start_span = self.peek_span();
1543        if self.is_bitwise_and_expression()? {
1544            while self.is_punctuation("^") {
1545                if !self.is_bitwise_and_expression()? {
1546                    return Err(self.error_at("expected bitwise_and_expression"));
1547                }
1548                self.context.apply_op(
1549                    &self.op_lookup,
1550                    "^",
1551                    2,
1552                    start_span.expect("production has token at start"),
1553                    self.last_span,
1554                )?;
1555            }
1556            Ok(true)
1557        } else {
1558            Ok(false)
1559        }
1560    }
1561
1562    /// `bitwise_and_expression = bitwise_shift_expression { "&" bitwise_shift_expression }.`
1563    fn is_bitwise_and_expression(&mut self) -> Result<bool> {
1564        let start_span = self.peek_span();
1565        if self.is_bitwise_shift_expression()? {
1566            while self.is_punctuation("&") {
1567                if !self.is_bitwise_shift_expression()? {
1568                    return Err(self.error_at("expected bitwise_shift_expression"));
1569                }
1570                self.context.apply_op(
1571                    &self.op_lookup,
1572                    "&",
1573                    2,
1574                    start_span.expect("production has token at start"),
1575                    self.last_span,
1576                )?;
1577            }
1578            Ok(true)
1579        } else {
1580            Ok(false)
1581        }
1582    }
1583
1584    /// `bitwise_shift_expression = additive_expression { ("<<" | ">>") additive_expression }.`
1585    fn is_bitwise_shift_expression(&mut self) -> Result<bool> {
1586        let start_span = self.peek_span();
1587        if self.is_additive_expression()? {
1588            loop {
1589                let op_name = if self.is_punctuation("<<") {
1590                    Some("<<")
1591                } else if self.is_punctuation(">>") {
1592                    Some(">>")
1593                } else {
1594                    None
1595                };
1596
1597                if let Some(op_name) = op_name {
1598                    if !self.is_additive_expression()? {
1599                        return Err(self.error_at("expected additive_expression"));
1600                    }
1601                    self.context.apply_op(
1602                        &self.op_lookup,
1603                        op_name,
1604                        2,
1605                        start_span.expect("production has token at start"),
1606                        self.last_span,
1607                    )?;
1608                } else {
1609                    break;
1610                }
1611            }
1612            Ok(true)
1613        } else {
1614            Ok(false)
1615        }
1616    }
1617
1618    /// `additive_expression = multiplicative_expression { ("+" | "-") multiplicative_expression }.`
1619    fn is_additive_expression(&mut self) -> Result<bool> {
1620        let start_span = self.peek_span();
1621        if self.is_multiplicative_expression()? {
1622            loop {
1623                let op_name = if self.is_punctuation("+") {
1624                    Some("+")
1625                } else if self.is_punctuation("-") {
1626                    Some("-")
1627                } else {
1628                    None
1629                };
1630
1631                if let Some(op_name) = op_name {
1632                    if !self.is_multiplicative_expression()? {
1633                        return Err(self.error_at("expected multiplicative_expression"));
1634                    }
1635                    self.context.apply_op(
1636                        &self.op_lookup,
1637                        op_name,
1638                        2,
1639                        start_span.expect("production has token at start"),
1640                        self.last_span,
1641                    )?;
1642                } else {
1643                    break;
1644                }
1645            }
1646            Ok(true)
1647        } else {
1648            Ok(false)
1649        }
1650    }
1651
1652    /// `multiplicative_expression = cast_expression { ("*" | "/" | "%") cast_expression }.`
1653    fn is_multiplicative_expression(&mut self) -> Result<bool> {
1654        let start_span = self.peek_span();
1655        if self.is_cast_expression()? {
1656            loop {
1657                let op_name = if self.is_punctuation("*") {
1658                    Some("*")
1659                } else if self.is_punctuation("/") {
1660                    Some("/")
1661                } else if self.is_punctuation("%") {
1662                    Some("%")
1663                } else {
1664                    None
1665                };
1666
1667                if let Some(op_name) = op_name {
1668                    if !self.is_cast_expression()? {
1669                        return Err(self.error_at("expected cast_expression"));
1670                    }
1671                    self.context.apply_op(
1672                        &self.op_lookup,
1673                        op_name,
1674                        2,
1675                        start_span.expect("production has token at start"),
1676                        self.last_span,
1677                    )?;
1678                } else {
1679                    break;
1680                }
1681            }
1682            Ok(true)
1683        } else {
1684            Ok(false)
1685        }
1686    }
1687
1688    /// `cast_expression = unary_expression { "as" identifier }.`
1689    ///
1690    /// Binds tighter than `* / %` but looser than unary prefix and postfix, matching Rust: `-x as
1691    /// f64` is `(-x) as f64`, and `x as i32 as f64` applies left-to-right (`(x as i32) as f64`).
1692    ///
1693    /// # Errors
1694    ///
1695    /// Returns an error if `"as"` isn't followed by an identifier, or if any sub-expression
1696    /// returns an error.
1697    fn is_cast_expression(&mut self) -> Result<bool> {
1698        let start_span = self.peek_span();
1699        if self.is_unary_expression()? {
1700            while self.is_keyword("as") {
1701                let type_name = match self.peek_token() {
1702                    Some(Token::Identifier(ident)) => ident.to_string(),
1703                    _ => return Err(self.error_at("expected type name after `as`")),
1704                };
1705                self.advance();
1706                self.context.apply_cast(
1707                    &self.op_lookup,
1708                    &type_name,
1709                    start_span.expect("production has token at start"),
1710                    self.last_span,
1711                )?;
1712            }
1713            Ok(true)
1714        } else {
1715            Ok(false)
1716        }
1717    }
1718
1719    /// `unary_expression = ("-" | "!") unary_expression | postfix_expression.`
1720    fn is_unary_expression(&mut self) -> Result<bool> {
1721        let start_span = self.peek_span();
1722        let op_name = if self.is_punctuation("-") {
1723            Some("-")
1724        } else if self.is_punctuation("!") {
1725            Some("!")
1726        } else {
1727            None
1728        };
1729
1730        if let Some(op_name) = op_name {
1731            if !self.is_unary_expression()? {
1732                return Err(self.error_at("expected unary_expression"));
1733            }
1734            self.context.apply_op(
1735                &self.op_lookup,
1736                op_name,
1737                1,
1738                start_span.expect("production has token at start"),
1739                self.last_span,
1740            )?;
1741            Ok(true)
1742        } else {
1743            self.is_postfix_expression()
1744        }
1745    }
1746
1747    /// `literal_pattern = ["-"] literal.`
1748    ///
1749    /// A deliberately narrower relative of [`is_unary_expression`](Self::is_unary_expression):
1750    /// Rust's own pattern grammar allows a leading unary `-` only directly on a literal (no
1751    /// `!`, no chained `--`, and the operand must be a bare literal token, not an arbitrary
1752    /// `postfix_expression`).
1753    ///
1754    /// # Errors
1755    ///
1756    /// Returns an error if a leading `-` is not followed by a literal, or if the literal or its
1757    /// negation fails (e.g. an out-of-range suffixed integer, or negating an unsigned literal).
1758    fn is_literal_pattern(&mut self) -> Result<bool> {
1759        let start_span = self.peek_span();
1760        let negated = self.is_punctuation("-");
1761        match self.peek_token() {
1762            Some(Token::Literal(lit)) => {
1763                let lit_clone = lit.clone();
1764                self.advance();
1765                push_literal_token(&mut self.context, lit_clone)?;
1766                if negated {
1767                    self.context.apply_op(
1768                        &self.op_lookup,
1769                        "-",
1770                        1,
1771                        start_span.expect("production has token at start"),
1772                        self.last_span,
1773                    )?;
1774                }
1775                Ok(true)
1776            }
1777            _ if negated => Err(self.error_at("expected literal after `-`")),
1778            _ => Ok(false),
1779        }
1780    }
1781
1782    /// `postfix_expression = primary_expression { postfix_suffix }.`
1783    ///
1784    /// `postfix_suffix = "(" [ parameter_list ] ")" | "." (unsuffixed_integer | identifier).`
1785    ///
1786    /// The repetition allows chained indices (`t.0.1`): each `"." unsuffixed_integer`
1787    /// is applied in turn to whatever value the previous step left on top of the
1788    /// stack. Source text like `.0.1` tokenizes as a single `.` followed by one
1789    /// float literal `0.1` (Rust's own lexer maximally munches the digits after
1790    /// the second `.`), so that case is detected and split back into its two
1791    /// integer indices — see the `Token::Literal(CelLiteral::Float(..))` arm below.
1792    /// Named members consume their optional argument list as one operation, not a call
1793    /// of a previously evaluated scalar result.
1794    ///
1795    /// # Errors
1796    ///
1797    /// Returns malformed selector conversion errors at the original selector token,
1798    /// including a single maximally munched float token containing chained indices.
1799    fn is_postfix_expression(&mut self) -> Result<bool> {
1800        let start_span = self.peek_span();
1801        if !self.is_primary_expression()? {
1802            return Ok(false);
1803        }
1804        loop {
1805            if let Some(arg_count) = self.postfix_arguments()? {
1806                // Stack order is [callee, arg1, arg2, ...]; lookup peeks top (arg_count + 1) entries.
1807                self.context.apply_op(
1808                    &self.op_lookup,
1809                    "()",
1810                    arg_count + 1,
1811                    start_span.expect("production has token at start"),
1812                    self.last_span,
1813                )?;
1814            } else if self.is_punctuation(".") {
1815                match self.peek_token() {
1816                    Some(Token::Identifier(_)) => {
1817                        let name_start = self.peek_span().expect("member has a name token");
1818                        let name = self.expect_identifier("expected member name")?;
1819                        let name_span = ExprSpan {
1820                            start: name_start,
1821                            end: name_start,
1822                        };
1823                        let explicit_args = self.postfix_arguments()?;
1824                        self.context.apply_member(
1825                            &self.op_lookup,
1826                            &name,
1827                            name_span,
1828                            explicit_args,
1829                            start_span.expect("production has token at start"),
1830                            self.last_span,
1831                        )?;
1832                    }
1833                    Some(Token::Literal(CelLiteral::Int(integer))) => {
1834                        let integer = integer.clone();
1835                        if !integer.suffix().is_empty() {
1836                            return Err(self.error_at("tuple index must be an unsuffixed integer"));
1837                        }
1838                        self.advance();
1839                        let index = integer.base10_parse::<usize>().map_err(|e| {
1840                            ParseError::new(
1841                                format!("invalid tuple index `{integer}`: {e}"),
1842                                integer.span(),
1843                            )
1844                        })?;
1845                        self.apply_tuple_index(
1846                            index,
1847                            start_span.expect("production has token at start"),
1848                        )?;
1849                    }
1850                    Some(Token::Literal(CelLiteral::Float(float))) => {
1851                        let float = float.clone();
1852                        if !float.suffix().is_empty() {
1853                            return Err(self.error_at("tuple index must be an unsuffixed integer"));
1854                        }
1855                        // base10_digits() returns the decimal digits with underscores
1856                        // stripped and the suffix removed, e.g. "0.1" or "10.25" for
1857                        // ordinary decimal floats — splitting on '.' recovers the two
1858                        // chained integer indices. Scientific-notation floats (e.g.
1859                        // `1e2`) normalize to digits with no '.' at all; reject those
1860                        // as a parse error (checked before advancing, so the error
1861                        // span still points at the float token) rather than assuming
1862                        // a '.' is always present.
1863                        let digits = float.base10_digits();
1864                        let Some((first, second)) = digits.split_once('.') else {
1865                            return Err(self.error_at(
1866                                "tuple index chain must use decimal notation (e.g. `.0.1`)",
1867                            ));
1868                        };
1869                        self.advance();
1870                        let first_index = first.parse::<usize>().map_err(|e| {
1871                            ParseError::new(
1872                                format!("invalid tuple index `{first}`: {e}"),
1873                                float.span(),
1874                            )
1875                        })?;
1876                        let second_index = second.parse::<usize>().map_err(|e| {
1877                            ParseError::new(
1878                                format!("invalid tuple index `{second}`: {e}"),
1879                                float.span(),
1880                            )
1881                        })?;
1882                        let idx_start = start_span.expect("production has token at start");
1883                        self.apply_tuple_index(first_index, idx_start)?;
1884                        self.apply_tuple_index(second_index, idx_start)?;
1885                    }
1886                    _ => return Err(self.error_at("expected integer or member name after '.'")),
1887                }
1888            } else {
1889                break;
1890            }
1891        }
1892        Ok(true)
1893    }
1894
1895    /// `postfix_arguments = [ "(" [ parameter_list ] ")" ].`
1896    ///
1897    /// Returns `None` without consuming input when no argument list follows.
1898    ///
1899    /// # Errors
1900    /// Returns an error for malformed arguments or a missing closing parenthesis.
1901    ///
1902    /// - Complexity: O(n) in the parsed argument expressions.
1903    fn postfix_arguments(&mut self) -> Result<Option<usize>> {
1904        if !self.is_open_paren() {
1905            return Ok(None);
1906        }
1907        if self.is_close_paren() {
1908            return Ok(Some(0));
1909        }
1910        let count = self.parameter_list()?;
1911        if !self.is_close_paren() {
1912            return Err(self.error_at("expected closing parenthesis"));
1913        }
1914        Ok(Some(count))
1915    }
1916
1917    /// Applies `.N`, leaving semantic bounds validation to the owning context.
1918    ///
1919    /// # Errors
1920    /// Returns an error if the top of stack isn't a tuple, or if `index` is
1921    /// out of range for its arity.
1922    fn apply_tuple_index(&mut self, index: usize, start: Span) -> Result<()> {
1923        self.context.tuple_index(index, start, self.last_span)?;
1924        Ok(())
1925    }
1926
1927    /// `parameter_list = expression { "," expression }.`
1928    ///
1929    /// Always parses at least one `expression` — callers that need to allow zero
1930    /// arguments (`postfix_expression`'s `"(" [ parameter_list ] ")"`) check for that
1931    /// possibility themselves before calling, rather than `parameter_list` swallowing it.
1932    ///
1933    /// Returns the argument count.
1934    ///
1935    /// # Errors
1936    /// Returns an error if the first token can't start an `expression`, or if a comma
1937    /// isn't followed by one.
1938    fn parameter_list(&mut self) -> Result<usize> {
1939        if !self.is_ordinary_expression()? {
1940            return Err(self.error_at("expected expression"));
1941        }
1942        let mut count = 1;
1943        while self.is_punctuation(",") {
1944            if !self.is_ordinary_expression()? {
1945                return Err(self.error_at("expected expression after comma"));
1946            }
1947            count += 1;
1948        }
1949        Ok(count)
1950    }
1951
1952    /// `primary_expression = literal | value_identifier [ record_fields ] | tuple_or_group |
1953    /// array_expression | if_expression.`
1954    ///
1955    /// Dispatches to [`is_if_expression`](Self::is_if_expression) when the `if` keyword is seen,
1956    /// to [`is_tuple_or_group`](Self::is_tuple_or_group) when `(` is seen, to
1957    /// [`is_array_expression`](Self::is_array_expression) when `[` is seen.
1958    /// A closure reaches this production only through a parenthesized expression.
1959    ///
1960    /// # Errors
1961    ///
1962    /// Returns an error if:
1963    /// - A literal value cannot be parsed (e.g., integer out of range).
1964    /// - An identifier is not found in the op lookup table.
1965    /// - A tuple-or-group expression fails to parse.
1966    /// - An array literal fails to parse or is rejected by the context it emits into.
1967    /// - An `if` expression fails to parse.
1968    fn is_primary_expression(&mut self) -> Result<bool> {
1969        match self.peek_token() {
1970            Some(Token::Literal(lit)) => {
1971                let lit_clone = lit.clone();
1972                self.advance();
1973                push_literal_token(&mut self.context, lit_clone)?;
1974                Ok(true)
1975            }
1976            Some(Token::Identifier(ident)) => {
1977                let ident_name = ident.to_string();
1978                let ident_span = ident.span();
1979                self.advance();
1980
1981                if ident_name == "if" {
1982                    return self.is_if_expression(ident_span);
1983                }
1984                if matches!(ident_name.as_str(), "else" | "as") {
1985                    return Err(ParseError::new(
1986                        "reserved keyword is not a value name",
1987                        ident_span,
1988                    ));
1989                }
1990
1991                if self.restriction == ExpressionRestriction::None
1992                    && matches!(
1993                        self.peek_token(),
1994                        Some(Token::OpenDelim {
1995                            delimiter: Delimiter::Brace,
1996                            ..
1997                        })
1998                    )
1999                {
2000                    return self.is_record_fields(&ident_name, ident_span);
2001                }
2002
2003                self.context
2004                    .apply_op(&self.op_lookup, &ident_name, 0, ident_span, ident_span)?;
2005
2006                Ok(true)
2007            }
2008            Some(Token::OpenDelim {
2009                delimiter: Delimiter::Parenthesis,
2010                ..
2011            }) => self.is_tuple_or_group(),
2012            Some(Token::OpenDelim {
2013                delimiter: Delimiter::Bracket,
2014                ..
2015            }) => self.is_array_expression(),
2016            _ => Ok(false),
2017        }
2018    }
2019
2020    /// `record_fields = "{" [ identifier ":" expression { "," identifier ":" expression } ] "}".`
2021    ///
2022    /// - Precondition: the constructor identifier is consumed and the next token is `{`.
2023    /// - Postcondition: fields are emitted once in source order before the constructor.
2024    /// - Complexity: O(n + f² + f * d + r) for n tokens, f fields, descriptor depth d and r schemas.
2025    ///
2026    /// # Errors
2027    /// Returns malformed field syntax errors or the context's record validation error.
2028    fn is_record_fields(&mut self, name: &str, name_token: Span) -> Result<bool> {
2029        self.advance();
2030        let ambient_start = self.context.current_stack_offset();
2031        let mut fields = Vec::new();
2032        if !matches!(
2033            self.peek_token(),
2034            Some(Token::CloseDelim {
2035                delimiter: Delimiter::Brace,
2036                ..
2037            })
2038        ) {
2039            loop {
2040                let Some(Token::Identifier(field)) = self.peek_token() else {
2041                    return Err(self.error_at("expected record field name"));
2042                };
2043                let field_name = field.to_string();
2044                let field_span = field.span();
2045                self.advance();
2046                if !self.is_punctuation(":") {
2047                    return Err(self.error_at("expected `:` after record field name"));
2048                }
2049                if !self.is_ordinary_expression()? {
2050                    return Err(self.error_at("expected record field expression"));
2051                }
2052                fields.push((
2053                    field_name,
2054                    ExprSpan {
2055                        start: field_span,
2056                        end: field_span,
2057                    },
2058                ));
2059                if !self.is_punctuation(",") {
2060                    break;
2061                }
2062            }
2063        }
2064        if !matches!(
2065            self.peek_token(),
2066            Some(Token::CloseDelim {
2067                delimiter: Delimiter::Brace,
2068                ..
2069            })
2070        ) {
2071            return Err(self.error_at("expected `,` or `}` after record field"));
2072        }
2073        self.advance();
2074        self.context.make_record(
2075            &self.op_lookup,
2076            name,
2077            ExprSpan {
2078                start: name_token,
2079                end: name_token,
2080            },
2081            fields,
2082            ambient_start,
2083            name_token,
2084            self.last_span,
2085        )?;
2086        Ok(true)
2087    }
2088
2089    /// `tuple_or_group = "(" [ expression ["," [ expression { "," expression } ]] ] ")".`
2090    ///
2091    /// `()` parses as unit, `(expr)` as grouping, `(expr,)` as a 1-tuple, and
2092    /// `(expr, expr, ...)` as an n-tuple.
2093    ///
2094    /// - Precondition: The next token is `Token::OpenDelim` with `Delimiter::Parenthesis`.
2095    ///
2096    /// # Errors
2097    ///
2098    /// Returns an error if the parenthesized expression or tuple literal is malformed, has a
2099    /// missing or misplaced comma, or is missing its closing `)`.
2100    fn is_tuple_or_group(&mut self) -> Result<bool> {
2101        let open_span = self
2102            .peek_span()
2103            .expect("tuple_or_group requires an opening '(' token");
2104        self.advance();
2105        // Unit expression: ()
2106        if matches!(
2107            self.peek_token(),
2108            Some(Token::CloseDelim {
2109                delimiter: Delimiter::Parenthesis,
2110                ..
2111            })
2112        ) {
2113            self.advance();
2114            self.context.make_tuple(
2115                0,
2116                self.context.current_stack_offset(),
2117                open_span,
2118                self.last_span,
2119            )?;
2120            return Ok(true);
2121        }
2122        let ambient_start = self.context.current_stack_offset();
2123        if !self.is_ordinary_expression()? {
2124            return Err(self.error_at("expected expression"));
2125        }
2126        if matches!(
2127            self.peek_token(),
2128            Some(Token::CloseDelim {
2129                delimiter: Delimiter::Parenthesis,
2130                ..
2131            })
2132        ) {
2133            // Grouping: exactly one expression, no comma.
2134            self.advance();
2135            return Ok(true);
2136        }
2137        if !self.is_punctuation(",") {
2138            return Err(self.error_at("expected ',' or closing parenthesis"));
2139        }
2140        let mut count = 1;
2141        if matches!(
2142            self.peek_token(),
2143            Some(Token::CloseDelim {
2144                delimiter: Delimiter::Parenthesis,
2145                ..
2146            })
2147        ) {
2148            // Single element + trailing comma: 1-tuple.
2149            self.advance();
2150            self.context
2151                .make_tuple(count, ambient_start, open_span, self.last_span)?;
2152            return Ok(true);
2153        }
2154        loop {
2155            if !self.is_ordinary_expression()? {
2156                return Err(self.error_at("expected expression after ','"));
2157            }
2158            count += 1;
2159            if matches!(
2160                self.peek_token(),
2161                Some(Token::CloseDelim {
2162                    delimiter: Delimiter::Parenthesis,
2163                    ..
2164                })
2165            ) {
2166                self.advance();
2167                break;
2168            }
2169            if !self.is_punctuation(",") {
2170                return Err(self.error_at("expected ',' or closing parenthesis"));
2171            }
2172        }
2173        self.context
2174            .make_tuple(count, ambient_start, open_span, self.last_span)?;
2175        Ok(true)
2176    }
2177
2178    /// `array_expression = "[" [ expression { "," expression } ] "]" [ ":" type_expr ] .`
2179    ///
2180    /// Every comma requires another element, so the production stays Wirth-style LL(1) — each
2181    /// decision is made by inspecting exactly the current token — and a trailing comma
2182    /// (`[0i32,]`) is a parse error, unlike the 1-tuple form `(0i32,)` where the comma is what
2183    /// distinguishes a tuple from a grouping.
2184    ///
2185    /// - Precondition: The next token is `Token::OpenDelim` with `Delimiter::Bracket`.
2186    ///
2187    /// # Errors
2188    ///
2189    /// Returns an error if the literal is empty and unannotated (`[]` has no inferable element
2190    /// type; see <https://github.com/stlab/cel-rs/issues/212>), if an element expression is
2191    /// missing or malformed, if a comma or the closing `]` is missing, if the type annotation is
2192    /// malformed or names a non-array type, or if the context this emits into rejects the
2193    /// collected elements (e.g. heterogeneous element types).
2194    ///
2195    /// - Postcondition: Returns `Ok(true)` on success; `Ok(false)` is never returned.
2196    fn is_array_expression(&mut self) -> Result<bool> {
2197        let open_span = self
2198            .peek_span()
2199            .expect("array_expression requires an opening '[' token");
2200        self.advance();
2201        let ambient_start = self.context.current_stack_offset();
2202        let mut count = 0usize;
2203        if !self.is_close_bracket() {
2204            if !self.is_ordinary_expression()? {
2205                return Err(self.error_at("expected expression"));
2206            }
2207            count = 1;
2208            loop {
2209                if self.is_close_bracket() {
2210                    break;
2211                }
2212                if !self.is_punctuation(",") {
2213                    return Err(self.error_at("expected ',' or closing ']'"));
2214                }
2215                // A comma always requires another element: `]` here (a trailing comma) can't
2216                // start an `expression`, so this is where `[0i32,]` is rejected.
2217                if !self.is_ordinary_expression()? {
2218                    return Err(self.error_at("expected expression after ','"));
2219                }
2220                count += 1;
2221            }
2222        }
2223
2224        let mut type_annotation = None;
2225        let mut annotation_span = None;
2226        if self.is_punctuation(":") {
2227            let colon_span = self.last_span;
2228            let annotation = self.parse_type_expression()?;
2229            let annotation_end = annotation.span().end;
2230            annotation_span = Some(ExprSpan {
2231                start: colon_span,
2232                end: annotation_end,
2233            });
2234            type_annotation = Some(annotation);
2235        }
2236
2237        if count == 0 && type_annotation.is_none() {
2238            return Err(ParseError::new_range(
2239                "an empty array literal has no inferable element type; \
2240                 see https://github.com/stlab/cel-rs/issues/212"
2241                    .to_string(),
2242                open_span,
2243                self.last_span,
2244            ));
2245        }
2246
2247        let type_resolver = self.type_resolver.as_ref();
2248        let mut resolve_array_type = |type_expr: &TypeExpr| {
2249            let resolved = type_expr.resolve(type_resolver)?;
2250            ResolvedArrayType::from_resolved_type(resolved, type_expr.span())
2251        };
2252        self.context.make_annotated_array(
2253            count,
2254            ambient_start,
2255            parser_context::AnnotatedArray::new(
2256                &mut resolve_array_type,
2257                type_annotation,
2258                annotation_span,
2259            ),
2260            open_span,
2261            self.last_span,
2262        )?;
2263        Ok(true)
2264    }
2265
2266    /// `type_expression = identifier [ "(" [ type_expression { "," type_expression } ] ")" ]
2267    ///                  | "[" type_expression "]"
2268    ///                  | "(" [ type_expression ["," [ type_expression { "," type_expression } ]] ] ")" .`
2269    ///
2270    /// `()` is the empty tuple type (0 elements); `(T)` is grouping (same as bare `T`); `(T,)`
2271    /// is a 1-element tuple; `(T, U, ...)` is n-element, no trailing comma. `[T]` names an
2272    /// array type whose elements have type `T`. `Name(A, B)` applies zero or more type
2273    /// arguments to a named (generic) type.
2274    ///
2275    /// # Errors
2276    ///
2277    /// Returns an error if a named leaf is missing, if an array, tuple, or type-argument list is
2278    /// malformed, or if a closing `]`/`)` is missing.
2279    ///
2280    /// # Examples
2281    /// See [`Self::parse_type_expr_str`] for recognizing type syntax on the current cursor.
2282    pub fn parse_type_expression(&mut self) -> Result<TypeExpr> {
2283        if let Some(Token::Identifier(_)) = self.peek_token() {
2284            let name = self.expect_identifier("expected a type name")?;
2285            let name_span = self.last_span;
2286            let mut args = Vec::new();
2287            let mut end_span = name_span;
2288            if self.is_open_paren() {
2289                if !self.is_close_paren() {
2290                    args.push(self.parse_type_expression()?);
2291                    while self.is_punctuation(",") {
2292                        args.push(self.parse_type_expression()?);
2293                    }
2294                    if !self.is_close_paren() {
2295                        return Err(
2296                            self.error_at("expected ',' or closing ')' in type argument list")
2297                        );
2298                    }
2299                }
2300                end_span = self.last_span;
2301            }
2302            return Ok(TypeExpr::Named {
2303                name,
2304                args,
2305                span: ExprSpan {
2306                    start: name_span,
2307                    end: end_span,
2308                },
2309            });
2310        }
2311
2312        if matches!(
2313            self.peek_token(),
2314            Some(Token::OpenDelim {
2315                delimiter: Delimiter::Bracket,
2316                ..
2317            })
2318        ) {
2319            let open_span = self
2320                .peek_span()
2321                .expect("array type expression requires an opening '[' token");
2322            self.advance();
2323            if self.is_close_bracket() {
2324                return Err(ParseError::new_range(
2325                    "expected a type expression after '['".to_string(),
2326                    open_span,
2327                    self.last_span,
2328                ));
2329            }
2330            let element = self.parse_type_expression()?;
2331            if !self.is_close_bracket() {
2332                return Err(self.error_at("expected closing ']' after array type expression"));
2333            }
2334            return Ok(TypeExpr::Array {
2335                element: Box::new(element),
2336                span: ExprSpan {
2337                    start: open_span,
2338                    end: self.last_span,
2339                },
2340            });
2341        }
2342
2343        if !self.is_open_paren() {
2344            return Err(self.error_at("expected a type name, '[' or '('"));
2345        }
2346        let open_span = self.last_span;
2347        if self.is_close_paren() {
2348            return Ok(TypeExpr::Tuple {
2349                elements: Vec::new(),
2350                span: ExprSpan {
2351                    start: open_span,
2352                    end: self.last_span,
2353                },
2354            });
2355        }
2356
2357        let first = self.parse_type_expression()?;
2358        if self.is_close_paren() {
2359            let span = ExprSpan {
2360                start: open_span,
2361                end: self.last_span,
2362            };
2363            return Ok(match first {
2364                TypeExpr::Named { name, args, .. } => TypeExpr::Named { name, args, span },
2365                TypeExpr::Array { element, .. } => TypeExpr::Array { element, span },
2366                TypeExpr::Tuple { elements, .. } => TypeExpr::Tuple { elements, span },
2367            });
2368        }
2369        if !self.is_punctuation(",") {
2370            return Err(self.error_at("expected ',' or closing ')'"));
2371        }
2372        if self.is_close_paren() {
2373            return Ok(TypeExpr::Tuple {
2374                elements: vec![first],
2375                span: ExprSpan {
2376                    start: open_span,
2377                    end: self.last_span,
2378                },
2379            });
2380        }
2381        let mut elements = vec![first];
2382        loop {
2383            elements.push(self.parse_type_expression()?);
2384            if self.is_close_paren() {
2385                break;
2386            }
2387            if !self.is_punctuation(",") {
2388                return Err(self.error_at("expected ',' or closing ')'"));
2389            }
2390        }
2391        Ok(TypeExpr::Tuple {
2392            elements,
2393            span: ExprSpan {
2394                start: open_span,
2395                end: self.last_span,
2396            },
2397        })
2398    }
2399
2400    /// `closure_expression = ("||" | "|" [ closure_param { "," closure_param } ] "|") expression .`
2401    /// `closure_param = identifier ":" type_expression .`
2402    ///
2403    /// Compiles the body as a fully independent nested context (via
2404    /// [`parse_nested_context`](Self::parse_nested_context)) whose only visible names are its
2405    /// own declared parameters plus whatever library/built-in functions are always reachable —
2406    /// [`OpLookup::isolate_scopes`] hides every other transient scope (including one an enclosing
2407    /// caller, e.g. adam-lang, pushed around this whole parse) for the duration of the body
2408    /// parse, so a closure never resolves a free variable from its lexical surroundings.
2409    ///
2410    /// Runtime contexts resolve each annotation through the configured [`TypeResolver`] into
2411    /// its exact [`cel_runtime::ValueType`]. Syntax-only contexts retain the unresolved,
2412    /// span-carrying [`TypeExpr`] without consulting runtime tables. Both use the same
2413    /// [`parse_type_expression`](Self::parse_type_expression) production.
2414    ///
2415    /// - Precondition: the opening `|` (`params_already_closed == false`) or the combined `||`
2416    ///   token naming an empty parameter list (`params_already_closed == true`) has already been
2417    ///   consumed by [`is_primary_expression`](Self::is_primary_expression); `self.last_span` is
2418    ///   its span.
2419    ///
2420    /// # Errors
2421    ///
2422    /// Returns an error if a parameter name is reserved or duplicated, its `:`, its type,
2423    /// or the closing `|` is malformed
2424    /// or missing; if a runtime parameter names an unrecognized type or unsupported array
2425    /// element shape; if a used parameter lacks clone support; if the body is missing or malformed;
2426    /// or if this `ParserContext` implementation's `push_closure` rejects the closure (e.g.
2427    /// `DynSegmentContext` when the body doesn't produce exactly one value).
2428    ///
2429    /// - Postcondition: Returns `Ok(true)` on success; `Ok(false)` is never returned.
2430    fn is_closure_expression(&mut self, params_already_closed: bool) -> Result<bool> {
2431        let start_span = self.last_span;
2432        let mut params: Vec<(String, Span, Option<cel_runtime::ValueType>, TypeExpr)> = Vec::new();
2433        if !params_already_closed {
2434            loop {
2435                let name = self.expect_identifier("expected closure parameter name")?;
2436                let name_span = self.last_span;
2437                if !self.is_punctuation(":") {
2438                    return Err(self.error_at("expected ':' after closure parameter name"));
2439                }
2440                let ty_ast = self.parse_type_expression()?;
2441                let ty = if C::resolves_closure_types() {
2442                    Some(closure_param_type_from_type_expr(
2443                        &ty_ast,
2444                        self.type_resolver.as_ref(),
2445                    )?)
2446                } else {
2447                    None
2448                };
2449                params.push((name, name_span, ty, ty_ast));
2450                if self.is_punctuation(",") {
2451                    continue;
2452                }
2453                break;
2454            }
2455            if !self.is_punctuation("|") {
2456                return Err(self.error_at("expected ',' or closing '|'"));
2457            }
2458        }
2459
2460        let mut param_types = Vec::with_capacity(params.len());
2461        let mut ast_params = Vec::with_capacity(params.len());
2462        let mut param_table = HashMap::with_capacity(params.len());
2463        // Transfer each type tree once; runtime formals and syntax retain independent names.
2464        for (index, (name, name_span, ty, ty_ast)) in params.into_iter().enumerate() {
2465            if let Some(ty) = &ty {
2466                param_types.push(ty.clone());
2467            }
2468            ast_params.push(ClosureParam {
2469                name: name.clone(),
2470                name_span: ExprSpan {
2471                    start: name_span,
2472                    end: name_span,
2473                },
2474                type_expr: ty_ast,
2475            });
2476            param_table.insert(name, (index, ty));
2477        }
2478        if let Some(error) = ast::closure_parameter_diagnostics(&ast_params)
2479            .into_iter()
2480            .next()
2481        {
2482            return Err(error);
2483        }
2484        let isolated = self.op_lookup.isolate_scopes();
2485        self.op_lookup
2486            .push_scope(move |name, segment, arity, _span| {
2487                if arity != 0 {
2488                    return Ok(false);
2489                }
2490                let Some((idx, ty)) = param_table.get(name) else {
2491                    return Ok(false);
2492                };
2493                if let Some(ty) = ty {
2494                    ty.push_argument(segment, *idx)?;
2495                }
2496                Ok(true)
2497            });
2498
2499        let body_result = self.parse_nested_context(|p| p.is_expression());
2500        self.op_lookup.pop_scope();
2501        self.op_lookup.restore_scopes(isolated);
2502        let body = body_result?;
2503
2504        self.context
2505            .push_closure(param_types, ast_params, body, start_span)?;
2506        Ok(true)
2507    }
2508
2509    /// `if_expression = "if" expression "{" expression "}" [ "else" ( "{" expression "}" | if_expression ) ].`
2510    ///
2511    /// - Precondition: The `if` keyword has already been consumed by the caller; `if_span` is
2512    ///   its span.
2513    ///
2514    /// # Errors
2515    ///
2516    /// Returns an error if the condition is missing, if a `{` or `}` delimiter is missing,
2517    /// if the then-branch or else-branch expression is missing, or if the then and else
2518    /// branch types do not match (as detected by `join2`).
2519    ///
2520    /// - Postcondition: Returns `Ok(true)` on success; `Ok(false)` is never returned.
2521    fn is_if_expression(&mut self, if_span: Span) -> Result<bool> {
2522        if !self.with_restriction(ExpressionRestriction::NoStructLiteral, |p| {
2523            p.is_expression()
2524        })? {
2525            return Err(self.error_at("expected condition after `if`"));
2526        }
2527        match self.peek_token() {
2528            Some(Token::OpenDelim {
2529                delimiter: Delimiter::Brace,
2530                ..
2531            }) => {
2532                self.advance();
2533            }
2534            _ => return Err(self.error_at("expected `{` after if condition")),
2535        }
2536        let mut then_fragment = self.context.new_fragment();
2537        std::mem::swap(&mut self.context, &mut then_fragment);
2538        if !self.is_ordinary_expression()? {
2539            return Err(self.error_at("expected expression in then-branch"));
2540        }
2541        std::mem::swap(&mut self.context, &mut then_fragment);
2542        match self.peek_token() {
2543            Some(Token::CloseDelim {
2544                delimiter: Delimiter::Brace,
2545                ..
2546            }) => {
2547                self.advance();
2548            }
2549            _ => return Err(self.error_at("expected `}` after then-branch")),
2550        }
2551        let else_fragment = if self.is_keyword("else") {
2552            if self.is_keyword("if") {
2553                // else if: recursively parse another if_expression
2554                let elif_span = self.last_span;
2555                let mut fragment = self.context.new_fragment();
2556                std::mem::swap(&mut self.context, &mut fragment);
2557                self.is_if_expression(elif_span)?;
2558                std::mem::swap(&mut self.context, &mut fragment);
2559                Some(fragment)
2560            } else {
2561                // else { expr }
2562                match self.peek_token() {
2563                    Some(Token::OpenDelim {
2564                        delimiter: Delimiter::Brace,
2565                        ..
2566                    }) => {
2567                        self.advance();
2568                    }
2569                    _ => return Err(self.error_at("expected `{` or `if` after `else`")),
2570                }
2571                let mut fragment = self.context.new_fragment();
2572                std::mem::swap(&mut self.context, &mut fragment);
2573                if !self.is_ordinary_expression()? {
2574                    return Err(self.error_at("expected expression in else-branch"));
2575                }
2576                std::mem::swap(&mut self.context, &mut fragment);
2577                match self.peek_token() {
2578                    Some(Token::CloseDelim {
2579                        delimiter: Delimiter::Brace,
2580                        ..
2581                    }) => {
2582                        self.advance();
2583                    }
2584                    _ => return Err(self.error_at("expected `}` after else-branch")),
2585                }
2586                Some(fragment)
2587            }
2588        } else {
2589            // No `else`/`else if` in the source — each ParserContext::join2 impl decides what
2590            // this means (DynSegmentContext synthesizes an implicit `()` fragment;
2591            // AstContext records `None` directly on Expr::If).
2592            None
2593        };
2594        self.context
2595            .join2(then_fragment, else_fragment, if_span, self.last_span)
2596            .map_err(|e| ParseError::new_range(e.to_string(), if_span, self.last_span))?;
2597        Ok(true)
2598    }
2599}
2600
2601impl Parser<DynSegmentContext> {
2602    /// Parses one `expression` from the current token stream and returns the segment.
2603    ///
2604    /// Unlike [`parse_str`](Self::parse_str), this method does not require end-of-stream,
2605    /// allowing adam-lang to parse an expression embedded within a larger token stream.
2606    ///
2607    /// # Errors
2608    ///
2609    /// Returns an error if the input does not contain a valid `expression`.
2610    ///
2611    /// - Complexity: O(n) in the number of tokens in the expression.
2612    ///
2613    /// # Examples
2614    /// See [`Self::parse_tokens`] for executing an expression from an installed cursor.
2615    pub fn parse_expression(&mut self) -> Result<DynSegment> {
2616        self.parse_expression_ctx()
2617            .map(DynSegmentContext::into_inner)
2618    }
2619
2620    /// Parses one embedded expression with a header brace restriction.
2621    ///
2622    /// - Complexity: O(n) in the expression's token count.
2623    ///
2624    /// # Errors
2625    /// Returns syntax, type, or operation-resolution errors.
2626    ///
2627    /// # Examples
2628    /// See [`ExpressionRestriction`].
2629    pub fn parse_expression_restricted(
2630        &mut self,
2631        restriction: ExpressionRestriction,
2632    ) -> Result<DynSegment> {
2633        self.parse_expression_ctx_restricted(restriction)
2634            .map(DynSegmentContext::into_inner)
2635    }
2636
2637    /// Parses one `literal_pattern` from the current token stream and returns the segment.
2638    ///
2639    /// Unlike [`parse_str`](Self::parse_str), this method does not require end-of-stream.
2640    ///
2641    /// # Errors
2642    ///
2643    /// Returns an error if the input does not contain a valid `literal_pattern`.
2644    ///
2645    /// # Examples
2646    /// See [`Self::parse_literal_pattern_ctx`] for parsing and executing literal patterns.
2647    pub fn parse_literal_pattern(&mut self) -> Result<DynSegment> {
2648        self.parse_literal_pattern_ctx()
2649            .map(DynSegmentContext::into_inner)
2650    }
2651
2652    /// Parses a token stream into a [`DynSegment`].
2653    ///
2654    /// Sets the token source, runs the expression grammar, and returns the segment on success.
2655    ///
2656    /// # Errors
2657    ///
2658    /// Returns an error if the input does not contain a valid CEL expression.
2659    ///
2660    /// - Complexity: O(n) in the number of tokens.
2661    ///
2662    /// # Examples
2663    /// ```
2664    /// use cel_parser::{CELParser, OpLookup};
2665    /// use proc_macro2::TokenStream;
2666    /// let mut parser = CELParser::new(OpLookup::new());
2667    /// let tokens = "42".parse::<TokenStream>().unwrap();
2668    /// assert_eq!(parser.parse_tokens(tokens.into_iter()).unwrap().call0::<i32>().unwrap(), 42);
2669    /// parser.set_tokens("43".parse::<TokenStream>().unwrap().into_iter());
2670    /// assert_eq!(parser.parse_expression().unwrap().call0::<i32>().unwrap(), 43);
2671    /// ```
2672    pub fn parse_tokens(&mut self, tokens: TokenStreamIter) -> Result<DynSegment> {
2673        self.parse_tokens_ctx(tokens)
2674            .map(DynSegmentContext::into_inner)
2675    }
2676
2677    /// Parses a string into a [`DynSegment`].
2678    ///
2679    /// Tokenizes the string then parses; equivalent to
2680    /// `parse_tokens(TokenStream::from_str(s)?.into_iter())`.
2681    ///
2682    /// # Errors
2683    ///
2684    /// Returns an error on lex failure or if the input does not contain a valid CEL expression.
2685    ///
2686    /// - Complexity: O(n) in the length of `s`.
2687    ///
2688    /// # Examples
2689    /// ```
2690    /// use cel_parser::{CELParser, OpLookup};
2691    /// let mut segment = CELParser::new(OpLookup::new()).parse_str("40 + 2").unwrap();
2692    /// assert_eq!(segment.call0::<i32>().unwrap(), 42);
2693    /// ```
2694    pub fn parse_str(&mut self, s: &str) -> Result<DynSegment> {
2695        self.parse_str_ctx(s).map(DynSegmentContext::into_inner)
2696    }
2697}
2698
2699impl Parser<AstContext> {
2700    /// Parses one `expression` from the current token stream and returns the built [`Expr`].
2701    ///
2702    /// Unlike [`parse_str_ast`](Self::parse_str_ast), this method does not require
2703    /// end-of-stream, allowing adam-lang to parse an expression embedded within a larger token
2704    /// stream.
2705    ///
2706    /// # Errors
2707    ///
2708    /// Returns an error if the input does not contain a valid `expression`.
2709    ///
2710    /// - Complexity: O(n) in the number of tokens in the expression.
2711    ///
2712    /// # Examples
2713    /// See [`Self::parse_tokens_ast`] for retaining syntax from an installed cursor.
2714    pub fn parse_expression_ast(&mut self) -> Result<Expr> {
2715        self.parse_expression_ctx().map(AstContext::into_expr)
2716    }
2717
2718    /// Parses a token stream into an [`Expr`] tree.
2719    ///
2720    /// Sets the token source, runs the expression grammar, and returns the tree on success.
2721    ///
2722    /// # Errors
2723    ///
2724    /// Returns an error if the input does not contain a valid CEL expression.
2725    ///
2726    /// - Complexity: O(n) in the number of tokens.
2727    ///
2728    /// # Examples
2729    /// ```
2730    /// use cel_parser::{AstContext, Expr, Literal, OpLookup, Parser};
2731    /// use proc_macro2::TokenStream;
2732    /// let mut parser = Parser::<AstContext>::new(OpLookup::new());
2733    /// let tokens = "42".parse::<TokenStream>().unwrap();
2734    /// assert!(matches!(parser.parse_tokens_ast(tokens.into_iter()).unwrap(),
2735    ///     Expr::Literal { value: Literal::I32(42), .. }));
2736    /// parser.set_tokens("43".parse::<TokenStream>().unwrap().into_iter());
2737    /// assert!(matches!(parser.parse_expression_ast().unwrap(),
2738    ///     Expr::Literal { value: Literal::I32(43), .. }));
2739    /// ```
2740    pub fn parse_tokens_ast(&mut self, tokens: TokenStreamIter) -> Result<Expr> {
2741        self.parse_tokens_ctx(tokens).map(AstContext::into_expr)
2742    }
2743
2744    /// Parses a string into an [`Expr`] tree.
2745    ///
2746    /// Tokenizes the string then parses; equivalent to
2747    /// `parse_tokens_ast(TokenStream::from_str(s)?.into_iter())`.
2748    ///
2749    /// # Errors
2750    ///
2751    /// Returns an error on lex failure or if the input does not contain a valid CEL expression.
2752    ///
2753    /// - Complexity: O(n) in the length of `s`.
2754    ///
2755    /// # Examples
2756    ///
2757    /// ```rust
2758    /// use cel_parser::{AstContext, Expr, OpLookup, Parser};
2759    ///
2760    /// let mut parser = Parser::<AstContext>::new(OpLookup::new());
2761    /// let expr = parser.parse_str_ast("1 + 2").unwrap();
2762    /// assert!(matches!(expr, Expr::Op { .. }));
2763    /// ```
2764    pub fn parse_str_ast(&mut self, s: &str) -> Result<Expr> {
2765        self.parse_str_ctx(s).map(AstContext::into_expr)
2766    }
2767}
2768
2769#[cfg(test)]
2770mod tests {
2771    use super::*;
2772    use annotate_snippets::Renderer;
2773
2774    #[test]
2775    fn simple_expression() {
2776        let mut parser = CELParser::new(OpLookup::new());
2777        let result = parser.parse_str("10");
2778        assert!(result.is_ok());
2779        assert_eq!(result.unwrap().call0::<i32>().unwrap(), 10);
2780    }
2781
2782    #[test]
2783    fn parse_nested_context_compiles_an_independent_segment_without_disturbing_the_outer_one()
2784    -> anyhow::Result<()> {
2785        let mut parser = CELParser::new(OpLookup::new());
2786        parser.set_tokens(quote::quote! { 1 + 2 }.into_iter());
2787        // Start the outer expression: push a literal directly onto the (not-yet-swapped) context.
2788        parser
2789            .context
2790            .push_literal(100i32, proc_macro2::Span::call_site());
2791
2792        let nested = parser
2793            .parse_nested_context(|p| p.is_or_expression())
2794            .map_err(|e| anyhow::anyhow!("{}", e))?;
2795
2796        // The outer context still only has the one literal pushed before the nested parse.
2797        assert_eq!(parser.context.into_inner().call0::<i32>()?, 100);
2798        // The nested context has its own, independently-evaluated result.
2799        assert_eq!(nested.into_inner().call0::<i32>()?, 3);
2800        Ok(())
2801    }
2802
2803    #[test]
2804    fn unbalanced_delimiter_count_is_zero_before_any_parse() {
2805        let parser = CELParser::new(OpLookup::new());
2806        assert_eq!(parser.unbalanced_delimiter_count(), 0);
2807    }
2808
2809    #[test]
2810    fn unbalanced_delimiter_count_is_zero_after_a_successful_parse() {
2811        let mut parser = CELParser::new(OpLookup::new());
2812        parser.parse_str("(1 + 2)").unwrap();
2813        assert_eq!(parser.unbalanced_delimiter_count(), 0);
2814    }
2815
2816    #[test]
2817    fn unbalanced_delimiter_count_reports_a_brace_left_dangling_by_a_failed_if_expression() {
2818        let mut parser = CELParser::new(OpLookup::new());
2819        let err = match parser.parse_str("if true { }") {
2820            Err(e) => e,
2821            Ok(_) => panic!("an empty then-branch must fail to parse"),
2822        };
2823        assert!(err.message().contains("then-branch"));
2824        assert_eq!(parser.unbalanced_delimiter_count(), 1);
2825    }
2826
2827    #[test]
2828    fn unbalanced_delimiter_count_reports_a_paren_left_dangling_by_a_failed_tuple_literal() {
2829        let mut parser = CELParser::new(OpLookup::new());
2830        if parser.parse_str("(+)").is_ok() {
2831            panic!("`+` alone cannot start an expression");
2832        }
2833        assert_eq!(parser.unbalanced_delimiter_count(), 1);
2834    }
2835
2836    #[test]
2837    fn unbalanced_delimiter_count_resets_when_a_new_parse_begins() {
2838        let mut parser = CELParser::new(OpLookup::new());
2839        if parser.parse_str("(+)").is_ok() {
2840            panic!("`+` alone cannot start an expression");
2841        }
2842        assert_eq!(parser.unbalanced_delimiter_count(), 1);
2843        parser.parse_str("1 + 2").unwrap();
2844        assert_eq!(parser.unbalanced_delimiter_count(), 0);
2845    }
2846
2847    #[test]
2848    fn integer_literal_i32_suffix() {
2849        let mut parser = CELParser::new(OpLookup::new());
2850        let result = parser.parse_str("10i32");
2851        assert!(result.is_ok());
2852        assert_eq!(result.unwrap().call0::<i32>().unwrap(), 10);
2853    }
2854
2855    #[test]
2856    fn invalid_integer_suffix() {
2857        let mut parser = CELParser::new(OpLookup::new());
2858        let err = match parser.parse_str("10xyz") {
2859            Err(e) => e,
2860            Ok(_) => panic!("expected parse error for invalid integer suffix"),
2861        };
2862        assert!(err.message().contains("invalid integer literal suffix"));
2863        assert!(err.message().contains("xyz"));
2864    }
2865
2866    #[test]
2867    fn validate_literal_accepts_a_well_formed_integer() {
2868        let lit: lex_lexer::Literal = syn::parse_str("5i32").unwrap();
2869        assert!(validate_literal(&lit).is_ok());
2870    }
2871
2872    #[test]
2873    fn validate_literal_accepts_a_well_formed_float() {
2874        let lit: lex_lexer::Literal = syn::parse_str("1.5f64").unwrap();
2875        assert!(validate_literal(&lit).is_ok());
2876    }
2877
2878    #[test]
2879    fn validate_literal_rejects_an_unrecognized_integer_suffix() {
2880        let lit: lex_lexer::Literal = syn::parse_str("10xyz").unwrap();
2881        let err = validate_literal(&lit).expect_err("unrecognized suffix must be rejected");
2882        assert!(err.message().contains("invalid integer literal suffix"));
2883    }
2884
2885    #[test]
2886    fn validate_literal_rejects_an_out_of_range_integer() {
2887        let lit: lex_lexer::Literal = syn::parse_str("999i8").unwrap();
2888        let err = validate_literal(&lit).expect_err("out-of-range literal must be rejected");
2889        assert!(err.message().contains("invalid i8 literal"));
2890    }
2891
2892    #[test]
2893    fn float_literal() {
2894        let mut parser = CELParser::new(OpLookup::new());
2895        let result = parser.parse_str("42.14");
2896        assert!(result.is_ok());
2897        let value = result.unwrap().call0::<f64>().unwrap();
2898        assert!((value - 42.14).abs() < 1e-10);
2899    }
2900
2901    #[test]
2902    fn float_literal_f64_suffix() {
2903        let mut parser = CELParser::new(OpLookup::new());
2904        let result = parser.parse_str("42.14f64");
2905        assert!(result.is_ok());
2906        let value = result.unwrap().call0::<f64>().unwrap();
2907        assert!((value - 42.14).abs() < 1e-10);
2908    }
2909
2910    #[test]
2911    fn float_literal_f32_suffix() {
2912        let mut parser = CELParser::new(OpLookup::new());
2913        let result = parser.parse_str("42.14f32");
2914        assert!(result.is_ok());
2915        let value = result.unwrap().call0::<f32>().unwrap();
2916        assert!((value - 42.14f32).abs() < 1e-6);
2917    }
2918
2919    #[test]
2920    fn invalid_float_suffix() {
2921        let mut parser = CELParser::new(OpLookup::new());
2922        let err = match parser.parse_str("3.14xyz") {
2923            Err(e) => e,
2924            Ok(_) => panic!("expected parse error for invalid float suffix"),
2925        };
2926        assert!(err.message().contains("invalid float literal suffix"));
2927        assert!(err.message().contains("xyz"));
2928    }
2929
2930    #[test]
2931    fn boolean_literal() {
2932        let mut parser = CELParser::new(OpLookup::new());
2933        let result = parser.parse_str("true");
2934        assert!(result.is_ok());
2935        assert!(result.unwrap().call0::<bool>().unwrap());
2936    }
2937
2938    #[test]
2939    fn string_literal() {
2940        let mut parser = CELParser::new(OpLookup::new());
2941        let result = parser.parse_str(r#""hello""#);
2942        assert!(result.is_ok());
2943        assert_eq!(result.unwrap().call0::<String>().unwrap(), "hello");
2944    }
2945
2946    #[test]
2947    fn string_concatenation() {
2948        let mut parser = CELParser::new(OpLookup::new());
2949        let result = parser.parse_str(r#""a" + "b""#);
2950        assert!(result.is_ok());
2951        assert_eq!(result.unwrap().call0::<String>().unwrap(), "ab");
2952    }
2953
2954    #[test]
2955    fn incomplete_expression() {
2956        let mut parser = CELParser::new(OpLookup::new());
2957        let result = parser.parse_str("10 + 25 25");
2958        let err = match result {
2959            Ok(_) => panic!("expected parse error"),
2960            Err(e) => e,
2961        };
2962        assert_eq!(err.message(), "unexpected token");
2963    }
2964
2965    #[test]
2966    fn arithmetic_expression() {
2967        let mut parser = CELParser::new(OpLookup::new());
2968        let result = parser.parse_str("10 + 20 * 30");
2969        assert!(result.is_ok());
2970    }
2971
2972    #[test]
2973    fn parenthesized_expression() {
2974        let mut parser = CELParser::new(OpLookup::new());
2975        let result = parser.parse_str("(10 + 20) * 30");
2976        assert!(result.is_ok());
2977    }
2978
2979    #[test]
2980    fn unit_still_parses_as_unit() {
2981        let mut parser = CELParser::new(OpLookup::new());
2982        let mut seg = parser.parse_str("()").unwrap();
2983        seg.call0::<()>().unwrap();
2984    }
2985
2986    #[test]
2987    fn single_paren_expression_is_grouping_not_tuple() {
2988        let mut parser = CELParser::new(OpLookup::new());
2989        let mut seg = parser.parse_str("(1i32 + 2i32)").unwrap();
2990        assert_eq!(seg.call0::<i32>().unwrap(), 3);
2991    }
2992
2993    #[test]
2994    fn one_tuple_requires_trailing_comma() {
2995        let mut parser = CELParser::new(OpLookup::new());
2996        let mut seg = parser.parse_str("(1i32,)").unwrap();
2997        assert_eq!(seg.peek_tuple_arity(), Some(1));
2998        seg.tuple_index(0).unwrap();
2999        assert_eq!(seg.call0::<i32>().unwrap(), 1);
3000    }
3001
3002    #[test]
3003    fn two_element_tuple_no_trailing_comma() {
3004        let mut parser = CELParser::new(OpLookup::new());
3005        let seg: DynSegment = parser.parse_str(r#"("Hello", 42i32)"#).unwrap();
3006        assert_eq!(seg.peek_tuple_arity(), Some(2));
3007    }
3008
3009    #[test]
3010    fn trailing_comma_rejected_for_arity_two() {
3011        let mut parser = CELParser::new(OpLookup::new());
3012        let result = parser.parse_str("(1i32, 2i32,)");
3013        assert!(result.is_err(), "trailing comma is only valid for 1-tuples");
3014    }
3015
3016    #[test]
3017    fn missing_comma_between_elements_is_an_error() {
3018        let mut parser = CELParser::new(OpLookup::new());
3019        let result = parser.parse_str("(1i32 2i32)");
3020        assert!(result.is_err());
3021    }
3022
3023    #[test]
3024    fn index_first_element_of_tuple() {
3025        let mut parser = CELParser::new(OpLookup::new());
3026        let mut seg = parser.parse_str("(10i32, 20i32).0").unwrap();
3027        assert_eq!(seg.call0::<i32>().unwrap(), 10);
3028    }
3029
3030    #[test]
3031    fn tuple_element_can_be_arithmetic_expression() {
3032        let mut parser = CELParser::new(OpLookup::new());
3033        let mut seg = parser.parse_str("(1i32 + 2i32, 3i32).0").unwrap();
3034        assert_eq!(seg.call0::<i32>().unwrap(), 3);
3035    }
3036
3037    #[test]
3038    fn tuple_second_element_can_be_arithmetic_expression() {
3039        let mut parser = CELParser::new(OpLookup::new());
3040        let mut seg = parser.parse_str("(1i32 + 2i32, 3i32).1").unwrap();
3041        assert_eq!(seg.call0::<i32>().unwrap(), 3);
3042    }
3043
3044    #[test]
3045    fn tuple_ambient_start_correct_after_sibling_expression() {
3046        // Regression test: a fully-evaluated sibling subexpression earlier in
3047        // the same additive chain must not shift where the following tuple
3048        // literal thinks its elements land on the real stack.
3049        let mut parser = CELParser::new(OpLookup::new());
3050        let mut seg = parser
3051            .parse_str("(1i32 + 2i32) + 3i32 + (4i32, 5i32).0")
3052            .unwrap();
3053        assert_eq!(seg.call0::<i32>().unwrap(), 10);
3054    }
3055
3056    #[test]
3057    fn tuple_index_inside_if_then_branch() {
3058        // Regression test: `join2` pops the condition bool before running
3059        // the chosen fragment, so a tuple literal inside that fragment must
3060        // compute its layout as if that pop already happened.
3061        let mut parser = CELParser::new(OpLookup::new());
3062        let mut seg = parser
3063            .parse_str("if true { (10i32, 20i32).1 } else { 0i32 }")
3064            .unwrap();
3065        assert_eq!(seg.call0::<i32>().unwrap(), 20);
3066    }
3067
3068    #[test]
3069    fn tuple_index_inside_if_else_branch() {
3070        let mut parser = CELParser::new(OpLookup::new());
3071        let mut seg = parser
3072            .parse_str("if false { 0i32 } else { (10i32, 20i32).1 }")
3073            .unwrap();
3074        assert_eq!(seg.call0::<i32>().unwrap(), 20);
3075    }
3076
3077    #[test]
3078    fn tuple_index_inside_and_rhs() {
3079        let mut parser = CELParser::new(OpLookup::new());
3080        let mut seg = parser
3081            .parse_str("(1i32, 2i32).1 == 2i32 && (10i32, 20i32).1 == 20i32")
3082            .unwrap();
3083        assert!(seg.call0::<bool>().unwrap());
3084    }
3085
3086    #[test]
3087    fn tuple_index_inside_or_rhs() {
3088        let mut parser = CELParser::new(OpLookup::new());
3089        let mut seg = parser
3090            .parse_str("(1i32, 2i32).1 == 99i32 || (10i32, 20i32).1 == 20i32")
3091            .unwrap();
3092        assert!(seg.call0::<bool>().unwrap());
3093    }
3094
3095    #[test]
3096    fn tuple_containing_indexed_nested_tuple_result() {
3097        // Regression test: extracting an element from a misaligned nested
3098        // tuple must not leave the tuple's own leading padding as dead space
3099        // on the stack — otherwise a later tuple literal built from this
3100        // result computes its element offsets against the wrong ambient
3101        // start and reads garbage. (7, not 1: the dead gap's own marker
3102        // byte is hardcoded to value 1, so a value of 1 here would pass
3103        // even when reading the wrong offset.)
3104        let mut parser = CELParser::new(OpLookup::new());
3105        let mut seg = parser.parse_str("(0u8, (7u8, 2u64).0).1").unwrap();
3106        assert_eq!(seg.call0::<u8>().unwrap(), 7);
3107    }
3108
3109    #[test]
3110    fn index_second_element_of_tuple() {
3111        let mut parser = CELParser::new(OpLookup::new());
3112        let mut seg = parser.parse_str("(10i32, 20i32).1").unwrap();
3113        assert_eq!(seg.call0::<i32>().unwrap(), 20);
3114    }
3115
3116    #[test]
3117    fn indexing_combined_with_addition() {
3118        let mut parser = CELParser::new(OpLookup::new());
3119        let mut seg = parser.parse_str("5i32 + (0i32, 1i32).1").unwrap();
3120        assert_eq!(seg.call0::<i32>().unwrap(), 6);
3121    }
3122
3123    #[test]
3124    fn indexing_combined_with_addition_on_the_right() {
3125        let mut parser = CELParser::new(OpLookup::new());
3126        let mut seg = parser.parse_str("(0i32, 1i32).1 + 5i32").unwrap();
3127        assert_eq!(seg.call0::<i32>().unwrap(), 6);
3128    }
3129
3130    #[test]
3131    fn out_of_range_index_is_a_parse_error() {
3132        let mut parser = CELParser::new(OpLookup::new());
3133        let result = parser.parse_str("(1i32, 2i32).5");
3134        assert!(result.is_err());
3135    }
3136
3137    #[test]
3138    fn indexing_a_non_tuple_is_a_parse_error() {
3139        let mut parser = CELParser::new(OpLookup::new());
3140        let result = parser.parse_str("1i32.0");
3141        assert!(result.is_err());
3142    }
3143
3144    #[test]
3145    fn suffixed_index_is_a_parse_error() {
3146        let mut parser = CELParser::new(OpLookup::new());
3147        let result = parser.parse_str("(1i32, 2i32).0i32");
3148        assert!(result.is_err());
3149    }
3150
3151    #[test]
3152    fn chained_tuple_index_into_nested_tuple() {
3153        // `.1` selects the inner tuple `(10i32, 20i32)`, then `.0` selects its
3154        // first element. Source text `.1.0` tokenizes as a single float
3155        // literal `1.0`, which must be split back into the chained indices
3156        // `1` then `0` — using a shape/value where applying the indices to
3157        // the wrong operand or in the wrong order gives a different (wrong)
3158        // answer than 10 (e.g. swapping order would try `.0` on an i32 and
3159        // fail to parse; picking element 0 first would return "a" or 10
3160        // depending on order, not confusably 10 either way, so pick values
3161        // that make a mix-up obvious).
3162        let mut parser = CELParser::new(OpLookup::new());
3163        let mut seg = parser
3164            .parse_str(r#"("a", (10i32, 20i32)).1.0"#)
3165            .expect("chained .1.0 index should parse");
3166        assert_eq!(seg.call0::<i32>().unwrap(), 10);
3167    }
3168
3169    #[test]
3170    fn chained_tuple_index_suffixed_second_part_is_a_parse_error() {
3171        // The suffix lands on the whole `0.1i32` float token; the existing
3172        // unsuffixed-integer rule must still reject it.
3173        let mut parser = CELParser::new(OpLookup::new());
3174        let result = parser.parse_str("(1i32, 2i32).0.1i32");
3175        assert!(result.is_err());
3176    }
3177
3178    #[test]
3179    fn chained_tuple_index_scientific_notation_is_a_parse_error_not_a_panic() {
3180        // `1e2` normalizes to digits with no '.' at all (scientific notation),
3181        // unlike ordinary decimal floats like `0.1` — must be a graceful parse
3182        // error, not a panic on the assumption that '.' is always present.
3183        let mut parser = CELParser::new(OpLookup::new());
3184        let result = parser.parse_str("(1i32, 2i32).1e2");
3185        assert!(result.is_err());
3186    }
3187
3188    /// Retains malformed selector intervals even after the token has been consumed.
3189    #[test]
3190    fn invalid_tuple_selector_conversion_retains_its_token_span() {
3191        let mut parser = CELParser::new(OpLookup::new());
3192        for (source, expected) in [
3193            ("(1.0f64,).184467440737095516160", 10..31),
3194            ("(1.0f64,).0.1e0", 10..15),
3195            ("(1.0f64,).184467440737095516160, host", 10..31),
3196            ("(1.0f64,).0.1e0, host", 10..15),
3197        ] {
3198            let error = parser.parse_str(source).err().expect("invalid selector");
3199            assert_eq!(
3200                SourceSpan::from_proc_macro2_range(
3201                    error.span(),
3202                    error.end_span().unwrap_or(error.span())
3203                )
3204                .to_byte_range(source),
3205                expected,
3206                "{source}"
3207            );
3208        }
3209    }
3210
3211    #[test]
3212    fn complex_expression() {
3213        let mut parser = CELParser::new(OpLookup::new());
3214        let result = parser.parse_str("10 + 20 * (30 - 5) / 2");
3215        assert!(result.is_ok());
3216    }
3217
3218    #[test]
3219    fn logical_expression() {
3220        let mut parser = CELParser::new(OpLookup::new());
3221        let result = parser.parse_str("true && false || true");
3222        assert!(result.is_ok());
3223    }
3224
3225    #[test]
3226    fn comparison_expression() {
3227        let mut parser = CELParser::new(OpLookup::new());
3228        let result = parser.parse_str("10 == 20 && 30 > 40");
3229        assert!(result.is_ok());
3230    }
3231
3232    #[test]
3233    fn bitwise_expression() {
3234        let mut parser = CELParser::new(OpLookup::new());
3235        let result = parser.parse_str("1 | 2 & 3 ^ 4");
3236        assert!(result.is_ok());
3237    }
3238
3239    #[test]
3240    fn shift_expression() {
3241        let mut parser = CELParser::new(OpLookup::new());
3242        let result = parser.parse_str("8u32 << 2u32 + 16u32 >> 1u32");
3243        assert!(result.is_ok());
3244    }
3245
3246    #[test]
3247    fn unary_expression() {
3248        let mut parser = CELParser::new(OpLookup::new());
3249        let result = parser.parse_str("-10 + -20");
3250        assert!(result.is_ok());
3251    }
3252
3253    #[test]
3254    fn double_negation() {
3255        let mut parser = CELParser::new(OpLookup::new());
3256        let result = parser.parse_str("!!true");
3257        assert!(
3258            result.is_ok(),
3259            "Failed to parse !!true: {}",
3260            result.err().unwrap()
3261        );
3262        assert!(result.unwrap().call0::<bool>().unwrap());
3263    }
3264
3265    #[test]
3266    fn double_minus() {
3267        let mut parser = CELParser::new(OpLookup::new());
3268        let result = parser.parse_str("--5");
3269        assert!(
3270            result.is_ok(),
3271            "Failed to parse --5: {}",
3272            result.err().unwrap()
3273        );
3274        assert_eq!(result.unwrap().call0::<i32>().unwrap(), 5);
3275    }
3276
3277    #[test]
3278    fn chained_unary_expression() {
3279        let mut parser = CELParser::new(OpLookup::new());
3280        let result = parser.parse_str("!!false || !!true");
3281        if let Err(ref e) = result {
3282            eprintln!("Error: {:?}", e);
3283            eprintln!("Error message: {}", e.message());
3284        }
3285        assert!(result.is_ok(), "Failed to parse: {}", result.err().unwrap());
3286        assert!(result.unwrap().call0::<bool>().unwrap());
3287    }
3288
3289    #[test]
3290    fn cast_expression_parses_and_executes() {
3291        let mut parser = CELParser::new(OpLookup::new());
3292        let result = parser.parse_str("1024i32 as f64");
3293        assert_eq!(result.unwrap().call0::<f64>().unwrap(), 1024.0);
3294    }
3295
3296    #[test]
3297    fn cast_expression_chains_left_to_right() {
3298        // `x as i32 as f64` must apply left-to-right: `(x as i32) as f64`, not
3299        // `x as (i32 as f64)` (which isn't even expressible - a cast target is a bare
3300        // identifier, not another cast expression).
3301        let mut parser = CELParser::new(OpLookup::new());
3302        let result = parser.parse_str("1.5f64 as i32 as f64");
3303        assert_eq!(result.unwrap().call0::<f64>().unwrap(), 1.0);
3304    }
3305
3306    #[test]
3307    fn unary_negation_binds_tighter_than_cast() {
3308        // `-x as u32` must parse as `(-x) as u32` (matching Rust): the negative operand hits the
3309        // checked int->int cast's own out-of-range check at execution, not a "no operation `-`
3310        // for `u32`" error at parse time, which is what parsing it as `-(x as u32)` would
3311        // produce instead - `u32` has no registered unary negation, matching Rust (u32 doesn't
3312        // implement `Neg`).
3313        let mut parser = CELParser::new(OpLookup::new());
3314        let mut seg = parser.parse_str("-1i32 as u32").expect(
3315            "parses: unary negation is registered for i32, and i32 -> u32 is a valid cast pair",
3316        );
3317        let err = seg.call0::<u32>().unwrap_err();
3318        // The cast's own error is wrapped in a `SpanContext` (see `span_err`), which becomes the
3319        // outermost context and so is what plain `Display`/`to_string()` shows - the underlying
3320        // "does not fit" message is further down the `anyhow` chain, hence `{:#}`.
3321        let full = format!("{err:#}");
3322        assert!(
3323            full.contains("does not fit"),
3324            "expected an out-of-range cast error, got: {full}"
3325        );
3326    }
3327
3328    #[test]
3329    fn cast_binds_tighter_than_multiplicative_operators() {
3330        // `*` requires homogeneous operand types, so if `as` binds tighter than `*` (matching
3331        // Rust), this parses as `2i32 * (3i32 as f64)` - an `i32 * f64` mismatch, which this
3332        // grammar's eager, statically-typed segment construction rejects immediately at parse
3333        // time (operator/operand-type matching isn't deferred to execution, unlike a cast's own
3334        // value-range check). If `as` bound looser instead, this would parse as
3335        // `(2i32 * 3i32) as f64` and succeed.
3336        let mut parser = CELParser::new(OpLookup::new());
3337        let result = parser.parse_str("2i32 * 3i32 as f64");
3338        assert!(result.is_err(), "expected a type-mismatch parse error");
3339    }
3340
3341    #[test]
3342    fn cast_binds_tighter_than_additive_operators() {
3343        // Same reasoning as the multiplicative case: `1i32 + 2i32 as f64` must parse as
3344        // `1i32 + (2i32 as f64)` (an `i32 + f64` mismatch, a parse-time error), not
3345        // `(1i32 + 2i32) as f64` (which would succeed).
3346        let mut parser = CELParser::new(OpLookup::new());
3347        let result = parser.parse_str("1i32 + 2i32 as f64");
3348        assert!(result.is_err(), "expected a type-mismatch parse error");
3349    }
3350
3351    #[test]
3352    fn invalid_expression() {
3353        let mut parser = CELParser::new(OpLookup::new());
3354        let result = parser.parse_str("+");
3355        assert!(result.is_err());
3356    }
3357
3358    #[test]
3359    fn error_formatting() {
3360        let source = "10 + 20 30";
3361        let mut parser = CELParser::new(OpLookup::new());
3362        let result = parser.parse_str(source);
3363
3364        assert!(result.is_err());
3365
3366        let err = match &result {
3367            Ok(_) => panic!("expected parse error"),
3368            Err(e) => e,
3369        };
3370        assert_eq!(err.message(), "unexpected token");
3371
3372        let formatted = err.format_rustc_style(source, "test.cel", 1u32, &Renderer::plain());
3373        assert!(formatted.contains("error: unexpected token"));
3374        assert!(formatted.contains("test.cel:1:"));
3375        assert!(formatted.contains("1 | 10 + 20 30"));
3376        assert!(formatted.contains("^"));
3377    }
3378
3379    #[test]
3380    fn error_formatting_with_line_offset() {
3381        let source = "10 + 20 30";
3382        let mut parser = CELParser::new(OpLookup::new());
3383        let result = parser.parse_str(source);
3384
3385        assert!(result.is_err());
3386
3387        let err = match &result {
3388            Ok(_) => panic!("expected parse error"),
3389            Err(e) => e,
3390        };
3391        let formatted = err.format_rustc_style(source, "large_file.rs", 42u32, &Renderer::plain());
3392        assert!(formatted.contains("error: unexpected token"));
3393        assert!(formatted.contains("large_file.rs:42:"));
3394        assert!(formatted.contains("42 | 10 + 20 30"));
3395        assert!(formatted.contains("^"));
3396    }
3397
3398    #[test]
3399    fn print_error_formatting() {
3400        let line = line!() + 1;
3401        let source = r#"
3402
3403         10 + 20  30 // Unexpected token
3404
3405     "#;
3406
3407        let mut parser = CELParser::new(OpLookup::new());
3408        let result = parser.parse_str(source);
3409
3410        assert!(result.is_err(), "Expected parsing to fail");
3411
3412        let err = match &result {
3413            Ok(_) => panic!("expected parse error"),
3414            Err(e) => e,
3415        };
3416        eprintln!(
3417            "DEBUG: span.start.line = {}, span.start.column = {}",
3418            err.span().start().line,
3419            err.span().start().column
3420        );
3421
3422        let formatted_error = err.format_rustc_style(source, file!(), line, &Renderer::plain());
3423        println!("{}", formatted_error);
3424
3425        let formatted = formatted_error;
3426
3427        let expected_line = line + 2;
3428
3429        assert!(
3430            formatted.contains("error: unexpected token"),
3431            "Should contain error message, got: {}",
3432            formatted
3433        );
3434        assert!(
3435            formatted.contains(&format!("{}:", expected_line)),
3436            "Should show error on line {}, got: {}",
3437            expected_line,
3438            formatted
3439        );
3440        assert!(
3441            formatted.contains("30"),
3442            "Should show the source line with '30', got: {}",
3443            formatted
3444        );
3445        assert!(
3446            formatted.contains("^"),
3447            "Should have carets pointing to error, got: {}",
3448            formatted
3449        );
3450    }
3451
3452    #[test]
3453    fn test_addition_execution() -> anyhow::Result<()> {
3454        let mut parser = CELParser::new(OpLookup::new());
3455        let mut segment = parser
3456            .parse_str("10 + 20")
3457            .map_err(|e| anyhow::anyhow!("{}", e))?;
3458        let result = segment.call0::<i32>()?;
3459        assert_eq!(result, 30);
3460        Ok(())
3461    }
3462
3463    #[test]
3464    fn test_multiplication_execution() -> anyhow::Result<()> {
3465        let mut parser = CELParser::new(OpLookup::new());
3466        let mut segment = parser
3467            .parse_str("3 * 7")
3468            .map_err(|e| anyhow::anyhow!("{}", e))?;
3469        let result = segment.call0::<i32>()?;
3470        assert_eq!(result, 21);
3471        Ok(())
3472    }
3473
3474    #[test]
3475    fn test_complex_arithmetic_execution() -> anyhow::Result<()> {
3476        let mut parser = CELParser::new(OpLookup::new());
3477        let mut segment = parser
3478            .parse_str("10 + 20 * 3")
3479            .map_err(|e| anyhow::anyhow!("{}", e))?;
3480        let result = segment.call0::<i32>()?;
3481        assert_eq!(result, 70);
3482        Ok(())
3483    }
3484
3485    #[test]
3486    fn test_parenthesized_arithmetic_execution() -> anyhow::Result<()> {
3487        let mut parser = CELParser::new(OpLookup::new());
3488        let mut segment = parser
3489            .parse_str("(10 + 20) * 3")
3490            .map_err(|e| anyhow::anyhow!("{}", e))?;
3491        let result = segment.call0::<i32>()?;
3492        assert_eq!(result, 90);
3493        Ok(())
3494    }
3495
3496    #[test]
3497    fn test_comparison_execution() -> anyhow::Result<()> {
3498        let mut parser = CELParser::new(OpLookup::new());
3499        let mut segment = parser
3500            .parse_str("10 < 20")
3501            .map_err(|e| anyhow::anyhow!("{}", e))?;
3502        let result = segment.call0::<bool>()?;
3503        assert!(result);
3504        Ok(())
3505    }
3506
3507    #[test]
3508    fn test_logical_and_execution() -> anyhow::Result<()> {
3509        let mut parser = CELParser::new(OpLookup::new());
3510        let mut segment = parser
3511            .parse_str("true && false")
3512            .map_err(|e| anyhow::anyhow!("{}", e))?;
3513        let result = segment.call0::<bool>()?;
3514        assert!(!result);
3515        Ok(())
3516    }
3517
3518    #[test]
3519    fn test_unary_negation_execution() -> anyhow::Result<()> {
3520        let mut parser = CELParser::new(OpLookup::new());
3521        let mut segment = parser
3522            .parse_str("-42")
3523            .map_err(|e| anyhow::anyhow!("{}", e))?;
3524        let result = segment.call0::<i32>()?;
3525        assert_eq!(result, -42);
3526        Ok(())
3527    }
3528
3529    #[test]
3530    fn test_logical_not_execution() -> anyhow::Result<()> {
3531        let mut parser = CELParser::new(OpLookup::new());
3532        let mut segment = parser
3533            .parse_str("!true")
3534            .map_err(|e| anyhow::anyhow!("{}", e))?;
3535        let result = segment.call0::<bool>()?;
3536        assert!(!result);
3537        Ok(())
3538    }
3539
3540    #[test]
3541    fn test_u32_addition_execution() -> anyhow::Result<()> {
3542        let mut parser = CELParser::new(OpLookup::new());
3543        let mut segment = parser
3544            .parse_str("10u32 + 20u32")
3545            .map_err(|e| anyhow::anyhow!("{}", e))?;
3546        let result = segment.call0::<u32>()?;
3547        assert_eq!(result, 30);
3548        Ok(())
3549    }
3550
3551    #[test]
3552    fn test_identifier_with_scope() -> anyhow::Result<()> {
3553        let mut lookup = OpLookup::new();
3554        lookup.push_scope(|name, segment, num_operands, _span| {
3555            if num_operands == 0 {
3556                match name {
3557                    "x" => {
3558                        segment.op0(|| 10i32);
3559                        Ok(true)
3560                    }
3561                    "y" => {
3562                        segment.op0(|| 20i32);
3563                        Ok(true)
3564                    }
3565                    _ => Ok(false),
3566                }
3567            } else {
3568                Ok(false)
3569            }
3570        });
3571        let mut parser = CELParser::new(lookup);
3572        let mut segment = parser
3573            .parse_str("x + y")
3574            .map_err(|e| anyhow::anyhow!("{}", e))?;
3575        let result = segment.call0::<i32>()?;
3576        assert_eq!(result, 30);
3577        Ok(())
3578    }
3579
3580    #[test]
3581    fn test_undefined_identifier_error() {
3582        let mut parser = CELParser::new(OpLookup::new());
3583        let result = parser.parse_str("undefined_var + 10");
3584
3585        assert!(result.is_err());
3586        if let Err(e) = result {
3587            let error_msg = format!("{:?}", e);
3588            assert!(
3589                error_msg.contains("undefined identifier: `undefined_var`"),
3590                "Error message should contain 'undefined identifier: `undefined_var`', got: {}",
3591                error_msg
3592            );
3593        }
3594    }
3595
3596    #[test]
3597    fn test_identifier_scope_error_propagated() {
3598        let mut lookup = OpLookup::new();
3599        lookup.push_scope(|name, _segment, num_operands, _span| {
3600            if name == "bad_id" && num_operands == 0 {
3601                return Err(anyhow::anyhow!("custom identifier rejected"));
3602            }
3603            Ok(false)
3604        });
3605        let mut parser = CELParser::new(lookup);
3606        let err = match parser.parse_str("bad_id + 1") {
3607            Err(e) => e,
3608            Ok(_) => panic!("scope Err should propagate, not become Undefined identifier"),
3609        };
3610        assert!(
3611            err.message().contains("custom identifier rejected"),
3612            "expected scope error message, got: {}",
3613            err.message()
3614        );
3615        assert!(
3616            !err.message().contains("undefined identifier:"),
3617            "scope Err must not be rewritten as undefined identifier"
3618        );
3619    }
3620
3621    #[test]
3622    fn test_undefined_identifier_error_formatting() {
3623        let input = "undefined_var + 10";
3624        let mut parser = CELParser::new(OpLookup::new());
3625        let result = parser.parse_str(input);
3626
3627        assert!(result.is_err());
3628        if let Err(e) = result {
3629            let formatted_error = e.format_rustc_style(input, "test.cel", 1, &Renderer::plain());
3630            assert!(formatted_error.contains("undefined identifier"));
3631            assert!(formatted_error.contains("undefined_var"));
3632            assert!(formatted_error.contains("test.cel"));
3633        }
3634    }
3635
3636    #[test]
3637    fn test_float_arithmetic_execution() -> anyhow::Result<()> {
3638        let mut parser = CELParser::new(OpLookup::new());
3639        let mut segment = parser
3640            .parse_str("3.5 * 2.0")
3641            .map_err(|e| anyhow::anyhow!("{}", e))?;
3642        let result = segment.call0::<f64>()?;
3643        assert_eq!(result, 7.0);
3644        Ok(())
3645    }
3646
3647    #[test]
3648    fn call_empty_arg_list() -> anyhow::Result<()> {
3649        let mut lookup = OpLookup::new();
3650        lookup.push_scope(
3651            |name, segment, num_operands, _span| match (name, num_operands) {
3652                ("f", 0) => {
3653                    segment.op0(|| 0i32);
3654                    Ok(true)
3655                }
3656                ("()", 1) => {
3657                    segment.op1(|_callee: i32| 99i32)?;
3658                    Ok(true)
3659                }
3660                _ => Ok(false),
3661            },
3662        );
3663        let mut parser = CELParser::new(lookup);
3664        let mut segment = parser
3665            .parse_str("f()")
3666            .map_err(|e| anyhow::anyhow!("{}", e))?;
3667        assert_eq!(segment.call0::<i32>()?, 99);
3668        Ok(())
3669    }
3670
3671    #[test]
3672    fn call_single_arg() -> anyhow::Result<()> {
3673        let mut lookup = OpLookup::new();
3674        lookup.push_scope(
3675            |name, segment, num_operands, _span| match (name, num_operands) {
3676                ("f", 0) => {
3677                    segment.op0(|| 0i32);
3678                    Ok(true)
3679                }
3680                ("()", 2) => {
3681                    segment.op2(|_callee: i32, arg: i32| arg)?;
3682                    Ok(true)
3683                }
3684                _ => Ok(false),
3685            },
3686        );
3687        let mut parser = CELParser::new(lookup);
3688        let mut segment = parser
3689            .parse_str("f(42)")
3690            .map_err(|e| anyhow::anyhow!("{}", e))?;
3691        assert_eq!(segment.call0::<i32>()?, 42);
3692        Ok(())
3693    }
3694
3695    #[test]
3696    fn call_multiple_args() -> anyhow::Result<()> {
3697        let mut lookup = OpLookup::new();
3698        lookup.push_scope(
3699            |name, segment, num_operands, _span| match (name, num_operands) {
3700                ("f", 0) => {
3701                    segment.op0(|| 0i32);
3702                    Ok(true)
3703                }
3704                ("()", 3) => {
3705                    segment.op3(|_callee: i32, arg1: i32, arg2: i32| arg1 + arg2)?;
3706                    Ok(true)
3707                }
3708                _ => Ok(false),
3709            },
3710        );
3711        let mut parser = CELParser::new(lookup);
3712        let mut segment = parser
3713            .parse_str("f(10, 32)")
3714            .map_err(|e| anyhow::anyhow!("{}", e))?;
3715        assert_eq!(segment.call0::<i32>()?, 42);
3716        Ok(())
3717    }
3718
3719    #[test]
3720    fn call_missing_closing_paren() {
3721        let mut lookup = OpLookup::new();
3722        lookup.push_scope(
3723            |name, segment, num_operands, _span| match (name, num_operands) {
3724                ("f", 0) => {
3725                    segment.op0(|| 0i32);
3726                    Ok(true)
3727                }
3728                _ => Ok(false),
3729            },
3730        );
3731        let mut parser = CELParser::new(lookup);
3732        let err = match parser.parse_str("f(42 43)") {
3733            Err(e) => e,
3734            Ok(_) => panic!("expected parse error for missing closing parenthesis"),
3735        };
3736        assert_eq!(err.message(), "expected closing parenthesis");
3737    }
3738
3739    #[test]
3740    fn call_trailing_comma() {
3741        let mut lookup = OpLookup::new();
3742        lookup.push_scope(
3743            |name, segment, num_operands, _span| match (name, num_operands) {
3744                ("f", 0) => {
3745                    segment.op0(|| 0i32);
3746                    Ok(true)
3747                }
3748                _ => Ok(false),
3749            },
3750        );
3751        let mut parser = CELParser::new(lookup);
3752        let err = match parser.parse_str("f(42,)") {
3753            Err(e) => e,
3754            Ok(_) => panic!("expected parse error for trailing comma"),
3755        };
3756        assert_eq!(err.message(), "expected expression after comma");
3757    }
3758
3759    #[test]
3760    fn call_undefined_call_op() {
3761        let mut lookup = OpLookup::new();
3762        lookup.push_scope(
3763            |name, segment, num_operands, _span| match (name, num_operands) {
3764                ("f", 0) => {
3765                    segment.op0(|| 0i32);
3766                    Ok(true)
3767                }
3768                _ => Ok(false),
3769            },
3770        );
3771        let mut parser = CELParser::new(lookup);
3772        let err = match parser.parse_str("f()") {
3773            Err(e) => e,
3774            Ok(_) => panic!("expected error when () operator is not registered"),
3775        };
3776        assert!(
3777            err.message().starts_with("no operation"),
3778            "error should report no operation found, got: {}",
3779            err.message()
3780        );
3781    }
3782
3783    #[test]
3784    fn call_leading_comma_reports_expected_expression_at_the_comma() {
3785        let mut lookup = OpLookup::new();
3786        lookup.push_scope(
3787            |name, segment, num_operands, _span| match (name, num_operands) {
3788                ("f", 0) => {
3789                    segment.op0(|| 0i32);
3790                    Ok(true)
3791                }
3792                _ => Ok(false),
3793            },
3794        );
3795        let mut parser = CELParser::new(lookup);
3796        let err = match parser.parse_str("f(,5)") {
3797            Err(e) => e,
3798            Ok(_) => panic!("expected parse error for leading comma"),
3799        };
3800        assert_eq!(err.message(), "expected expression");
3801    }
3802
3803    #[test]
3804    fn call_chained() -> anyhow::Result<()> {
3805        let mut lookup = OpLookup::new();
3806        lookup.push_scope(
3807            |name, segment, num_operands, _span| match (name, num_operands) {
3808                ("f", 0) => {
3809                    segment.op0(|| 0i32);
3810                    Ok(true)
3811                }
3812                ("()", 1) => {
3813                    segment.op1r(|callee: i32| {
3814                        callee
3815                            .checked_add(7)
3816                            .ok_or_else(|| anyhow::anyhow!("overflow"))
3817                    })?;
3818                    Ok(true)
3819                }
3820                _ => Ok(false),
3821            },
3822        );
3823        let mut parser = CELParser::new(lookup);
3824        let mut segment = parser
3825            .parse_str("f()()")
3826            .map_err(|e| anyhow::anyhow!("{}", e))?;
3827        assert_eq!(segment.call0::<i32>()?, 14);
3828        Ok(())
3829    }
3830
3831    #[test]
3832    fn op_type_mismatch_error_spans_full_expression() {
3833        let source = r#""Hello" + 32.0"#;
3834        let mut parser = CELParser::new(OpLookup::new());
3835        let err = match parser.parse_str(source) {
3836            Err(e) => e,
3837            Ok(_) => panic!("expected parse error for type mismatch"),
3838        };
3839        assert!(
3840            err.message().starts_with("no operation"),
3841            "expected 'no operation' prefix, got: {}",
3842            err.message()
3843        );
3844        let end_span = err.end_span().expect("op-lookup errors carry an end span");
3845        assert!(
3846            end_span.end().column >= 14,
3847            "end span should reach the end of 32.0 (expected end.column >= 14, got {})",
3848            end_span.end().column
3849        );
3850    }
3851
3852    #[test]
3853    fn and_short_circuits_on_false() {
3854        // Without short-circuit the RHS executes and division-by-zero errors.
3855        // With short-circuit the RHS fragment is skipped, returning false directly.
3856        let mut parser = CELParser::new(OpLookup::new());
3857        let mut segment = parser
3858            .parse_str("false && (1i32 / 0i32 == 0i32)")
3859            .expect("should parse");
3860        assert!(!segment.call0::<bool>().unwrap());
3861    }
3862
3863    #[test]
3864    fn and_evaluates_rhs_when_lhs_true() {
3865        let mut parser = CELParser::new(OpLookup::new());
3866        let mut segment = parser.parse_str("true && false").expect("should parse");
3867        assert!(!segment.call0::<bool>().unwrap());
3868    }
3869
3870    #[test]
3871    fn and_chained_short_circuits() {
3872        let mut parser = CELParser::new(OpLookup::new());
3873        let mut segment = parser
3874            .parse_str("true && false && (1i32 / 0i32 == 0i32)")
3875            .expect("should parse");
3876        assert!(!segment.call0::<bool>().unwrap());
3877    }
3878
3879    #[test]
3880    fn and_lhs_type_error() {
3881        // LHS is i32, not bool — join2 must reject it at parse time.
3882        let mut parser = CELParser::new(OpLookup::new());
3883        let err = match parser.parse_str("1i32 && true") {
3884            Err(e) => e,
3885            Ok(_) => panic!("lhs i32 should fail for &&"),
3886        };
3887        assert!(err.end_span().is_some());
3888    }
3889
3890    #[test]
3891    fn or_lhs_type_error() {
3892        // LHS is i32, not bool — join2 must reject it at parse time.
3893        let mut parser = CELParser::new(OpLookup::new());
3894        let err = match parser.parse_str("1i32 || true") {
3895            Err(e) => e,
3896            Ok(_) => panic!("lhs i32 should fail for ||"),
3897        };
3898        assert!(err.end_span().is_some());
3899    }
3900
3901    #[test]
3902    fn or_short_circuits_on_true() {
3903        // Without short-circuit the RHS executes and division-by-zero errors.
3904        // With short-circuit the RHS fragment is skipped, returning true directly.
3905        let mut parser = CELParser::new(OpLookup::new());
3906        let mut segment = parser
3907            .parse_str("true || (1i32 / 0i32 == 0i32)")
3908            .expect("should parse");
3909        assert!(segment.call0::<bool>().unwrap());
3910    }
3911
3912    #[test]
3913    fn or_evaluates_rhs_when_lhs_false() {
3914        let mut parser = CELParser::new(OpLookup::new());
3915        let mut segment = parser.parse_str("false || true").expect("should parse");
3916        assert!(segment.call0::<bool>().unwrap());
3917    }
3918
3919    #[test]
3920    fn or_chained() {
3921        let mut parser = CELParser::new(OpLookup::new());
3922        let mut segment = parser
3923            .parse_str("true || false || false")
3924            .expect("should parse");
3925        assert!(segment.call0::<bool>().unwrap());
3926    }
3927
3928    #[test]
3929    fn if_true_branch_selected() {
3930        let mut parser = CELParser::new(OpLookup::new());
3931        let mut segment = parser
3932            .parse_str("if true { 1i32 } else { 2i32 }")
3933            .expect("should parse");
3934        assert_eq!(segment.call0::<i32>().unwrap(), 1);
3935    }
3936
3937    #[test]
3938    fn if_false_branch_selected() {
3939        let mut parser = CELParser::new(OpLookup::new());
3940        let mut segment = parser
3941            .parse_str("if false { 1i32 } else { 2i32 }")
3942            .expect("should parse");
3943        assert_eq!(segment.call0::<i32>().unwrap(), 2);
3944    }
3945
3946    #[test]
3947    fn if_else_if_first_branch() {
3948        let mut parser = CELParser::new(OpLookup::new());
3949        let mut segment = parser
3950            .parse_str("if true { 1i32 } else if false { 2i32 } else { 3i32 }")
3951            .expect("should parse");
3952        assert_eq!(segment.call0::<i32>().unwrap(), 1);
3953    }
3954
3955    #[test]
3956    fn if_else_if_middle_branch() {
3957        let mut parser = CELParser::new(OpLookup::new());
3958        let mut segment = parser
3959            .parse_str("if false { 1i32 } else if true { 2i32 } else { 3i32 }")
3960            .expect("should parse");
3961        assert_eq!(segment.call0::<i32>().unwrap(), 2);
3962    }
3963
3964    #[test]
3965    fn if_else_if_last_branch() {
3966        let mut parser = CELParser::new(OpLookup::new());
3967        let mut segment = parser
3968            .parse_str("if false { 1i32 } else if false { 2i32 } else { 3i32 }")
3969            .expect("should parse");
3970        assert_eq!(segment.call0::<i32>().unwrap(), 3);
3971    }
3972
3973    #[test]
3974    fn if_omitted_else_unit_branch() {
3975        let mut parser = CELParser::new(OpLookup::new());
3976        let mut segment = parser.parse_str("if true { () }").expect("should parse");
3977        segment.call0::<()>().expect("should execute");
3978    }
3979
3980    #[test]
3981    fn if_omitted_else_rejects_non_unit_then() {
3982        // then-branch returns i32, implicit else returns () — types must match.
3983        let mut parser = CELParser::new(OpLookup::new());
3984        assert!(parser.parse_str("if false { 1i32 }").is_err());
3985    }
3986
3987    #[test]
3988    fn if_branch_type_mismatch_is_error() {
3989        let mut parser = CELParser::new(OpLookup::new());
3990        assert!(parser.parse_str("if true { 1i32 } else { true }").is_err());
3991    }
3992
3993    #[test]
3994    fn if_branch_tuple_arity_mismatch_is_error() {
3995        // Regression test: a flat erased tuple identity
3996        // marker type, so a naive type_id comparison would accept branches
3997        // with genuinely different tuple shapes — join2 must compare shapes,
3998        // not just the marker type, and reject this.
3999        let mut parser = CELParser::new(OpLookup::new());
4000        let result = parser.parse_str("if false { (1i32, 2i32) } else { (3i64, 4i64, 5i64) }.0");
4001        assert!(
4002            result.is_err(),
4003            "branches with different tuple shapes must not be accepted"
4004        );
4005    }
4006
4007    #[test]
4008    fn if_branch_tuple_element_type_mismatch_is_error() {
4009        let mut parser = CELParser::new(OpLookup::new());
4010        let result = parser.parse_str("if false { (1i32, 2i32) } else { (3i64, 4i64) }.0");
4011        assert!(
4012            result.is_err(),
4013            "branches with the same arity but different element types must not be accepted"
4014        );
4015    }
4016
4017    #[test]
4018    fn if_missing_open_brace_is_error() {
4019        let mut parser = CELParser::new(OpLookup::new());
4020        assert!(parser.parse_str("if true 1i32 } else { 2i32 }").is_err());
4021    }
4022
4023    #[test]
4024    fn if_missing_else_after_brace_is_fine() {
4025        // Omitting else is allowed; result type must be ().
4026        let mut parser = CELParser::new(OpLookup::new());
4027        let mut segment = parser.parse_str("if false { () }").expect("should parse");
4028        segment.call0::<()>().expect("should execute");
4029    }
4030
4031    #[test]
4032    fn if_trailing_else_is_error() {
4033        // `else` with no body is a parse error.
4034        let mut parser = CELParser::new(OpLookup::new());
4035        assert!(parser.parse_str("if true { () } else").is_err());
4036    }
4037
4038    #[test]
4039    fn parse_expression_stops_before_comma() -> anyhow::Result<()> {
4040        use lex_lexer::LexLexer;
4041        let stream: proc_macro2::TokenStream = "10i32 + 20i32, 5i32".parse().unwrap();
4042        let mut parser = CELParser::new(OpLookup::new());
4043        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4044        let mut seg = parser
4045            .parse_expression()
4046            .map_err(|e| anyhow::anyhow!("{}", e))?;
4047        let result: i32 = seg.call0()?;
4048        assert_eq!(result, 30);
4049        let remaining: Vec<_> = parser.take_lex_tokens().expect("tokens present").collect();
4050        // The comma and "5i32" should remain unconsumed.
4051        assert_eq!(
4052            remaining.len(),
4053            2,
4054            "expected 2 remaining tokens (comma and 5i32)"
4055        );
4056        Ok(())
4057    }
4058
4059    #[test]
4060    fn parse_expression_on_empty_input_returns_error() {
4061        use lex_lexer::LexLexer;
4062        let stream: proc_macro2::TokenStream = "".parse().unwrap();
4063        let mut parser = CELParser::new(OpLookup::new());
4064        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4065        let result = parser.parse_expression();
4066        assert!(result.is_err(), "expected Err for empty input");
4067    }
4068
4069    #[test]
4070    fn parse_literal_pattern_accepts_a_bare_literal() -> anyhow::Result<()> {
4071        use lex_lexer::LexLexer;
4072        let stream: proc_macro2::TokenStream = "5i32".parse().unwrap();
4073        let mut parser = CELParser::new(OpLookup::new());
4074        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4075        let mut seg = parser
4076            .parse_literal_pattern()
4077            .map_err(|e| anyhow::anyhow!("{}", e))?;
4078        assert_eq!(seg.call0::<i32>()?, 5);
4079        Ok(())
4080    }
4081
4082    #[test]
4083    fn parse_literal_pattern_accepts_a_negated_integer_literal() -> anyhow::Result<()> {
4084        use lex_lexer::LexLexer;
4085        let stream: proc_macro2::TokenStream = "-5i32".parse().unwrap();
4086        let mut parser = CELParser::new(OpLookup::new());
4087        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4088        let mut seg = parser
4089            .parse_literal_pattern()
4090            .map_err(|e| anyhow::anyhow!("{}", e))?;
4091        assert_eq!(seg.call0::<i32>()?, -5);
4092        Ok(())
4093    }
4094
4095    #[test]
4096    fn parse_literal_pattern_accepts_a_negated_float_literal() -> anyhow::Result<()> {
4097        use lex_lexer::LexLexer;
4098        let stream: proc_macro2::TokenStream = "-1.5f64".parse().unwrap();
4099        let mut parser = CELParser::new(OpLookup::new());
4100        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4101        let mut seg = parser
4102            .parse_literal_pattern()
4103            .map_err(|e| anyhow::anyhow!("{}", e))?;
4104        assert_eq!(seg.call0::<f64>()?, -1.5);
4105        Ok(())
4106    }
4107
4108    #[test]
4109    fn parse_literal_pattern_stops_before_a_trailing_operator() -> anyhow::Result<()> {
4110        use lex_lexer::LexLexer;
4111        let stream: proc_macro2::TokenStream = "1i32 + 2i32".parse().unwrap();
4112        let mut parser = CELParser::new(OpLookup::new());
4113        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4114        let mut seg = parser
4115            .parse_literal_pattern()
4116            .map_err(|e| anyhow::anyhow!("{}", e))?;
4117        assert_eq!(seg.call0::<i32>()?, 1);
4118        let remaining: Vec<_> = parser.take_lex_tokens().expect("tokens present").collect();
4119        assert_eq!(
4120            remaining.len(),
4121            2,
4122            "expected 2 remaining tokens (+ and 2i32)"
4123        );
4124        Ok(())
4125    }
4126
4127    #[test]
4128    fn parse_literal_pattern_rejects_a_dash_not_followed_by_a_literal() {
4129        use lex_lexer::LexLexer;
4130        let stream: proc_macro2::TokenStream = "-mode".parse().unwrap();
4131        let mut parser = CELParser::new(OpLookup::new());
4132        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4133        assert!(parser.parse_literal_pattern().is_err());
4134    }
4135
4136    #[test]
4137    fn parse_literal_pattern_rejects_negating_an_unsigned_literal() {
4138        use lex_lexer::LexLexer;
4139        let stream: proc_macro2::TokenStream = "-1u32".parse().unwrap();
4140        let mut parser = CELParser::new(OpLookup::new());
4141        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4142        assert!(parser.parse_literal_pattern().is_err());
4143    }
4144
4145    #[test]
4146    fn parse_literal_pattern_rejects_a_bare_identifier() {
4147        use lex_lexer::LexLexer;
4148        let stream: proc_macro2::TokenStream = "mode".parse().unwrap();
4149        let mut parser = CELParser::new(OpLookup::new());
4150        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4151        assert!(parser.parse_literal_pattern().is_err());
4152    }
4153
4154    #[test]
4155    fn parse_literal_pattern_rejects_a_tuple() {
4156        use lex_lexer::LexLexer;
4157        let stream: proc_macro2::TokenStream = "(1i32, 2i32)".parse().unwrap();
4158        let mut parser = CELParser::new(OpLookup::new());
4159        parser.set_lex_tokens(LexLexer::new(stream.into_iter()).peekable());
4160        assert!(parser.parse_literal_pattern().is_err());
4161    }
4162
4163    #[test]
4164    fn closure_literal_with_one_param_compiles_and_calls() -> anyhow::Result<()> {
4165        let mut parser = CELParser::new(OpLookup::new());
4166        let mut segment = parser
4167            .parse_str("|x: i32| x + 1")
4168            .map_err(|e| anyhow::anyhow!("{}", e))?;
4169        let closure: cel_runtime::DynClosure = segment.call0()?;
4170        let x = 5i32;
4171        assert_eq!(closure.call::<i32>(&[&x])?, 6);
4172        Ok(())
4173    }
4174
4175    #[test]
4176    fn closure_literal_with_zero_params_compiles_and_calls() -> anyhow::Result<()> {
4177        let mut parser = CELParser::new(OpLookup::new());
4178        let mut segment = parser
4179            .parse_str("|| 42")
4180            .map_err(|e| anyhow::anyhow!("{}", e))?;
4181        let closure: cel_runtime::DynClosure = segment.call0()?;
4182        assert_eq!(closure.call::<i32>(&[])?, 42);
4183        Ok(())
4184    }
4185
4186    #[test]
4187    fn closure_literal_with_two_params_compiles_and_calls_in_order() -> anyhow::Result<()> {
4188        let mut parser = CELParser::new(OpLookup::new());
4189        let mut segment = parser
4190            .parse_str("|a: i32, b: i32| a - b")
4191            .map_err(|e| anyhow::anyhow!("{}", e))?;
4192        let closure: cel_runtime::DynClosure = segment.call0()?;
4193        let (a, b) = (10i32, 3i32);
4194        assert_eq!(closure.call::<i32>(&[&a, &b])?, 7);
4195        Ok(())
4196    }
4197
4198    #[test]
4199    fn closure_literal_with_tuple_typed_param_compiles_and_calls() -> anyhow::Result<()> {
4200        let mut parser = CELParser::new(OpLookup::new());
4201        let mut segment = parser
4202            .parse_str("|r: (i32, i32)| r.0 + r.1")
4203            .map_err(|e| anyhow::anyhow!("{}", e))?;
4204        let closure: cel_runtime::DynClosure = segment.call0()?;
4205
4206        // Borrow a canonical whole-value owner at the checked closure boundary.
4207        let mut pair = DynSegment::new::<()>();
4208        let ambient_start = pair.current_stack_offset();
4209        pair.just(10i32);
4210        pair.annotate_top(crate::op_table::builtin_value_type(TypeId::of::<i32>()).unwrap())?;
4211        pair.just(20i32);
4212        pair.annotate_top(crate::op_table::builtin_value_type(TypeId::of::<i32>()).unwrap())?;
4213        pair.make_tuple(2, ambient_start)?;
4214        let pair = pair.call_dyn_value(&[])?;
4215
4216        assert_eq!(closure.call::<i32>(&[&pair])?, 30);
4217        Ok(())
4218    }
4219
4220    #[test]
4221    fn closure_param_accepts_a_builtin_generic_type() -> anyhow::Result<()> {
4222        let mut parser = CELParser::new(OpLookup::new());
4223        let mut segment = parser
4224            .parse_str("|r: RangeInclusive(f64)| r")
4225            .map_err(|e| anyhow::anyhow!("{}", e))?;
4226        let closure: cel_runtime::DynClosure = segment.call0()?;
4227        let range = 1.0f64..=2.0f64;
4228        assert_eq!(
4229            closure.call::<std::ops::RangeInclusive<f64>>(&[&range])?,
4230            1.0..=2.0
4231        );
4232        Ok(())
4233    }
4234
4235    #[test]
4236    fn closure_param_accepts_an_array_type() {
4237        let mut parser = CELParser::new(OpLookup::new());
4238        let mut segment = parser.parse_str("(|xs: [i32]| xs)([1, 2])").unwrap();
4239        let array = segment
4240            .call0::<cel_runtime::OwnedValue>()
4241            .unwrap()
4242            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()
4243            .unwrap();
4244        assert_eq!(array.as_slice(), &[1, 2]);
4245    }
4246
4247    #[test]
4248    fn closure_body_referencing_an_undeclared_name_is_a_parse_error() {
4249        let mut parser = CELParser::new(OpLookup::new());
4250        let err = parser.parse_str("|x: i32| x + y");
4251        assert!(err.is_err());
4252    }
4253
4254    #[test]
4255    fn nested_closure_referencing_only_its_own_param_compiles_and_calls() -> anyhow::Result<()> {
4256        let mut parser = CELParser::new(OpLookup::new());
4257        // The outer closure's own parameter `x` must NOT be visible inside the inner closure
4258        // body — a bare nested closure literal parses fine directly as the outer body's
4259        // `expression`, with no extra block grouping needed (this grammar has no bare
4260        // block-expression production, only `if`'s own braces, so `{ ... }` grouping isn't an
4261        // option here anyway).
4262        let mut segment = parser
4263            .parse_str("|x: i32| |y: i32| y + 1")
4264            .map_err(|e| anyhow::anyhow!("{}", e))?;
4265        let outer: cel_runtime::DynClosure = segment.call0()?;
4266        let x = 0i32;
4267        let inner: cel_runtime::DynClosure = outer.call(&[&x])?;
4268        let y = 41i32;
4269        assert_eq!(inner.call::<i32>(&[&y])?, 42);
4270        Ok(())
4271    }
4272
4273    #[test]
4274    fn closure_body_cannot_see_an_enclosing_scopes_names() {
4275        // A scope pushed before parsing (standing in for e.g. adam-lang's own cell-name scope)
4276        // must not leak into a closure body's name resolution.
4277        let mut lookup = OpLookup::new();
4278        lookup.push_scope(|name, segment, arity, _span| {
4279            if name == "outer_only" && arity == 0 {
4280                segment.just(1i32);
4281                Ok(true)
4282            } else {
4283                Ok(false)
4284            }
4285        });
4286        let mut parser = CELParser::new(lookup);
4287        let err = parser.parse_str("|x: i32| x + outer_only");
4288        assert!(err.is_err());
4289    }
4290
4291    #[test]
4292    fn ast_context_parses_a_one_param_closure() {
4293        let mut parser = Parser::<AstContext>::new(OpLookup::new());
4294        let expr = parser.parse_str_ast("|x: i32| x").unwrap();
4295        let Expr::Closure { params, body, .. } = expr else {
4296            panic!("expected Closure");
4297        };
4298        assert_eq!(params.len(), 1);
4299        assert_eq!(params[0].name, "x");
4300        assert!(matches!(
4301            &params[0].type_expr,
4302            TypeExpr::Named { name, args, .. } if name == "i32" && args.is_empty()
4303        ));
4304        assert!(matches!(*body, Expr::Ident { ref name, .. } if name == "x"));
4305    }
4306
4307    #[test]
4308    fn ast_context_parses_a_zero_param_closure() {
4309        let mut parser = Parser::<AstContext>::new(OpLookup::new());
4310        let expr = parser.parse_str_ast("|| 1i32").unwrap();
4311        let Expr::Closure { params, .. } = expr else {
4312            panic!("expected Closure");
4313        };
4314        assert!(params.is_empty());
4315    }
4316
4317    #[test]
4318    fn ast_context_parses_a_tuple_typed_closure_param() {
4319        let mut parser = Parser::<AstContext>::new(OpLookup::new());
4320        let expr = parser.parse_str_ast("|x: (i32, f64)| x.0").unwrap();
4321        let Expr::Closure { params, .. } = expr else {
4322            panic!("expected Closure");
4323        };
4324        match &params[0].type_expr {
4325            TypeExpr::Tuple { elements, .. } => assert_eq!(elements.len(), 2),
4326            other => panic!("expected Tuple, got {other:?}"),
4327        }
4328    }
4329
4330    #[test]
4331    fn range_expression_constructs_a_range() {
4332        let mut parser = CELParser::new(OpLookup::new());
4333        let mut seg = parser.parse_str("1i32..5i32").unwrap();
4334        assert_eq!(seg.call0::<std::ops::Range<i32>>().unwrap(), 1i32..5i32);
4335    }
4336
4337    #[test]
4338    fn range_inclusive_expression_constructs_a_range_inclusive() {
4339        let mut parser = CELParser::new(OpLookup::new());
4340        let mut seg = parser.parse_str("1i32..=5i32").unwrap();
4341        assert_eq!(
4342            seg.call0::<std::ops::RangeInclusive<i32>>().unwrap(),
4343            1i32..=5i32
4344        );
4345    }
4346
4347    #[test]
4348    fn range_from_expression_constructs_a_range_from() {
4349        let mut parser = CELParser::new(OpLookup::new());
4350        let mut seg = parser.parse_str("3i32..").unwrap();
4351        assert_eq!(seg.call0::<std::ops::RangeFrom<i32>>().unwrap(), 3i32..);
4352    }
4353
4354    #[test]
4355    fn range_to_expression_constructs_a_range_to() {
4356        let mut parser = CELParser::new(OpLookup::new());
4357        let mut seg = parser.parse_str("..7i32").unwrap();
4358        assert_eq!(seg.call0::<std::ops::RangeTo<i32>>().unwrap(), ..7i32);
4359    }
4360
4361    #[test]
4362    fn range_to_inclusive_expression_constructs_a_range_to_inclusive() {
4363        let mut parser = CELParser::new(OpLookup::new());
4364        let mut seg = parser.parse_str("..=7i32").unwrap();
4365        assert_eq!(
4366            seg.call0::<std::ops::RangeToInclusive<i32>>().unwrap(),
4367            ..=7i32
4368        );
4369    }
4370
4371    #[test]
4372    fn range_full_expression_constructs_a_range_full() {
4373        let mut parser = CELParser::new(OpLookup::new());
4374        let mut seg = parser.parse_str("..").unwrap();
4375        seg.call0::<std::ops::RangeFull>().unwrap();
4376    }
4377
4378    #[test]
4379    fn range_endpoints_are_full_or_expressions() {
4380        // `1 + 2..3 * 4` must group as `(1 + 2)..(3 * 4)`, matching Rust's own precedence
4381        // (range binds looser than every arithmetic/bitwise operator).
4382        let mut parser = CELParser::new(OpLookup::new());
4383        let mut seg = parser.parse_str("1i32 + 2i32..3i32 * 4i32").unwrap();
4384        assert_eq!(seg.call0::<std::ops::Range<i32>>().unwrap(), 3i32..12i32);
4385    }
4386
4387    #[test]
4388    fn chained_ranges_are_a_parse_error() {
4389        // Ranges don't chain, matching Rust (`1..2..3` is also a compile error there). No
4390        // special "non-chainable" check is needed in `is_range_expression` itself: after
4391        // parsing `1..2`, the leftover `..3` fails `parse_tokens_ctx`'s end-of-stream check
4392        // the same way `"10 + 25 25"` already does (see `incomplete_expression`).
4393        let mut parser = CELParser::new(OpLookup::new());
4394        let result = parser.parse_str("1i32..2i32..3i32");
4395        assert!(result.is_err(), "expected a parse error, got Ok");
4396    }
4397
4398    #[test]
4399    fn range_to_inclusive_without_a_right_operand_is_a_parse_error() {
4400        // `..=` always requires a right endpoint — there is no inclusive-from-only range.
4401        let mut parser = CELParser::new(OpLookup::new());
4402        let result = parser.parse_str("..=");
4403        assert!(result.is_err(), "expected a parse error, got Ok");
4404    }
4405
4406    #[test]
4407    fn range_endpoint_absorbs_a_trailing_comparison_confirming_or_expression_operands() {
4408        // Confirms range operands are `or_expression`, not `bitwise_or_expression`: in
4409        // `1i32..5i32 == true`, `5i32 == true` must group together as the range's right
4410        // endpoint's own `or_expression` and fail *there* (`i32` vs `bool`) — proving the
4411        // endpoint absorbed the whole comparison, rather than `..` grabbing only `5i32` and
4412        // `==` applying afterward to an already-built `Range`.
4413        let mut parser = CELParser::new(OpLookup::new());
4414        let err = match parser.parse_str("1i32..5i32 == true") {
4415            Err(e) => e,
4416            Ok(_) => panic!("expected a parse error"),
4417        };
4418        let message = err.message();
4419        assert!(
4420            message.starts_with("no operation `==`"),
4421            "expected a 'no operation `==`' error, got: {message}"
4422        );
4423        assert!(
4424            !message.contains("Range"),
4425            "expected the error to be about `i32`/`bool` (the inner endpoint's own comparison), \
4426             not `Range<i32>` (which would mean `..` grabbed only `5i32` and `==` applied afterward \
4427             to an already-built range) — got: {message}"
4428        );
4429    }
4430
4431    #[test]
4432    fn parameter_list_accepts_a_range_expression() -> anyhow::Result<()> {
4433        let mut lookup = OpLookup::new();
4434        lookup.push_scope(
4435            |name, segment, num_operands, _span| match (name, num_operands) {
4436                ("f", 0) => {
4437                    segment.op0(|| 0i32);
4438                    Ok(true)
4439                }
4440                ("()", 2) => {
4441                    segment.op2(|_callee: i32, arg: std::ops::Range<i32>| arg)?;
4442                    Ok(true)
4443                }
4444                _ => Ok(false),
4445            },
4446        );
4447        let mut parser = CELParser::new(lookup);
4448        let mut segment = parser
4449            .parse_str("f(1i32..5i32)")
4450            .map_err(|e| anyhow::anyhow!("{}", e))?;
4451        assert_eq!(segment.call0::<std::ops::Range<i32>>()?, 1i32..5i32);
4452        Ok(())
4453    }
4454
4455    #[test]
4456    fn tuple_or_group_accepts_a_range_expression() -> anyhow::Result<()> {
4457        let mut parser = CELParser::new(OpLookup::new());
4458        let mut seg = parser.parse_str("(1i32..5i32)").unwrap();
4459        assert_eq!(seg.call0::<std::ops::Range<i32>>().unwrap(), 1i32..5i32);
4460        Ok(())
4461    }
4462
4463    #[test]
4464    fn if_expression_branches_accept_range_expressions() {
4465        let mut parser = CELParser::new(OpLookup::new());
4466        let mut seg = parser
4467            .parse_str("if true { 1i32..5i32 } else { 2i32..6i32 }")
4468            .unwrap();
4469        assert_eq!(seg.call0::<std::ops::Range<i32>>().unwrap(), 1i32..5i32);
4470    }
4471
4472    #[test]
4473    fn closure_body_accepts_a_range_expression() -> anyhow::Result<()> {
4474        let mut parser = CELParser::new(OpLookup::new());
4475        let mut segment = parser
4476            .parse_str("|| 1i32..5i32")
4477            .map_err(|e| anyhow::anyhow!("{}", e))?;
4478        let closure: cel_runtime::DynClosure = segment.call0()?;
4479        assert_eq!(closure.call::<std::ops::Range<i32>>(&[])?, 1i32..5i32);
4480        Ok(())
4481    }
4482
4483    /// Returns the [`ParseError`] the runtime-executing parser must produce for `source`.
4484    fn array_parse_error(source: &str) -> ParseError {
4485        let mut parser = CELParser::new(OpLookup::new());
4486        match parser.parse_str(source) {
4487            Err(e) => e,
4488            Ok(_) => panic!("expected a parse error for `{source}`"),
4489        }
4490    }
4491
4492    #[test]
4493    fn array_literal_evaluates_to_its_elements_in_source_order() -> anyhow::Result<()> {
4494        let mut parser = CELParser::new(OpLookup::new());
4495        let mut segment = parser
4496            .parse_str("[0i32, 1i32, 2i32]")
4497            .map_err(|e| anyhow::anyhow!("{}", e))?;
4498        let array = segment
4499            .call0::<cel_runtime::OwnedValue>()?
4500            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4501        assert_eq!(array.into_vec(), vec![0, 1, 2]);
4502        Ok(())
4503    }
4504
4505    #[test]
4506    fn typed_array_annotation_preserves_unsuffixed_non_empty_inference() -> anyhow::Result<()> {
4507        let mut parser = CELParser::new(OpLookup::new());
4508        let mut segment = parser
4509            .parse_str("[0, 1]")
4510            .map_err(|e| anyhow::anyhow!("{}", e))?;
4511        let array = segment
4512            .call0::<cel_runtime::OwnedValue>()?
4513            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4514        assert_eq!(array.into_vec(), vec![0, 1]);
4515        Ok(())
4516    }
4517
4518    #[test]
4519    fn typed_array_annotation_evaluates_to_the_declared_scalar_type() -> anyhow::Result<()> {
4520        let mut parser = CELParser::new(OpLookup::new());
4521        let mut segment = parser
4522            .parse_str("[0, 1]: [i32]")
4523            .map_err(|e| anyhow::anyhow!("{}", e))?;
4524        let array = segment
4525            .call0::<cel_runtime::OwnedValue>()?
4526            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4527        assert_eq!(array.into_vec(), vec![0, 1]);
4528        Ok(())
4529    }
4530
4531    #[test]
4532    fn typed_array_annotation_evaluates_to_the_declared_f64_type() -> anyhow::Result<()> {
4533        let mut parser = CELParser::new(OpLookup::new());
4534        let mut segment = parser
4535            .parse_str("[1.0, 42.5]: [f64]")
4536            .map_err(|e| anyhow::anyhow!("{}", e))?;
4537        let array = segment
4538            .call0::<cel_runtime::OwnedValue>()?
4539            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<f64>>>()?;
4540        assert_eq!(array.into_vec(), vec![1.0, 42.5]);
4541        Ok(())
4542    }
4543
4544    #[test]
4545    fn typed_array_annotation_constructs_a_typed_empty_array() -> anyhow::Result<()> {
4546        let mut parser = CELParser::new(OpLookup::new());
4547        let mut segment = parser
4548            .parse_str("[]: [i32]")
4549            .map_err(|e| anyhow::anyhow!("{}", e))?;
4550        let array = segment
4551            .call0::<cel_runtime::OwnedValue>()?
4552            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4553        assert!(array.into_vec().is_empty());
4554        Ok(())
4555    }
4556
4557    #[test]
4558    fn typed_array_annotation_constructs_a_nested_typed_empty_array() -> anyhow::Result<()> {
4559        let mut parser = CELParser::new(OpLookup::new());
4560        let mut segment = parser
4561            .parse_str("[]: [[i32]]")
4562            .map_err(|e| anyhow::anyhow!("{}", e))?;
4563        let array = segment
4564            .call0::<cel_runtime::OwnedValue>()?
4565            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>>(
4566            )?;
4567        assert!(array.into_vec().is_empty());
4568        Ok(())
4569    }
4570
4571    #[test]
4572    fn single_element_array_literal_needs_no_trailing_comma() -> anyhow::Result<()> {
4573        let mut parser = CELParser::new(OpLookup::new());
4574        let mut segment = parser
4575            .parse_str("[7i32]")
4576            .map_err(|e| anyhow::anyhow!("{}", e))?;
4577        let array = segment
4578            .call0::<cel_runtime::OwnedValue>()?
4579            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4580        assert_eq!(array.into_vec(), vec![7]);
4581        Ok(())
4582    }
4583
4584    #[test]
4585    fn array_elements_can_be_arbitrary_expressions() -> anyhow::Result<()> {
4586        let mut parser = CELParser::new(OpLookup::new());
4587        let mut segment = parser
4588            .parse_str("[1i32 + 2i32, if true { 4i32 } else { 5i32 }]")
4589            .map_err(|e| anyhow::anyhow!("{}", e))?;
4590        let array = segment
4591            .call0::<cel_runtime::OwnedValue>()?
4592            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4593        assert_eq!(array.into_vec(), vec![3, 4]);
4594        Ok(())
4595    }
4596
4597    #[test]
4598    fn empty_array_literal_is_rejected_with_a_dedicated_diagnostic() {
4599        let err = array_parse_error("[]");
4600        assert!(
4601            err.message().contains("empty array"),
4602            "got: {}",
4603            err.message()
4604        );
4605        assert!(
4606            err.message().contains("issues/212"),
4607            "the diagnostic must reference the contextual-typing issue, got: {}",
4608            err.message()
4609        );
4610    }
4611
4612    #[test]
4613    fn array_literal_rejects_a_trailing_comma() {
4614        let err = array_parse_error("[0i32,]");
4615        assert!(
4616            err.message().contains("expected expression after ','"),
4617            "got: {}",
4618            err.message()
4619        );
4620    }
4621
4622    #[test]
4623    fn array_literal_requires_a_comma_between_elements() {
4624        let err = array_parse_error("[0i32 1i32]");
4625        assert!(
4626            err.message().contains("expected ',' or closing ']'"),
4627            "got: {}",
4628            err.message()
4629        );
4630    }
4631
4632    #[test]
4633    fn array_literal_rejects_a_mismatched_element_naming_its_index() {
4634        let err = array_parse_error("[0i32, 1.0f64]");
4635        assert!(
4636            err.message().contains("array element 1"),
4637            "got: {}",
4638            err.message()
4639        );
4640    }
4641
4642    #[test]
4643    fn array_literal_accepts_a_tuple_element() {
4644        use cel_runtime::{ArrayOf, Cons, Leaf, Nil};
4645        let body = CELParser::new(OpLookup::new())
4646            .parse_str("[(0i32, 1i32)]")
4647            .unwrap();
4648        assert!(
4649            body.prepare::<Nil, ArrayOf<Cons<Leaf<i32>, Cons<Leaf<i32>, Nil>>>>()
4650                .is_ok()
4651        );
4652    }
4653
4654    #[test]
4655    fn nested_array_literal_evaluates_to_an_array_of_arrays() -> anyhow::Result<()> {
4656        let mut parser = CELParser::new(OpLookup::new());
4657        let mut segment = parser
4658            .parse_str("[[0i32], [1i32]]")
4659            .map_err(|e| anyhow::anyhow!("{}", e))?;
4660        let array = segment
4661            .call0::<cel_runtime::OwnedValue>()?
4662            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>>(
4663            )?;
4664        let rows: Vec<_> = array
4665            .into_vec()
4666            .into_iter()
4667            .map(|row| row.into_vec())
4668            .collect();
4669        assert_eq!(rows, vec![vec![0], vec![1]]);
4670        Ok(())
4671    }
4672
4673    #[test]
4674    fn nested_array_literal_rejects_a_recursive_element_mismatch() {
4675        let err = array_parse_error("[[0i32], [1.0f64]]");
4676        assert!(
4677            err.message().contains("array element 1"),
4678            "got: {}",
4679            err.message()
4680        );
4681    }
4682
4683    #[test]
4684    fn nested_array_literal_rejects_a_depth_mismatch() {
4685        let err = array_parse_error("[[0i32], [[1i32]]]");
4686        assert!(
4687            err.message().contains("array element 1"),
4688            "got: {}",
4689            err.message()
4690        );
4691    }
4692
4693    #[test]
4694    fn typed_array_annotation_rejects_an_exact_element_type_mismatch() {
4695        let err = array_parse_error("[0, 1]: [f64]");
4696        assert!(
4697            err.message().contains("expected f64"),
4698            "got: {}",
4699            err.message()
4700        );
4701        assert!(err.message().contains("type i32"), "got: {}", err.message());
4702    }
4703
4704    #[test]
4705    fn typed_array_annotation_rejects_a_recursive_element_type_mismatch() {
4706        // The annotation's mismatch is one level below the outer array: the values are nested
4707        // `i32` arrays, the annotation names nested `f64` arrays.
4708        let err = array_parse_error("[[0, 1]]: [[f64]]");
4709        assert!(
4710            err.message().contains("expected [f64]"),
4711            "got: {}",
4712            err.message()
4713        );
4714        assert!(
4715            err.message().contains("type [i32]"),
4716            "got: {}",
4717            err.message()
4718        );
4719    }
4720
4721    #[test]
4722    fn typed_array_annotation_mismatch_names_the_registered_type_not_its_rust_path() {
4723        let resolver = [(
4724            "Celsius",
4725            ResolvedLeafType::new("Celsius", cel_runtime::ValueType::leaf::<Celsius>()),
4726        )];
4727        let mut parser = CELParser::with_type_resolver(OpLookup::new(), resolver);
4728        let err = match parser.parse_str("[0]: [Celsius]") {
4729            Err(e) => e,
4730            Ok(_) => panic!("expected `[0]: [Celsius]` to reject its `i32` element"),
4731        };
4732        assert!(
4733            err.message().contains("expected Celsius"),
4734            "the registered annotation name must survive into the diagnostic, got: {}",
4735            err.message()
4736        );
4737        assert!(
4738            !err.message().contains("::"),
4739            "no Rust type path may leak into the diagnostic, got: {}",
4740            err.message()
4741        );
4742    }
4743
4744    /// The runtime rejects a nested literal whose own annotation names a different custom element
4745    /// type — the mismatch `ty::check_expr` must also report statically.
4746    #[test]
4747    fn typed_array_annotation_rejects_a_nested_custom_element_type_mismatch() {
4748        #[derive(Clone)]
4749        struct CustomB;
4750
4751        let resolver = [
4752            (
4753                "Celsius",
4754                ResolvedLeafType::new("Celsius", cel_runtime::ValueType::leaf::<Celsius>()),
4755            ),
4756            (
4757                "CustomB",
4758                ResolvedLeafType::new("CustomB", cel_runtime::ValueType::leaf::<CustomB>()),
4759            ),
4760        ];
4761        let mut parser = CELParser::with_type_resolver(OpLookup::new(), resolver);
4762        let err = match parser.parse_str("[[]: [Celsius]]: [[CustomB]]") {
4763            Err(e) => e,
4764            Ok(_) => panic!("expected the inner annotation to conflict with the outer one"),
4765        };
4766        assert!(
4767            err.message().contains("expected [CustomB]"),
4768            "got: {}",
4769            err.message()
4770        );
4771        assert!(
4772            err.message().contains("[Celsius]"),
4773            "got: {}",
4774            err.message()
4775        );
4776    }
4777
4778    #[test]
4779    fn typed_array_annotation_rejects_an_unknown_type_name() {
4780        let err = array_parse_error("[0]: [Nope]");
4781        assert!(
4782            err.message().contains("unknown type `Nope`"),
4783            "got: {}",
4784            err.message()
4785        );
4786    }
4787
4788    #[test]
4789    fn typed_array_annotation_requires_a_complete_array_type() {
4790        let err = array_parse_error("[0]: i32");
4791        assert!(
4792            err.message()
4793                .contains("array annotations must name a complete array type"),
4794            "got: {}",
4795            err.message()
4796        );
4797    }
4798
4799    #[test]
4800    fn typed_array_annotation_accepts_tuple_array_elements() {
4801        use cel_runtime::{ArrayOf, Cons, Leaf, Nil};
4802        let body = CELParser::new(OpLookup::new())
4803            .parse_str("[]: [(i32, f64)]")
4804            .unwrap();
4805        assert!(
4806            body.prepare::<Nil, ArrayOf<Cons<Leaf<i32>, Cons<Leaf<f64>, Nil>>>>()
4807                .is_ok()
4808        );
4809    }
4810
4811    #[test]
4812    fn array_literal_is_accepted_as_a_call_argument() -> anyhow::Result<()> {
4813        let mut lookup = OpLookup::new();
4814        lookup.push_scope(
4815            |name, segment, num_operands, _span| match (name, num_operands) {
4816                ("len", 0) => {
4817                    segment.op0(|| 0i32);
4818                    Ok(true)
4819                }
4820                ("()", 2) => {
4821                    segment.op2_repr::<cel_runtime::Leaf<i32>, cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>, cel_runtime::Leaf<i32>, _>(
4822                        |_callee, arg| arg.len() as i32)?;
4823                    Ok(true)
4824                }
4825                _ => Ok(false),
4826            },
4827        );
4828        let mut parser = CELParser::new(lookup);
4829        let mut segment = parser
4830            .parse_str("len([1i32, 2i32, 3i32])")
4831            .map_err(|e| anyhow::anyhow!("{}", e))?;
4832        assert_eq!(segment.call0::<i32>()?, 3);
4833        Ok(())
4834    }
4835
4836    #[test]
4837    fn array_literal_is_accepted_as_a_tuple_element() -> anyhow::Result<()> {
4838        let mut parser = CELParser::new(OpLookup::new());
4839        let mut segment = parser
4840            .parse_str("([1i32, 2i32], 3i32).0")
4841            .map_err(|e| anyhow::anyhow!("{}", e))?;
4842        let array = segment
4843            .call0::<cel_runtime::OwnedValue>()?
4844            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4845        assert_eq!(array.into_vec(), vec![1, 2]);
4846        Ok(())
4847    }
4848
4849    #[test]
4850    fn array_literal_is_accepted_inside_an_if_branch() -> anyhow::Result<()> {
4851        let mut parser = CELParser::new(OpLookup::new());
4852        let mut segment = parser
4853            .parse_str("if true { [1i32, 2i32] } else { [3i32, 4i32] }")
4854            .map_err(|e| anyhow::anyhow!("{}", e))?;
4855        let array = segment
4856            .call0::<cel_runtime::OwnedValue>()?
4857            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4858        assert_eq!(array.into_vec(), vec![1, 2]);
4859        Ok(())
4860    }
4861
4862    #[test]
4863    fn array_literal_is_accepted_as_a_closure_body() -> anyhow::Result<()> {
4864        let mut parser = CELParser::new(OpLookup::new());
4865        let mut segment = parser
4866            .parse_str("|x: i32| [x, x]")
4867            .map_err(|e| anyhow::anyhow!("{}", e))?;
4868        let closure: cel_runtime::DynClosure = segment.call0()?;
4869        let x = 5i32;
4870        let array = closure
4871            .call::<cel_runtime::OwnedValue>(&[&x])?
4872            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4873        assert_eq!(array.into_vec(), vec![5, 5]);
4874        Ok(())
4875    }
4876
4877    #[test]
4878    fn array_ambient_start_is_correct_after_a_sibling_expression() -> anyhow::Result<()> {
4879        // Regression guard mirroring `tuple_ambient_start_correct_after_sibling_expression`: the
4880        // array literal is deliberately *not* the first sub-expression, and its sibling has a
4881        // different alignment than its element type. The `u8` sum is pushed first (offset 0,
4882        // align 1), so the `i32` elements start at a nonzero offset that is only correct if the
4883        // ambient start is both threaded through from the sibling and padded up to `i32`'s
4884        // alignment.
4885        let mut parser = CELParser::new(OpLookup::new());
4886        let mut segment = parser
4887            .parse_str("(1u8 + 2u8, [3i32, 4i32]).1")
4888            .map_err(|e| anyhow::anyhow!("{}", e))?;
4889        let array = segment
4890            .call0::<cel_runtime::OwnedValue>()?
4891            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()?;
4892        assert_eq!(array.into_vec(), vec![3, 4]);
4893        Ok(())
4894    }
4895
4896    /// A custom element type implementing no trait beyond `'static` — not `Copy`, `Clone`,
4897    /// `Debug`, or `PartialEq` — so an array of it exercises the minimum element bound.
4898    struct Celsius(i32);
4899
4900    /// A second custom element type, distinct from [`Celsius`], so one call in an otherwise
4901    /// homogeneous literal can return a different type.
4902    struct Fahrenheit(i32);
4903
4904    /// The unapplied nullary function `f`.
4905    struct FName;
4906
4907    /// The unapplied nullary function `g`.
4908    struct GName;
4909
4910    /// The unapplied nullary function `h`.
4911    struct HName;
4912
4913    /// Returns an [`OpLookup`] resolving the nullary calls `f()`, `g()`, and `h()`, where `f`
4914    /// and `g` return [`Celsius`] values and `h` returns a [`Celsius`] when `homogeneous` and a
4915    /// [`Fahrenheit`] otherwise.
4916    fn temperature_lookup(homogeneous: bool) -> OpLookup {
4917        let mut lookup = OpLookup::new();
4918        lookup.push_scope(
4919            move |name, segment, num_operands, _span| match (name, num_operands) {
4920                ("f", 0) => {
4921                    segment.op0(|| FName);
4922                    Ok(true)
4923                }
4924                ("g", 0) => {
4925                    segment.op0(|| GName);
4926                    Ok(true)
4927                }
4928                ("h", 0) => {
4929                    segment.op0(|| HName);
4930                    Ok(true)
4931                }
4932                ("()", 1) => {
4933                    let Some(callee) = segment
4934                        .peek_stack_infos(1)
4935                        .first()
4936                        .map(|i| i.value_type.type_id())
4937                    else {
4938                        return Ok(false);
4939                    };
4940                    if callee == TypeId::of::<FName>() {
4941                        segment.op1(|_: FName| Celsius(0))?;
4942                    } else if callee == TypeId::of::<GName>() {
4943                        segment.op1(|_: GName| Celsius(1))?;
4944                    } else if callee == TypeId::of::<HName>() {
4945                        if homogeneous {
4946                            segment.op1(|_: HName| Celsius(2))?;
4947                        } else {
4948                            segment.op1(|_: HName| Fahrenheit(2))?;
4949                        }
4950                    } else {
4951                        return Ok(false);
4952                    }
4953                    Ok(true)
4954                }
4955                _ => Ok(false),
4956            },
4957        );
4958        lookup
4959    }
4960
4961    #[test]
4962    fn array_literal_of_calls_returning_a_custom_type_converts_to_its_vector() -> anyhow::Result<()>
4963    {
4964        let mut parser = CELParser::new(temperature_lookup(true));
4965        let mut segment = parser
4966            .parse_str("[f(), g(), h()]")
4967            .map_err(|e| anyhow::anyhow!("{}", e))?;
4968        let array = segment
4969            .call0::<cel_runtime::OwnedValue>()?
4970            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<Celsius>>>()?;
4971        let degrees: Vec<i32> = array.into_vec().into_iter().map(|c| c.0).collect();
4972        assert_eq!(degrees, vec![0, 1, 2]);
4973        Ok(())
4974    }
4975
4976    #[test]
4977    fn array_literal_of_one_custom_typed_call_evaluates_to_that_type() -> anyhow::Result<()> {
4978        // The same registration the mismatch test below uses: `h` alone is a perfectly good
4979        // array element, so that test's failure is about element homogeneity, not about `h`.
4980        let mut parser = CELParser::new(temperature_lookup(false));
4981        let mut segment = parser
4982            .parse_str("[h()]")
4983            .map_err(|e| anyhow::anyhow!("{}", e))?;
4984        let array = segment
4985            .call0::<cel_runtime::OwnedValue>()?
4986            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<Fahrenheit>>>()?;
4987        let degrees: Vec<i32> = array.into_vec().into_iter().map(|f| f.0).collect();
4988        assert_eq!(degrees, vec![2]);
4989        Ok(())
4990    }
4991
4992    #[test]
4993    fn array_literal_of_calls_rejects_a_mismatched_return_type_before_execution() {
4994        // `parse_str` compiles the literal, so a mismatch is reported here — the segment is
4995        // never built and therefore never executed.
4996        let mut parser = CELParser::new(temperature_lookup(false));
4997        let err = match parser.parse_str("[f(), g(), h()]") {
4998            Err(e) => e,
4999            Ok(_) => panic!("expected `[f(), g(), h()]` with a mismatched `h` to fail"),
5000        };
5001        let message = err.message();
5002        assert!(message.contains("array element 2"), "got: {message}");
5003        assert!(message.contains("Fahrenheit"), "got: {message}");
5004        assert!(message.contains("Celsius"), "got: {message}");
5005    }
5006
5007    #[test]
5008    fn static_check_and_execution_agree_on_a_nested_custom_typed_array() -> anyhow::Result<()> {
5009        const SOURCE: &str = "[[f(), g()], [h()]]";
5010
5011        // The static checker sees only the AST, where `Celsius` — a host-registered type with no
5012        // `Ty` variant — infers as `Ty::Any`, so the literal infers as an array of arrays of it:
5013        // an under-approximation of the runtime type, reported without diagnostics.
5014        let mut ast_parser = Parser::<AstContext>::new(OpLookup::new());
5015        let expr = ast_parser
5016            .parse_str_ast(SOURCE)
5017            .map_err(|e| anyhow::anyhow!("{}", e))?;
5018        let (ty, diagnostics) = ty::check_expr(&expr, &|_name| Ty::Any);
5019        let messages: Vec<String> = diagnostics
5020            .iter()
5021            .map(|d| d.message().to_string())
5022            .collect();
5023        assert!(messages.is_empty(), "unexpected diagnostics: {messages:?}");
5024        assert_eq!(ty, Ty::Array(Box::new(Ty::Array(Box::new(Ty::Any)))));
5025
5026        // The same source, compiled and executed, produces the value the checker accepted.
5027        let mut parser = CELParser::new(temperature_lookup(true));
5028        let mut segment = parser
5029            .parse_str(SOURCE)
5030            .map_err(|e| anyhow::anyhow!("{}", e))?;
5031        let array = segment.call0::<cel_runtime::OwnedValue>()?.try_into_typed::<cel_runtime::ArrayOf<cel_runtime::ArrayOf<cel_runtime::Leaf<Celsius>>>>()?;
5032        let mut rows: Vec<Vec<i32>> = Vec::new();
5033        for row in array.into_vec() {
5034            rows.push(row.into_vec().into_iter().map(|c| c.0).collect());
5035        }
5036        assert_eq!(rows, vec![vec![0, 1], vec![2]]);
5037        Ok(())
5038    }
5039}
5040
5041#[cfg(test)]
5042mod playground {
5043    use super::*;
5044    use annotate_snippets::Renderer;
5045
5046    #[test]
5047    fn custom_scope_identifier() -> Result<()> {
5048        let mut lookup = OpLookup::new();
5049        lookup.push_scope(|name, segment, _num_operands, _span| {
5050            if name == "constant" {
5051                segment.just(42i64);
5052                return Ok(true);
5053            }
5054            Ok(false)
5055        });
5056        let mut parser = CELParser::new(lookup);
5057        let mut segment = parser.parse_str("constant == 42i64 && 15i64 < constant")?;
5058        assert!(segment.call0::<bool>().unwrap());
5059        Ok(())
5060    }
5061    #[test]
5062    fn literal_types() {
5063        use CELParser;
5064        use op_table::OpLookup;
5065        let source = r#"
5066            b'a'
5067        "#;
5068        assert_eq!(
5069            CELParser::new(OpLookup::new())
5070                .parse_str(source)
5071                .unwrap()
5072                .call0::<u8>()
5073                .unwrap(),
5074            b'a'
5075        );
5076    }
5077
5078    #[test]
5079    fn expression_macro_error3() {
5080        use CELParser;
5081        use op_table::OpLookup;
5082
5083        let line = line!() + 1;
5084        let source = r#"
5085            "Hello" + "World" + 32.0
5086        "#;
5087        match CELParser::new(OpLookup::new()).parse_str(source) {
5088            Ok(_) => panic!("expected parse error"),
5089            Err(e) => println!(
5090                "{}",
5091                e.format_rustc_style(source, file!(), line, &Renderer::styled())
5092            ),
5093        }
5094    }
5095
5096    #[test]
5097    fn arithmetic_overflow_error() {
5098        use error::FormatRustcStyle;
5099
5100        let line = line!() + 1;
5101        let source = r#"
5102           1 + 1 +
5103                2147483646 + 1
5104        "#;
5105        let mut seg = CELParser::new(OpLookup::new())
5106            .parse_str(source)
5107            .expect("parses successfully");
5108        match seg.call0::<i32>() {
5109            Ok(v) => panic!("expected overflow, got {v}"),
5110            Err(e) => println!(
5111                "{}",
5112                e.format_rustc_style(source, file!(), line, &Renderer::styled())
5113            ),
5114        }
5115    }
5116}