adam_lang/ast.rs
1//! A span-carrying adam-lang structural AST, built by [`crate::AdamAstParser`] as an alternative to
2//! [`crate::AdamParser`]'s direct `adam_rs::Sheet` construction. Method bodies and cell
3//! initializers reference [`cel_parser::Expr`]/[`cel_parser::lex_lexer::Literal`] directly.
4//! Carries no resolved types, no `TypeRegistry` lookups, and never fails on semantic grounds
5//! (unknown type name, literal/type mismatch, undeclared cell, arity mismatch) — those checks
6//! are deferred to a later, separate compile-to-`Sheet` phase, mirroring
7//! [`cel_parser::AstContext`]'s design.
8
9pub use cel_parser::ExprSpan;
10use cel_parser::lex_lexer::Literal;
11
12pub use cel_parser::Comment;
13
14/// An ordered file of imports and sheet definitions, without resolved names or types.
15///
16/// Errors include malformed file items and errors recovered inside definitions.
17///
18/// # Examples
19///
20/// ```
21/// let module = adam_lang::AdamAstParser::new().parse_module_str(
22/// "use math::ops as operations;
23/// sheet component(input: i32, result: i32) {
24/// relationship { result := input; }
25/// }
26/// sheet root {
27/// source x: i32 = 1 filter 0..=10 require { @positive x > 0; };
28/// cell y: i32;
29/// cell z: i32;
30/// out pair: (i32, i32) := (x, y);
31/// relationship component(input: x, result: y);
32/// conditional true {
33/// true => { relationship { (y, z) := (x, x); } }
34/// _ => { relationship { y := x; } }
35/// }
36/// }"
37/// ).unwrap();
38/// assert!(module.errors.is_empty());
39/// assert_eq!(module.items.len(), 3);
40/// ```
41#[derive(Debug, Clone)]
42pub struct Module {
43 /// File items in source order, including recovery placeholders.
44 pub items: Vec<ModuleItem>,
45 /// Recovered syntax errors in source order.
46 pub errors: Vec<cel_parser::ParseError>,
47 /// Comments before each item and after the last item, populated by trivia attachment.
48 pub comments: Vec<Vec<Comment>>,
49}
50
51/// A file-level declaration or a recovered malformed declaration.
52///
53/// # Examples
54///
55/// See [`Module`] for a parsed file containing imports and sheet definitions.
56#[derive(Debug, Clone)]
57pub enum ModuleItem {
58 /// An explicit named import.
59 Use(UseDecl),
60 /// A root or parameterized definition.
61 Sheet(Sheet),
62 /// The span of a malformed file-level declaration.
63 Error(ExprSpan),
64}
65
66impl ModuleItem {
67 /// Returns the declaration or recovery span.
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// let module = adam_lang::AdamAstParser::new().parse_module_str("sheet root {}").unwrap();
73 /// assert_eq!(module.items[0].span().start.start().line, 1);
74 /// ```
75 pub fn span(&self) -> ExprSpan {
76 match self {
77 Self::Use(item) => item.span,
78 Self::Sheet(item) => item.span,
79 Self::Error(span) => *span,
80 }
81 }
82}
83
84/// `use_decl = "use" identifier { "::" identifier } [ "as" identifier ] ";".`
85///
86/// # Examples
87///
88/// See [`Module`] for an aliased import in a parsed file.
89#[derive(Debug, Clone)]
90pub struct UseDecl {
91 /// The `use` keyword's span, excluding preceding documentation.
92 pub keyword_span: ExprSpan,
93 /// Leading inner documentation, if present.
94 pub doc_comment: Option<String>,
95 /// Unresolved path segments and their individual spans.
96 pub path: Vec<(String, ExprSpan)>,
97 /// The optional local alias and its span.
98 pub alias: Option<(String, ExprSpan)>,
99 /// The whole import's span.
100 pub span: ExprSpan,
101}
102
103/// A required typed formal parameter, without a default or an initializer.
104///
105/// # Examples
106///
107/// See [`Module`] for the component's `input` and `result` parameters.
108#[derive(Debug, Clone)]
109pub struct ParameterDecl {
110 /// The formal parameter's name.
111 pub name: String,
112 /// The name token's span.
113 pub name_span: ExprSpan,
114 /// The required unresolved type expression.
115 pub type_name: TypeExpr,
116}
117
118/// A named binding from a formal parameter to a caller's cell identifier.
119///
120/// # Examples
121///
122/// See [`Module`] for `input: x` and `result: y` actual arguments.
123#[derive(Debug, Clone)]
124pub struct NamedArgument {
125 /// The formal parameter's name.
126 pub name: String,
127 /// The formal name token's span.
128 pub name_span: ExprSpan,
129 /// The actual cell's name.
130 pub cell: String,
131 /// The actual cell token's span.
132 pub cell_span: ExprSpan,
133}
134
135/// `relationship_use = "relationship" path "(" [ named_arguments ] ")" ";".`
136///
137/// # Examples
138///
139/// See [`Module`] for a component use with named actual arguments.
140#[derive(Debug, Clone)]
141pub struct RelationshipUse {
142 /// The `relationship` keyword's span, excluding any preceding documentation.
143 pub keyword_span: ExprSpan,
144 /// Unresolved callee path segments and their spans.
145 pub path: Vec<(String, ExprSpan)>,
146 /// Named cell arguments in source order.
147 pub arguments: Vec<NamedArgument>,
148 /// Every comment in the opening-brace gap when this use is the first body item.
149 /// Empty for a later item; its ordinary sibling trivia uses `leading_comment` instead.
150 pub opening_comments: Vec<Comment>,
151 /// A recovered leading comment.
152 pub leading_comment: Option<Comment>,
153 /// A leading documentation comment.
154 pub doc_comment: Option<String>,
155 /// A comment on the declaration's last source line.
156 pub trailing_line_comment: Option<Comment>,
157 /// Whether a blank line precedes this declaration.
158 pub blank_line_before: bool,
159 /// The whole declaration's span.
160 pub span: ExprSpan,
161}
162
163/// A conditional branch item, retaining inline and included relationship order.
164///
165/// # Examples
166///
167/// See [`Module`] for inline relationships in named and default branches.
168#[derive(Debug, Clone)]
169pub enum BranchItem {
170 /// An ordinary inline relationship.
171 Relationship(RelationshipDecl),
172 /// A named component use.
173 RelationshipUse(RelationshipUse),
174}
175
176/// A parsed adam-lang sheet declaration, with source spans on every node.
177///
178/// Built by [`crate::AdamAstParser`]; consumed by the language server, the formatter,
179/// and [`crate::ModuleCompiler`].
180///
181/// # Examples
182///
183/// See [`Module`] for a root sheet and a parameterized definition.
184#[derive(Debug, Clone)]
185pub struct Sheet {
186 /// The `sheet` keyword's span, excluding any preceding documentation.
187 pub keyword_span: ExprSpan,
188 /// The sheet's declared name.
189 pub name: String,
190 /// The name token's span.
191 pub name_span: ExprSpan,
192 /// `None` for a root; `Some`, including an empty vector, for a component.
193 pub parameters: Option<Vec<ParameterDecl>>,
194 /// The sheet's items, in declaration order.
195 pub items: Vec<SheetItem>,
196 /// A leading `//`/`/* */` comment immediately preceding the `sheet` keyword (e.g. a file
197 /// header), if recovered by [`crate::trivia::attach_trivia`]. Unlike every other node's
198 /// `leading_comment`, this one has no enclosing sibling list to attach via — it covers the
199 /// gap between the start of the source and the sheet's own span.
200 pub leading_comment: Option<Comment>,
201 /// A leading `//!` doc comment immediately preceding the `sheet` keyword, if recovered by
202 /// [`crate::AdamAstParser`].
203 pub doc_comment: Option<String>,
204 /// A trailing comment immediately preceding the sheet's own closing `}`, if recovered. See
205 /// <https://github.com/stlab/cel-rs/issues/52>.
206 pub trailing_comment: Option<Comment>,
207 /// Whether a blank line preceded the sheet's own closing `}`, if recovered. See
208 /// <https://github.com/stlab/cel-rs/issues/52>.
209 pub blank_line_before_close: bool,
210 /// The span of the sheet's own opening `{`, used to recover trailing trivia when `items` is
211 /// empty. See <https://github.com/stlab/cel-rs/issues/52>.
212 pub open_brace_span: ExprSpan,
213 /// The span of the whole `sheet ... { ... }` construct.
214 pub span: ExprSpan,
215 /// Syntax errors recovered while parsing, in source order. Empty for a syntactically clean
216 /// sheet.
217 pub errors: Vec<cel_parser::ParseError>,
218}
219
220/// One top-level item inside a `sheet { ... }` body.
221///
222/// # Examples
223///
224/// See [`Module`] for source, cell, output, relationship-use and conditional items.
225#[derive(Debug, Clone)]
226pub enum SheetItem {
227 /// A `cell` declaration.
228 Cell(CellDecl),
229 /// A `relationship` declaration.
230 Relationship(RelationshipDecl),
231 /// A named component use.
232 RelationshipUse(RelationshipUse),
233 /// A `conditional` declaration.
234 Conditional(ConditionalDecl),
235 /// An `out` declaration.
236 Out(OutDecl),
237 /// A `source` declaration.
238 Source(SourceDecl),
239 /// A syntax error recovered at declaration granularity; `span` covers the skipped tokens.
240 Error {
241 /// The span of the skipped, malformed item.
242 span: ExprSpan,
243 /// A leading `//`/`/* */` comment immediately preceding this item, if recovered by
244 /// [`crate::trivia::attach_trivia`]. Preserved even though the item failed to parse, so
245 /// a comment explaining a broken declaration (e.g. `// TODO: fix this`) isn't silently
246 /// dropped.
247 leading_comment: Option<Comment>,
248 /// A leading `///` doc comment immediately preceding this item, if recovered by
249 /// [`crate::AdamAstParser`] before parsing failed.
250 doc_comment: Option<String>,
251 /// A `//`/`/* */` comment on the same source line as this item's own last token, if
252 /// recovered by [`crate::trivia::attach_trivia`]. Distinct from `leading_comment`, which
253 /// precedes an item on its own line. See <https://github.com/stlab/cel-rs/issues/59>.
254 trailing_line_comment: Option<Comment>,
255 /// Whether the gap before this item contained a blank line, if recovered by
256 /// [`crate::trivia::attach_trivia`].
257 blank_line_before: bool,
258 },
259}
260
261impl SheetItem {
262 /// Returns this item's source span.
263 ///
264 /// # Examples
265 ///
266 /// ```
267 /// let sheet = adam_lang::AdamAstParser::new()
268 /// .parse_str("sheet s {\n source x: i32;\n}").unwrap();
269 /// assert_eq!(sheet.items[0].span().start.start().line, 2);
270 /// assert_eq!(sheet.items[0].span().start.start().column, 4);
271 /// ```
272 pub fn span(&self) -> ExprSpan {
273 match self {
274 SheetItem::Cell(c) => c.span,
275 SheetItem::Relationship(r) => r.span,
276 SheetItem::RelationshipUse(r) => r.span,
277 SheetItem::Conditional(c) => c.span,
278 SheetItem::Out(o) => o.span,
279 SheetItem::Source(s) => s.span,
280 SheetItem::Error { span, .. } => *span,
281 }
282 }
283
284 /// Sets this item's leading comment.
285 pub(crate) fn set_leading_comment(&mut self, comment: Comment) {
286 match self {
287 SheetItem::Cell(c) => c.leading_comment = Some(comment),
288 SheetItem::Relationship(r) => r.leading_comment = Some(comment),
289 SheetItem::RelationshipUse(r) => r.leading_comment = Some(comment),
290 SheetItem::Conditional(c) => c.leading_comment = Some(comment),
291 SheetItem::Out(o) => o.leading_comment = Some(comment),
292 SheetItem::Source(s) => s.leading_comment = Some(comment),
293 SheetItem::Error {
294 leading_comment, ..
295 } => *leading_comment = Some(comment),
296 }
297 }
298
299 /// Sets a `//`/`/* */` comment recovered on the same source line as this item's own last
300 /// token. See <https://github.com/stlab/cel-rs/issues/59>.
301 pub(crate) fn set_trailing_line_comment(&mut self, comment: Comment) {
302 match self {
303 SheetItem::Cell(c) => c.trailing_line_comment = Some(comment),
304 SheetItem::Relationship(r) => r.trailing_line_comment = Some(comment),
305 SheetItem::RelationshipUse(r) => r.trailing_line_comment = Some(comment),
306 SheetItem::Conditional(c) => c.trailing_line_comment = Some(comment),
307 SheetItem::Out(o) => o.trailing_line_comment = Some(comment),
308 SheetItem::Source(s) => s.trailing_line_comment = Some(comment),
309 SheetItem::Error {
310 trailing_line_comment,
311 ..
312 } => *trailing_line_comment = Some(comment),
313 }
314 }
315
316 /// Sets whether a blank line preceded this item.
317 pub(crate) fn set_blank_line_before(&mut self, value: bool) {
318 match self {
319 SheetItem::Cell(c) => c.blank_line_before = value,
320 SheetItem::Relationship(r) => r.blank_line_before = value,
321 SheetItem::RelationshipUse(r) => r.blank_line_before = value,
322 SheetItem::Conditional(c) => c.blank_line_before = value,
323 SheetItem::Out(o) => o.blank_line_before = value,
324 SheetItem::Source(s) => s.blank_line_before = value,
325 SheetItem::Error {
326 blank_line_before, ..
327 } => *blank_line_before = value,
328 }
329 }
330
331 /// Sets this item's doc comment and widens its span to start at `start` (the doc comment's
332 /// own first token), so `trivia::attach_trivia`'s gap scan stops before the doc comment's
333 /// source text instead of misparsing it as a plain `//` comment.
334 pub(crate) fn set_doc_comment(&mut self, text: String, start: proc_macro2::Span) {
335 match self {
336 SheetItem::Cell(c) => {
337 c.doc_comment = Some(text);
338 c.span.start = start;
339 }
340 SheetItem::Relationship(r) => {
341 r.doc_comment = Some(text);
342 r.span.start = start;
343 }
344 SheetItem::RelationshipUse(r) => {
345 r.doc_comment = Some(text);
346 r.span.start = start;
347 }
348 SheetItem::Conditional(c) => {
349 c.doc_comment = Some(text);
350 c.span.start = start;
351 }
352 SheetItem::Out(o) => {
353 o.doc_comment = Some(text);
354 o.span.start = start;
355 }
356 SheetItem::Source(s) => {
357 s.doc_comment = Some(text);
358 s.span.start = start;
359 }
360 SheetItem::Error {
361 doc_comment, span, ..
362 } => {
363 *doc_comment = Some(text);
364 span.start = start;
365 }
366 }
367 }
368}
369
370/// Re-exports CEL's shared named, generic, tuple, array, and callable type expressions.
371///
372/// `()` is the empty tuple type; `(T)` is grouping; `(T,)` is a singleton tuple.
373/// Larger tuples prohibit trailing commas. Both languages use the same parser.
374///
375/// # Examples
376///
377/// See [`crate::TypeRegistry::resolve`] for parsing and resolving a nested array-and-tuple type.
378pub use cel_parser::TypeExpr;
379
380/// `cell_decl = "cell" identifier cell_type_init [ cell_filter ] [ "require" "{" { requirement }
381/// "}" ] ";".`
382///
383/// `type_name`/`initializer` are unresolved — no `TypeRegistry` lookup, no literal validation.
384/// Parsed declarations contain an annotation, an initializer, or both. Public field storage
385/// does not enforce this invariant for manually constructed nodes.
386///
387/// # Examples
388///
389/// See [`Module`] for annotated cells in a parsed sheet.
390#[derive(Debug, Clone)]
391pub struct CellDecl {
392 /// The cell's declared name.
393 pub name: String,
394 /// The name token's span.
395 pub name_span: ExprSpan,
396 /// The `: type_expr` annotation, if present.
397 pub type_name: Option<TypeExpr>,
398 /// The `= expression` initializer, if present. Unresolved and unevaluated here — see
399 /// `crate::parser::AdamParser` for the compile-to-`Sheet` phase, which parses this with no
400 /// cell scope pushed and evaluates it eagerly, once, at parse time.
401 pub initializer: Option<cel_parser::Expr>,
402 /// The `filter` clause, if present.
403 pub filter: Option<CellFilter>,
404 /// The `require { ... }` validation block, if present.
405 pub require: Option<RequireBlock>,
406 /// A leading `//`/`/* */` comment immediately preceding this declaration, if recovered by
407 /// [`crate::trivia::attach_trivia`].
408 pub leading_comment: Option<Comment>,
409 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
410 /// [`crate::AdamAstParser`].
411 pub doc_comment: Option<String>,
412 /// A `//`/`/* */` comment on the same source line as this declaration's own last token, if
413 /// recovered by [`crate::trivia::attach_trivia`]. See
414 /// <https://github.com/stlab/cel-rs/issues/59>.
415 pub trailing_line_comment: Option<Comment>,
416 /// Whether a blank line preceded this declaration, if recovered by
417 /// [`crate::trivia::attach_trivia`].
418 pub blank_line_before: bool,
419 /// The span of the whole `cell ...;` declaration.
420 pub span: ExprSpan,
421}
422
423/// `source_decl = "source" identifier cell_type_init [ cell_filter ] [ "require" "{" {
424/// requirement } "}" ] ";".`
425///
426/// Same shape as [`CellDecl`]: a `source` cell's initializer is a one-time expression exactly like a
427/// plain `cell`'s, and it supports the same `filter` clause and `require` block.
428///
429/// # Examples
430///
431/// See [`Module`] for a source with an initializer, range filter and requirement.
432#[derive(Debug, Clone)]
433pub struct SourceDecl {
434 /// The source cell's declared name.
435 pub name: String,
436 /// The name token's span.
437 pub name_span: ExprSpan,
438 /// The `: type_expr` annotation, if present.
439 pub type_name: Option<TypeExpr>,
440 /// The `= expression` initializer, if present. Unresolved and unevaluated here — see
441 /// `crate::parser::AdamParser` for the compile-to-`Sheet` phase, which parses this with no
442 /// cell scope pushed and evaluates it eagerly, once, at parse time.
443 pub initializer: Option<cel_parser::Expr>,
444 /// The `filter` clause, if present.
445 pub filter: Option<CellFilter>,
446 /// The `require { ... }` validation block, if present.
447 pub require: Option<RequireBlock>,
448 /// A leading `//`/`/* */` comment immediately preceding this declaration, if recovered by
449 /// [`crate::trivia::attach_trivia`].
450 pub leading_comment: Option<Comment>,
451 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
452 /// [`crate::AdamAstParser`].
453 pub doc_comment: Option<String>,
454 /// A `//`/`/* */` comment on the same source line as this declaration's own last token, if
455 /// recovered by [`crate::trivia::attach_trivia`]. See
456 /// <https://github.com/stlab/cel-rs/issues/59>.
457 pub trailing_line_comment: Option<Comment>,
458 /// Whether a blank line preceded this declaration, if recovered by
459 /// [`crate::trivia::attach_trivia`].
460 pub blank_line_before: bool,
461 /// The span of the whole `source ...;` declaration.
462 pub span: ExprSpan,
463}
464
465/// `cell_filter = "filter" expression.`
466///
467/// # Examples
468///
469/// See [`Module`] for an inclusive range filter.
470#[derive(Debug, Clone)]
471pub struct CellFilter {
472 /// The filter's body expression. `_` inside it denotes the candidate value being conformed;
473 /// every other identifier that names an already-declared cell is a deduced dependency.
474 pub body: cel_parser::Expr,
475 /// The span of the whole `filter ...` clause.
476 pub span: ExprSpan,
477}
478
479/// `relationship_decl = "relationship" "{" { binding } "}".`
480///
481/// # Examples
482///
483/// See [`Module`] for direct and destructuring bindings in relationships.
484#[derive(Debug, Clone)]
485pub struct RelationshipDecl {
486 /// The relationship block's bindings, in declaration order.
487 pub bindings: Vec<BindingDecl>,
488 /// A leading comment immediately preceding this declaration, if recovered.
489 pub leading_comment: Option<Comment>,
490 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
491 /// [`crate::AdamAstParser`].
492 pub doc_comment: Option<String>,
493 /// A `//`/`/* */` comment on the same source line as this declaration's own closing `}`, if
494 /// recovered. Distinct from `trailing_comment`, which precedes this declaration's own
495 /// closing `}` on an earlier line. See <https://github.com/stlab/cel-rs/issues/59>.
496 pub trailing_line_comment: Option<Comment>,
497 /// Whether a blank line preceded this declaration, if recovered.
498 pub blank_line_before: bool,
499 /// A trailing comment immediately preceding this declaration's own closing `}`, if
500 /// recovered. See <https://github.com/stlab/cel-rs/issues/52>.
501 pub trailing_comment: Option<Comment>,
502 /// Whether a blank line preceded this declaration's own closing `}`, if recovered. See
503 /// <https://github.com/stlab/cel-rs/issues/52>.
504 pub blank_line_before_close: bool,
505 /// The span of this declaration's own opening `{`, used to recover trailing trivia when
506 /// `bindings` is empty. See <https://github.com/stlab/cel-rs/issues/52>.
507 pub open_brace_span: ExprSpan,
508 /// The span of the whole `relationship { ... }` declaration.
509 pub span: ExprSpan,
510}
511
512/// `binding = binding_target ":=" expression ";".`
513/// `binding_target = identifier | "(" identifier "," [ identifier { "," identifier } ] ")".`
514///
515/// The compiler deduces inputs from resolved cell references in `body`. The structural
516/// AST records only the explicit output names, without resolving cell declarations.
517///
518/// Parenthesizing the left-hand side requests tuple destructuring, matching Rust's tuple-pattern
519/// syntax: `(a, b) := ...` and the single-element `(a,) := ...` (trailing comma mandatory,
520/// exactly as in a Rust 1-tuple pattern) both destructure `body`'s tuple result element-wise into
521/// `outputs`. A bare identifier binds `body`'s whole result directly to that one output instead.
522/// A parenthesized binding target always requires a comma; `(a) := ...` is invalid.
523/// See [`Self::destructure`].
524///
525/// # Examples
526///
527/// See [`Module`] for both direct and destructuring binding targets.
528#[derive(Debug, Clone)]
529pub struct BindingDecl {
530 /// The binding's output cell names (the left-hand side), in declaration order.
531 pub outputs: Vec<(String, ExprSpan)>,
532 /// Whether the left-hand side requests destructuring (`(a, b) := ...` or `(a,) := ...`) as
533 /// opposed to a direct bind (`a := ...`). Always
534 /// `true` when `outputs.len() > 1`; when `outputs.len() == 1`, distinguishes "destructure
535 /// this single-element tuple" from "bind this whole value directly" — a distinction the
536 /// parenthesized-or-not left-hand side is the only way to express, since both forms name
537 /// exactly one output.
538 pub destructure: bool,
539 /// The parsed right-hand-side expression.
540 pub body: cel_parser::Expr,
541 /// A leading comment immediately preceding this binding, if recovered by
542 /// [`crate::trivia::attach_trivia`].
543 pub leading_comment: Option<Comment>,
544 /// A `//`/`/* */` comment on the same source line as this binding's own last token, if
545 /// recovered by [`crate::trivia::attach_trivia`]. See
546 /// <https://github.com/stlab/cel-rs/issues/59>.
547 pub trailing_line_comment: Option<Comment>,
548 /// Whether a blank line preceded this binding, if recovered by
549 /// [`crate::trivia::attach_trivia`].
550 pub blank_line_before: bool,
551 /// The span of the whole `a := ...;` / `(a, b) := ...;` declaration.
552 pub span: ExprSpan,
553}
554
555/// `out_decl = "out" identifier [ ":" type_expr ] ":=" expression [ cell_filter ] [ "require"
556/// "{" { requirement } "}" ] ";".`
557///
558/// `type_expr` is unresolved here (no `TypeRegistry` lookup), matching `CellDecl`. When
559/// absent, the cell's type is inferred from `initializer`'s result type by the compile phase
560/// (`crate::parser::AdamParser`) — never here.
561///
562/// # Examples
563///
564/// See [`Module`] for a tuple-valued annotated output.
565#[derive(Debug, Clone)]
566pub struct OutDecl {
567 /// The declared cell's name.
568 pub name: String,
569 /// The name token's span.
570 pub name_span: ExprSpan,
571 /// The `: type_expr` annotation, if present.
572 pub type_name: Option<TypeExpr>,
573 /// The parsed initializer expression that computes this cell's value.
574 pub initializer: cel_parser::Expr,
575 /// The `filter` clause, if present.
576 pub filter: Option<CellFilter>,
577 /// The `require { ... }` validation block, if present.
578 pub require: Option<RequireBlock>,
579 /// A leading `//`/`/* */` comment immediately preceding this declaration, if recovered by
580 /// [`crate::trivia::attach_trivia`].
581 pub leading_comment: Option<Comment>,
582 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
583 /// [`crate::AdamAstParser`].
584 pub doc_comment: Option<String>,
585 /// A `//`/`/* */` comment on the same source line as this declaration's own last token, if
586 /// recovered by [`crate::trivia::attach_trivia`]. See
587 /// <https://github.com/stlab/cel-rs/issues/59>.
588 pub trailing_line_comment: Option<Comment>,
589 /// Whether a blank line preceded this declaration, if recovered by
590 /// [`crate::trivia::attach_trivia`].
591 pub blank_line_before: bool,
592 /// The span of the whole `out ... ;` declaration.
593 pub span: ExprSpan,
594}
595
596/// An optional `require { ... }` block on a cell, source or output declaration.
597///
598/// # Examples
599///
600/// See [`Module`] for a labeled requirement on a source.
601#[derive(Debug, Clone)]
602pub struct RequireBlock {
603 /// The block's requirements, in declaration order.
604 pub requirements: Vec<RequirementDecl>,
605 /// A trailing comment immediately preceding this block's own closing `}`, if recovered.
606 /// See <https://github.com/stlab/cel-rs/issues/52>.
607 pub trailing_comment: Option<Comment>,
608 /// Whether a blank line preceded this block's own closing `}`, if recovered. See
609 /// <https://github.com/stlab/cel-rs/issues/52>.
610 pub blank_line_before_close: bool,
611 /// The span of this block's own opening `{`, used to recover trailing trivia when
612 /// `requirements` is empty.
613 pub open_brace_span: ExprSpan,
614 /// The span of the whole `require { ... }` block.
615 pub span: ExprSpan,
616}
617
618/// `requirement = [ "@" identifier ] expression ";".`
619///
620/// `name`, when present, is a plain string label passed to `adam_rs::Sheet::add_requirement`,
621/// not a cell reference — it may coincide with a cell name declared elsewhere in the sheet but
622/// doesn't have to.
623///
624/// # Examples
625///
626/// See [`Module`] for an explicitly labeled Boolean requirement.
627#[derive(Debug, Clone)]
628pub struct RequirementDecl {
629 /// The requirement's declared label, if the `@identifier` marker was present.
630 pub name: Option<String>,
631 /// The `@identifier` marker's span, if present.
632 pub name_span: Option<ExprSpan>,
633 /// The parsed requirement body expression; must type-check as `bool`.
634 pub body: cel_parser::Expr,
635 /// A leading comment immediately preceding this requirement, if recovered by
636 /// [`crate::trivia::attach_trivia`].
637 pub leading_comment: Option<Comment>,
638 /// A `//`/`/* */` comment on the same source line as this requirement's own last token, if
639 /// recovered by [`crate::trivia::attach_trivia`]. See
640 /// <https://github.com/stlab/cel-rs/issues/59>.
641 pub trailing_line_comment: Option<Comment>,
642 /// Whether a blank line preceded this requirement, if recovered by
643 /// [`crate::trivia::attach_trivia`].
644 pub blank_line_before: bool,
645 /// The span of the whole `[ "@" identifier ] expr;` declaration.
646 pub span: ExprSpan,
647}
648
649/// `conditional_decl = "conditional" expression "{" { conditional_branch } "}".`
650///
651/// # Examples
652///
653/// See [`Module`] for a conditional with named and default branches.
654#[derive(Debug, Clone)]
655pub struct ConditionalDecl {
656 /// The match subject: an arbitrary expression over already-declared cells (a bare
657 /// identifier, e.g. `mode`, is the degenerate single-cell case).
658 pub match_expr: cel_parser::Expr,
659 /// The named (literal `=>`) branches, in declaration order.
660 pub branches: Vec<ConditionalBranch>,
661 /// The `_ => { ... }` default branch, if present.
662 pub default: Option<DefaultBranch>,
663 /// A leading comment immediately preceding this declaration, if recovered.
664 pub leading_comment: Option<Comment>,
665 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
666 /// [`crate::AdamAstParser`].
667 pub doc_comment: Option<String>,
668 /// A `//`/`/* */` comment on the same source line as this declaration's own closing `}`, if
669 /// recovered. Distinct from `trailing_comment`, which precedes this declaration's own
670 /// closing `}` on an earlier line. See <https://github.com/stlab/cel-rs/issues/59>.
671 pub trailing_line_comment: Option<Comment>,
672 /// Whether a blank line preceded this declaration, if recovered.
673 pub blank_line_before: bool,
674 /// A trailing comment immediately preceding this declaration's own closing `}`, if
675 /// recovered. See <https://github.com/stlab/cel-rs/issues/52>.
676 pub trailing_comment: Option<Comment>,
677 /// Whether a blank line preceded this declaration's own closing `}`, if recovered. See
678 /// <https://github.com/stlab/cel-rs/issues/52>.
679 pub blank_line_before_close: bool,
680 /// The span of this declaration's own opening `{`, used to recover trailing trivia when
681 /// there are no `branches`/`default`. See <https://github.com/stlab/cel-rs/issues/52>.
682 pub open_brace_span: ExprSpan,
683 /// The span of the whole `conditional ... { ... }` declaration.
684 pub span: ExprSpan,
685}
686
687/// The `_ => { ... }` default branch of a `conditional_decl`, mirroring `ConditionalBranch`'s
688/// shape (it has no match literal of its own).
689///
690/// # Examples
691///
692/// See [`Module`] for a conditional with a terminal default branch.
693#[derive(Debug, Clone)]
694pub struct DefaultBranch {
695 /// Comments before the default pattern, in source order.
696 pub leading_comment: Option<Comment>,
697 /// Whether a blank line precedes the default pattern.
698 pub blank_line_before: bool,
699 /// The default branch's relationships, in declaration order.
700 pub relationships: Vec<BranchItem>,
701 /// A trailing comment immediately preceding this branch's own closing `}`, if recovered.
702 /// See <https://github.com/stlab/cel-rs/issues/52>.
703 pub trailing_comment: Option<Comment>,
704 /// A `//`/`/* */` comment on the same source line as this branch's own closing `}` (between
705 /// it and the enclosing conditional's own closing `}`), if recovered. See
706 /// <https://github.com/stlab/cel-rs/issues/59>.
707 pub trailing_line_comment: Option<Comment>,
708 /// Whether a blank line preceded this branch's own closing `}`, if recovered.
709 pub blank_line_before_close: bool,
710 /// The span of this branch's own opening `{`, used to recover trailing trivia when
711 /// `relationships` is empty.
712 pub open_brace_span: ExprSpan,
713 /// The span from the `_` token through this branch's own closing `}`.
714 pub span: ExprSpan,
715}
716
717/// `conditional_branch = literal_pattern "=>" "{" { relationship_item } "}".`
718/// `literal_pattern = ["-"] literal.`
719///
720/// # Examples
721///
722/// See [`Module`] for a Boolean literal branch containing a destructuring binding.
723#[derive(Debug, Clone)]
724pub struct ConditionalBranch {
725 /// The branch's unresolved match literal, always stored unsigned; see [`Self::negated`]
726 /// for whether a leading `-` applies to it.
727 pub literal: Literal,
728 /// Whether the branch key is negated by a leading `-` (Rust's own `LiteralPattern` rule —
729 /// see <https://doc.rust-lang.org/reference/patterns.html#literal-patterns>). A `-`, if
730 /// present, precedes [`Self::literal_span`] and is covered by [`Self::span`]'s start, but
731 /// not by `literal_span` itself.
732 pub negated: bool,
733 /// The literal token's own span (never includes a leading `-`; see [`Self::negated`]).
734 pub literal_span: ExprSpan,
735 /// The branch's relationships, in declaration order.
736 pub relationships: Vec<BranchItem>,
737 /// A leading comment immediately preceding this branch, if recovered by
738 /// [`crate::trivia::attach_trivia`].
739 pub leading_comment: Option<Comment>,
740 /// A `//`/`/* */` comment on the same source line as this branch's own closing `}`, if
741 /// recovered. Distinct from `trailing_comment`, which precedes this branch's own closing
742 /// `}` on an earlier line. See <https://github.com/stlab/cel-rs/issues/59>.
743 pub trailing_line_comment: Option<Comment>,
744 /// Whether a blank line preceded this branch, if recovered by
745 /// [`crate::trivia::attach_trivia`].
746 pub blank_line_before: bool,
747 /// A trailing comment immediately preceding this branch's own closing `}`, if recovered.
748 /// See <https://github.com/stlab/cel-rs/issues/52>.
749 pub trailing_comment: Option<Comment>,
750 /// Whether a blank line preceded this branch's own closing `}`, if recovered. See
751 /// <https://github.com/stlab/cel-rs/issues/52>.
752 pub blank_line_before_close: bool,
753 /// The span of this branch's own opening `{`, used to recover trailing trivia when
754 /// `relationships` is empty. See <https://github.com/stlab/cel-rs/issues/52>.
755 pub open_brace_span: ExprSpan,
756 /// The span from the branch's literal through its closing `}`.
757 pub span: ExprSpan,
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763 use proc_macro2::Span;
764
765 /// Checks nondefault source coordinates and independent trivia replacement for every variant.
766 #[test]
767 fn sheet_item_operations_preserve_unrelated_fields_for_every_variant() {
768 let source = "sheet s {\ncell a: i32;\nsource b: i32;\nout c := 1;\nrelationship {}\nrelationship component();\nconditional true {}\nbogus;\n}";
769 let mut sheet = crate::AdamAstParser::new().parse_str(source).unwrap();
770 assert_eq!(sheet.errors.len(), 1);
771 assert_eq!(sheet.items.len(), 7);
772 let replacement = "header"
773 .parse::<proc_macro2::TokenStream>()
774 .unwrap()
775 .into_iter()
776 .next()
777 .unwrap()
778 .span();
779 for (index, item) in sheet.items.iter_mut().enumerate() {
780 let original = item.span();
781 assert_eq!(original.start.start().line, index + 2);
782 assert_eq!(original.start.start().column, 0);
783 item.set_leading_comment(Comment::Line("old".into()));
784 item.set_leading_comment(Comment::Block("leading".into()));
785 item.set_trailing_line_comment(Comment::Line("trailing".into()));
786 item.set_blank_line_before(true);
787 item.set_blank_line_before(false);
788 item.set_doc_comment("documentation".into(), replacement);
789 assert_eq!(item.span().start.start(), replacement.start());
790 assert_eq!(item.span().end.end(), original.end.end());
791 let (leading, trailing, blank, doc) = item_trivia(item);
792 assert_eq!(leading, Some(&Comment::Block("leading".into())));
793 assert_eq!(trailing, Some(&Comment::Line("trailing".into())));
794 assert!(!blank);
795 assert_eq!(doc, Some("documentation"));
796 }
797 }
798
799 /// Borrows each variant's trivia without changing its span or contents.
800 fn item_trivia(item: &SheetItem) -> (Option<&Comment>, Option<&Comment>, bool, Option<&str>) {
801 match item {
802 SheetItem::Cell(c) => (
803 c.leading_comment.as_ref(),
804 c.trailing_line_comment.as_ref(),
805 c.blank_line_before,
806 c.doc_comment.as_deref(),
807 ),
808 SheetItem::Source(c) => (
809 c.leading_comment.as_ref(),
810 c.trailing_line_comment.as_ref(),
811 c.blank_line_before,
812 c.doc_comment.as_deref(),
813 ),
814 SheetItem::Out(c) => (
815 c.leading_comment.as_ref(),
816 c.trailing_line_comment.as_ref(),
817 c.blank_line_before,
818 c.doc_comment.as_deref(),
819 ),
820 SheetItem::Relationship(c) => (
821 c.leading_comment.as_ref(),
822 c.trailing_line_comment.as_ref(),
823 c.blank_line_before,
824 c.doc_comment.as_deref(),
825 ),
826 SheetItem::RelationshipUse(c) => (
827 c.leading_comment.as_ref(),
828 c.trailing_line_comment.as_ref(),
829 c.blank_line_before,
830 c.doc_comment.as_deref(),
831 ),
832 SheetItem::Conditional(c) => (
833 c.leading_comment.as_ref(),
834 c.trailing_line_comment.as_ref(),
835 c.blank_line_before,
836 c.doc_comment.as_deref(),
837 ),
838 SheetItem::Error {
839 leading_comment,
840 trailing_line_comment,
841 blank_line_before,
842 doc_comment,
843 ..
844 } => (
845 leading_comment.as_ref(),
846 trailing_line_comment.as_ref(),
847 *blank_line_before,
848 doc_comment.as_deref(),
849 ),
850 }
851 }
852
853 /// Wraps a token span as a point expression span.
854 fn point(span: Span) -> ExprSpan {
855 ExprSpan {
856 start: span,
857 end: span,
858 }
859 }
860
861 /// Returns the stored cell declaration span.
862 #[test]
863 fn sheet_item_span_reads_the_cell_variant() {
864 let span = point(Span::call_site());
865 let item = SheetItem::Cell(CellDecl {
866 name: "x".to_string(),
867 name_span: span,
868 type_name: None,
869 initializer: None,
870 filter: None,
871 require: None,
872 leading_comment: None,
873 doc_comment: None,
874 trailing_line_comment: None,
875 blank_line_before: false,
876 span,
877 });
878 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
879 }
880
881 /// Returns the stored inline relationship span.
882 #[test]
883 fn sheet_item_span_reads_the_relationship_variant() {
884 let span = point(Span::call_site());
885 let item = SheetItem::Relationship(RelationshipDecl {
886 bindings: Vec::new(),
887 leading_comment: None,
888 doc_comment: None,
889 trailing_line_comment: None,
890 blank_line_before: false,
891 trailing_comment: None,
892 blank_line_before_close: false,
893 open_brace_span: span,
894 span,
895 });
896 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
897 }
898
899 /// Returns the stored conditional declaration span.
900 #[test]
901 fn sheet_item_span_reads_the_conditional_variant() {
902 let span = point(Span::call_site());
903 let item = SheetItem::Conditional(ConditionalDecl {
904 match_expr: cel_parser::Expr::Ident {
905 name: "m".to_string(),
906 span,
907 },
908 branches: Vec::new(),
909 default: None,
910 leading_comment: None,
911 doc_comment: None,
912 trailing_line_comment: None,
913 blank_line_before: false,
914 trailing_comment: None,
915 blank_line_before_close: false,
916 open_brace_span: span,
917 span,
918 });
919 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
920 }
921
922 /// Returns the stored recovery range.
923 #[test]
924 fn sheet_item_span_reads_the_error_variant() {
925 let span = point(Span::call_site());
926 let item = SheetItem::Error {
927 span,
928 leading_comment: None,
929 doc_comment: None,
930 trailing_line_comment: None,
931 blank_line_before: false,
932 };
933 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
934 }
935
936 /// Attaches a leading comment to a cell.
937 #[test]
938 fn set_leading_comment_sets_the_cell_variant() {
939 let span = point(Span::call_site());
940 let mut item = SheetItem::Cell(CellDecl {
941 name: "x".to_string(),
942 name_span: span,
943 type_name: None,
944 initializer: None,
945 filter: None,
946 require: None,
947 leading_comment: None,
948 doc_comment: None,
949 trailing_line_comment: None,
950 blank_line_before: false,
951 span,
952 });
953 item.set_leading_comment(Comment::Line("hi".to_string()));
954 match item {
955 SheetItem::Cell(c) => {
956 assert_eq!(c.leading_comment, Some(Comment::Line("hi".to_string())))
957 }
958 other => panic!("expected Cell, got {other:?}"),
959 }
960 }
961
962 /// Attaches a leading comment to a recovery placeholder.
963 #[test]
964 fn set_leading_comment_sets_the_error_variant() {
965 let span = point(Span::call_site());
966 let mut item = SheetItem::Error {
967 span,
968 leading_comment: None,
969 doc_comment: None,
970 trailing_line_comment: None,
971 blank_line_before: false,
972 };
973 item.set_leading_comment(Comment::Line("hi".to_string()));
974 match item {
975 SheetItem::Error {
976 leading_comment, ..
977 } => {
978 assert_eq!(leading_comment, Some(Comment::Line("hi".to_string())))
979 }
980 other => panic!("expected Error, got {other:?}"),
981 }
982 }
983
984 /// Marks a cell as preceded by a blank line.
985 #[test]
986 fn set_blank_line_before_sets_the_cell_variant() {
987 let span = point(Span::call_site());
988 let mut item = SheetItem::Cell(CellDecl {
989 name: "x".to_string(),
990 name_span: span,
991 type_name: None,
992 initializer: None,
993 filter: None,
994 require: None,
995 leading_comment: None,
996 doc_comment: None,
997 trailing_line_comment: None,
998 blank_line_before: false,
999 span,
1000 });
1001 item.set_blank_line_before(true);
1002 match item {
1003 SheetItem::Cell(c) => assert!(c.blank_line_before),
1004 other => panic!("expected Cell, got {other:?}"),
1005 }
1006 }
1007
1008 /// Returns the stored output declaration span.
1009 #[test]
1010 fn sheet_item_span_reads_the_out_variant() {
1011 let span = point(Span::call_site());
1012 let item = SheetItem::Out(OutDecl {
1013 name: "o".to_string(),
1014 name_span: span,
1015 type_name: None,
1016 initializer: cel_parser::Expr::Ident {
1017 name: "x".to_string(),
1018 span,
1019 },
1020 filter: None,
1021 require: None,
1022 leading_comment: None,
1023 doc_comment: None,
1024 trailing_line_comment: None,
1025 blank_line_before: false,
1026 span,
1027 });
1028 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
1029 }
1030
1031 /// Attaches a leading comment to an output.
1032 #[test]
1033 fn set_leading_comment_sets_the_out_variant() {
1034 let span = point(Span::call_site());
1035 let mut item = SheetItem::Out(OutDecl {
1036 name: "o".to_string(),
1037 name_span: span,
1038 type_name: None,
1039 initializer: cel_parser::Expr::Ident {
1040 name: "x".to_string(),
1041 span,
1042 },
1043 filter: None,
1044 require: None,
1045 leading_comment: None,
1046 doc_comment: None,
1047 trailing_line_comment: None,
1048 blank_line_before: false,
1049 span,
1050 });
1051 item.set_leading_comment(Comment::Line("hi".to_string()));
1052 match item {
1053 SheetItem::Out(o) => {
1054 assert_eq!(o.leading_comment, Some(Comment::Line("hi".to_string())))
1055 }
1056 other => panic!("expected Out, got {other:?}"),
1057 }
1058 }
1059
1060 /// Accepts the CEL parser type expression without conversion.
1061 #[test]
1062 fn ast_type_expr_is_the_shared_cel_parser_type_expr() {
1063 /// Requires the expression's static type to match the shared parser type.
1064 fn assert_same_type<T>(_: &T) {}
1065 let span = cel_parser::ExprSpan {
1066 start: Span::call_site(),
1067 end: Span::call_site(),
1068 };
1069 let expr: TypeExpr = cel_parser::TypeExpr::Named {
1070 name: "i32".to_string(),
1071 args: Vec::new(),
1072 span,
1073 };
1074 assert_same_type::<cel_parser::TypeExpr>(&expr);
1075 }
1076
1077 /// Returns a named type expression's stored span.
1078 #[test]
1079 fn type_expr_named_span_is_its_own_span() {
1080 let span = point(Span::call_site());
1081 let expr = TypeExpr::Named {
1082 name: "i32".to_string(),
1083 args: Vec::new(),
1084 span,
1085 };
1086 assert_eq!(format!("{:?}", expr.span()), format!("{span:?}"));
1087 }
1088
1089 /// Returns a tuple type expression's stored span.
1090 #[test]
1091 fn type_expr_tuple_span_is_the_whole_parenthesized_span() {
1092 let span = point(Span::call_site());
1093 let expr = TypeExpr::Tuple {
1094 elements: Vec::new(),
1095 span,
1096 };
1097 assert_eq!(format!("{:?}", expr.span()), format!("{span:?}"));
1098 }
1099}