1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{
5 parse::{Parse, ParseStream},
6 parse_macro_input, parse_str,
7 punctuated::Punctuated,
8 DeriveInput, FnArg, Ident, ItemFn, Pat, PatIdent, Path, Result, Token, Type, TypePath,
9 TypeReference,
10};
11
12#[derive(Debug, Clone)]
13struct IdentList {
14 values: Punctuated<Ident, Token![,]>,
15}
16
17impl Parse for IdentList {
18 fn parse(input: ParseStream) -> Result<Self> {
19 Ok(Self {
20 values: input.parse_terminated(Ident::parse)?,
21 })
22 }
23}
24
25fn unpack_context(ty: &Type, pat: &Pat) -> Option<Ident> {
26 match ty {
27 Type::Path(TypePath { path, .. }) => {
28 if let Some(segment) = path.segments.iter().last() {
29 if segment.ident == "WidgetContext" {
30 if let Pat::Ident(PatIdent { ident, .. }) = pat {
31 return Some(ident.to_owned());
32 }
33 }
34 }
35 }
36 Type::Reference(TypeReference { elem, .. }) => {
37 return unpack_context(elem, pat);
38 }
39 _ => {}
40 }
41 None
42}
43
44fn is_arg_context(arg: &FnArg) -> Option<Ident> {
45 if let FnArg::Typed(pat) = arg {
46 unpack_context(&pat.ty, &pat.pat)
47 } else {
48 None
49 }
50}
51
52#[proc_macro_attribute]
89pub fn pre_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {
90 let ItemFn {
91 attrs,
92 vis,
93 sig,
94 block,
95 } = parse_macro_input!(input as ItemFn);
96 let context = sig
97 .inputs
98 .iter()
99 .find_map(is_arg_context)
100 .unwrap_or_else(|| panic!("Could not find function context argument!"));
101 let list = parse_macro_input!(attr as IdentList);
102 let hooks = list
103 .values
104 .into_iter()
105 .map(|v| quote! { #context.use_hook(#v); });
106
107 let tokens = quote! {
108 #(#attrs)*
109 #vis #sig {
110 #({#hooks})*
111 #block
112 }
113 };
114 tokens.into()
115}
116
117#[proc_macro_attribute]
121pub fn post_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {
122 let ItemFn {
123 attrs,
124 vis,
125 sig,
126 block,
127 } = parse_macro_input!(input as ItemFn);
128 let context = sig
129 .inputs
130 .iter()
131 .find_map(is_arg_context)
132 .unwrap_or_else(|| panic!("Could not find function context argument!"));
133 let list = parse_macro_input!(attr as IdentList);
134 let hooks = list
135 .values
136 .into_iter()
137 .map(|v| quote! { #context.use_hook(#v); });
138
139 let tokens = quote! {
140 #(#attrs)*
141 #vis #sig {
142 let result = {
143 #block
144 };
145 #({#hooks})*
146 result
147 }
148 };
149 tokens.into()
150}
151
152#[proc_macro_derive(PropsData, attributes(remote, props_data, prefab))]
173pub fn derive_props(input: TokenStream) -> TokenStream {
174 let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);
175
176 let mut path = Path::from(ident);
177 let mut props_data = parse_str::<Path>("PropsData").unwrap();
178 let mut prefab = parse_str::<Path>("Prefab").unwrap();
179 for attr in attrs {
180 if let Some(ident) = attr.path.get_ident() {
181 if ident == "remote" {
182 path = attr.parse_args::<Path>().unwrap();
183 } else if ident == "props_data" {
184 props_data = attr.parse_args::<Path>().unwrap();
185 } else if ident == "prefab" {
186 prefab = attr.parse_args::<Path>().unwrap();
187 }
188 }
189 }
190
191 let tokens = quote! {
192 impl #props_data for #path
193 where
194 Self: Clone,
195 {
196 fn clone_props(&self) -> Box<dyn #props_data> {
197 Box::new(self.clone())
198 }
199
200 fn as_any(&self) -> &dyn std::any::Any {
201 self
202 }
203 }
204
205 impl #prefab for #path {}
206 };
207 tokens.into()
208}
209
210#[proc_macro_derive(MessageData, attributes(remote, message_data))]
223pub fn derive_message(input: TokenStream) -> TokenStream {
224 let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);
225
226 let mut path = Path::from(ident);
227 let mut message_data = parse_str::<Path>("MessageData").unwrap();
228 for attr in attrs {
229 if let Some(ident) = attr.path.get_ident() {
230 if ident == "remote" {
231 path = attr.parse_args::<Path>().unwrap();
232 } else if ident == "message_data" {
233 message_data = attr.parse_args::<Path>().unwrap();
234 }
235 }
236 }
237
238 let tokens = quote! {
239 impl #message_data for #path
240 where
241 Self: Clone,
242 {
243 fn clone_message(&self) -> Box<dyn #message_data> {
244 Box::new(self.clone())
245 }
246
247 fn as_any(&self) -> &dyn std::any::Any {
248 self
249 }
250 }
251 };
252 tokens.into()
253}