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