1use std::any::{Any, TypeId};
6use std::cell::RefCell;
7use std::collections::{HashMap, HashSet};
8use std::str::FromStr;
9use std::sync::{Arc, Mutex};
10
11use indexmap::IndexMap;
12
13use adam_rs::{
14 CellId, ErrorSite, MatchExpr, Method, RelationshipId, Requirement, RequirementId, Sheet,
15};
16use cel_parser::lex_lexer::{HasSpan, LexLexer, Token};
17use cel_parser::{CELParser, OpLookup, ParseError, SourceSpan};
18use cel_runtime::DynSegment;
19use proc_macro2::{Span, TokenStream};
20
21use crate::TypeRegistry;
22use crate::cell_value::{CellValue, Frame, RawFrame};
23use crate::type_registry::{AddConditionalFn, TypeShape};
24
25pub type Result<T> = std::result::Result<T, ParseError>;
27
28type NamedCells = Vec<(String, CellId, TypeShape)>;
32
33pub struct ParsedSheet {
55 pub sheet: Sheet,
57 pub cell_names: IndexMap<String, (CellId, TypeShape)>,
59 pub output_names: IndexMap<String, CellId>,
63 pub requirement_names: HashMap<RequirementId, String>,
67 pub method_spans: HashMap<(RelationshipId, usize), SourceSpan>,
80 pub relationship_spans: HashMap<RelationshipId, SourceSpan>,
84 pub cell_spans: HashMap<CellId, SourceSpan>,
88}
89
90impl std::fmt::Debug for ParsedSheet {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("ParsedSheet")
93 .field("cell_names", &self.cell_names)
94 .field("output_names", &self.output_names)
95 .field("requirement_names", &self.requirement_names)
96 .field("method_spans", &self.method_spans)
97 .field("relationship_spans", &self.relationship_spans)
98 .field("cell_spans", &self.cell_spans)
99 .finish()
100 }
101}
102
103impl ParsedSheet {
104 pub fn read_compound<R: cel_runtime::ValueRepr>(&self, id: CellId) -> anyhow::Result<R::Value> {
120 let cell = self.sheet.read::<CellValue>(id)?;
121 anyhow::ensure!(
122 cell.0.value_type().same_shape(&R::value_type()),
123 "compound cell representation mismatch"
124 );
125 Ok(unsafe { cell.0.try_clone()?.into_typed_bound::<R>() })
127 }
128
129 pub fn write_compound<R: cel_runtime::ValueRepr>(
146 &mut self,
147 id: CellId,
148 value: R::Value,
149 ) -> anyhow::Result<()> {
150 let descriptor = self.sheet.read::<CellValue>(id)?.0.value_type().clone();
151 let value = cel_runtime::OwnedValue::new::<R>(value).with_value_type(descriptor)?;
152 self.sheet.write(id, CellValue(value))?;
153 Ok(())
154 }
155
156 pub fn source_compound<R: cel_runtime::ValueRepr>(
172 &self,
173 id: CellId,
174 ) -> anyhow::Result<R::Value> {
175 let cell = self.sheet.source::<CellValue>(id)?;
176 anyhow::ensure!(
177 cell.0.value_type().same_shape(&R::value_type()),
178 "compound cell representation mismatch"
179 );
180 Ok(unsafe { cell.0.try_clone()?.into_typed_bound::<R>() })
182 }
183
184 pub fn locate_error(&self, e: &adam_rs::Error) -> Vec<(SourceSpan, String)> {
213 let by_id: HashMap<CellId, String> = self
214 .cell_names
215 .iter()
216 .map(|(n, (id, _))| (*id, n.clone()))
217 .collect();
218 let name = |id: CellId| by_id.get(&id).cloned();
219 let mut out = Vec::new();
220 for (i, site) in e.sites().iter().enumerate() {
221 let span = match site {
222 ErrorSite::Method(r, idx) => self.method_spans.get(&(*r, *idx)).copied(),
223 ErrorSite::Relationship(r) => self.relationship_spans.get(r).copied(),
224 ErrorSite::Cell(c) => self.cell_spans.get(c).copied(),
225 ErrorSite::MethodIndex(_) => None,
226 _ => None,
227 };
228 if let Some(span) = span {
229 out.push((span, crate::error_labels::site_label(e, i, &name)));
230 }
231 }
232 out
233 }
234}
235
236impl std::ops::Deref for ParsedSheet {
237 type Target = Sheet;
238
239 fn deref(&self) -> &Sheet {
240 &self.sheet
241 }
242}
243
244impl std::ops::DerefMut for ParsedSheet {
245 fn deref_mut(&mut self) -> &mut Sheet {
246 &mut self.sheet
247 }
248}
249
250struct ParseContext {
256 cursor: crate::token_cursor::TokenCursor,
257 sheet: Sheet,
258 cell_names: IndexMap<String, (CellId, TypeShape)>,
261 output_names: IndexMap<String, CellId>,
264 requirement_names: HashMap<RequirementId, String>,
266 method_spans: HashMap<(RelationshipId, usize), SourceSpan>,
269 relationship_spans: HashMap<RelationshipId, SourceSpan>,
272 cell_spans: HashMap<CellId, SourceSpan>,
275 cell_token_spans: HashMap<CellId, Span>,
277}
278
279impl std::ops::Deref for ParseContext {
280 type Target = crate::token_cursor::TokenCursor;
281
282 fn deref(&self) -> &crate::token_cursor::TokenCursor {
283 &self.cursor
284 }
285}
286
287impl std::ops::DerefMut for ParseContext {
288 fn deref_mut(&mut self) -> &mut crate::token_cursor::TokenCursor {
289 &mut self.cursor
290 }
291}
292
293pub struct AdamParser {
309 pub(crate) types: TypeRegistry,
310 pub(crate) cel: CELParser,
311}
312
313impl AdamParser {
314 pub fn new(types: TypeRegistry, op_lookup: OpLookup) -> Self {
323 let cel = CELParser::with_type_resolver(op_lookup, types.cel_type_resolver());
324 AdamParser { types, cel }
325 }
326
327 pub fn op_lookup_mut(&mut self) -> &mut OpLookup {
329 self.cel.op_lookup_mut()
330 }
331
332 pub fn parse_str(&mut self, source: &str) -> Result<ParsedSheet> {
343 let stream =
344 TokenStream::from_str(source).map_err(|e| ParseError::from_lex_error(source, e))?;
345 let mut ctx = ParseContext {
346 cursor: crate::token_cursor::TokenCursor::new(
347 LexLexer::new(stream.into_iter()).peekable(),
348 ),
349 sheet: Sheet::new(),
350 cell_names: IndexMap::new(),
351 output_names: IndexMap::new(),
352 requirement_names: HashMap::new(),
353 method_spans: HashMap::new(),
354 relationship_spans: HashMap::new(),
355 cell_spans: HashMap::new(),
356 cell_token_spans: HashMap::new(),
357 };
358 let _ = ctx.consume_doc_comment_run(true); self.parse_sheet(&mut ctx)?;
360 if let Some(tok) = ctx.peek_token() {
361 return Err(ParseError::new("unexpected token", tok.span()));
362 }
363 ctx.sheet
364 .validate()
365 .map_err(|e| Self::validated_sheet_error(&ctx, e))?;
366 Ok(ParsedSheet {
367 sheet: ctx.sheet,
368 cell_names: ctx.cell_names,
369 output_names: ctx.output_names,
370 requirement_names: ctx.requirement_names,
371 method_spans: ctx.method_spans,
372 relationship_spans: ctx.relationship_spans,
373 cell_spans: ctx.cell_spans,
374 })
375 }
376
377 fn parse_sheet(&mut self, ctx: &mut ParseContext) -> Result<()> {
383 if !ctx.is_keyword("sheet") {
384 return Err(ctx.err_at("expected `sheet`"));
385 }
386 ctx.consume_ident()?; ctx.expect_open_brace()?;
388 while !ctx.at_close_brace() {
389 self.parse_sheet_item(ctx)?;
390 }
391 ctx.expect_close_brace()?;
392 Ok(())
393 }
394
395 fn parse_sheet_item(&mut self, ctx: &mut ParseContext) -> Result<()> {
398 let _ = ctx.consume_doc_comment_run(false); match ctx.peek_token() {
400 Some(Token::Identifier(id)) if id == "cell" => self.parse_cell_decl(ctx),
401 Some(Token::Identifier(id)) if id == "relationship" => {
402 self.parse_relationship_decl(ctx).map(|_| ())
403 }
404 Some(Token::Identifier(id)) if id == "conditional" => self.parse_conditional_decl(ctx),
405 Some(Token::Identifier(id)) if id == "out" => self.parse_out_decl(ctx),
406 Some(Token::Identifier(id)) if id == "source" => self.parse_source_decl(ctx),
407 Some(tok) => Err(ParseError::new(
408 "expected `cell`, `relationship`, `conditional`, `out`, or `source`",
409 tok.span(),
410 )),
411 None => Err(ParseError::new(
412 "unexpected end of input",
413 Span::call_site(),
414 )),
415 }
416 }
417
418 fn parse_cell_decl(&mut self, ctx: &mut ParseContext) -> Result<()> {
423 ctx.is_keyword("cell"); let (name, name_span) = ctx.consume_ident()?;
425 if ctx.cell_names.contains_key(&name) {
426 return Err(ParseError::new(
427 format!("duplicate cell `{name}`"),
428 name_span,
429 ));
430 }
431
432 let declared_shape: Option<TypeShape> = if ctx.consume_punct(":") {
433 let type_expr = self.parse_type_expr(ctx)?;
434 Some(
435 self.types
436 .resolve(&type_expr)
437 .map_err(|(msg, span)| ParseError::new(msg, span))?,
438 )
439 } else {
440 None
441 };
442
443 let has_initializer = ctx.consume_punct("=");
444 let (shape, cell_id) = if has_initializer {
445 let segment = self.parse_cel_expression(ctx)?;
446 self.validate_initializer(&segment, declared_shape.as_ref(), "cell", &name, name_span)?;
447 let (actual_shape, cell_id) = self
448 .build_cell_from_segment(segment, ctx)
449 .map_err(|e| ParseError::new(e.message(), name_span))?;
450 (actual_shape, cell_id)
451 } else {
452 let declared = declared_shape.ok_or_else(|| {
453 ParseError::new("expected `:` or `=` in cell declaration", name_span)
454 })?;
455 let cell_id = self.build_default_cell(&declared, name_span, ctx)?;
456 (declared, cell_id)
457 };
458 ctx.cell_spans
459 .insert(cell_id, SourceSpan::from_proc_macro2(name_span));
460 ctx.cell_token_spans.insert(cell_id, name_span);
461
462 let filter = if ctx.is_keyword("filter") {
463 Some(self.parse_cell_filter(ctx, &name, name_span, &shape)?)
464 } else {
465 None
466 };
467
468 ctx.cell_names.insert(name.clone(), (cell_id, shape));
473
474 let require_names_and_reqs: Vec<(Option<String>, Requirement)> =
475 if ctx.is_keyword("require") {
476 self.parse_requirement_block(ctx)?
477 } else {
478 Vec::new()
479 };
480
481 ctx.expect_punct(";")?;
482 if let Some(filter) = filter {
483 ctx.sheet
484 .add_filter(cell_id, filter)
485 .map_err(|e| Self::sheet_error(ctx, name_span, e))?;
486 }
487 for (req_name, requirement) in require_names_and_reqs {
488 Self::register_requirement(ctx, cell_id, req_name, requirement, name_span)?;
489 }
490 Ok(())
491 }
492
493 fn parse_source_decl(&mut self, ctx: &mut ParseContext) -> Result<()> {
502 ctx.is_keyword("source"); let (name, name_span) = ctx.consume_ident()?;
504 if ctx.cell_names.contains_key(&name) {
505 return Err(ParseError::new(
506 format!("duplicate cell `{name}`"),
507 name_span,
508 ));
509 }
510
511 let declared_shape: Option<TypeShape> = if ctx.consume_punct(":") {
512 let type_expr = self.parse_type_expr(ctx)?;
513 Some(
514 self.types
515 .resolve(&type_expr)
516 .map_err(|(msg, span)| ParseError::new(msg, span))?,
517 )
518 } else {
519 None
520 };
521
522 let has_initializer = ctx.consume_punct("=");
523 let (shape, cell_id) = if has_initializer {
524 let segment = self.parse_cel_expression(ctx)?;
525 self.validate_initializer(
526 &segment,
527 declared_shape.as_ref(),
528 "source",
529 &name,
530 name_span,
531 )?;
532 let (actual_shape, cell_id) = self
533 .build_source_cell_from_segment(segment, ctx)
534 .map_err(|e| ParseError::new(e.message(), name_span))?;
535 (actual_shape, cell_id)
536 } else {
537 let declared = declared_shape.ok_or_else(|| {
538 ParseError::new("expected `:` or `=` in source declaration", name_span)
539 })?;
540 let cell_id = self.build_default_source_cell(&declared, name_span, ctx)?;
541 (declared, cell_id)
542 };
543 ctx.cell_spans
544 .insert(cell_id, SourceSpan::from_proc_macro2(name_span));
545 ctx.cell_token_spans.insert(cell_id, name_span);
546
547 let filter = if ctx.is_keyword("filter") {
548 Some(self.parse_cell_filter(ctx, &name, name_span, &shape)?)
549 } else {
550 None
551 };
552
553 ctx.cell_names.insert(name.clone(), (cell_id, shape));
556
557 let require_names_and_reqs: Vec<(Option<String>, Requirement)> =
558 if ctx.is_keyword("require") {
559 self.parse_requirement_block(ctx)?
560 } else {
561 Vec::new()
562 };
563
564 ctx.expect_punct(";")?;
565 if let Some(filter) = filter {
566 ctx.sheet
567 .add_filter(cell_id, filter)
568 .map_err(|e| Self::sheet_error(ctx, name_span, e))?;
569 }
570 for (req_name, requirement) in require_names_and_reqs {
571 Self::register_requirement(ctx, cell_id, req_name, requirement, name_span)?;
572 }
573 Ok(())
574 }
575
576 fn parse_cell_filter(
598 &mut self,
599 ctx: &mut ParseContext,
600 cell_name: &str,
601 cell_span: Span,
602 declared_shape: &TypeShape,
603 ) -> Result<adam_rs::Filter> {
604 let (segment, inputs, underscore_used) = self.parse_filter_expr(ctx, declared_shape)?;
605
606 let value_type_id = cell_type_id(declared_shape);
607 let output_type_id = segment.peek_output_type_id().ok_or_else(|| {
608 ParseError::new(
609 format!("cell `{cell_name}`: filter produced no value"),
610 cell_span,
611 )
612 })?;
613
614 let arg_ids: Vec<CellId> = inputs.iter().map(|(_, id, _)| *id).collect();
615 let arg_type_ids: Vec<TypeId> = inputs
616 .iter()
617 .map(|(_, _, shape)| cell_type_id(shape))
618 .collect();
619 let mut input_shapes = vec![declared_shape.clone()];
620 input_shapes.extend(inputs.iter().map(|(_, _, shape)| shape.clone()));
621
622 if let Some(range_shape) = self.types.range_entry(output_type_id) {
627 if range_shape.element_type_id != value_type_id {
628 return Err(ParseError::new(
629 format!(
630 "cell `{cell_name}`: filter range bounds must be `{}`",
631 self.types.display_name(declared_shape)
632 ),
633 cell_span,
634 ));
635 }
636
637 let default_fn = self
638 .types
639 .entry_by_type_id(value_type_id)
640 .expect("declared cell type registered")
641 .default_fn
642 .expect("numeric range-filter cell type has a default");
643 let placeholder = default_fn();
644 let result = segment.peek_stack_infos(1)[0].value_type.clone();
645 let segment = std::rc::Rc::new(RefCell::new(
646 RawFrame::new(segment, &input_shapes, &result, &self.types)
647 .map_err(|e| ParseError::new(e.to_string(), cell_span))?,
648 ));
649 let clamp_segment = std::rc::Rc::clone(&segment);
650 let bounds_segment = std::rc::Rc::clone(&segment);
651 let clamp_fn = range_shape.clamp_fn;
652 let bounds_fn = range_shape.bounds_fn;
653
654 return Ok(adam_rs::Filter::range(
655 value_type_id,
656 arg_ids,
657 arg_type_ids,
658 move |value, args| {
659 clamp_fn(
660 clamp_segment.borrow_mut().call_with_prefix(value, args)?,
661 value,
662 )
663 },
664 move |args| {
665 bounds_fn(
666 bounds_segment
667 .borrow_mut()
668 .call_checked_with_prefix(placeholder.as_ref(), args)
669 .ok()?,
670 )
671 .ok()
672 },
673 ));
674 }
675
676 if !underscore_used {
677 return Err(ParseError::new(
678 "filter must reference `_` (the value being filtered)",
679 cell_span,
680 ));
681 }
682
683 if !segment.peek_stack_infos(1)[0]
684 .value_type
685 .same_shape(&self.types.value_type(declared_shape))
686 {
687 return Err(ParseError::new(
688 format!(
689 "cell `{cell_name}`: filter must produce `{}`",
690 self.types.display_name(declared_shape)
691 ),
692 cell_span,
693 ));
694 }
695
696 let frame = RefCell::new(
697 Frame::new(
698 segment,
699 &input_shapes,
700 std::slice::from_ref(declared_shape),
701 false,
702 &self.types,
703 )
704 .map_err(|e| ParseError::new(e.to_string(), cell_span))?,
705 );
706
707 Ok(adam_rs::Filter::new(
708 value_type_id,
709 arg_ids,
710 arg_type_ids,
711 move |value, args| {
712 Ok(frame
713 .borrow_mut()
714 .call_with_prefix(value, args)?
715 .pop()
716 .expect("one result"))
717 },
718 ))
719 }
720
721 fn eval_segment_boxed(&self, segment: DynSegment) -> Result<(TypeShape, Box<dyn Any>)> {
731 let shape = self.segment_shape(&segment, Span::call_site(), format_args!(""))?;
732 let mut frame = Frame::new(
733 segment,
734 &[],
735 std::slice::from_ref(&shape),
736 false,
737 &self.types,
738 )
739 .map_err(|e| ParseError::new(e.to_string(), Span::call_site()))?;
740 let value = frame
741 .call(&[])
742 .map_err(|e| ParseError::new(e.to_string(), Span::call_site()))?
743 .pop()
744 .expect("one result");
745 Ok((shape, value))
746 }
747
748 fn segment_shape(
754 &self,
755 segment: &DynSegment,
756 span: Span,
757 context: std::fmt::Arguments<'_>,
758 ) -> Result<TypeShape> {
759 let info = segment.peek_stack_infos(1).first().ok_or_else(|| {
760 ParseError::new(format!("{context}expression produced no value"), span)
761 })?;
762 self.types
763 .shape_of(&info.value_type)
764 .map_err(|message| ParseError::new(format!("{context}{message}"), span))
765 }
766
767 fn validate_initializer(
774 &self,
775 segment: &DynSegment,
776 declared: Option<&TypeShape>,
777 construct: &str,
778 name: &str,
779 span: Span,
780 ) -> Result<()> {
781 let actual = self.segment_shape(segment, span, format_args!("{construct} `{name}`: "))?;
782 if let Some(expected) = declared
783 && expected != &actual
784 {
785 return Err(ParseError::new(
786 format!(
787 "{construct} `{name}`: type mismatch: expected `{}`, got `{}`",
788 self.types.display_name(expected),
789 self.types.display_name(&actual)
790 ),
791 span,
792 ));
793 }
794 if !matches!(actual, TypeShape::Named(_)) {
795 CellValue::validate(&self.types.value_type(&actual))
796 .map_err(|e| ParseError::new(e.to_string(), span))?;
797 }
798 Ok(())
799 }
800
801 fn build_cell_from_segment(
808 &self,
809 segment: DynSegment,
810 ctx: &mut ParseContext,
811 ) -> Result<(TypeShape, CellId)> {
812 let (shape, boxed) = self.eval_segment_boxed(segment)?;
813 let cell_id = match &shape {
814 TypeShape::Named(type_id) => {
815 let entry = self.types.entry_by_type_id(*type_id).expect("registered");
816 (entry.add_cell_fn)(&mut ctx.sheet, boxed)
817 }
818 _ => {
819 let value = *boxed.downcast::<CellValue>().expect("canonical compound");
820 ctx.sheet.add_cell(value)
821 }
822 };
823 Ok((shape, cell_id))
824 }
825
826 fn associated_display_name(&self, elem: &cel_runtime::AssociatedType) -> String {
832 self.types
833 .shape_of(&elem.value_type)
834 .map(|shape| self.types.display_name(&shape))
835 .unwrap_or_else(|_| elem.value_type.type_name().to_string())
836 }
837
838 fn validate_default_cell(&self, shape: &TypeShape, span: Span) -> Result<()> {
845 self.types
846 .validate_default(shape)
847 .and_then(|()| CellValue::validate(&self.types.value_type(shape)))
848 .map_err(|error| ParseError::new(error.to_string(), span))
849 }
850
851 fn build_default_cell(
859 &self,
860 shape: &TypeShape,
861 span: Span,
862 ctx: &mut ParseContext,
863 ) -> Result<CellId> {
864 self.validate_default_cell(shape, span)?;
865 match shape {
866 TypeShape::Named(type_id) => {
867 let entry = self
868 .types
869 .entry_by_type_id(*type_id)
870 .expect("build_default_cell: type registered (resolved via TypeRegistry)");
871 let default_fn = entry.default_fn.ok_or_else(|| {
872 ParseError::new(
873 format!("type `{}` has no default; provide `= ...`", entry.type_name),
874 span,
875 )
876 })?;
877 Ok((entry.add_cell_fn)(&mut ctx.sheet, default_fn()))
878 }
879 _ => {
880 let mut body = DynSegment::new::<()>();
881 self.types
882 .emit_default(shape, &mut body)
883 .map_err(|e| ParseError::new(e.to_string(), span))?;
884 self.build_cell_from_segment(body, ctx)
885 .map(|(_, id)| id)
886 .map_err(|error| ParseError::new(error.message(), span))
887 }
888 }
889 }
890
891 fn build_source_cell_from_segment(
903 &self,
904 segment: DynSegment,
905 ctx: &mut ParseContext,
906 ) -> Result<(TypeShape, CellId)> {
907 let (shape, boxed) = self.eval_segment_boxed(segment)?;
908 let cell_id = match &shape {
909 TypeShape::Named(type_id) => {
910 let entry = self.types.entry_by_type_id(*type_id).expect("registered");
911 (entry.add_source_fn)(&mut ctx.sheet, boxed)
912 }
913 _ => {
914 let value = *boxed.downcast::<CellValue>().expect("canonical compound");
915 ctx.sheet.add_source(value)
916 }
917 };
918 Ok((shape, cell_id))
919 }
920
921 fn build_default_source_cell(
929 &self,
930 shape: &TypeShape,
931 span: Span,
932 ctx: &mut ParseContext,
933 ) -> Result<CellId> {
934 self.validate_default_cell(shape, span)?;
935 match shape {
936 TypeShape::Named(type_id) => {
937 let entry = self.types.entry_by_type_id(*type_id).expect(
938 "build_default_source_cell: type registered (resolved via TypeRegistry)",
939 );
940 let default_fn = entry.default_fn.ok_or_else(|| {
941 ParseError::new(
942 format!("type `{}` has no default; provide `= ...`", entry.type_name),
943 span,
944 )
945 })?;
946 Ok((entry.add_source_fn)(&mut ctx.sheet, default_fn()))
947 }
948 _ => {
949 let mut body = DynSegment::new::<()>();
950 self.types
951 .emit_default(shape, &mut body)
952 .map_err(|e| ParseError::new(e.to_string(), span))?;
953 self.build_source_cell_from_segment(body, ctx)
954 .map(|(_, id)| id)
955 .map_err(|error| ParseError::new(error.message(), span))
956 }
957 }
958 }
959
960 fn parse_type_expr(&mut self, ctx: &mut ParseContext) -> Result<crate::ast::TypeExpr> {
962 let tokens = ctx.cursor.take_tokens().expect("tokens present");
963 self.cel.set_lex_tokens(tokens);
964 let result = self.cel.parse_type_expression();
965 ctx.cursor
966 .set_tokens(self.cel.take_lex_tokens().expect("tokens set"));
967 result
968 }
969
970 fn parse_relationship_decl(&mut self, ctx: &mut ParseContext) -> Result<RelationshipId> {
981 let block_start = ctx.peek_span();
982 ctx.is_keyword("relationship"); ctx.expect_open_brace()?;
984 let mut methods = Vec::new();
985 let mut spans: Vec<(Span, Span)> = Vec::new();
986 while !ctx.at_close_brace() {
987 let (method, start, end) = self.parse_binding(ctx)?;
988 methods.push(method);
989 spans.push((start, end));
990 }
991 let close_span = ctx.expect_close_brace()?;
992 match ctx.sheet.add_relationship(methods) {
993 Ok(rel_id) => {
994 ctx.relationship_spans.insert(
995 rel_id,
996 SourceSpan::from_proc_macro2_range(block_start, close_span),
997 );
998 for (idx, (start, end)) in spans.into_iter().enumerate() {
999 ctx.method_spans.insert(
1000 (rel_id, idx),
1001 SourceSpan::from_proc_macro2_range(start, end),
1002 );
1003 }
1004 Ok(rel_id)
1005 }
1006 Err(e) => {
1007 let by_id: HashMap<CellId, String> = ctx
1008 .cell_names
1009 .iter()
1010 .map(|(n, (id, _))| (*id, n.clone()))
1011 .collect();
1012 let name = |id: CellId| by_id.get(&id).cloned();
1013
1014 let primary_idx = match e.sites().first() {
1026 Some(ErrorSite::MethodIndex(i)) => Some(*i),
1027 _ => None,
1028 };
1029 let (start, end) = primary_idx
1030 .and_then(|i| spans.get(i).copied())
1031 .unwrap_or((block_start, close_span));
1032 let secondary: Vec<cel_parser::SpanLabel> = e
1033 .sites()
1034 .iter()
1035 .enumerate()
1036 .filter_map(|(i, s)| {
1037 let sp = match s {
1038 ErrorSite::MethodIndex(idx) if Some(*idx) != primary_idx => spans
1039 .get(*idx)
1040 .map(|(st, en)| SourceSpan::from_proc_macro2_range(*st, *en)),
1041 ErrorSite::Cell(c) => ctx.cell_spans.get(c).copied(),
1042 _ => None,
1043 }?;
1044 Some(cel_parser::SpanLabel {
1045 span: sp,
1046 label: crate::error_labels::site_label(&e, i, &name),
1047 })
1048 })
1049 .collect();
1050 Err(ParseError::new_range(e.to_string(), start, end).with_secondary(secondary))
1051 }
1052 }
1053 }
1054
1055 fn parse_binding(&mut self, ctx: &mut ParseContext) -> Result<(Method, Span, Span)> {
1060 let start_span = ctx.peek_span();
1061 let (names, destructure) = parse_binding_target(ctx)?;
1062 let mut outputs: NamedCells = Vec::with_capacity(names.len());
1063 for (name, span) in names {
1064 let (cell_id, shape) = ctx
1065 .cell_names
1066 .get(&name)
1067 .cloned()
1068 .ok_or_else(|| ParseError::new(format!("undeclared cell `{name}`"), span))?;
1069 outputs.push((name, cell_id, shape));
1070 }
1071 ctx.expect_punct(":=")?;
1072 let (segment, inputs) = self.parse_deduced_expr(ctx)?;
1073 let end_span = ctx.expect_punct(";")?;
1074 let compiled = self.compile_outputs(ctx, &segment, &outputs, destructure, start_span)?;
1075 Ok((
1076 build_method(inputs, outputs, segment, compiled, &self.types)?,
1077 start_span,
1078 end_span,
1079 ))
1080 }
1081
1082 fn parse_deduced_expr(&mut self, ctx: &mut ParseContext) -> Result<(DynSegment, NamedCells)> {
1099 self.parse_deduced_expr_restricted(ctx, cel_parser::ExpressionRestriction::None)
1100 }
1101
1102 fn parse_deduced_expr_restricted(
1110 &mut self,
1111 ctx: &mut ParseContext,
1112 restriction: cel_parser::ExpressionRestriction,
1113 ) -> Result<(DynSegment, NamedCells)> {
1114 let push_table: std::collections::HashMap<String, (CellId, TypeShape, InputPush)> = ctx
1118 .cell_names
1119 .iter()
1120 .map(|(name, (cell_id, shape))| {
1121 let push = self.types.value_type(shape);
1122 (name.clone(), (*cell_id, shape.clone(), push))
1123 })
1124 .collect();
1125
1126 let accumulator: Arc<Mutex<NamedCells>> = Arc::new(Mutex::new(Vec::new()));
1127 let scope_accumulator = Arc::clone(&accumulator);
1128
1129 self.cel
1130 .op_lookup_mut()
1131 .push_scope(move |name, segment, arity, _span| {
1132 if arity != 0 {
1133 return Ok(false);
1134 }
1135 let Some((cell_id, shape, push)) = push_table.get(name) else {
1136 return Ok(false);
1137 };
1138 let idx = {
1139 let mut acc = scope_accumulator.lock().expect("scope mutex not poisoned");
1140 match acc.iter().position(|(n, ..)| n == name) {
1141 Some(pos) => pos,
1142 None => {
1143 acc.push((name.to_string(), *cell_id, shape.clone()));
1144 acc.len() - 1
1145 }
1146 }
1147 };
1148 segment.push_argument(idx, push.clone())?;
1149 Ok(true)
1150 });
1151
1152 let result = self.parse_cel_expression_restricted(ctx, restriction);
1153 self.cel.op_lookup_mut().pop_scope();
1154 let segment = result?;
1155
1156 let inputs = accumulator
1157 .lock()
1158 .expect("scope mutex not poisoned")
1159 .clone();
1160 Ok((segment, inputs))
1161 }
1162
1163 fn parse_filter_expr(
1176 &mut self,
1177 ctx: &mut ParseContext,
1178 declared_shape: &TypeShape,
1179 ) -> Result<(DynSegment, NamedCells, bool)> {
1180 let push_table: std::collections::HashMap<String, (CellId, TypeShape, InputPush)> = ctx
1181 .cell_names
1182 .iter()
1183 .map(|(name, (cell_id, shape))| {
1184 let push = self.types.value_type(shape);
1185 (name.clone(), (*cell_id, shape.clone(), push))
1186 })
1187 .collect();
1188
1189 let value_push = self.types.value_type(declared_shape);
1190
1191 let accumulator: Arc<Mutex<NamedCells>> = Arc::new(Mutex::new(Vec::new()));
1192 let scope_accumulator = Arc::clone(&accumulator);
1193 let underscore_used: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
1194 let scope_underscore_used = Arc::clone(&underscore_used);
1195
1196 self.cel
1197 .op_lookup_mut()
1198 .push_scope(move |name, segment, arity, _span| {
1199 if arity != 0 {
1200 return Ok(false);
1201 }
1202 if name == "_" {
1203 *scope_underscore_used
1204 .lock()
1205 .expect("scope mutex not poisoned") = true;
1206 segment.push_argument(0, value_push.clone())?;
1207 return Ok(true);
1208 }
1209 let Some((cell_id, shape, push)) = push_table.get(name) else {
1210 return Ok(false);
1211 };
1212 let idx = {
1213 let mut acc = scope_accumulator.lock().expect("scope mutex not poisoned");
1214 match acc.iter().position(|(n, ..)| n == name) {
1215 Some(pos) => pos + 1,
1216 None => {
1217 acc.push((name.to_string(), *cell_id, shape.clone()));
1218 acc.len()
1219 }
1220 }
1221 };
1222 segment.push_argument(idx, push.clone())?;
1223 Ok(true)
1224 });
1225
1226 let result = self.parse_cel_expression(ctx);
1227 self.cel.op_lookup_mut().pop_scope();
1228 let segment = result?;
1229
1230 let inputs = accumulator
1231 .lock()
1232 .expect("scope mutex not poisoned")
1233 .clone();
1234 let used = *underscore_used.lock().expect("scope mutex not poisoned");
1235 Ok((segment, inputs, used))
1236 }
1237
1238 fn parse_match_expr(
1250 &mut self,
1251 ctx: &mut ParseContext,
1252 match_span: proc_macro2::Span,
1253 ) -> Result<(TypeShape, MatchExpr)> {
1254 let (segment, inputs) = self.parse_deduced_expr_restricted(
1255 ctx,
1256 cel_parser::ExpressionRestriction::NoStructLiteral,
1257 )?;
1258 self.build_match_expr(segment, inputs, match_span)
1259 }
1260
1261 fn build_match_expr(
1274 &self,
1275 segment: DynSegment,
1276 inputs: NamedCells,
1277 match_span: proc_macro2::Span,
1278 ) -> Result<(TypeShape, MatchExpr)> {
1279 let input_ids: Vec<CellId> = inputs.iter().map(|(_, id, _)| *id).collect();
1280 let input_types: Vec<TypeId> = inputs
1281 .iter()
1282 .map(|(_, _, shape)| cell_type_id(shape))
1283 .collect();
1284
1285 let shape = self.segment_shape(&segment, match_span, format_args!("match expression: "))?;
1286 let eq_fn = match &shape {
1287 TypeShape::Named(id) => {
1288 self.types
1289 .entry_by_type_id(*id)
1290 .expect("registered")
1291 .eq_dyn_fn
1292 }
1293 _ => compound_eq,
1294 };
1295 let frame = RefCell::new(
1296 Frame::new(
1297 segment,
1298 &inputs
1299 .iter()
1300 .map(|(_, _, shape)| shape.clone())
1301 .collect::<Vec<_>>(),
1302 std::slice::from_ref(&shape),
1303 false,
1304 &self.types,
1305 )
1306 .map_err(|e| ParseError::new(e.to_string(), match_span))?,
1307 );
1308 let match_expr = MatchExpr::new(
1309 input_ids,
1310 input_types,
1311 cell_type_id(&shape),
1312 eq_fn,
1313 move |args| Ok(frame.borrow_mut().call(args)?.pop().expect("one result")),
1314 );
1315 Ok((shape, match_expr))
1316 }
1317
1318 fn parse_conditional_decl(&mut self, ctx: &mut ParseContext) -> Result<()> {
1320 ctx.is_keyword("conditional"); let match_span = ctx.peek_span();
1322 let (match_shape, match_expr) = self.parse_match_expr(ctx, match_span)?;
1323 ctx.expect_open_brace()?;
1324
1325 let mut branches: Vec<(Box<dyn Any>, Vec<RelationshipId>)> = Vec::new();
1326 let mut default_rel_ids: Vec<RelationshipId> = Vec::new();
1327
1328 while !ctx.at_close_brace() {
1329 if matches!(ctx.peek_token(), Some(Token::Identifier(id)) if id == "_") {
1331 ctx.advance(); ctx.expect_punct("=>")?;
1333 ctx.expect_open_brace()?;
1334 let rel_ids = self.parse_branch_relationships(ctx)?;
1335 ctx.expect_close_brace()?;
1336 ctx.consume_punct(",");
1337 default_rel_ids = rel_ids;
1338 break; }
1340
1341 let branch_span = ctx.peek_span();
1347 let segment = self.parse_literal_pattern(ctx)?;
1348 let (branch_shape, branch_val) = self.eval_segment_boxed(segment)?;
1349 if branch_shape != match_shape {
1350 return Err(ParseError::new(
1351 format!(
1352 "conditional branch: type mismatch: expected `{}`, got `{}`",
1353 self.types.display_name(&match_shape),
1354 self.types.display_name(&branch_shape)
1355 ),
1356 branch_span,
1357 ));
1358 }
1359 ctx.expect_punct("=>")?;
1360 ctx.expect_open_brace()?;
1361 let rel_ids = self.parse_branch_relationships(ctx)?;
1362 ctx.expect_close_brace()?;
1363 ctx.consume_punct(",");
1364 branches.push((branch_val, rel_ids));
1365 }
1366 ctx.expect_close_brace()?;
1367
1368 match &match_shape {
1369 TypeShape::Named(type_id) => {
1370 let add_cond_fn: AddConditionalFn = self
1371 .types
1372 .entry_by_type_id(*type_id)
1373 .ok_or_else(|| {
1374 ParseError::new("match cell type not in TypeRegistry", match_span)
1375 })?
1376 .add_conditional_fn;
1377 add_cond_fn(&mut ctx.sheet, match_expr, branches, default_rel_ids)
1378 .map_err(|e| Self::sheet_error(ctx, match_span, e))?;
1379 }
1380 _ => {
1381 let typed_branches: Vec<(Vec<CellValue>, Vec<RelationshipId>)> = branches
1382 .into_iter()
1383 .map(|(val, rel_ids)| {
1384 let seq = *val.downcast::<CellValue>().expect("canonical compound");
1385 (vec![seq], rel_ids)
1386 })
1387 .collect();
1388 ctx.sheet
1389 .add_conditional::<CellValue>(match_expr, typed_branches, default_rel_ids)
1390 .map_err(|e| Self::sheet_error(ctx, match_span, e))?;
1391 }
1392 }
1393
1394 Ok(())
1395 }
1396
1397 fn sheet_error(ctx: &ParseContext, primary: Span, e: adam_rs::Error) -> ParseError {
1403 let by_id: HashMap<CellId, String> = ctx
1404 .cell_names
1405 .iter()
1406 .map(|(n, (id, _))| (*id, n.clone()))
1407 .collect();
1408 let name = |id: CellId| by_id.get(&id).cloned();
1409 let secondary: Vec<cel_parser::SpanLabel> = e
1410 .sites()
1411 .iter()
1412 .enumerate()
1413 .filter_map(|(i, s)| {
1414 let sp = match s {
1415 ErrorSite::Relationship(r) => ctx.relationship_spans.get(r).copied(),
1416 ErrorSite::Cell(c) => ctx.cell_spans.get(c).copied(),
1417 _ => None,
1418 }?;
1419 Some(cel_parser::SpanLabel {
1420 span: sp,
1421 label: crate::error_labels::site_label(&e, i, &name),
1422 })
1423 })
1424 .collect();
1425 let message = e.to_string();
1426 ParseError::new(message, primary).with_secondary(secondary)
1427 }
1428
1429 fn validated_sheet_error(ctx: &ParseContext, e: adam_rs::Error) -> ParseError {
1437 let by_id: HashMap<CellId, String> = ctx
1438 .cell_names
1439 .iter()
1440 .map(|(n, (id, _))| (*id, n.clone()))
1441 .collect();
1442 let name = |id: CellId| by_id.get(&id).cloned();
1443 let primary_index = e.sites().iter().position(|s| match s {
1444 ErrorSite::Cell(c) => ctx.cell_token_spans.contains_key(c),
1445 _ => false,
1446 });
1447 let primary = primary_index
1448 .and_then(|i| match e.sites()[i] {
1449 ErrorSite::Cell(c) => ctx.cell_token_spans.get(&c).copied(),
1450 _ => None,
1451 })
1452 .unwrap_or_else(Span::call_site);
1453 let secondary: Vec<cel_parser::SpanLabel> = e
1454 .sites()
1455 .iter()
1456 .enumerate()
1457 .filter(|(i, _)| Some(*i) != primary_index)
1458 .filter_map(|(i, s)| {
1459 let span = match s {
1460 ErrorSite::Relationship(r) => ctx.relationship_spans.get(r).copied(),
1461 ErrorSite::Cell(c) => ctx.cell_spans.get(c).copied(),
1462 _ => None,
1463 }?;
1464 Some(cel_parser::SpanLabel {
1465 span,
1466 label: crate::error_labels::site_label(&e, i, &name),
1467 })
1468 })
1469 .collect();
1470 ParseError::new(e.to_string(), primary).with_secondary(secondary)
1471 }
1472
1473 fn parse_branch_relationships(
1476 &mut self,
1477 ctx: &mut ParseContext,
1478 ) -> Result<Vec<RelationshipId>> {
1479 let mut rel_ids = Vec::new();
1480 while !ctx.at_close_brace() {
1481 if !matches!(ctx.peek_token(), Some(Token::Identifier(id)) if id == "relationship") {
1482 return Err(ctx.err_at("expected `relationship`"));
1483 }
1484 rel_ids.push(self.parse_relationship_decl(ctx)?);
1485 }
1486 Ok(rel_ids)
1487 }
1488
1489 fn parse_out_decl(&mut self, ctx: &mut ParseContext) -> Result<()> {
1492 ctx.is_keyword("out"); let (name, name_span) = ctx.consume_ident()?;
1494 if ctx.cell_names.contains_key(&name) {
1495 return Err(ParseError::new(
1496 format!("duplicate cell `{name}`"),
1497 name_span,
1498 ));
1499 }
1500
1501 let declared_shape: Option<TypeShape> = if ctx.consume_punct(":") {
1502 let type_expr = self.parse_type_expr(ctx)?;
1503 Some(
1504 self.types
1505 .resolve(&type_expr)
1506 .map_err(|(msg, span)| ParseError::new(msg, span))?,
1507 )
1508 } else {
1509 None
1510 };
1511
1512 ctx.expect_punct(":=")?;
1513 let (segment, inputs) = self.parse_deduced_expr(ctx)?;
1514
1515 let actual_shape =
1521 self.segment_shape(&segment, name_span, format_args!("out `{name}`: "))?;
1522
1523 let out_shape = match &declared_shape {
1524 Some(declared) => {
1525 if declared != &actual_shape {
1526 return Err(ctx.err_at(format!(
1527 "out `{name}`: type mismatch: expected `{}`, got `{}`",
1528 self.types.display_name(declared),
1529 self.types.display_name(&actual_shape)
1530 )));
1531 }
1532 declared.clone()
1533 }
1534 None => actual_shape,
1535 };
1536
1537 if !matches!(out_shape, TypeShape::Named(_)) {
1538 CellValue::validate(&self.types.value_type(&out_shape))
1539 .map_err(|error| ParseError::new(error.to_string(), name_span))?;
1540 }
1541 let cell_id = self.build_default_cell(&out_shape, name_span, ctx)?;
1542 ctx.cell_spans
1543 .insert(cell_id, SourceSpan::from_proc_macro2(name_span));
1544 ctx.cell_token_spans.insert(cell_id, name_span);
1545
1546 let filter = if ctx.is_keyword("filter") {
1547 Some(self.parse_cell_filter(ctx, &name, name_span, &out_shape)?)
1548 } else {
1549 None
1550 };
1551
1552 ctx.cell_names
1553 .insert(name.clone(), (cell_id, out_shape.clone()));
1554
1555 let compiled = CompiledOutputs::Single;
1556 let writer = build_method(
1557 inputs,
1558 vec![(name.clone(), cell_id, out_shape)],
1559 segment,
1560 compiled,
1561 &self.types,
1562 )?;
1563
1564 let requirements = if ctx.is_keyword("require") {
1565 self.parse_requirement_block(ctx)?
1566 } else {
1567 Vec::new()
1568 };
1569
1570 ctx.expect_punct(";")?;
1571
1572 let out_cell = ctx
1573 .sheet
1574 .add_out(writer, Vec::new())
1575 .map_err(|e| ParseError::new(e.to_string(), name_span))?;
1576 for (req_name, requirement) in requirements {
1577 Self::register_requirement(ctx, out_cell, req_name, requirement, name_span)?;
1578 }
1579 if let Some(filter) = filter {
1580 ctx.sheet
1581 .add_filter(out_cell, filter)
1582 .map_err(|e| Self::sheet_error(ctx, name_span, e))?;
1583 }
1584 ctx.output_names.insert(name, out_cell);
1585
1586 Ok(())
1587 }
1588
1589 fn parse_requirement_block(
1597 &mut self,
1598 ctx: &mut ParseContext,
1599 ) -> Result<Vec<(Option<String>, Requirement)>> {
1600 ctx.expect_open_brace()?;
1601 let mut requirements = Vec::new();
1602 let mut names = HashSet::new();
1603 while !ctx.at_close_brace() {
1604 let span = ctx
1605 .peek_token()
1606 .ok_or_else(|| ParseError::new("unexpected end of input", Span::call_site()))?
1607 .span();
1608 let (name, requirement) = self.parse_requirement(ctx)?;
1609 if let Some(label) = &name
1610 && !names.insert(label.clone())
1611 {
1612 return Err(ParseError::new(
1613 format!("duplicate requirement `{label}`"),
1614 span,
1615 ));
1616 }
1617 requirements.push((name, requirement));
1618 }
1619 ctx.expect_close_brace()?;
1620 Ok(requirements)
1621 }
1622
1623 fn register_requirement(
1630 ctx: &mut ParseContext,
1631 cell: CellId,
1632 name: Option<String>,
1633 requirement: Requirement,
1634 span: Span,
1635 ) -> Result<()> {
1636 let id = ctx
1637 .sheet
1638 .add_requirement(cell, requirement)
1639 .map_err(|e| ParseError::new(e.to_string(), span))?;
1640 if let Some(name) = name {
1641 ctx.requirement_names.insert(id, name);
1642 }
1643 Ok(())
1644 }
1645
1646 fn parse_requirement(
1648 &mut self,
1649 ctx: &mut ParseContext,
1650 ) -> Result<(Option<String>, Requirement)> {
1651 let name = if ctx.consume_punct("@") {
1652 let (name, _name_span) = ctx.consume_ident()?;
1653 Some(name)
1654 } else {
1655 None
1656 };
1657 let (segment, inputs) = self.parse_deduced_expr(ctx)?;
1658 ctx.expect_punct(";")?;
1659
1660 let bool_type_id = TypeId::of::<bool>();
1661 let actual_type_id = segment.peek_output_type_id().ok_or_else(|| {
1662 ctx.err_at(match &name {
1663 Some(name) => format!("requirement `{name}`: expression produced no value"),
1664 None => "requirement: expression produced no value".to_string(),
1665 })
1666 })?;
1667 if actual_type_id != bool_type_id {
1668 let got = self
1669 .types
1670 .entry_by_type_id(actual_type_id)
1671 .map(|e| e.type_name)
1672 .unwrap_or("?");
1673 return Err(ctx.err_at(match &name {
1674 Some(name) => format!("requirement `{name}`: expected `bool`, got `{got}`"),
1675 None => format!("requirement: expected `bool`, got `{got}`"),
1676 }));
1677 }
1678
1679 let input_ids: Vec<CellId> = inputs.iter().map(|(_, id, _)| *id).collect();
1680 let input_types: Vec<TypeId> = inputs
1681 .iter()
1682 .map(|(_, _, shape)| cell_type_id(shape))
1683 .collect();
1684 let frame = RefCell::new(
1685 RawFrame::new(
1686 segment,
1687 &inputs
1688 .iter()
1689 .map(|(_, _, shape)| shape.clone())
1690 .collect::<Vec<_>>(),
1691 &cel_runtime::ValueType::leaf::<bool>(),
1692 &self.types,
1693 )
1694 .map_err(|e| ctx.err_at(e.to_string()))?,
1695 );
1696 let requirement = Requirement::new(input_ids, input_types, move |args| {
1697 let value = frame.borrow_mut().call(args)?;
1698 Ok(unsafe { value.into_typed_bound::<cel_runtime::Leaf<bool>>() })
1700 });
1701
1702 Ok((name, requirement))
1703 }
1704
1705 fn compile_outputs(
1721 &self,
1722 ctx: &mut ParseContext,
1723 segment: &DynSegment,
1724 outputs: &[(String, CellId, TypeShape)],
1725 destructure: bool,
1726 binding_span: Span,
1727 ) -> Result<CompiledOutputs> {
1728 if outputs.len() == 1 && !destructure {
1729 let (out_name, _, out_shape) = &outputs[0];
1730 let actual =
1731 self.segment_shape(segment, binding_span, format_args!("output `{out_name}`: "))?;
1732 if &actual != out_shape {
1733 return Err(ctx.err_at(format!(
1734 "output `{out_name}`: type mismatch: expected `{}`, got `{}`",
1735 self.types.display_name(out_shape),
1736 self.types.display_name(&actual)
1737 )));
1738 }
1739 Ok(CompiledOutputs::Single)
1740 } else {
1741 let arity = segment.peek_tuple_arity().unwrap_or(0);
1742 if arity != outputs.len() {
1743 return Err(ctx.err_at(format!(
1744 "output expression has arity {arity} but {} output(s) declared",
1745 outputs.len()
1746 )));
1747 }
1748 let associated = segment.peek_stack_infos(1)[0]
1749 .value_type
1750 .tuple_elements()
1751 .map(<[_]>::to_vec)
1752 .unwrap_or_default();
1753 for (i, ((out_name, _, out_shape), elem)) in outputs.iter().zip(&associated).enumerate()
1754 {
1755 if !self
1756 .types
1757 .value_type(out_shape)
1758 .same_shape(&elem.value_type)
1759 {
1760 return Err(ctx.err_at(format!(
1761 "output {i} `{out_name}`: type mismatch: expected `{}`, got `{}`",
1762 self.types.display_name(out_shape),
1763 self.associated_display_name(elem)
1764 )));
1765 }
1766 }
1767 Ok(CompiledOutputs::Tuple)
1768 }
1769 }
1770
1771 fn parse_cel_expression(&mut self, ctx: &mut ParseContext) -> Result<DynSegment> {
1773 self.parse_cel_expression_restricted(ctx, cel_parser::ExpressionRestriction::None)
1774 }
1775
1776 fn parse_cel_expression_restricted(
1783 &mut self,
1784 ctx: &mut ParseContext,
1785 restriction: cel_parser::ExpressionRestriction,
1786 ) -> Result<DynSegment> {
1787 let tokens = ctx.cursor.take_tokens().expect("tokens present");
1788 self.cel.set_lex_tokens(tokens);
1789 let result = self.cel.parse_expression_restricted(restriction);
1790 ctx.cursor
1791 .set_tokens(self.cel.take_lex_tokens().expect("tokens set"));
1792 let mut segment = result?;
1793 if let Some(info) = segment.peek_stack_infos(1).first() {
1794 let enriched = self
1795 .types
1796 .enrich_value_type(&info.value_type)
1797 .map_err(|error| ParseError::new(error.to_string(), Span::call_site()))?;
1798 segment
1799 .annotate_top(enriched)
1800 .map_err(|error| ParseError::new(error.to_string(), Span::call_site()))?;
1801 }
1802 Ok(segment)
1803 }
1804
1805 fn parse_literal_pattern(&mut self, ctx: &mut ParseContext) -> Result<DynSegment> {
1808 let tokens = ctx.cursor.take_tokens().expect("tokens present");
1809 self.cel.set_lex_tokens(tokens);
1810 let result = self.cel.parse_literal_pattern();
1811 ctx.cursor
1812 .set_tokens(self.cel.take_lex_tokens().expect("tokens set"));
1813 result
1814 }
1815}
1816
1817type InputPush = cel_runtime::ValueType;
1823
1824fn compound_eq(a: &dyn Any, b: &dyn Any) -> bool {
1828 a.downcast_ref::<CellValue>() == b.downcast_ref::<CellValue>()
1829}
1830
1831enum CompiledOutputs {
1833 Single,
1835 Tuple,
1837}
1838
1839fn build_method(
1842 inputs: NamedCells,
1843 outputs: NamedCells,
1844 segment: DynSegment,
1845 compiled: CompiledOutputs,
1846 types: &TypeRegistry,
1847) -> Result<Method> {
1848 let input_ids: Vec<CellId> = inputs.iter().map(|(_, id, _)| *id).collect();
1849 let output_ids: Vec<CellId> = outputs.iter().map(|(_, id, _)| *id).collect();
1850 let input_types: Vec<TypeId> = inputs
1851 .iter()
1852 .map(|(_, _, shape)| cell_type_id(shape))
1853 .collect();
1854 let output_types: Vec<TypeId> = outputs
1855 .iter()
1856 .map(|(_, _, shape)| cell_type_id(shape))
1857 .collect();
1858
1859 let frame = RefCell::new(
1860 Frame::new(
1861 segment,
1862 &inputs.iter().map(|(_, _, s)| s.clone()).collect::<Vec<_>>(),
1863 &outputs
1864 .iter()
1865 .map(|(_, _, s)| s.clone())
1866 .collect::<Vec<_>>(),
1867 matches!(compiled, CompiledOutputs::Tuple),
1868 types,
1869 )
1870 .map_err(|e| ParseError::new(e.to_string(), Span::call_site()))?,
1871 );
1872 Ok(Method::new(
1873 input_ids,
1874 output_ids,
1875 input_types,
1876 output_types,
1877 move |inputs| frame.borrow_mut().call(inputs),
1878 ))
1879}
1880
1881fn cell_type_id(shape: &TypeShape) -> TypeId {
1885 match shape {
1886 TypeShape::Named(type_id) => *type_id,
1887 _ => TypeId::of::<CellValue>(),
1888 }
1889}
1890
1891fn parse_binding_target(ctx: &mut ParseContext) -> Result<(Vec<(String, Span)>, bool)> {
1898 if !ctx.at_open_paren() {
1899 let (name, span) = ctx.consume_ident()?;
1900 return Ok((vec![(name, span)], false));
1901 }
1902
1903 ctx.expect_open_paren()?;
1904 let (first_name, first_span) = ctx.consume_ident()?;
1905 if ctx.at_close_paren() {
1906 ctx.expect_close_paren()?;
1908 return Ok((vec![(first_name, first_span)], false));
1909 }
1910 if !ctx.consume_punct(",") {
1911 return Err(ctx.err_at("expected ',' or closing parenthesis"));
1912 }
1913 if ctx.at_close_paren() {
1914 ctx.expect_close_paren()?;
1916 return Ok((vec![(first_name, first_span)], true));
1917 }
1918 let mut outputs = vec![(first_name, first_span)];
1919 loop {
1920 let (name, span) = ctx.consume_ident()?;
1921 outputs.push((name, span));
1922 if ctx.at_close_paren() {
1923 break;
1924 }
1925 if !ctx.consume_punct(",") {
1926 return Err(ctx.err_at("expected ',' or closing parenthesis"));
1927 }
1928 }
1929 ctx.expect_close_paren()?;
1930 Ok((outputs, true))
1931}
1932
1933#[cfg(test)]
1938mod tests {
1939 use super::*;
1940 use crate::TypeRegistry;
1941 use cel_parser::OpLookup;
1942 use cel_runtime::{ArrayOf, Leaf};
1943
1944 fn parser() -> AdamParser {
1945 AdamParser::new(TypeRegistry::new(), OpLookup::new())
1946 }
1947
1948 #[derive(Clone, Debug, PartialEq)]
1949 struct Custom(i32);
1950
1951 #[derive(Clone)]
1952 struct CountFn;
1953
1954 fn custom_lookup() -> OpLookup {
1955 let mut lookup = OpLookup::new();
1956 lookup.push_scope(|name, segment, arity, _span| match name {
1957 "left" if arity == 0 => {
1958 segment.op0(|| Custom(1));
1959 Ok(true)
1960 }
1961 "right" if arity == 0 => {
1962 segment.op0(|| Custom(2));
1963 Ok(true)
1964 }
1965 "count" if arity == 0 => {
1966 segment.op0(|| CountFn);
1967 Ok(true)
1968 }
1969 "()" if arity == 2 => {
1970 if segment.peek_stack_infos(1)[0]
1971 .value_type
1972 .array_element()
1973 .is_some_and(|element| element.array_element().is_some())
1974 {
1975 segment
1976 .op2_repr::<Leaf<CountFn>, ArrayOf<ArrayOf<Leaf<Custom>>>, Leaf<i32>, _>(
1977 |_callee, array| array.len() as i32,
1978 )?;
1979 } else {
1980 segment.op2_repr::<Leaf<CountFn>, ArrayOf<Leaf<Custom>>, Leaf<i32>, _>(
1981 |_callee, array| array.len() as i32,
1982 )?;
1983 }
1984 Ok(true)
1985 }
1986 _ => Ok(false),
1987 });
1988 lookup
1989 }
1990
1991 #[test]
1992 fn parse_empty_sheet() {
1993 let _sheet = parser().parse_str("sheet empty {}").unwrap();
1994 }
1995
1996 #[test]
1997 fn parse_cell_with_annotation_and_initializer() {
1998 let _sheet = parser()
1999 .parse_str("sheet s { cell width: f64 = 1920.0; }")
2000 .unwrap();
2001 }
2002
2003 #[test]
2004 fn parse_cell_annotation_only_uses_default() {
2005 let _sheet = parser().parse_str("sheet s { cell area: f64; }").unwrap();
2006 }
2007
2008 #[test]
2009 fn parse_cell_initializer_infers_type() {
2010 let _sheet = parser().parse_str("sheet s { cell mode = 0i32; }").unwrap();
2011 }
2012
2013 #[test]
2014 fn parse_cell_unknown_type_is_error() {
2015 let result = parser().parse_str("sheet s { cell x: unknown_type; }");
2016 assert!(result.is_err());
2017 let err = result.expect_err("expected Err");
2018 let msg = err.message().to_lowercase();
2019 assert!(
2020 msg.contains("unknown type") || msg.contains("unknown_type"),
2021 "{msg}"
2022 );
2023 }
2024
2025 #[test]
2026 fn parse_cell_missing_default_is_error() {
2027 #[derive(PartialEq, Clone, Debug)]
2028 struct NoDef(i32);
2029 let mut reg = TypeRegistry::new();
2030 reg.register_no_default::<NoDef>("NoDef");
2031 let mut p = AdamParser::new(reg, OpLookup::new());
2032 let result = p.parse_str("sheet s { cell x: NoDef; }");
2033 assert!(result.is_err());
2034 }
2035
2036 #[test]
2037 fn parse_cell_initializer_supports_registry_typed_arrays() {
2038 let mut reg = TypeRegistry::new();
2039 reg.register_no_default::<Custom>("Custom");
2040 let mut parser = AdamParser::new(reg, custom_lookup());
2041
2042 let parsed = parser
2043 .parse_str(
2044 "sheet s { \
2045 cell values: i32 = count([left, right]: [Custom]); \
2046 cell empty: i32 = count([]: [Custom]); \
2047 cell nested: i32 = count([[]: [Custom]]: [[Custom]]); \
2048 }",
2049 )
2050 .unwrap();
2051
2052 let (values_id, _) = parsed.cell_names["values"];
2053 assert_eq!(*parsed.read::<i32>(values_id).unwrap(), 2);
2054
2055 let (empty_id, _) = parsed.cell_names["empty"];
2056 assert_eq!(*parsed.read::<i32>(empty_id).unwrap(), 0);
2057
2058 let (nested_id, _) = parsed.cell_names["nested"];
2059 assert_eq!(*parsed.read::<i32>(nested_id).unwrap(), 1);
2060 }
2061
2062 #[test]
2063 fn parse_cell_initializer_rejects_unknown_registry_typed_arrays() {
2064 let mut parser = AdamParser::new(TypeRegistry::new(), custom_lookup());
2065 let err = parser
2066 .parse_str("sheet s { cell values = []: [Custom]; }")
2067 .expect_err("unknown custom array annotations must fail");
2068
2069 assert!(
2070 err.message().contains("unknown type `Custom`"),
2071 "got: {}",
2072 err.message()
2073 );
2074 }
2075
2076 #[test]
2077 fn parse_cell_initializer_preserves_persistent_array_cells() {
2078 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2079 let parsed = parser
2080 .parse_str("sheet s { cell values = []: [(i32, f64)]; }")
2081 .unwrap();
2082 type Pairs = cel_runtime::ArrayOf<
2083 cel_runtime::Cons<
2084 cel_runtime::Leaf<i32>,
2085 cel_runtime::Cons<cel_runtime::Leaf<f64>, cel_runtime::Nil>,
2086 >,
2087 >;
2088 assert!(
2089 parsed
2090 .read_compound::<Pairs>(parsed.cell_names["values"].0)
2091 .unwrap()
2092 .is_empty()
2093 );
2094 }
2095
2096 #[test]
2097 fn parse_source_decl_registers_a_source_kind_cell() {
2098 let sheet = parser()
2099 .parse_str("sheet s { source width: i32 = 4; }")
2100 .unwrap();
2101 let (width, _) = sheet.cell_names["width"];
2102 assert_eq!(sheet.cell_kind(width), Some(adam_rs::CellKind::Source));
2103 }
2104
2105 #[test]
2106 fn parse_source_decl_requires_colon_or_equals() {
2107 let result = parser().parse_str("sheet s { source width; }");
2108 assert!(result.is_err());
2109 }
2110
2111 #[test]
2112 fn parse_cell_decl_with_a_require_block_attaches_requirements() {
2113 let sheet = parser()
2114 .parse_str("sheet s { cell x: i32 = 5 require { @positive x > 0; }; }")
2115 .unwrap();
2116 let (x, _) = sheet.cell_names["x"];
2117 assert_eq!(sheet.cell_requirements(x).unwrap().len(), 1);
2118 }
2119
2120 #[test]
2121 fn parse_source_decl_with_a_require_block_attaches_requirements() {
2122 let sheet = parser()
2123 .parse_str("sheet s { source x: i32 = 5 require { @positive x > 0; }; }")
2124 .unwrap();
2125 let (x, _) = sheet.cell_names["x"];
2126 assert_eq!(sheet.cell_requirements(x).unwrap().len(), 1);
2127 }
2128
2129 #[test]
2130 fn parse_source_decl_with_a_filter_clause_attaches_a_filter() {
2131 let sheet = parser()
2132 .parse_str("sheet s { source x: i32 = 5 filter 0..=10; }")
2133 .unwrap();
2134 let (x, _) = sheet.cell_names["x"];
2135 assert!(sheet.filter_kind(x).is_some());
2136 }
2137
2138 #[test]
2139 fn parse_out_decl_with_a_filter_clause_attaches_a_filter() {
2140 let sheet = parser()
2141 .parse_str("sheet s { cell width: i32 = 4; out area := width filter 0..=100; }")
2142 .unwrap();
2143 let area = sheet.output_names["area"];
2144 assert!(sheet.filter_kind(area).is_some());
2145 }
2146
2147 #[test]
2148 fn cell_filter_with_no_named_dependency_clamps_on_write() {
2149 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2150 let mut parsed = parser
2151 .parse_str(
2152 "sheet s { cell a: i32 filter if _ < 1 { 1 } else if _ > 100 { 100 } else { _ }; }",
2153 )
2154 .unwrap();
2155 let (cell_id, _) = parsed.cell_names["a"];
2156 parsed.sheet.write(cell_id, 500i32).unwrap();
2157 parsed.sheet.propagate().unwrap();
2158 assert_eq!(*parsed.sheet.read::<i32>(cell_id).unwrap(), 100);
2159 }
2160
2161 #[test]
2162 fn cell_filter_referencing_a_cell_tracks_its_current_value() {
2163 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2164 let mut parsed = parser
2165 .parse_str(
2166 "sheet s { \
2167 cell hi: i32 = 100; \
2168 cell a: i32 filter if _ < 1 { 1 } else if _ > hi { hi } else { _ }; \
2169 }",
2170 )
2171 .unwrap();
2172 let (a_id, _) = parsed.cell_names["a"];
2173 let (hi_id, _) = parsed.cell_names["hi"];
2174
2175 parsed.sheet.write(a_id, 500i32).unwrap();
2176 parsed.sheet.propagate().unwrap();
2177 assert_eq!(*parsed.sheet.read::<i32>(a_id).unwrap(), 100);
2178
2179 parsed.sheet.write(hi_id, 10i32).unwrap();
2180 parsed.sheet.write(a_id, 500i32).unwrap();
2181 parsed.sheet.propagate().unwrap();
2182 assert_eq!(*parsed.sheet.read::<i32>(a_id).unwrap(), 10);
2183 }
2184
2185 #[test]
2186 fn cell_filter_referencing_the_same_value_twice_is_idempotent() {
2187 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2190 let mut parsed = parser
2191 .parse_str("sheet s { cell step: i32 = 10; cell a: i32 filter _ - (_ % step); }")
2192 .unwrap();
2193 let (a_id, _) = parsed.cell_names["a"];
2194 parsed.sheet.write(a_id, 27i32).unwrap();
2195 parsed.sheet.propagate().unwrap();
2196 assert_eq!(*parsed.sheet.read::<i32>(a_id).unwrap(), 20);
2197 }
2198
2199 #[test]
2200 fn filter_dependency_cycle_reported_with_secondary_labels() {
2201 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2202 let ok_source = "sheet s {\n cell b: i32 = 0;\n cell a: i32 = 5;\n relationship { b := a; }\n cell c: i32 = 0 filter _ + b - b;\n}";
2203 assert!(parser.parse_str(ok_source).is_ok());
2204
2205 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2206 let source = "sheet s {\n cell b: i32 = 0;\n cell a: i32 = 5 filter _ + b - b;\n relationship { b := a; }\n}";
2209 let err = parser.parse_str(source).unwrap_err();
2210 assert_eq!(
2211 err.message(),
2212 "a filter or conditional depends on a cell it governs (dependency cycle)"
2213 );
2214 assert!(!err.secondary().is_empty());
2215 }
2216
2217 #[test]
2218 fn cell_filter_without_underscore_is_a_parse_error() {
2219 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2220 let err = parser.parse_str("sheet s { cell a: i32 filter 1; }");
2221 assert!(err.is_err());
2222 }
2223
2224 #[test]
2225 fn cell_filter_body_type_mismatch_is_a_parse_error() {
2226 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2227 let err = parser.parse_str("sheet s { cell a: i32 filter _ > 0; }");
2228 assert!(err.is_err());
2229 }
2230
2231 #[test]
2232 fn cell_filter_undeclared_identifier_is_a_parse_error() {
2233 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2234 let err = parser.parse_str("sheet s { cell a: i32 filter _ + nope; }");
2235 assert!(err.is_err());
2236 }
2237
2238 #[test]
2239 fn cell_filter_with_a_range_inclusive_body_clamps_on_write() {
2240 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2241 let mut parsed = parser
2242 .parse_str("sheet s { cell a: i32 filter 0..=100; }")
2243 .unwrap();
2244 let (a_id, _) = parsed.cell_names["a"];
2245 assert!(matches!(
2246 parsed.sheet.filter_kind(a_id),
2247 Some(adam_rs::FilterKind::Range { .. })
2248 ));
2249 parsed.sheet.write(a_id, 500i32).unwrap();
2250 parsed.sheet.propagate().unwrap();
2251 assert_eq!(*parsed.sheet.read::<i32>(a_id).unwrap(), 100);
2252 }
2253
2254 #[test]
2256 fn public_range_bounds_rejects_invalid_scalar_inputs() {
2257 let parsed = AdamParser::new(TypeRegistry::new(), OpLookup::new())
2258 .parse_str("sheet s { cell hi: f64 = 10.0; cell value: f64 filter 0.0..=hi; }")
2259 .unwrap();
2260 let Some(adam_rs::FilterKind::Range { bounds }) =
2261 parsed.sheet.filter_kind(parsed.cell_names["value"].0)
2262 else {
2263 panic!("expected range filter");
2264 };
2265 let invalid: [&[&dyn Any]; 4] = [&[&42u64], &[&1u8], &[], &[&10.0f64, &20.0f64]];
2266 for _ in 0..2 {
2267 for args in invalid {
2268 let (lo, hi) = bounds(&[&10.0f64]).unwrap();
2269 assert_eq!(*lo.downcast::<f64>().unwrap(), 0.0);
2270 assert_eq!(*hi.downcast::<f64>().unwrap(), 10.0);
2271 assert!(bounds(args).is_none(), "invalid scalar inputs accepted");
2274 let (_, hi) = bounds(&[&25.0f64]).unwrap();
2275 assert_eq!(*hi.downcast::<f64>().unwrap(), 25.0);
2276 }
2277 }
2278 }
2279
2280 #[test]
2282 fn public_range_bounds_rejects_invalid_compound_inputs() {
2283 use cel_runtime::{Cons, IntoCStackList, Leaf, Nil, OwnedValue};
2284
2285 let parsed = AdamParser::new(TypeRegistry::new(), OpLookup::new())
2286 .parse_str(
2287 "sheet s { cell limits: (f64, f64) = (0.0, 10.0); \
2288 cell value: f64 filter limits.0..=limits.1; }",
2289 )
2290 .unwrap();
2291 let Some(adam_rs::FilterKind::Range { bounds }) =
2292 parsed.sheet.filter_kind(parsed.cell_names["value"].0)
2293 else {
2294 panic!("expected range filter");
2295 };
2296 type Limits = Cons<Leaf<f64>, Cons<Leaf<f64>, Nil>>;
2297 type WrongLimits = Cons<Leaf<f64>, Cons<Leaf<u64>, Nil>>;
2298 let valid = CellValue(OwnedValue::new::<Limits>((0.0, 10.0).into_c_stack_list()));
2299 let wrong_shape = CellValue(OwnedValue::new::<WrongLimits>(
2301 (0.0, 10.0f64.to_bits()).into_c_stack_list(),
2302 ));
2303 let invalid: [&[&dyn Any]; 4] = [&[&wrong_shape], &[&false], &[], &[&valid, &valid]];
2304 for _ in 0..2 {
2305 for args in invalid {
2306 let (lo, hi) = bounds(&[&valid]).unwrap();
2307 assert_eq!(*lo.downcast::<f64>().unwrap(), 0.0);
2308 assert_eq!(*hi.downcast::<f64>().unwrap(), 10.0);
2309 assert!(bounds(args).is_none(), "invalid compound inputs accepted");
2310 assert!(bounds(&[&valid]).is_some());
2311 }
2312 }
2313 }
2314
2315 #[test]
2316 fn cell_filter_range_does_not_require_underscore() {
2317 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2318 let result = parser.parse_str("sheet s { cell a: i32 filter 0..=100; }");
2319 assert!(result.is_ok());
2320 }
2321
2322 #[test]
2323 fn cell_filter_range_bounds_track_cell_dependencies_live() {
2324 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2325 let mut parsed = parser
2326 .parse_str(
2327 "sheet s { cell lo: i32 = 0; cell hi: i32 = 100; cell a: i32 filter lo..=hi; }",
2328 )
2329 .unwrap();
2330 let (a_id, _) = parsed.cell_names["a"];
2331 let (hi_id, _) = parsed.cell_names["hi"];
2332 assert_eq!(parsed.sheet.filter_range::<i32>(a_id), Some((0, 100)));
2333 parsed.sheet.write(hi_id, 10i32).unwrap();
2334 assert_eq!(parsed.sheet.filter_range::<i32>(a_id), Some((0, 10)));
2335 parsed.sheet.write(a_id, 500i32).unwrap();
2336 parsed.sheet.propagate().unwrap();
2337 assert_eq!(*parsed.sheet.read::<i32>(a_id).unwrap(), 10);
2338 }
2339
2340 #[test]
2341 fn cell_filter_range_with_float_cell_type_works() {
2342 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2343 let mut parsed = parser
2344 .parse_str("sheet s { cell a: f64 filter 0.0..=100.0; }")
2345 .unwrap();
2346 let (a_id, _) = parsed.cell_names["a"];
2347 parsed.sheet.write(a_id, 500.0f64).unwrap();
2348 parsed.sheet.propagate().unwrap();
2349 assert_eq!(*parsed.sheet.read::<f64>(a_id).unwrap(), 100.0);
2350 }
2351
2352 #[test]
2353 fn cell_filter_range_with_mismatched_element_type_is_a_parse_error() {
2354 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2355 let err = parser
2356 .parse_str("sheet s { cell a: f64 filter 0..=100; }")
2357 .expect_err("expected Err");
2358 assert!(
2359 err.message().contains("filter range bounds must be"),
2360 "{}",
2361 err.message()
2362 );
2363 }
2364
2365 #[test]
2366 fn cell_filter_range_with_mismatched_element_type_and_underscore_is_still_a_range_error() {
2367 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2372 let err = parser
2373 .parse_str("sheet s { cell a: f64 filter (_ as i32)..=100; }")
2374 .expect_err("expected Err");
2375 assert!(
2376 err.message().contains("filter range bounds must be"),
2377 "{}",
2378 err.message()
2379 );
2380 }
2381
2382 #[test]
2383 fn cell_filter_range_returns_none_instead_of_panicking_when_a_bound_expression_fails_to_evaluate()
2384 {
2385 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2396 let mut parsed = parser
2397 .parse_str("sheet s { cell hi: i32 = 1; cell a: i32 filter 0..=(100 / hi); }")
2398 .unwrap();
2399 let (a_id, _) = parsed.cell_names["a"];
2400 let (hi_id, _) = parsed.cell_names["hi"];
2401
2402 assert_eq!(parsed.sheet.filter_range::<i32>(a_id), Some((0, 100)));
2403
2404 parsed.sheet.write(hi_id, 0i32).unwrap();
2409 assert_eq!(parsed.sheet.filter_range::<i32>(a_id), None);
2410 }
2411
2412 #[test]
2413 fn cell_filter_general_expression_still_compiles_to_opaque_kind() {
2414 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2415 let parsed = parser
2416 .parse_str("sheet s { cell a: i32 filter if _ < 0 { 0 } else { _ }; }")
2417 .unwrap();
2418 let (a_id, _) = parsed.cell_names["a"];
2419 assert!(matches!(
2420 parsed.sheet.filter_kind(a_id),
2421 Some(adam_rs::FilterKind::Opaque)
2422 ));
2423 }
2424
2425 #[test]
2426 fn filter_tracks_a_tuple_typed_range_cell_dynamically() {
2427 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2428 let mut parsed = parser
2429 .parse_str(
2430 "sheet s { \
2431 cell a_range: (i32, i32) = (1, 100); \
2432 cell max: i32 = 100; \
2433 relationship { a_range := (1, max); } \
2434 cell a: i32 filter if _ < a_range.0 { a_range.0 } \
2435 else if _ > a_range.1 { a_range.1 } else { _ }; \
2436 }",
2437 )
2438 .unwrap();
2439 let (a_id, _) = parsed.cell_names["a"];
2440 let (max_id, _) = parsed.cell_names["max"];
2441
2442 parsed.sheet.write(a_id, 500i32).unwrap();
2443 parsed.sheet.propagate().unwrap();
2444 assert_eq!(*parsed.sheet.read::<i32>(a_id).unwrap(), 100);
2445
2446 parsed.sheet.write(max_id, 10i32).unwrap();
2447 parsed.sheet.propagate().unwrap();
2448 parsed.sheet.write(a_id, 500i32).unwrap();
2449 parsed.sheet.propagate().unwrap();
2450 assert_eq!(*parsed.sheet.read::<i32>(a_id).unwrap(), 10);
2451 }
2452
2453 #[test]
2454 fn cell_filter_on_a_tuple_typed_cell_is_supported() {
2455 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2456 let sheet = parser
2457 .parse_str("sheet s { cell a: (i32, i32) filter (_.0, _.1); }")
2458 .unwrap();
2459 assert!(sheet.filter_kind(sheet.cell_names["a"].0).is_some());
2460 }
2461
2462 #[test]
2463 fn parse_filter_clause_attaches_a_filter() {
2464 let sheet = parser()
2465 .parse_str("sheet s { cell x: i32 = 0 filter 0..=10; }")
2466 .unwrap();
2467 let (x, _) = sheet.cell_names["x"];
2468 assert!(sheet.filter_kind(x).is_some());
2469 }
2470
2471 #[test]
2472 fn parse_filter_clause_with_a_label_is_now_a_parse_error() {
2473 let result = parser().parse_str("sheet s { cell x: i32 = 0 filter clamp: 0..=10; }");
2474 assert!(result.is_err());
2475 }
2476
2477 #[test]
2478 fn parse_multiple_cells() {
2479 let _sheet = parser()
2480 .parse_str(
2481 r#"
2482 sheet image_resize {
2483 cell width: f64 = 1920.0;
2484 cell height: f64 = 1080.0;
2485 cell area: f64;
2486 cell mode: i32 = 0;
2487 }
2488 "#,
2489 )
2490 .unwrap();
2491 }
2492
2493 #[test]
2494 fn parse_relationship_with_a_single_binding() {
2495 let mut sheet = parser()
2496 .parse_str(
2497 r#"
2498 sheet s {
2499 cell a: i32 = 2;
2500 cell b: i32 = 0;
2501 relationship {
2502 b := a;
2503 }
2504 }
2505 "#,
2506 )
2507 .unwrap();
2508 sheet.propagate().unwrap();
2509 let (b_id, _) = sheet.cell_names["b"].clone();
2510 assert_eq!(*sheet.read::<i32>(b_id).unwrap(), 2);
2511 }
2512
2513 #[test]
2514 fn parse_relationship_deduces_inputs_from_referenced_identifiers() {
2515 let mut sheet = parser()
2516 .parse_str(
2517 r#"
2518 sheet s {
2519 cell a: i32 = 2;
2520 cell b: i32 = 3;
2521 cell c: i32 = 0;
2522 relationship {
2523 c := a * b;
2524 }
2525 }
2526 "#,
2527 )
2528 .unwrap();
2529 sheet.propagate().unwrap();
2530 let (c_id, _) = sheet.cell_names["c"].clone();
2531 assert_eq!(*sheet.read::<i32>(c_id).unwrap(), 6);
2532 }
2533
2534 #[test]
2536 fn member_labels_are_not_cell_dependencies() {
2537 let mut lookup = OpLookup::new();
2538 lookup.push_scope(|name, segment, arity, _| {
2539 if matches!(name, "x" | "width") && arity == 1 {
2540 segment.op1(|value: f64| value * 2.0)?;
2541 return Ok(true);
2542 }
2543 Ok(false)
2544 });
2545 let mut sheet = AdamParser::new(TypeRegistry::new(), lookup)
2546 .parse_str(
2547 "sheet s { cell input: f64 = 3.0; cell x: bool = true; \
2548 cell width: bool = false; cell result: f64 = 0.0; \
2549 relationship { result := input.x().width; } }",
2550 )
2551 .unwrap();
2552 let input = sheet.cell_names["input"].0;
2553 let result = sheet.cell_names["result"].0;
2554 let (&(relationship, method), _) = sheet.method_spans.iter().next().unwrap();
2555 assert_eq!(
2556 sheet.method_inputs(relationship, method),
2557 Some([input].as_slice())
2558 );
2559 sheet.propagate().unwrap();
2560 assert_eq!(*sheet.read::<f64>(result).unwrap(), 12.0);
2561 sheet.write(input, 5.0f64).unwrap();
2562 sheet.propagate().unwrap();
2563 assert_eq!(*sheet.read::<f64>(result).unwrap(), 20.0);
2564 }
2565
2566 #[test]
2568 fn field_labels_and_member_names_are_not_cell_dependencies() {
2569 use cel_parser::{OperationSignature, RecordField, RecordSchema, ResolvedLeafType};
2570 use cel_runtime::ValueType;
2571 #[derive(Clone, Debug, PartialEq)]
2573 struct TestPoint {
2574 x: f64,
2575 y: f64,
2576 }
2577 let point = ResolvedLeafType::new("TestPoint", ValueType::leaf::<TestPoint>());
2578 let scalar = ResolvedLeafType::new("f64", ValueType::leaf::<f64>());
2579 let mut lookup = OpLookup::new();
2580 lookup
2581 .register_record(RecordSchema {
2582 ty: point.clone(),
2583 fields: vec![
2584 RecordField {
2585 name: "x",
2586 ty: scalar.clone().into(),
2587 },
2588 RecordField {
2589 name: "y",
2590 ty: scalar.clone().into(),
2591 },
2592 ],
2593 emit: Box::new(|s, order, _, _| {
2594 assert_eq!(order, [0, 1]);
2595 s.op2(|x: f64, y: f64| TestPoint { x, y })
2596 }),
2597 })
2598 .unwrap();
2599 lookup
2600 .register_operation(OperationSignature {
2601 name: "x",
2602 operands: vec![point.into()],
2603 result: scalar.into(),
2604 emit: Box::new(|s, _| s.op1(|p: TestPoint| p.x)),
2605 })
2606 .unwrap();
2607 let mut sheet = AdamParser::new(TypeRegistry::new(), lookup)
2608 .parse_str(
2609 "sheet s { cell a: f64 = 2.0; cell b: f64 = 3.0; \
2610 cell x: bool = false; cell y: bool = false; cell TestPoint: bool = true; \
2611 cell result: f64 = 0.0; \
2612 relationship { result := TestPoint { x: a, y: b }.x; } }",
2613 )
2614 .unwrap();
2615 let a = sheet.cell_names["a"].0;
2616 let b = sheet.cell_names["b"].0;
2617 let result = sheet.cell_names["result"].0;
2618 let (&(relationship, method), _) = sheet.method_spans.iter().next().unwrap();
2619 assert_eq!(
2620 sheet.method_inputs(relationship, method),
2621 Some([a, b].as_slice())
2622 );
2623 sheet.propagate().unwrap();
2624 assert_eq!(*sheet.read::<f64>(result).unwrap(), 2.0);
2625 sheet.write(a, 7.0f64).unwrap();
2626 sheet.propagate().unwrap();
2627 assert_eq!(*sheet.read::<f64>(result).unwrap(), 7.0);
2628 }
2629
2630 #[test]
2631 fn parse_relationship_with_multiple_bindings_lets_the_planner_pick_a_direction() {
2632 let mut sheet = parser()
2639 .parse_str(
2640 r#"
2641 sheet s {
2642 cell c: i32 = 0;
2643 cell a: i32 = 2;
2644 cell b: i32 = 3;
2645 relationship {
2646 c := a * b;
2647 a := c / b;
2648 b := c / a;
2649 }
2650 }
2651 "#,
2652 )
2653 .unwrap();
2654 sheet.propagate().unwrap();
2655 let (c_id, _) = sheet.cell_names["c"].clone();
2656 assert_eq!(*sheet.read::<i32>(c_id).unwrap(), 6);
2657 }
2658
2659 #[test]
2660 fn mismatched_method_cells_error_spans_the_relationship_block_not_the_sheet() {
2661 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2662 let source = "sheet s {\n cell a: i32;\n cell b: i32;\n\n relationship {\n a := b;\n b := 42;\n }\n}";
2663 let err = parser.parse_str(source).err().unwrap();
2664 assert_eq!(err.span().start().line, 7);
2669 }
2670
2671 #[test]
2672 fn successful_relationship_parse_populates_method_spans() {
2673 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2674 let source = "sheet s {\n cell a: i32;\n cell b: i32;\n\n relationship {\n a := b;\n }\n}";
2675 let parsed = parser.parse_str(source).unwrap();
2676 assert_eq!(parsed.method_spans.len(), 1);
2677 let ((_, idx), span) = parsed.method_spans.iter().next().unwrap();
2678 assert_eq!(*idx, 0);
2679 assert_eq!(span.start.line, 6);
2680 }
2681
2682 #[test]
2683 fn parse_populates_relationship_and_cell_spans() {
2684 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2685 let parsed = parser
2686 .parse_str("sheet s { cell a: i32 = 0; cell b: i32; relationship { b := a; } }")
2687 .unwrap();
2688 assert_eq!(parsed.cell_spans.len(), 2);
2689 assert_eq!(parsed.relationship_spans.len(), 1);
2690 for (_, (id, _)) in &parsed.cell_names {
2692 assert!(parsed.cell_spans.contains_key(id));
2693 }
2694 }
2695
2696 #[test]
2697 fn locate_error_resolves_a_cycle_to_multiple_ordered_spans() {
2698 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2699 let mut parsed = parser
2700 .parse_str(
2701 "sheet s { cell x: i32 = 0; cell y: i32 = 0; relationship { x := y + 1i32; } \
2702 relationship { y := x + 1i32; } }",
2703 )
2704 .unwrap();
2705 let err = parsed.propagate().unwrap_err();
2706 assert!(matches!(err, adam_rs::Error::Cycle { .. }));
2707 let located = parsed.locate_error(&err);
2708 let rel_spans = located.len();
2710 assert!(
2711 rel_spans >= 2,
2712 "expected >=2 spans, got {rel_spans}: {located:?}"
2713 );
2714 }
2715
2716 #[test]
2717 fn duplicate_output_set_error_underlines_both_bindings() {
2718 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
2719 let source =
2720 "sheet s { cell a: i32 = 0; cell b: i32; relationship { b := a; b := a + 1i32; } }";
2721 let err = parser.parse_str(source).unwrap_err();
2722 let out =
2723 err.format_rustc_style(source, "t.adm2", 1, &annotate_snippets::Renderer::plain());
2724 assert!(out.contains("outputs"), "{out}"); assert!(out.contains("collides with this earlier method"), "{out}");
2728 assert!(
2729 out.contains("output cell `b` is claimed more than once"),
2730 "{out}"
2731 );
2732 }
2733
2734 #[test]
2735 fn parse_binding_undeclared_output_is_an_error() {
2736 let result = parser().parse_str(
2737 r#"
2738 sheet s {
2739 cell a: i32 = 1;
2740 relationship {
2741 missing := a;
2742 }
2743 }
2744 "#,
2745 );
2746 assert!(result.is_err());
2747 }
2748
2749 #[test]
2750 fn parse_binding_multi_output_tuple_matches_existing_tuple_shape_rules() {
2751 let mut sheet = parser()
2752 .parse_str(
2753 r#"
2754 sheet s {
2755 cell w: i32 = 4;
2756 cell x: i32 = 0;
2757 cell y: i32 = 0;
2758 relationship {
2759 (x, y) := (w, w * 2);
2760 }
2761 }
2762 "#,
2763 )
2764 .unwrap();
2765 sheet.propagate().unwrap();
2766 let (x_id, _) = sheet.cell_names["x"].clone();
2767 let (y_id, _) = sheet.cell_names["y"].clone();
2768 assert_eq!(*sheet.read::<i32>(x_id).unwrap(), 4);
2769 assert_eq!(*sheet.read::<i32>(y_id).unwrap(), 8);
2770 }
2771
2772 #[test]
2773 fn parse_binding_multi_output_arity_mismatch_is_an_error() {
2774 let result = parser().parse_str(
2775 r#"
2776 sheet s {
2777 cell w: i32 = 4;
2778 cell x: i32 = 0;
2779 cell y: i32 = 0;
2780 relationship {
2781 (x, y) := w;
2782 }
2783 }
2784 "#,
2785 );
2786 assert!(result.is_err());
2787 }
2788
2789 #[test]
2790 fn parse_binding_multi_output_without_parens_is_a_parse_error() {
2791 let result = parser().parse_str(
2792 r#"
2793 sheet s {
2794 cell w: i32 = 4;
2795 cell x: i32 = 0;
2796 cell y: i32 = 0;
2797 relationship {
2798 x, y := (w, w * 2);
2799 }
2800 }
2801 "#,
2802 );
2803 assert!(result.is_err());
2804 }
2805
2806 #[test]
2807 fn parse_binding_single_element_tuple_destructure_extracts_the_element() {
2808 let mut sheet = parser()
2809 .parse_str(
2810 r#"
2811 sheet s {
2812 cell w: i32 = 4;
2813 cell x: i32 = 0;
2814 relationship {
2815 (x,) := (w,);
2816 }
2817 }
2818 "#,
2819 )
2820 .unwrap();
2821 sheet.propagate().unwrap();
2822 let (x_id, _) = sheet.cell_names["x"].clone();
2823 assert_eq!(*sheet.read::<i32>(x_id).unwrap(), 4);
2824 }
2825
2826 #[test]
2827 fn parse_binding_single_parenthesized_identifier_without_comma_is_a_direct_bind() {
2828 let mut sheet = parser()
2829 .parse_str(
2830 r#"
2831 sheet s {
2832 cell w: i32 = 4;
2833 cell x: i32 = 0;
2834 relationship {
2835 (x) := w;
2836 }
2837 }
2838 "#,
2839 )
2840 .unwrap();
2841 sheet.propagate().unwrap();
2842 let (x_id, _) = sheet.cell_names["x"].clone();
2843 assert_eq!(*sheet.read::<i32>(x_id).unwrap(), 4);
2844 }
2845
2846 #[test]
2847 fn parse_out_with_direct_initializer_and_no_require_block() {
2848 let mut sheet = parser()
2849 .parse_str(
2850 r#"
2851 sheet s {
2852 cell a: i32 = 3;
2853 cell b: i32 = 4;
2854 out area: i32 := a * b;
2855 }
2856 "#,
2857 )
2858 .unwrap();
2859 sheet.propagate().unwrap();
2860 let cell_id = sheet.output_names["area"];
2861 assert_eq!(*sheet.sheet.read::<i32>(cell_id).unwrap(), 12);
2862 }
2863
2864 #[test]
2865 fn parse_out_with_no_type_annotation_infers_from_initializer() {
2866 let sheet = parser()
2867 .parse_str(
2868 r#"
2869 sheet s {
2870 cell a: i32 = 3;
2871 out doubled := a * 2;
2872 }
2873 "#,
2874 )
2875 .unwrap();
2876 let (_, shape) = sheet.cell_names["doubled"].clone();
2877 assert_eq!(
2878 shape,
2879 crate::type_registry::TypeShape::Named(std::any::TypeId::of::<i32>())
2880 );
2881 }
2882
2883 #[test]
2884 fn parse_out_with_a_require_block_registers_named_requirements() {
2885 let mut sheet = parser()
2886 .parse_str(
2887 r#"
2888 sheet s {
2889 cell a: i32 = 3;
2890 cell b: i32 = 4;
2891 out area: i32 := a * b require {
2892 @positive area > 0;
2893 @small area < 1000;
2894 };
2895 }
2896 "#,
2897 )
2898 .unwrap();
2899 sheet.propagate().unwrap();
2900 let output_id = sheet.output_names["area"];
2901 assert!(sheet.sheet.cell_requirements(output_id).unwrap().len() == 2);
2902 assert!(
2903 sheet
2904 .sheet
2905 .violated_requirements(output_id)
2906 .next()
2907 .is_none()
2908 );
2909 }
2910
2911 #[test]
2912 fn parse_out_require_block_requirement_can_violate() {
2913 let mut sheet = parser()
2914 .parse_str(
2915 r#"
2916 sheet s {
2917 cell a: i32 = 3;
2918 cell b: i32 = 4;
2919 out area: i32 := a * b require {
2920 @too_small area > 1000;
2921 };
2922 }
2923 "#,
2924 )
2925 .unwrap();
2926 sheet.propagate().unwrap();
2927 let output_id = sheet.output_names["area"];
2928 assert_eq!(sheet.sheet.violated_requirements(output_id).count(), 1);
2929 }
2930
2931 #[test]
2932 fn parse_requirement_non_bool_body_is_an_error() {
2933 let result = parser().parse_str(
2934 r#"
2935 sheet s {
2936 cell a: i32 = 3;
2937 out x: i32 := a require {
2938 @bad a;
2939 };
2940 }
2941 "#,
2942 );
2943 assert!(result.is_err());
2944 }
2945
2946 #[test]
2947 fn parses_a_sheet_with_an_outer_doc_comment_on_a_cell() {
2948 let parsed = parser()
2949 .parse_str("sheet s {\n /// the total\n cell x: i32 = 1;\n}")
2950 .unwrap();
2951 assert_eq!(parsed.cell_names.len(), 1);
2952 }
2953
2954 #[test]
2955 fn parses_a_sheet_with_an_inner_doc_comment() {
2956 let parsed = parser()
2957 .parse_str("//! module docs\nsheet s {\n cell x: i32 = 1;\n}")
2958 .unwrap();
2959 assert_eq!(parsed.cell_names.len(), 1);
2960 }
2961
2962 #[test]
2963 fn parses_a_sheet_with_doc_comments_on_every_declaration_kind() {
2964 let source = "//! module docs\nsheet s {\n /// a cell\n cell x: i32 = 1;\n\n /// another cell\n cell y: i32 = 2;\n\n /// a relationship\n relationship { y := x; }\n}";
2965 let parsed = parser().parse_str(source).unwrap();
2966 assert_eq!(parsed.cell_names.len(), 2);
2967 }
2968
2969 #[test]
2970 fn parse_binding_undeclared_input_is_error() {
2971 let result = parser().parse_str(
2972 r#"
2973 sheet s {
2974 cell x: f64 = 1.0;
2975 relationship { x := bogus; }
2976 }
2977 "#,
2978 );
2979 assert!(result.is_err());
2980 let err = result.expect_err("expected Err");
2981 let msg = err.message().to_lowercase();
2982 assert!(msg.contains("bogus") || msg.contains("undefined"), "{msg}");
2983 }
2984
2985 #[test]
2986 fn parse_method_output_type_mismatch_is_error() {
2987 let result = parser().parse_str(
2988 r#"
2989 sheet s {
2990 cell x: f64 = 0.0;
2991 cell n: i32 = 0;
2992 relationship { n := x; }
2993 }
2994 "#,
2995 );
2996 assert!(result.is_err(), "f64 body for i32 output must be an error");
2997 }
2998
2999 #[test]
3000 fn parse_and_propagate_multi_output_tuple_sheet() {
3001 let mut sheet = parser()
3002 .parse_str(
3003 r#"
3004 sheet s {
3005 cell a: i32 = 3;
3006 cell b: i32 = 4;
3007 cell sum: i32;
3008 cell diff: i32;
3009 relationship { (sum, diff) := (a + b, a - b); }
3010 }
3011 "#,
3012 )
3013 .unwrap();
3014
3015 sheet.propagate().unwrap();
3020 let _ = sheet; }
3022
3023 #[test]
3024 fn parse_method_output_tuple_arity_mismatch_is_error() {
3025 let result = parser().parse_str(
3026 r#"
3027 sheet s {
3028 cell a: i32 = 1;
3029 cell b: i32 = 2;
3030 cell x: i32;
3031 cell y: i32;
3032 cell z: i32;
3033 relationship { (x, y, z) := (a + b, a - b); }
3034 }
3035 "#,
3036 );
3037 assert!(
3038 result.is_err(),
3039 "2-tuple body for 3 declared outputs must be an error"
3040 );
3041 let err = result.expect_err("expected Err");
3042 let msg = err.message().to_lowercase();
3043 assert!(msg.contains("arity"), "{msg}");
3044 }
3045
3046 #[test]
3047 fn parse_method_output_type_mismatch_names_the_registered_type() {
3048 let result = parser().parse_str(
3049 r#"
3050 sheet s {
3051 cell a: String = "x";
3052 cell x: i32;
3053 relationship { x := a; }
3054 }
3055 "#,
3056 );
3057 let err = result.expect_err("a String body for an i32 output must be an error");
3058 let msg = err.message();
3059 assert!(msg.contains("got `String`"), "{msg}");
3060 assert!(!msg.contains("::"), "no Rust type path may leak: {msg}");
3061 }
3062
3063 #[test]
3064 fn parse_method_destructured_output_type_mismatch_names_the_registered_type() {
3065 let result = parser().parse_str(
3066 r#"
3067 sheet s {
3068 cell a: String = "x";
3069 cell b: i32 = 1;
3070 cell x: i32;
3071 cell y: i32;
3072 relationship { (x, y) := (a, b); }
3073 }
3074 "#,
3075 );
3076 let err = result.expect_err("a String tuple element for an i32 output must be an error");
3077 let msg = err.message();
3078 assert!(msg.contains("got `String`"), "{msg}");
3079 assert!(!msg.contains("::"), "no Rust type path may leak: {msg}");
3080 }
3081
3082 #[test]
3083 fn parse_method_output_tuple_element_type_mismatch_is_error() {
3084 let result = parser().parse_str(
3085 r#"
3086 sheet s {
3087 cell a: i32 = 1;
3088 cell b: f64 = 2.0;
3089 cell x: i32;
3090 cell y: i32;
3091 relationship { (x, y) := (a, b); }
3092 }
3093 "#,
3094 );
3095 assert!(
3096 result.is_err(),
3097 "f64 tuple element for an i32 output must be an error"
3098 );
3099 let err = result.expect_err("expected Err");
3100 let msg = err.message().to_lowercase();
3101 assert!(msg.contains("type mismatch"), "{msg}");
3102 }
3103
3104 #[test]
3105 fn parse_method_single_output_rejects_tuple_body() {
3106 let result = parser().parse_str(
3107 r#"
3108 sheet s {
3109 cell x: i32 = 1;
3110 cell y: i32;
3111 relationship { y := (x,); }
3112 }
3113 "#,
3114 );
3115 assert!(
3116 result.is_err(),
3117 "1-tuple body for a single declared output must be an error"
3118 );
3119 }
3120
3121 #[test]
3122 fn parse_method_single_tuple_typed_output() {
3123 let mut sheet = parser()
3124 .parse_str(
3125 r#"
3126 sheet s {
3127 cell a: i32 = 3;
3128 cell b: i32 = 4;
3129 cell pair: (i32, i32);
3130 relationship { pair := (a, b); }
3131 }
3132 "#,
3133 )
3134 .unwrap();
3135 sheet.propagate().unwrap();
3136 let (cell_id, _) = sheet.cell_names["pair"].clone();
3137 let value = sheet
3138 .read_compound::<cel_runtime::Cons<
3139 cel_runtime::Leaf<i32>,
3140 cel_runtime::Cons<cel_runtime::Leaf<i32>, cel_runtime::Nil>,
3141 >>(cell_id)
3142 .unwrap();
3143 let (a, b) = (value.1, value.0.1);
3144 assert_eq!((a, b), (3, 4));
3145 }
3146
3147 #[test]
3148 fn parse_method_tuple_typed_output_among_several() {
3149 let mut sheet = parser()
3150 .parse_str(
3151 r#"
3152 sheet s {
3153 cell a: i32 = 3;
3154 cell b: i32 = 4;
3155 cell pair: (i32, i32);
3156 cell extra: i32;
3157 relationship { (pair, extra) := ((a, b), a); }
3158 }
3159 "#,
3160 )
3161 .unwrap();
3162 sheet.propagate().unwrap();
3163 let (pair_id, _) = sheet.cell_names["pair"].clone();
3164 let (extra_id, _) = sheet.cell_names["extra"].clone();
3165 let pair = sheet
3166 .read_compound::<cel_runtime::Cons<
3167 cel_runtime::Leaf<i32>,
3168 cel_runtime::Cons<cel_runtime::Leaf<i32>, cel_runtime::Nil>,
3169 >>(pair_id)
3170 .unwrap();
3171 let (a, b) = (pair.1, pair.0.1);
3172 assert_eq!((a, b), (3, 4));
3173 assert_eq!(*sheet.read::<i32>(extra_id).unwrap(), 3);
3174 }
3175
3176 #[test]
3177 fn parse_method_with_tuple_typed_input_supports_field_indexing() {
3178 let mut sheet = parser()
3179 .parse_str(
3180 r#"
3181 sheet s {
3182 cell pair: (i32, i32) = (10, 20);
3183 cell sum: i32;
3184 relationship { sum := pair.0 + pair.1; }
3185 }
3186 "#,
3187 )
3188 .unwrap();
3189 sheet.propagate().unwrap();
3190 let (sum_id, _) = sheet.cell_names["sum"].clone();
3191 assert_eq!(*sheet.read::<i32>(sum_id).unwrap(), 30);
3192 }
3193
3194 #[test]
3195 fn parse_method_tuple_output_shape_mismatch_is_an_error() {
3196 let result = parser().parse_str(
3197 r#"
3198 sheet s {
3199 cell a: i32 = 1;
3200 cell b: f64 = 2.0;
3201 cell pair: (i32, i32);
3202 relationship { pair := (a, b); }
3203 }
3204 "#,
3205 );
3206 assert!(result.is_err());
3207 }
3208
3209 #[test]
3210 fn existing_multi_output_scalar_methods_still_work_unchanged() {
3211 let mut sheet = parser()
3214 .parse_str(
3215 r#"
3216 sheet s {
3217 cell a: i32 = 3;
3218 cell b: i32 = 4;
3219 cell sum: i32;
3220 cell diff: i32;
3221 relationship { (sum, diff) := (a + b, a - b); }
3222 }
3223 "#,
3224 )
3225 .unwrap();
3226 sheet.propagate().unwrap();
3227 let (sum_id, _) = sheet.cell_names["sum"].clone();
3228 let (diff_id, _) = sheet.cell_names["diff"].clone();
3229 assert_eq!(*sheet.read::<i32>(sum_id).unwrap(), 7);
3230 assert_eq!(*sheet.read::<i32>(diff_id).unwrap(), -1);
3231 }
3232
3233 #[test]
3234 fn parse_method_single_empty_tuple_typed_output() {
3235 let mut sheet = parser()
3236 .parse_str(
3237 r#"
3238 sheet s {
3239 cell x: i32 = 1;
3240 cell nothing: ();
3241 // `()` alone references no cell, and adam_rs rejects a method with no
3242 // inputs -- reference `x` via an if/else whose branches both still
3243 // evaluate to `()`, so the binding has a deduced input.
3244 relationship { nothing := if x > 0 { () } else { () }; }
3245 }
3246 "#,
3247 )
3248 .unwrap();
3249 sheet.propagate().unwrap();
3250 let (cell_id, _) = sheet.cell_names["nothing"].clone();
3251 sheet.read_compound::<cel_runtime::Nil>(cell_id).unwrap();
3252 }
3253
3254 #[test]
3255 fn parse_conditional_decl() {
3256 let _sheet = parser()
3257 .parse_str(
3258 r#"
3259 sheet image_resize {
3260 cell width: f64 = 1920.0;
3261 cell height: f64 = 1080.0;
3262 cell ratio: f64 = 1.0;
3263 cell mode: i32 = 0;
3264 conditional mode {
3265 0i32 => {
3266 relationship { height := width; }
3267 },
3268 1i32 => {
3269 relationship { height := width * ratio; }
3270 },
3271 _ => {
3272 relationship { height := width; }
3273 },
3274 }
3275 }
3276 "#,
3277 )
3278 .unwrap();
3279 }
3280
3281 #[test]
3282 fn parse_conditional_branch_with_multiple_relationships() {
3283 let mut sheet = parser()
3284 .parse_str(
3285 r#"
3286 sheet s {
3287 cell mode: i32 = 0;
3288 cell a: f64 = 2.0;
3289 cell b: f64 = 3.0;
3290 cell c: f64;
3291 cell d: f64 = 4.0;
3292 cell e: f64 = 5.0;
3293 cell f: f64;
3294 conditional mode {
3295 0i32 => {
3296 relationship { c := a * b; }
3297 relationship { f := d * e; }
3298 }
3299 }
3300 }
3301 "#,
3302 )
3303 .unwrap();
3304 sheet.propagate().unwrap();
3305 }
3306
3307 #[test]
3308 fn conditional_branch_bare_binding_without_relationship_wrapper_is_error() {
3309 let result = parser().parse_str(
3310 r#"
3311 sheet s {
3312 cell x: i32 = 0;
3313 conditional x { 0i32 => { x := x; } }
3314 }
3315 "#,
3316 );
3317 assert!(
3318 result.is_err(),
3319 "a conditional_branch body now requires relationship_decl, not a bare binding"
3320 );
3321 }
3322
3323 #[test]
3324 fn conditional_undeclared_match_cell_is_error() {
3325 let result = parser().parse_str(
3326 r#"
3327 sheet s {
3328 cell x: i32 = 0;
3329 conditional bogus { 0i32 => { relationship { x := x; } } }
3330 }
3331 "#,
3332 );
3333 assert!(result.is_err());
3334 let err = result.expect_err("expected Err");
3335 let msg = err.message().to_lowercase();
3336 assert!(msg.contains("bogus") || msg.contains("undeclared"), "{msg}");
3337 }
3338
3339 #[test]
3340 fn conditional_branch_literal_type_mismatch_is_error() {
3341 let result = parser().parse_str(
3342 r#"
3343 sheet s {
3344 cell mode: i32 = 0;
3345 cell x: f64 = 0.0;
3346 conditional mode { 1.0 => { relationship { x := x; } } }
3347 }
3348 "#,
3349 );
3350 assert!(
3351 result.is_err(),
3352 "float literal for i32 match cell must be an error"
3353 );
3354 }
3355
3356 #[test]
3357 fn conditional_branch_negative_literal_key_matches() {
3358 let mut sheet = parser()
3359 .parse_str(
3360 r#"
3361 sheet s {
3362 cell mode: i32 = -1;
3363 cell x: i32 = 1;
3364 cell y: i32 = 0;
3365 conditional mode {
3366 -1i32 => { relationship { y := x; } },
3367 }
3368 }
3369 "#,
3370 )
3371 .unwrap();
3372 sheet.propagate().unwrap();
3373 let (y_id, _) = sheet.cell_names["y"].clone();
3374 assert_eq!(*sheet.read::<i32>(y_id).unwrap(), 1);
3375 }
3376
3377 #[test]
3378 fn conditional_branch_negating_unsigned_literal_key_is_error() {
3379 let result = parser().parse_str(
3380 r#"
3381 sheet s {
3382 cell mode: u32 = 0;
3383 cell x: i32 = 1;
3384 cell y: i32 = 0;
3385 conditional mode {
3386 -1u32 => { relationship { y := x; } },
3387 }
3388 }
3389 "#,
3390 );
3391 assert!(
3392 result.is_err(),
3393 "negating an unsigned literal must be a parse error"
3394 );
3395 }
3396
3397 #[test]
3398 fn conditional_with_tuple_typed_match_cell_and_default_only_still_works() {
3399 let mut sheet = parser()
3402 .parse_str(
3403 r#"
3404 sheet s {
3405 cell mode: (i32, i32) = (0, 0);
3406 cell x: f64 = 1.0;
3407 cell y: f64;
3408 conditional mode {
3409 _ => { relationship { y := x * 2.0; } },
3410 }
3411 }
3412 "#,
3413 )
3414 .unwrap();
3415 sheet.propagate().unwrap();
3416 let (y_id, _) = sheet.cell_names["y"].clone();
3417 assert_eq!(*sheet.read::<f64>(y_id).unwrap(), 2.0);
3418 }
3419
3420 #[test]
3421 fn conditional_branch_tuple_literal_key_is_error() {
3422 let result = parser().parse_str(
3423 r#"
3424 sheet s {
3425 cell mode: (i32, i32) = (0, 0);
3426 cell x: f64 = 1.0;
3427 cell y: f64;
3428 conditional mode {
3429 (0, 0) => { relationship { y := x; } },
3430 _ => { relationship { y := x * 2.0; } },
3431 }
3432 }
3433 "#,
3434 );
3435 assert!(result.is_err(), "tuple branch key must be a parse error");
3436 }
3437
3438 #[test]
3439 fn conditional_on_a_two_cell_boolean_expression_activates_and_reacts_to_writes() {
3440 let mut sheet = parser()
3441 .parse_str(
3442 r#"
3443 sheet s {
3444 cell a: bool = false;
3445 cell b: bool = false;
3446 cell x: i32 = 1;
3447 cell y: i32 = 0;
3448 conditional a && b {
3449 true => { relationship { y := x; } },
3450 }
3451 }
3452 "#,
3453 )
3454 .unwrap();
3455 let (a_id, _) = sheet.cell_names["a"].clone();
3456 let (b_id, _) = sheet.cell_names["b"].clone();
3457 let (y_id, _) = sheet.cell_names["y"].clone();
3458
3459 sheet.write(a_id, true).unwrap();
3460 sheet.write(b_id, false).unwrap();
3461 sheet.propagate().unwrap();
3462 assert_eq!(*sheet.read::<i32>(y_id).unwrap(), 0);
3463
3464 sheet.write(b_id, true).unwrap();
3465 sheet.propagate().unwrap();
3466 assert_eq!(*sheet.read::<i32>(y_id).unwrap(), 1);
3467 }
3468
3469 #[test]
3470 fn conditional_expression_referencing_the_same_cell_twice_compiles_and_evaluates() {
3471 let mut sheet = parser()
3472 .parse_str(
3473 r#"
3474 sheet s {
3475 cell a: bool = true;
3476 cell x: i32 = 1;
3477 cell y: i32 = 0;
3478 conditional a && a {
3479 true => { relationship { y := x; } },
3480 }
3481 }
3482 "#,
3483 )
3484 .unwrap();
3485 sheet.propagate().unwrap();
3486 let (y_id, _) = sheet.cell_names["y"].clone();
3487 assert_eq!(*sheet.read::<i32>(y_id).unwrap(), 1);
3488 }
3489
3490 #[test]
3491 fn conditional_bare_identifier_match_subject_still_works() {
3492 let mut sheet = parser()
3493 .parse_str(
3494 r#"
3495 sheet s {
3496 cell mode: i32 = 0;
3497 cell x: i32 = 1;
3498 cell y: i32 = 0;
3499 conditional mode {
3500 1i32 => { relationship { y := x; } },
3501 }
3502 }
3503 "#,
3504 )
3505 .unwrap();
3506 let (mode_id, _) = sheet.cell_names["mode"].clone();
3507 let (y_id, _) = sheet.cell_names["y"].clone();
3508
3509 sheet.propagate().unwrap();
3510 assert_eq!(*sheet.read::<i32>(y_id).unwrap(), 0);
3511
3512 sheet.write(mode_id, 1_i32).unwrap();
3513 sheet.propagate().unwrap();
3514 assert_eq!(*sheet.read::<i32>(y_id).unwrap(), 1);
3515 }
3516
3517 #[test]
3519 fn adam_registered_match_header_reserves_braces() {
3520 let mut parser = parser();
3521 parser
3522 .op_lookup_mut()
3523 .register_record(cel_parser::RecordSchema {
3524 ty: cel_parser::ResolvedLeafType::new(
3525 "Gate",
3526 cel_runtime::ValueType::leaf::<bool>(),
3527 ),
3528 fields: vec![cel_parser::RecordField {
3529 name: "value",
3530 ty: cel_parser::ResolvedLeafType::new(
3531 "bool",
3532 cel_runtime::ValueType::leaf::<bool>(),
3533 )
3534 .into(),
3535 }],
3536 emit: Box::new(|segment, _, _, _| segment.op1(|value: bool| value)),
3537 })
3538 .unwrap();
3539 let mut sheet = parser
3540 .parse_str(
3541 "sheet s {
3542 cell flag: bool = true;
3543 cell input: i32 = 7;
3544 cell output: i32 = 0;
3545 conditional (Gate { value: flag }) {
3546 true => { relationship { output := if flag { input } else { 0 }; } }
3547 _ => { relationship { output := 0; } }
3548 }
3549 }",
3550 )
3551 .unwrap();
3552 let output = sheet.cell_names["output"].0;
3553 let flag = sheet.cell_names["flag"].0;
3554 sheet.propagate().unwrap();
3555 assert_eq!(*sheet.read::<i32>(output).unwrap(), 7);
3556 sheet.write(flag, false).unwrap();
3557 sheet.propagate().unwrap();
3558 assert_eq!(*sheet.read::<i32>(output).unwrap(), 0);
3559 }
3560
3561 #[test]
3562 fn conditional_branch_bare_identifier_key_is_error() {
3563 let result = parser().parse_str(
3566 r#"
3567 sheet s {
3568 cell mode: i32 = 0;
3569 cell x: i32 = 1;
3570 cell y: i32 = 0;
3571 conditional mode {
3572 mode => { relationship { y := x; } },
3573 }
3574 }
3575 "#,
3576 );
3577 assert!(
3578 result.is_err(),
3579 "bare identifier branch key must be a parse error"
3580 );
3581 }
3582
3583 #[test]
3584 fn conditional_expression_referencing_an_undeclared_identifier_is_a_parse_error() {
3585 let result = parser().parse_str(
3586 r#"
3587 sheet s {
3588 cell a: bool = true;
3589 conditional a && nope {
3590 true => { relationship { a := a; } },
3591 }
3592 }
3593 "#,
3594 );
3595 assert!(result.is_err());
3596 }
3597
3598 #[test]
3599 fn parse_cell_literal_type_mismatch_is_error() {
3600 let result = parser().parse_str("sheet s { cell x: i32 = 1.0; }");
3602 assert!(
3603 result.is_err(),
3604 "float literal for i32 annotation must be an error"
3605 );
3606 }
3607
3608 #[test]
3609 fn parse_and_propagate_sheet() {
3610 let mut p = AdamParser::new(TypeRegistry::new(), OpLookup::new());
3611 let mut sheet = p
3612 .parse_str(
3613 r#"
3614 sheet image_resize {
3615 cell width: f64 = 4.0;
3616 cell height: f64 = 3.0;
3617 cell area: f64;
3618 relationship {
3619 area := width * height;
3620 width := area / height;
3621 height := area / width;
3622 }
3623 }
3624 "#,
3625 )
3626 .unwrap();
3627
3628 sheet.propagate().unwrap();
3633 let _ = sheet; }
3635
3636 #[test]
3637 fn parse_duplicate_cell_is_error() {
3638 let result = parser().parse_str("sheet s { cell x: i32; cell x: f64; }");
3639 match result {
3640 Ok(_) => panic!("expected error for duplicate cell name"),
3641 Err(e) => {
3642 let msg = e.to_string();
3643 assert!(
3644 msg.contains("duplicate cell `x`"),
3645 "error mentions duplicate name: {msg}"
3646 );
3647 }
3648 }
3649 }
3650
3651 #[test]
3652 fn parse_str_returns_cell_names_in_declaration_order() {
3653 let parsed = parser()
3654 .parse_str("sheet s { cell z: i32 = 1; cell a: i32 = 2; cell m: i32 = 3; }")
3655 .unwrap();
3656 let names: Vec<&str> = parsed.cell_names.keys().map(String::as_str).collect();
3657 assert_eq!(names, vec!["z", "a", "m"]);
3658 }
3659
3660 #[test]
3661 fn parsed_sheet_derefs_to_sheet_for_propagate() {
3662 let mut parsed = parser().parse_str("sheet s { cell x: i32 = 1; }").unwrap();
3663 parsed.propagate().unwrap();
3665 }
3666
3667 #[test]
3668 fn parse_out_with_explicit_type_propagates_correctly() {
3669 let mut sheet = parser()
3670 .parse_str(
3671 r#"
3672 sheet s {
3673 cell width: f64 = 4.0;
3674 cell height: f64 = 3.0;
3675 out area: f64 := width * height;
3676 }
3677 "#,
3678 )
3679 .unwrap();
3680 sheet.propagate().unwrap();
3681 let cell_id = *sheet.output_names.get("area").expect("area registered");
3682 assert_eq!(*sheet.read::<f64>(cell_id).unwrap(), 12.0);
3683 }
3684
3685 #[test]
3686 fn parse_out_with_tuple_type_value_debug_formats_correctly() {
3687 let mut sheet = parser()
3688 .parse_str(
3689 r#"
3690 sheet s {
3691 cell x: i32 = 3;
3692 out pair: (i32, i32) := (x, x);
3693 }
3694 "#,
3695 )
3696 .unwrap();
3697 sheet.propagate().unwrap();
3698 let cell_id = *sheet.output_names.get("pair").unwrap();
3699 let value = sheet.read::<CellValue>(cell_id).unwrap();
3700 assert_eq!(format!("{value:?}"), "(3, 3)");
3701 }
3702
3703 #[test]
3704 fn parse_out_with_no_annotation_infers_type_from_writer_body() {
3705 let sheet = parser()
3706 .parse_str(
3707 r#"
3708 sheet s {
3709 cell width: f64 = 4.0;
3710 out doubled := width + width;
3711 }
3712 "#,
3713 )
3714 .unwrap();
3715 let (_, shape) = sheet.cell_names.get("doubled").unwrap().clone();
3716 assert_eq!(shape, TypeShape::Named(std::any::TypeId::of::<f64>()));
3717 }
3718
3719 #[test]
3720 fn parse_out_type_mismatch_is_error() {
3721 let result = parser().parse_str(
3722 r#"
3723 sheet s {
3724 cell width: f64 = 4.0;
3725 out area: i32 := width;
3726 }
3727 "#,
3728 );
3729 assert!(
3730 result.is_err(),
3731 "f64 body for an i32 annotation must be an error"
3732 );
3733 }
3734
3735 #[test]
3736 fn parse_out_with_requirements_reports_output_valid_and_violated() {
3737 let mut sheet = parser()
3738 .parse_str(
3739 r#"
3740 sheet s {
3741 cell width: f64 = 4.0;
3742 cell height: f64 = 3.0;
3743 cell max_area: f64 = 100.0;
3744 out area: f64 := width * height require {
3745 @max_area width * height <= max_area;
3746 };
3747 }
3748 "#,
3749 )
3750 .unwrap();
3751 sheet.propagate().unwrap();
3752 let output_id = *sheet.output_names.get("area").unwrap();
3753 assert!(sheet.cell_requirements_valid(output_id));
3754 assert_eq!(sheet.violated_requirements(output_id).count(), 0);
3755 }
3756
3757 #[test]
3758 fn parse_out_requirement_violation_is_reported_after_propagate() {
3759 let mut sheet = parser()
3760 .parse_str(
3761 r#"
3762 sheet s {
3763 cell width: f64 = 40.0;
3764 cell height: f64 = 30.0;
3765 cell max_area: f64 = 100.0;
3766 out area: f64 := width * height require {
3767 @max_area width * height <= max_area;
3768 };
3769 }
3770 "#,
3771 )
3772 .unwrap();
3773 sheet.propagate().unwrap();
3774 let output_id = *sheet.output_names.get("area").unwrap();
3775 assert!(!sheet.cell_requirements_valid(output_id));
3776 assert_eq!(sheet.violated_requirements(output_id).count(), 1);
3777 }
3778
3779 #[test]
3780 fn requirement_names_are_metadata_for_explicit_labels_only() {
3782 let mut parsed = parser()
3783 .parse_str(
3784 "sheet s {
3785 cell x: i32 = 1 require { @same x > 0i32; x > 0i32; x < 9i32; };
3786 source y: i32 = 2 require { @same y > 0i32; y > 0i32; };
3787 out z := x require { @last z < 0i32; z < 0i32; @first z > 9i32; };
3788 }",
3789 )
3790 .unwrap();
3791 let x = parsed.cell_names["x"].0;
3792 let y = parsed.cell_names["y"].0;
3793 let z = parsed.output_names["z"];
3794 assert_eq!(parsed.requirement_names.len(), 4);
3795 for cell in [x, y] {
3796 let ids = parsed.cell_requirements(cell).unwrap();
3797 assert_eq!(parsed.requirement_names[&ids[0]], "same");
3798 assert!(!parsed.requirement_names.contains_key(&ids[1]));
3799 }
3800 parsed.propagate().unwrap();
3801 let violated: Vec<_> = parsed.violated_requirements(z).collect();
3802 assert_eq!(violated, parsed.cell_requirements(z).unwrap());
3803 assert_eq!(parsed.requirement_names[&violated[0]], "last");
3804 assert!(!parsed.requirement_names.contains_key(&violated[1]));
3805 assert_eq!(parsed.requirement_names[&violated[2]], "first");
3806 }
3807
3808 #[test]
3809 fn duplicate_requirement_labels_are_rejected_per_declaration() {
3811 for declaration in [
3812 "cell x: i32 = 1",
3813 "source x: i32 = 1",
3814 "cell y: i32 = 1; out x := y",
3815 ] {
3816 let source =
3817 format!("sheet s {{ {declaration} require {{ @dup x > 0i32; @dup x < 9i32; }}; }}");
3818 assert!(parser().parse_str(&source).is_err(), "{source}");
3819 }
3820 }
3821
3822 #[test]
3823 fn parse_out_duplicate_requirement_names_is_error() {
3824 let result = parser().parse_str(
3825 r#"
3826 sheet s {
3827 cell width: f64 = 4.0;
3828 out area: f64 := width require {
3829 @dup width <= 10.0;
3830 @dup width >= 0.0;
3831 };
3832 }
3833 "#,
3834 );
3835 assert!(
3836 result.is_err(),
3837 "two requirements sharing a name must be an error"
3838 );
3839 }
3840
3841 #[test]
3842 fn parse_out_with_an_unlabeled_requirement_is_enforced() {
3843 let mut sheet = parser()
3844 .parse_str(
3845 r#"
3846 sheet s {
3847 cell width: f64 = 40.0;
3848 cell height: f64 = 30.0;
3849 cell max_area: f64 = 100.0;
3850 out area: f64 := width * height require {
3851 width * height <= max_area;
3852 };
3853 }
3854 "#,
3855 )
3856 .unwrap();
3857 sheet.propagate().unwrap();
3858 let output_id = *sheet.output_names.get("area").unwrap();
3859 assert_eq!(sheet.violated_requirements(output_id).count(), 1);
3860 }
3861
3862 #[test]
3863 fn parse_out_with_a_bare_identifier_unlabeled_requirement_is_enforced() {
3864 let mut sheet = parser()
3865 .parse_str(
3866 r#"
3867 sheet s {
3868 cell is_valid: bool = false;
3869 out area: i32 := 4 require {
3870 is_valid;
3871 };
3872 }
3873 "#,
3874 )
3875 .unwrap();
3876 sheet.propagate().unwrap();
3877 let output_id = *sheet.output_names.get("area").unwrap();
3878 assert_eq!(sheet.violated_requirements(output_id).count(), 1);
3879 }
3880
3881 #[test]
3882 fn parse_out_cell_referenced_elsewhere_succeeds() {
3883 let result = parser().parse_str(
3886 r#"
3887 sheet s {
3888 cell width: f64 = 4.0;
3889 cell height: f64 = 3.0;
3890 out area: f64 := width * height;
3891 relationship { width := area; }
3892 }
3893 "#,
3894 );
3895 assert!(
3896 result.is_ok(),
3897 "referencing an out cell as another relationship's input must not be an error"
3898 );
3899 }
3900
3901 #[test]
3902 fn parse_out_infers_structural_array_without_registration() {
3903 let result = parser().parse_str(
3904 r#"
3905 sheet s {
3906 cell dummy: i32 = 0;
3907 out x := []: [i32];
3908 }
3909 "#,
3910 );
3911 let mut sheet = result.unwrap();
3912 sheet.propagate().unwrap();
3913 assert!(
3914 sheet
3915 .read_compound::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>(
3916 sheet.cell_names["x"].0
3917 )
3918 .unwrap()
3919 .is_empty()
3920 );
3921 }
3922
3923 #[test]
3925 fn empty_tuple_output_executes_its_writer() {
3926 let mut lookup = OpLookup::new();
3927 lookup.push_scope(|name, segment, arity, _| {
3928 if name == "bad" && arity == 0 {
3929 segment.op0r(|| -> anyhow::Result<i32> { anyhow::bail!("unit writer failed") });
3930 return Ok(true);
3931 }
3932 Ok(false)
3933 });
3934 let mut sheet = AdamParser::new(TypeRegistry::new(), lookup)
3935 .parse_str("sheet s { cell input: i32 = 0; cell nothing: (); relationship { nothing := (input, bad, ()).2; } }")
3936 .unwrap();
3937 let error = sheet.propagate().unwrap_err();
3938 assert!(error.to_string().contains("unit writer failed"), "{error}");
3939 }
3940
3941 #[test]
3942 fn parse_out_cell_referenced_in_conditional_succeeds() {
3943 let result = parser().parse_str(
3946 r#"
3947 sheet s {
3948 cell width: f64 = 4.0;
3949 cell height: f64 = 3.0;
3950 cell mode: i32 = 0;
3951 out area: f64 := width * height;
3952 conditional mode {
3953 0i32 => { relationship { width := area; } }
3954 }
3955 }
3956 "#,
3957 );
3958 assert!(
3959 result.is_ok(),
3960 "referencing an out cell as a conditional branch relationship's input must not be an error"
3961 );
3962 }
3963
3964 #[test]
3965 fn parse_cell_with_explicit_tuple_type_and_initializer() {
3966 let mut p = parser();
3967 let parsed = p
3968 .parse_str("sheet s { cell a: (i32, f64) = (1, 2.5); }")
3969 .unwrap();
3970 let (cell_id, shape) = parsed.cell_names["a"].clone();
3971 assert_eq!(
3972 shape,
3973 TypeShape::Tuple(vec![
3974 TypeShape::Named(std::any::TypeId::of::<i32>()),
3975 TypeShape::Named(std::any::TypeId::of::<f64>()),
3976 ])
3977 );
3978 let value = parsed
3979 .read_compound::<cel_runtime::Cons<
3980 cel_runtime::Leaf<i32>,
3981 cel_runtime::Cons<cel_runtime::Leaf<f64>, cel_runtime::Nil>,
3982 >>(cell_id)
3983 .unwrap();
3984 let (a, b) = (value.1, value.0.1);
3985 assert_eq!((a, b), (1, 2.5));
3986 }
3987
3988 #[test]
3989 fn parse_cell_with_tuple_type_and_no_initializer_uses_recursive_default() {
3990 let parsed = parser()
3991 .parse_str("sheet s { cell a: (i32, f64); }")
3992 .unwrap();
3993 let (cell_id, _) = parsed.cell_names["a"].clone();
3994 let value = parsed
3995 .read_compound::<cel_runtime::Cons<
3996 cel_runtime::Leaf<i32>,
3997 cel_runtime::Cons<cel_runtime::Leaf<f64>, cel_runtime::Nil>,
3998 >>(cell_id)
3999 .unwrap();
4000 let (a, b) = (value.1, value.0.1);
4001 assert_eq!((a, b), (0, 0.0));
4002 }
4003
4004 #[test]
4005 fn parse_cell_with_tuple_initializer_arity_mismatch_is_an_error() {
4006 let result = parser().parse_str("sheet s { cell a: (i32, f64, i32) = (1, 2.5); }");
4007 assert!(result.is_err());
4008 }
4009
4010 #[test]
4011 fn parse_cell_with_nested_tuple_type_round_trips() {
4012 let parsed = parser()
4013 .parse_str("sheet s { cell a: (i32, (f64, String)) = (1, (2.5, \"x\")); }")
4014 .unwrap();
4015 let (cell_id, _) = parsed.cell_names["a"].clone();
4016 type Inner = cel_runtime::Cons<
4017 cel_runtime::Leaf<f64>,
4018 cel_runtime::Cons<cel_runtime::Leaf<String>, cel_runtime::Nil>,
4019 >;
4020 type Outer =
4021 cel_runtime::Cons<cel_runtime::Leaf<i32>, cel_runtime::Cons<Inner, cel_runtime::Nil>>;
4022 let value = parsed.read_compound::<Outer>(cell_id).unwrap();
4023 assert_eq!(value.1, 1);
4024 let (b, c) = (value.0.1.1, value.0.1.0.1);
4025 assert_eq!((b, c), (2.5, "x".to_string()));
4026 }
4027
4028 #[test]
4029 fn parse_out_with_explicit_tuple_type_infers_and_stores_correctly() {
4030 let mut sheet = parser()
4031 .parse_str(
4032 r#"
4033 sheet s {
4034 cell x: i32 = 3;
4035 out pair: (i32, i32) := (x, x);
4036 }
4037 "#,
4038 )
4039 .unwrap();
4040 sheet.propagate().unwrap();
4041 let cell_id = *sheet.output_names.get("pair").unwrap();
4042 let value = sheet
4043 .read_compound::<cel_runtime::Cons<
4044 cel_runtime::Leaf<i32>,
4045 cel_runtime::Cons<cel_runtime::Leaf<i32>, cel_runtime::Nil>,
4046 >>(cell_id)
4047 .unwrap();
4048 let (a, b) = (value.1, value.0.1);
4049 assert_eq!((a, b), (3, 3));
4050 }
4051
4052 #[test]
4053 fn parse_cell_range_initializer_fails_cleanly_at_type_inference_not_grammar() {
4054 let result = parser().parse_str("sheet s { cell x = 1i32..5i32; }");
4059 assert!(result.is_err());
4060 let err = result.expect_err("expected Err");
4061 assert!(err.message().contains("cannot infer a type"));
4062 }
4063
4064 #[test]
4066 fn shape_error_out_preserves_context_and_span() {
4067 for expression in ["1i32..5i32", "(1i32, 1i32..5i32)"] {
4068 let source = format!("sheet s {{\nout result := {expression};\n}}");
4069 let err = parser().parse_str(&source).unwrap_err();
4070 assert!(err.message().contains("cannot infer a type"));
4071 assert_eq!(
4072 err.span().start(),
4073 proc_macro2::LineColumn { line: 2, column: 4 }
4074 );
4075 assert_eq!(
4076 err.span().end(),
4077 proc_macro2::LineColumn {
4078 line: 2,
4079 column: 10
4080 }
4081 );
4082 assert!(err.message().starts_with("out `result`:"));
4083 }
4084 }
4085
4086 #[test]
4088 fn shape_error_match_preserves_context_and_span() {
4089 for (expression, end_column) in [("1i32..5i32", 16), ("(1i32, 1i32..5i32)", 30)] {
4090 let source = format!("sheet s {{\nconditional {expression} {{ _ => {{}} }}\n}}");
4091 let err = parser().parse_str(&source).unwrap_err();
4092 assert!(err.message().contains("cannot infer a type"));
4093 assert_eq!(
4094 err.span().start(),
4095 proc_macro2::LineColumn {
4096 line: 2,
4097 column: 12
4098 }
4099 );
4100 assert_eq!(
4101 err.span().end(),
4102 proc_macro2::LineColumn {
4103 line: 2,
4104 column: end_column
4105 }
4106 );
4107 assert!(err.message().starts_with("match expression:"));
4108 }
4109 }
4110
4111 #[test]
4113 fn shape_error_output_preserves_context_and_span() {
4114 for (shape, expression) in [("i32", "1i32..5i32"), ("(i32, i32)", "(1i32, 1i32..5i32)")] {
4115 let source = format!(
4116 "sheet s {{\ncell result: {shape};\nrelationship {{\nresult := {expression};\n}}\n}}"
4117 );
4118 let err = parser().parse_str(&source).unwrap_err();
4119 assert!(err.message().contains("cannot infer a type"));
4120 assert_eq!(
4121 err.span().start(),
4122 proc_macro2::LineColumn { line: 4, column: 0 }
4123 );
4124 assert_eq!(
4125 err.span().end(),
4126 proc_macro2::LineColumn { line: 4, column: 6 }
4127 );
4128 assert!(err.message().starts_with("output `result`:"));
4129 }
4130 }
4131
4132 #[test]
4134 fn shape_error_initializer_preserves_context_and_span() {
4135 for expression in ["1i32..5i32", "(1i32, 1i32..5i32)"] {
4136 let source = format!("sheet s {{\ncell result = {expression};\n}}");
4137 let err = parser().parse_str(&source).unwrap_err();
4138 assert!(err.message().contains("cannot infer a type"));
4139 assert_eq!(
4140 err.span().start(),
4141 proc_macro2::LineColumn { line: 2, column: 5 }
4142 );
4143 assert_eq!(
4144 err.span().end(),
4145 proc_macro2::LineColumn {
4146 line: 2,
4147 column: 11
4148 }
4149 );
4150 assert!(err.message().starts_with("cell `result`:"));
4151 }
4152 }
4153
4154 #[test]
4156 fn shape_error_source_preserves_context_and_span() {
4157 for (declaration, message) in [
4158 ("source result: i32 = true;", "type mismatch"),
4159 ("source result = 1i32..5i32;", "cannot infer a type"),
4160 ("source result = (1i32, 1i32..5i32);", "cannot infer a type"),
4161 ] {
4162 let source = format!("sheet s {{\n{declaration}\n}}");
4163 let err = parser().parse_str(&source).unwrap_err();
4164 assert!(err.message().contains(message));
4165 assert!(
4166 err.message().starts_with("source `result`:"),
4167 "{}",
4168 err.message()
4169 );
4170 assert_eq!(
4171 err.span().start(),
4172 proc_macro2::LineColumn { line: 2, column: 7 }
4173 );
4174 assert_eq!(
4175 err.span().end(),
4176 proc_macro2::LineColumn {
4177 line: 2,
4178 column: 13
4179 }
4180 );
4181 }
4182 }
4183
4184 #[test]
4186 fn shape_error_registered_mismatches_preserve_context() {
4187 for (source, prefix) in [
4188 ("sheet s { out result: i32 := true; }", "out `result`:"),
4189 (
4190 "sheet s { cell result: i32; relationship { result := true; } }",
4191 "output `result`:",
4192 ),
4193 ] {
4194 let err = parser().parse_str(source).unwrap_err();
4195 assert!(err.message().contains("type mismatch"));
4196 assert!(err.message().starts_with(prefix));
4197 }
4198 }
4199
4200 #[test]
4201 fn dependency_cycle_validation_error_spans_the_governed_cell() {
4202 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
4203 let source = "sheet s {\n cell mode: i32 = 0;\n cell other: i32 = 0;\n\n conditional mode {\n 0i32 => {\n relationship {\n mode := other;\n other := mode;\n }\n }\n }\n}";
4206 let err = parser.parse_str(source).unwrap_err();
4207 assert_eq!(err.span().start().line, 2);
4208 }
4209
4210 #[test]
4221 fn conditional_invalid_relationship_and_cell_sites_render_as_secondary_labels() {
4222 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
4223 let source = "sheet s {\n cell a: i32 = 0;\n cell b: i32 = 0;\n cell p: i32 = 0;\n relationship {\n p := a;\n }\n\n conditional p {\n 0i32 => {\n relationship {\n a := b;\n }\n }\n }\n}";
4227 let err = parser.parse_str(source).unwrap_err();
4228 let out =
4229 err.format_rustc_style(source, "t.adm2", 1, &annotate_snippets::Renderer::plain());
4230 assert!(out.contains("cell `p` is part of the cycle"), "{out}");
4231 }
4232
4233 #[test]
4234 fn out_decl_duplicate_requirement_error_spans_the_duplicate_label() {
4236 let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
4237 let source = "sheet s {\n cell w: i32 = 1;\n out a: i32 := w require {\n @pos a > 0;\n @pos a > 0;\n };\n}";
4240 let err = parser.parse_str(source).unwrap_err();
4241 assert_eq!(err.span().start().line, 5);
4242 assert!(err.to_string().contains("duplicate requirement `pos`"));
4243 }
4244}