1use std::collections::HashMap;
20
21use crate::types::{
22 BinaryOp, ComprehensionData, Expr, ListElement, MapEntry, Span, Spanned, SpannedExpr, UnaryOp,
23};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum MacroStyle {
28 Global,
30 Receiver,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum ArgCount {
37 Exact(usize),
39 VarArg(usize),
41}
42
43impl ArgCount {
44 pub fn matches(&self, count: usize) -> bool {
46 match self {
47 ArgCount::Exact(n) => count == *n,
48 ArgCount::VarArg(min) => count >= *min,
49 }
50 }
51
52 pub fn count(&self) -> usize {
54 match self {
55 ArgCount::Exact(n) => *n,
56 ArgCount::VarArg(min) => *min,
57 }
58 }
59
60 pub fn is_vararg(&self) -> bool {
62 matches!(self, ArgCount::VarArg(_))
63 }
64}
65
66#[derive(Debug)]
68pub enum MacroExpansion {
69 Expanded(SpannedExpr),
71 Error(String),
74}
75
76pub struct MacroContext<'a> {
81 next_id_fn: &'a mut dyn FnMut() -> i64,
83 errors: Vec<(String, Span)>,
85 store_macro_call_fn: Option<MacroCallStoreFn<'a>>,
87}
88
89type MacroCallStoreFn<'a> = &'a mut dyn FnMut(i64, &Span, &SpannedExpr, &str, &[SpannedExpr]);
90
91impl<'a> MacroContext<'a> {
92 pub fn new(
94 next_id_fn: &'a mut dyn FnMut() -> i64,
95 store_macro_call_fn: Option<MacroCallStoreFn<'a>>,
96 ) -> Self {
97 Self {
98 next_id_fn,
99 errors: Vec::new(),
100 store_macro_call_fn,
101 }
102 }
103
104 pub fn next_id(&mut self) -> i64 {
106 (self.next_id_fn)()
107 }
108
109 pub fn add_error(&mut self, message: String, span: Span) {
111 self.errors.push((message, span));
112 }
113
114 pub fn take_errors(&mut self) -> Vec<(String, Span)> {
116 std::mem::take(&mut self.errors)
117 }
118
119 pub fn store_macro_call(
121 &mut self,
122 call_id: i64,
123 span: &Span,
124 receiver: &SpannedExpr,
125 method_name: &str,
126 args: &[SpannedExpr],
127 ) {
128 if let Some(f) = &mut self.store_macro_call_fn {
129 f(call_id, span, receiver, method_name, args);
130 }
131 }
132}
133
134pub type MacroExpander = fn(
146 ctx: &mut MacroContext,
147 span: Span,
148 receiver: Option<SpannedExpr>,
149 args: Vec<SpannedExpr>,
150) -> MacroExpansion;
151
152#[derive(Clone)]
154pub struct Macro {
155 pub name: &'static str,
157 pub style: MacroStyle,
159 pub arg_count: ArgCount,
161 pub expander: MacroExpander,
163 pub description: Option<&'static str>,
165}
166
167impl Macro {
168 pub const fn new(
170 name: &'static str,
171 style: MacroStyle,
172 arg_count: ArgCount,
173 expander: MacroExpander,
174 ) -> Self {
175 Self {
176 name,
177 style,
178 arg_count,
179 expander,
180 description: None,
181 }
182 }
183
184 pub const fn with_description(
186 name: &'static str,
187 style: MacroStyle,
188 arg_count: ArgCount,
189 expander: MacroExpander,
190 description: &'static str,
191 ) -> Self {
192 Self {
193 name,
194 style,
195 arg_count,
196 expander,
197 description: Some(description),
198 }
199 }
200
201 pub fn key(&self) -> String {
203 make_key(
204 self.name,
205 self.arg_count.count(),
206 self.style == MacroStyle::Receiver,
207 )
208 }
209}
210
211impl std::fmt::Debug for Macro {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("Macro")
214 .field("name", &self.name)
215 .field("style", &self.style)
216 .field("arg_count", &self.arg_count)
217 .field("description", &self.description)
218 .finish_non_exhaustive()
219 }
220}
221
222fn make_key(name: &str, arg_count: usize, is_receiver: bool) -> String {
224 format!("{}:{}:{}", name, arg_count, is_receiver)
225}
226
227#[derive(Debug, Clone)]
232pub struct MacroRegistry {
233 macros: HashMap<String, Macro>,
235 vararg_keys: HashMap<String, usize>,
237}
238
239impl Default for MacroRegistry {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245impl MacroRegistry {
246 pub fn new() -> Self {
248 Self {
249 macros: HashMap::new(),
250 vararg_keys: HashMap::new(),
251 }
252 }
253
254 pub fn standard() -> Self {
256 let mut registry = Self::new();
257 for macro_def in STANDARD_MACROS {
258 registry.register(macro_def.clone());
259 }
260 registry
261 }
262
263 pub fn register(&mut self, macro_def: Macro) {
265 let key = macro_def.key();
266
267 if macro_def.arg_count.is_vararg() {
269 let vararg_key = format!(
270 "{}:{}",
271 macro_def.name,
272 macro_def.style == MacroStyle::Receiver
273 );
274 self.vararg_keys
275 .insert(vararg_key, macro_def.arg_count.count());
276 }
277
278 self.macros.insert(key, macro_def);
279 }
280
281 pub fn lookup(&self, name: &str, arg_count: usize, is_receiver: bool) -> Option<&Macro> {
285 let exact_key = make_key(name, arg_count, is_receiver);
287 if let Some(m) = self.macros.get(&exact_key) {
288 return Some(m);
289 }
290
291 let vararg_lookup_key = format!("{}:{}", name, is_receiver);
293 if let Some(&min_args) = self.vararg_keys.get(&vararg_lookup_key) {
294 if arg_count >= min_args {
295 let vararg_key = make_key(name, min_args, is_receiver);
296 return self.macros.get(&vararg_key);
297 }
298 }
299
300 None
301 }
302
303 pub fn contains(&self, name: &str) -> bool {
305 self.macros.values().any(|m| m.name == name)
306 }
307
308 pub fn iter(&self) -> impl Iterator<Item = &Macro> {
310 self.macros.values()
311 }
312
313 pub fn len(&self) -> usize {
315 self.macros.len()
316 }
317
318 pub fn is_empty(&self) -> bool {
320 self.macros.is_empty()
321 }
322
323 pub fn expected_args_description(&self, name: &str, is_receiver: bool) -> Option<String> {
329 let mut counts: Vec<usize> = Vec::new();
330 let mut has_vararg = false;
331 let mut vararg_min = 0;
332
333 for m in self.macros.values() {
334 if m.name == name && (m.style == MacroStyle::Receiver) == is_receiver {
335 match m.arg_count {
336 ArgCount::Exact(n) => {
337 if !counts.contains(&n) {
338 counts.push(n);
339 }
340 }
341 ArgCount::VarArg(min) => {
342 has_vararg = true;
343 vararg_min = min;
344 }
345 }
346 }
347 }
348
349 if counts.is_empty() && !has_vararg {
350 return None;
351 }
352
353 if has_vararg {
354 return Some(format!(
355 "at least {} argument{}",
356 vararg_min,
357 if vararg_min == 1 { "" } else { "s" }
358 ));
359 }
360
361 counts.sort();
362 Some(match counts.as_slice() {
363 [n] => format!("exactly {} argument{}", n, if *n == 1 { "" } else { "s" }),
364 _ => {
365 let parts: Vec<String> = counts.iter().map(|n| n.to_string()).collect();
366 format!("{} arguments", parts.join(" or "))
367 }
368 })
369 }
370}
371
372const ACCU_VAR: &str = "__result__";
378
379pub static STANDARD_MACROS: &[Macro] = &[
381 Macro::with_description(
383 "has",
384 MacroStyle::Global,
385 ArgCount::Exact(1),
386 expand_has,
387 "Tests whether a field is set on a message",
388 ),
389 Macro::with_description(
391 "all",
392 MacroStyle::Receiver,
393 ArgCount::Exact(2),
394 expand_all_2arg,
395 "Tests whether all elements satisfy a condition",
396 ),
397 Macro::with_description(
398 "all",
399 MacroStyle::Receiver,
400 ArgCount::Exact(3),
401 expand_all_3arg,
402 "Tests whether all elements satisfy a condition (two-variable form)",
403 ),
404 Macro::with_description(
406 "exists",
407 MacroStyle::Receiver,
408 ArgCount::Exact(2),
409 expand_exists_2arg,
410 "Tests whether any element satisfies a condition",
411 ),
412 Macro::with_description(
413 "exists",
414 MacroStyle::Receiver,
415 ArgCount::Exact(3),
416 expand_exists_3arg,
417 "Tests whether any element satisfies a condition (two-variable form)",
418 ),
419 Macro::with_description(
421 "exists_one",
422 MacroStyle::Receiver,
423 ArgCount::Exact(2),
424 expand_exists_one_2arg,
425 "Tests whether exactly one element satisfies a condition",
426 ),
427 Macro::with_description(
428 "exists_one",
429 MacroStyle::Receiver,
430 ArgCount::Exact(3),
431 expand_exists_one_3arg,
432 "Tests whether exactly one element satisfies a condition (two-variable form)",
433 ),
434 Macro::with_description(
436 "existsOne",
437 MacroStyle::Receiver,
438 ArgCount::Exact(2),
439 expand_exists_one_2arg,
440 "Tests whether exactly one element satisfies a condition",
441 ),
442 Macro::with_description(
443 "existsOne",
444 MacroStyle::Receiver,
445 ArgCount::Exact(3),
446 expand_exists_one_3arg,
447 "Tests whether exactly one element satisfies a condition (two-variable form)",
448 ),
449 Macro::with_description(
451 "map",
452 MacroStyle::Receiver,
453 ArgCount::Exact(2),
454 expand_map_2arg,
455 "Transforms elements of a list",
456 ),
457 Macro::with_description(
458 "map",
459 MacroStyle::Receiver,
460 ArgCount::Exact(3),
461 expand_map_3arg,
462 "Transforms elements of a list with filtering",
463 ),
464 Macro::with_description(
466 "filter",
467 MacroStyle::Receiver,
468 ArgCount::Exact(2),
469 expand_filter,
470 "Filters elements of a list by a condition",
471 ),
472 Macro::with_description(
474 "transformList",
475 MacroStyle::Receiver,
476 ArgCount::Exact(3),
477 expand_transform_list_3arg,
478 "Transforms list elements with index and value variables",
479 ),
480 Macro::with_description(
481 "transformList",
482 MacroStyle::Receiver,
483 ArgCount::Exact(4),
484 expand_transform_list_4arg,
485 "Transforms list elements with index, value, and filter",
486 ),
487 Macro::with_description(
489 "transformMap",
490 MacroStyle::Receiver,
491 ArgCount::Exact(3),
492 expand_transform_map_3arg,
493 "Transforms map entries with key and value variables",
494 ),
495 Macro::with_description(
496 "transformMap",
497 MacroStyle::Receiver,
498 ArgCount::Exact(4),
499 expand_transform_map_4arg,
500 "Transforms map entries with key, value, and filter",
501 ),
502 Macro::with_description(
504 "cel.bind",
505 MacroStyle::Global,
506 ArgCount::Exact(3),
507 expand_bind,
508 "Binds a variable to a value for use in an expression",
509 ),
510 Macro::with_description(
512 "optMap",
513 MacroStyle::Receiver,
514 ArgCount::Exact(2),
515 expand_opt_map,
516 "Transforms an optional value if present",
517 ),
518 Macro::with_description(
520 "optFlatMap",
521 MacroStyle::Receiver,
522 ArgCount::Exact(2),
523 expand_opt_flat_map,
524 "Chains optional operations",
525 ),
526 Macro::with_description(
528 "proto.hasExt",
529 MacroStyle::Global,
530 ArgCount::Exact(2),
531 expand_proto_has_ext,
532 "Tests whether a proto extension field is set",
533 ),
534 Macro::with_description(
536 "proto.getExt",
537 MacroStyle::Global,
538 ArgCount::Exact(2),
539 expand_proto_get_ext,
540 "Gets the value of a proto extension field",
541 ),
542 Macro::with_description(
544 "cel.block",
545 MacroStyle::Global,
546 ArgCount::Exact(2),
547 expand_cel_block,
548 "Binds a list of slot expressions for common subexpression elimination",
549 ),
550 Macro::with_description(
552 "cel.index",
553 MacroStyle::Global,
554 ArgCount::Exact(1),
555 expand_cel_index,
556 "References a slot variable by index in a cel.block",
557 ),
558 Macro::with_description(
560 "cel.iterVar",
561 MacroStyle::Global,
562 ArgCount::Exact(2),
563 expand_cel_iter_var,
564 "References an iterator variable for nested comprehensions",
565 ),
566 Macro::with_description(
568 "cel.accuVar",
569 MacroStyle::Global,
570 ArgCount::Exact(2),
571 expand_cel_accu_var,
572 "References an accumulator variable for nested comprehensions",
573 ),
574];
575
576fn synthetic(ctx: &mut MacroContext, node: Expr, span: Span) -> SpannedExpr {
580 Spanned::new(ctx.next_id(), node, span)
581}
582
583fn extract_iter_var(ctx: &mut MacroContext, expr: &SpannedExpr) -> Option<String> {
586 match &expr.node {
587 Expr::Ident(name) => Some(name.clone()),
588 _ => {
589 ctx.add_error(
590 "iteration variable must be an identifier".to_string(),
591 expr.span.clone(),
592 );
593 None
594 }
595 }
596}
597
598fn expand_has(
602 ctx: &mut MacroContext,
603 span: Span,
604 _receiver: Option<SpannedExpr>,
605 args: Vec<SpannedExpr>,
606) -> MacroExpansion {
607 if args.len() != 1 {
608 return MacroExpansion::Error(format!("has() requires 1 argument, got {}", args.len()));
609 }
610
611 let arg = args.into_iter().next().unwrap();
612
613 match arg.node {
614 Expr::Member { expr, field, .. } => {
615 let result = Spanned::new(ctx.next_id(), Expr::MemberTestOnly { expr, field }, span);
616 MacroExpansion::Expanded(result)
617 }
618 _ => MacroExpansion::Error(
619 "has() argument must be a field selection (e.g., has(m.x))".to_string(),
620 ),
621 }
622}
623
624fn expand_all_2arg(
627 ctx: &mut MacroContext,
628 span: Span,
629 receiver: Option<SpannedExpr>,
630 args: Vec<SpannedExpr>,
631) -> MacroExpansion {
632 let receiver = match receiver {
633 Some(r) => r,
634 None => return MacroExpansion::Error("all() requires a receiver".to_string()),
635 };
636
637 let iter_var = match extract_iter_var(ctx, &args[0]) {
638 Some(v) => v,
639 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
640 };
641 let cond = args[1].clone();
642
643 expand_all_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
644}
645
646fn expand_all_3arg(
647 ctx: &mut MacroContext,
648 span: Span,
649 receiver: Option<SpannedExpr>,
650 args: Vec<SpannedExpr>,
651) -> MacroExpansion {
652 let receiver = match receiver {
653 Some(r) => r,
654 None => return MacroExpansion::Error("all() requires a receiver".to_string()),
655 };
656
657 let iter_var = match extract_iter_var(ctx, &args[0]) {
658 Some(v) => v,
659 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
660 };
661 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
662 Some(v) => v,
663 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
664 };
665 let cond = args[2].clone();
666
667 expand_all_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
668}
669
670fn expand_all_impl(
671 ctx: &mut MacroContext,
672 span: Span,
673 receiver: SpannedExpr,
674 iter_var: String,
675 iter_var2: String,
676 cond: SpannedExpr,
677 args: &[SpannedExpr],
678) -> MacroExpansion {
679 let call_id = ctx.next_id();
680 ctx.store_macro_call(call_id, &span, &receiver, "all", args);
681
682 let accu_var = ACCU_VAR.to_string();
683 let accu_init = synthetic(ctx, Expr::Bool(true), span.clone());
684 let loop_condition = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
685
686 let accu_ref = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
687 let loop_step = synthetic(
688 ctx,
689 Expr::Binary {
690 op: BinaryOp::And,
691 left: Box::new(cond),
692 right: Box::new(accu_ref),
693 },
694 span.clone(),
695 );
696
697 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
698
699 MacroExpansion::Expanded(Spanned::new(
700 call_id,
701 Expr::Comprehension(ComprehensionData {
702 iter_var,
703 iter_var2,
704 iter_range: Box::new(receiver),
705 accu_var,
706 accu_init: Box::new(accu_init),
707 loop_condition: Box::new(loop_condition),
708 loop_step: Box::new(loop_step),
709 result: Box::new(result),
710 }),
711 span,
712 ))
713}
714
715fn expand_exists_2arg(
718 ctx: &mut MacroContext,
719 span: Span,
720 receiver: Option<SpannedExpr>,
721 args: Vec<SpannedExpr>,
722) -> MacroExpansion {
723 let receiver = match receiver {
724 Some(r) => r,
725 None => return MacroExpansion::Error("exists() requires a receiver".to_string()),
726 };
727
728 let iter_var = match extract_iter_var(ctx, &args[0]) {
729 Some(v) => v,
730 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
731 };
732 let cond = args[1].clone();
733
734 expand_exists_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
735}
736
737fn expand_exists_3arg(
738 ctx: &mut MacroContext,
739 span: Span,
740 receiver: Option<SpannedExpr>,
741 args: Vec<SpannedExpr>,
742) -> MacroExpansion {
743 let receiver = match receiver {
744 Some(r) => r,
745 None => return MacroExpansion::Error("exists() requires a receiver".to_string()),
746 };
747
748 let iter_var = match extract_iter_var(ctx, &args[0]) {
749 Some(v) => v,
750 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
751 };
752 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
753 Some(v) => v,
754 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
755 };
756 let cond = args[2].clone();
757
758 expand_exists_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
759}
760
761fn expand_exists_impl(
762 ctx: &mut MacroContext,
763 span: Span,
764 receiver: SpannedExpr,
765 iter_var: String,
766 iter_var2: String,
767 cond: SpannedExpr,
768 args: &[SpannedExpr],
769) -> MacroExpansion {
770 let call_id = ctx.next_id();
771 ctx.store_macro_call(call_id, &span, &receiver, "exists", args);
772
773 let accu_var = ACCU_VAR.to_string();
774 let accu_init = synthetic(ctx, Expr::Bool(false), span.clone());
775
776 let accu_ref_cond = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
777 let loop_condition = synthetic(
778 ctx,
779 Expr::Unary {
780 op: UnaryOp::Not,
781 expr: Box::new(accu_ref_cond),
782 },
783 span.clone(),
784 );
785
786 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
787 let loop_step = synthetic(
788 ctx,
789 Expr::Binary {
790 op: BinaryOp::Or,
791 left: Box::new(cond),
792 right: Box::new(accu_ref_step),
793 },
794 span.clone(),
795 );
796
797 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
798
799 MacroExpansion::Expanded(Spanned::new(
800 call_id,
801 Expr::Comprehension(ComprehensionData {
802 iter_var,
803 iter_var2,
804 iter_range: Box::new(receiver),
805 accu_var,
806 accu_init: Box::new(accu_init),
807 loop_condition: Box::new(loop_condition),
808 loop_step: Box::new(loop_step),
809 result: Box::new(result),
810 }),
811 span,
812 ))
813}
814
815fn expand_exists_one_2arg(
818 ctx: &mut MacroContext,
819 span: Span,
820 receiver: Option<SpannedExpr>,
821 args: Vec<SpannedExpr>,
822) -> MacroExpansion {
823 let receiver = match receiver {
824 Some(r) => r,
825 None => return MacroExpansion::Error("exists_one() requires a receiver".to_string()),
826 };
827
828 let iter_var = match extract_iter_var(ctx, &args[0]) {
829 Some(v) => v,
830 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
831 };
832 let cond = args[1].clone();
833
834 expand_exists_one_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
835}
836
837fn expand_exists_one_3arg(
838 ctx: &mut MacroContext,
839 span: Span,
840 receiver: Option<SpannedExpr>,
841 args: Vec<SpannedExpr>,
842) -> MacroExpansion {
843 let receiver = match receiver {
844 Some(r) => r,
845 None => return MacroExpansion::Error("exists_one() requires a receiver".to_string()),
846 };
847
848 let iter_var = match extract_iter_var(ctx, &args[0]) {
849 Some(v) => v,
850 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
851 };
852 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
853 Some(v) => v,
854 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
855 };
856 let cond = args[2].clone();
857
858 expand_exists_one_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
859}
860
861fn expand_exists_one_impl(
862 ctx: &mut MacroContext,
863 span: Span,
864 receiver: SpannedExpr,
865 iter_var: String,
866 iter_var2: String,
867 cond: SpannedExpr,
868 args: &[SpannedExpr],
869) -> MacroExpansion {
870 let call_id = ctx.next_id();
871 ctx.store_macro_call(call_id, &span, &receiver, "exists_one", args);
872
873 let accu_var = ACCU_VAR.to_string();
874 let accu_init = synthetic(ctx, Expr::Int(0), span.clone());
875
876 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
877
878 let accu_ref_then = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
879 let one_step = synthetic(ctx, Expr::Int(1), span.clone());
880 let increment = synthetic(
881 ctx,
882 Expr::Binary {
883 op: BinaryOp::Add,
884 left: Box::new(accu_ref_then),
885 right: Box::new(one_step),
886 },
887 span.clone(),
888 );
889 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
890 let loop_step = synthetic(
891 ctx,
892 Expr::Ternary {
893 cond: Box::new(cond),
894 then_expr: Box::new(increment),
895 else_expr: Box::new(accu_ref_else),
896 },
897 span.clone(),
898 );
899
900 let accu_ref_result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
901 let one_result = synthetic(ctx, Expr::Int(1), span.clone());
902 let result = synthetic(
903 ctx,
904 Expr::Binary {
905 op: BinaryOp::Eq,
906 left: Box::new(accu_ref_result),
907 right: Box::new(one_result),
908 },
909 span.clone(),
910 );
911
912 MacroExpansion::Expanded(Spanned::new(
913 call_id,
914 Expr::Comprehension(ComprehensionData {
915 iter_var,
916 iter_var2,
917 iter_range: Box::new(receiver),
918 accu_var,
919 accu_init: Box::new(accu_init),
920 loop_condition: Box::new(loop_condition),
921 loop_step: Box::new(loop_step),
922 result: Box::new(result),
923 }),
924 span,
925 ))
926}
927
928fn expand_map_2arg(
931 ctx: &mut MacroContext,
932 span: Span,
933 receiver: Option<SpannedExpr>,
934 args: Vec<SpannedExpr>,
935) -> MacroExpansion {
936 let receiver = match receiver {
937 Some(r) => r,
938 None => return MacroExpansion::Error("map() requires a receiver".to_string()),
939 };
940
941 let iter_var = match extract_iter_var(ctx, &args[0]) {
942 Some(v) => v,
943 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
944 };
945 let transform = args[1].clone();
946
947 expand_map_impl(ctx, span, receiver, iter_var, None, transform, &args)
948}
949
950fn expand_map_3arg(
951 ctx: &mut MacroContext,
952 span: Span,
953 receiver: Option<SpannedExpr>,
954 args: Vec<SpannedExpr>,
955) -> MacroExpansion {
956 let receiver = match receiver {
957 Some(r) => r,
958 None => return MacroExpansion::Error("map() requires a receiver".to_string()),
959 };
960
961 let iter_var = match extract_iter_var(ctx, &args[0]) {
962 Some(v) => v,
963 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
964 };
965 let filter = args[1].clone();
966 let transform = args[2].clone();
967
968 expand_map_impl(
969 ctx,
970 span,
971 receiver,
972 iter_var,
973 Some(filter),
974 transform,
975 &args,
976 )
977}
978
979fn expand_map_impl(
980 ctx: &mut MacroContext,
981 span: Span,
982 receiver: SpannedExpr,
983 iter_var: String,
984 filter_cond: Option<SpannedExpr>,
985 transform: SpannedExpr,
986 args: &[SpannedExpr],
987) -> MacroExpansion {
988 let call_id = ctx.next_id();
989 ctx.store_macro_call(call_id, &span, &receiver, "map", args);
990
991 let accu_var = ACCU_VAR.to_string();
992 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
993 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
994
995 let transformed_list = synthetic(
996 ctx,
997 Expr::List(vec![ListElement {
998 expr: transform,
999 optional: false,
1000 }]),
1001 span.clone(),
1002 );
1003 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1004 let append_step = synthetic(
1005 ctx,
1006 Expr::Binary {
1007 op: BinaryOp::Add,
1008 left: Box::new(accu_ref_step),
1009 right: Box::new(transformed_list),
1010 },
1011 span.clone(),
1012 );
1013
1014 let loop_step = if let Some(filter) = filter_cond {
1015 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1016 synthetic(
1017 ctx,
1018 Expr::Ternary {
1019 cond: Box::new(filter),
1020 then_expr: Box::new(append_step),
1021 else_expr: Box::new(accu_ref_else),
1022 },
1023 span.clone(),
1024 )
1025 } else {
1026 append_step
1027 };
1028
1029 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1030
1031 MacroExpansion::Expanded(Spanned::new(
1032 call_id,
1033 Expr::Comprehension(ComprehensionData {
1034 iter_var,
1035 iter_var2: String::new(),
1036 iter_range: Box::new(receiver),
1037 accu_var,
1038 accu_init: Box::new(accu_init),
1039 loop_condition: Box::new(loop_condition),
1040 loop_step: Box::new(loop_step),
1041 result: Box::new(result),
1042 }),
1043 span,
1044 ))
1045}
1046
1047fn expand_filter(
1050 ctx: &mut MacroContext,
1051 span: Span,
1052 receiver: Option<SpannedExpr>,
1053 args: Vec<SpannedExpr>,
1054) -> MacroExpansion {
1055 let receiver = match receiver {
1056 Some(r) => r,
1057 None => return MacroExpansion::Error("filter() requires a receiver".to_string()),
1058 };
1059
1060 let iter_var = match extract_iter_var(ctx, &args[0]) {
1061 Some(v) => v,
1062 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1063 };
1064 let cond = args[1].clone();
1065
1066 let call_id = ctx.next_id();
1067 ctx.store_macro_call(call_id, &span, &receiver, "filter", &args);
1068
1069 let accu_var = ACCU_VAR.to_string();
1070 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
1071 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1072
1073 let iter_ref = synthetic(ctx, Expr::Ident(iter_var.clone()), span.clone());
1074 let element_list = synthetic(
1075 ctx,
1076 Expr::List(vec![ListElement {
1077 expr: iter_ref,
1078 optional: false,
1079 }]),
1080 span.clone(),
1081 );
1082
1083 let accu_ref_then = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1084 let append_step = synthetic(
1085 ctx,
1086 Expr::Binary {
1087 op: BinaryOp::Add,
1088 left: Box::new(accu_ref_then),
1089 right: Box::new(element_list),
1090 },
1091 span.clone(),
1092 );
1093
1094 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1095 let loop_step = synthetic(
1096 ctx,
1097 Expr::Ternary {
1098 cond: Box::new(cond),
1099 then_expr: Box::new(append_step),
1100 else_expr: Box::new(accu_ref_else),
1101 },
1102 span.clone(),
1103 );
1104
1105 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1106
1107 MacroExpansion::Expanded(Spanned::new(
1108 call_id,
1109 Expr::Comprehension(ComprehensionData {
1110 iter_var,
1111 iter_var2: String::new(),
1112 iter_range: Box::new(receiver),
1113 accu_var,
1114 accu_init: Box::new(accu_init),
1115 loop_condition: Box::new(loop_condition),
1116 loop_step: Box::new(loop_step),
1117 result: Box::new(result),
1118 }),
1119 span,
1120 ))
1121}
1122
1123struct TransformParams {
1124 iter_var: String,
1125 iter_var2: String,
1126 filter_cond: Option<SpannedExpr>,
1127 transform: SpannedExpr,
1128}
1129
1130fn expand_transform_list_3arg(
1133 ctx: &mut MacroContext,
1134 span: Span,
1135 receiver: Option<SpannedExpr>,
1136 args: Vec<SpannedExpr>,
1137) -> MacroExpansion {
1138 let receiver = match receiver {
1139 Some(r) => r,
1140 None => return MacroExpansion::Error("transformList() requires a receiver".to_string()),
1141 };
1142
1143 let iter_var = match extract_iter_var(ctx, &args[0]) {
1144 Some(v) => v,
1145 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1146 };
1147 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1148 Some(v) => v,
1149 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1150 };
1151 let transform = args[2].clone();
1152
1153 expand_transform_list_impl(
1154 ctx,
1155 span,
1156 receiver,
1157 TransformParams {
1158 iter_var,
1159 iter_var2,
1160 filter_cond: None,
1161 transform,
1162 },
1163 &args,
1164 )
1165}
1166
1167fn expand_transform_list_4arg(
1168 ctx: &mut MacroContext,
1169 span: Span,
1170 receiver: Option<SpannedExpr>,
1171 args: Vec<SpannedExpr>,
1172) -> MacroExpansion {
1173 let receiver = match receiver {
1174 Some(r) => r,
1175 None => return MacroExpansion::Error("transformList() requires a receiver".to_string()),
1176 };
1177
1178 let iter_var = match extract_iter_var(ctx, &args[0]) {
1179 Some(v) => v,
1180 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1181 };
1182 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1183 Some(v) => v,
1184 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1185 };
1186 let filter = args[2].clone();
1187 let transform = args[3].clone();
1188
1189 expand_transform_list_impl(
1190 ctx,
1191 span,
1192 receiver,
1193 TransformParams {
1194 iter_var,
1195 iter_var2,
1196 filter_cond: Some(filter),
1197 transform,
1198 },
1199 &args,
1200 )
1201}
1202
1203fn expand_transform_list_impl(
1204 ctx: &mut MacroContext,
1205 span: Span,
1206 receiver: SpannedExpr,
1207 params: TransformParams,
1208 args: &[SpannedExpr],
1209) -> MacroExpansion {
1210 let call_id = ctx.next_id();
1211 ctx.store_macro_call(call_id, &span, &receiver, "transformList", args);
1212
1213 let accu_var = ACCU_VAR.to_string();
1214 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
1215 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1216
1217 let transformed_list = synthetic(
1218 ctx,
1219 Expr::List(vec![ListElement {
1220 expr: params.transform,
1221 optional: false,
1222 }]),
1223 span.clone(),
1224 );
1225 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1226 let append_step = synthetic(
1227 ctx,
1228 Expr::Binary {
1229 op: BinaryOp::Add,
1230 left: Box::new(accu_ref_step),
1231 right: Box::new(transformed_list),
1232 },
1233 span.clone(),
1234 );
1235
1236 let loop_step = if let Some(filter) = params.filter_cond {
1237 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1238 synthetic(
1239 ctx,
1240 Expr::Ternary {
1241 cond: Box::new(filter),
1242 then_expr: Box::new(append_step),
1243 else_expr: Box::new(accu_ref_else),
1244 },
1245 span.clone(),
1246 )
1247 } else {
1248 append_step
1249 };
1250
1251 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1252
1253 MacroExpansion::Expanded(Spanned::new(
1254 call_id,
1255 Expr::Comprehension(ComprehensionData {
1256 iter_var: params.iter_var,
1257 iter_var2: params.iter_var2,
1258 iter_range: Box::new(receiver),
1259 accu_var,
1260 accu_init: Box::new(accu_init),
1261 loop_condition: Box::new(loop_condition),
1262 loop_step: Box::new(loop_step),
1263 result: Box::new(result),
1264 }),
1265 span,
1266 ))
1267}
1268
1269fn expand_transform_map_3arg(
1272 ctx: &mut MacroContext,
1273 span: Span,
1274 receiver: Option<SpannedExpr>,
1275 args: Vec<SpannedExpr>,
1276) -> MacroExpansion {
1277 let receiver = match receiver {
1278 Some(r) => r,
1279 None => return MacroExpansion::Error("transformMap() requires a receiver".to_string()),
1280 };
1281
1282 let iter_var = match extract_iter_var(ctx, &args[0]) {
1283 Some(v) => v,
1284 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1285 };
1286 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1287 Some(v) => v,
1288 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1289 };
1290 let transform = args[2].clone();
1291
1292 expand_transform_map_impl(
1293 ctx,
1294 span,
1295 receiver,
1296 TransformParams {
1297 iter_var,
1298 iter_var2,
1299 filter_cond: None,
1300 transform,
1301 },
1302 &args,
1303 )
1304}
1305
1306fn expand_transform_map_4arg(
1307 ctx: &mut MacroContext,
1308 span: Span,
1309 receiver: Option<SpannedExpr>,
1310 args: Vec<SpannedExpr>,
1311) -> MacroExpansion {
1312 let receiver = match receiver {
1313 Some(r) => r,
1314 None => return MacroExpansion::Error("transformMap() requires a receiver".to_string()),
1315 };
1316
1317 let iter_var = match extract_iter_var(ctx, &args[0]) {
1318 Some(v) => v,
1319 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1320 };
1321 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1322 Some(v) => v,
1323 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1324 };
1325 let filter = args[2].clone();
1326 let transform = args[3].clone();
1327
1328 expand_transform_map_impl(
1329 ctx,
1330 span,
1331 receiver,
1332 TransformParams {
1333 iter_var,
1334 iter_var2,
1335 filter_cond: Some(filter),
1336 transform,
1337 },
1338 &args,
1339 )
1340}
1341
1342fn expand_transform_map_impl(
1343 ctx: &mut MacroContext,
1344 span: Span,
1345 receiver: SpannedExpr,
1346 params: TransformParams,
1347 args: &[SpannedExpr],
1348) -> MacroExpansion {
1349 let call_id = ctx.next_id();
1350 ctx.store_macro_call(call_id, &span, &receiver, "transformMap", args);
1351
1352 let accu_var = ACCU_VAR.to_string();
1353 let accu_init = synthetic(ctx, Expr::Map(vec![]), span.clone());
1354 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1355
1356 let key_ref = synthetic(ctx, Expr::Ident(params.iter_var.clone()), span.clone());
1357 let transformed_map = synthetic(
1358 ctx,
1359 Expr::Map(vec![MapEntry {
1360 key: key_ref,
1361 value: params.transform,
1362 optional: false,
1363 }]),
1364 span.clone(),
1365 );
1366 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1367 let append_step = synthetic(
1368 ctx,
1369 Expr::Binary {
1370 op: BinaryOp::Add,
1371 left: Box::new(accu_ref_step),
1372 right: Box::new(transformed_map),
1373 },
1374 span.clone(),
1375 );
1376
1377 let loop_step = if let Some(filter) = params.filter_cond {
1378 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1379 synthetic(
1380 ctx,
1381 Expr::Ternary {
1382 cond: Box::new(filter),
1383 then_expr: Box::new(append_step),
1384 else_expr: Box::new(accu_ref_else),
1385 },
1386 span.clone(),
1387 )
1388 } else {
1389 append_step
1390 };
1391
1392 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1393
1394 MacroExpansion::Expanded(Spanned::new(
1395 call_id,
1396 Expr::Comprehension(ComprehensionData {
1397 iter_var: params.iter_var,
1398 iter_var2: params.iter_var2,
1399 iter_range: Box::new(receiver),
1400 accu_var,
1401 accu_init: Box::new(accu_init),
1402 loop_condition: Box::new(loop_condition),
1403 loop_step: Box::new(loop_step),
1404 result: Box::new(result),
1405 }),
1406 span,
1407 ))
1408}
1409
1410fn expand_bind(
1420 ctx: &mut MacroContext,
1421 span: Span,
1422 _receiver: Option<SpannedExpr>,
1423 args: Vec<SpannedExpr>,
1424) -> MacroExpansion {
1425 if args.len() != 3 {
1426 return MacroExpansion::Error(format!(
1427 "cel.bind() requires 3 arguments, got {}",
1428 args.len()
1429 ));
1430 }
1431
1432 let var_name = match &args[0].node {
1434 Expr::Ident(name) => name.clone(),
1435 _ => {
1436 return MacroExpansion::Error(
1437 "cel.bind() first argument must be an identifier".to_string(),
1438 )
1439 }
1440 };
1441
1442 let init = args[1].clone();
1444
1445 let body = args[2].clone();
1447
1448 let call_id = ctx.next_id();
1449 ctx.store_macro_call(call_id, &span, &args[0], "cel.bind", &args);
1450
1451 MacroExpansion::Expanded(Spanned::new(
1452 call_id,
1453 Expr::Bind {
1454 var_name,
1455 init: Box::new(init),
1456 body: Box::new(body),
1457 },
1458 span,
1459 ))
1460}
1461
1462fn build_method_call(
1466 ctx: &mut MacroContext,
1467 span: &Span,
1468 receiver: SpannedExpr,
1469 method: &str,
1470 args: Vec<SpannedExpr>,
1471) -> SpannedExpr {
1472 let member = synthetic(
1473 ctx,
1474 Expr::Member {
1475 expr: Box::new(receiver),
1476 field: method.to_string(),
1477 optional: false,
1478 },
1479 span.clone(),
1480 );
1481
1482 synthetic(
1483 ctx,
1484 Expr::Call {
1485 expr: Box::new(member),
1486 args,
1487 },
1488 span.clone(),
1489 )
1490}
1491
1492fn build_optional_of(ctx: &mut MacroContext, span: &Span, expr: SpannedExpr) -> SpannedExpr {
1494 let optional_ident = synthetic(ctx, Expr::Ident("optional".to_string()), span.clone());
1495 let optional_of_member = synthetic(
1496 ctx,
1497 Expr::Member {
1498 expr: Box::new(optional_ident),
1499 field: "of".to_string(),
1500 optional: false,
1501 },
1502 span.clone(),
1503 );
1504
1505 synthetic(
1506 ctx,
1507 Expr::Call {
1508 expr: Box::new(optional_of_member),
1509 args: vec![expr],
1510 },
1511 span.clone(),
1512 )
1513}
1514
1515fn build_optional_none(ctx: &mut MacroContext, span: &Span) -> SpannedExpr {
1517 let optional_ident = synthetic(ctx, Expr::Ident("optional".to_string()), span.clone());
1518 let optional_none_member = synthetic(
1519 ctx,
1520 Expr::Member {
1521 expr: Box::new(optional_ident),
1522 field: "none".to_string(),
1523 optional: false,
1524 },
1525 span.clone(),
1526 );
1527
1528 synthetic(
1529 ctx,
1530 Expr::Call {
1531 expr: Box::new(optional_none_member),
1532 args: vec![],
1533 },
1534 span.clone(),
1535 )
1536}
1537
1538fn expand_opt_map(
1547 ctx: &mut MacroContext,
1548 span: Span,
1549 receiver: Option<SpannedExpr>,
1550 args: Vec<SpannedExpr>,
1551) -> MacroExpansion {
1552 let receiver = match receiver {
1553 Some(r) => r,
1554 None => return MacroExpansion::Error("optMap() requires a receiver".to_string()),
1555 };
1556
1557 let iter_var = match extract_iter_var(ctx, &args[0]) {
1558 Some(v) => v,
1559 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1560 };
1561 let transform = args[1].clone();
1562
1563 let call_id = ctx.next_id();
1564 ctx.store_macro_call(call_id, &span, &receiver, "optMap", &args);
1565
1566 let has_value_call = build_method_call(ctx, &span, receiver.clone(), "hasValue", vec![]);
1568
1569 let value_call = build_method_call(ctx, &span, receiver, "value", vec![]);
1571
1572 let wrapped_result = build_optional_of(ctx, &span, transform);
1574
1575 let bind_expr = synthetic(
1577 ctx,
1578 Expr::Bind {
1579 var_name: iter_var,
1580 init: Box::new(value_call),
1581 body: Box::new(wrapped_result),
1582 },
1583 span.clone(),
1584 );
1585
1586 let none_call = build_optional_none(ctx, &span);
1588
1589 MacroExpansion::Expanded(Spanned::new(
1591 call_id,
1592 Expr::Ternary {
1593 cond: Box::new(has_value_call),
1594 then_expr: Box::new(bind_expr),
1595 else_expr: Box::new(none_call),
1596 },
1597 span,
1598 ))
1599}
1600
1601fn expand_opt_flat_map(
1613 ctx: &mut MacroContext,
1614 span: Span,
1615 receiver: Option<SpannedExpr>,
1616 args: Vec<SpannedExpr>,
1617) -> MacroExpansion {
1618 let receiver = match receiver {
1619 Some(r) => r,
1620 None => return MacroExpansion::Error("optFlatMap() requires a receiver".to_string()),
1621 };
1622
1623 let iter_var = match extract_iter_var(ctx, &args[0]) {
1624 Some(v) => v,
1625 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1626 };
1627 let transform = args[1].clone();
1628
1629 let call_id = ctx.next_id();
1630 ctx.store_macro_call(call_id, &span, &receiver, "optFlatMap", &args);
1631
1632 let has_value_call = build_method_call(ctx, &span, receiver.clone(), "hasValue", vec![]);
1634
1635 let value_call = build_method_call(ctx, &span, receiver, "value", vec![]);
1637
1638 let bind_expr = synthetic(
1641 ctx,
1642 Expr::Bind {
1643 var_name: iter_var,
1644 init: Box::new(value_call),
1645 body: Box::new(transform),
1646 },
1647 span.clone(),
1648 );
1649
1650 let none_call = build_optional_none(ctx, &span);
1652
1653 MacroExpansion::Expanded(Spanned::new(
1655 call_id,
1656 Expr::Ternary {
1657 cond: Box::new(has_value_call),
1658 then_expr: Box::new(bind_expr),
1659 else_expr: Box::new(none_call),
1660 },
1661 span,
1662 ))
1663}
1664
1665fn validate_qualified_identifier(expr: &SpannedExpr) -> Option<String> {
1675 match &expr.node {
1676 Expr::Ident(name) => Some(name.clone()),
1677 Expr::Member { expr, field, .. } => {
1678 let prefix = validate_qualified_identifier(expr)?;
1679 Some(format!("{}.{}", prefix, field))
1680 }
1681 _ => None,
1682 }
1683}
1684
1685fn expand_proto_get_ext(
1687 ctx: &mut MacroContext,
1688 span: Span,
1689 _receiver: Option<SpannedExpr>,
1690 args: Vec<SpannedExpr>,
1691) -> MacroExpansion {
1692 if args.len() != 2 {
1693 return MacroExpansion::Error(format!(
1694 "proto.getExt() requires 2 arguments, got {}",
1695 args.len()
1696 ));
1697 }
1698
1699 let ext_name = match validate_qualified_identifier(&args[1]) {
1700 Some(name) => name,
1701 None => return MacroExpansion::Error(
1702 "proto.getExt() second argument must be a qualified identifier (e.g., pkg.ExtField)"
1703 .to_string(),
1704 ),
1705 };
1706
1707 let msg = args[0].clone();
1708 let call_id = ctx.next_id();
1709 ctx.store_macro_call(call_id, &span, &msg, "proto.getExt", &args);
1710
1711 MacroExpansion::Expanded(Spanned::new(
1712 call_id,
1713 Expr::Member {
1714 expr: Box::new(msg),
1715 field: ext_name,
1716 optional: false,
1717 },
1718 span,
1719 ))
1720}
1721
1722fn expand_proto_has_ext(
1724 ctx: &mut MacroContext,
1725 span: Span,
1726 _receiver: Option<SpannedExpr>,
1727 args: Vec<SpannedExpr>,
1728) -> MacroExpansion {
1729 if args.len() != 2 {
1730 return MacroExpansion::Error(format!(
1731 "proto.hasExt() requires 2 arguments, got {}",
1732 args.len()
1733 ));
1734 }
1735
1736 let ext_name = match validate_qualified_identifier(&args[1]) {
1737 Some(name) => name,
1738 None => return MacroExpansion::Error(
1739 "proto.hasExt() second argument must be a qualified identifier (e.g., pkg.ExtField)"
1740 .to_string(),
1741 ),
1742 };
1743
1744 let msg = args[0].clone();
1745 let call_id = ctx.next_id();
1746 ctx.store_macro_call(call_id, &span, &msg, "proto.hasExt", &args);
1747
1748 MacroExpansion::Expanded(Spanned::new(
1749 call_id,
1750 Expr::MemberTestOnly {
1751 expr: Box::new(msg),
1752 field: ext_name,
1753 },
1754 span,
1755 ))
1756}
1757
1758fn expand_cel_block(
1777 ctx: &mut MacroContext,
1778 span: Span,
1779 _receiver: Option<SpannedExpr>,
1780 args: Vec<SpannedExpr>,
1781) -> MacroExpansion {
1782 if args.len() != 2 {
1783 return MacroExpansion::Error(format!(
1784 "cel.block() requires 2 arguments, got {}",
1785 args.len()
1786 ));
1787 }
1788
1789 let slots = match &args[0].node {
1790 Expr::List(elements) => {
1791 if elements.is_empty() {
1792 return MacroExpansion::Error(
1793 "cel.block() first argument must be a non-empty list".to_string(),
1794 );
1795 }
1796 elements.clone()
1797 }
1798 _ => {
1799 return MacroExpansion::Error(
1800 "cel.block() first argument must be a list literal".to_string(),
1801 )
1802 }
1803 };
1804
1805 let result = args[1].clone();
1806 let call_id = ctx.next_id();
1807 ctx.store_macro_call(call_id, &span, &args[0], "cel.block", &args);
1808
1809 let mut body = result;
1811 for (i, slot) in slots.into_iter().enumerate().rev() {
1812 let var_name = format!("@index{}", i);
1813 body = synthetic(
1814 ctx,
1815 Expr::Bind {
1816 var_name,
1817 init: Box::new(slot.expr),
1818 body: Box::new(body),
1819 },
1820 span.clone(),
1821 );
1822 }
1823
1824 body.id = call_id;
1826 MacroExpansion::Expanded(body)
1827}
1828
1829fn extract_int_literal(expr: &SpannedExpr) -> Option<i64> {
1831 match &expr.node {
1832 Expr::Int(n) => Some(*n),
1833 _ => None,
1834 }
1835}
1836
1837fn expand_cel_index(
1839 ctx: &mut MacroContext,
1840 span: Span,
1841 _receiver: Option<SpannedExpr>,
1842 args: Vec<SpannedExpr>,
1843) -> MacroExpansion {
1844 if args.len() != 1 {
1845 return MacroExpansion::Error(format!(
1846 "cel.index() requires 1 argument, got {}",
1847 args.len()
1848 ));
1849 }
1850
1851 let index = match extract_int_literal(&args[0]) {
1852 Some(n) => n,
1853 None => {
1854 return MacroExpansion::Error(
1855 "cel.index() argument must be an integer literal".to_string(),
1856 )
1857 }
1858 };
1859
1860 let call_id = ctx.next_id();
1861 ctx.store_macro_call(call_id, &span, &args[0], "cel.index", &args);
1862
1863 MacroExpansion::Expanded(Spanned::new(
1864 call_id,
1865 Expr::Ident(format!("@index{}", index)),
1866 span,
1867 ))
1868}
1869
1870fn expand_cel_iter_var(
1872 ctx: &mut MacroContext,
1873 span: Span,
1874 _receiver: Option<SpannedExpr>,
1875 args: Vec<SpannedExpr>,
1876) -> MacroExpansion {
1877 if args.len() != 2 {
1878 return MacroExpansion::Error(format!(
1879 "cel.iterVar() requires 2 arguments, got {}",
1880 args.len()
1881 ));
1882 }
1883
1884 let n = match extract_int_literal(&args[0]) {
1885 Some(n) => n,
1886 None => {
1887 return MacroExpansion::Error(
1888 "cel.iterVar() first argument must be an integer literal".to_string(),
1889 )
1890 }
1891 };
1892 let m = match extract_int_literal(&args[1]) {
1893 Some(m) => m,
1894 None => {
1895 return MacroExpansion::Error(
1896 "cel.iterVar() second argument must be an integer literal".to_string(),
1897 )
1898 }
1899 };
1900
1901 let call_id = ctx.next_id();
1902 ctx.store_macro_call(call_id, &span, &args[0], "cel.iterVar", &args);
1903
1904 MacroExpansion::Expanded(Spanned::new(
1905 call_id,
1906 Expr::Ident(format!("@it:{}:{}", n, m)),
1907 span,
1908 ))
1909}
1910
1911fn expand_cel_accu_var(
1913 ctx: &mut MacroContext,
1914 span: Span,
1915 _receiver: Option<SpannedExpr>,
1916 args: Vec<SpannedExpr>,
1917) -> MacroExpansion {
1918 if args.len() != 2 {
1919 return MacroExpansion::Error(format!(
1920 "cel.accuVar() requires 2 arguments, got {}",
1921 args.len()
1922 ));
1923 }
1924
1925 let n = match extract_int_literal(&args[0]) {
1926 Some(n) => n,
1927 None => {
1928 return MacroExpansion::Error(
1929 "cel.accuVar() first argument must be an integer literal".to_string(),
1930 )
1931 }
1932 };
1933 let m = match extract_int_literal(&args[1]) {
1934 Some(m) => m,
1935 None => {
1936 return MacroExpansion::Error(
1937 "cel.accuVar() second argument must be an integer literal".to_string(),
1938 )
1939 }
1940 };
1941
1942 let call_id = ctx.next_id();
1943 ctx.store_macro_call(call_id, &span, &args[0], "cel.accuVar", &args);
1944
1945 MacroExpansion::Expanded(Spanned::new(
1946 call_id,
1947 Expr::Ident(format!("@ac:{}:{}", n, m)),
1948 span,
1949 ))
1950}
1951
1952#[cfg(test)]
1953mod tests {
1954 use super::*;
1955
1956 fn dummy_expander(
1957 _ctx: &mut MacroContext,
1958 _span: Span,
1959 _receiver: Option<SpannedExpr>,
1960 _args: Vec<SpannedExpr>,
1961 ) -> MacroExpansion {
1962 MacroExpansion::Error("dummy".to_string())
1963 }
1964
1965 #[test]
1966 fn test_arg_count_exact() {
1967 let exact = ArgCount::Exact(2);
1968 assert!(exact.matches(2));
1969 assert!(!exact.matches(1));
1970 assert!(!exact.matches(3));
1971 assert_eq!(exact.count(), 2);
1972 assert!(!exact.is_vararg());
1973 }
1974
1975 #[test]
1976 fn test_arg_count_vararg() {
1977 let vararg = ArgCount::VarArg(2);
1978 assert!(vararg.matches(2));
1979 assert!(vararg.matches(3));
1980 assert!(vararg.matches(10));
1981 assert!(!vararg.matches(1));
1982 assert_eq!(vararg.count(), 2);
1983 assert!(vararg.is_vararg());
1984 }
1985
1986 #[test]
1987 fn test_macro_key() {
1988 let m = Macro::new(
1989 "all",
1990 MacroStyle::Receiver,
1991 ArgCount::Exact(2),
1992 dummy_expander,
1993 );
1994 assert_eq!(m.key(), "all:2:true");
1995
1996 let m2 = Macro::new(
1997 "has",
1998 MacroStyle::Global,
1999 ArgCount::Exact(1),
2000 dummy_expander,
2001 );
2002 assert_eq!(m2.key(), "has:1:false");
2003 }
2004
2005 #[test]
2006 fn test_registry_lookup_exact() {
2007 let mut registry = MacroRegistry::new();
2008 registry.register(Macro::new(
2009 "all",
2010 MacroStyle::Receiver,
2011 ArgCount::Exact(2),
2012 dummy_expander,
2013 ));
2014 registry.register(Macro::new(
2015 "all",
2016 MacroStyle::Receiver,
2017 ArgCount::Exact(3),
2018 dummy_expander,
2019 ));
2020
2021 assert!(registry.lookup("all", 2, true).is_some());
2022 assert!(registry.lookup("all", 3, true).is_some());
2023 assert!(registry.lookup("all", 4, true).is_none());
2024 assert!(registry.lookup("all", 2, false).is_none());
2025 }
2026
2027 #[test]
2028 fn test_registry_lookup_vararg() {
2029 let mut registry = MacroRegistry::new();
2030 registry.register(Macro::new(
2031 "custom",
2032 MacroStyle::Receiver,
2033 ArgCount::VarArg(2),
2034 dummy_expander,
2035 ));
2036
2037 assert!(registry.lookup("custom", 2, true).is_some());
2038 assert!(registry.lookup("custom", 3, true).is_some());
2039 assert!(registry.lookup("custom", 10, true).is_some());
2040 assert!(registry.lookup("custom", 1, true).is_none());
2041 }
2042
2043 #[test]
2044 fn test_registry_standard() {
2045 let registry = MacroRegistry::standard();
2046
2047 assert!(registry.lookup("has", 1, false).is_some());
2048 assert!(registry.lookup("all", 2, true).is_some());
2049 assert!(registry.lookup("all", 3, true).is_some());
2050 assert!(registry.lookup("exists", 2, true).is_some());
2051 assert!(registry.lookup("exists", 3, true).is_some());
2052 assert!(registry.lookup("exists_one", 2, true).is_some());
2053 assert!(registry.lookup("exists_one", 3, true).is_some());
2054 assert!(registry.lookup("map", 2, true).is_some());
2055 assert!(registry.lookup("map", 3, true).is_some());
2056 assert!(registry.lookup("filter", 2, true).is_some());
2057 }
2058
2059 #[test]
2060 fn test_registry_contains() {
2061 let registry = MacroRegistry::standard();
2062 assert!(registry.contains("has"));
2063 assert!(registry.contains("all"));
2064 assert!(registry.contains("cel.bind"));
2065 assert!(!registry.contains("nonexistent"));
2066 }
2067
2068 #[test]
2069 fn test_cel_bind_macro_registered() {
2070 let registry = MacroRegistry::standard();
2071 assert!(registry.lookup("cel.bind", 3, false).is_some());
2072 assert!(registry.lookup("cel.bind", 3, true).is_none());
2074 }
2075
2076 #[test]
2077 fn test_opt_map_macro_registered() {
2078 let registry = MacroRegistry::standard();
2079 assert!(registry.lookup("optMap", 2, true).is_some());
2081 assert!(registry.lookup("optMap", 2, false).is_none());
2083 assert!(registry.lookup("optMap", 1, true).is_none());
2085 assert!(registry.lookup("optMap", 3, true).is_none());
2086 }
2087
2088 #[test]
2089 fn test_opt_flat_map_macro_registered() {
2090 let registry = MacroRegistry::standard();
2091 assert!(registry.lookup("optFlatMap", 2, true).is_some());
2093 assert!(registry.lookup("optFlatMap", 2, false).is_none());
2095 assert!(registry.lookup("optFlatMap", 1, true).is_none());
2097 assert!(registry.lookup("optFlatMap", 3, true).is_none());
2098 }
2099
2100 #[test]
2101 fn test_registry_contains_opt_macros() {
2102 let registry = MacroRegistry::standard();
2103 assert!(registry.contains("optMap"));
2104 assert!(registry.contains("optFlatMap"));
2105 }
2106
2107 #[test]
2108 fn test_proto_has_ext_macro_registered() {
2109 let registry = MacroRegistry::standard();
2110 assert!(registry.lookup("proto.hasExt", 2, false).is_some());
2111 assert!(registry.lookup("proto.hasExt", 2, true).is_none());
2112 assert!(registry.lookup("proto.hasExt", 1, false).is_none());
2113 }
2114
2115 #[test]
2116 fn test_proto_get_ext_macro_registered() {
2117 let registry = MacroRegistry::standard();
2118 assert!(registry.lookup("proto.getExt", 2, false).is_some());
2119 assert!(registry.lookup("proto.getExt", 2, true).is_none());
2120 assert!(registry.lookup("proto.getExt", 1, false).is_none());
2121 }
2122
2123 #[test]
2124 fn test_validate_qualified_identifier() {
2125 let ident = Spanned::new(1, Expr::Ident("a".to_string()), 0..1);
2127 assert_eq!(validate_qualified_identifier(&ident), Some("a".to_string()));
2128
2129 let dotted = Spanned::new(
2131 2,
2132 Expr::Member {
2133 expr: Box::new(Spanned::new(1, Expr::Ident("a".to_string()), 0..1)),
2134 field: "b".to_string(),
2135 optional: false,
2136 },
2137 0..3,
2138 );
2139 assert_eq!(
2140 validate_qualified_identifier(&dotted),
2141 Some("a.b".to_string())
2142 );
2143
2144 let deep = Spanned::new(
2146 4,
2147 Expr::Member {
2148 expr: Box::new(Spanned::new(
2149 3,
2150 Expr::Member {
2151 expr: Box::new(Spanned::new(
2152 2,
2153 Expr::Member {
2154 expr: Box::new(Spanned::new(1, Expr::Ident("a".to_string()), 0..1)),
2155 field: "b".to_string(),
2156 optional: false,
2157 },
2158 0..3,
2159 )),
2160 field: "c".to_string(),
2161 optional: false,
2162 },
2163 0..5,
2164 )),
2165 field: "d".to_string(),
2166 optional: false,
2167 },
2168 0..7,
2169 );
2170 assert_eq!(
2171 validate_qualified_identifier(&deep),
2172 Some("a.b.c.d".to_string())
2173 );
2174
2175 let non_ident = Spanned::new(1, Expr::Int(42), 0..2);
2177 assert_eq!(validate_qualified_identifier(&non_ident), None);
2178 }
2179
2180 #[test]
2181 fn test_proto_get_ext_expansion() {
2182 let mut id = 10i64;
2183 let mut next_id = || -> i64 {
2184 id += 1;
2185 id
2186 };
2187 let mut ctx = MacroContext::new(&mut next_id, None);
2188
2189 let msg = Spanned::new(1, Expr::Ident("msg".to_string()), 0..3);
2190 let ext = Spanned::new(
2191 2,
2192 Expr::Member {
2193 expr: Box::new(Spanned::new(1, Expr::Ident("pkg".to_string()), 4..7)),
2194 field: "ExtField".to_string(),
2195 optional: false,
2196 },
2197 4..16,
2198 );
2199
2200 let result = expand_proto_get_ext(&mut ctx, 0..20, None, vec![msg, ext]);
2201 match result {
2202 MacroExpansion::Expanded(expr) => match &expr.node {
2203 Expr::Member {
2204 expr,
2205 field,
2206 optional,
2207 } => {
2208 assert_eq!(field, "pkg.ExtField");
2209 assert!(!optional);
2210 assert!(matches!(&expr.node, Expr::Ident(name) if name == "msg"));
2211 }
2212 other => panic!("expected Member, got {:?}", other),
2213 },
2214 MacroExpansion::Error(e) => panic!("unexpected error: {}", e),
2215 }
2216 }
2217
2218 #[test]
2219 fn test_proto_has_ext_expansion() {
2220 let mut id = 10i64;
2221 let mut next_id = || -> i64 {
2222 id += 1;
2223 id
2224 };
2225 let mut ctx = MacroContext::new(&mut next_id, None);
2226
2227 let msg = Spanned::new(1, Expr::Ident("msg".to_string()), 0..3);
2228 let ext = Spanned::new(
2229 2,
2230 Expr::Member {
2231 expr: Box::new(Spanned::new(1, Expr::Ident("pkg".to_string()), 4..7)),
2232 field: "ExtField".to_string(),
2233 optional: false,
2234 },
2235 4..16,
2236 );
2237
2238 let result = expand_proto_has_ext(&mut ctx, 0..20, None, vec![msg, ext]);
2239 match result {
2240 MacroExpansion::Expanded(expr) => match &expr.node {
2241 Expr::MemberTestOnly { expr, field } => {
2242 assert_eq!(field, "pkg.ExtField");
2243 assert!(matches!(&expr.node, Expr::Ident(name) if name == "msg"));
2244 }
2245 other => panic!("expected MemberTestOnly, got {:?}", other),
2246 },
2247 MacroExpansion::Error(e) => panic!("unexpected error: {}", e),
2248 }
2249 }
2250
2251 #[test]
2252 fn test_proto_ext_error_non_qualified() {
2253 let mut id = 10i64;
2254 let mut next_id = || -> i64 {
2255 id += 1;
2256 id
2257 };
2258 let mut ctx = MacroContext::new(&mut next_id, None);
2259
2260 let msg = Spanned::new(1, Expr::Ident("msg".to_string()), 0..3);
2261 let bad_arg = Spanned::new(2, Expr::Int(42), 4..6);
2262
2263 let result =
2264 expand_proto_get_ext(&mut ctx, 0..10, None, vec![msg.clone(), bad_arg.clone()]);
2265 assert!(matches!(result, MacroExpansion::Error(_)));
2266
2267 let result = expand_proto_has_ext(&mut ctx, 0..10, None, vec![msg, bad_arg]);
2268 assert!(matches!(result, MacroExpansion::Error(_)));
2269 }
2270}