1use std::collections::HashMap;
20
21use crate::types::{BinaryOp, Expr, ListElement, MapEntry, Span, Spanned, SpannedExpr, UnaryOp};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum MacroStyle {
26 Global,
28 Receiver,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ArgCount {
35 Exact(usize),
37 VarArg(usize),
39}
40
41impl ArgCount {
42 pub fn matches(&self, count: usize) -> bool {
44 match self {
45 ArgCount::Exact(n) => count == *n,
46 ArgCount::VarArg(min) => count >= *min,
47 }
48 }
49
50 pub fn count(&self) -> usize {
52 match self {
53 ArgCount::Exact(n) => *n,
54 ArgCount::VarArg(min) => *min,
55 }
56 }
57
58 pub fn is_vararg(&self) -> bool {
60 matches!(self, ArgCount::VarArg(_))
61 }
62}
63
64#[derive(Debug)]
66pub enum MacroExpansion {
67 Expanded(SpannedExpr),
69 Error(String),
72}
73
74pub struct MacroContext<'a> {
79 next_id_fn: &'a mut dyn FnMut() -> i64,
81 errors: Vec<(String, Span)>,
83 store_macro_call_fn: Option<&'a mut dyn FnMut(i64, &Span, &SpannedExpr, &str, &[SpannedExpr])>,
85}
86
87impl<'a> MacroContext<'a> {
88 pub fn new(
90 next_id_fn: &'a mut dyn FnMut() -> i64,
91 store_macro_call_fn: Option<&'a mut dyn FnMut(i64, &Span, &SpannedExpr, &str, &[SpannedExpr])>,
92 ) -> Self {
93 Self {
94 next_id_fn,
95 errors: Vec::new(),
96 store_macro_call_fn,
97 }
98 }
99
100 pub fn next_id(&mut self) -> i64 {
102 (self.next_id_fn)()
103 }
104
105 pub fn add_error(&mut self, message: String, span: Span) {
107 self.errors.push((message, span));
108 }
109
110 pub fn take_errors(&mut self) -> Vec<(String, Span)> {
112 std::mem::take(&mut self.errors)
113 }
114
115 pub fn store_macro_call(
117 &mut self,
118 call_id: i64,
119 span: &Span,
120 receiver: &SpannedExpr,
121 method_name: &str,
122 args: &[SpannedExpr],
123 ) {
124 if let Some(f) = &mut self.store_macro_call_fn {
125 f(call_id, span, receiver, method_name, args);
126 }
127 }
128}
129
130pub type MacroExpander = fn(
142 ctx: &mut MacroContext,
143 span: Span,
144 receiver: Option<SpannedExpr>,
145 args: Vec<SpannedExpr>,
146) -> MacroExpansion;
147
148#[derive(Clone)]
150pub struct Macro {
151 pub name: &'static str,
153 pub style: MacroStyle,
155 pub arg_count: ArgCount,
157 pub expander: MacroExpander,
159 pub description: Option<&'static str>,
161}
162
163impl Macro {
164 pub const fn new(
166 name: &'static str,
167 style: MacroStyle,
168 arg_count: ArgCount,
169 expander: MacroExpander,
170 ) -> Self {
171 Self {
172 name,
173 style,
174 arg_count,
175 expander,
176 description: None,
177 }
178 }
179
180 pub const fn with_description(
182 name: &'static str,
183 style: MacroStyle,
184 arg_count: ArgCount,
185 expander: MacroExpander,
186 description: &'static str,
187 ) -> Self {
188 Self {
189 name,
190 style,
191 arg_count,
192 expander,
193 description: Some(description),
194 }
195 }
196
197 pub fn key(&self) -> String {
199 make_key(self.name, self.arg_count.count(), self.style == MacroStyle::Receiver)
200 }
201}
202
203impl std::fmt::Debug for Macro {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 f.debug_struct("Macro")
206 .field("name", &self.name)
207 .field("style", &self.style)
208 .field("arg_count", &self.arg_count)
209 .field("description", &self.description)
210 .finish_non_exhaustive()
211 }
212}
213
214fn make_key(name: &str, arg_count: usize, is_receiver: bool) -> String {
216 format!("{}:{}:{}", name, arg_count, is_receiver)
217}
218
219#[derive(Debug, Clone)]
224pub struct MacroRegistry {
225 macros: HashMap<String, Macro>,
227 vararg_keys: HashMap<String, usize>,
229}
230
231impl Default for MacroRegistry {
232 fn default() -> Self {
233 Self::new()
234 }
235}
236
237impl MacroRegistry {
238 pub fn new() -> Self {
240 Self {
241 macros: HashMap::new(),
242 vararg_keys: HashMap::new(),
243 }
244 }
245
246 pub fn standard() -> Self {
248 let mut registry = Self::new();
249 for macro_def in STANDARD_MACROS {
250 registry.register(macro_def.clone());
251 }
252 registry
253 }
254
255 pub fn register(&mut self, macro_def: Macro) {
257 let key = macro_def.key();
258
259 if macro_def.arg_count.is_vararg() {
261 let vararg_key = format!("{}:{}", macro_def.name, macro_def.style == MacroStyle::Receiver);
262 self.vararg_keys.insert(vararg_key, macro_def.arg_count.count());
263 }
264
265 self.macros.insert(key, macro_def);
266 }
267
268 pub fn lookup(&self, name: &str, arg_count: usize, is_receiver: bool) -> Option<&Macro> {
272 let exact_key = make_key(name, arg_count, is_receiver);
274 if let Some(m) = self.macros.get(&exact_key) {
275 return Some(m);
276 }
277
278 let vararg_lookup_key = format!("{}:{}", name, is_receiver);
280 if let Some(&min_args) = self.vararg_keys.get(&vararg_lookup_key) {
281 if arg_count >= min_args {
282 let vararg_key = make_key(name, min_args, is_receiver);
283 return self.macros.get(&vararg_key);
284 }
285 }
286
287 None
288 }
289
290 pub fn contains(&self, name: &str) -> bool {
292 self.macros.values().any(|m| m.name == name)
293 }
294
295 pub fn iter(&self) -> impl Iterator<Item = &Macro> {
297 self.macros.values()
298 }
299
300 pub fn len(&self) -> usize {
302 self.macros.len()
303 }
304
305 pub fn is_empty(&self) -> bool {
307 self.macros.is_empty()
308 }
309}
310
311const ACCU_VAR: &str = "__result__";
317
318pub static STANDARD_MACROS: &[Macro] = &[
320 Macro::with_description(
322 "has",
323 MacroStyle::Global,
324 ArgCount::Exact(1),
325 expand_has,
326 "Tests whether a field is set on a message",
327 ),
328
329 Macro::with_description(
331 "all",
332 MacroStyle::Receiver,
333 ArgCount::Exact(2),
334 expand_all_2arg,
335 "Tests whether all elements satisfy a condition",
336 ),
337 Macro::with_description(
338 "all",
339 MacroStyle::Receiver,
340 ArgCount::Exact(3),
341 expand_all_3arg,
342 "Tests whether all elements satisfy a condition (two-variable form)",
343 ),
344
345 Macro::with_description(
347 "exists",
348 MacroStyle::Receiver,
349 ArgCount::Exact(2),
350 expand_exists_2arg,
351 "Tests whether any element satisfies a condition",
352 ),
353 Macro::with_description(
354 "exists",
355 MacroStyle::Receiver,
356 ArgCount::Exact(3),
357 expand_exists_3arg,
358 "Tests whether any element satisfies a condition (two-variable form)",
359 ),
360
361 Macro::with_description(
363 "exists_one",
364 MacroStyle::Receiver,
365 ArgCount::Exact(2),
366 expand_exists_one_2arg,
367 "Tests whether exactly one element satisfies a condition",
368 ),
369 Macro::with_description(
370 "exists_one",
371 MacroStyle::Receiver,
372 ArgCount::Exact(3),
373 expand_exists_one_3arg,
374 "Tests whether exactly one element satisfies a condition (two-variable form)",
375 ),
376
377 Macro::with_description(
379 "map",
380 MacroStyle::Receiver,
381 ArgCount::Exact(2),
382 expand_map_2arg,
383 "Transforms elements of a list",
384 ),
385 Macro::with_description(
386 "map",
387 MacroStyle::Receiver,
388 ArgCount::Exact(3),
389 expand_map_3arg,
390 "Transforms elements of a list with filtering",
391 ),
392
393 Macro::with_description(
395 "filter",
396 MacroStyle::Receiver,
397 ArgCount::Exact(2),
398 expand_filter,
399 "Filters elements of a list by a condition",
400 ),
401
402 Macro::with_description(
404 "transformList",
405 MacroStyle::Receiver,
406 ArgCount::Exact(3),
407 expand_transform_list_3arg,
408 "Transforms list elements with index and value variables",
409 ),
410 Macro::with_description(
411 "transformList",
412 MacroStyle::Receiver,
413 ArgCount::Exact(4),
414 expand_transform_list_4arg,
415 "Transforms list elements with index, value, and filter",
416 ),
417
418 Macro::with_description(
420 "transformMap",
421 MacroStyle::Receiver,
422 ArgCount::Exact(3),
423 expand_transform_map_3arg,
424 "Transforms map entries with key and value variables",
425 ),
426 Macro::with_description(
427 "transformMap",
428 MacroStyle::Receiver,
429 ArgCount::Exact(4),
430 expand_transform_map_4arg,
431 "Transforms map entries with key, value, and filter",
432 ),
433 Macro::with_description(
435 "cel.bind",
436 MacroStyle::Global,
437 ArgCount::Exact(3),
438 expand_bind,
439 "Binds a variable to a value for use in an expression",
440 ),
441 Macro::with_description(
443 "optMap",
444 MacroStyle::Receiver,
445 ArgCount::Exact(2),
446 expand_opt_map,
447 "Transforms an optional value if present",
448 ),
449 Macro::with_description(
451 "optFlatMap",
452 MacroStyle::Receiver,
453 ArgCount::Exact(2),
454 expand_opt_flat_map,
455 "Chains optional operations",
456 ),
457];
458
459fn synthetic(ctx: &mut MacroContext, node: Expr, span: Span) -> SpannedExpr {
463 Spanned::new(ctx.next_id(), node, span)
464}
465
466fn extract_iter_var(ctx: &mut MacroContext, expr: &SpannedExpr) -> Option<String> {
469 match &expr.node {
470 Expr::Ident(name) => Some(name.clone()),
471 _ => {
472 ctx.add_error(
473 "iteration variable must be an identifier".to_string(),
474 expr.span.clone(),
475 );
476 None
477 }
478 }
479}
480
481fn expand_has(
485 ctx: &mut MacroContext,
486 span: Span,
487 _receiver: Option<SpannedExpr>,
488 args: Vec<SpannedExpr>,
489) -> MacroExpansion {
490 if args.len() != 1 {
491 return MacroExpansion::Error(format!("has() requires 1 argument, got {}", args.len()));
492 }
493
494 let arg = args.into_iter().next().unwrap();
495
496 match arg.node {
497 Expr::Member { expr, field, .. } => {
498 let result = Spanned::new(
499 ctx.next_id(),
500 Expr::MemberTestOnly { expr, field },
501 span,
502 );
503 MacroExpansion::Expanded(result)
504 }
505 _ => MacroExpansion::Error(
506 "has() argument must be a field selection (e.g., has(m.x))".to_string(),
507 ),
508 }
509}
510
511fn expand_all_2arg(
514 ctx: &mut MacroContext,
515 span: Span,
516 receiver: Option<SpannedExpr>,
517 args: Vec<SpannedExpr>,
518) -> MacroExpansion {
519 let receiver = match receiver {
520 Some(r) => r,
521 None => return MacroExpansion::Error("all() requires a receiver".to_string()),
522 };
523
524 let iter_var = match extract_iter_var(ctx, &args[0]) {
525 Some(v) => v,
526 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
527 };
528 let cond = args[1].clone();
529
530 expand_all_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
531}
532
533fn expand_all_3arg(
534 ctx: &mut MacroContext,
535 span: Span,
536 receiver: Option<SpannedExpr>,
537 args: Vec<SpannedExpr>,
538) -> MacroExpansion {
539 let receiver = match receiver {
540 Some(r) => r,
541 None => return MacroExpansion::Error("all() requires a receiver".to_string()),
542 };
543
544 let iter_var = match extract_iter_var(ctx, &args[0]) {
545 Some(v) => v,
546 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
547 };
548 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
549 Some(v) => v,
550 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
551 };
552 let cond = args[2].clone();
553
554 expand_all_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
555}
556
557fn expand_all_impl(
558 ctx: &mut MacroContext,
559 span: Span,
560 receiver: SpannedExpr,
561 iter_var: String,
562 iter_var2: String,
563 cond: SpannedExpr,
564 args: &[SpannedExpr],
565) -> MacroExpansion {
566 let call_id = ctx.next_id();
567 ctx.store_macro_call(call_id, &span, &receiver, "all", args);
568
569 let accu_var = ACCU_VAR.to_string();
570 let accu_init = synthetic(ctx, Expr::Bool(true), span.clone());
571 let loop_condition = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
572
573 let accu_ref = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
574 let loop_step = synthetic(
575 ctx,
576 Expr::Binary {
577 op: BinaryOp::And,
578 left: Box::new(cond),
579 right: Box::new(accu_ref),
580 },
581 span.clone(),
582 );
583
584 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
585
586 MacroExpansion::Expanded(Spanned::new(
587 call_id,
588 Expr::Comprehension {
589 iter_var,
590 iter_var2,
591 iter_range: Box::new(receiver),
592 accu_var,
593 accu_init: Box::new(accu_init),
594 loop_condition: Box::new(loop_condition),
595 loop_step: Box::new(loop_step),
596 result: Box::new(result),
597 },
598 span,
599 ))
600}
601
602fn expand_exists_2arg(
605 ctx: &mut MacroContext,
606 span: Span,
607 receiver: Option<SpannedExpr>,
608 args: Vec<SpannedExpr>,
609) -> MacroExpansion {
610 let receiver = match receiver {
611 Some(r) => r,
612 None => return MacroExpansion::Error("exists() requires a receiver".to_string()),
613 };
614
615 let iter_var = match extract_iter_var(ctx, &args[0]) {
616 Some(v) => v,
617 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
618 };
619 let cond = args[1].clone();
620
621 expand_exists_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
622}
623
624fn expand_exists_3arg(
625 ctx: &mut MacroContext,
626 span: Span,
627 receiver: Option<SpannedExpr>,
628 args: Vec<SpannedExpr>,
629) -> MacroExpansion {
630 let receiver = match receiver {
631 Some(r) => r,
632 None => return MacroExpansion::Error("exists() requires a receiver".to_string()),
633 };
634
635 let iter_var = match extract_iter_var(ctx, &args[0]) {
636 Some(v) => v,
637 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
638 };
639 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
640 Some(v) => v,
641 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
642 };
643 let cond = args[2].clone();
644
645 expand_exists_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
646}
647
648fn expand_exists_impl(
649 ctx: &mut MacroContext,
650 span: Span,
651 receiver: SpannedExpr,
652 iter_var: String,
653 iter_var2: String,
654 cond: SpannedExpr,
655 args: &[SpannedExpr],
656) -> MacroExpansion {
657 let call_id = ctx.next_id();
658 ctx.store_macro_call(call_id, &span, &receiver, "exists", args);
659
660 let accu_var = ACCU_VAR.to_string();
661 let accu_init = synthetic(ctx, Expr::Bool(false), span.clone());
662
663 let accu_ref_cond = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
664 let loop_condition = synthetic(
665 ctx,
666 Expr::Unary {
667 op: UnaryOp::Not,
668 expr: Box::new(accu_ref_cond),
669 },
670 span.clone(),
671 );
672
673 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
674 let loop_step = synthetic(
675 ctx,
676 Expr::Binary {
677 op: BinaryOp::Or,
678 left: Box::new(cond),
679 right: Box::new(accu_ref_step),
680 },
681 span.clone(),
682 );
683
684 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
685
686 MacroExpansion::Expanded(Spanned::new(
687 call_id,
688 Expr::Comprehension {
689 iter_var,
690 iter_var2,
691 iter_range: Box::new(receiver),
692 accu_var,
693 accu_init: Box::new(accu_init),
694 loop_condition: Box::new(loop_condition),
695 loop_step: Box::new(loop_step),
696 result: Box::new(result),
697 },
698 span,
699 ))
700}
701
702fn expand_exists_one_2arg(
705 ctx: &mut MacroContext,
706 span: Span,
707 receiver: Option<SpannedExpr>,
708 args: Vec<SpannedExpr>,
709) -> MacroExpansion {
710 let receiver = match receiver {
711 Some(r) => r,
712 None => return MacroExpansion::Error("exists_one() requires a receiver".to_string()),
713 };
714
715 let iter_var = match extract_iter_var(ctx, &args[0]) {
716 Some(v) => v,
717 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
718 };
719 let cond = args[1].clone();
720
721 expand_exists_one_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
722}
723
724fn expand_exists_one_3arg(
725 ctx: &mut MacroContext,
726 span: Span,
727 receiver: Option<SpannedExpr>,
728 args: Vec<SpannedExpr>,
729) -> MacroExpansion {
730 let receiver = match receiver {
731 Some(r) => r,
732 None => return MacroExpansion::Error("exists_one() requires a receiver".to_string()),
733 };
734
735 let iter_var = match extract_iter_var(ctx, &args[0]) {
736 Some(v) => v,
737 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
738 };
739 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
740 Some(v) => v,
741 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
742 };
743 let cond = args[2].clone();
744
745 expand_exists_one_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
746}
747
748fn expand_exists_one_impl(
749 ctx: &mut MacroContext,
750 span: Span,
751 receiver: SpannedExpr,
752 iter_var: String,
753 iter_var2: String,
754 cond: SpannedExpr,
755 args: &[SpannedExpr],
756) -> MacroExpansion {
757 let call_id = ctx.next_id();
758 ctx.store_macro_call(call_id, &span, &receiver, "exists_one", args);
759
760 let accu_var = ACCU_VAR.to_string();
761 let accu_init = synthetic(ctx, Expr::Int(0), span.clone());
762
763 let accu_ref_cond = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
764 let one_cond = synthetic(ctx, Expr::Int(1), span.clone());
765 let loop_condition = synthetic(
766 ctx,
767 Expr::Binary {
768 op: BinaryOp::Le,
769 left: Box::new(accu_ref_cond),
770 right: Box::new(one_cond),
771 },
772 span.clone(),
773 );
774
775 let accu_ref_then = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
776 let one_step = synthetic(ctx, Expr::Int(1), span.clone());
777 let increment = synthetic(
778 ctx,
779 Expr::Binary {
780 op: BinaryOp::Add,
781 left: Box::new(accu_ref_then),
782 right: Box::new(one_step),
783 },
784 span.clone(),
785 );
786 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
787 let loop_step = synthetic(
788 ctx,
789 Expr::Ternary {
790 cond: Box::new(cond),
791 then_expr: Box::new(increment),
792 else_expr: Box::new(accu_ref_else),
793 },
794 span.clone(),
795 );
796
797 let accu_ref_result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
798 let one_result = synthetic(ctx, Expr::Int(1), span.clone());
799 let result = synthetic(
800 ctx,
801 Expr::Binary {
802 op: BinaryOp::Eq,
803 left: Box::new(accu_ref_result),
804 right: Box::new(one_result),
805 },
806 span.clone(),
807 );
808
809 MacroExpansion::Expanded(Spanned::new(
810 call_id,
811 Expr::Comprehension {
812 iter_var,
813 iter_var2,
814 iter_range: Box::new(receiver),
815 accu_var,
816 accu_init: Box::new(accu_init),
817 loop_condition: Box::new(loop_condition),
818 loop_step: Box::new(loop_step),
819 result: Box::new(result),
820 },
821 span,
822 ))
823}
824
825fn expand_map_2arg(
828 ctx: &mut MacroContext,
829 span: Span,
830 receiver: Option<SpannedExpr>,
831 args: Vec<SpannedExpr>,
832) -> MacroExpansion {
833 let receiver = match receiver {
834 Some(r) => r,
835 None => return MacroExpansion::Error("map() requires a receiver".to_string()),
836 };
837
838 let iter_var = match extract_iter_var(ctx, &args[0]) {
839 Some(v) => v,
840 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
841 };
842 let transform = args[1].clone();
843
844 expand_map_impl(ctx, span, receiver, iter_var, None, transform, &args)
845}
846
847fn expand_map_3arg(
848 ctx: &mut MacroContext,
849 span: Span,
850 receiver: Option<SpannedExpr>,
851 args: Vec<SpannedExpr>,
852) -> MacroExpansion {
853 let receiver = match receiver {
854 Some(r) => r,
855 None => return MacroExpansion::Error("map() requires a receiver".to_string()),
856 };
857
858 let iter_var = match extract_iter_var(ctx, &args[0]) {
859 Some(v) => v,
860 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
861 };
862 let filter = args[1].clone();
863 let transform = args[2].clone();
864
865 expand_map_impl(ctx, span, receiver, iter_var, Some(filter), transform, &args)
866}
867
868fn expand_map_impl(
869 ctx: &mut MacroContext,
870 span: Span,
871 receiver: SpannedExpr,
872 iter_var: String,
873 filter_cond: Option<SpannedExpr>,
874 transform: SpannedExpr,
875 args: &[SpannedExpr],
876) -> MacroExpansion {
877 let call_id = ctx.next_id();
878 ctx.store_macro_call(call_id, &span, &receiver, "map", args);
879
880 let accu_var = ACCU_VAR.to_string();
881 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
882 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
883
884 let transformed_list = synthetic(ctx, Expr::List(vec![ListElement { expr: transform, optional: false }]), span.clone());
885 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
886 let append_step = synthetic(
887 ctx,
888 Expr::Binary {
889 op: BinaryOp::Add,
890 left: Box::new(accu_ref_step),
891 right: Box::new(transformed_list),
892 },
893 span.clone(),
894 );
895
896 let loop_step = if let Some(filter) = filter_cond {
897 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
898 synthetic(
899 ctx,
900 Expr::Ternary {
901 cond: Box::new(filter),
902 then_expr: Box::new(append_step),
903 else_expr: Box::new(accu_ref_else),
904 },
905 span.clone(),
906 )
907 } else {
908 append_step
909 };
910
911 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
912
913 MacroExpansion::Expanded(Spanned::new(
914 call_id,
915 Expr::Comprehension {
916 iter_var,
917 iter_var2: String::new(),
918 iter_range: Box::new(receiver),
919 accu_var,
920 accu_init: Box::new(accu_init),
921 loop_condition: Box::new(loop_condition),
922 loop_step: Box::new(loop_step),
923 result: Box::new(result),
924 },
925 span,
926 ))
927}
928
929fn expand_filter(
932 ctx: &mut MacroContext,
933 span: Span,
934 receiver: Option<SpannedExpr>,
935 args: Vec<SpannedExpr>,
936) -> MacroExpansion {
937 let receiver = match receiver {
938 Some(r) => r,
939 None => return MacroExpansion::Error("filter() requires a receiver".to_string()),
940 };
941
942 let iter_var = match extract_iter_var(ctx, &args[0]) {
943 Some(v) => v,
944 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
945 };
946 let cond = args[1].clone();
947
948 let call_id = ctx.next_id();
949 ctx.store_macro_call(call_id, &span, &receiver, "filter", &args);
950
951 let accu_var = ACCU_VAR.to_string();
952 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
953 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
954
955 let iter_ref = synthetic(ctx, Expr::Ident(iter_var.clone()), span.clone());
956 let element_list = synthetic(ctx, Expr::List(vec![ListElement { expr: iter_ref, optional: false }]), span.clone());
957
958 let accu_ref_then = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
959 let append_step = synthetic(
960 ctx,
961 Expr::Binary {
962 op: BinaryOp::Add,
963 left: Box::new(accu_ref_then),
964 right: Box::new(element_list),
965 },
966 span.clone(),
967 );
968
969 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
970 let loop_step = synthetic(
971 ctx,
972 Expr::Ternary {
973 cond: Box::new(cond),
974 then_expr: Box::new(append_step),
975 else_expr: Box::new(accu_ref_else),
976 },
977 span.clone(),
978 );
979
980 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
981
982 MacroExpansion::Expanded(Spanned::new(
983 call_id,
984 Expr::Comprehension {
985 iter_var,
986 iter_var2: String::new(),
987 iter_range: Box::new(receiver),
988 accu_var,
989 accu_init: Box::new(accu_init),
990 loop_condition: Box::new(loop_condition),
991 loop_step: Box::new(loop_step),
992 result: Box::new(result),
993 },
994 span,
995 ))
996}
997
998fn expand_transform_list_3arg(
1001 ctx: &mut MacroContext,
1002 span: Span,
1003 receiver: Option<SpannedExpr>,
1004 args: Vec<SpannedExpr>,
1005) -> MacroExpansion {
1006 let receiver = match receiver {
1007 Some(r) => r,
1008 None => return MacroExpansion::Error("transformList() requires a receiver".to_string()),
1009 };
1010
1011 let iter_var = match extract_iter_var(ctx, &args[0]) {
1012 Some(v) => v,
1013 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1014 };
1015 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1016 Some(v) => v,
1017 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1018 };
1019 let transform = args[2].clone();
1020
1021 expand_transform_list_impl(ctx, span, receiver, iter_var, iter_var2, None, transform, &args)
1022}
1023
1024fn expand_transform_list_4arg(
1025 ctx: &mut MacroContext,
1026 span: Span,
1027 receiver: Option<SpannedExpr>,
1028 args: Vec<SpannedExpr>,
1029) -> MacroExpansion {
1030 let receiver = match receiver {
1031 Some(r) => r,
1032 None => return MacroExpansion::Error("transformList() requires a receiver".to_string()),
1033 };
1034
1035 let iter_var = match extract_iter_var(ctx, &args[0]) {
1036 Some(v) => v,
1037 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1038 };
1039 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1040 Some(v) => v,
1041 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1042 };
1043 let filter = args[2].clone();
1044 let transform = args[3].clone();
1045
1046 expand_transform_list_impl(ctx, span, receiver, iter_var, iter_var2, Some(filter), transform, &args)
1047}
1048
1049fn expand_transform_list_impl(
1050 ctx: &mut MacroContext,
1051 span: Span,
1052 receiver: SpannedExpr,
1053 iter_var: String,
1054 iter_var2: String,
1055 filter_cond: Option<SpannedExpr>,
1056 transform: SpannedExpr,
1057 args: &[SpannedExpr],
1058) -> MacroExpansion {
1059 let call_id = ctx.next_id();
1060 ctx.store_macro_call(call_id, &span, &receiver, "transformList", args);
1061
1062 let accu_var = ACCU_VAR.to_string();
1063 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
1064 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1065
1066 let transformed_list = synthetic(ctx, Expr::List(vec![ListElement { expr: transform, optional: false }]), span.clone());
1067 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1068 let append_step = synthetic(
1069 ctx,
1070 Expr::Binary {
1071 op: BinaryOp::Add,
1072 left: Box::new(accu_ref_step),
1073 right: Box::new(transformed_list),
1074 },
1075 span.clone(),
1076 );
1077
1078 let loop_step = if let Some(filter) = filter_cond {
1079 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1080 synthetic(
1081 ctx,
1082 Expr::Ternary {
1083 cond: Box::new(filter),
1084 then_expr: Box::new(append_step),
1085 else_expr: Box::new(accu_ref_else),
1086 },
1087 span.clone(),
1088 )
1089 } else {
1090 append_step
1091 };
1092
1093 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1094
1095 MacroExpansion::Expanded(Spanned::new(
1096 call_id,
1097 Expr::Comprehension {
1098 iter_var,
1099 iter_var2,
1100 iter_range: Box::new(receiver),
1101 accu_var,
1102 accu_init: Box::new(accu_init),
1103 loop_condition: Box::new(loop_condition),
1104 loop_step: Box::new(loop_step),
1105 result: Box::new(result),
1106 },
1107 span,
1108 ))
1109}
1110
1111fn expand_transform_map_3arg(
1114 ctx: &mut MacroContext,
1115 span: Span,
1116 receiver: Option<SpannedExpr>,
1117 args: Vec<SpannedExpr>,
1118) -> MacroExpansion {
1119 let receiver = match receiver {
1120 Some(r) => r,
1121 None => return MacroExpansion::Error("transformMap() requires a receiver".to_string()),
1122 };
1123
1124 let iter_var = match extract_iter_var(ctx, &args[0]) {
1125 Some(v) => v,
1126 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1127 };
1128 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1129 Some(v) => v,
1130 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1131 };
1132 let transform = args[2].clone();
1133
1134 expand_transform_map_impl(ctx, span, receiver, iter_var, iter_var2, None, transform, &args)
1135}
1136
1137fn expand_transform_map_4arg(
1138 ctx: &mut MacroContext,
1139 span: Span,
1140 receiver: Option<SpannedExpr>,
1141 args: Vec<SpannedExpr>,
1142) -> MacroExpansion {
1143 let receiver = match receiver {
1144 Some(r) => r,
1145 None => return MacroExpansion::Error("transformMap() requires a receiver".to_string()),
1146 };
1147
1148 let iter_var = match extract_iter_var(ctx, &args[0]) {
1149 Some(v) => v,
1150 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1151 };
1152 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1153 Some(v) => v,
1154 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1155 };
1156 let filter = args[2].clone();
1157 let transform = args[3].clone();
1158
1159 expand_transform_map_impl(ctx, span, receiver, iter_var, iter_var2, Some(filter), transform, &args)
1160}
1161
1162fn expand_transform_map_impl(
1163 ctx: &mut MacroContext,
1164 span: Span,
1165 receiver: SpannedExpr,
1166 iter_var: String,
1167 iter_var2: String,
1168 filter_cond: Option<SpannedExpr>,
1169 transform: SpannedExpr,
1170 args: &[SpannedExpr],
1171) -> MacroExpansion {
1172 let call_id = ctx.next_id();
1173 ctx.store_macro_call(call_id, &span, &receiver, "transformMap", args);
1174
1175 let accu_var = ACCU_VAR.to_string();
1176 let accu_init = synthetic(ctx, Expr::Map(vec![]), span.clone());
1177 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1178
1179 let key_ref = synthetic(ctx, Expr::Ident(iter_var.clone()), span.clone());
1180 let transformed_map = synthetic(ctx, Expr::Map(vec![MapEntry { key: key_ref, value: transform, optional: false }]), span.clone());
1181 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1182 let append_step = synthetic(
1183 ctx,
1184 Expr::Binary {
1185 op: BinaryOp::Add,
1186 left: Box::new(accu_ref_step),
1187 right: Box::new(transformed_map),
1188 },
1189 span.clone(),
1190 );
1191
1192 let loop_step = if let Some(filter) = filter_cond {
1193 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1194 synthetic(
1195 ctx,
1196 Expr::Ternary {
1197 cond: Box::new(filter),
1198 then_expr: Box::new(append_step),
1199 else_expr: Box::new(accu_ref_else),
1200 },
1201 span.clone(),
1202 )
1203 } else {
1204 append_step
1205 };
1206
1207 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1208
1209 MacroExpansion::Expanded(Spanned::new(
1210 call_id,
1211 Expr::Comprehension {
1212 iter_var,
1213 iter_var2,
1214 iter_range: Box::new(receiver),
1215 accu_var,
1216 accu_init: Box::new(accu_init),
1217 loop_condition: Box::new(loop_condition),
1218 loop_step: Box::new(loop_step),
1219 result: Box::new(result),
1220 },
1221 span,
1222 ))
1223}
1224
1225fn expand_bind(
1235 ctx: &mut MacroContext,
1236 span: Span,
1237 _receiver: Option<SpannedExpr>,
1238 args: Vec<SpannedExpr>,
1239) -> MacroExpansion {
1240 if args.len() != 3 {
1241 return MacroExpansion::Error(format!(
1242 "cel.bind() requires 3 arguments, got {}",
1243 args.len()
1244 ));
1245 }
1246
1247 let var_name = match &args[0].node {
1249 Expr::Ident(name) => name.clone(),
1250 _ => {
1251 return MacroExpansion::Error(
1252 "cel.bind() first argument must be an identifier".to_string(),
1253 )
1254 }
1255 };
1256
1257 let init = args[1].clone();
1259
1260 let body = args[2].clone();
1262
1263 let call_id = ctx.next_id();
1264 ctx.store_macro_call(call_id, &span, &args[0], "cel.bind", &args);
1265
1266 MacroExpansion::Expanded(Spanned::new(
1267 call_id,
1268 Expr::Bind {
1269 var_name,
1270 init: Box::new(init),
1271 body: Box::new(body),
1272 },
1273 span,
1274 ))
1275}
1276
1277fn build_method_call(
1281 ctx: &mut MacroContext,
1282 span: &Span,
1283 receiver: SpannedExpr,
1284 method: &str,
1285 args: Vec<SpannedExpr>,
1286) -> SpannedExpr {
1287 let member = synthetic(
1288 ctx,
1289 Expr::Member {
1290 expr: Box::new(receiver),
1291 field: method.to_string(),
1292 optional: false,
1293 },
1294 span.clone(),
1295 );
1296
1297 synthetic(
1298 ctx,
1299 Expr::Call {
1300 expr: Box::new(member),
1301 args,
1302 },
1303 span.clone(),
1304 )
1305}
1306
1307fn build_optional_of(ctx: &mut MacroContext, span: &Span, expr: SpannedExpr) -> SpannedExpr {
1309 let optional_ident = synthetic(ctx, Expr::Ident("optional".to_string()), span.clone());
1310 let optional_of_member = synthetic(
1311 ctx,
1312 Expr::Member {
1313 expr: Box::new(optional_ident),
1314 field: "of".to_string(),
1315 optional: false,
1316 },
1317 span.clone(),
1318 );
1319
1320 synthetic(
1321 ctx,
1322 Expr::Call {
1323 expr: Box::new(optional_of_member),
1324 args: vec![expr],
1325 },
1326 span.clone(),
1327 )
1328}
1329
1330fn build_optional_none(ctx: &mut MacroContext, span: &Span) -> SpannedExpr {
1332 let optional_ident = synthetic(ctx, Expr::Ident("optional".to_string()), span.clone());
1333 let optional_none_member = synthetic(
1334 ctx,
1335 Expr::Member {
1336 expr: Box::new(optional_ident),
1337 field: "none".to_string(),
1338 optional: false,
1339 },
1340 span.clone(),
1341 );
1342
1343 synthetic(
1344 ctx,
1345 Expr::Call {
1346 expr: Box::new(optional_none_member),
1347 args: vec![],
1348 },
1349 span.clone(),
1350 )
1351}
1352
1353fn expand_opt_map(
1362 ctx: &mut MacroContext,
1363 span: Span,
1364 receiver: Option<SpannedExpr>,
1365 args: Vec<SpannedExpr>,
1366) -> MacroExpansion {
1367 let receiver = match receiver {
1368 Some(r) => r,
1369 None => return MacroExpansion::Error("optMap() requires a receiver".to_string()),
1370 };
1371
1372 let iter_var = match extract_iter_var(ctx, &args[0]) {
1373 Some(v) => v,
1374 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1375 };
1376 let transform = args[1].clone();
1377
1378 let call_id = ctx.next_id();
1379 ctx.store_macro_call(call_id, &span, &receiver, "optMap", &args);
1380
1381 let has_value_call = build_method_call(ctx, &span, receiver.clone(), "hasValue", vec![]);
1383
1384 let value_call = build_method_call(ctx, &span, receiver, "value", vec![]);
1386
1387 let wrapped_result = build_optional_of(ctx, &span, transform);
1389
1390 let bind_expr = synthetic(
1392 ctx,
1393 Expr::Bind {
1394 var_name: iter_var,
1395 init: Box::new(value_call),
1396 body: Box::new(wrapped_result),
1397 },
1398 span.clone(),
1399 );
1400
1401 let none_call = build_optional_none(ctx, &span);
1403
1404 MacroExpansion::Expanded(Spanned::new(
1406 call_id,
1407 Expr::Ternary {
1408 cond: Box::new(has_value_call),
1409 then_expr: Box::new(bind_expr),
1410 else_expr: Box::new(none_call),
1411 },
1412 span,
1413 ))
1414}
1415
1416fn expand_opt_flat_map(
1428 ctx: &mut MacroContext,
1429 span: Span,
1430 receiver: Option<SpannedExpr>,
1431 args: Vec<SpannedExpr>,
1432) -> MacroExpansion {
1433 let receiver = match receiver {
1434 Some(r) => r,
1435 None => return MacroExpansion::Error("optFlatMap() requires a receiver".to_string()),
1436 };
1437
1438 let iter_var = match extract_iter_var(ctx, &args[0]) {
1439 Some(v) => v,
1440 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1441 };
1442 let transform = args[1].clone();
1443
1444 let call_id = ctx.next_id();
1445 ctx.store_macro_call(call_id, &span, &receiver, "optFlatMap", &args);
1446
1447 let has_value_call = build_method_call(ctx, &span, receiver.clone(), "hasValue", vec![]);
1449
1450 let value_call = build_method_call(ctx, &span, receiver, "value", vec![]);
1452
1453 let bind_expr = synthetic(
1456 ctx,
1457 Expr::Bind {
1458 var_name: iter_var,
1459 init: Box::new(value_call),
1460 body: Box::new(transform),
1461 },
1462 span.clone(),
1463 );
1464
1465 let none_call = build_optional_none(ctx, &span);
1467
1468 MacroExpansion::Expanded(Spanned::new(
1470 call_id,
1471 Expr::Ternary {
1472 cond: Box::new(has_value_call),
1473 then_expr: Box::new(bind_expr),
1474 else_expr: Box::new(none_call),
1475 },
1476 span,
1477 ))
1478}
1479
1480#[cfg(test)]
1481mod tests {
1482 use super::*;
1483
1484 fn dummy_expander(
1485 _ctx: &mut MacroContext,
1486 _span: Span,
1487 _receiver: Option<SpannedExpr>,
1488 _args: Vec<SpannedExpr>,
1489 ) -> MacroExpansion {
1490 MacroExpansion::Error("dummy".to_string())
1491 }
1492
1493 #[test]
1494 fn test_arg_count_exact() {
1495 let exact = ArgCount::Exact(2);
1496 assert!(exact.matches(2));
1497 assert!(!exact.matches(1));
1498 assert!(!exact.matches(3));
1499 assert_eq!(exact.count(), 2);
1500 assert!(!exact.is_vararg());
1501 }
1502
1503 #[test]
1504 fn test_arg_count_vararg() {
1505 let vararg = ArgCount::VarArg(2);
1506 assert!(vararg.matches(2));
1507 assert!(vararg.matches(3));
1508 assert!(vararg.matches(10));
1509 assert!(!vararg.matches(1));
1510 assert_eq!(vararg.count(), 2);
1511 assert!(vararg.is_vararg());
1512 }
1513
1514 #[test]
1515 fn test_macro_key() {
1516 let m = Macro::new("all", MacroStyle::Receiver, ArgCount::Exact(2), dummy_expander);
1517 assert_eq!(m.key(), "all:2:true");
1518
1519 let m2 = Macro::new("has", MacroStyle::Global, ArgCount::Exact(1), dummy_expander);
1520 assert_eq!(m2.key(), "has:1:false");
1521 }
1522
1523 #[test]
1524 fn test_registry_lookup_exact() {
1525 let mut registry = MacroRegistry::new();
1526 registry.register(Macro::new("all", MacroStyle::Receiver, ArgCount::Exact(2), dummy_expander));
1527 registry.register(Macro::new("all", MacroStyle::Receiver, ArgCount::Exact(3), dummy_expander));
1528
1529 assert!(registry.lookup("all", 2, true).is_some());
1530 assert!(registry.lookup("all", 3, true).is_some());
1531 assert!(registry.lookup("all", 4, true).is_none());
1532 assert!(registry.lookup("all", 2, false).is_none());
1533 }
1534
1535 #[test]
1536 fn test_registry_lookup_vararg() {
1537 let mut registry = MacroRegistry::new();
1538 registry.register(Macro::new("custom", MacroStyle::Receiver, ArgCount::VarArg(2), dummy_expander));
1539
1540 assert!(registry.lookup("custom", 2, true).is_some());
1541 assert!(registry.lookup("custom", 3, true).is_some());
1542 assert!(registry.lookup("custom", 10, true).is_some());
1543 assert!(registry.lookup("custom", 1, true).is_none());
1544 }
1545
1546 #[test]
1547 fn test_registry_standard() {
1548 let registry = MacroRegistry::standard();
1549
1550 assert!(registry.lookup("has", 1, false).is_some());
1551 assert!(registry.lookup("all", 2, true).is_some());
1552 assert!(registry.lookup("all", 3, true).is_some());
1553 assert!(registry.lookup("exists", 2, true).is_some());
1554 assert!(registry.lookup("exists", 3, true).is_some());
1555 assert!(registry.lookup("exists_one", 2, true).is_some());
1556 assert!(registry.lookup("exists_one", 3, true).is_some());
1557 assert!(registry.lookup("map", 2, true).is_some());
1558 assert!(registry.lookup("map", 3, true).is_some());
1559 assert!(registry.lookup("filter", 2, true).is_some());
1560 }
1561
1562 #[test]
1563 fn test_registry_contains() {
1564 let registry = MacroRegistry::standard();
1565 assert!(registry.contains("has"));
1566 assert!(registry.contains("all"));
1567 assert!(registry.contains("cel.bind"));
1568 assert!(!registry.contains("nonexistent"));
1569 }
1570
1571 #[test]
1572 fn test_cel_bind_macro_registered() {
1573 let registry = MacroRegistry::standard();
1574 assert!(registry.lookup("cel.bind", 3, false).is_some());
1575 assert!(registry.lookup("cel.bind", 3, true).is_none());
1577 }
1578
1579 #[test]
1580 fn test_opt_map_macro_registered() {
1581 let registry = MacroRegistry::standard();
1582 assert!(registry.lookup("optMap", 2, true).is_some());
1584 assert!(registry.lookup("optMap", 2, false).is_none());
1586 assert!(registry.lookup("optMap", 1, true).is_none());
1588 assert!(registry.lookup("optMap", 3, true).is_none());
1589 }
1590
1591 #[test]
1592 fn test_opt_flat_map_macro_registered() {
1593 let registry = MacroRegistry::standard();
1594 assert!(registry.lookup("optFlatMap", 2, true).is_some());
1596 assert!(registry.lookup("optFlatMap", 2, false).is_none());
1598 assert!(registry.lookup("optFlatMap", 1, true).is_none());
1600 assert!(registry.lookup("optFlatMap", 3, true).is_none());
1601 }
1602
1603 #[test]
1604 fn test_registry_contains_opt_macros() {
1605 let registry = MacroRegistry::standard();
1606 assert!(registry.contains("optMap"));
1607 assert!(registry.contains("optFlatMap"));
1608 }
1609}