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