2024-06-22 15:35:56 +02:00
|
|
|
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
|
|
|
|
|
//
|
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
|
|
|
|
//! Handling of the insides of compound structures (structs and enum variants)
|
|
|
|
|
|
|
|
|
|
use proc_macro2::{Span, TokenStream};
|
2024-06-23 09:06:32 +02:00
|
|
|
use quote::quote;
|
2024-07-01 07:46:02 +02:00
|
|
|
use syn::{spanned::Spanned, *};
|
2024-06-22 15:35:56 +02:00
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
use crate::error_message::ParentRef;
|
|
|
|
|
use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit};
|
2024-07-09 17:01:42 +02:00
|
|
|
use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
|
|
|
|
|
use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
|
|
|
|
|
use crate::types::{namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty};
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
|
|
|
/// A struct or enum variant's contents.
|
2024-06-23 09:06:32 +02:00
|
|
|
pub(crate) struct Compound {
|
|
|
|
|
/// The fields of this compound.
|
|
|
|
|
fields: Vec<FieldDef>,
|
|
|
|
|
}
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
|
|
|
impl Compound {
|
|
|
|
|
/// Construct a compound from fields.
|
|
|
|
|
pub(crate) fn from_fields(compound_fields: &Fields) -> Result<Self> {
|
2024-06-23 09:06:32 +02:00
|
|
|
let mut fields = Vec::with_capacity(compound_fields.len());
|
2024-07-01 07:46:02 +02:00
|
|
|
let mut text_field = None;
|
2024-06-23 09:06:32 +02:00
|
|
|
for (i, field) in compound_fields.iter().enumerate() {
|
|
|
|
|
let index = match i.try_into() {
|
|
|
|
|
Ok(v) => v,
|
|
|
|
|
// we are converting to u32, are you crazy?!
|
|
|
|
|
// (u32, because syn::Member::Index needs that.)
|
|
|
|
|
Err(_) => {
|
|
|
|
|
return Err(Error::new_spanned(
|
|
|
|
|
field,
|
|
|
|
|
"okay, mate, that are way too many fields. get your life together.",
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-01 07:46:02 +02:00
|
|
|
let field = FieldDef::from_field(field, index)?;
|
|
|
|
|
|
|
|
|
|
if field.is_text_field() {
|
|
|
|
|
if let Some(other_field) = text_field.as_ref() {
|
|
|
|
|
let mut err = Error::new_spanned(
|
|
|
|
|
field.member(),
|
|
|
|
|
"only one `#[xml(text)]` field allowed per compound",
|
|
|
|
|
);
|
|
|
|
|
err.combine(Error::new(
|
|
|
|
|
*other_field,
|
|
|
|
|
"the other `#[xml(text)]` field is here",
|
|
|
|
|
));
|
|
|
|
|
return Err(err);
|
|
|
|
|
}
|
|
|
|
|
text_field = Some(field.member().span())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fields.push(field);
|
2024-06-22 15:35:56 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
Ok(Self { fields })
|
2024-06-22 15:35:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Make and return a set of states which is used to construct the target
|
|
|
|
|
/// type from XML events.
|
|
|
|
|
///
|
|
|
|
|
/// The states are returned as partial state machine. See the return
|
|
|
|
|
/// type's documentation for details.
|
|
|
|
|
pub(crate) fn make_from_events_statemachine(
|
|
|
|
|
&self,
|
|
|
|
|
state_ty_ident: &Ident,
|
2024-06-23 09:06:32 +02:00
|
|
|
output_name: &ParentRef,
|
2024-06-22 15:35:56 +02:00
|
|
|
state_prefix: &str,
|
|
|
|
|
) -> Result<FromEventsSubmachine> {
|
2024-06-23 09:06:32 +02:00
|
|
|
let scope = FromEventsScope::new();
|
2024-06-26 17:54:36 +02:00
|
|
|
let FromEventsScope {
|
|
|
|
|
ref attrs,
|
|
|
|
|
ref builder_data_ident,
|
|
|
|
|
ref text,
|
|
|
|
|
..
|
|
|
|
|
} = scope;
|
2024-06-23 09:06:32 +02:00
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
let default_state_ident = quote::format_ident!("{}Default", state_prefix);
|
|
|
|
|
let builder_data_ty: Type = TypePath {
|
|
|
|
|
qself: None,
|
|
|
|
|
path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
|
|
|
|
|
}
|
|
|
|
|
.into();
|
|
|
|
|
let mut states = Vec::new();
|
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
let mut builder_data_def = TokenStream::default();
|
|
|
|
|
let mut builder_data_init = TokenStream::default();
|
|
|
|
|
let mut output_cons = TokenStream::default();
|
2024-06-26 17:54:36 +02:00
|
|
|
let mut text_handler = None;
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
|
|
|
for field in self.fields.iter() {
|
|
|
|
|
let member = field.member();
|
|
|
|
|
let builder_field_name = mangle_member(member);
|
2024-07-03 11:15:33 +02:00
|
|
|
let part = field.make_builder_part(&scope, output_name)?;
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
|
|
|
match part {
|
|
|
|
|
FieldBuilderPart::Init {
|
|
|
|
|
value: FieldTempInit { ty, init },
|
|
|
|
|
} => {
|
|
|
|
|
builder_data_def.extend(quote! {
|
|
|
|
|
#builder_field_name: #ty,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
builder_data_init.extend(quote! {
|
|
|
|
|
#builder_field_name: #init,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
output_cons.extend(quote! {
|
|
|
|
|
#member: #builder_data_ident.#builder_field_name,
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
|
|
|
|
FieldBuilderPart::Text {
|
|
|
|
|
value: FieldTempInit { ty, init },
|
|
|
|
|
collect,
|
|
|
|
|
finalize,
|
|
|
|
|
} => {
|
|
|
|
|
if text_handler.is_some() {
|
2024-07-01 07:46:02 +02:00
|
|
|
// the existence of only one text handler is enforced
|
|
|
|
|
// by Compound's constructor(s).
|
|
|
|
|
panic!("more than one field attempts to collect text data");
|
2024-06-26 17:54:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder_data_def.extend(quote! {
|
|
|
|
|
#builder_field_name: #ty,
|
|
|
|
|
});
|
|
|
|
|
builder_data_init.extend(quote! {
|
|
|
|
|
#builder_field_name: #init,
|
|
|
|
|
});
|
|
|
|
|
text_handler = Some(quote! {
|
|
|
|
|
#collect
|
|
|
|
|
::core::result::Result::Ok(::std::ops::ControlFlow::Break(
|
|
|
|
|
Self::#default_state_ident { #builder_data_ident }
|
|
|
|
|
))
|
|
|
|
|
});
|
|
|
|
|
output_cons.extend(quote! {
|
|
|
|
|
#member: #finalize,
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-26 17:54:36 +02:00
|
|
|
let text_handler = match text_handler {
|
|
|
|
|
Some(v) => v,
|
|
|
|
|
None => quote! {
|
2024-07-01 07:40:28 +02:00
|
|
|
// note: u8::is_ascii_whitespace includes U+000C, which is not
|
|
|
|
|
// part of XML's white space definition.'
|
|
|
|
|
if #text.as_bytes().iter().any(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') {
|
|
|
|
|
::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
|
|
|
|
|
} else {
|
|
|
|
|
::core::result::Result::Ok(::std::ops::ControlFlow::Break(
|
|
|
|
|
Self::#default_state_ident { #builder_data_ident }
|
|
|
|
|
))
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
|
|
|
|
|
let unknown_child_err = format!("Unknown child in {}.", output_name);
|
|
|
|
|
|
|
|
|
|
let output_cons = match output_name {
|
|
|
|
|
ParentRef::Named(ref path) => {
|
|
|
|
|
quote! {
|
|
|
|
|
#path { #output_cons }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-06-22 15:35:56 +02:00
|
|
|
|
|
|
|
|
states.push(State::new_with_builder(
|
|
|
|
|
default_state_ident.clone(),
|
2024-07-03 11:15:33 +02:00
|
|
|
builder_data_ident,
|
2024-06-22 15:35:56 +02:00
|
|
|
&builder_data_ty,
|
|
|
|
|
).with_impl(quote! {
|
|
|
|
|
match ev {
|
|
|
|
|
// EndElement in Default state -> done parsing.
|
|
|
|
|
::xso::exports::rxml::Event::EndElement(_) => {
|
|
|
|
|
::core::result::Result::Ok(::std::ops::ControlFlow::Continue(
|
|
|
|
|
#output_cons
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
::xso::exports::rxml::Event::StartElement(..) => {
|
|
|
|
|
::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
::xso::exports::rxml::Event::Text(_, #text) => {
|
|
|
|
|
#text_handler
|
2024-06-22 15:35:56 +02:00
|
|
|
}
|
|
|
|
|
// we ignore these: a correct parser only generates
|
|
|
|
|
// them at document start, and there we want to indeed
|
|
|
|
|
// not worry about them being in front of the first
|
|
|
|
|
// element.
|
|
|
|
|
::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
|
|
|
|
|
Self::#default_state_ident { #builder_data_ident }
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Ok(FromEventsSubmachine {
|
|
|
|
|
defs: quote! {
|
2024-06-23 09:06:32 +02:00
|
|
|
struct #builder_data_ty {
|
|
|
|
|
#builder_data_def
|
|
|
|
|
}
|
2024-06-22 15:35:56 +02:00
|
|
|
},
|
|
|
|
|
states,
|
|
|
|
|
init: quote! {
|
2024-06-23 09:06:32 +02:00
|
|
|
let #builder_data_ident = #builder_data_ty {
|
|
|
|
|
#builder_data_init
|
|
|
|
|
};
|
|
|
|
|
if #attrs.len() > 0 {
|
2024-06-22 15:35:56 +02:00
|
|
|
return ::core::result::Result::Err(::xso::error::Error::Other(
|
|
|
|
|
#unknown_attr_err,
|
|
|
|
|
).into());
|
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
|
2024-06-22 15:35:56 +02:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Make and return a set of states which is used to destructure the
|
|
|
|
|
/// target type into XML events.
|
|
|
|
|
///
|
|
|
|
|
/// The states are returned as partial state machine. See the return
|
|
|
|
|
/// type's documentation for details.
|
|
|
|
|
///
|
|
|
|
|
/// **Important:** The returned submachine is not in functional state!
|
|
|
|
|
/// It's `init` must be modified so that a variable called `name` of type
|
|
|
|
|
/// `rxml::QName` is in scope.
|
2024-07-09 17:01:42 +02:00
|
|
|
pub(crate) fn make_as_item_iter_statemachine(
|
2024-06-22 15:35:56 +02:00
|
|
|
&self,
|
|
|
|
|
input_name: &Path,
|
|
|
|
|
state_prefix: &str,
|
2024-07-09 17:01:42 +02:00
|
|
|
lifetime: &Lifetime,
|
|
|
|
|
) -> Result<AsItemsSubmachine> {
|
|
|
|
|
let scope = AsItemsScope::new(lifetime);
|
|
|
|
|
|
|
|
|
|
let element_head_start_state_ident =
|
|
|
|
|
quote::format_ident!("{}ElementHeadStart", state_prefix);
|
|
|
|
|
let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
|
|
|
|
|
let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
|
2024-06-22 15:35:56 +02:00
|
|
|
let name_ident = quote::format_ident!("name");
|
2024-07-09 17:01:42 +02:00
|
|
|
let ns_ident = quote::format_ident!("ns");
|
|
|
|
|
let dummy_ident = quote::format_ident!("dummy");
|
2024-06-22 15:35:56 +02:00
|
|
|
let mut states = Vec::new();
|
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
let mut destructure = TokenStream::default();
|
|
|
|
|
let mut start_init = TokenStream::default();
|
|
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
states.push(
|
2024-07-09 17:01:42 +02:00
|
|
|
State::new(element_head_start_state_ident.clone())
|
|
|
|
|
.with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
|
|
|
|
|
.with_field(&ns_ident, &namespace_ty(Span::call_site()))
|
|
|
|
|
.with_field(
|
|
|
|
|
&name_ident,
|
|
|
|
|
&ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let mut element_head_end_idx = states.len();
|
|
|
|
|
states.push(
|
|
|
|
|
State::new(element_head_end_state_ident.clone()).with_impl(quote! {
|
|
|
|
|
::core::option::Option::Some(::xso::Item::ElementHeadEnd)
|
|
|
|
|
}),
|
2024-06-22 15:35:56 +02:00
|
|
|
);
|
|
|
|
|
|
2024-06-26 17:54:36 +02:00
|
|
|
for (i, field) in self.fields.iter().enumerate() {
|
2024-06-23 09:06:32 +02:00
|
|
|
let member = field.member();
|
|
|
|
|
let bound_name = mangle_member(member);
|
2024-07-09 17:01:42 +02:00
|
|
|
let part = field.make_iterator_part(&bound_name)?;
|
2024-06-26 17:54:36 +02:00
|
|
|
let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
|
2024-07-09 17:01:42 +02:00
|
|
|
let ty = scope.borrow(field.ty().clone());
|
2024-06-23 09:06:32 +02:00
|
|
|
|
|
|
|
|
match part {
|
2024-07-09 17:01:42 +02:00
|
|
|
FieldIteratorPart::Header { generator } => {
|
|
|
|
|
// we have to make sure that we carry our data around in
|
|
|
|
|
// all the previous states.
|
|
|
|
|
for state in &mut states[..element_head_end_idx] {
|
|
|
|
|
state.add_field(&bound_name, &ty);
|
|
|
|
|
}
|
|
|
|
|
states.insert(
|
|
|
|
|
element_head_end_idx,
|
|
|
|
|
State::new(state_name)
|
|
|
|
|
.with_field(&bound_name, &ty)
|
|
|
|
|
.with_impl(quote! {
|
|
|
|
|
#generator
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
element_head_end_idx += 1;
|
|
|
|
|
|
2024-06-23 09:06:32 +02:00
|
|
|
destructure.extend(quote! {
|
2024-07-09 17:01:42 +02:00
|
|
|
#member: ref #bound_name,
|
2024-06-23 09:06:32 +02:00
|
|
|
});
|
|
|
|
|
start_init.extend(quote! {
|
|
|
|
|
#bound_name,
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-06-26 17:54:36 +02:00
|
|
|
|
|
|
|
|
FieldIteratorPart::Text { generator } => {
|
|
|
|
|
// we have to make sure that we carry our data around in
|
|
|
|
|
// all the previous states.
|
|
|
|
|
for state in states.iter_mut() {
|
2024-07-09 17:01:42 +02:00
|
|
|
state.add_field(&bound_name, &ty);
|
2024-06-26 17:54:36 +02:00
|
|
|
}
|
|
|
|
|
states.push(
|
|
|
|
|
State::new(state_name)
|
2024-07-09 17:01:42 +02:00
|
|
|
.with_field(&bound_name, &ty)
|
2024-06-26 17:54:36 +02:00
|
|
|
.with_impl(quote! {
|
2024-07-09 17:01:42 +02:00
|
|
|
#generator.map(|value| ::xso::Item::Text(
|
2024-06-26 18:26:13 +02:00
|
|
|
value,
|
2024-06-26 17:54:36 +02:00
|
|
|
))
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
destructure.extend(quote! {
|
|
|
|
|
#member: #bound_name,
|
|
|
|
|
});
|
|
|
|
|
start_init.extend(quote! {
|
|
|
|
|
#bound_name,
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-06-23 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
states[0].set_impl(quote! {
|
|
|
|
|
{
|
2024-07-09 17:01:42 +02:00
|
|
|
::core::option::Option::Some(::xso::Item::ElementHeadStart(
|
|
|
|
|
#ns_ident,
|
2024-06-23 09:06:32 +02:00
|
|
|
#name_ident,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-06-22 15:35:56 +02:00
|
|
|
states.push(
|
2024-07-09 17:01:42 +02:00
|
|
|
State::new(element_foot_state_ident.clone()).with_impl(quote! {
|
|
|
|
|
::core::option::Option::Some(::xso::Item::ElementFoot)
|
2024-06-22 15:35:56 +02:00
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-09 17:01:42 +02:00
|
|
|
Ok(AsItemsSubmachine {
|
2024-06-22 15:35:56 +02:00
|
|
|
defs: TokenStream::default(),
|
|
|
|
|
states,
|
|
|
|
|
destructure: quote! {
|
2024-06-23 09:06:32 +02:00
|
|
|
#input_name { #destructure }
|
2024-06-22 15:35:56 +02:00
|
|
|
},
|
|
|
|
|
init: quote! {
|
2024-07-09 17:01:42 +02:00
|
|
|
Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
|
2024-06-22 15:35:56 +02:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|