From 11fc1e0e48320c69e1c4de299a37c63b9162de3f Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Mon, 9 Mar 2026 11:22:32 +0100 Subject: [PATCH] Add 'again' macro for recursive expansion The 'again' macro is a new addition to the macro system, allowing for recursive macro expansion. This commit introduces the necessary AST nodes, macro expansion logic, and an integration test to ensure its functionality. The `soa_series.myc` example has also been updated to demonstrate its usage. --- examples/repeat.myc | 3 +++ examples/soa_series.myc | 4 +++- src/ast/compiler/macros.rs | 22 ++++++++++++++++++++++ src/integration_test.rs | 16 ++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 examples/repeat.myc diff --git a/examples/repeat.myc b/examples/repeat.myc new file mode 100644 index 0000000..aa143db --- /dev/null +++ b/examples/repeat.myc @@ -0,0 +1,3 @@ +(do + (repeat n 10 (print n) ) +) diff --git a/examples/soa_series.myc b/examples/soa_series.myc index b6713cc..f3f4f01 100644 --- a/examples/soa_series.myc +++ b/examples/soa_series.myc @@ -1,4 +1,4 @@ -;; Output: 26.7 +;; Output: 228 ;; Benchmark: 1.4us ;; Benchmark-Repeat: 1406 (do @@ -8,6 +8,8 @@ (push my_ticks {:price 11.2 :volume 200 :msg "B"}) (push my_ticks {:price 15.5 :volume 300 :msg "C"}) + (repeat n 100 (push my_ticks {:price (+ n 15.5) :volume (ceil (/ n 300)) :msg "??"}) ) + (def prices (.price my_ticks)) (+ (prices 0) (prices 1)) diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index 357f85b..1e74b42 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -275,6 +275,17 @@ impl MacroExpander { }) } + UntypedKind::Again { args } => { + let expanded_args = self.expand_recursive(*args)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Again { + args: Box::new(expanded_args), + }, + ty: (), + }) + } + _ => Ok(node), } } @@ -536,6 +547,17 @@ impl MacroExpander { }) } + UntypedKind::Again { args } => { + let expanded_args = self.expand_template(*args, state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Again { + args: Box::new(expanded_args), + }, + ty: (), + }) + } + _ => Ok(node), } } diff --git a/src/integration_test.rs b/src/integration_test.rs index e11dde9..3f3ff4a 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -813,4 +813,20 @@ mod tests { let res = env.run_script(&source_fixed); assert!(res.is_ok(), "Inlining slot clash triggered: {:?}", res.err()); } + + #[test] + fn test_repro_placeholder_in_template_failure() { + let env = Environment::new(); + let source = r#" + (do + (macro repeat [var limit body] + `((fn [~var __limit] + (if (< ~var __limit) + (do ~body (again (+ ~var 1) __limit)))) + 0 ~limit)) + (repeat n 10 42)) + "#; + let res = env.run_script(source); + assert!(res.is_ok(), "Expansion failed to strip placeholders: {:?}", res.err()); + } }