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}