1use compact_str::CompactString;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5use crate::lexer::Span;
6
7pub type AstString = CompactString;
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct Program {
11 #[serde(default, skip_serializing_if = "Vec::is_empty")]
12 pub declarations: Vec<Declaration>,
13 pub main: Expr,
14 #[serde(default, skip_serializing_if = "Vec::is_empty")]
15 pub declaration_spans: Vec<Span>,
16 #[serde(default, skip_serializing_if = "Vec::is_empty")]
17 pub expression_spans: Vec<Span>,
18 #[serde(default, skip_serializing_if = "Vec::is_empty")]
19 pub expression_source_spans: Vec<ExpressionSourceSpan>,
20}
21
22impl Program {
23 pub fn block(expressions: Vec<Expr>) -> Self {
24 Self {
25 declarations: Vec::new(),
26 main: Expr::Block(expressions),
27 declaration_spans: Vec::new(),
28 expression_spans: Vec::new(),
29 expression_source_spans: Vec::new(),
30 }
31 }
32
33 pub(crate) fn module_with_spans(
34 declarations: Vec<Declaration>,
35 declaration_spans: Vec<Span>,
36 expressions: Vec<Expr>,
37 expression_spans: Vec<Span>,
38 expression_source_spans: Vec<ExpressionSourceSpan>,
39 ) -> Self {
40 Self {
41 declarations,
42 main: Expr::Block(expressions),
43 declaration_spans,
44 expression_spans,
45 expression_source_spans,
46 }
47 }
48
49 pub fn process(&self, name: &str) -> Option<&ProcessDecl> {
50 self.declarations
51 .iter()
52 .find_map(|declaration| match declaration {
53 Declaration::Process(process) if process.name.as_str() == name => Some(process),
54 _ => None,
55 })
56 }
57}
58
59impl PartialEq for Program {
60 fn eq(&self, other: &Self) -> bool {
61 self.declarations == other.declarations && self.main == other.main
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66pub struct ExpressionSourceSpan {
67 pub path: Vec<u32>,
68 pub span: Span,
69}
70
71#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
72pub enum Declaration {
73 Type(TypeDecl),
74 Process(ProcessDecl),
75}
76
77#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
78pub struct TypeDecl {
79 pub name: AstString,
80 pub ty: TypeExpr,
81}
82
83#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
84pub struct ProcessDecl {
85 pub name: AstString,
86 pub params: Vec<ProcessParam>,
87 #[serde(default, skip_serializing_if = "Vec::is_empty")]
88 pub signals: Vec<ProcessSignalDecl>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub return_ty: Option<TypeExpr>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub label: Option<LabelMetadata>,
93 pub body: Expr,
94}
95
96#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
97pub struct ProcessParam {
98 pub name: AstString,
99 pub ty: TypeExpr,
100}
101
102#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
103pub struct ProcessSignalDecl {
104 pub name: AstString,
105 pub ty: TypeExpr,
106}
107
108#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
109pub struct LabelMetadata {
110 pub title: AstString,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub description: Option<AstString>,
113}
114
115#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
116pub struct AssignTarget {
117 pub root: AstString,
118 #[serde(default, skip_serializing_if = "Vec::is_empty")]
119 pub steps: Vec<AssignPathStep>,
120}
121
122impl AssignTarget {
123 pub fn variable(root: AstString) -> Self {
124 Self {
125 root,
126 steps: Vec::new(),
127 }
128 }
129
130 pub fn is_simple(&self) -> bool {
131 self.steps.is_empty()
132 }
133}
134
135#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
136pub enum AssignPathStep {
137 Field(AstString),
138 Index(Expr),
139}
140
141#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
142pub enum Expr {
143 Block(Vec<Expr>),
144 LabelAnnotated {
145 label: LabelMetadata,
146 expr: Box<Expr>,
147 },
148 Null,
149 Bool(bool),
150 Number(f64),
151 String(AstString),
152 Variable(AstString),
153 Tuple(Vec<Expr>),
154 List(Vec<Expr>),
155 ListComprehension {
156 element: Box<Expr>,
157 clauses: Vec<ListComprehensionClause>,
158 },
159 Record(Vec<(AstString, Expr)>),
160 Assign {
161 target: AssignTarget,
162 expr: Box<Expr>,
163 },
164 If {
165 condition: Box<Expr>,
166 then_block: Box<Expr>,
167 else_block: Box<Expr>,
168 },
169 For {
170 binding: AstString,
171 iterable: Box<Expr>,
172 body: Box<Expr>,
173 },
174 While {
175 condition: Box<Expr>,
176 body: Box<Expr>,
177 },
178 Break,
179 Continue,
180 StartProcess(ProcessStartExpr),
181 ProcessRef {
182 process: AstString,
183 },
184 HostDescriptorConstructor {
185 type_name: AstString,
186 input: Box<Expr>,
187 },
188 ResourceRef(ResourceRefExpr),
189 ReceiverCall {
190 receiver: Box<Expr>,
191 operation: AstString,
192 args: Vec<Expr>,
193 },
194 Await(Box<Expr>),
195 SleepFor(Box<Expr>),
196 SleepUntil(Box<Expr>),
197 WaitSignal {
198 name: AstString,
199 },
200 SignalRun {
201 run: Box<Expr>,
202 name: AstString,
203 payload: Box<Expr>,
204 },
205 ResultUnwrap(Box<Expr>),
206 Cancel(Box<Expr>),
207 Print(Box<Expr>),
208 Yield(Box<Expr>),
209 Wake(Box<Expr>),
210 Finish(Box<Expr>),
211 Fail(Box<Expr>),
212 BuiltinCall {
213 name: AstString,
214 args: Vec<Expr>,
215 },
216 Field {
217 target: Box<Expr>,
218 field: AstString,
219 },
220 Index {
221 target: Box<Expr>,
222 index: Box<Expr>,
223 },
224 Unary {
225 op: UnaryOp,
226 expr: Box<Expr>,
227 },
228 Binary {
229 left: Box<Expr>,
230 op: BinaryOp,
231 right: Box<Expr>,
232 },
233 TypeLiteral(Box<TypeExpr>),
234}
235
236#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
237pub enum ListComprehensionClause {
238 For { binding: AstString, iterable: Expr },
239 If { condition: Expr },
240}
241
242impl Expr {
243 pub fn children(&self) -> ExprChildren<'_> {
257 let mut buffer = SmallExprVec::new();
258 match self {
259 Expr::Null
260 | Expr::Bool(_)
261 | Expr::Number(_)
262 | Expr::String(_)
263 | Expr::Variable(_)
264 | Expr::Break
265 | Expr::Continue
266 | Expr::WaitSignal { .. }
267 | Expr::ProcessRef { .. }
268 | Expr::ResourceRef(_)
269 | Expr::TypeLiteral(_) => {}
270 Expr::Block(expressions) | Expr::Tuple(expressions) | Expr::List(expressions) => {
271 buffer.extend(expressions.iter());
272 }
273 Expr::ListComprehension { element, clauses } => {
274 for clause in clauses {
275 match clause {
276 ListComprehensionClause::For { iterable, .. } => buffer.push(iterable),
277 ListComprehensionClause::If { condition } => buffer.push(condition),
278 }
279 }
280 buffer.push(element);
281 }
282 Expr::LabelAnnotated { expr, .. } => buffer.push(expr),
283 Expr::Record(entries) => buffer.extend(entries.iter().map(|(_, value)| value)),
284 Expr::Assign { target, expr } => {
285 for step in &target.steps {
286 if let AssignPathStep::Index(index) = step {
287 buffer.push(index);
288 }
289 }
290 buffer.push(expr);
291 }
292 Expr::If {
293 condition,
294 then_block,
295 else_block,
296 } => {
297 buffer.push(condition);
298 buffer.push(then_block);
299 buffer.push(else_block);
300 }
301 Expr::For { iterable, body, .. } => {
302 buffer.push(iterable);
303 buffer.push(body);
304 }
305 Expr::While { condition, body } => {
306 buffer.push(condition);
307 buffer.push(body);
308 }
309 Expr::StartProcess(start) => buffer.extend(start.args.iter().map(|(_, value)| value)),
310 Expr::HostDescriptorConstructor { input, .. } => buffer.push(input),
311 Expr::ReceiverCall { receiver, args, .. } => {
312 buffer.push(receiver);
313 buffer.extend(args.iter());
314 }
315 Expr::SignalRun { run, payload, .. } => {
316 buffer.push(run);
317 buffer.push(payload);
318 }
319 Expr::Await(expr)
320 | Expr::SleepFor(expr)
321 | Expr::SleepUntil(expr)
322 | Expr::ResultUnwrap(expr)
323 | Expr::Cancel(expr)
324 | Expr::Print(expr)
325 | Expr::Yield(expr)
326 | Expr::Wake(expr)
327 | Expr::Fail(expr)
328 | Expr::Unary { expr, .. } => buffer.push(expr),
329 Expr::Finish(expr) => buffer.push(expr),
330 Expr::BuiltinCall { args, .. } => buffer.extend(args.iter()),
331 Expr::Field { target, .. } => buffer.push(target),
332 Expr::Index { target, index } => {
333 buffer.push(target);
334 buffer.push(index);
335 }
336 Expr::Binary { left, right, .. } => {
337 buffer.push(left);
338 buffer.push(right);
339 }
340 }
341 ExprChildren {
342 buffer,
343 position: 0,
344 }
345 }
346}
347
348type SmallExprVec<'expr> = smallvec::SmallVec<[&'expr Expr; 3]>;
349
350pub struct ExprChildren<'expr> {
352 buffer: SmallExprVec<'expr>,
353 position: usize,
354}
355
356impl<'expr> Iterator for ExprChildren<'expr> {
357 type Item = &'expr Expr;
358
359 fn next(&mut self) -> Option<Self::Item> {
360 let item = self.buffer.get(self.position).copied();
361 if item.is_some() {
362 self.position += 1;
363 }
364 item
365 }
366
367 fn size_hint(&self) -> (usize, Option<usize>) {
368 let remaining = self.buffer.len() - self.position;
369 (remaining, Some(remaining))
370 }
371}
372
373impl ExactSizeIterator for ExprChildren<'_> {}
374
375pub trait ExprVisitor {
376 fn visit_expr(&mut self, expr: &Expr) {
377 walk_expr(self, expr);
378 }
379}
380
381pub fn walk_expr<V>(visitor: &mut V, expr: &Expr)
382where
383 V: ExprVisitor + ?Sized,
384{
385 for child in expr.children() {
386 visitor.visit_expr(child);
387 }
388}
389
390pub trait ExprFolder {
391 fn fold_expr(&mut self, expr: Expr) -> Expr {
392 fold_expr_children(self, expr)
393 }
394}
395
396pub fn fold_expr_children<F>(folder: &mut F, expr: Expr) -> Expr
397where
398 F: ExprFolder + ?Sized,
399{
400 match expr {
401 Expr::Block(expressions) => Expr::Block(
402 expressions
403 .into_iter()
404 .map(|expr| folder.fold_expr(expr))
405 .collect(),
406 ),
407 Expr::LabelAnnotated { label, expr } => Expr::LabelAnnotated {
408 label,
409 expr: Box::new(folder.fold_expr(*expr)),
410 },
411 Expr::Tuple(items) => Expr::Tuple(
412 items
413 .into_iter()
414 .map(|expr| folder.fold_expr(expr))
415 .collect(),
416 ),
417 Expr::List(items) => Expr::List(
418 items
419 .into_iter()
420 .map(|expr| folder.fold_expr(expr))
421 .collect(),
422 ),
423 Expr::ListComprehension { element, clauses } => Expr::ListComprehension {
424 element: Box::new(folder.fold_expr(*element)),
425 clauses: clauses
426 .into_iter()
427 .map(|clause| fold_list_comprehension_clause(folder, clause))
428 .collect(),
429 },
430 Expr::Record(entries) => Expr::Record(
431 entries
432 .into_iter()
433 .map(|(name, value)| (name, folder.fold_expr(value)))
434 .collect(),
435 ),
436 Expr::Assign { target, expr } => Expr::Assign {
437 target: fold_assign_target(folder, target),
438 expr: Box::new(folder.fold_expr(*expr)),
439 },
440 Expr::If {
441 condition,
442 then_block,
443 else_block,
444 } => Expr::If {
445 condition: Box::new(folder.fold_expr(*condition)),
446 then_block: Box::new(folder.fold_expr(*then_block)),
447 else_block: Box::new(folder.fold_expr(*else_block)),
448 },
449 Expr::For {
450 binding,
451 iterable,
452 body,
453 } => Expr::For {
454 binding,
455 iterable: Box::new(folder.fold_expr(*iterable)),
456 body: Box::new(folder.fold_expr(*body)),
457 },
458 Expr::While { condition, body } => Expr::While {
459 condition: Box::new(folder.fold_expr(*condition)),
460 body: Box::new(folder.fold_expr(*body)),
461 },
462 Expr::StartProcess(mut start) => {
463 start.args = start
464 .args
465 .into_iter()
466 .map(|(name, value)| (name, folder.fold_expr(value)))
467 .collect();
468 Expr::StartProcess(start)
469 }
470 Expr::ProcessRef { process } => Expr::ProcessRef { process },
471 Expr::HostDescriptorConstructor { type_name, input } => Expr::HostDescriptorConstructor {
472 type_name,
473 input: Box::new(folder.fold_expr(*input)),
474 },
475 Expr::ReceiverCall {
476 receiver,
477 operation,
478 args,
479 } => Expr::ReceiverCall {
480 receiver: Box::new(folder.fold_expr(*receiver)),
481 operation,
482 args: args
483 .into_iter()
484 .map(|expr| folder.fold_expr(expr))
485 .collect(),
486 },
487 Expr::Await(expr) => Expr::Await(Box::new(folder.fold_expr(*expr))),
488 Expr::SleepFor(expr) => Expr::SleepFor(Box::new(folder.fold_expr(*expr))),
489 Expr::SleepUntil(expr) => Expr::SleepUntil(Box::new(folder.fold_expr(*expr))),
490 Expr::SignalRun { run, name, payload } => Expr::SignalRun {
491 run: Box::new(folder.fold_expr(*run)),
492 name,
493 payload: Box::new(folder.fold_expr(*payload)),
494 },
495 Expr::ResultUnwrap(expr) => Expr::ResultUnwrap(Box::new(folder.fold_expr(*expr))),
496 Expr::Cancel(expr) => Expr::Cancel(Box::new(folder.fold_expr(*expr))),
497 Expr::Print(expr) => Expr::Print(Box::new(folder.fold_expr(*expr))),
498 Expr::Yield(expr) => Expr::Yield(Box::new(folder.fold_expr(*expr))),
499 Expr::Wake(expr) => Expr::Wake(Box::new(folder.fold_expr(*expr))),
500 Expr::Finish(expr) => Expr::Finish(Box::new(folder.fold_expr(*expr))),
501 Expr::Fail(expr) => Expr::Fail(Box::new(folder.fold_expr(*expr))),
502 Expr::BuiltinCall { name, args } => Expr::BuiltinCall {
503 name,
504 args: args
505 .into_iter()
506 .map(|expr| folder.fold_expr(expr))
507 .collect(),
508 },
509 Expr::Field { target, field } => Expr::Field {
510 target: Box::new(folder.fold_expr(*target)),
511 field,
512 },
513 Expr::Index { target, index } => Expr::Index {
514 target: Box::new(folder.fold_expr(*target)),
515 index: Box::new(folder.fold_expr(*index)),
516 },
517 Expr::Unary { op, expr } => Expr::Unary {
518 op,
519 expr: Box::new(folder.fold_expr(*expr)),
520 },
521 Expr::Binary { left, op, right } => Expr::Binary {
522 left: Box::new(folder.fold_expr(*left)),
523 op,
524 right: Box::new(folder.fold_expr(*right)),
525 },
526 leaf @ (Expr::Null
527 | Expr::Bool(_)
528 | Expr::Number(_)
529 | Expr::String(_)
530 | Expr::Variable(_)
531 | Expr::Break
532 | Expr::Continue
533 | Expr::ResourceRef(_)
534 | Expr::WaitSignal { .. }
535 | Expr::TypeLiteral(_)) => leaf,
536 }
537}
538
539fn fold_list_comprehension_clause<F>(
540 folder: &mut F,
541 clause: ListComprehensionClause,
542) -> ListComprehensionClause
543where
544 F: ExprFolder + ?Sized,
545{
546 match clause {
547 ListComprehensionClause::For { binding, iterable } => ListComprehensionClause::For {
548 binding,
549 iterable: folder.fold_expr(iterable),
550 },
551 ListComprehensionClause::If { condition } => ListComprehensionClause::If {
552 condition: folder.fold_expr(condition),
553 },
554 }
555}
556
557fn fold_assign_target<F>(folder: &mut F, target: AssignTarget) -> AssignTarget
558where
559 F: ExprFolder + ?Sized,
560{
561 AssignTarget {
562 root: target.root,
563 steps: target
564 .steps
565 .into_iter()
566 .map(|step| match step {
567 AssignPathStep::Field(field) => AssignPathStep::Field(field),
568 AssignPathStep::Index(index) => AssignPathStep::Index(folder.fold_expr(index)),
569 })
570 .collect(),
571 }
572}
573
574#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
575pub enum TypeExpr {
576 Any,
577 Str,
578 Int,
579 Float,
580 Bool,
581 Dict,
582 Null,
585 Enum(Vec<AstString>),
586 List(Box<TypeExpr>),
587 Object(Vec<TypeField>),
588 Ref(AstString),
589 Process {
590 input: Box<TypeExpr>,
591 output: Box<TypeExpr>,
592 input_count: usize,
593 },
594 TriggerHandle(Box<TypeExpr>),
595 Union(Vec<TypeExpr>),
599}
600
601pub fn format_type_expr(ty: &TypeExpr) -> String {
602 match ty {
603 TypeExpr::Any => "any".to_string(),
604 TypeExpr::Str => "str".to_string(),
605 TypeExpr::Int => "int".to_string(),
606 TypeExpr::Float => "float".to_string(),
607 TypeExpr::Bool => "bool".to_string(),
608 TypeExpr::Dict => "dict".to_string(),
609 TypeExpr::Null => "null".to_string(),
610 TypeExpr::Enum(values) => format!(
611 "enum[{}]",
612 values
613 .iter()
614 .map(|value| format!("\"{value}\""))
615 .collect::<Vec<_>>()
616 .join(", ")
617 ),
618 TypeExpr::List(item) => format!("list[{}]", format_type_expr(item)),
619 TypeExpr::Object(fields) => {
620 let fields = fields
621 .iter()
622 .map(|field| {
623 let optional = if field.optional { "?" } else { "" };
624 format!(
625 "{}: {}{}",
626 field.name,
627 format_type_expr(&field.ty),
628 optional
629 )
630 })
631 .collect::<Vec<_>>()
632 .join(", ");
633 format!("{{ {fields} }}")
634 }
635 TypeExpr::Ref(name) => name.to_string(),
636 TypeExpr::Process { input, output, .. } => {
637 format!(
638 "Process<{}, {}>",
639 format_type_expr(input),
640 format_type_expr(output)
641 )
642 }
643 TypeExpr::TriggerHandle(event) => {
644 format!("TriggerHandle<{}>", format_type_expr(event))
645 }
646 TypeExpr::Union(items) => items
647 .iter()
648 .map(format_type_expr)
649 .collect::<Vec<_>>()
650 .join(" | "),
651 }
652}
653
654impl fmt::Display for TypeExpr {
655 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656 f.write_str(&format_type_expr(self))
657 }
658}
659
660#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
661pub struct TypeField {
662 pub name: AstString,
663 pub ty: TypeExpr,
664 pub optional: bool,
665}
666
667#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
668pub struct ProcessStartExpr {
669 pub process: AstString,
670 pub args: Vec<(AstString, Expr)>,
671}
672
673#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
674pub struct ResourceRefExpr {
675 #[serde(default, skip_serializing_if = "Vec::is_empty")]
676 pub path: Vec<AstString>,
677 pub resource_type: AstString,
678 pub alias: AstString,
679}
680
681impl ResourceRefExpr {
682 pub fn unresolved(path: Vec<AstString>) -> Self {
683 Self {
684 path,
685 resource_type: AstString::default(),
686 alias: AstString::default(),
687 }
688 }
689
690 pub fn resolved(
691 path: Vec<AstString>,
692 resource_type: impl Into<AstString>,
693 alias: impl Into<AstString>,
694 ) -> Self {
695 Self {
696 path,
697 resource_type: resource_type.into(),
698 alias: alias.into(),
699 }
700 }
701
702 pub fn path_string(&self) -> String {
703 if self.path.is_empty() {
704 format!("{}.{}", self.resource_type, self.alias)
705 } else {
706 self.path
707 .iter()
708 .map(AstString::as_str)
709 .collect::<Vec<_>>()
710 .join(".")
711 }
712 }
713}
714
715#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
716pub enum UnaryOp {
717 Negate,
718 Not,
719}
720
721#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
722pub enum BinaryOp {
723 Add,
724 Subtract,
725 Multiply,
726 Divide,
727 Modulo,
728 Equal,
729 NotEqual,
730 Less,
731 LessEqual,
732 Greater,
733 GreaterEqual,
734 And,
735 Or,
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741
742 #[test]
743 fn type_expr_formatting_covers_nested_shapes() {
744 let ty = TypeExpr::Object(vec![
745 TypeField {
746 name: "status".into(),
747 ty: TypeExpr::Enum(vec!["ok".into(), "err".into()]),
748 optional: false,
749 },
750 TypeField {
751 name: "tags".into(),
752 ty: TypeExpr::List(Box::new(TypeExpr::Str)),
753 optional: true,
754 },
755 TypeField {
756 name: "owner".into(),
757 ty: TypeExpr::Ref("User".into()),
758 optional: false,
759 },
760 TypeField {
761 name: "value".into(),
762 ty: TypeExpr::Union(vec![TypeExpr::Int, TypeExpr::Null]),
763 optional: false,
764 },
765 ]);
766
767 assert_eq!(
768 format_type_expr(&ty),
769 r#"{ status: enum["ok", "err"], tags: list[str]?, owner: User, value: int | null }"#
770 );
771 assert_eq!(ty.to_string(), format_type_expr(&ty));
772 }
773
774 fn var(name: &str) -> Expr {
775 Expr::Variable(name.into())
776 }
777
778 fn child_vars(expr: &Expr) -> Vec<String> {
779 expr.children()
780 .map(|child| match child {
781 Expr::Variable(name) => name.to_string(),
782 other => format!("{other:?}"),
783 })
784 .collect()
785 }
786
787 #[test]
788 fn children_yields_leaves_as_empty() {
789 for leaf in [
790 Expr::Null,
791 Expr::Bool(true),
792 Expr::Number(1.0),
793 Expr::String("s".into()),
794 var("x"),
795 Expr::Break,
796 Expr::Continue,
797 Expr::WaitSignal {
798 name: "ready".into(),
799 },
800 Expr::TypeLiteral(Box::new(TypeExpr::Str)),
801 ] {
802 let children: Vec<_> = leaf.children().collect();
803 assert!(children.is_empty(), "{leaf:?} should have no children");
804 }
805 }
806
807 #[test]
808 fn children_yields_composite_subexpressions_in_order() {
809 let block = Expr::Block(vec![var("a"), var("b"), var("c")]);
810 assert_eq!(child_vars(&block), ["a", "b", "c"]);
811
812 let record = Expr::Record(vec![("k1".into(), var("v1")), ("k2".into(), var("v2"))]);
813 assert_eq!(child_vars(&record), ["v1", "v2"]);
814
815 let if_expr = Expr::If {
816 condition: Box::new(var("cond")),
817 then_block: Box::new(var("then")),
818 else_block: Box::new(var("else")),
819 };
820 assert_eq!(child_vars(&if_expr), ["cond", "then", "else"]);
821
822 let while_expr = Expr::While {
823 condition: Box::new(var("cond")),
824 body: Box::new(var("body")),
825 };
826 assert_eq!(child_vars(&while_expr), ["cond", "body"]);
827
828 let receiver = Expr::ReceiverCall {
829 receiver: Box::new(var("recv")),
830 operation: "op".into(),
831 args: vec![var("arg0"), var("arg1")],
832 };
833 assert_eq!(child_vars(&receiver), ["recv", "arg0", "arg1"]);
834
835 let binary = Expr::Binary {
836 left: Box::new(var("left")),
837 op: BinaryOp::Add,
838 right: Box::new(var("right")),
839 };
840 assert_eq!(child_vars(&binary), ["left", "right"]);
841 }
842
843 #[test]
844 fn children_yields_assign_index_steps_before_value() {
845 let assign = Expr::Assign {
846 target: AssignTarget {
847 root: "root".into(),
848 steps: vec![
849 AssignPathStep::Field("field".into()),
850 AssignPathStep::Index(var("idx")),
851 ],
852 },
853 expr: Box::new(var("value")),
854 };
855 assert_eq!(child_vars(&assign), ["idx", "value"]);
858 }
859
860 #[test]
861 fn children_handles_finish() {
862 assert_eq!(child_vars(&Expr::Finish(Box::new(var("done")))), ["done"]);
863 }
864
865 #[test]
866 fn children_size_hint_is_exact() {
867 let block = Expr::Block(vec![var("a"), var("b"), var("c"), var("d")]);
868 let iter = block.children();
869 assert_eq!(iter.len(), 4);
870 assert_eq!(iter.size_hint(), (4, Some(4)));
871 }
872
873 #[test]
874 fn visitor_walks_descendants_through_single_child_boundary() {
875 struct VariableCollector(Vec<String>);
876
877 impl ExprVisitor for VariableCollector {
878 fn visit_expr(&mut self, expr: &Expr) {
879 if let Expr::Variable(name) = expr {
880 self.0.push(name.to_string());
881 }
882 walk_expr(self, expr);
883 }
884 }
885
886 let expr = Expr::While {
887 condition: Box::new(var("ready")),
888 body: Box::new(Expr::Block(vec![
889 Expr::Assign {
890 target: AssignTarget {
891 root: "items".into(),
892 steps: vec![AssignPathStep::Index(var("idx"))],
893 },
894 expr: Box::new(var("value")),
895 },
896 Expr::Finish(Box::new(var("done"))),
897 ])),
898 };
899
900 let mut collector = VariableCollector(Vec::new());
901 collector.visit_expr(&expr);
902
903 assert_eq!(collector.0, ["ready", "idx", "value", "done"]);
904 }
905
906 #[test]
907 fn folder_reconstructs_owned_expr_trees() {
908 struct RenameVariables;
909
910 impl ExprFolder for RenameVariables {
911 fn fold_expr(&mut self, expr: Expr) -> Expr {
912 match expr {
913 Expr::Variable(name) => Expr::Variable(format!("renamed_{name}").into()),
914 other => fold_expr_children(self, other),
915 }
916 }
917 }
918
919 let expr = Expr::Assign {
920 target: AssignTarget {
921 root: "items".into(),
922 steps: vec![AssignPathStep::Index(var("idx"))],
923 },
924 expr: Box::new(Expr::List(vec![var("first"), var("second")])),
925 };
926
927 let mut folder = RenameVariables;
928 let folded = folder.fold_expr(expr);
929
930 let Expr::Assign { target, expr } = folded else {
931 panic!("expected assign");
932 };
933 assert!(matches!(
934 target.steps.as_slice(),
935 [AssignPathStep::Index(Expr::Variable(name))] if name.as_str() == "renamed_idx"
936 ));
937 let Expr::List(items) = *expr else {
938 panic!("expected list");
939 };
940 assert_eq!(items, vec![var("renamed_first"), var("renamed_second")]);
941 }
942}