enums.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 enums
  8
  9use std::collections::HashMap;
 10
 11use proc_macro2::Span;
 12use quote::quote;
 13use syn::*;
 14
 15use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
 16use crate::compound::Compound;
 17use crate::error_message::ParentRef;
 18use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, XmlCompoundMeta};
 19use crate::state::{AsItemsStateMachine, FromEventsStateMachine};
 20
 21/// The definition of an enum variant, switched on the XML element's name.
 22struct NameVariant {
 23    /// The XML name of the element to map the enum variant to.
 24    name: NameRef,
 25
 26    /// The name of the variant
 27    ident: Ident,
 28
 29    /// The field(s) of this struct.
 30    inner: Compound,
 31}
 32
 33impl NameVariant {
 34    /// Construct a new name-selected variant from its declaration.
 35    fn new(decl: &Variant) -> Result<Self> {
 36        // We destructure here so that we get informed when new fields are
 37        // added and can handle them, either by processing them or raising
 38        // an error if they are present.
 39        let XmlCompoundMeta {
 40            span: meta_span,
 41            namespace,
 42            name,
 43            debug,
 44            builder,
 45            iterator,
 46        } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
 47
 48        reject_key!(debug flag not on "enum variants" only on "enums and structs");
 49        reject_key!(namespace not on "enum variants" only on "enums and structs");
 50        reject_key!(builder not on "enum variants" only on "enums and structs");
 51        reject_key!(iterator not on "enum variants" only on "enums and structs");
 52
 53        let Some(name) = name else {
 54            return Err(Error::new(meta_span, "`name` is required on enum variants"));
 55        };
 56
 57        Ok(Self {
 58            name,
 59            ident: decl.ident.clone(),
 60            inner: Compound::from_fields(&decl.fields)?,
 61        })
 62    }
 63
 64    fn make_from_events_statemachine(
 65        &self,
 66        enum_ident: &Ident,
 67        state_ty_ident: &Ident,
 68    ) -> Result<FromEventsStateMachine> {
 69        let xml_name = &self.name;
 70
 71        Ok(self
 72            .inner
 73            .make_from_events_statemachine(
 74                state_ty_ident,
 75                &ParentRef::Named(Path {
 76                    leading_colon: None,
 77                    segments: [
 78                        PathSegment::from(enum_ident.clone()),
 79                        self.ident.clone().into(),
 80                    ]
 81                    .into_iter()
 82                    .collect(),
 83                }),
 84                &self.ident.to_string(),
 85            )?
 86            .with_augmented_init(|init| {
 87                quote! {
 88                    if name.1 != #xml_name {
 89                        ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
 90                            name,
 91                            attrs,
 92                        })
 93                    } else {
 94                        #init
 95                    }
 96                }
 97            })
 98            .compile())
 99    }
