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::{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            ..
 82        } = scope;
 83
 84        let default_state_ident = quote::format_ident!("{}Default", state_prefix);
 85        let builder_data_ty: Type = TypePath {
 86            qself: None,
 87            path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
 88        }
 89        .into();
 90        let mut states = Vec::new();
 91
 92        let mut builder_data_def = TokenStream::default();
 93        let mut builder_data_init = TokenStream::default();
 94        let mut output_cons = TokenStream::default();
 95        let mut text_handler = None;
 96
 97        for field in self.fields.iter() {
 98            let member = field.member();
 99            let builder_field_name = mangle_member(member);
100            let part = field.make_builder_part(&scope, output_name)?;
101
102            match part {
103                FieldBuilderPart::Init {
104                    value: FieldTempInit { ty, init },
105                } => {
106                    builder_data_def.extend(quote! {
107                        #builder_field_name: #ty,
108                    });
109
110                    builder_data_init.extend(quote! {
111                        #builder_field_name: #init,
112                    });
113
114                    output_cons.extend(quote! {
115                        #member: #builder_data_ident.#builder_field_name,
116                    });
117                }
118
119                FieldBuilderPart::Text {
120                    value: FieldTempInit { ty, init },
121                    collect,
122                    finalize,
123                } => {
124                    if text_handler.is_some() {
125                        // the existence of only one text handler is enforced
126                        // by Compound's constructor(s).
127                        panic!("more than one field attempts to collect text data");
128                    }
129
130                    builder_data_def.extend(quote! {
131                        #builder_field_name: #ty,
132                    });
133                    builder_data_init.extend(quote! {
134                        #builder_field_name: #init,
135                    });
136                    text_handler = Some(quote! {
137                        #collect
138                        ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
139                            Self::#default_state_ident { #builder_data_ident }
140                        ))
141                    });
142                    output_cons.extend(quote! {
143                        #member: #finalize,
144                    });
145                }
146            }
147        }
148
149        let text_handler = match text_handler {
150            Some(v) => v,
151            None => quote! {
152                // note: u8::is_ascii_whitespace includes U+000C, which is not
153                // part of XML's white space definition.'
154                if #text.as_bytes().iter().any(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') {
155                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
156                } else {
157                    ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
158                        Self::#default_state_ident { #builder_data_ident }
159                    ))
160                }
161            },
162        };
163
164        let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
165        let unknown_child_err = format!("Unknown child in {}.", output_name);
166
167        let output_cons = match output_name {
168            ParentRef::Named(ref path) => {
169                quote! {
170                    #path { #output_cons }
171                }
172            }
173        };
174
175        states.push(State::new_with_builder(
176            default_state_ident.clone(),
177            builder_data_ident,
178            &builder_data_ty,
179        ).with_impl(quote! {
180            match ev {
181                // EndElement in Default state -> done parsing.
182                ::xso::exports::rxml::Event::EndElement(_) => {
183                    ::core::result::Result::Ok(::std::ops::ControlFlow::Continue(
184                        #output_cons
185                    ))
186                }
187                ::xso::exports::rxml::Event::StartElement(..) => {
188                    ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
189                }
190                ::xso::exports::rxml::Event::Text(_, #text) => {
191                    #text_handler
192                }
193                // we ignore these: a correct parser only generates
194                // them at document start, and there we want to indeed
195                // not worry about them being in front of the first
196                // element.
197                ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
198                    Self::#default_state_ident { #builder_data_ident }
199                ))
200            }
201        }));
202
203        Ok(FromEventsSubmachine {
204            defs: quote! {
205                struct #builder_data_ty {
206                    #builder_data_def
207                }
208            },
209            states,
210            init: quote! {
211                let #builder_data_ident = #builder_data_ty {
212                    #builder_data_init
213                };
214                if #attrs.len() > 0 {
215                    return ::core::result::Result::Err(::xso::error::Error::Other(
216                        #unknown_attr_err,
217                    ).into());
218                }
219                ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
220            },
221        })
222    }
223
224    /// Make and return a set of states which is used to destructure the
225    /// target type into XML events.
226    ///
227    /// The states are returned as partial state machine. See the return
228    /// type's documentation for details.
229    ///
230    /// **Important:** The returned submachine is not in functional state!
231    /// It's `init` must be modified so that a variable called `name` of type
232    /// `rxml::QName` is in scope.
233    pub(crate) fn make_as_item_iter_statemachine(
234        &self,
235        input_name: &Path,
236        state_prefix: &str,
237        lifetime: &Lifetime,
238    ) -> Result<AsItemsSubmachine> {
239        let scope = AsItemsScope::new(lifetime);
240
241        let element_head_start_state_ident =
242            quote::format_ident!("{}ElementHeadStart", state_prefix);
243        let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
244        let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
245        let name_ident = quote::format_ident!("name");
246        let ns_ident = quote::format_ident!("ns");
247        let dummy_ident = quote::format_ident!("dummy");
248        let mut states = Vec::new();
249
250        let mut destructure = TokenStream::default();
251        let mut start_init = TokenStream::default();
252
253        states.push(
254            State::new(element_head_start_state_ident.clone())
255                .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
256                .with_field(&ns_ident, &namespace_ty(Span::call_site()))
257                .with_field(
258                    &name_ident,
259                    &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
260                ),
261        );
262
263        let mut element_head_end_idx = states.len();
264        states.push(
265            State::new(element_head_end_state_ident.clone()).with_impl(quote! {
266                ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
267            }),
268        );
269
270        for (i, field) in self.fields.iter().enumerate() {
271            let member = field.member();
272            let bound_name = mangle_member(member);
273            let part = field.make_iterator_part(&bound_name)?;
274            let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
275            let ty = scope.borrow(field.ty().clone());
276
277            match part {
278                FieldIteratorPart::Header { generator } => {
279                    // we have to make sure that we carry our data around in
280                    // all the previous states.
281                    for state in &mut states[..element_head_end_idx] {
282                        state.add_field(&bound_name, &ty);
283                    }
284                    states.insert(
285                        element_head_end_idx,
286                        State::new(state_name)
287                            .with_field(&bound_name, &ty)
288                            .with_impl(quote! {
289                                #generator
290                            }),
291                    );
292                    element_head_end_idx += 1;
293
294                    destructure.extend(quote! {
295                        #member: ref #bound_name,
296                    });
297                    start_init.extend(quote! {
298                        #bound_name,
299                    });
300                }
301
302                FieldIteratorPart::Text { generator } => {
303                    // we have to make sure that we carry our data around in
304                    // all the previous states.
305                    for state in states.iter_mut() {
306                        state.add_field(&bound_name, &ty);
307                    }
308                    states.push(
309                        State::new(state_name)
310                            .with_field(&bound_name, &ty)
311                            .with_impl(quote! {
312                                #generator.map(|value| ::xso::Item::Text(
313                                    value,
314                                ))
315                            }),
316                    );
317                    destructure.extend(quote! {
318                        #member: #bound_name,
319                    });
320                    start_init.extend(quote! {
321                        #bound_name,
322                    });
323                }
324            }
325        }
326
327        states[0].set_impl(quote! {
328            {
329                ::core::option::Option::Some(::xso::Item::ElementHeadStart(
330                    #ns_ident,
331                    #name_ident,
332                ))
333            }
334        });
335
336        states.push(
337            State::new(element_foot_state_ident.clone()).with_impl(quote! {
338                ::core::option::Option::Some(::xso::Item::ElementFoot)
339            }),
340        );
341
342        Ok(AsItemsSubmachine {
343            defs: TokenStream::default(),
344            states,
345            destructure: quote! {
346                #input_name { #destructure }
347            },
348            init: quote! {
349                Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
350            },
351        })
352    }
353}