1use proc_macro::TokenStream;
2use quote::*;
3use syn::parse::*;
4use syn::punctuated::Punctuated;
5use syn::spanned::Spanned;
6use syn::*;
7
8use crate::template::SlotType;
9
10use super::i18n::LocaleGroup;
11use super::template::Template;
12
13struct ComponentAttr {
14 items: Punctuated<ComponentAttrItem, token::Comma>,
15}
16
17impl Parse for ComponentAttr {
18 fn parse(input: ParseStream) -> Result<Self> {
19 let items = Punctuated::parse_terminated(input)?;
20 Ok(Self { items })
21 }
22}
23
24enum ComponentAttrItem {
25 Backend {
26 attr_name: Ident,
27 #[allow(dead_code)]
28 equal_token: token::Eq,
29 impl_token: Option<token::Impl>,
30 path: Path,
31 },
32 SlotData {
33 attr_name: Ident,
34 #[allow(dead_code)]
35 equal_token: token::Eq,
36 path: Path,
37 },
38 Translation {
39 attr_name: Ident,
40 #[allow(dead_code)]
41 equal_token: token::Eq,
42 name: Ident,
43 },
44}
45
46impl Parse for ComponentAttrItem {
47 fn parse(input: ParseStream) -> Result<Self> {
48 let attr_name: Ident = input.parse()?;
49 let ret = match attr_name.to_string().as_str() {
50 "Backend" => Self::Backend {
51 attr_name,
52 equal_token: input.parse()?,
53 impl_token: input.parse()?,
54 path: input.parse()?,
55 },
56 "SlotData" => Self::SlotData {
57 attr_name,
58 equal_token: input.parse()?,
59 path: input.parse()?,
60 },
61 "Translation" => Self::Translation {
62 attr_name,
63 equal_token: input.parse()?,
64 name: input.parse()?,
65 },
66 _ => {
67 return Err(Error::new(attr_name.span(), "Unknown attribute parameter"));
68 }
69 };
70 Ok(ret)
71 }
72}
73
74struct ComponentBody {
75 inner: ItemStruct,
76 component_name: proc_macro2::TokenStream,
77 backend_param: proc_macro2::TokenStream,
78 backend_param_in_impl: Option<GenericParam>,
79 slot_kind: proc_macro2::TokenStream,
80 slot_data_ty: proc_macro2::TokenStream,
81 template: Result<Template>,
82 template_field: Ident,
83 locale_group: LocaleGroup,
84}
85
86impl ComponentBody {
87 fn new(attr: ComponentAttr, mut inner: ItemStruct) -> Result<Self> {
88 let mut backend_attr = None;
90 let mut slot_data_attr = None;
91 let mut locale_group_name = None;
92 for item in attr.items {
93 match item {
94 ComponentAttrItem::Backend {
95 attr_name,
96 impl_token,
97 path,
98 ..
99 } => {
100 if backend_attr.is_some() {
101 return Err(Error::new(
102 attr_name.span(),
103 "Duplicated attribute parameter",
104 ));
105 }
106 backend_attr = Some((impl_token, path));
107 }
108 ComponentAttrItem::SlotData {
109 attr_name, path, ..
110 } => {
111 if slot_data_attr.is_some() {
112 return Err(Error::new(
113 attr_name.span(),
114 "Duplicated attribute parameter",
115 ));
116 }
117 slot_data_attr = Some(path);
118 }
119 ComponentAttrItem::Translation {
120 attr_name, name, ..
121 } => {
122 if locale_group_name.is_some() {
123 return Err(Error::new(
124 attr_name.span(),
125 "Duplicated attribute parameter",
126 ));
127 }
128 locale_group_name = Some(name);
129 }
130 }
131 }
132 let backend_param = match &backend_attr {
133 None => quote! { __MBackend },
134 Some((Some(_), path)) => {
135 let span = path.span();
136 quote_spanned! {span=> __MBackend }
137 }
138 Some((None, path)) => {
139 let span = path.span();
140 quote_spanned! {span=> #path }
141 }
142 };
143 let backend_param_in_impl = match backend_attr {
144 None => Some(parse_quote! { __MBackend: maomi::backend::Backend }),
145 Some((Some(_), path)) => {
146 let span = path.span();
147 Some(parse_quote_spanned! {span=> __MBackend: #path })
148 }
149 Some((None, _)) => None,
150 };
151 let slot_data_ty = match slot_data_attr {
152 None => quote! { () },
153 Some(path) => {
154 let span = path.span();
155 quote_spanned! {span=> #path }
156 }
157 };
158 let locale_group = match locale_group_name {
159 None => LocaleGroup::get_default(),
160 Some(x) => LocaleGroup::get(&x.to_string()),
161 };
162
163 let component_name = {
165 let component_name_ident = &inner.ident;
166 let component_type_params = inner.generics.params.iter().map(|x| {
167 let span = x.span();
168 match x {
169 GenericParam::Type(x) => {
170 let x = x.ident.clone();
171 quote_spanned! {span=> #x }
172 }
173 GenericParam::Lifetime(x) => {
174 let x = x.lifetime.clone();
175 quote_spanned! {span=> #x }
176 }
177 GenericParam::Const(x) => {
178 let x = x.ident.clone();
179 quote_spanned! {span=> #x }
180 }
181 }
182 });
183 quote! {
184 #component_name_ident<#(#component_type_params),*>
185 }
186 };
187
188 let mut slot_kind = quote! {
190 maomi::node::NoneSlot
191 };
192
193 let mut template = None;
195 let mut template_field = None;
196 if let Fields::Named(fields) = &mut inner.fields {
197 for field in &mut fields.named {
198 let mut has_template = false;
199 if let Type::Macro(m) = &mut field.ty {
200 if m.mac.path.is_ident("template") {
201 if template.is_some() {
202 Err(syn::Error::new(
203 m.span(),
204 "a component struct can only contain one `template!` field",
205 ))?;
206 continue;
207 }
208 has_template = true;
209 }
210 }
211 if has_template {
212 thread_local! {
213 static EMPTY_TY: Type = parse_str("()").unwrap();
214 }
215 if let Type::Macro(m) = &mut field.ty {
216 let tokens = m.mac.tokens.clone();
217 let t = Template::parse.parse2(tokens);
218 if let Ok(x) = &t {
219 match x.slot_type() {
220 SlotType::None => {}
221 SlotType::StaticSingle => {
222 slot_kind = quote! {
223 maomi::node::StaticSingleSlot
224 };
225 }
226 SlotType::Dynamic => {
227 slot_kind = quote! {
228 maomi::node::DynamicSlot
229 };
230 }
231 }
232 }
233 field.ty = parse_quote! {
234 maomi::template::Template<
235 #component_name,
236 maomi::node::DynNodeList,
237 #slot_kind<maomi::backend::tree::ForestTokenAddr, (maomi::backend::tree::ForestToken, maomi::prop::Prop<#slot_data_ty>)>,
238 >
239 };
240 template = Some(t);
241 template_field = field.ident.clone();
242 } else {
243 unreachable!()
244 }
245 }
246 }
247 } else {
248 Err(syn::Error::new(
249 inner.span(),
250 "a component struct must be a named struct",
251 ))?;
252 }
253 let template = if let Some(t) = template {
254 t
255 } else {
256 return Err(syn::Error::new(
257 inner.span(),
258 "a component struct must contain a `template!` field",
259 ));
260 };
261
262 Ok(Self {
263 inner,
264 component_name,
265 backend_param,
266 backend_param_in_impl,
267 slot_kind,
268 slot_data_ty,
269 template,
270 template_field: template_field.unwrap(),
271 locale_group,
272 })
273 }
274}
275
276impl ToTokens for ComponentBody {
277 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
278 let Self {
279 inner,
280 component_name,
281 backend_param,
282 backend_param_in_impl,
283 slot_kind,
284 slot_data_ty,
285 template,
286 template_field,
287 locale_group,
288 } = self;
289
290 inner.to_tokens(tokens);
292
293 let impl_type_params = {
295 let items = inner
296 .generics
297 .params
298 .iter()
299 .chain(backend_param_in_impl.as_ref());
300 quote! {
301 <#(#items),*>
302 }
303 };
304 let impl_type_params_without_backend_param = {
305 let items = inner.generics.params.iter();
306 quote! {
307 <#(#items),*>
308 }
309 };
310
311 match template.as_ref() {
313 Ok(template) => {
314 let template_children = template.to_children(backend_param, locale_group);
315 quote! {
316 impl #impl_type_params_without_backend_param maomi::template::ComponentSlotKind for #component_name {
317 type SlotChildren<C> = #slot_kind<maomi::backend::tree::ForestTokenAddr, C>;
318 type SlotData = #slot_data_ty;
319 }
320 }.to_tokens(tokens);
321 quote! {
322 impl #impl_type_params maomi::template::ComponentTemplate<#backend_param> for #component_name {
323 type TemplateField = maomi::template::Template<
324 Self,
325 Self::TemplateStructure,
326 Self::SlotChildren<(maomi::backend::tree::ForestToken, maomi::prop::Prop<Self::SlotData>)>,
327 >;
328 type TemplateStructure = maomi::node::DynNodeList;
329
330 #[inline]
331 fn template(&self) -> &Self::TemplateField {
332 &self.#template_field
333 }
334
335 #[inline]
336 fn template_init(&mut self, __m_init: maomi::template::TemplateInit<#component_name>) {
337 self.#template_field.init(__m_init);
338 }
339
340 #[inline]
341 fn template_create_or_update<'__m_b>(
342 &'__m_b mut self,
343 __m_backend_context: &'__m_b maomi::BackendContext<#backend_param>,
344 __m_backend_element: &'__m_b mut maomi::backend::tree::ForestNodeMut<
345 <#backend_param as maomi::backend::Backend>::GeneralElement,
346 >,
347 __m_slot_fn: &mut dyn FnMut(
348 maomi::node::SlotChange<
349 &mut maomi::backend::tree::ForestNodeMut<
350 <#backend_param as maomi::backend::Backend>::GeneralElement,
351 >,
352 &maomi::backend::tree::ForestToken,
353 &Self::SlotData,
354 >,
355 ) -> Result<(), maomi::error::Error>,
356 ) -> Result<(), maomi::error::Error>
357 where
358 Self: Sized,
359 {
360 let __m_event_self_weak = maomi::template::TemplateHelper::component_weak(
361 &self.#template_field,
362 ).unwrap();
363 let mut __m_slot_scopes = self.#template_field.__m_slot_scopes.borrow_mut();
364 let mut __m_slot_scopes = maomi::node::SlotKindTrait::update(&mut *__m_slot_scopes);
365 {
366 let __m_slot_scopes = &mut __m_slot_scopes;
367 let __m_self_owner_weak = self.#template_field.__m_self_owner_weak.as_ref().unwrap();
368 let __m_parent_element = __m_backend_element;
369 let mut __m_children_results = #template_children;
370 if let Some(__m_children) = self.#template_field.__m_structure.as_ref() {
371 __m_children_results(__m_parent_element, Some(&mut *__m_children.borrow_mut()))?;
372 } else {
373 self.#template_field.__m_structure = Some(std::cell::RefCell::new(
374 unsafe { __m_children_results(__m_parent_element, None)?.unwrap_unchecked() }
375 ));
376 }
377 }
378 maomi::node::SlotKindUpdateTrait::finish(__m_slot_scopes, |(n, _)| {
379 __m_slot_fn(maomi::node::SlotChange::Removed(&n))?;
380 Ok(())
381 })?;
382 Ok(())
383 }
384 }
385 }.to_tokens(tokens);
386 }
387 Err(err) => {
388 err.to_compile_error().to_tokens(tokens);
389 }
390 }
391 }
392}
393
394pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
395 let component_attr = parse_macro_input!(attr as ComponentAttr);
396 match ComponentBody::new(component_attr, parse_macro_input!(item as ItemStruct)) {
397 Ok(component_body) => quote! {
398 #component_body
399 }
400 .into(),
401 Err(err) => err.to_compile_error().into(),
402 }
403}