compound.rs

  1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7//! Handling of the insides of compound structures (structs and enum variants)
  8
  9use proc_macro2::{Span, TokenStream};
 10use quote::quote;
 11use syn::{spanned::Spanned, *};
 12
 13use crate::error_message::ParentRef;
 14use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher};
 15use crate::meta::NamespaceRef;
 16use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
 17use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
 18use crate::types::{feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty, ref_ty};
 19
 20/// A struct or enum variant's contents.
 21pub(crate) struct Compound {
 22    /// The fields of this compound.
 23    fields: Vec<FieldDef>,
 24}
 25
 26impl Compound {
 27    /// Construct a compound from processed field definitions.
 28    pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
 29        compound_fields: I,
 30    ) -> Result<Self> {
 31        let compound_fields = compound_fields.into_iter();
 32        let size_hint = compound_fields.size_hint();
 33        let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
 34        let mut text_field = None;
 35        for field in compound_fields {
 36            let field = field?;
 37
 38            if field.is_text_field() {
 39                if let Some(other_field) = text_field.as_ref() {
 40                    let mut err = Error::new_spanned(
 41                        field.member(),
 42                        "only one `#[xml(text)]` field allowed per compound",
 43                    );
 44                    err.combine(Error::new(
 45                        *other_field,
 46                        "the other `#[xml(text)]` field is here",
 47                    ));
 48                    return Err(err);
 49                }
 50                text_field = Some(field.member().span())
 51            }
 52
 53            fields.push(field);
 54        }
 55        Ok(Self { fields })
 56    }
 57
 58    /// Construct a compound from fields.
 59    pub(crate) fn from_fields(
 60        compound_fields: &Fields,
 61        container_namespace: &NamespaceRef,
 62    ) -> Result<Self> {
 63        Self::from_field_defs(compound_fields.iter().enumerate().map(|(i, field)| {
 64            let index = match i.try_into() {
 65                Ok(v) => v,
 66                // we are converting to u32, are you crazy?!
 67                // (u32, because syn::Member::Index needs that.)
 68                Err(_) => {
 69                    return Err(Error::new_spanned(
 70                        field,
 71                        "okay, mate, that are way too many fields. get your life together.",
 72                    ))
 73                }
 74            };
 75            FieldDef::from_field(field, index, container_namespace)
 76        }))
 77    }
 78
 79    /// Make and return a set of states which is used to construct the target
 80    /// type from XML events.
 81    ///
 82    /// The states are returned as partial state machine. See the return
 83    /// type's documentation for details.
 84    pub(crate) fn make_from_events_statemachine(
 85        &self,
 86        state_ty_ident: &Ident,
 87        output_name: &ParentRef,
 88        state_prefix: &str,
 89    ) -> Result<FromEventsSubmachine> {
 90        let scope = FromEventsScope::new(state_ty_ident.clone());
 91        let FromEventsScope {
 92            ref attrs,
 93            ref builder_data_ident,
 94            ref text,
 95            ref substate_data,
 96            ref substate_result,
 97            ..
 98        } = scope;
 99
100        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
101        let builder_data_ty: Type = TypePath {
102            qself: None,
103            path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
104        }
105        .into();
106        let mut states = Vec::new();
107
108        let mut builder_data_def = TokenStream::default();
109        let mut builder_data_init = TokenStream::default();
110        let mut output_cons = TokenStream::default();
111        let mut child_matchers = TokenStream::default();
112        let mut fallback_child_matcher = None;
113        let mut text_handler = None;
114        let mut extra_defs = TokenStream::default();
115        let is_tuple = !output_name.is_path();
116
117        for (i, field) in self.fields.iter().enumerate() {
118            let member = field.member();
119            let builder_field_name = mangle_member(member);
120            let part = field.make_builder_part(&scope, output_name)?;
121            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
122
123            match part {
124                FieldBuilderPart::Init {
125                    value: FieldTempInit { ty, init },
126                } => {
127                    builder_data_def.extend(quote! {
128                        #builder_field_name: #ty,
129                    });
130
131                    builder_data_init.extend(quote! {
132                        #builder_field_name: #init,
133                    });
134
135                    if is_tuple {
136                        output_cons.extend(quote! {
137                            #builder_data_ident.#builder_field_name,
138                        });
139                    } else {
140                        output_cons.extend(quote! {
141                            #member: #builder_data_ident.#builder_field_name,
142                        });
143                    }
144                }
145
146                FieldBuilderPart::Text {
147                    value: FieldTempInit { ty, init },
148                    collect,
149                    finalize,
150                } => {
151                    if text_handler.is_some() {
152                        // the existence of only one text handler is enforced
153                        // by Compound's constructor(s).
154                        panic!("more than one field attempts to collect text data");
155                    }
156
157                    builder_data_def.extend(quote! {
158                        #builder_field_name: #ty,
159                    });
160                    builder_data_init.extend(quote! {
161                        #builder_field_name: #init,
162                    });
163                    text_handler = Some(quote! {
164                        #collect
165                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
166                            Self::#default_state_ident { #builder_data_ident }
167                        ))
168                    });
169
170                    if is_tuple {
171                        output_cons.extend(quote! {
172                            #finalize,
173                        });
174                    } else {
175                        output_cons.extend(quote! {
176                            #member: #finalize,
177                        });
178                    }
179                }
180
181                FieldBuilderPart::Nested {
182                    extra_defs: field_extra_defs,
183                    value: FieldTempInit { ty, init },
184                    matcher,
185                    builder,
186                    collect,
187                    finalize,
188                } => {
189                    let feed = feed_fn(builder.clone());
190
191                    states.push(State::new_with_builder(
192                        state_name.clone(),
193                        &builder_data_ident,
194                        &builder_data_ty,
195                    ).with_field(
196                        substate_data,
197                        &builder,
198                    ).with_mut(substate_data).with_impl(quote! {
199                        match #feed(&mut #substate_data, ev)? {
200                            ::core::option::Option::Some(#substate_result) => {
201                                #collect
202                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
203                                    #builder_data_ident,
204                                }))
205                            }
206                            ::core::option::Option::None => {
207                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
208                                    #builder_data_ident,
209                                    #substate_data,
210                                }))
211                            }
212                        }
213                    }));
214
215                    builder_data_def.extend(quote! {
216                        #builder_field_name: #ty,
217                    });
218
219                    builder_data_init.extend(quote! {
220                        #builder_field_name: #init,
221                    });
222
223                    match matcher {
224                        NestedMatcher::Selective(matcher) => {
225                            child_matchers.extend(quote! {
226                                let (name, attrs) = match #matcher {
227                                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
228                                    ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
229                                    ::core::result::Result::Ok(#substate_data) => {
230                                        return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
231                                            #builder_data_ident,
232                                            #substate_data,
233                                        }))
234                                    }
235                                };
236                            });
237                        }
238                        NestedMatcher::Fallback(matcher) => {
239                            if let Some((span, _)) = fallback_child_matcher.as_ref() {
240                                let mut err = Error::new(
241                                    field.span(),
242                                    "more than one field is attempting to consume all unmatched child elements"
243                                );
244                                err.combine(Error::new(
245                                    *span,
246                                    "the previous field collecting all unmatched child elements is here"
247                                ));
248                                return Err(err);
249                            }
250
251                            let matcher = quote! {
252                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
253                                    #builder_data_ident,
254                                    #substate_data: { #matcher },
255                                }))
256                            };
257
258                            fallback_child_matcher = Some((field.span(), matcher));
259                        }
260                    }
261
262                    if is_tuple {
263                        output_cons.extend(quote! {
264                            #finalize,
265                        });
266                    } else {
267                        output_cons.extend(quote! {
268                            #member: #finalize,
269                        });
270                    }
271
272                    extra_defs.extend(field_extra_defs);
273                }
274            }
275        }
276
277        let text_handler = match text_handler {
278            Some(v) => v,
279            None => quote! {
280                // note: u8::is_ascii_whitespace includes U+000C, which is not
281                // part of XML's white space definition.'
282                if !::xso::is_xml_whitespace(#text.as_bytes()) {
283                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
284                } else {
285                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
286                        Self::#default_state_ident { #builder_data_ident }
287                    ))
288                }
289            },
290        };
291
292        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
293        let unknown_child_err = format!("Unknown child in {}.", output_name);
294
295        let output_cons = match output_name {
296            ParentRef::Named(ref path) => {
297                quote! {
298                    #path { #output_cons }
299                }
300            }
301            ParentRef::Unnamed { .. } => {
302                quote! {
303                    ( #output_cons )
304                }
305            }
306        };
307
308        let child_fallback = match fallback_child_matcher {
309            Some((_, matcher)) => matcher,
310            None => quote! {
311                let _ = (name, attrs);
312                ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
313            },
314        };
315
316        states.push(State::new_with_builder(
317            default_state_ident.clone(),
318            builder_data_ident,
319            &builder_data_ty,
320        ).with_impl(quote! {
321            match ev {
322                // EndElement in Default state -> done parsing.
323                ::xso::exports::rxml::Event::EndElement(_) => {
324                    ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
325                        #output_cons
326                    ))
327                }
328                ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
329                    #child_matchers
330                    #child_fallback
331                }
332                ::xso::exports::rxml::Event::Text(_, #text) => {
333                    #text_handler
334                }
335                // we ignore these: a correct parser only generates
336                // them at document start, and there we want to indeed
337                // not worry about them being in front of the first
338                // element.
339                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
340                    Self::#default_state_ident { #builder_data_ident }
341                ))
342            }
343        }));
344
345        Ok(FromEventsSubmachine {
346            defs: quote! {
347                #extra_defs
348
349                struct #builder_data_ty {
350                    #builder_data_def
351                }
352            },
353            states,
354            init: quote! {
355                let #builder_data_ident = #builder_data_ty {
356                    #builder_data_init
357                };
358                if #attrs.len() > 0 {
359                    return ::core::result::Result::Err(::xso::error::Error::Other(
360                        #unknown_attr_err,
361                    ).into());
362                }
363                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
364            },
365        })
366    }
367
368    /// Make and return a set of states which is used to destructure the
369    /// target type into XML events.
370    ///
371    /// The states are returned as partial state machine. See the return
372    /// type's documentation for details.
373    ///
374    /// **Important:** The returned submachine is not in functional state!
375    /// It's `init` must be modified so that a variable called `name` of type
376    /// `rxml::QName` is in scope.
377    pub(crate) fn make_as_item_iter_statemachine(
378        &self,
379        input_name: &ParentRef,
380        state_ty_ident: &Ident,
381        state_prefix: &str,
382        lifetime: &Lifetime,
383    ) -> Result<AsItemsSubmachine> {
384        let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
385
386        let element_head_start_state_ident =
387            quote::format_ident!("{}ElementHeadStart", state_prefix);
388        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
389        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
390        let name_ident = quote::format_ident!("name");
391        let ns_ident = quote::format_ident!("ns");
392        let dummy_ident = quote::format_ident!("dummy");
393        let mut states = Vec::new();
394
395        let is_tuple = !input_name.is_path();
396        let mut destructure = TokenStream::default();
397        let mut start_init = TokenStream::default();
398        let mut extra_defs = TokenStream::default();
399
400        states.push(
401            State::new(element_head_start_state_ident.clone())
402                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
403                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
404                .with_field(
405                    &name_ident,
406                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
407                ),
408        );
409
410        let mut element_head_end_idx = states.len();
411        states.push(
412            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
413                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
414            }),
415        );
416
417        for (i, field) in self.fields.iter().enumerate() {
418            let member = field.member();
419            let bound_name = mangle_member(member);
420            let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
421            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
422            let ty = scope.borrow(field.ty().clone());
423
424            match part {
425                FieldIteratorPart::Header { generator } => {
426                    // we have to make sure that we carry our data around in
427                    // all the previous states.
428                    for state in &mut states[..element_head_end_idx] {
429                        state.add_field(&bound_name, &ty);
430                    }
431                    states.insert(
432                        element_head_end_idx,
433                        State::new(state_name)
434                            .with_field(&bound_name, &ty)
435                            .with_impl(quote! {
436                                #generator
437                            }),
438                    );
439                    element_head_end_idx += 1;
440
441                    if is_tuple {
442                        destructure.extend(quote! {
443                            ref #bound_name,
444                        });
445                    } else {
446                        destructure.extend(quote! {
447                            #member: ref #bound_name,
448                        });
449                    }
450                    start_init.extend(quote! {
451                        #bound_name,
452                    });
453                }
454
455                FieldIteratorPart::Text { generator } => {
456                    // we have to make sure that we carry our data around in
457                    // all the previous states.
458                    for state in states.iter_mut() {
459                        state.add_field(&bound_name, &ty);
460                    }
461                    states.push(
462                        State::new(state_name)
463                            .with_field(&bound_name, &ty)
464                            .with_impl(quote! {
465                                #generator.map(|value| ::xso::Item::Text(
466                                    value,
467                                ))
468                            }),
469                    );
470                    if is_tuple {
471                        destructure.extend(quote! {
472                            #bound_name,
473                        });
474                    } else {
475                        destructure.extend(quote! {
476                            #member: #bound_name,
477                        });
478                    }
479                    start_init.extend(quote! {
480                        #bound_name,
481                    });
482                }
483
484                FieldIteratorPart::Content {
485                    extra_defs: field_extra_defs,
486                    value: FieldTempInit { ty, init },
487                    generator,
488                } => {
489                    // we have to make sure that we carry our data around in
490                    // all the previous states.
491                    for state in states.iter_mut() {
492                        state.add_field(&bound_name, &ty);
493                    }
494
495                    states.push(
496                        State::new(state_name.clone())
497                            .with_field(&bound_name, &ty)
498                            .with_mut(&bound_name)
499                            .with_impl(quote! {
500                                #generator?
501                            }),
502                    );
503                    if is_tuple {
504                        destructure.extend(quote! {
505                            #bound_name,
506                        });
507                    } else {
508                        destructure.extend(quote! {
509                            #member: #bound_name,
510                        });
511                    }
512                    start_init.extend(quote! {
513                        #bound_name: #init,
514                    });
515
516                    extra_defs.extend(field_extra_defs);
517                }
518            }
519        }
520
521        states[0].set_impl(quote! {
522            {
523                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
524                    #ns_ident,
525                    #name_ident,
526                ))
527            }
528        });
529
530        states.push(
531            State::new(element_foot_state_ident.clone()).with_impl(quote! {
532                ::core::option::Option::Some(::xso::Item::ElementFoot)
533            }),
534        );
535
536        let destructure = match input_name {
537            ParentRef::Named(ref input_path) => quote! {
538                #input_path { #destructure }
539            },
540            ParentRef::Unnamed { .. } => quote! {
541                ( #destructure )
542            },
543        };
544
545        Ok(AsItemsSubmachine {
546            defs: extra_defs,
547            states,
548            destructure,
549            init: quote! {
550                Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
551            },
552        })
553    }
554
555    /// Return a reference to this compound's only field's type.
556    ///
557    /// If the compound does not have exactly one field, this function returns
558    /// None.
559    pub(crate) fn single_ty(&self) -> Option<&Type> {
560        if self.fields.len() > 1 {
561            return None;
562        }
563        self.fields.get(0).map(|x| x.ty())
564    }
565
566    /// Construct a tuple type with this compound's field's types in the same
567    /// order as they appear in the compound.
568    pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
569        TypeTuple {
570            paren_token: token::Paren::default(),
571            elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
572        }
573    }
574
575    /// Construct a tuple type with references to this compound's field's
576    /// types in the same order as they appear in the compound, with the given
577    /// lifetime.
578    pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
579        TypeTuple {
580            paren_token: token::Paren::default(),
581            elems: self
582                .fields
583                .iter()
584                .map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
585                .collect(),
586        }
587    }
588
589    /// Return the number of fields in this compound.
590    pub(crate) fn field_count(&self) -> usize {
591        self.fields.len()
592    }
593}