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