xso_proc/
compound.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 the insides of compound structures (structs and enum variants)
8
9use proc_macro2::{Span, TokenStream};
10use quote::quote;
11use syn::{spanned::Spanned, *};
12
13use crate::error_message::ParentRef;
14use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher};
15use crate::meta::NamespaceRef;
16use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
17use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
18use crate::types::{
19    default_fn, discard_builder_ty, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty,
20    ref_ty, unknown_attribute_policy_path, unknown_child_policy_path,
21};
22
23fn resolve_policy(policy: Option<Ident>, mut enum_ref: Path) -> Expr {
24    match policy {
25        Some(ident) => {
26            enum_ref.segments.push(ident.into());
27            Expr::Path(ExprPath {
28                attrs: Vec::new(),
29                qself: None,
30                path: enum_ref,
31            })
32        }
33        None => {
34            let default_fn = default_fn(Type::Path(TypePath {
35                qself: None,
36                path: enum_ref,
37            }));
38            Expr::Call(ExprCall {
39                attrs: Vec::new(),
40                func: Box::new(default_fn),
41                paren_token: token::Paren::default(),
42                args: punctuated::Punctuated::new(),
43            })
44        }
45    }
46}
47
48/// A struct or enum variant's contents.
49pub(crate) struct Compound {
50    /// The fields of this compound.
51    fields: Vec<FieldDef>,
52
53    /// Policy defining how to handle unknown attributes.
54    unknown_attribute_policy: Expr,
55
56    /// Policy defining how to handle unknown children.
57    unknown_child_policy: Expr,
58}
59
60impl Compound {
61    /// Construct a compound from processed field definitions.
62    pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
63        compound_fields: I,
64        unknown_attribute_policy: Option<Ident>,
65        unknown_child_policy: Option<Ident>,
66    ) -> Result<Self> {
67        let unknown_attribute_policy = resolve_policy(
68            unknown_attribute_policy,
69            unknown_attribute_policy_path(Span::call_site()),
70        );
71        let unknown_child_policy = resolve_policy(
72            unknown_child_policy,
73            unknown_child_policy_path(Span::call_site()),
74        );
75        let compound_fields = compound_fields.into_iter();
76        let size_hint = compound_fields.size_hint();
77        let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
78        let mut text_field = None;
79        for field in compound_fields {
80            let field = field?;
81
82            if field.is_text_field() {
83                if let Some(other_field) = text_field.as_ref() {
84                    let mut err = Error::new_spanned(
85                        field.member(),
86                        "only one `#[xml(text)]` field allowed per compound",
87                    );
88                    err.combine(Error::new(
89                        *other_field,
90                        "the other `#[xml(text)]` field is here",
91                    ));
92                    return Err(err);
93                }
94                text_field = Some(field.member().span())
95            }
96
97            fields.push(field);
98        }
99        Ok(Self {
100            fields,
101            unknown_attribute_policy,
102            unknown_child_policy,
103        })
104    }
105
106    /// Construct a compound from fields.
107    pub(crate) fn from_fields(
108        compound_fields: &Fields,
109        container_namespace: &NamespaceRef,
110        unknown_attribute_policy: Option<Ident>,
111        unknown_child_policy: Option<Ident>,
112    ) -> Result<Self> {
113        Self::from_field_defs(
114            compound_fields.iter().enumerate().map(|(i, field)| {
115                let index = match i.try_into() {
116                    Ok(v) => v,
117                    // we are converting to u32, are you crazy?!
118                    // (u32, because syn::Member::Index needs that.)
119                    Err(_) => {
120                        return Err(Error::new_spanned(
121                            field,
122                            "okay, mate, that are way too many fields. get your life together.",
123                        ))
124                    }
125                };
126                FieldDef::from_field(field, index, container_namespace)
127            }),
128            unknown_attribute_policy,
129            unknown_child_policy,
130        )
131    }
132
133    /// Make and return a set of states which is used to construct the target
134    /// type from XML events.
135    ///
136    /// The states are returned as partial state machine. See the return
137    /// type's documentation for details.
138    pub(crate) fn make_from_events_statemachine(
139        &self,
140        state_ty_ident: &Ident,
141        output_name: &ParentRef,
142        state_prefix: &str,
143    ) -> Result<FromEventsSubmachine> {
144        let scope = FromEventsScope::new(state_ty_ident.clone());
145        let FromEventsScope {
146            ref attrs,
147            ref builder_data_ident,
148            ref text,
149            ref substate_data,
150            ref substate_result,
151            ..
152        } = scope;
153
154        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
155        let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
156        let builder_data_ty: Type = TypePath {
157            qself: None,
158            path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
159        }
160        .into();
161        let mut states = Vec::new();
162
163        let mut builder_data_def = TokenStream::default();
164        let mut builder_data_init = TokenStream::default();
165        let mut output_cons = TokenStream::default();
166        let mut child_matchers = TokenStream::default();
167        let mut fallback_child_matcher = None;
168        let mut text_handler = None;
169        let mut extra_defs = TokenStream::default();
170        let is_tuple = !output_name.is_path();
171
172        for (i, field) in self.fields.iter().enumerate() {
173            let member = field.member();
174            let builder_field_name = mangle_member(member);
175            let part = field.make_builder_part(&scope, output_name)?;
176            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
177
178            match part {
179                FieldBuilderPart::Init {
180                    value: FieldTempInit { ty, init },
181                } => {
182                    builder_data_def.extend(quote! {
183                        #builder_field_name: #ty,
184                    });
185
186                    builder_data_init.extend(quote! {
187                        #builder_field_name: #init,
188                    });
189
190                    if is_tuple {
191                        output_cons.extend(quote! {
192                            #builder_data_ident.#builder_field_name,
193                        });
194                    } else {
195                        output_cons.extend(quote! {
196                            #member: #builder_data_ident.#builder_field_name,
197                        });
198                    }
199                }
200
201                FieldBuilderPart::Text {
202                    value: FieldTempInit { ty, init },
203                    collect,
204                    finalize,
205                } => {
206                    if text_handler.is_some() {
207                        // the existence of only one text handler is enforced
208                        // by Compound's constructor(s).
209                        panic!("more than one field attempts to collect text data");
210                    }
211
212                    builder_data_def.extend(quote! {
213                        #builder_field_name: #ty,
214                    });
215                    builder_data_init.extend(quote! {
216                        #builder_field_name: #init,
217                    });
218                    text_handler = Some(quote! {
219                        #collect
220                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
221                            Self::#default_state_ident { #builder_data_ident }
222                        ))
223                    });
224
225                    if is_tuple {
226                        output_cons.extend(quote! {
227                            #finalize,
228                        });
229                    } else {
230                        output_cons.extend(quote! {
231                            #member: #finalize,
232                        });
233                    }
234                }
235
236                FieldBuilderPart::Nested {
237                    extra_defs: field_extra_defs,
238                    value: FieldTempInit { ty, init },
239                    matcher,
240                    builder,
241                    collect,
242                    finalize,
243                } => {
244                    let feed = feed_fn(builder.clone());
245
246                    states.push(State::new_with_builder(
247                        state_name.clone(),
248                        &builder_data_ident,
249                        &builder_data_ty,
250                    ).with_field(
251                        substate_data,
252                        &builder,
253                    ).with_mut(substate_data).with_impl(quote! {
254                        match #feed(&mut #substate_data, ev)? {
255                            ::core::option::Option::Some(#substate_result) => {
256                                #collect
257                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
258                                    #builder_data_ident,
259                                }))
260                            }
261                            ::core::option::Option::None => {
262                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
263                                    #builder_data_ident,
264                                    #substate_data,
265                                }))
266                            }
267                        }
268                    }));
269
270                    builder_data_def.extend(quote! {
271                        #builder_field_name: #ty,
272                    });
273
274                    builder_data_init.extend(quote! {
275                        #builder_field_name: #init,
276                    });
277
278                    match matcher {
279                        NestedMatcher::Selective(matcher) => {
280                            child_matchers.extend(quote! {
281                                let (name, attrs) = match #matcher {
282                                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
283                                    ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
284                                    ::core::result::Result::Ok(#substate_data) => {
285                                        return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
286                                            #builder_data_ident,
287                                            #substate_data,
288                                        }))
289                                    }
290                                };
291                            });
292                        }
293                        NestedMatcher::Fallback(matcher) => {
294                            if let Some((span, _)) = fallback_child_matcher.as_ref() {
295                                let mut err = Error::new(
296                                    field.span(),
297                                    "more than one field is attempting to consume all unmatched child elements"
298                                );
299                                err.combine(Error::new(
300                                    *span,
301                                    "the previous field collecting all unmatched child elements is here"
302                                ));
303                                return Err(err);
304                            }
305
306                            let matcher = quote! {
307                                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
308                                    #builder_data_ident,
309                                    #substate_data: { #matcher },
310                                }))
311                            };
312
313                            fallback_child_matcher = Some((field.span(), matcher));
314                        }
315                    }
316
317                    if is_tuple {
318                        output_cons.extend(quote! {
319                            #finalize,
320                        });
321                    } else {
322                        output_cons.extend(quote! {
323                            #member: #finalize,
324                        });
325                    }
326
327                    extra_defs.extend(field_extra_defs);
328                }
329            }
330        }
331
332        let text_handler = match text_handler {
333            Some(v) => v,
334            None => quote! {
335                // note: u8::is_ascii_whitespace includes U+000C, which is not
336                // part of XML's white space definition.'
337                if !::xso::is_xml_whitespace(#text.as_bytes()) {
338                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
339                } else {
340                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
341                        Self::#default_state_ident { #builder_data_ident }
342                    ))
343                }
344            },
345        };
346
347        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
348        let unknown_child_err = format!("Unknown child in {}.", output_name);
349        let unknown_child_policy = &self.unknown_child_policy;
350
351        let output_cons = match output_name {
352            ParentRef::Named(ref path) => {
353                quote! {
354                    #path { #output_cons }
355                }
356            }
357            ParentRef::Unnamed { .. } => {
358                quote! {
359                    ( #output_cons )
360                }
361            }
362        };
363
364        let discard_builder_ty = discard_builder_ty(Span::call_site());
365        let discard_feed = feed_fn(discard_builder_ty.clone());
366        let child_fallback = match fallback_child_matcher {
367            Some((_, matcher)) => matcher,
368            None => quote! {
369                let _ = (name, attrs);
370                let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?;
371                ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
372                    #builder_data_ident,
373                    #substate_data: #discard_builder_ty::new(),
374                }))
375            },
376        };
377
378        states.push(State::new_with_builder(
379            discard_state_ident.clone(),
380            &builder_data_ident,
381            &builder_data_ty,
382        ).with_field(
383            substate_data,
384            &discard_builder_ty,
385        ).with_mut(substate_data).with_impl(quote! {
386            match #discard_feed(&mut #substate_data, ev)? {
387                ::core::option::Option::Some(#substate_result) => {
388                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
389                        #builder_data_ident,
390                    }))
391                }
392                ::core::option::Option::None => {
393                    ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
394                        #builder_data_ident,
395                        #substate_data,
396                    }))
397                }
398            }
399        }));
400
401        states.push(State::new_with_builder(
402            default_state_ident.clone(),
403            builder_data_ident,
404            &builder_data_ty,
405        ).with_impl(quote! {
406            match ev {
407                // EndElement in Default state -> done parsing.
408                ::xso::exports::rxml::Event::EndElement(_) => {
409                    ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
410                        #output_cons
411                    ))
412                }
413                ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
414                    #child_matchers
415                    #child_fallback
416                }
417                ::xso::exports::rxml::Event::Text(_, #text) => {
418                    #text_handler
419                }
420                // we ignore these: a correct parser only generates
421                // them at document start, and there we want to indeed
422                // not worry about them being in front of the first
423                // element.
424                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
425                    Self::#default_state_ident { #builder_data_ident }
426                ))
427            }
428        }));
429
430        let unknown_attribute_policy = &self.unknown_attribute_policy;
431
432        Ok(FromEventsSubmachine {
433            defs: quote! {
434                #extra_defs
435
436                struct #builder_data_ty {
437                    #builder_data_def
438                }
439            },
440            states,
441            init: quote! {
442                let #builder_data_ident = #builder_data_ty {
443                    #builder_data_init
444                };
445                if #attrs.len() > 0 {
446                    let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
447                }
448                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
449            },
450        })
451    }
452
453    /// Make and return a set of states which is used to destructure the
454    /// target type into XML events.
455    ///
456    /// The states are returned as partial state machine. See the return
457    /// type's documentation for details.
458    ///
459    /// **Important:** The returned submachine is not in functional state!
460    /// It's `init` must be modified so that a variable called `name` of type
461    /// `rxml::QName` is in scope.
462    pub(crate) fn make_as_item_iter_statemachine(
463        &self,
464        input_name: &ParentRef,
465        state_ty_ident: &Ident,
466        state_prefix: &str,
467        lifetime: &Lifetime,
468    ) -> Result<AsItemsSubmachine> {
469        let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
470
471        let element_head_start_state_ident =
472            quote::format_ident!("{}ElementHeadStart", state_prefix);
473        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
474        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
475        let name_ident = quote::format_ident!("name");
476        let ns_ident = quote::format_ident!("ns");
477        let dummy_ident = quote::format_ident!("dummy");
478        let mut states = Vec::new();
479
480        let is_tuple = !input_name.is_path();
481        let mut destructure = TokenStream::default();
482        let mut start_init = TokenStream::default();
483        let mut extra_defs = TokenStream::default();
484
485        states.push(
486            State::new(element_head_start_state_ident.clone())
487                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
488                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
489                .with_field(
490                    &name_ident,
491                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
492                ),
493        );
494
495        let mut element_head_end_idx = states.len();
496        states.push(
497            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
498                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
499            }),
500        );
501
502        for (i, field) in self.fields.iter().enumerate() {
503            let member = field.member();
504            let bound_name = mangle_member(member);
505            let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
506            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
507            let ty = scope.borrow(field.ty().clone());
508
509            match part {
510                FieldIteratorPart::Header { generator } => {
511                    // we have to make sure that we carry our data around in
512                    // all the previous states.
513                    for state in &mut states[..element_head_end_idx] {
514                        state.add_field(&bound_name, &ty);
515                    }
516                    states.insert(
517                        element_head_end_idx,
518                        State::new(state_name)
519                            .with_field(&bound_name, &ty)
520                            .with_impl(quote! {
521                                #generator
522                            }),
523                    );
524                    element_head_end_idx += 1;
525
526                    if is_tuple {
527                        destructure.extend(quote! {
528                            ref #bound_name,
529                        });
530                    } else {
531                        destructure.extend(quote! {
532                            #member: ref #bound_name,
533                        });
534                    }
535                    start_init.extend(quote! {
536                        #bound_name,
537                    });
538                }
539
540                FieldIteratorPart::Text { generator } => {
541                    // we have to make sure that we carry our data around in
542                    // all the previous states.
543                    for state in states.iter_mut() {
544                        state.add_field(&bound_name, &ty);
545                    }
546                    states.push(
547                        State::new(state_name)
548                            .with_field(&bound_name, &ty)
549                            .with_impl(quote! {
550                                #generator.map(|value| ::xso::Item::Text(
551                                    value,
552                                ))
553                            }),
554                    );
555                    if is_tuple {
556                        destructure.extend(quote! {
557                            #bound_name,
558                        });
559                    } else {
560                        destructure.extend(quote! {
561                            #member: #bound_name,
562                        });
563                    }
564                    start_init.extend(quote! {
565                        #bound_name,
566                    });
567                }
568
569                FieldIteratorPart::Content {
570                    extra_defs: field_extra_defs,
571                    value: FieldTempInit { ty, init },
572                    generator,
573                } => {
574                    // we have to make sure that we carry our data around in
575                    // all the previous states.
576                    for state in states.iter_mut() {
577                        state.add_field(&bound_name, &ty);
578                    }
579
580                    states.push(
581                        State::new(state_name.clone())
582                            .with_field(&bound_name, &ty)
583                            .with_mut(&bound_name)
584                            .with_impl(quote! {
585                                #generator?
586                            }),
587                    );
588                    if is_tuple {
589                        destructure.extend(quote! {
590                            #bound_name,
591                        });
592                    } else {
593                        destructure.extend(quote! {
594                            #member: #bound_name,
595                        });
596                    }
597                    start_init.extend(quote! {
598                        #bound_name: #init,
599                    });
600
601                    extra_defs.extend(field_extra_defs);
602                }
603            }
604        }
605
606        states[0].set_impl(quote! {
607            {
608                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
609                    #ns_ident,
610                    #name_ident,
611                ))
612            }
613        });
614
615        states.push(
616            State::new(element_foot_state_ident.clone()).with_impl(quote! {
617                ::core::option::Option::Some(::xso::Item::ElementFoot)
618            }),
619        );
620
621        let destructure = match input_name {
622            ParentRef::Named(ref input_path) => quote! {
623                #input_path { #destructure }
624            },
625            ParentRef::Unnamed { .. } => quote! {
626                ( #destructure )
627            },
628        };
629
630        Ok(AsItemsSubmachine {
631            defs: extra_defs,
632            states,
633            destructure,
634            init: quote! {
635                Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
636            },
637        })
638    }
639
640    /// Return a reference to this compound's only field's type.
641    ///
642    /// If the compound does not have exactly one field, this function returns
643    /// None.
644    pub(crate) fn single_ty(&self) -> Option<&Type> {
645        if self.fields.len() > 1 {
646            return None;
647        }
648        self.fields.get(0).map(|x| x.ty())
649    }
650
651    /// Construct a tuple type with this compound's field's types in the same
652    /// order as they appear in the compound.
653    pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
654        TypeTuple {
655            paren_token: token::Paren::default(),
656            elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
657        }
658    }
659
660    /// Construct a tuple type with this compound's field's types in the same
661    /// order as they appear in the compound.
662    pub(crate) fn to_single_or_tuple_ty(&self) -> Type {
663        match self.single_ty() {
664            None => self.to_tuple_ty().into(),
665            Some(v) => v.clone(),
666        }
667    }
668
669    /// Construct a tuple type with references to this compound's field's
670    /// types in the same order as they appear in the compound, with the given
671    /// lifetime.
672    pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
673        TypeTuple {
674            paren_token: token::Paren::default(),
675            elems: self
676                .fields
677                .iter()
678                .map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
679                .collect(),
680        }
681    }
682
683    /// Return the number of fields in this compound.
684    pub(crate) fn field_count(&self) -> usize {
685        self.fields.len()
686    }
687}