Simplify deserializer macro setup

This commit is contained in:
Dirkjan Ochtman 2022-09-07 09:21:48 +02:00
parent 01c896a9b2
commit 674039a791
2 changed files with 231 additions and 250 deletions

View File

@ -3,6 +3,235 @@ use quote::quote;
use crate::{ContainerMeta, FieldMeta, Namespace};
pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
let ident = &input.ident;
let container_meta = ContainerMeta::from_derive(input);
let default_namespace = match &container_meta.ns.uri {
Some(ns) => quote!(#ns),
None => quote!(""),
};
let mut xml_generics = input.generics.clone();
let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site()));
xml.bounds
.extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone()));
xml_generics.params.push(xml.into());
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
let (xml_impl_generics, _, _) = xml_generics.split_for_impl();
let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new(););
for (k, v) in container_meta.ns.prefixes.iter() {
namespaces_map.extend(quote!(
namespaces_map.insert(#k, #v);
))
}
// Varying values
let mut elements_tokens = Tokens::default();
let mut attributes_tokens = Tokens::default();
// Common values
let mut declare_values = TokenStream::new();
let mut return_val = TokenStream::new();
match &input.data {
syn::Data::Struct(ref data) => {
match data.fields {
syn::Fields::Named(ref fields) => {
fields.named.iter().enumerate().for_each(|(index, field)| {
let field_meta = FieldMeta::from_field(field);
let tokens = match field_meta.attribute {
true => &mut attributes_tokens,
false => &mut elements_tokens,
};
process_field(
field,
index,
&mut declare_values,
&mut return_val,
tokens,
field_meta,
&container_meta,
);
});
}
syn::Fields::Unnamed(_) => panic!("unamed"),
syn::Fields::Unit => {}
};
}
_ => todo!(),
};
// Elements
let elements_enum = elements_tokens.enum_;
let elements_consts = elements_tokens.consts;
let elements_names = elements_tokens.names;
let elem_type_match = elements_tokens.match_;
// Attributes
let attributes_enum = attributes_tokens.enum_;
let attributes_consts = attributes_tokens.consts;
let attributes_names = attributes_tokens.names;
let attr_type_match = attributes_tokens.match_;
let name = ident.to_string();
quote!(
impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause {
fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result<Self, ::instant_xml::Error> {
use ::instant_xml::de::{Deserializer, Id, Node};
use ::instant_xml::Error;
use ::core::marker::PhantomData;
enum __Elements {
#elements_enum
__Ignore,
}
enum __Attributes {
#attributes_enum
__Ignore,
}
#declare_values
loop {
let node = match deserializer.next() {
Some(result) => result?,
None => break,
};
match node {
Node::Attribute(attr) => {
let id = deserializer.attribute_id(&attr)?;
let field = {
#attributes_consts
match id {
#attributes_names
_ => __Attributes::__Ignore
}
};
match field {
#attr_type_match
__Attributes::__Ignore => {}
}
}
Node::Open(data) => {
let id = deserializer.element_id(&data)?;
let element = {
#elements_consts
match id {
#elements_names
_ => __Elements::__Ignore
}
};
match element {
#elem_type_match
__Elements::__Ignore => {
let mut nested = deserializer.nested(data);
nested.ignore()?;
}
}
}
_ => return Err(Error::UnexpectedState),
}
}
Ok(Self { #return_val })
}
const KIND: ::instant_xml::de::Kind = ::instant_xml::de::Kind::Element(::instant_xml::de::Id {
ns: #default_namespace,
name: #name,
});
}
)
}
#[allow(clippy::too_many_arguments)]
fn process_field(
field: &syn::Field,
index: usize,
declare_values: &mut TokenStream,
return_val: &mut TokenStream,
tokens: &mut Tokens,
field_meta: FieldMeta,
container_meta: &ContainerMeta,
) {
let field_var = field.ident.as_ref().unwrap();
let field_var_str = field_var.to_string();
let const_field_var_str = Ident::new(&field_var_str.to_uppercase(), Span::call_site());
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
tokens.enum_.extend(quote!(#enum_name,));
let default_ns = match &field_meta.ns.uri {
None => &container_meta.ns.uri,
_ => &field_meta.ns.uri,
};
let ns = match default_ns {
Some(Namespace::Path(path)) => quote!(#path),
Some(Namespace::Literal(ns)) => quote!(#ns),
None => quote!(""),
};
tokens.consts.extend(quote!(
const #const_field_var_str: Id<'static> = <#no_lifetime_type>::KIND.name(
Id { ns: #ns, name: #field_var_str }
);
));
if !field_meta.attribute {
tokens.names.extend(quote!(
#const_field_var_str => __Elements::#enum_name,
));
} else {
tokens.names.extend(quote!(
#const_field_var_str => __Attributes::#enum_name,
));
}
declare_values.extend(quote!(
let mut #enum_name: Option<#no_lifetime_type> = None;
));
if !field_meta.attribute {
tokens.match_.extend(quote!(
__Elements::#enum_name => {
if #enum_name.is_some() {
return Err(Error::DuplicateValue);
}
let mut nested = deserializer.nested(data);
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
},
));
} else {
tokens.match_.extend(quote!(
__Attributes::#enum_name => {
if #enum_name.is_some() {
return Err(Error::DuplicateValue);
}
let mut nested = deserializer.for_attr(attr);
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
},
));
}
return_val.extend(quote!(
#field_var: match #enum_name {
Some(v) => v,
None => <#no_lifetime_type>::missing_value()?,
},
));
}
struct Tokens {
enum_: TokenStream,
consts: TokenStream,
@ -21,249 +250,6 @@ impl Default for Tokens {
}
}
pub struct Deserializer {
out: TokenStream,
}
impl quote::ToTokens for Deserializer {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.out.clone());
}
}
impl Deserializer {
pub fn new(input: &syn::DeriveInput) -> Deserializer {
let ident = &input.ident;
let container_meta = ContainerMeta::from_derive(input);
let default_namespace = match &container_meta.ns.uri {
Some(ns) => quote!(#ns),
None => quote!(""),
};
let mut xml_generics = input.generics.clone();
let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site()));
xml.bounds
.extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone()));
xml_generics.params.push(xml.into());
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
let (xml_impl_generics, _, _) = xml_generics.split_for_impl();
let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new(););
for (k, v) in container_meta.ns.prefixes.iter() {
namespaces_map.extend(quote!(
namespaces_map.insert(#k, #v);
))
}
// Varying values
let mut elements_tokens = Tokens::default();
let mut attributes_tokens = Tokens::default();
// Common values
let mut declare_values = TokenStream::new();
let mut return_val = TokenStream::new();
match &input.data {
syn::Data::Struct(ref data) => {
match data.fields {
syn::Fields::Named(ref fields) => {
fields.named.iter().enumerate().for_each(|(index, field)| {
let field_meta = FieldMeta::from_field(field);
let tokens = match field_meta.attribute {
true => &mut attributes_tokens,
false => &mut elements_tokens,
};
Self::process_field(
field,
index,
&mut declare_values,
&mut return_val,
tokens,
field_meta,
&container_meta,
);
});
}
syn::Fields::Unnamed(_) => panic!("unamed"),
syn::Fields::Unit => {}
};
}
_ => todo!(),
};
// Elements
let elements_enum = elements_tokens.enum_;
let elements_consts = elements_tokens.consts;
let elements_names = elements_tokens.names;
let elem_type_match = elements_tokens.match_;
// Attributes
let attributes_enum = attributes_tokens.enum_;
let attributes_consts = attributes_tokens.consts;
let attributes_names = attributes_tokens.names;
let attr_type_match = attributes_tokens.match_;
let name = ident.to_string();
let out = quote!(
impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause {
fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result<Self, ::instant_xml::Error> {
use ::instant_xml::de::{Deserializer, Id, Node};
use ::instant_xml::Error;
use ::core::marker::PhantomData;
enum __Elements {
#elements_enum
__Ignore,
}
enum __Attributes {
#attributes_enum
__Ignore,
}
#declare_values
loop {
let node = match deserializer.next() {
Some(result) => result?,
None => break,
};
match node {
Node::Attribute(attr) => {
let id = deserializer.attribute_id(&attr)?;
let field = {
#attributes_consts
match id {
#attributes_names
_ => __Attributes::__Ignore
}
};
match field {
#attr_type_match
__Attributes::__Ignore => {}
}
}
Node::Open(data) => {
let id = deserializer.element_id(&data)?;
let element = {
#elements_consts
match id {
#elements_names
_ => __Elements::__Ignore
}
};
match element {
#elem_type_match
__Elements::__Ignore => {
let mut nested = deserializer.nested(data);
nested.ignore()?;
}
}
}
_ => return Err(Error::UnexpectedState),
}
}
Ok(Self { #return_val })
}
const KIND: ::instant_xml::de::Kind = ::instant_xml::de::Kind::Element(::instant_xml::de::Id {
ns: #default_namespace,
name: #name,
});
}
);
Deserializer { out }
}
#[allow(clippy::too_many_arguments)]
fn process_field(
field: &syn::Field,
index: usize,
declare_values: &mut TokenStream,
return_val: &mut TokenStream,
tokens: &mut Tokens,
field_meta: FieldMeta,
container_meta: &ContainerMeta,
) {
let field_var = field.ident.as_ref().unwrap();
let field_var_str = field_var.to_string();
let const_field_var_str = Ident::new(&field_var_str.to_uppercase(), Span::call_site());
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
tokens.enum_.extend(quote!(#enum_name,));
let default_ns = match &field_meta.ns.uri {
None => &container_meta.ns.uri,
_ => &field_meta.ns.uri,
};
let ns = match default_ns {
Some(Namespace::Path(path)) => quote!(#path),
Some(Namespace::Literal(ns)) => quote!(#ns),
None => quote!(""),
};
tokens.consts.extend(quote!(
const #const_field_var_str: Id<'static> = <#no_lifetime_type>::KIND.name(
Id { ns: #ns, name: #field_var_str }
);
));
if !field_meta.attribute {
tokens.names.extend(quote!(
#const_field_var_str => __Elements::#enum_name,
));
} else {
tokens.names.extend(quote!(
#const_field_var_str => __Attributes::#enum_name,
));
}
declare_values.extend(quote!(
let mut #enum_name: Option<#no_lifetime_type> = None;
));
if !field_meta.attribute {
tokens.match_.extend(quote!(
__Elements::#enum_name => {
if #enum_name.is_some() {
return Err(Error::DuplicateValue);
}
let mut nested = deserializer.nested(data);
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
},
));
} else {
tokens.match_.extend(quote!(
__Attributes::#enum_name => {
if #enum_name.is_some() {
return Err(Error::DuplicateValue);
}
let mut nested = deserializer.for_attr(attr);
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
},
));
}
return_val.extend(quote!(
#field_var: match #enum_name {
Some(v) => v,
None => <#no_lifetime_type>::missing_value()?,
},
));
}
}
fn discard_lifetimes(ty: &mut syn::Type) {
match ty {
syn::Type::Path(ty) => discard_path_lifetimes(ty),

View File

@ -6,7 +6,7 @@ mod ser;
use std::collections::BTreeMap;
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, TokenStream, TokenTree};
use quote::{quote, ToTokens};
use quote::ToTokens;
use syn::parse_macro_input;
use syn::punctuated::Punctuated;
use syn::token::Colon2;
@ -20,12 +20,7 @@ pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[proc_macro_derive(FromXml, attributes(xml))]
pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = parse_macro_input!(input as syn::DeriveInput);
let deserializer = de::Deserializer::new(&ast);
proc_macro::TokenStream::from(quote!(
#deserializer
))
proc_macro::TokenStream::from(de::from_xml(&ast))
}
#[derive(Default)]