Refactor how attributes are parsed

This commit is contained in:
Dirkjan Ochtman 2022-09-01 16:29:07 +02:00
parent bebeba8d72
commit dee065cd9a
3 changed files with 191 additions and 187 deletions

View File

@ -1,7 +1,7 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use crate::{namespaces, retrieve_field_attribute, FieldAttribute};
use crate::{ContainerMeta, FieldMeta, Namespace};
struct Tokens {
enum_: TokenStream,
@ -34,6 +34,13 @@ impl quote::ToTokens for Deserializer {
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.default {
Namespace::Default => String::new(),
Namespace::Prefix(_) => panic!("container namespace cannot be prefix"),
Namespace::Literal(ns) => ns,
};
let generics = (&input.generics).into_token_stream();
let lifetimes = (&input.generics.params).into_token_stream();
@ -56,10 +63,9 @@ impl Deserializer {
let name = ident.to_string();
let mut out = TokenStream::new();
let (default_namespace, other_namespaces) = namespaces(&input.attrs);
let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new(););
for (k, v) in other_namespaces.iter() {
for (k, v) in container_meta.ns.prefixes.iter() {
namespaces_map.extend(quote!(
namespaces_map.insert(#k, #v);
))
@ -78,26 +84,16 @@ impl Deserializer {
match data.fields {
syn::Fields::Named(ref fields) => {
fields.named.iter().enumerate().for_each(|(index, field)| {
let mut field_namespace = None;
let (tokens, def_prefix, is_element) = match retrieve_field_attribute(field) {
Some(FieldAttribute::Namespace(value)) => {
field_namespace = Some(value);
(&mut elements_tokens, None, true)
let field_meta = FieldMeta::from_field(field);
if let Namespace::Prefix(prefix) = &field_meta.ns.default {
if !container_meta.ns.prefixes.contains_key(prefix) {
panic!("unknown prefix for this type");
}
Some(FieldAttribute::PrefixIdentifier(def_prefix)) => {
if other_namespaces.get(&def_prefix).is_none() {
panic!("Namespace with such prefix do not exist for this struct");
}
(&mut elements_tokens, Some(def_prefix), true)
},
Some(FieldAttribute::Attribute) => {
(&mut attributes_tokens, None, false)
}
None => {
(&mut elements_tokens, None, true)
},
let tokens = match field_meta.attribute {
true => &mut attributes_tokens,
false => &mut elements_tokens,
};
Self::process_field(
@ -106,9 +102,7 @@ impl Deserializer {
&mut declare_values,
&mut return_val,
tokens,
is_element,
def_prefix,
field_namespace,
field_meta,
);
});
}
@ -237,9 +231,7 @@ impl Deserializer {
declare_values: &mut TokenStream,
return_val: &mut TokenStream,
tokens: &mut Tokens,
is_element: bool,
def_prefix: Option<String>,
field_namespace: Option<String>,
field_meta: FieldMeta,
) {
let field_var = field.ident.as_ref().unwrap();
let field_var_str = field_var.to_string();
@ -254,7 +246,7 @@ impl Deserializer {
const #const_field_var_str: &str = <#no_lifetime_type>::KIND.name(#field_var_str);
));
if is_element {
if !field_meta.attribute {
tokens.names.extend(quote!(
#const_field_var_str => __Elements::#enum_name,
));
@ -268,28 +260,21 @@ impl Deserializer {
let mut #enum_name: Option<#no_lifetime_type> = None;
));
let def_prefix = match def_prefix {
Some(def_prefix) => quote!(Some(#def_prefix)),
None => quote!(None::<&str>),
let (field_prefix, new_default_ns) = match field_meta.ns.default {
Namespace::Default => (quote!(None::<&str>), quote!(None::<&str>)),
Namespace::Prefix(prefix) => (quote!(Some(#prefix)), quote!(None)),
Namespace::Literal(ns) => (quote!(None::<&str>), quote!(Some(#ns))),
};
let field_namespace = match field_namespace {
Some(field_namespace) => {
quote!(let field_namespace: Option<&str> = Some(#field_namespace);)
}
None => quote!(let field_namespace: Option<&str> = None;),
};
if is_element {
if !field_meta.attribute {
tokens.match_.extend(quote!(
__Elements::#enum_name => {
if #enum_name.is_some() {
panic!("duplicated value");
}
deserializer.compare_namespace(&item.prefix, #def_prefix)?;
#field_namespace
deserializer.set_next_def_namespace(field_namespace)?;
deserializer.compare_namespace(&item.prefix, #field_prefix)?;
deserializer.set_next_def_namespace(#new_default_ns)?;
#enum_name = Some(<#no_lifetime_type>::deserialize(deserializer)?);
},
));

View File

@ -7,105 +7,20 @@ use std::collections::HashMap;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Lit, Meta, NestedMeta};
use syn::punctuated::Punctuated;
use syn::{parse_macro_input, Meta, NestedMeta};
use crate::ser::Serializer;
const XML: &str = "xml";
pub(crate) enum FieldAttribute {
Namespace(String),
PrefixIdentifier(String),
Attribute,
}
pub(crate) fn namespaces(attributes: &Vec<syn::Attribute>) -> (String, HashMap<String, String>) {
let mut default_namespace = String::new();
let mut other_namespaces = HashMap::default();
let (list, name) = match retrieve_attr_list(attributes) {
Some((Some(list), name)) => (list, name),
None => return (default_namespace, other_namespaces),
_ => panic!("wrong parameters"),
};
if name == "namespace" {
let mut iter = list.nested.iter();
let mut next = iter.next();
if let Some(NestedMeta::Lit(Lit::Str(v))) = next {
default_namespace = v.value();
next = iter.next();
}
while let Some(value) = next {
if let NestedMeta::Meta(Meta::NameValue(key)) = value {
if let Lit::Str(value) = &key.lit {
other_namespaces
.insert(key.path.get_ident().unwrap().to_string(), value.value());
next = iter.next();
continue;
}
}
panic!("Wrong data")
}
}
(default_namespace, other_namespaces)
}
pub(crate) fn retrieve_field_attribute(input: &syn::Field) -> Option<FieldAttribute> {
match retrieve_attr_list(&input.attrs) {
Some((Some(list), name)) if name.as_str() == "namespace" => match list.nested.first() {
Some(NestedMeta::Lit(Lit::Str(v))) => Some(FieldAttribute::Namespace(v.value())),
Some(NestedMeta::Meta(Meta::Path(v))) => {
if let Some(ident) = v.get_ident() {
Some(FieldAttribute::PrefixIdentifier(ident.to_string()))
} else {
panic!("unexpected parameter");
}
}
_ => panic!("unexpected parameter"),
},
Some((None, name)) if name.as_str() == "attribute" => Some(FieldAttribute::Attribute),
None => None,
_ => panic!("unexpected parameter"),
}
}
fn retrieve_attr_list(attributes: &Vec<syn::Attribute>) -> Option<(Option<syn::MetaList>, String)> {
for attr in attributes {
if !attr.path.is_ident(XML) {
continue;
}
let nested = match attr.parse_meta() {
Ok(Meta::List(meta)) => meta.nested,
Ok(_) => todo!(),
_ => todo!(),
};
let list = match nested.first() {
Some(NestedMeta::Meta(Meta::List(list))) => list,
Some(NestedMeta::Meta(Meta::Path(path))) => {
return Some((None, path.get_ident()?.to_string()))
}
_ => return None,
};
return Some((Some(list.to_owned()), list.path.get_ident()?.to_string()));
}
None
}
#[proc_macro_derive(ToXml, attributes(xml))]
pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = parse_macro_input!(input as syn::DeriveInput);
let ident = &ast.ident;
let generics = (&ast.generics).into_token_stream();
let root_name = ident.to_string();
let mut serializer = Serializer::new(&ast.attrs);
let mut serializer = Serializer::new(&ast);
let mut header = TokenStream::new();
serializer.add_header(&mut header);
@ -162,9 +77,118 @@ 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
))
}
#[derive(Default)]
struct ContainerMeta {
ns: NamespaceMeta,
}
impl ContainerMeta {
fn from_derive(input: &syn::DeriveInput) -> ContainerMeta {
let mut meta = ContainerMeta::default();
for item in meta_items(&input.attrs) {
match item {
Meta::List(list) if list.path.is_ident("namespace") => {
meta.ns = NamespaceMeta::from_list(&list.nested)
}
_ => panic!("invalid xml attribute syntax"),
}
}
meta
}
}
#[derive(Default)]
struct FieldMeta {
attribute: bool,
ns: NamespaceMeta,
}
impl FieldMeta {
fn from_field(input: &syn::Field) -> FieldMeta {
let mut meta = FieldMeta::default();
for item in meta_items(&input.attrs) {
match item {
Meta::Path(path) if path.is_ident("attribute") => meta.attribute = true,
Meta::List(list) if list.path.is_ident("namespace") => {
meta.ns = NamespaceMeta::from_list(&list.nested)
}
_ => panic!("invalid xml attribute syntax"),
}
}
meta
}
}
#[derive(Default)]
struct NamespaceMeta {
default: Namespace,
prefixes: HashMap<String, String>,
}
impl NamespaceMeta {
fn from_list(list: &Punctuated<NestedMeta, syn::token::Comma>) -> NamespaceMeta {
let mut meta = NamespaceMeta::default();
for (i, item) in list.iter().enumerate() {
match item {
NestedMeta::Meta(inner) => match inner {
Meta::Path(path) => match path.get_ident() {
Some(id) => meta.default = Namespace::Prefix(id.to_string()),
None => panic!("invalid xml attribute syntax"),
},
Meta::NameValue(nv) => match (nv.path.get_ident(), &nv.lit) {
(Some(id), syn::Lit::Str(lit)) => {
meta.prefixes.insert(id.to_string(), lit.value());
}
_ => panic!("invalid xml attribute syntax"),
},
_ => panic!("invalid xml attribute syntax"),
},
NestedMeta::Lit(syn::Lit::Str(lit)) if i == 0 => {
meta.default = Namespace::Literal(lit.value())
}
_ => panic!("invalid xml attribute syntax"),
}
}
meta
}
}
fn meta_items(attrs: &[syn::Attribute]) -> impl Iterator<Item = Meta> + '_ {
attrs
.iter()
.filter_map(|attr| {
if !attr.path.is_ident("xml") {
return None;
}
match attr.parse_meta() {
Ok(Meta::List(meta)) => Some(meta.nested.into_iter()),
_ => panic!("unexpected xml attribute syntax"),
}
})
.flatten()
.map(|item| match item {
NestedMeta::Meta(item) => item,
NestedMeta::Lit(_) => panic!("unexpected xml attribute syntax"),
})
}
enum Namespace {
Default,
Prefix(String),
Literal(String),
}
impl Default for Namespace {
fn default() -> Self {
Namespace::Default
}
}

View File

@ -1,22 +1,16 @@
use std::collections::HashMap;
use proc_macro2::TokenStream;
use quote::quote;
use crate::{namespaces, retrieve_field_attribute, FieldAttribute};
use crate::{ContainerMeta, FieldMeta, Namespace};
pub struct Serializer {
default_namespace: String,
other_namespaces: HashMap<String, String>,
meta: ContainerMeta,
}
impl<'a> Serializer {
pub fn new(attributes: &'a Vec<syn::Attribute>) -> Serializer {
let (default_namespace, other_namespaces) = namespaces(attributes);
Serializer {
default_namespace,
other_namespaces,
pub fn new(input: &syn::DeriveInput) -> Self {
Self {
meta: ContainerMeta::from_derive(input),
}
}
@ -26,7 +20,11 @@ impl<'a> Serializer {
serializer.output.write_str(field_context.name)?;
));
let default_namespace = &self.default_namespace;
let default_namespace = match &self.meta.ns.default {
Namespace::Default => "",
Namespace::Prefix(_) => panic!("type cannot have prefix as namespace"),
Namespace::Literal(ns) => ns,
};
output.extend(quote!(
// Check if parent default namespace equals
if serializer.parent_default_namespace() != #default_namespace {
@ -37,7 +35,7 @@ impl<'a> Serializer {
serializer.update_parent_default_namespace(#default_namespace);
));
let mut sorted_values: Vec<_> = self.other_namespaces.iter().collect();
let mut sorted_values: Vec<_> = self.meta.ns.prefixes.iter().collect();
sorted_values.sort();
for (key, val) in sorted_values {
@ -87,16 +85,26 @@ impl<'a> Serializer {
};
);
let stream_ref = match retrieve_field_attribute(field) {
Some(FieldAttribute::Namespace(namespace)) => {
let field_meta = FieldMeta::from_field(field);
if field_meta.attribute {
attributes.extend(quote!(
#declaration
serializer.add_attribute_key(&#name)?;
field.attribute = Some(instant_xml::FieldAttribute::Attribute);
serializer.set_field_context(field)?;
self.#field_value.serialize(serializer)?;
));
return;
}
if let Namespace::Literal(ns) = &field_meta.ns.default {
body.extend(quote!(
#declaration
field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace));
field.attribute = Some(instant_xml::FieldAttribute::Namespace(#ns));
));
body
}
Some(FieldAttribute::PrefixIdentifier(prefix_key)) => {
match self.other_namespaces.get(&prefix_key) {
} else if let Namespace::Prefix(prefix) = &field_meta.ns.default {
match self.meta.ns.prefixes.get(prefix) {
Some(val) => {
body.extend(quote!(
#declaration
@ -104,36 +112,23 @@ impl<'a> Serializer {
// Check if such namespace already exist, if so change its prefix to parent prefix
let prefix_key = match serializer.parent_namespaces.get(#val) {
Some(key) => key,
None => #prefix_key,
None => #prefix,
};
));
}
None => panic!("Prefix not defined: {}", prefix_key),
None => panic!("Prefix not defined: {}", prefix),
};
body.extend(quote!(
field.attribute = Some(instant_xml::FieldAttribute::Prefix(prefix_key));
));
body
}
Some(FieldAttribute::Attribute) => {
attributes.extend(quote!(
#declaration
serializer.add_attribute_key(&#name)?;
field.attribute = Some(instant_xml::FieldAttribute::Attribute);
));
attributes
}
_ => {
} else {
body.extend(quote!(
#declaration
));
body
}
};
stream_ref.extend(quote!(
body.extend(quote!(
serializer.set_field_context(field)?;
self.#field_value.serialize(serializer)?;
));
@ -143,7 +138,7 @@ impl<'a> Serializer {
let mut namespaces = quote!(
let mut to_remove: Vec<&str> = Vec::new();
);
for (k, v) in self.other_namespaces.iter() {
for (k, v) in self.meta.ns.prefixes.iter() {
namespaces.extend(quote!(
// Only adding to HashMap if namespace do not exist, if it exist it will use the parent defined prefix
if let std::collections::hash_map::Entry::Vacant(v) = serializer.parent_namespaces.entry(#v) {