xmpp-rs/xso-proc/src/lib.rs

195 lines
6.4 KiB
Rust

/*!
# Macros for parsing XML into Rust structs, and vice versa
**If you are a user of `xso_proc` or `xso`, please
return to `xso` for more information**. The documentation of
`xso_proc` is geared toward developers of `…_macros` and `…_core`.
**You have been warned.**
## How the derive macros work
The processing is roughly grouped in the following stages:
1. [`syn`] is used to parse the incoming [`TokenStream`] into a [`syn::Item`].
Based on that, the decision is made whether a struct or an enum is being
derived on.
2. Depending on the item type (enum vs. struct), a [`ItemDef`] object is
created which implements that item. The actual implementations reside in
[`crate::structs`] and [`crate::enums`] (respectively).
1. The [`crate::meta::XmlCompoundMeta`] type is used to convert the
raw token streams from the `#[xml(..)]` attributes into structs/enums
for easier handling.
That stage only does syntax checks, no (or just little) semantics. This
separation of concerns helps with simplifying the code both in `meta`
and the following modules.
2. Enum variants and structs are processed using
[`crate::compound::Compound`], their fields being converted from
[`crate::meta::XmlFieldMeta`] to [`crate::field::FieldDef`]. For enums,
additional processing on the enum itself takes place in
[`crate::enums`]. Likewise there's special handling for structs in
[`crate::structs`].
3. If any wrapping was declared, the resulting `ItemDef` is wrapped using
[`crate::wrapped`].
3. After all data has been structured, action is taken depending on the
specific derive macro which has been invoked.
*/
#![warn(missing_docs)]
#![allow(rustdoc::private_intra_doc_links)]
mod common;
mod compound;
mod enums;
mod error_message;
mod field;
mod meta;
mod structs;
mod wrapped;
use proc_macro::TokenStream as RawTokenStream;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::*;
use self::common::bake_generics;
use self::common::ItemDef;
/// Parse any implemented [`syn::Item`] into a [`ItemDef`] object.
fn parse(
item: Item,
) -> Result<(
Box<dyn ItemDef>,
Ident,
TokenStream,
TokenStream,
Option<WhereClause>,
)> {
match item {
Item::Struct(item) => {
let def = self::structs::parse_struct(&item)?;
let ident = item.ident;
let (generics_decl, generics_ref, where_clause) = bake_generics(item.generics);
Ok((def, ident, generics_decl, generics_ref, where_clause))
}
Item::Enum(item) => {
let def = self::enums::parse_enum(&item)?;
let ident = item.ident;
let (generics_decl, generics_ref, where_clause) = bake_generics(item.generics);
Ok((def, ident, generics_decl, generics_ref, where_clause))
}
other => Err(Error::new_spanned(
other,
"can only be applied to enum and struct definitions",
)),
}
}
/// Build the FromXml implementation for a given [`syn::Item`].
fn try_from_element_impl(item: Item) -> Result<TokenStream> {
let (def, ident, generics_decl, generics_ref, where_clause) = parse(item)?;
let try_from_impl = def.build_try_from_element(
&(Path::from(ident.clone()).into()),
&Ident::new("residual", Span::call_site()),
)?;
Ok(quote! {
#[allow(non_snake_case)]
impl #generics_decl ::std::convert::TryFrom<::xso::exports::minidom::Element> for #ident #generics_ref #where_clause {
type Error = ::xso::error::Error;
fn try_from(mut residual: ::xso::exports::minidom::Element) -> Result<Self, Self::Error> {
#try_from_impl
}
}
impl #generics_decl ::xso::FromXml for #ident #generics_ref #where_clause {
fn from_tree(elem: ::xso::exports::minidom::Element) -> Result<Self, ::xso::error::Error> {
Self::try_from(elem)
}
fn absent() -> Option<Self> {
None
}
}
})
}
/// Derive macro for `FromXml`.
#[proc_macro_derive(FromXml, attributes(xml))]
pub fn try_from_element(input: RawTokenStream) -> RawTokenStream {
let item = syn::parse_macro_input!(input as Item);
let result = try_from_element_impl(item);
match result {
Ok(v) => v.into(),
Err(e) => e.into_compile_error().into(),
}
}
/// Build the DynNamespace implementation for a given [`syn::Item`].
fn dyn_namespace_impl(item: Item) -> Result<TokenStream> {
let (def, ident, generics_decl, generics_ref, where_clause) = parse(item)?;
let dyn_namespace_impl = def.build_dyn_namespace()?;
Ok(quote! {
#[allow(non_snake_case)]
impl #generics_decl ::xso::DynNamespace for #ident #generics_ref #where_clause {
#dyn_namespace_impl
}
})
}
/// Derive macro for `DynNamespace`.
#[proc_macro_derive(DynNamespace, attributes(xml))]
pub fn dyn_namespace(input: RawTokenStream) -> RawTokenStream {
let item = syn::parse_macro_input!(input as Item);
let result = dyn_namespace_impl(item);
match result {
Ok(v) => v.into(),
Err(e) => e.into_compile_error().into(),
}
}
/// Build the IntoXml implementation for a given [`syn::Item`].
fn into_element_impl(item: Item) -> Result<TokenStream> {
let (def, ident, generics_decl, generics_ref, where_clause) = parse(item)?;
let into_element_impl = def.build_into_element(
&(Path::from(ident.clone()).into()),
&Ident::new("other", Span::call_site()),
)?;
Ok(quote! {
#[allow(non_snake_case)]
impl #generics_decl ::std::convert::From<#ident #generics_ref> for ::xso::exports::minidom::Element #where_clause {
fn from(mut other: #ident #generics_ref) -> Self {
#into_element_impl
}
}
impl #generics_decl ::xso::IntoXml for #ident #generics_ref {
fn into_tree(self) -> Option<::xso::exports::minidom::Element> {
Some(::minidom::Element::from(self))
}
}
})
}
/// Derive macro for `IntoXml`.
#[proc_macro_derive(IntoXml, attributes(xml))]
pub fn into_element(input: RawTokenStream) -> RawTokenStream {
let item = syn::parse_macro_input!(input as Item);
let result = into_element_impl(item);
match result {
Ok(v) => v.into(),
Err(e) => e.into_compile_error().into(),
}
}