message_bus_macros/
codegen.rs1use crate::{
2 analysis::Analysis,
3 ast::{Ast, SubTopic, Topic},
4};
5use proc_macro2::{Span, TokenStream as TokenStream2};
6use quote::quote;
7use syn::Ident;
8
9fn make_topics_enum(name: &Ident, topics: &[Topic], sub_topics: &[SubTopic]) -> TokenStream2 {
10 let mut arms = Vec::new();
11
12 for topic in topics {
13 let tn = &topic.name;
14 let tp = &topic.payload;
15 let doc = format!("Type-level definition of the `{tn}` topic");
16
17 arms.push(quote!(
18 #[doc = #doc]
19 #tn(#tp)
20 ));
21 }
22
23 for sub_topic in sub_topics {
24 let tn = &sub_topic.name;
25 let doc = format!("Type-level definition of the `{tn}` sub-topic");
26
27 arms.push(quote!(
28 #[doc = #doc]
29 #tn(#tn)
30 ));
31 }
32
33 let doc = format!("Type-level definition of all topics in `{name}`");
34 quote!(
35 #[doc = #doc]
36 #[derive(Clone, Debug)]
37 pub enum #name {
38 #(#arms),*
39 }
40 )
41}
42
43fn codegen_topics(topics: &[Topic], subtopic_tracker: &mut SubTopicTracker) -> Vec<TokenStream2> {
44 let mut tokens = Vec::new();
45
46 for topic in topics {
47 let topic_name = &topic.name;
48 let topic_payload = &topic.payload;
49 let topic_static = Ident::new(&format!("__TOPIC_{topic_name}"), Span::call_site());
50 let topic_capacity = &topic.capacity;
51
52 let doc_topic = format!("Handle to the `{topic_name}` topic.");
53 let doc_sub = format!("Subscribe to the `{topic_name}` topic.");
54 let doc_pub = format!("Publish to the `{topic_name}` topic.");
55
56 let publish_parent_topics = subtopic_tracker.to_parent_publishes(topic_name);
57
58 tokens.push(quote!(
59 #[doc = #doc_topic]
60 pub struct #topic_name;
61
62 #[doc(hidden)]
63 #[allow(non_upper_case_globals)]
64 static #topic_static: ::make_message_bus::Topic<#topic_payload> = ::make_message_bus::Topic::new::<#topic_capacity>();
65
66 impl #topic_name {
67 #[doc = #doc_sub]
68 pub fn subscribe() -> ::make_message_bus::Subscriber<#topic_payload> {
69 #topic_static.subscribe()
70 }
71
72 #[doc = #doc_pub]
73 pub fn publish(payload: #topic_payload) {
74 #(#publish_parent_topics)*
75
76 #topic_static.publish(payload);
77 }
78 }
79 ));
80 }
81
82 tokens
83}
84
85fn codegen_subtopics(
86 sub_topics: &[SubTopic],
87 subtopic_tracker: &mut SubTopicTracker,
88) -> Vec<TokenStream2> {
89 let mut tokens = Vec::new();
90
91 for sub_topic in sub_topics {
92 subtopic_tracker.add_subtopic(sub_topic.name.clone());
93
94 let topic_enum = make_topics_enum(
95 &sub_topic.name,
96 &sub_topic.ast.topics,
97 &sub_topic.ast.sub_topics,
98 );
99 let topics = codegen_topics(&sub_topic.ast.topics, subtopic_tracker);
100
101 let sub_topic_tokens = codegen_subtopics(&sub_topic.ast.sub_topics, subtopic_tracker);
103 let sub_topic_name = &sub_topic.name;
104 let sub_topic_module = &sub_topic.module;
105 let sub_topic_doc1 = format!(
106 "Module containing topics and implementation for the `{sub_topic_name}` subtopic"
107 );
108 let sub_topic_doc2 =
109 format!("All topics in the `{sub_topic_module}::{sub_topic_name}` subtopic");
110 let sub_topic_static = Ident::new(&format!("__TOPIC_{sub_topic_name}"), Span::call_site());
111
112 let pub_use = if !subtopic_tracker.at_root() {
113 quote!(pub use #sub_topic_module::#sub_topic_name;)
114 } else {
115 quote!()
116 };
117
118 let doc_sub = format!("Subscribe to the `{sub_topic_name}` sub-topic.");
119
120 let mut capacity = 0;
121 find_total_capacity(sub_topic, &mut capacity);
122
123 tokens.push(quote!(
124 #pub_use
125
126 #[doc = #sub_topic_doc1]
127 pub mod #sub_topic_module {
128 #[doc(hidden)]
129 #[allow(non_upper_case_globals)]
130 static #sub_topic_static: ::make_message_bus::Topic<#sub_topic_name> = ::make_message_bus::Topic::new::<#capacity>();
131
132 #[doc = #sub_topic_doc2]
133 #topic_enum
134
135 impl #sub_topic_name {
136 #[doc = #doc_sub]
137 pub fn subscribe() -> ::make_message_bus::Subscriber<#sub_topic_name> {
138 #sub_topic_static.subscribe()
139 }
140 }
141
142 #(#topics)*
143
144 #(#sub_topic_tokens)*
145 }
146 ));
147 subtopic_tracker.remove_last_subtopic();
148 }
149
150 tokens
151}
152
153fn find_total_capacity(sub_topic: &SubTopic, capacity: &mut usize) {
154 let topic_cap: usize = sub_topic
155 .ast
156 .topics
157 .iter()
158 .map(|topic| topic.capacity)
159 .sum();
160
161 *capacity += topic_cap;
162
163 for st in &sub_topic.ast.sub_topics {
164 find_total_capacity(st, capacity);
165 }
166}
167
168struct SubTopicTracker(Vec<Ident>);
169
170impl SubTopicTracker {
171 pub fn new() -> Self {
172 Self(Vec::new())
173 }
174
175 pub fn depth(&self) -> usize {
176 self.0.len()
177 }
178
179 pub fn at_root(&self) -> bool {
180 self.depth() < 2
181 }
182
183 pub fn add_subtopic(&mut self, subtopic: Ident) {
184 self.0.push(subtopic);
185 }
186
187 pub fn remove_last_subtopic(&mut self) {
188 self.0.pop();
189 }
190
191 pub fn to_parent_publishes(&self, current_topic: &Ident) -> Vec<TokenStream2> {
192 let mut publish_tokens = Vec::new();
193
194 let mut super_tokens = quote!();
195 let mut payload = quote!(payload.clone());
196 let mut last_topic = current_topic;
197
198 for parent_topic in self.0.iter().rev() {
199 let parent_topic_static =
200 Ident::new(&format!("__TOPIC_{parent_topic}"), Span::call_site());
201
202 payload = quote!(#super_tokens #parent_topic::#last_topic(#payload));
203
204 publish_tokens.push(quote!(
205 #super_tokens #parent_topic_static.publish(#payload);
206 ));
207
208 super_tokens = quote!(#super_tokens super::);
209 last_topic = parent_topic;
210 }
211
212 publish_tokens
213 }
214}
215
216pub fn generate(ast: &Ast, _anaysis: &Analysis) -> proc_macro::TokenStream {
217 let mut subtopic_tracker = SubTopicTracker::new();
218
219 let tokens = codegen_subtopics(&ast.sub_topics, &mut subtopic_tracker);
220
221 quote! {
222 #(#tokens)*
223 }
224 .into()
225}