tatara_rust_macro_rules/
lib.rs1use serde::{Deserialize, Serialize};
25use tatara_rust_ast::{AstError, CompileToCrate, CrateScaffold, Ident};
26
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
28pub struct MacroRulesSpec {
29 pub macro_name: Ident,
30 pub arms: Vec<MacroArm>,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34pub struct MacroArm {
35 pub matcher: String,
38 pub transcriber: String,
40}
41
42impl CompileToCrate for MacroRulesSpec {
43 fn compile_to_crate(&self, crate_name: &str) -> Result<CrateScaffold, AstError> {
44 let mut scaffold = CrateScaffold::new(crate_name, "0.1.0");
45 scaffold.add_file("Cargo.toml", render_cargo_toml(crate_name));
46 scaffold.add_file("src/lib.rs", render_lib_rs(self));
47 Ok(scaffold)
48 }
49}
50
51fn render_cargo_toml(crate_name: &str) -> String {
52 format!(
53 r#"[package]
54name = "{crate_name}"
55version = "0.1.0"
56edition = "2024"
57license = "MIT"
58description = "Declarative-macro crate emitted from a tatara-rust-macro-rules MacroRulesSpec."
59
60[lib]
61"#
62 )
63}
64
65fn render_lib_rs(spec: &MacroRulesSpec) -> String {
66 let mac = &spec.macro_name.0;
67 let arms = spec
68 .arms
69 .iter()
70 .map(|a| format!(" {} => {};", a.matcher, a.transcriber))
71 .collect::<Vec<_>>()
72 .join("\n");
73 format!(
74 r#"// GENERATED by tatara-rust-macro-rules from a MacroRulesSpec.
75#[macro_export]
76macro_rules! {mac} {{
77{arms}
78}}
79"#
80 )
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 fn sample() -> MacroRulesSpec {
88 MacroRulesSpec {
89 macro_name: Ident::new("my_vec"),
90 arms: vec![MacroArm {
91 matcher: "( $($e:expr),* $(,)? )".into(),
92 transcriber: "{ ::std::vec![ $($e),* ] }".into(),
93 }],
94 }
95 }
96
97 #[test]
98 fn compiles_to_lib_and_cargo() {
99 let scaffold = sample().compile_to_crate("my-vec-macros").unwrap();
100 let files = scaffold.to_files();
101 assert!(files.contains_key("Cargo.toml"));
102 assert!(files.contains_key("src/lib.rs"));
103 }
104
105 #[test]
106 fn lib_rs_emits_macro_export() {
107 let scaffold = sample().compile_to_crate("my-vec-macros").unwrap();
108 let lib = scaffold.to_files().get("src/lib.rs").unwrap().clone();
109 assert!(lib.contains("#[macro_export]"));
110 assert!(lib.contains("macro_rules! my_vec"));
111 assert!(lib.contains("( $($e:expr),* $(,)? )"));
112 assert!(lib.contains("::std::vec!"));
113 }
114
115 #[test]
116 fn cargo_toml_is_normal_lib_not_proc_macro() {
117 let scaffold = sample().compile_to_crate("my-vec-macros").unwrap();
118 let toml = scaffold.to_files().get("Cargo.toml").unwrap().clone();
119 assert!(!toml.contains("proc-macro"));
121 }
122
123 #[test]
124 fn multiple_arms_emit_in_order() {
125 let spec = MacroRulesSpec {
126 macro_name: Ident::new("two"),
127 arms: vec![
128 MacroArm {
129 matcher: "()".into(),
130 transcriber: "{ () }".into(),
131 },
132 MacroArm {
133 matcher: "($e:expr)".into(),
134 transcriber: "{ $e }".into(),
135 },
136 ],
137 };
138 let scaffold = spec.compile_to_crate("two-macros").unwrap();
139 let lib = scaffold.to_files().get("src/lib.rs").unwrap().clone();
140 assert!(lib.find("()").unwrap() < lib.find("($e:expr)").unwrap());
141 }
142
143 #[test]
144 fn serde_roundtrip() {
145 let s = sample();
146 let j = serde_json::to_string(&s).unwrap();
147 let back: MacroRulesSpec = serde_json::from_str(&j).unwrap();
148 assert_eq!(s, back);
149 }
150}