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};
 15use crate::meta::NamespaceRef;
 16use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
 17use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
 18use crate::types::{feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty};
 19
 20/// A struct or enum variant's contents.
 21pub(crate) struct Compound {
 22    /// The fields of this compound.
 23    fields: Vec<FieldDef>,
 24}
 25
 26impl Compound {
 27    /// Construct a compound from processed field definitions.
 28    pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
 29        compound_fields: I,
 30    ) -> Result<Self> {
 31        let compound_fields = compound_fields.into_iter();
 32        let size_hint = compound_fields.size_hint();
 33        let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
 34        let mut text_field = None;
 35        for field in compound_fields {
 36            let field = field?;
 37
 38            if field.is_text_field() {
 39                if let Some(other_field) = text_field.as_ref() {
 40                    let mut err = Error::new_spanned(
 41                        field.member(),
 42                        "only one `#[xml(text)]` field allowed per compound",
 43                    );
 44                    err.combine(Error::new(
 45                        *other_field,
 46                        "the other `#[xml(text)]` field is here",
 47                    ));
 48                    return Err(err);
 49                }
 50                text_field = Some(field.member().span())
 51            }
 52
 53            fields.push(field);
 54        }
 55        Ok(Self { fields })
 56    }
 57
 58    /// Construct a compound from fields.
 59    pub(crate) fn from_fields(
 60        compound_fields: &Fields,
 61        container_namespace: &NamespaceRef,
 62    ) -> Result<Self> {
 63        Self::from_field_defs(compound_fields.iter().enumerate().map(|(i, field)| {
 64            let index = match i.try_into() {
 65                Ok(v) => v,
 66                // we are converting to u32, are you crazy?!
 67                // (u32, because syn::Member::Index needs that.)
 68                Err(_) => {
 69                    return Err(Error::new_spanned(
 70                        field,
 71                        "okay, mate, that are way too many fields. get your life together.",
 72                    ))
 73                }
 74            };
 75            FieldDef::from_field(field, index, container_namespace)
 76        }))
 77    }
 78
 79    /// Make and return a set of states which is used to construct the target
 80    /// type from XML events.
 81    ///
 82    /// The states are returned as partial state machine. See the return
 83    /// type's documentation for details.
 84    pub(crate) fn make_from_events_statemachine(
 85        &self,
 86        state_ty_ident: &Ident,
 87        output_name: &ParentRef,
 88        state_prefix: &str,
 89    ) -> Result<FromEventsSubmachine> {
 90        let scope = FromEventsScope::new(state_ty_ident.clone());
 91        let FromEventsScope {
 92            ref attrs,
 93            ref builder_data_ident,
 94            ref text,
 95            ref substate_data,
 96            ref substate_result,
 97            ..
 98        } = scope;
 99
100        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
101        let builder_data_ty: Type = TypePath {
102            qself: None,
103            path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
104        }
105        .into();
106        let mut states = Vec::new();
107
108        let mut builder_data_def = TokenStream::default();
109        let mut builder_data_init = TokenStream::default();
110        let mut output_cons = TokenStream::default();
111        let mut child_matchers = TokenStream::default();
112        let mut text_handler = None;
113        let mut extra_defs = TokenStream::default();
114        let is_tuple = !output_name.is_path();
115
116        for (i, field) in self.fields.iter().enumerate() {
117            let member = field.member();
118            let builder_field_name = mangle_member(member);
119            let part = field.make_builder_part(&scope, output_name)?;
120            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
121
122            match part {
123                FieldBuilderPart::Init {
124                    value: FieldTempInit { ty, init },
125                } => {
126                    builder_data_def.extend(quote! {
127                        #builder_field_name: #ty,
128                    });
129
130                    builder_data_init.extend(quote! {
131                        #builder_field_name: #init,
132                    });
133
134                    if is_tuple {
135                        output_cons.extend(quote! {
136                            #builder_data_ident.#builder_field_name,
137                        });
138                    } else {
139                        output_cons.extend(quote! {
140                            #member: #builder_data_ident.#builder_field_name,
141                        });
142                    }
143                }
144
145                FieldBuilderPart::Text {
146                    value: FieldTempInit { ty, init },
147                    collect,
148                    finalize,
149                } => {
150                    if text_handler.is_some() {
151                        // the existence of only one text handler is enforced
152                        // by Compound's constructor(s).
153                        panic!("more than one field attempts to collect text data");
154                    }
155
156                    builder_data_def.extend(quote! {
157                        #builder_field_name: #ty,
158                    });
159                    builder_data_init.extend(quote! {
160                        #builder_field_name: #init,
161                    });
162                    text_handler = Some(quote! {
163                        #collect
164                        ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
165                            Self::#default_state_ident { #builder_data_ident }
166                        ))
167                    });
168
169                    if is_tuple {
170                        output_cons.extend(quote! {
171                            #finalize,
172                        });
173                    } else {
174                        output_cons.extend(quote! {
175                            #member: #finalize,
176                        });
177                    }
178                }
179
180                FieldBuilderPart::Nested {
181                    extra_defs: field_extra_defs,
182                    value: FieldTempInit { ty, init },
183                    matcher,
184                    builder,
185                    collect,
186                    finalize,
187                } => {
188                    let feed = feed_fn(builder.clone());
189
190                    states.push(State::new_with_builder(
191                        state_name.clone(),
192                        &builder_data_ident,
193                        &builder_data_ty,
194                    ).with_field(
195                        substate_data,
196                        &builder,
197                    ).with_mut(substate_data).with_impl(quote! {
198                        match #feed(&mut #substate_data, ev)? {
199                            ::std::option::Option::Some(#substate_result) => {
200                                #collect
201                                ::std::result::Result::Ok(::std::ops::ControlFlow::Break(Self::#default_state_ident {
202                                    #builder_data_ident,
203                                }))
204                            }
205                            ::std::option::Option::None => {
206                                ::std::result::Result::Ok(::std::ops::ControlFlow::Break(Self::#state_name {
207                                    #builder_data_ident,
208                                    #substate_data,
209                                }))
210                            }
211                        }
212                    }));
213
214                    builder_data_def.extend(quote! {
215                        #builder_field_name: #ty,
216                    });
217
218                    builder_data_init.extend(quote! {
219                        #builder_field_name: #init,
220                    });
221
222                    child_matchers.extend(quote! {
223                        let (name, attrs) = match #matcher {
224                            ::std::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
225                            ::std::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::std::result::Result::Err(e),
226                            ::std::result::Result::Ok(#substate_data) => {
227                                return ::std::result::Result::Ok(::std::ops::ControlFlow::Break(Self::#state_name {
228                                    #builder_data_ident,
229                                    #substate_data,
230                                }))
231                            }
232                        };
233                    });
234
235                    if is_tuple {
236                        output_cons.extend(quote! {
237                            #finalize,
238                        });
239                    } else {
240                        output_cons.extend(quote! {
241                            #member: #finalize,
242                        });
243                    }
244
245                    extra_defs.extend(field_extra_defs);
246                }
247            }
248        }
249
250        let text_handler = match text_handler {
251            Some(v) => v,
252            None => quote! {
253                // note: u8::is_ascii_whitespace includes U+000C, which is not
254                // part of XML's white space definition.'
255                if #text.as_bytes().iter().any(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') {
256                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
257                } else {
258                    ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
259                        Self::#default_state_ident { #builder_data_ident }
260                    ))
261                }
262            },
263        };
264
265        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
266        let unknown_child_err = format!("Unknown child in {}.", output_name);
267
268        let output_cons = match output_name {
269            ParentRef::Named(ref path) => {
270                quote! {
271                    #path { #output_cons }
272                }
273            }
274            ParentRef::Unnamed { .. } => {
275                quote! {
276                    ( #output_cons )
277                }
278            }
279        };
280
281        states.push(State::new_with_builder(
282            default_state_ident.clone(),
283            builder_data_ident,
284            &builder_data_ty,
285        ).with_impl(quote! {
286            match ev {
287                // EndElement in Default state -> done parsing.
288                ::xso::exports::rxml::Event::EndElement(_) => {
289                    ::core::result::Result::Ok(::std::ops::ControlFlow::Continue(
290                        #output_cons
291                    ))
292                }
293                ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
294                    #child_matchers
295                    let _ = (name, attrs);
296                    ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
297                }
298                ::xso::exports::rxml::Event::Text(_, #text) => {
299                    #text_handler
300                }
301                // we ignore these: a correct parser only generates
302                // them at document start, and there we want to indeed
303                // not worry about them being in front of the first
304                // element.
305                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
306                    Self::#default_state_ident { #builder_data_ident }
307                ))
308            }
309        }));
310
311        Ok(FromEventsSubmachine {
312            defs: quote! {
313                #extra_defs
314
315                struct #builder_data_ty {
316                    #builder_data_def
317                }
318            },
319            states,
320            init: quote! {
321                let #builder_data_ident = #builder_data_ty {
322                    #builder_data_init
323                };
324                if #attrs.len() > 0 {
325                    return ::core::result::Result::Err(::xso::error::Error::Other(
326                        #unknown_attr_err,
327                    ).into());
328                }
329                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
330            },
331        })
332    }
333
334    /// Make and return a set of states which is used to destructure the
335    /// target type into XML events.
336    ///
337    /// The states are returned as partial state machine. See the return
338    /// type's documentation for details.
339    ///
340    /// **Important:** The returned submachine is not in functional state!
341    /// It's `init` must be modified so that a variable called `name` of type
342    /// `rxml::QName` is in scope.
343    pub(crate) fn make_as_item_iter_statemachine(
344        &self,
345        input_name: &ParentRef,
346        state_ty_ident: &Ident,
347        state_prefix: &str,
348        lifetime: &Lifetime,
349    ) -> Result<AsItemsSubmachine> {
350        let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
351
352        let element_head_start_state_ident =
353            quote::format_ident!("{}ElementHeadStart", state_prefix);
354        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
355        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
356        let name_ident = quote::format_ident!("name");
357        let ns_ident = quote::format_ident!("ns");
358        let dummy_ident = quote::format_ident!("dummy");
359        let mut states = Vec::new();
360
361        let is_tuple = !input_name.is_path();
362        let mut destructure = TokenStream::default();
363        let mut start_init = TokenStream::default();
364        let mut extra_defs = TokenStream::default();
365
366        states.push(
367            State::new(element_head_start_state_ident.clone())
368                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
369                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
370                .with_field(
371                    &name_ident,
372                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
373                ),
374        );
375
376        let mut element_head_end_idx = states.len();
377        states.push(
378            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
379                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
380            }),
381        );
382
383        for (i, field) in self.fields.iter().enumerate() {
384            let member = field.member();
385            let bound_name = mangle_member(member);
386            let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
387            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
388            let ty = scope.borrow(field.ty().clone());
389
390            match part {
391                FieldIteratorPart::Header { generator } => {
392                    // we have to make sure that we carry our data around in
393                    // all the previous states.
394                    for state in &mut states[..element_head_end_idx] {
395                        state.add_field(&bound_name, &ty);
396                    }
397                    states.insert(
398                        element_head_end_idx,
399                        State::new(state_name)
400                            .with_field(&bound_name, &ty)
401                            .with_impl(quote! {
402                                #generator
403                            }),
404                    );
405                    element_head_end_idx += 1;
406
407                    if is_tuple {
408                        destructure.extend(quote! {
409                            ref #bound_name,
410                        });
411                    } else {
412                        destructure.extend(quote! {
413                            #member: ref #bound_name,
414                        });
415                    }
416                    start_init.extend(quote! {
417                        #bound_name,
418                    });
419                }
420
421                FieldIteratorPart::Text { generator } => {
422                    // we have to make sure that we carry our data around in
423                    // all the previous states.
424                    for state in states.iter_mut() {
425                        state.add_field(&bound_name, &ty);
426                    }
427                    states.push(
428                        State::new(state_name)
429                            .with_field(&bound_name, &ty)
430                            .with_impl(quote! {
431                                #generator.map(|value| ::xso::Item::Text(
432                                    value,
433                                ))
434                            }),
435                    );
436                    if is_tuple {
437                        destructure.extend(quote! {
438                            #bound_name,
439                        });
440                    } else {
441                        destructure.extend(quote! {
442                            #member: #bound_name,
443                        });
444                    }
445                    start_init.extend(quote! {
446                        #bound_name,
447                    });
448                }
449
450                FieldIteratorPart::Content {
451                    extra_defs: field_extra_defs,
452                    value: FieldTempInit { ty, init },
453                    generator,
454                } => {
455                    // we have to make sure that we carry our data around in
456                    // all the previous states.
457                    for state in states.iter_mut() {
458                        state.add_field(&bound_name, &ty);
459                    }
460
461                    states.push(
462                        State::new(state_name.clone())
463                            .with_field(&bound_name, &ty)
464                            .with_mut(&bound_name)
465                            .with_impl(quote! {
466                                #generator?
467                            }),
468                    );
469                    if is_tuple {
470                        destructure.extend(quote! {
471                            #bound_name,
472                        });
473                    } else {
474                        destructure.extend(quote! {
475                            #member: #bound_name,
476                        });
477                    }
478                    start_init.extend(quote! {
479                        #bound_name: #init,
480                    });
481
482                    extra_defs.extend(field_extra_defs);
483                }
484            }
485        }
486
487        states[0].set_impl(quote! {
488            {
489                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
490                    #ns_ident,
491                    #name_ident,
492                ))
493            }
494        });
495
496        states.push(
497            State::new(element_foot_state_ident.clone()).with_impl(quote! {
498                ::core::option::Option::Some(::xso::Item::ElementFoot)
499            }),
500        );
501
502        let destructure = match input_name {
503            ParentRef::Named(ref input_path) => quote! {
504                #input_path { #destructure }
505            },
506            ParentRef::Unnamed { .. } => quote! {
507                ( #destructure )
508            },
509        };
510
511        Ok(AsItemsSubmachine {
512            defs: extra_defs,
513            states,
514            destructure,
515            init: quote! {
516                Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
517            },
518        })
519    }
520}