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        Ok(Self {
 79            namespace,
 80            name,
 81            inner: Compound::from_fields(fields)?,
 82            target_ty_ident: ident.clone(),
 83            builder_ty_ident: quote::format_ident!("{}FromXmlBuilder", ident),
 84            item_iter_ty_ident: quote::format_ident!("{}AsXmlIterator", ident),
 85            debug: meta.debug.is_set(),
 86        })
 87    }
 88
 89    pub(crate) fn make_from_events_builder(
 90        &self,
 91        vis: &Visibility,
 92        name_ident: &Ident,
 93        attrs_ident: &Ident,
 94    ) -> Result<FromXmlParts> {
 95        let xml_namespace = &self.namespace;
 96        let xml_name = &self.name;
 97
 98        let target_ty_ident = &self.target_ty_ident;
 99        let builder_ty_ident = &self.builder_ty_ident;
100        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
101
102        let defs = self
103            .inner
104            .make_from_events_statemachine(
105                &state_ty_ident,
106                &Path::from(target_ty_ident.clone()).into(),
107                "Struct",
108            )?
109            .with_augmented_init(|init| {
110                quote! {
111                    if name.0 != #xml_namespace || name.1 != #xml_name {
112                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
113                            name,
114                            attrs,
115                        })
116                    } else {
117                        #init
118                    }
119                }
120            })
121            .compile()
122            .render(
123                vis,
124                builder_ty_ident,
125                &state_ty_ident,
126                &TypePath {
127                    qself: None,
128                    path: target_ty_ident.clone().into(),
129                }
130                .into(),
131            )?;
132
133        Ok(FromXmlParts {
134            defs,
135            from_events_body: quote! {
136                #builder_ty_ident::new(#name_ident, #attrs_ident)
137            },
138            builder_ty_ident: builder_ty_ident.clone(),
139        })
140    }
141
142    pub(crate) fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
143        let xml_namespace = &self.namespace;
144        let xml_name = &self.name;
145
146        let target_ty_ident = &self.target_ty_ident;
147        let item_iter_ty_ident = &self.item_iter_ty_ident;
148        let item_iter_ty_lifetime = Lifetime {
149            apostrophe: Span::call_site(),
150            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
151        };
152        let item_iter_ty = Type::Path(TypePath {
153            qself: None,
154            path: Path {
155                leading_colon: None,
156                segments: [PathSegment {
157                    ident: item_iter_ty_ident.clone(),
158                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
159                        colon2_token: None,
160                        lt_token: token::Lt {
161                            spans: [Span::call_site()],
162                        },
163                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
164                            .into_iter()
165                            .collect(),
166                        gt_token: token::Gt {
167                            spans: [Span::call_site()],
168                        },
169                    }),
170                }]
171                .into_iter()
172                .collect(),
173            },
174        });
175        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
176
177        let defs = self
178            .inner
179            .make_as_item_iter_statemachine(
180                &target_ty_ident.clone().into(),
181                "Struct",
182                &item_iter_ty_lifetime,
183            )?
184            .with_augmented_init(|init| {
185                quote! {
186                    let name = (
187                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
188                        ::std::borrow::Cow::Borrowed(#xml_name),
189                    );
190                    #init
191                }
192            })
193            .compile()
194            .render(
195                vis,
196                &TypePath {
197                    qself: None,
198                    path: target_ty_ident.clone().into(),
199                }
200                .into(),
201                &state_ty_ident,
202                &item_iter_ty_lifetime,
203                &item_iter_ty,
204            )?;
205
206        Ok(AsXmlParts {
207            defs,
208            as_xml_iter_body: quote! {
209                #item_iter_ty_ident::new(self)
210            },
211            item_iter_ty,
212            item_iter_ty_lifetime,
213        })
214    }
215
216    pub(crate) fn debug(&self) -> bool {
217        self.debug
218    }
219}