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
324const ACCU_VAR: &str = "__result__";
330
331pub static STANDARD_MACROS: &[Macro] = &[
333 Macro::with_description(
335 "has",
336 MacroStyle::Global,
337 ArgCount::Exact(1),
338 expand_has,
339 "Tests whether a field is set on a message",
340 ),
341 Macro::with_description(
343 "all",
344 MacroStyle::Receiver,
345 ArgCount::Exact(2),
346 expand_all_2arg,
347 "Tests whether all elements satisfy a condition",
348 ),
349 Macro::with_description(
350 "all",
351 MacroStyle::Receiver,
352 ArgCount::Exact(3),
353 expand_all_3arg,
354 "Tests whether all elements satisfy a condition (two-variable form)",
355 ),
356 Macro::with_description(
358 "exists",
359 MacroStyle::Receiver,
360 ArgCount::Exact(2),
361 expand_exists_2arg,
362 "Tests whether any element satisfies a condition",
363 ),
364 Macro::with_description(
365 "exists",
366 MacroStyle::Receiver,
367 ArgCount::Exact(3),
368 expand_exists_3arg,
369 "Tests whether any element satisfies a condition (two-variable form)",
370 ),
371 Macro::with_description(
373 "exists_one",
374 MacroStyle::Receiver,
375 ArgCount::Exact(2),
376 expand_exists_one_2arg,
377 "Tests whether exactly one element satisfies a condition",
378 ),
379 Macro::with_description(
380 "exists_one",
381 MacroStyle::Receiver,
382 ArgCount::Exact(3),
383 expand_exists_one_3arg,
384 "Tests whether exactly one element satisfies a condition (two-variable form)",
385 ),
386 Macro::with_description(
388 "existsOne",
389 MacroStyle::Receiver,
390 ArgCount::Exact(2),
391 expand_exists_one_2arg,
392 "Tests whether exactly one element satisfies a condition",
393 ),
394 Macro::with_description(
395 "existsOne",
396 MacroStyle::Receiver,
397 ArgCount::Exact(3),
398 expand_exists_one_3arg,
399 "Tests whether exactly one element satisfies a condition (two-variable form)",
400 ),
401 Macro::with_description(
403 "map",
404 MacroStyle::Receiver,
405 ArgCount::Exact(2),
406 expand_map_2arg,
407 "Transforms elements of a list",
408 ),
409 Macro::with_description(
410 "map",
411 MacroStyle::Receiver,
412 ArgCount::Exact(3),
413 expand_map_3arg,
414 "Transforms elements of a list with filtering",
415 ),
416 Macro::with_description(
418 "filter",
419 MacroStyle::Receiver,
420 ArgCount::Exact(2),
421 expand_filter,
422 "Filters elements of a list by a condition",
423 ),
424 Macro::with_description(
426 "transformList",
427 MacroStyle::Receiver,
428 ArgCount::Exact(3),
429 expand_transform_list_3arg,
430 "Transforms list elements with index and value variables",
431 ),
432 Macro::with_description(
433 "transformList",
434 MacroStyle::Receiver,
435 ArgCount::Exact(4),
436 expand_transform_list_4arg,
437 "Transforms list elements with index, value, and filter",
438 ),
439 Macro::with_description(
441 "transformMap",
442 MacroStyle::Receiver,
443 ArgCount::Exact(3),
444 expand_transform_map_3arg,
445 "Transforms map entries with key and value variables",
446 ),
447 Macro::with_description(
448 "transformMap",
449 MacroStyle::Receiver,
450 ArgCount::Exact(4),
451 expand_transform_map_4arg,
452 "Transforms map entries with key, value, and filter",
453 ),
454 Macro::with_description(
456 "cel.bind",
457 MacroStyle::Global,
458 ArgCount::Exact(3),
459 expand_bind,
460 "Binds a variable to a value for use in an expression",
461 ),
462 Macro::with_description(
464 "optMap",
465 MacroStyle::Receiver,
466 ArgCount::Exact(2),
467 expand_opt_map,
468 "Transforms an optional value if present",
469 ),
470 Macro::with_description(
472 "optFlatMap",
473 MacroStyle::Receiver,
474 ArgCount::Exact(2),
475 expand_opt_flat_map,
476 "Chains optional operations",
477 ),
478 Macro::with_description(
480 "proto.hasExt",
481 MacroStyle::Global,
482 ArgCount::Exact(2),
483 expand_proto_has_ext,
484 "Tests whether a proto extension field is set",
485 ),
486 Macro::with_description(
488 "proto.getExt",
489 MacroStyle::Global,
490 ArgCount::Exact(2),
491 expand_proto_get_ext,
492 "Gets the value of a proto extension field",
493 ),
494 Macro::with_description(
496 "cel.block",
497 MacroStyle::Global,
498 ArgCount::Exact(2),
499 expand_cel_block,
500 "Binds a list of slot expressions for common subexpression elimination",
501 ),
502 Macro::with_description(
504 "cel.index",
505 MacroStyle::Global,
506 ArgCount::Exact(1),
507 expand_cel_index,
508 "References a slot variable by index in a cel.block",
509 ),
510 Macro::with_description(
512 "cel.iterVar",
513 MacroStyle::Global,
514 ArgCount::Exact(2),
515 expand_cel_iter_var,
516 "References an iterator variable for nested comprehensions",
517 ),
518 Macro::with_description(
520 "cel.accuVar",
521 MacroStyle::Global,
522 ArgCount::Exact(2),
523 expand_cel_accu_var,
524 "References an accumulator variable for nested comprehensions",
525 ),
526];
527
528fn synthetic(ctx: &mut MacroContext, node: Expr, span: Span) -> SpannedExpr {
532 Spanned::new(ctx.next_id(), node, span)
533}
534
535fn extract_iter_var(ctx: &mut MacroContext, expr: &SpannedExpr) -> Option<String> {
538 match &expr.node {
539 Expr::Ident(name) => Some(name.clone()),
540 _ => {
541 ctx.add_error(
542 "iteration variable must be an identifier".to_string(),
543 expr.span.clone(),
544 );
545 None
546 }
547 }
548}
549
550fn expand_has(
554 ctx: &mut MacroContext,
555 span: Span,
556 _receiver: Option<SpannedExpr>,
557 args: Vec<SpannedExpr>,
558) -> MacroExpansion {
559 if args.len() != 1 {
560 return MacroExpansion::Error(format!("has() requires 1 argument, got {}", args.len()));
561 }
562
563 let arg = args.into_iter().next().unwrap();
564
565 match arg.node {
566 Expr::Member { expr, field, .. } => {
567 let result = Spanned::new(ctx.next_id(), Expr::MemberTestOnly { expr, field }, span);
568 MacroExpansion::Expanded(result)
569 }
570 _ => MacroExpansion::Error(
571 "has() argument must be a field selection (e.g., has(m.x))".to_string(),
572 ),
573 }
574}
575
576fn expand_all_2arg(
579 ctx: &mut MacroContext,
580 span: Span,
581 receiver: Option<SpannedExpr>,
582 args: Vec<SpannedExpr>,
583) -> MacroExpansion {
584 let receiver = match receiver {
585 Some(r) => r,
586 None => return MacroExpansion::Error("all() requires a receiver".to_string()),
587 };
588
589 let iter_var = match extract_iter_var(ctx, &args[0]) {
590 Some(v) => v,
591 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
592 };
593 let cond = args[1].clone();
594
595 expand_all_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
596}
597
598fn expand_all_3arg(
599 ctx: &mut MacroContext,
600 span: Span,
601 receiver: Option<SpannedExpr>,
602 args: Vec<SpannedExpr>,
603) -> MacroExpansion {
604 let receiver = match receiver {
605 Some(r) => r,
606 None => return MacroExpansion::Error("all() requires a receiver".to_string()),
607 };
608
609 let iter_var = match extract_iter_var(ctx, &args[0]) {
610 Some(v) => v,
611 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
612 };
613 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
614 Some(v) => v,
615 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
616 };
617 let cond = args[2].clone();
618
619 expand_all_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
620}
621
622fn expand_all_impl(
623 ctx: &mut MacroContext,
624 span: Span,
625 receiver: SpannedExpr,
626 iter_var: String,
627 iter_var2: String,
628 cond: SpannedExpr,
629 args: &[SpannedExpr],
630) -> MacroExpansion {
631 let call_id = ctx.next_id();
632 ctx.store_macro_call(call_id, &span, &receiver, "all", args);
633
634 let accu_var = ACCU_VAR.to_string();
635 let accu_init = synthetic(ctx, Expr::Bool(true), span.clone());
636 let loop_condition = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
637
638 let accu_ref = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
639 let loop_step = synthetic(
640 ctx,
641 Expr::Binary {
642 op: BinaryOp::And,
643 left: Box::new(cond),
644 right: Box::new(accu_ref),
645 },
646 span.clone(),
647 );
648
649 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
650
651 MacroExpansion::Expanded(Spanned::new(
652 call_id,
653 Expr::Comprehension(ComprehensionData {
654 iter_var,
655 iter_var2,
656 iter_range: Box::new(receiver),
657 accu_var,
658 accu_init: Box::new(accu_init),
659 loop_condition: Box::new(loop_condition),
660 loop_step: Box::new(loop_step),
661 result: Box::new(result),
662 }),
663 span,
664 ))
665}
666
667fn expand_exists_2arg(
670 ctx: &mut MacroContext,
671 span: Span,
672 receiver: Option<SpannedExpr>,
673 args: Vec<SpannedExpr>,
674) -> MacroExpansion {
675 let receiver = match receiver {
676 Some(r) => r,
677 None => return MacroExpansion::Error("exists() requires a receiver".to_string()),
678 };
679
680 let iter_var = match extract_iter_var(ctx, &args[0]) {
681 Some(v) => v,
682 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
683 };
684 let cond = args[1].clone();
685
686 expand_exists_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
687}
688
689fn expand_exists_3arg(
690 ctx: &mut MacroContext,
691 span: Span,
692 receiver: Option<SpannedExpr>,
693 args: Vec<SpannedExpr>,
694) -> MacroExpansion {
695 let receiver = match receiver {
696 Some(r) => r,
697 None => return MacroExpansion::Error("exists() requires a receiver".to_string()),
698 };
699
700 let iter_var = match extract_iter_var(ctx, &args[0]) {
701 Some(v) => v,
702 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
703 };
704 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
705 Some(v) => v,
706 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
707 };
708 let cond = args[2].clone();
709
710 expand_exists_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
711}
712
713fn expand_exists_impl(
714 ctx: &mut MacroContext,
715 span: Span,
716 receiver: SpannedExpr,
717 iter_var: String,
718 iter_var2: String,
719 cond: SpannedExpr,
720 args: &[SpannedExpr],
721) -> MacroExpansion {
722 let call_id = ctx.next_id();
723 ctx.store_macro_call(call_id, &span, &receiver, "exists", args);
724
725 let accu_var = ACCU_VAR.to_string();
726 let accu_init = synthetic(ctx, Expr::Bool(false), span.clone());
727
728 let accu_ref_cond = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
729 let loop_condition = synthetic(
730 ctx,
731 Expr::Unary {
732 op: UnaryOp::Not,
733 expr: Box::new(accu_ref_cond),
734 },
735 span.clone(),
736 );
737
738 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
739 let loop_step = synthetic(
740 ctx,
741 Expr::Binary {
742 op: BinaryOp::Or,
743 left: Box::new(cond),
744 right: Box::new(accu_ref_step),
745 },
746 span.clone(),
747 );
748
749 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
750
751 MacroExpansion::Expanded(Spanned::new(
752 call_id,
753 Expr::Comprehension(ComprehensionData {
754 iter_var,
755 iter_var2,
756 iter_range: Box::new(receiver),
757 accu_var,
758 accu_init: Box::new(accu_init),
759 loop_condition: Box::new(loop_condition),
760 loop_step: Box::new(loop_step),
761 result: Box::new(result),
762 }),
763 span,
764 ))
765}
766
767fn expand_exists_one_2arg(
770 ctx: &mut MacroContext,
771 span: Span,
772 receiver: Option<SpannedExpr>,
773 args: Vec<SpannedExpr>,
774) -> MacroExpansion {
775 let receiver = match receiver {
776 Some(r) => r,
777 None => return MacroExpansion::Error("exists_one() requires a receiver".to_string()),
778 };
779
780 let iter_var = match extract_iter_var(ctx, &args[0]) {
781 Some(v) => v,
782 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
783 };
784 let cond = args[1].clone();
785
786 expand_exists_one_impl(ctx, span, receiver, iter_var, String::new(), cond, &args)
787}
788
789fn expand_exists_one_3arg(
790 ctx: &mut MacroContext,
791 span: Span,
792 receiver: Option<SpannedExpr>,
793 args: Vec<SpannedExpr>,
794) -> MacroExpansion {
795 let receiver = match receiver {
796 Some(r) => r,
797 None => return MacroExpansion::Error("exists_one() requires a receiver".to_string()),
798 };
799
800 let iter_var = match extract_iter_var(ctx, &args[0]) {
801 Some(v) => v,
802 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
803 };
804 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
805 Some(v) => v,
806 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
807 };
808 let cond = args[2].clone();
809
810 expand_exists_one_impl(ctx, span, receiver, iter_var, iter_var2, cond, &args)
811}
812
813fn expand_exists_one_impl(
814 ctx: &mut MacroContext,
815 span: Span,
816 receiver: SpannedExpr,
817 iter_var: String,
818 iter_var2: String,
819 cond: SpannedExpr,
820 args: &[SpannedExpr],
821) -> MacroExpansion {
822 let call_id = ctx.next_id();
823 ctx.store_macro_call(call_id, &span, &receiver, "exists_one", args);
824
825 let accu_var = ACCU_VAR.to_string();
826 let accu_init = synthetic(ctx, Expr::Int(0), span.clone());
827
828 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
829
830 let accu_ref_then = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
831 let one_step = synthetic(ctx, Expr::Int(1), span.clone());
832 let increment = synthetic(
833 ctx,
834 Expr::Binary {
835 op: BinaryOp::Add,
836 left: Box::new(accu_ref_then),
837 right: Box::new(one_step),
838 },
839 span.clone(),
840 );
841 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
842 let loop_step = synthetic(
843 ctx,
844 Expr::Ternary {
845 cond: Box::new(cond),
846 then_expr: Box::new(increment),
847 else_expr: Box::new(accu_ref_else),
848 },
849 span.clone(),
850 );
851
852 let accu_ref_result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
853 let one_result = synthetic(ctx, Expr::Int(1), span.clone());
854 let result = synthetic(
855 ctx,
856 Expr::Binary {
857 op: BinaryOp::Eq,
858 left: Box::new(accu_ref_result),
859 right: Box::new(one_result),
860 },
861 span.clone(),
862 );
863
864 MacroExpansion::Expanded(Spanned::new(
865 call_id,
866 Expr::Comprehension(ComprehensionData {
867 iter_var,
868 iter_var2,
869 iter_range: Box::new(receiver),
870 accu_var,
871 accu_init: Box::new(accu_init),
872 loop_condition: Box::new(loop_condition),
873 loop_step: Box::new(loop_step),
874 result: Box::new(result),
875 }),
876 span,
877 ))
878}
879
880fn expand_map_2arg(
883 ctx: &mut MacroContext,
884 span: Span,
885 receiver: Option<SpannedExpr>,
886 args: Vec<SpannedExpr>,
887) -> MacroExpansion {
888 let receiver = match receiver {
889 Some(r) => r,
890 None => return MacroExpansion::Error("map() requires a receiver".to_string()),
891 };
892
893 let iter_var = match extract_iter_var(ctx, &args[0]) {
894 Some(v) => v,
895 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
896 };
897 let transform = args[1].clone();
898
899 expand_map_impl(ctx, span, receiver, iter_var, None, transform, &args)
900}
901
902fn expand_map_3arg(
903 ctx: &mut MacroContext,
904 span: Span,
905 receiver: Option<SpannedExpr>,
906 args: Vec<SpannedExpr>,
907) -> MacroExpansion {
908 let receiver = match receiver {
909 Some(r) => r,
910 None => return MacroExpansion::Error("map() requires a receiver".to_string()),
911 };
912
913 let iter_var = match extract_iter_var(ctx, &args[0]) {
914 Some(v) => v,
915 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
916 };
917 let filter = args[1].clone();
918 let transform = args[2].clone();
919
920 expand_map_impl(
921 ctx,
922 span,
923 receiver,
924 iter_var,
925 Some(filter),
926 transform,
927 &args,
928 )
929}
930
931fn expand_map_impl(
932 ctx: &mut MacroContext,
933 span: Span,
934 receiver: SpannedExpr,
935 iter_var: String,
936 filter_cond: Option<SpannedExpr>,
937 transform: SpannedExpr,
938 args: &[SpannedExpr],
939) -> MacroExpansion {
940 let call_id = ctx.next_id();
941 ctx.store_macro_call(call_id, &span, &receiver, "map", args);
942
943 let accu_var = ACCU_VAR.to_string();
944 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
945 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
946
947 let transformed_list = synthetic(
948 ctx,
949 Expr::List(vec![ListElement {
950 expr: transform,
951 optional: false,
952 }]),
953 span.clone(),
954 );
955 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
956 let append_step = synthetic(
957 ctx,
958 Expr::Binary {
959 op: BinaryOp::Add,
960 left: Box::new(accu_ref_step),
961 right: Box::new(transformed_list),
962 },
963 span.clone(),
964 );
965
966 let loop_step = if let Some(filter) = filter_cond {
967 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
968 synthetic(
969 ctx,
970 Expr::Ternary {
971 cond: Box::new(filter),
972 then_expr: Box::new(append_step),
973 else_expr: Box::new(accu_ref_else),
974 },
975 span.clone(),
976 )
977 } else {
978 append_step
979 };
980
981 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
982
983 MacroExpansion::Expanded(Spanned::new(
984 call_id,
985 Expr::Comprehension(ComprehensionData {
986 iter_var,
987 iter_var2: String::new(),
988 iter_range: Box::new(receiver),
989 accu_var,
990 accu_init: Box::new(accu_init),
991 loop_condition: Box::new(loop_condition),
992 loop_step: Box::new(loop_step),
993 result: Box::new(result),
994 }),
995 span,
996 ))
997}
998
999fn expand_filter(
1002 ctx: &mut MacroContext,
1003 span: Span,
1004 receiver: Option<SpannedExpr>,
1005 args: Vec<SpannedExpr>,
1006) -> MacroExpansion {
1007 let receiver = match receiver {
1008 Some(r) => r,
1009 None => return MacroExpansion::Error("filter() requires a receiver".to_string()),
1010 };
1011
1012 let iter_var = match extract_iter_var(ctx, &args[0]) {
1013 Some(v) => v,
1014 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1015 };
1016 let cond = args[1].clone();
1017
1018 let call_id = ctx.next_id();
1019 ctx.store_macro_call(call_id, &span, &receiver, "filter", &args);
1020
1021 let accu_var = ACCU_VAR.to_string();
1022 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
1023 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1024
1025 let iter_ref = synthetic(ctx, Expr::Ident(iter_var.clone()), span.clone());
1026 let element_list = synthetic(
1027 ctx,
1028 Expr::List(vec![ListElement {
1029 expr: iter_ref,
1030 optional: false,
1031 }]),
1032 span.clone(),
1033 );
1034
1035 let accu_ref_then = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1036 let append_step = synthetic(
1037 ctx,
1038 Expr::Binary {
1039 op: BinaryOp::Add,
1040 left: Box::new(accu_ref_then),
1041 right: Box::new(element_list),
1042 },
1043 span.clone(),
1044 );
1045
1046 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1047 let loop_step = synthetic(
1048 ctx,
1049 Expr::Ternary {
1050 cond: Box::new(cond),
1051 then_expr: Box::new(append_step),
1052 else_expr: Box::new(accu_ref_else),
1053 },
1054 span.clone(),
1055 );
1056
1057 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1058
1059 MacroExpansion::Expanded(Spanned::new(
1060 call_id,
1061 Expr::Comprehension(ComprehensionData {
1062 iter_var,
1063 iter_var2: String::new(),
1064 iter_range: Box::new(receiver),
1065 accu_var,
1066 accu_init: Box::new(accu_init),
1067 loop_condition: Box::new(loop_condition),
1068 loop_step: Box::new(loop_step),
1069 result: Box::new(result),
1070 }),
1071 span,
1072 ))
1073}
1074
1075struct TransformParams {
1076 iter_var: String,
1077 iter_var2: String,
1078 filter_cond: Option<SpannedExpr>,
1079 transform: SpannedExpr,
1080}
1081
1082fn expand_transform_list_3arg(
1085 ctx: &mut MacroContext,
1086 span: Span,
1087 receiver: Option<SpannedExpr>,
1088 args: Vec<SpannedExpr>,
1089) -> MacroExpansion {
1090 let receiver = match receiver {
1091 Some(r) => r,
1092 None => return MacroExpansion::Error("transformList() requires a receiver".to_string()),
1093 };
1094
1095 let iter_var = match extract_iter_var(ctx, &args[0]) {
1096 Some(v) => v,
1097 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1098 };
1099 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1100 Some(v) => v,
1101 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1102 };
1103 let transform = args[2].clone();
1104
1105 expand_transform_list_impl(
1106 ctx,
1107 span,
1108 receiver,
1109 TransformParams {
1110 iter_var,
1111 iter_var2,
1112 filter_cond: None,
1113 transform,
1114 },
1115 &args,
1116 )
1117}
1118
1119fn expand_transform_list_4arg(
1120 ctx: &mut MacroContext,
1121 span: Span,
1122 receiver: Option<SpannedExpr>,
1123 args: Vec<SpannedExpr>,
1124) -> MacroExpansion {
1125 let receiver = match receiver {
1126 Some(r) => r,
1127 None => return MacroExpansion::Error("transformList() requires a receiver".to_string()),
1128 };
1129
1130 let iter_var = match extract_iter_var(ctx, &args[0]) {
1131 Some(v) => v,
1132 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1133 };
1134 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1135 Some(v) => v,
1136 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1137 };
1138 let filter = args[2].clone();
1139 let transform = args[3].clone();
1140
1141 expand_transform_list_impl(
1142 ctx,
1143 span,
1144 receiver,
1145 TransformParams {
1146 iter_var,
1147 iter_var2,
1148 filter_cond: Some(filter),
1149 transform,
1150 },
1151 &args,
1152 )
1153}
1154
1155fn expand_transform_list_impl(
1156 ctx: &mut MacroContext,
1157 span: Span,
1158 receiver: SpannedExpr,
1159 params: TransformParams,
1160 args: &[SpannedExpr],
1161) -> MacroExpansion {
1162 let call_id = ctx.next_id();
1163 ctx.store_macro_call(call_id, &span, &receiver, "transformList", args);
1164
1165 let accu_var = ACCU_VAR.to_string();
1166 let accu_init = synthetic(ctx, Expr::List(vec![]), span.clone());
1167 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1168
1169 let transformed_list = synthetic(
1170 ctx,
1171 Expr::List(vec![ListElement {
1172 expr: params.transform,
1173 optional: false,
1174 }]),
1175 span.clone(),
1176 );
1177 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1178 let append_step = synthetic(
1179 ctx,
1180 Expr::Binary {
1181 op: BinaryOp::Add,
1182 left: Box::new(accu_ref_step),
1183 right: Box::new(transformed_list),
1184 },
1185 span.clone(),
1186 );
1187
1188 let loop_step = if let Some(filter) = params.filter_cond {
1189 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1190 synthetic(
1191 ctx,
1192 Expr::Ternary {
1193 cond: Box::new(filter),
1194 then_expr: Box::new(append_step),
1195 else_expr: Box::new(accu_ref_else),
1196 },
1197 span.clone(),
1198 )
1199 } else {
1200 append_step
1201 };
1202
1203 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1204
1205 MacroExpansion::Expanded(Spanned::new(
1206 call_id,
1207 Expr::Comprehension(ComprehensionData {
1208 iter_var: params.iter_var,
1209 iter_var2: params.iter_var2,
1210 iter_range: Box::new(receiver),
1211 accu_var,
1212 accu_init: Box::new(accu_init),
1213 loop_condition: Box::new(loop_condition),
1214 loop_step: Box::new(loop_step),
1215 result: Box::new(result),
1216 }),
1217 span,
1218 ))
1219}
1220
1221fn expand_transform_map_3arg(
1224 ctx: &mut MacroContext,
1225 span: Span,
1226 receiver: Option<SpannedExpr>,
1227 args: Vec<SpannedExpr>,
1228) -> MacroExpansion {
1229 let receiver = match receiver {
1230 Some(r) => r,
1231 None => return MacroExpansion::Error("transformMap() requires a receiver".to_string()),
1232 };
1233
1234 let iter_var = match extract_iter_var(ctx, &args[0]) {
1235 Some(v) => v,
1236 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1237 };
1238 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1239 Some(v) => v,
1240 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1241 };
1242 let transform = args[2].clone();
1243
1244 expand_transform_map_impl(
1245 ctx,
1246 span,
1247 receiver,
1248 TransformParams {
1249 iter_var,
1250 iter_var2,
1251 filter_cond: None,
1252 transform,
1253 },
1254 &args,
1255 )
1256}
1257
1258fn expand_transform_map_4arg(
1259 ctx: &mut MacroContext,
1260 span: Span,
1261 receiver: Option<SpannedExpr>,
1262 args: Vec<SpannedExpr>,
1263) -> MacroExpansion {
1264 let receiver = match receiver {
1265 Some(r) => r,
1266 None => return MacroExpansion::Error("transformMap() requires a receiver".to_string()),
1267 };
1268
1269 let iter_var = match extract_iter_var(ctx, &args[0]) {
1270 Some(v) => v,
1271 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1272 };
1273 let iter_var2 = match extract_iter_var(ctx, &args[1]) {
1274 Some(v) => v,
1275 None => return MacroExpansion::Error("second argument must be an identifier".to_string()),
1276 };
1277 let filter = args[2].clone();
1278 let transform = args[3].clone();
1279
1280 expand_transform_map_impl(
1281 ctx,
1282 span,
1283 receiver,
1284 TransformParams {
1285 iter_var,
1286 iter_var2,
1287 filter_cond: Some(filter),
1288 transform,
1289 },
1290 &args,
1291 )
1292}
1293
1294fn expand_transform_map_impl(
1295 ctx: &mut MacroContext,
1296 span: Span,
1297 receiver: SpannedExpr,
1298 params: TransformParams,
1299 args: &[SpannedExpr],
1300) -> MacroExpansion {
1301 let call_id = ctx.next_id();
1302 ctx.store_macro_call(call_id, &span, &receiver, "transformMap", args);
1303
1304 let accu_var = ACCU_VAR.to_string();
1305 let accu_init = synthetic(ctx, Expr::Map(vec![]), span.clone());
1306 let loop_condition = synthetic(ctx, Expr::Bool(true), span.clone());
1307
1308 let key_ref = synthetic(ctx, Expr::Ident(params.iter_var.clone()), span.clone());
1309 let transformed_map = synthetic(
1310 ctx,
1311 Expr::Map(vec![MapEntry {
1312 key: key_ref,
1313 value: params.transform,
1314 optional: false,
1315 }]),
1316 span.clone(),
1317 );
1318 let accu_ref_step = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1319 let append_step = synthetic(
1320 ctx,
1321 Expr::Binary {
1322 op: BinaryOp::Add,
1323 left: Box::new(accu_ref_step),
1324 right: Box::new(transformed_map),
1325 },
1326 span.clone(),
1327 );
1328
1329 let loop_step = if let Some(filter) = params.filter_cond {
1330 let accu_ref_else = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1331 synthetic(
1332 ctx,
1333 Expr::Ternary {
1334 cond: Box::new(filter),
1335 then_expr: Box::new(append_step),
1336 else_expr: Box::new(accu_ref_else),
1337 },
1338 span.clone(),
1339 )
1340 } else {
1341 append_step
1342 };
1343
1344 let result = synthetic(ctx, Expr::Ident(accu_var.clone()), span.clone());
1345
1346 MacroExpansion::Expanded(Spanned::new(
1347 call_id,
1348 Expr::Comprehension(ComprehensionData {
1349 iter_var: params.iter_var,
1350 iter_var2: params.iter_var2,
1351 iter_range: Box::new(receiver),
1352 accu_var,
1353 accu_init: Box::new(accu_init),
1354 loop_condition: Box::new(loop_condition),
1355 loop_step: Box::new(loop_step),
1356 result: Box::new(result),
1357 }),
1358 span,
1359 ))
1360}
1361
1362fn expand_bind(
1372 ctx: &mut MacroContext,
1373 span: Span,
1374 _receiver: Option<SpannedExpr>,
1375 args: Vec<SpannedExpr>,
1376) -> MacroExpansion {
1377 if args.len() != 3 {
1378 return MacroExpansion::Error(format!(
1379 "cel.bind() requires 3 arguments, got {}",
1380 args.len()
1381 ));
1382 }
1383
1384 let var_name = match &args[0].node {
1386 Expr::Ident(name) => name.clone(),
1387 _ => {
1388 return MacroExpansion::Error(
1389 "cel.bind() first argument must be an identifier".to_string(),
1390 )
1391 }
1392 };
1393
1394 let init = args[1].clone();
1396
1397 let body = args[2].clone();
1399
1400 let call_id = ctx.next_id();
1401 ctx.store_macro_call(call_id, &span, &args[0], "cel.bind", &args);
1402
1403 MacroExpansion::Expanded(Spanned::new(
1404 call_id,
1405 Expr::Bind {
1406 var_name,
1407 init: Box::new(init),
1408 body: Box::new(body),
1409 },
1410 span,
1411 ))
1412}
1413
1414fn build_method_call(
1418 ctx: &mut MacroContext,
1419 span: &Span,
1420 receiver: SpannedExpr,
1421 method: &str,
1422 args: Vec<SpannedExpr>,
1423) -> SpannedExpr {
1424 let member = synthetic(
1425 ctx,
1426 Expr::Member {
1427 expr: Box::new(receiver),
1428 field: method.to_string(),
1429 optional: false,
1430 },
1431 span.clone(),
1432 );
1433
1434 synthetic(
1435 ctx,
1436 Expr::Call {
1437 expr: Box::new(member),
1438 args,
1439 },
1440 span.clone(),
1441 )
1442}
1443
1444fn build_optional_of(ctx: &mut MacroContext, span: &Span, expr: SpannedExpr) -> SpannedExpr {
1446 let optional_ident = synthetic(ctx, Expr::Ident("optional".to_string()), span.clone());
1447 let optional_of_member = synthetic(
1448 ctx,
1449 Expr::Member {
1450 expr: Box::new(optional_ident),
1451 field: "of".to_string(),
1452 optional: false,
1453 },
1454 span.clone(),
1455 );
1456
1457 synthetic(
1458 ctx,
1459 Expr::Call {
1460 expr: Box::new(optional_of_member),
1461 args: vec![expr],
1462 },
1463 span.clone(),
1464 )
1465}
1466
1467fn build_optional_none(ctx: &mut MacroContext, span: &Span) -> SpannedExpr {
1469 let optional_ident = synthetic(ctx, Expr::Ident("optional".to_string()), span.clone());
1470 let optional_none_member = synthetic(
1471 ctx,
1472 Expr::Member {
1473 expr: Box::new(optional_ident),
1474 field: "none".to_string(),
1475 optional: false,
1476 },
1477 span.clone(),
1478 );
1479
1480 synthetic(
1481 ctx,
1482 Expr::Call {
1483 expr: Box::new(optional_none_member),
1484 args: vec![],
1485 },
1486 span.clone(),
1487 )
1488}
1489
1490fn expand_opt_map(
1499 ctx: &mut MacroContext,
1500 span: Span,
1501 receiver: Option<SpannedExpr>,
1502 args: Vec<SpannedExpr>,
1503) -> MacroExpansion {
1504 let receiver = match receiver {
1505 Some(r) => r,
1506 None => return MacroExpansion::Error("optMap() requires a receiver".to_string()),
1507 };
1508
1509 let iter_var = match extract_iter_var(ctx, &args[0]) {
1510 Some(v) => v,
1511 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1512 };
1513 let transform = args[1].clone();
1514
1515 let call_id = ctx.next_id();
1516 ctx.store_macro_call(call_id, &span, &receiver, "optMap", &args);
1517
1518 let has_value_call = build_method_call(ctx, &span, receiver.clone(), "hasValue", vec![]);
1520
1521 let value_call = build_method_call(ctx, &span, receiver, "value", vec![]);
1523
1524 let wrapped_result = build_optional_of(ctx, &span, transform);
1526
1527 let bind_expr = synthetic(
1529 ctx,
1530 Expr::Bind {
1531 var_name: iter_var,
1532 init: Box::new(value_call),
1533 body: Box::new(wrapped_result),
1534 },
1535 span.clone(),
1536 );
1537
1538 let none_call = build_optional_none(ctx, &span);
1540
1541 MacroExpansion::Expanded(Spanned::new(
1543 call_id,
1544 Expr::Ternary {
1545 cond: Box::new(has_value_call),
1546 then_expr: Box::new(bind_expr),
1547 else_expr: Box::new(none_call),
1548 },
1549 span,
1550 ))
1551}
1552
1553fn expand_opt_flat_map(
1565 ctx: &mut MacroContext,
1566 span: Span,
1567 receiver: Option<SpannedExpr>,
1568 args: Vec<SpannedExpr>,
1569) -> MacroExpansion {
1570 let receiver = match receiver {
1571 Some(r) => r,
1572 None => return MacroExpansion::Error("optFlatMap() requires a receiver".to_string()),
1573 };
1574
1575 let iter_var = match extract_iter_var(ctx, &args[0]) {
1576 Some(v) => v,
1577 None => return MacroExpansion::Error("first argument must be an identifier".to_string()),
1578 };
1579 let transform = args[1].clone();
1580
1581 let call_id = ctx.next_id();
1582 ctx.store_macro_call(call_id, &span, &receiver, "optFlatMap", &args);
1583
1584 let has_value_call = build_method_call(ctx, &span, receiver.clone(), "hasValue", vec![]);
1586
1587 let value_call = build_method_call(ctx, &span, receiver, "value", vec![]);
1589
1590 let bind_expr = synthetic(
1593 ctx,
1594 Expr::Bind {
1595 var_name: iter_var,
1596 init: Box::new(value_call),
1597 body: Box::new(transform),
1598 },
1599 span.clone(),
1600 );
1601
1602 let none_call = build_optional_none(ctx, &span);
1604
1605 MacroExpansion::Expanded(Spanned::new(
1607 call_id,
1608 Expr::Ternary {
1609 cond: Box::new(has_value_call),
1610 then_expr: Box::new(bind_expr),
1611 else_expr: Box::new(none_call),
1612 },
1613 span,
1614 ))
1615}
1616
1617fn validate_qualified_identifier(expr: &SpannedExpr) -> Option<String> {
1627 match &expr.node {
1628 Expr::Ident(name) => Some(name.clone()),
1629 Expr::Member { expr, field, .. } => {
1630 let prefix = validate_qualified_identifier(expr)?;
1631 Some(format!("{}.{}", prefix, field))
1632 }
1633 _ => None,
1634 }
1635}
1636
1637fn expand_proto_get_ext(
1639 ctx: &mut MacroContext,
1640 span: Span,
1641 _receiver: Option<SpannedExpr>,
1642 args: Vec<SpannedExpr>,
1643) -> MacroExpansion {
1644 if args.len() != 2 {
1645 return MacroExpansion::Error(format!(
1646 "proto.getExt() requires 2 arguments, got {}",
1647 args.len()
1648 ));
1649 }
1650
1651 let ext_name = match validate_qualified_identifier(&args[1]) {
1652 Some(name) => name,
1653 None => return MacroExpansion::Error(
1654 "proto.getExt() second argument must be a qualified identifier (e.g., pkg.ExtField)"
1655 .to_string(),
1656 ),
1657 };
1658
1659 let msg = args[0].clone();
1660 let call_id = ctx.next_id();
1661 ctx.store_macro_call(call_id, &span, &msg, "proto.getExt", &args);
1662
1663 MacroExpansion::Expanded(Spanned::new(
1664 call_id,
1665 Expr::Member {
1666 expr: Box::new(msg),
1667 field: ext_name,
1668 optional: false,
1669 },
1670 span,
1671 ))
1672}
1673
1674fn expand_proto_has_ext(
1676 ctx: &mut MacroContext,
1677 span: Span,
1678 _receiver: Option<SpannedExpr>,
1679 args: Vec<SpannedExpr>,
1680) -> MacroExpansion {
1681 if args.len() != 2 {
1682 return MacroExpansion::Error(format!(
1683 "proto.hasExt() requires 2 arguments, got {}",
1684 args.len()
1685 ));
1686 }
1687
1688 let ext_name = match validate_qualified_identifier(&args[1]) {
1689 Some(name) => name,
1690 None => return MacroExpansion::Error(
1691 "proto.hasExt() second argument must be a qualified identifier (e.g., pkg.ExtField)"
1692 .to_string(),
1693 ),
1694 };
1695
1696 let msg = args[0].clone();
1697 let call_id = ctx.next_id();
1698 ctx.store_macro_call(call_id, &span, &msg, "proto.hasExt", &args);
1699
1700 MacroExpansion::Expanded(Spanned::new(
1701 call_id,
1702 Expr::MemberTestOnly {
1703 expr: Box::new(msg),
1704 field: ext_name,
1705 },
1706 span,
1707 ))
1708}
1709
1710fn expand_cel_block(
1729 ctx: &mut MacroContext,
1730 span: Span,
1731 _receiver: Option<SpannedExpr>,
1732 args: Vec<SpannedExpr>,
1733) -> MacroExpansion {
1734 if args.len() != 2 {
1735 return MacroExpansion::Error(format!(
1736 "cel.block() requires 2 arguments, got {}",
1737 args.len()
1738 ));
1739 }
1740
1741 let slots = match &args[0].node {
1742 Expr::List(elements) => {
1743 if elements.is_empty() {
1744 return MacroExpansion::Error(
1745 "cel.block() first argument must be a non-empty list".to_string(),
1746 );
1747 }
1748 elements.clone()
1749 }
1750 _ => {
1751 return MacroExpansion::Error(
1752 "cel.block() first argument must be a list literal".to_string(),
1753 )
1754 }
1755 };
1756
1757 let result = args[1].clone();
1758 let call_id = ctx.next_id();
1759 ctx.store_macro_call(call_id, &span, &args[0], "cel.block", &args);
1760
1761 let mut body = result;
1763 for (i, slot) in slots.into_iter().enumerate().rev() {
1764 let var_name = format!("@index{}", i);
1765 body = synthetic(
1766 ctx,
1767 Expr::Bind {
1768 var_name,
1769 init: Box::new(slot.expr),
1770 body: Box::new(body),
1771 },
1772 span.clone(),
1773 );
1774 }
1775
1776 body.id = call_id;
1778 MacroExpansion::Expanded(body)
1779}
1780
1781fn extract_int_literal(expr: &SpannedExpr) -> Option<i64> {
1783 match &expr.node {
1784 Expr::Int(n) => Some(*n),
1785 _ => None,
1786 }
1787}
1788
1789fn expand_cel_index(
1791 ctx: &mut MacroContext,
1792 span: Span,
1793 _receiver: Option<SpannedExpr>,
1794 args: Vec<SpannedExpr>,
1795) -> MacroExpansion {
1796 if args.len() != 1 {
1797 return MacroExpansion::Error(format!(
1798 "cel.index() requires 1 argument, got {}",
1799 args.len()
1800 ));
1801 }
1802
1803 let index = match extract_int_literal(&args[0]) {
1804 Some(n) => n,
1805 None => {
1806 return MacroExpansion::Error(
1807 "cel.index() argument must be an integer literal".to_string(),
1808 )
1809 }
1810 };
1811
1812 let call_id = ctx.next_id();
1813 ctx.store_macro_call(call_id, &span, &args[0], "cel.index", &args);
1814
1815 MacroExpansion::Expanded(Spanned::new(
1816 call_id,
1817 Expr::Ident(format!("@index{}", index)),
1818 span,
1819 ))
1820}
1821
1822fn expand_cel_iter_var(
1824 ctx: &mut MacroContext,
1825 span: Span,
1826 _receiver: Option<SpannedExpr>,
1827 args: Vec<SpannedExpr>,
1828) -> MacroExpansion {
1829 if args.len() != 2 {
1830 return MacroExpansion::Error(format!(
1831 "cel.iterVar() requires 2 arguments, got {}",
1832 args.len()
1833 ));
1834 }
1835
1836 let n = match extract_int_literal(&args[0]) {
1837 Some(n) => n,
1838 None => {
1839 return MacroExpansion::Error(
1840 "cel.iterVar() first argument must be an integer literal".to_string(),
1841 )
1842 }
1843 };
1844 let m = match extract_int_literal(&args[1]) {
1845 Some(m) => m,
1846 None => {
1847 return MacroExpansion::Error(
1848 "cel.iterVar() second argument must be an integer literal".to_string(),
1849 )
1850 }
1851 };
1852
1853 let call_id = ctx.next_id();
1854 ctx.store_macro_call(call_id, &span, &args[0], "cel.iterVar", &args);
1855
1856 MacroExpansion::Expanded(Spanned::new(
1857 call_id,
1858 Expr::Ident(format!("@it:{}:{}", n, m)),
1859 span,
1860 ))
1861}
1862
1863fn expand_cel_accu_var(
1865 ctx: &mut MacroContext,
1866 span: Span,
1867 _receiver: Option<SpannedExpr>,
1868 args: Vec<SpannedExpr>,
1869) -> MacroExpansion {
1870 if args.len() != 2 {
1871 return MacroExpansion::Error(format!(
1872 "cel.accuVar() requires 2 arguments, got {}",
1873 args.len()
1874 ));
1875 }
1876
1877 let n = match extract_int_literal(&args[0]) {
1878 Some(n) => n,
1879 None => {
1880 return MacroExpansion::Error(
1881 "cel.accuVar() first argument must be an integer literal".to_string(),
1882 )
1883 }
1884 };
1885 let m = match extract_int_literal(&args[1]) {
1886 Some(m) => m,
1887 None => {
1888 return MacroExpansion::Error(
1889 "cel.accuVar() second argument must be an integer literal".to_string(),
1890 )
1891 }
1892 };
1893
1894 let call_id = ctx.next_id();
1895 ctx.store_macro_call(call_id, &span, &args[0], "cel.accuVar", &args);
1896
1897 MacroExpansion::Expanded(Spanned::new(
1898 call_id,
1899 Expr::Ident(format!("@ac:{}:{}", n, m)),
1900 span,
1901 ))
1902}
1903
1904#[cfg(test)]
1905mod tests {
1906 use super::*;
1907
1908 fn dummy_expander(
1909 _ctx: &mut MacroContext,
1910 _span: Span,
1911 _receiver: Option<SpannedExpr>,
1912 _args: Vec<SpannedExpr>,
1913 ) -> MacroExpansion {
1914 MacroExpansion::Error("dummy".to_string())
1915 }
1916
1917 #[test]
1918 fn test_arg_count_exact() {
1919 let exact = ArgCount::Exact(2);
1920 assert!(exact.matches(2));
1921 assert!(!exact.matches(1));
1922 assert!(!exact.matches(3));
1923 assert_eq!(exact.count(), 2);
1924 assert!(!exact.is_vararg());
1925 }
1926
1927 #[test]
1928 fn test_arg_count_vararg() {
1929 let vararg = ArgCount::VarArg(2);
1930 assert!(vararg.matches(2));
1931 assert!(vararg.matches(3));
1932 assert!(vararg.matches(10));
1933 assert!(!vararg.matches(1));
1934 assert_eq!(vararg.count(), 2);
1935 assert!(vararg.is_vararg());
1936 }
1937
1938 #[test]
1939 fn test_macro_key() {
1940 let m = Macro::new(
1941 "all",
1942 MacroStyle::Receiver,
1943 ArgCount::Exact(2),
1944 dummy_expander,
1945 );
1946 assert_eq!(m.key(), "all:2:true");
1947
1948 let m2 = Macro::new(
1949 "has",
1950 MacroStyle::Global,
1951 ArgCount::Exact(1),
1952 dummy_expander,
1953 );
1954 assert_eq!(m2.key(), "has:1:false");
1955 }
1956
1957 #[test]
1958 fn test_registry_lookup_exact() {
1959 let mut registry = MacroRegistry::new();
1960 registry.register(Macro::new(
1961 "all",
1962 MacroStyle::Receiver,
1963 ArgCount::Exact(2),
1964 dummy_expander,
1965 ));
1966 registry.register(Macro::new(
1967 "all",
1968 MacroStyle::Receiver,
1969 ArgCount::Exact(3),
1970 dummy_expander,
1971 ));
1972
1973 assert!(registry.lookup("all", 2, true).is_some());
1974 assert!(registry.lookup("all", 3, true).is_some());
1975 assert!(registry.lookup("all", 4, true).is_none());
1976 assert!(registry.lookup("all", 2, false).is_none());
1977 }
1978
1979 #[test]
1980 fn test_registry_lookup_vararg() {
1981 let mut registry = MacroRegistry::new();
1982 registry.register(Macro::new(
1983 "custom",
1984 MacroStyle::Receiver,
1985 ArgCount::VarArg(2),
1986 dummy_expander,
1987 ));
1988
1989 assert!(registry.lookup("custom", 2, true).is_some());
1990 assert!(registry.lookup("custom", 3, true).is_some());
1991 assert!(registry.lookup("custom", 10, true).is_some());
1992 assert!(registry.lookup("custom", 1, true).is_none());
1993 }
1994
1995 #[test]
1996 fn test_registry_standard() {
1997 let registry = MacroRegistry::standard();
1998
1999 assert!(registry.lookup("has", 1, false).is_some());
2000 assert!(registry.lookup("all", 2, true).is_some());
2001 assert!(registry.lookup("all", 3, true).is_some());
2002 assert!(registry.lookup("exists", 2, true).is_some());
2003 assert!(registry.lookup("exists", 3, true).is_some());
2004 assert!(registry.lookup("exists_one", 2, true).is_some());
2005 assert!(registry.lookup("exists_one", 3, true).is_some());
2006 assert!(registry.lookup("map", 2, true).is_some());
2007 assert!(registry.lookup("map", 3, true).is_some());
2008 assert!(registry.lookup("filter", 2, true).is_some());
2009 }
2010
2011 #[test]
2012 fn test_registry_contains() {
2013 let registry = MacroRegistry::standard();
2014 assert!(registry.contains("has"));
2015 assert!(registry.contains("all"));
2016 assert!(registry.contains("cel.bind"));
2017 assert!(!registry.contains("nonexistent"));
2018 }
2019
2020 #[test]
2021 fn test_cel_bind_macro_registered() {
2022 let registry = MacroRegistry::standard();
2023 assert!(registry.lookup("cel.bind", 3, false).is_some());
2024 assert!(registry.lookup("cel.bind", 3, true).is_none());
2026 }
2027
2028 #[test]
2029 fn test_opt_map_macro_registered() {
2030 let registry = MacroRegistry::standard();
2031 assert!(registry.lookup("optMap", 2, true).is_some());
2033 assert!(registry.lookup("optMap", 2, false).is_none());
2035 assert!(registry.lookup("optMap", 1, true).is_none());
2037 assert!(registry.lookup("optMap", 3, true).is_none());
2038 }
2039
2040 #[test]
2041 fn test_opt_flat_map_macro_registered() {
2042 let registry = MacroRegistry::standard();
2043 assert!(registry.lookup("optFlatMap", 2, true).is_some());
2045 assert!(registry.lookup("optFlatMap", 2, false).is_none());
2047 assert!(registry.lookup("optFlatMap", 1, true).is_none());
2049 assert!(registry.lookup("optFlatMap", 3, true).is_none());
2050 }
2051
2052 #[test]
2053 fn test_registry_contains_opt_macros() {
2054 let registry = MacroRegistry::standard();
2055 assert!(registry.contains("optMap"));
2056 assert!(registry.contains("optFlatMap"));
2057 }
2058
2059 #[test]
2060 fn test_proto_has_ext_macro_registered() {
2061 let registry = MacroRegistry::standard();
2062 assert!(registry.lookup("proto.hasExt", 2, false).is_some());
2063 assert!(registry.lookup("proto.hasExt", 2, true).is_none());
2064 assert!(registry.lookup("proto.hasExt", 1, false).is_none());
2065 }
2066
2067 #[test]
2068 fn test_proto_get_ext_macro_registered() {
2069 let registry = MacroRegistry::standard();
2070 assert!(registry.lookup("proto.getExt", 2, false).is_some());
2071 assert!(registry.lookup("proto.getExt", 2, true).is_none());
2072 assert!(registry.lookup("proto.getExt", 1, false).is_none());
2073 }
2074
2075 #[test]
2076 fn test_validate_qualified_identifier() {
2077 let ident = Spanned::new(1, Expr::Ident("a".to_string()), 0..1);
2079 assert_eq!(validate_qualified_identifier(&ident), Some("a".to_string()));
2080
2081 let dotted = Spanned::new(
2083 2,
2084 Expr::Member {
2085 expr: Box::new(Spanned::new(1, Expr::Ident("a".to_string()), 0..1)),
2086 field: "b".to_string(),
2087 optional: false,
2088 },
2089 0..3,
2090 );
2091 assert_eq!(
2092 validate_qualified_identifier(&dotted),
2093 Some("a.b".to_string())
2094 );
2095
2096 let deep = Spanned::new(
2098 4,
2099 Expr::Member {
2100 expr: Box::new(Spanned::new(
2101 3,
2102 Expr::Member {
2103 expr: Box::new(Spanned::new(
2104 2,
2105 Expr::Member {
2106 expr: Box::new(Spanned::new(1, Expr::Ident("a".to_string()), 0..1)),
2107 field: "b".to_string(),
2108 optional: false,
2109 },
2110 0..3,
2111 )),
2112 field: "c".to_string(),
2113 optional: false,
2114 },
2115 0..5,
2116 )),
2117 field: "d".to_string(),
2118 optional: false,
2119 },
2120 0..7,
2121 );
2122 assert_eq!(
2123 validate_qualified_identifier(&deep),
2124 Some("a.b.c.d".to_string())
2125 );
2126
2127 let non_ident = Spanned::new(1, Expr::Int(42), 0..2);
2129 assert_eq!(validate_qualified_identifier(&non_ident), None);
2130 }
2131
2132 #[test]
2133 fn test_proto_get_ext_expansion() {
2134 let mut id = 10i64;
2135 let mut next_id = || -> i64 {
2136 id += 1;
2137 id
2138 };
2139 let mut ctx = MacroContext::new(&mut next_id, None);
2140
2141 let msg = Spanned::new(1, Expr::Ident("msg".to_string()), 0..3);
2142 let ext = Spanned::new(
2143 2,
2144 Expr::Member {
2145 expr: Box::new(Spanned::new(1, Expr::Ident("pkg".to_string()), 4..7)),
2146 field: "ExtField".to_string(),
2147 optional: false,
2148 },
2149 4..16,
2150 );
2151
2152 let result = expand_proto_get_ext(&mut ctx, 0..20, None, vec![msg, ext]);
2153 match result {
2154 MacroExpansion::Expanded(expr) => match &expr.node {
2155 Expr::Member {
2156 expr,
2157 field,
2158 optional,
2159 } => {
2160 assert_eq!(field, "pkg.ExtField");
2161 assert!(!optional);
2162 assert!(matches!(&expr.node, Expr::Ident(name) if name == "msg"));
2163 }
2164 other => panic!("expected Member, got {:?}", other),
2165 },
2166 MacroExpansion::Error(e) => panic!("unexpected error: {}", e),
2167 }
2168 }
2169
2170 #[test]
2171 fn test_proto_has_ext_expansion() {
2172 let mut id = 10i64;
2173 let mut next_id = || -> i64 {
2174 id += 1;
2175 id
2176 };
2177 let mut ctx = MacroContext::new(&mut next_id, None);
2178
2179 let msg = Spanned::new(1, Expr::Ident("msg".to_string()), 0..3);
2180 let ext = Spanned::new(
2181 2,
2182 Expr::Member {
2183 expr: Box::new(Spanned::new(1, Expr::Ident("pkg".to_string()), 4..7)),
2184 field: "ExtField".to_string(),
2185 optional: false,
2186 },
2187 4..16,
2188 );
2189
2190 let result = expand_proto_has_ext(&mut ctx, 0..20, None, vec![msg, ext]);
2191 match result {
2192 MacroExpansion::Expanded(expr) => match &expr.node {
2193 Expr::MemberTestOnly { expr, field } => {
2194 assert_eq!(field, "pkg.ExtField");
2195 assert!(matches!(&expr.node, Expr::Ident(name) if name == "msg"));
2196 }
2197 other => panic!("expected MemberTestOnly, got {:?}", other),
2198 },
2199 MacroExpansion::Error(e) => panic!("unexpected error: {}", e),
2200 }
2201 }
2202
2203 #[test]
2204 fn test_proto_ext_error_non_qualified() {
2205 let mut id = 10i64;
2206 let mut next_id = || -> i64 {
2207 id += 1;
2208 id
2209 };
2210 let mut ctx = MacroContext::new(&mut next_id, None);
2211
2212 let msg = Spanned::new(1, Expr::Ident("msg".to_string()), 0..3);
2213 let bad_arg = Spanned::new(2, Expr::Int(42), 4..6);
2214
2215 let result =
2216 expand_proto_get_ext(&mut ctx, 0..10, None, vec![msg.clone(), bad_arg.clone()]);
2217 assert!(matches!(result, MacroExpansion::Error(_)));
2218
2219 let result = expand_proto_has_ext(&mut ctx, 0..10, None, vec![msg, bad_arg]);
2220 assert!(matches!(result, MacroExpansion::Error(_)));
2221 }
2222}