structs.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 structs
  8
  9use proc_macro2::{Span, TokenStream};
 10use quote::quote;
 11use syn::*;
 12
 13use crate::compound::Compound;
 14use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
 15
 16/// Parts necessary to construct a `::xso::FromXml` implementation.
 17pub(crate) struct FromXmlParts {
 18    /// Additional items necessary for the implementation.
 19    pub(crate) defs: TokenStream,
 20
 21    /// The body of the `::xso::FromXml::from_xml` function.
 22    pub(crate) from_events_body: TokenStream,
 23
 24    /// The name of the type which is the `::xso::FromXml::Builder`.
 25    pub(crate) builder_ty_ident: Ident,
 26}
 27
 28/// Parts necessary to construct a `::xso::AsXml` implementation.
 29pub(crate) struct AsXmlParts {
 30    /// Additional items necessary for the implementation.
 31    pub(crate) defs: TokenStream,
 32
 33    /// The body of the `::xso::AsXml::as_xml_iter` function.
 34    pub(crate) as_xml_iter_body: TokenStream,
 35
 36    /// The type which is the `::xso::AsXml::ItemIter`.
 37    pub(crate) item_iter_ty: Type,
 38
 39    /// The lifetime name used in `item_iter_ty`.
 40    pub(crate) item_iter_ty_lifetime: Lifetime,
 41}
 42
 43/// Definition of a struct and how to parse it.
 44pub(crate) struct StructDef {
 45    /// The XML namespace of the element to map the struct to.
 46    namespace: NamespaceRef,
 47
 48    /// The XML name of the element to map the struct to.
 49    name: NameRef,
 50
 51    /// The field(s) of this struct.
 52    inner: Compound,
 53
 54    /// Name of the target type.
 55    target_ty_ident: Ident,
 56
 57    /// Name of the builder type.
 58    builder_ty_ident: Ident,
 59
 60    /// Name of the iterator type.
 61    item_iter_ty_ident: Ident,
 62
 63    /// Flag whether debug mode is enabled.
 64    debug: bool,
 65}
 66
 67impl StructDef {
 68    /// Create a new struct from its name, meta, and fields.
 69    pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
 70        let Some(namespace) = meta.namespace else {
 71            return Err(Error::new(meta.span, "`namespace` is required on structs"));
 72        };
 73
 74        let Some(name) = meta.name else {
 75            return Err(Error::new(meta.span, "`name` is required on structs"));
 76        };
 77
 78        let builder_ty_ident = match meta.builder {
 79            Some(v) => v,
 80            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
 81        };
 82
 83        let item_iter_ty_ident = match meta.iterator {
 84            Some(v) => v,
 85            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
 86        };
 87
 88        Ok(Self {
 89            namespace,
 90            name,
 91            inner: Compound::from_fields(fields)?,
 92            target_ty_ident: ident.clone(),
 93            builder_ty_ident,
 94            item_iter_ty_ident,
 95            debug: meta.debug.is_set(),
 96        })
 97    }
 98
 99    pub(crate) fn make_from_events_builder(
100        &self,
101        vis: &Visibility,
102        name_ident: &Ident,
103        attrs_ident: &Ident,
104    ) -> Result<FromXmlParts> {
105        let xml_namespace = &self.namespace;
106        let xml_name = &self.name;
107
108        let target_ty_ident = &self.target_ty_ident;
109        let builder_ty_ident = &self.builder_ty_ident;
110        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
111
112        let defs = self
113            .inner
114            .make_from_events_statemachine(
115                &state_ty_ident,
116                &Path::from(target_ty_ident.clone()).into(),
117                "Struct",
118            )?
119            .with_augmented_init(|init| {
120                quote! {
121                    if name.0 != #xml_namespace || name.1 != #xml_name {
122                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
123                            name,
124                            attrs,
125                        })
126                    } else {
127                        #init
128                    }
129                }
130            })
131            .compile()
132            .render(
133                vis,
134                builder_ty_ident,
135                &state_ty_ident,
136                &TypePath {
137                    qself: None,
138                    path: target_ty_ident.clone().into(),
139                }
140                .into(),
141            )?;
142
143        Ok(FromXmlParts {
144            defs,
145            from_events_body: quote! {
146                #builder_ty_ident::new(#name_ident, #attrs_ident)
147            },
148            builder_ty_ident: builder_ty_ident.clone(),
149        })
150    }
151
152    pub(crate) fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
153        let xml_namespace = &self.namespace;
154        let xml_name = &self.name;
155
156        let target_ty_ident = &self.target_ty_ident;
157        let item_iter_ty_ident = &self.item_iter_ty_ident;
158        let item_iter_ty_lifetime = Lifetime {
159            apostrophe: Span::call_site(),
160            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
161        };
162        let item_iter_ty = Type::Path(TypePath {
163            qself: None,
164            path: Path {
165                leading_colon: None,
166                segments: [PathSegment {
167                    ident: item_iter_ty_ident.clone(),
168                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
169                        colon2_token: None,
170                        lt_token: token::Lt {
171                            spans: [Span::call_site()],
172                        },
173                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
174                            .into_iter()
175                            .collect(),
176                        gt_token: token::Gt {
177                            spans: [Span::call_site()],
178                        },
179                    }),
180                }]
181                .into_iter()
182                .collect(),
183            },
184        });
185        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
186
187        let defs = self
188            .inner
189            .make_as_item_iter_statemachine(
190                &target_ty_ident.clone().into(),
191                "Struct",
192                &item_iter_ty_lifetime,
193            )?
194            .with_augmented_init(|init| {
195                quote! {
196                    let name = (
197                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
198                        ::std::borrow::Cow::Borrowed(#xml_name),
199                    );
200                    #init
201                }
202            })
203            .compile()
204            .render(
205                vis,
206                &TypePath {
207                    qself: None,
208                    path: target_ty_ident.clone().into(),
209                }
210                .into(),
211                &state_ty_ident,
212                &item_iter_ty_lifetime,
213                &item_iter_ty,
214            )?;
215
216        Ok(AsXmlParts {
217            defs,
218            as_xml_iter_body: quote! {
219                #item_iter_ty_ident::new(self)
220            },
221            item_iter_ty,
222            item_iter_ty_lifetime,
223        })
224    }
225
226    pub(crate) fn debug(&self) -> bool {
227        self.debug
228    }
229}