Skip to main content

tatara_rust_tlisp/
lib.rs

1//! `tatara-rust-tlisp` — tlisp authoring surface for every L2 macro Spec.
2//!
3//! Two purposes:
4//! 1. **Forward path** — write a `(defprocderive …)` / `(defprocattr …)` /
5//!    `(defprocfn …)` / `(defmacrules …)` / `(defsuite …)` form in
6//!    tlisp; this crate's [`render_tlisp`] turns each Spec back into
7//!    that text shape (round-trip).
8//! 2. **TataraDomain registration** — when tatara-lisp-derive lands as a
9//!    dep, every Spec gets `#[derive(TataraDomain)]` + `#[tatara(keyword =
10//!    "def…")]` in this crate. Authoring forms expand to the Spec at
11//!    compile time.
12//!
13//! Today only the rendering half ships. The derive macros land as a
14//! one-line addition per Spec once the upstream is reachable.
15
16use 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
26/// Render any Spec to its canonical tlisp `(def<kind> …)` form. The
27/// returned text is what the operator authors today in a `.tlisp` file.
28pub 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}