100
101    fn make_as_item_iter_statemachine(
102        &self,
103        xml_namespace: &NamespaceRef,
104        enum_ident: &Ident,
105        item_iter_ty_lifetime: &Lifetime,
106    ) -> Result<AsItemsStateMachine> {
107        let xml_name = &self.name;
108
109        Ok(self
110            .inner
111            .make_as_item_iter_statemachine(
112                &ParentRef::Named(Path {
113                    leading_colon: None,
114                    segments: [
115                        PathSegment::from(enum_ident.clone()),
116                        self.ident.clone().into(),
117                    ]
118                    .into_iter()
119                    .collect(),
120                }),
121                &self.ident.to_string(),
122                &item_iter_ty_lifetime,
123            )?
124            .with_augmented_init(|init| {
125                quote! {
126                    let name = (
127                        ::xso::exports::rxml::Namespace::from(#xml_namespace),
128                        ::std::borrow::Cow::Borrowed(#xml_name),
129                    );
130                    #init
131                }
132            })
133            .compile())
134    }
135}
136
137/// Definition of an enum and how to parse it.
138pub(crate) struct EnumDef {
139    /// The XML namespace of the element to map the enum to.
140    namespace: NamespaceRef,
141
142    /// The variants of the enum.
143    variants: Vec<NameVariant>,
144
145    /// Name of the target type.
146    target_ty_ident: Ident,
147
148    /// Name of the builder type.
149    builder_ty_ident: Ident,
150
151    /// Name of the iterator type.
152    item_iter_ty_ident: Ident,
153
154    /// Flag whether debug mode is enabled.
155    debug: bool,
156}
157
158impl EnumDef {
159    /// Create a new enum from its name, meta, and variants.
160    pub(crate) fn new<'x, I: IntoIterator<Item = &'x Variant>>(
161        ident: &Ident,
162        meta: XmlCompoundMeta,
163        variant_iter: I,
164    ) -> Result<Self> {
165        // We destructure here so that we get informed when new fields are
166        // added and can handle them, either by processing them or raising
167        // an error if they are present.
168        let XmlCompoundMeta {
169            span: meta_span,
170            namespace,
171            name,
172            debug,
173            builder,
174            iterator,
175        } = meta;
176
177        reject_key!(name not on "enums" only on "their variants");
178
179        let Some(namespace) = namespace else {
180            return Err(Error::new(meta_span, "`namespace` is required on enums"));
181        };
182
183        let mut variants = Vec::new();
184        let mut seen_names = HashMap::new();
185        for variant in variant_iter {
186            let variant = NameVariant::new(variant)?;
187            if let Some(other) = seen_names.get(&variant.name) {
188                return Err(Error::new_spanned(
189                    variant.name,
190                    format!(
191                        "duplicate `name` in enum: variants {} and {} have the same XML name",
192                        other, variant.ident
193                    ),
194                ));
195            }
196            seen_names.insert(variant.name.clone(), variant.ident.clone());
197            variants.push(variant);
198        }
199
200        let builder_ty_ident = match builder {
201            Some(v) => v,
202            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
203        };
204
205        let item_iter_ty_ident = match iterator {
206            Some(v) => v,
207            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
208        };
209
210        Ok(Self {
211            namespace,
212            variants,
213            target_ty_ident: ident.clone(),
214            builder_ty_ident,
215            item_iter_ty_ident,
216            debug: debug.is_set(),
217        })
218    }
219}
220
221impl ItemDef for EnumDef {
222    fn make_from_events_builder(
223        &self,
224        vis: &Visibility,
225        name_ident: &Ident,
226        attrs_ident: &Ident,
227    ) -> Result<FromXmlParts> {
228        let xml_namespace = &self.namespace;
229        let target_ty_ident = &self.target_ty_ident;
230        let builder_ty_ident = &self.builder_ty_ident;
231        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
232
233        let mut statemachine = FromEventsStateMachine::new();
234        for variant in self.variants.iter() {
235            statemachine
236                .merge(variant.make_from_events_statemachine(target_ty_ident, &state_ty_ident)?);
237        }
238
239        statemachine.set_pre_init(quote! {
240            if name.0 != #xml_namespace {
241                return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
242                    name,
243                    attrs,
244                })
245            }
246        });
247
248        let defs = statemachine.render(
249            vis,
250            builder_ty_ident,
251            &state_ty_ident,
252            &TypePath {
253                qself: None,
254                path: target_ty_ident.clone().into(),
255            }
256            .into(),
257        )?;
258
259        Ok(FromXmlParts {
260            defs,
261            from_events_body: quote! {
262                #builder_ty_ident::new(#name_ident, #attrs_ident)
263            },
264            builder_ty_ident: builder_ty_ident.clone(),
265        })
266    }
267
268    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
269        let target_ty_ident = &self.target_ty_ident;
270        let item_iter_ty_ident = &self.item_iter_ty_ident;
271        let item_iter_ty_lifetime = Lifetime {
272            apostrophe: Span::call_site(),
273            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
274        };
275        let item_iter_ty = Type::Path(TypePath {
276            qself: None,
277            path: Path {
278                leading_colon: None,
279                segments: [PathSegment {
280                    ident: item_iter_ty_ident.clone(),
281                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
282                        colon2_token: None,
283                        lt_token: token::Lt {
284                            spans: [Span::call_site()],
285                        },
286                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
287                            .into_iter()
288                            .collect(),
289                        gt_token: token::Gt {
290                            spans: [Span::call_site()],
291                        },
292                    }),
293                }]
294                .into_iter()
295                .collect(),
296            },
297        });
298        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
299
300        let mut statemachine = AsItemsStateMachine::new();
301        for variant in self.variants.iter() {
302            statemachine.merge(variant.make_as_item_iter_statemachine(
303                &self.namespace,
304                target_ty_ident,
305                &item_iter_ty_lifetime,
306            )?);
307        }
308
309        let defs = statemachine.render(
310            vis,
311            &TypePath {
312                qself: None,
313                path: target_ty_ident.clone().into(),
314            }
315            .into(),
316            &state_ty_ident,
317            &item_iter_ty_lifetime,
318            &item_iter_ty,
319        )?;
320
321        Ok(AsXmlParts {
322            defs,
323            as_xml_iter_body: quote! {
324                #item_iter_ty_ident::new(self)
325            },
326            item_iter_ty,
327            item_iter_ty_lifetime,
328        })
329    }
330
331    fn debug(&self) -> bool {
332        self.debug
333    }
334}