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