1use tatara_rust_ast::Ident;
17use tatara_rust_derive::ProcDeriveSpec;
18use tatara_rust_macro_rules::{MacroArm, MacroRulesSpec};
19use tatara_rust_proc_attr::{AttrTransform, ProcAttrSpec};
20use tatara_rust_proc_fn::{FnTransform, ProcFnSpec};
21use tatara_rust_suite::{MacroMemberSpec, MacroSuiteSpec};
22
23pub mod catalog;
24pub use catalog::{ParseError, SExpr, parse_macrocatalog, parse_sexprs, render_macrocatalog};
25
26pub trait RenderTlisp {
29 fn render_tlisp(&self) -> String;
30}
31
32impl RenderTlisp for ProcDeriveSpec {
33 fn render_tlisp(&self) -> String {
34 format!(
35 "(defprocderive\n :trait-name \"{}\"\n :items {})",
36 self.trait_name.0,
37 self.impl_template.items.len()
38 )
39 }
40}
41
42impl RenderTlisp for ProcAttrSpec {
43 fn render_tlisp(&self) -> String {
44 let transform = match &self.transform {
45 AttrTransform::PrependPrelude { prelude_tokens } => format!(
46 "(:kind \"prepend-prelude\" :prelude {prelude_tokens:?})"
47 ),
48 };
49 format!(
50 "(defprocattr\n :macro-name \"{}\"\n :transform {transform})",
51 self.macro_name.0
52 )
53 }
54}
55
56impl RenderTlisp for ProcFnSpec {
57 fn render_tlisp(&self) -> String {
58 let transform = match &self.transform {
59 FnTransform::PrependPrelude { prelude_tokens } => format!(
60 "(:kind \"prepend-prelude\" :prelude {prelude_tokens:?})"
61 ),
62 };
63 format!(
64 "(defprocfn\n :macro-name \"{}\"\n :transform {transform})",
65 self.macro_name.0
66 )
67 }
68}
69
70impl RenderTlisp for MacroRulesSpec {
71 fn render_tlisp(&self) -> String {
72 let arms = self
73 .arms
74 .iter()
75 .map(|a| format!(" (:matcher {:?} :transcriber {:?})", a.matcher, a.transcriber))
76 .collect::<Vec<_>>()
77 .join("\n");
78 format!(
79 "(defmacrules\n :macro-name \"{}\"\n :arms (\n{arms}))",
80 self.macro_name.0
81 )
82 }
83}
84
85impl RenderTlisp for MacroSuiteSpec {
86 fn render_tlisp(&self) -> String {
87 let members = self
88 .members
89 .iter()
90 .map(|m| {
91 let body = match m {
92 MacroMemberSpec::Derive { crate_name, spec } => format!(
93 "(:kind \"derive\" :crate-name {crate_name:?} :spec {})",
94 spec.render_tlisp()
95 ),
96 MacroMemberSpec::ProcAttr { crate_name, spec } => format!(
97 "(:kind \"proc-attr\" :crate-name {crate_name:?} :spec {})",
98 spec.render_tlisp()
99 ),
100 MacroMemberSpec::ProcFn { crate_name, spec } => format!(
101 "(:kind \"proc-fn\" :crate-name {crate_name:?} :spec {})",
102 spec.render_tlisp()
103 ),
104 MacroMemberSpec::MacroRules { crate_name, spec } => format!(
105 "(:kind \"macro-rules\" :crate-name {crate_name:?} :spec {})",
106 spec.render_tlisp()
107 ),
108 };
109 format!(" {body}")
110 })
111 .collect::<Vec<_>>()
112 .join("\n");
113 format!(
114 "(defsuite\n :workspace-name \"{}\"\n :members (\n{members}))",
115 self.workspace_name
116 )
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn derive_renders() {
126 let s = ProcDeriveSpec::new("Marker", vec![]);
127 let txt = s.render_tlisp();
128 assert!(txt.contains("(defprocderive"));
129 assert!(txt.contains(":trait-name \"Marker\""));
130 }
131
132 #[test]
133 fn proc_attr_renders() {
134 let s = ProcAttrSpec {
135 macro_name: Ident::new("instrumented"),
136 transform: AttrTransform::PrependPrelude {
137 prelude_tokens: "#[inline]".into(),
138 },
139 };
140 let txt = s.render_tlisp();
141 assert!(txt.contains("(defprocattr"));
142 assert!(txt.contains(":macro-name \"instrumented\""));
143 assert!(txt.contains("prepend-prelude"));
144 }
145
146 #[test]
147 fn proc_fn_renders() {
148 let s = ProcFnSpec {
149 macro_name: Ident::new("say_hi"),
150 transform: FnTransform::PrependPrelude {
151 prelude_tokens: "println!(\"hi\");".into(),
152 },
153 };
154 let txt = s.render_tlisp();
155 assert!(txt.contains("(defprocfn"));
156 assert!(txt.contains(":macro-name \"say_hi\""));
157 }
158
159 #[test]
160 fn macro_rules_renders_with_arms() {
161 let s = MacroRulesSpec {
162 macro_name: Ident::new("identity"),
163 arms: vec![MacroArm {
164 matcher: "($x:expr)".into(),
165 transcriber: "{ $x }".into(),
166 }],
167 };
168 let txt = s.render_tlisp();
169 assert!(txt.contains("(defmacrules"));
170 assert!(txt.contains(":matcher"));
171 assert!(txt.contains(":transcriber"));
172 }
173
174 #[test]
175 fn suite_renders_with_members() {
176 let suite = MacroSuiteSpec {
177 workspace_name: "my-macros".into(),
178 members: vec![MacroMemberSpec::Derive {
179 crate_name: "marker-derive".into(),
180 spec: ProcDeriveSpec::new("Marker", vec![]),
181 }],
182 };
183 let txt = suite.render_tlisp();
184 assert!(txt.contains("(defsuite"));
185 assert!(txt.contains(":workspace-name \"my-macros\""));
186 assert!(txt.contains(":kind \"derive\""));
187 assert!(txt.contains("marker-derive"));
188 }
189}