@@ -21,30 +21,23 @@ return to `xso` for more information**. The documentation of
// syn mostly works with proc_macro2, while the proc macros themselves use
// proc_macro.
use proc_macro::TokenStream as RawTokenStream;
-use proc_macro2::TokenStream;
+use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::*;
mod meta;
+mod structs;
/// Convert an [`syn::Item`] into the parts relevant for us.
///
/// If the item is of an unsupported variant, an appropriate error is
/// returned.
-fn parse_struct(item: Item) -> Result<(Visibility, meta::XmlCompoundMeta, Ident)> {
+fn parse_struct(item: Item) -> Result<(Visibility, Ident, structs::StructDef)> {
match item {
Item::Struct(item) => {
- match item.fields {
- Fields::Unit => (),
- other => {
- return Err(Error::new_spanned(
- other,
- "cannot derive on non-unit struct (yet!)",
- ))
- }
- }
let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
- Ok((item.vis, meta, item.ident))
+ let def = structs::StructDef::new(&item.ident, meta, &item.fields)?;
+ Ok((item.vis, item.ident, def))
}
other => Err(Error::new_spanned(other, "cannot derive on this item")),
}
@@ -53,90 +46,29 @@ fn parse_struct(item: Item) -> Result<(Visibility, meta::XmlCompoundMeta, Ident)
/// Generate a `xso::FromXml` implementation for the given item, or fail with
/// a proper compiler error.
fn from_xml_impl(input: Item) -> Result<TokenStream> {
- let (
- vis,
- meta::XmlCompoundMeta {
- namespace,
- name,
- span,
- },
- ident,
- ) = parse_struct(input)?;
-
- // we rebind to a different name here because otherwise some expressions
- // inside `quote! {}` below get a bit tricky to read (such as
- // `name.1 == #name`).
- let Some(xml_namespace) = namespace else {
- return Err(Error::new(span, "`namespace` key is required"));
- };
+ let (vis, ident, def) = parse_struct(input)?;
- let Some(xml_name) = name else {
- return Err(Error::new(span, "`name` key is required"));
- };
-
- let from_events_builder_ty_name = quote::format_ident!("{}FromEvents", ident);
- let state_ty_name = quote::format_ident!("{}FromEventsState", ident);
+ let name_ident = Ident::new("name", Span::call_site());
+ let attrs_ident = Ident::new("attrs", Span::call_site());
- let unknown_attr_err = format!(
- "Unknown attribute in {} element.",
- xml_name.repr_to_string()
- );
- let unknown_child_err = format!("Unknown child in {} element.", xml_name.repr_to_string());
- let docstr = format!("Build a [`{}`] from XML events", ident);
+ let structs::FromXmlParts {
+ defs,
+ from_events_body,
+ builder_ty_ident,
+ } = def.make_from_events_builder(&vis, &name_ident, &attrs_ident)?;
#[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
let mut result = quote! {
- enum #state_ty_name {
- Default,
- }
-
- #[doc = #docstr]
- #vis struct #from_events_builder_ty_name(::core::option::Option<#state_ty_name>);
-
- impl ::xso::FromEventsBuilder for #from_events_builder_ty_name {
- type Output = #ident;
-
- fn feed(
- &mut self,
- ev: ::xso::exports::rxml::Event
- ) -> ::core::result::Result<::core::option::Option<Self::Output>, ::xso::error::Error> {
- match self.0 {
- ::core::option::Option::None => panic!("feed() called after it returned a non-None value"),
- ::core::option::Option::Some(#state_ty_name::Default) => match ev {
- ::xso::exports::rxml::Event::StartElement(..) => {
- ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
- }
- ::xso::exports::rxml::Event::EndElement(..) => {
- self.0 = ::core::option::Option::None;
- ::core::result::Result::Ok(::core::option::Option::Some(#ident))
- }
- ::xso::exports::rxml::Event::Text(..) => {
- ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
- }
- // 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(::core::option::Option::None)
- }
- }
- }
- }
+ #defs
impl ::xso::FromXml for #ident {
- type Builder = #from_events_builder_ty_name;
+ type Builder = #builder_ty_ident;
fn from_events(
name: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap,
) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
- if name.0 != #xml_namespace || name.1 != #xml_name {
- return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs });
- }
- if attrs.len() > 0 {
- return ::core::result::Result::Err(::xso::error::Error::Other(#unknown_attr_err).into());
- }
- ::core::result::Result::Ok(#from_events_builder_ty_name(::core::option::Option::Some(#state_ty_name::Default)))
+ #from_events_body
}
}
};
@@ -172,73 +104,23 @@ pub fn from_xml(input: RawTokenStream) -> RawTokenStream {
/// Generate a `xso::IntoXml` implementation for the given item, or fail with
/// a proper compiler error.
fn into_xml_impl(input: Item) -> Result<TokenStream> {
- let (
- vis,
- meta::XmlCompoundMeta {
- namespace,
- name,
- span,
- },
- ident,
- ) = parse_struct(input)?;
+ let (vis, ident, def) = parse_struct(input)?;
- // we rebind to a different name here to stay consistent with
- // `from_xml_impl`.
- let Some(xml_namespace) = namespace else {
- return Err(Error::new(span, "`namespace` key is required"));
- };
-
- let Some(xml_name) = name else {
- return Err(Error::new(span, "`name` key is required"));
- };
-
- let into_events_iter_ty_name = quote::format_ident!("{}IntoEvents", ident);
- let state_ty_name = quote::format_ident!("{}IntoEventsState", ident);
-
- let docstr = format!("Decompose a [`{}`] into XML events", ident);
+ let structs::IntoXmlParts {
+ defs,
+ into_event_iter_body,
+ event_iter_ty_ident,
+ } = def.make_into_event_iter(&vis)?;
#[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
let mut result = quote! {
- enum #state_ty_name {
- Header,
- Footer,
- }
-
- #[doc = #docstr]
- #vis struct #into_events_iter_ty_name(::core::option::Option<#state_ty_name>);
-
- impl ::std::iter::Iterator for #into_events_iter_ty_name {
- type Item = ::core::result::Result<::xso::exports::rxml::Event, ::xso::error::Error>;
-
- fn next(&mut self) -> ::core::option::Option<Self::Item> {
- match self.0 {
- ::core::option::Option::Some(#state_ty_name::Header) => {
- self.0 = ::core::option::Option::Some(#state_ty_name::Footer);
- ::core::option::Option::Some(::core::result::Result::Ok(::xso::exports::rxml::Event::StartElement(
- ::xso::exports::rxml::parser::EventMetrics::zero(),
- (
- ::xso::exports::rxml::Namespace::from_str(#xml_namespace),
- #xml_name.to_owned(),
- ),
- ::xso::exports::rxml::AttrMap::new(),
- )))
- }
- ::core::option::Option::Some(#state_ty_name::Footer) => {
- self.0 = ::core::option::Option::None;
- ::core::option::Option::Some(::core::result::Result::Ok(::xso::exports::rxml::Event::EndElement(
- ::xso::exports::rxml::parser::EventMetrics::zero(),
- )))
- }
- ::core::option::Option::None => ::core::option::Option::None,
- }
- }
- }
+ #defs
impl ::xso::IntoXml for #ident {
- type EventIter = #into_events_iter_ty_name;
+ type EventIter = #event_iter_ty_ident;
fn into_event_iter(self) -> ::core::result::Result<Self::EventIter, ::xso::error::Error> {
- ::core::result::Result::Ok(#into_events_iter_ty_name(::core::option::Option::Some(#state_ty_name::Header)))
+ #into_event_iter_body
}
}
};
@@ -0,0 +1,218 @@
+// 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 structs
+
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::*;
+
+use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
+
+/// Parts necessary to construct a `::xso::FromXml` implementation.
+pub(crate) struct FromXmlParts {
+ /// Additional items necessary for the implementation.
+ pub(crate) defs: TokenStream,
+
+ /// The body of the `::xso::FromXml::from_xml` function.
+ pub(crate) from_events_body: TokenStream,
+
+ /// The name of the type which is the `::xso::FromXml::Builder`.
+ pub(crate) builder_ty_ident: Ident,
+}
+
+/// Parts necessary to construct a `::xso::IntoXml` implementation.
+pub(crate) struct IntoXmlParts {
+ /// Additional items necessary for the implementation.
+ pub(crate) defs: TokenStream,
+
+ /// The body of the `::xso::IntoXml::into_event_iter` function.
+ pub(crate) into_event_iter_body: TokenStream,
+
+ /// The name of the type which is the `::xso::IntoXml::EventIter`.
+ pub(crate) event_iter_ty_ident: Ident,
+}
+
+/// Definition of a struct and how to parse it.
+pub(crate) struct StructDef {
+ /// The XML namespace of the element to map the struct to.
+ namespace: NamespaceRef,
+
+ /// The XML name of the element to map the struct to.
+ name: NameRef,
+
+ /// Name of the target type.
+ target_ty_ident: Ident,
+
+ /// Name of the builder type.
+ builder_ty_ident: Ident,
+
+ /// Name of the iterator type.
+ event_iter_ty_ident: Ident,
+}
+
+impl StructDef {
+ /// Create a new struct from its name, meta, and fields.
+ pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
+ let Some(namespace) = meta.namespace else {
+ return Err(Error::new(meta.span, "`namespace` is required on structs"));
+ };
+
+ let Some(name) = meta.name else {
+ return Err(Error::new(meta.span, "`name` is required on structs"));
+ };
+
+ match fields {
+ Fields::Unit => (),
+ other => {
+ return Err(Error::new_spanned(
+ other,
+ "cannot derive on non-unit struct (yet!)",
+ ))
+ }
+ }
+
+ Ok(Self {
+ namespace,
+ name,
+ target_ty_ident: ident.clone(),
+ builder_ty_ident: quote::format_ident!("{}FromXmlBuilder", ident),
+ event_iter_ty_ident: quote::format_ident!("{}IntoXmlIterator", ident),
+ })
+ }
+
+ pub(crate) fn make_from_events_builder(
+ &self,
+ vis: &Visibility,
+ name_ident: &Ident,
+ attrs_ident: &Ident,
+ ) -> Result<FromXmlParts> {
+ let xml_namespace = &self.namespace;
+ let xml_name = &self.name;
+
+ let target_ty_ident = &self.target_ty_ident;
+ let builder_ty_ident = &self.builder_ty_ident;
+ let state_ty_name = quote::format_ident!("{}State", builder_ty_ident);
+
+ let unknown_attr_err = format!(
+ "Unknown attribute in {} element.",
+ xml_name.repr_to_string()
+ );
+ let unknown_child_err = format!("Unknown child in {} element.", xml_name.repr_to_string());
+
+ let docstr = format!("Build a [`{}`] from XML events", target_ty_ident);
+
+ Ok(FromXmlParts {
+ defs: quote! {
+ enum #state_ty_name {
+ Default,
+ }
+
+ #[doc = #docstr]
+ #vis struct #builder_ty_ident(::core::option::Option<#state_ty_name>);
+
+ impl ::xso::FromEventsBuilder for #builder_ty_ident {
+ type Output = #target_ty_ident;
+
+ fn feed(
+ &mut self,
+ ev: ::xso::exports::rxml::Event
+ ) -> ::core::result::Result<::core::option::Option<Self::Output>, ::xso::error::Error> {
+ match self.0 {
+ ::core::option::Option::None => panic!("feed() called after it returned a non-None value"),
+ ::core::option::Option::Some(#state_ty_name::Default) => match ev {
+ ::xso::exports::rxml::Event::StartElement(..) => {
+ ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
+ }
+ ::xso::exports::rxml::Event::EndElement(..) => {
+ self.0 = ::core::option::Option::None;
+ ::core::result::Result::Ok(::core::option::Option::Some(#target_ty_ident))
+ }
+ ::xso::exports::rxml::Event::Text(..) => {
+ ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
+ }
+ // 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(::core::option::Option::None)
+ }
+ }
+ }
+ }
+ },
+ from_events_body: quote! {
+ if #name_ident.0 != #xml_namespace || #name_ident.1 != #xml_name {
+ return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
+ name: #name_ident,
+ attrs: #attrs_ident,
+ });
+ }
+ if attrs.len() > 0 {
+ return ::core::result::Result::Err(::xso::error::Error::Other(
+ #unknown_attr_err,
+ ).into());
+ }
+ ::core::result::Result::Ok(#builder_ty_ident(::core::option::Option::Some(#state_ty_name::Default)))
+ },
+ builder_ty_ident: builder_ty_ident.clone(),
+ })
+ }
+
+ pub(crate) fn make_into_event_iter(&self, vis: &Visibility) -> Result<IntoXmlParts> {
+ let xml_namespace = &self.namespace;
+ let xml_name = &self.name;
+
+ let target_ty_ident = &self.target_ty_ident;
+ let event_iter_ty_ident = &self.event_iter_ty_ident;
+ let state_ty_name = quote::format_ident!("{}State", event_iter_ty_ident);
+
+ let docstr = format!("Decompose a [`{}`] into XML events", target_ty_ident);
+
+ Ok(IntoXmlParts {
+ defs: quote! {
+ enum #state_ty_name {
+ Header,
+ Footer,
+ }
+
+ #[doc = #docstr]
+ #vis struct #event_iter_ty_ident(::core::option::Option<#state_ty_name>);
+
+ impl ::std::iter::Iterator for #event_iter_ty_ident {
+ type Item = ::core::result::Result<::xso::exports::rxml::Event, ::xso::error::Error>;
+
+ fn next(&mut self) -> ::core::option::Option<Self::Item> {
+ match self.0 {
+ ::core::option::Option::Some(#state_ty_name::Header) => {
+ self.0 = ::core::option::Option::Some(#state_ty_name::Footer);
+ ::core::option::Option::Some(::core::result::Result::Ok(::xso::exports::rxml::Event::StartElement(
+ ::xso::exports::rxml::parser::EventMetrics::zero(),
+ (
+ ::xso::exports::rxml::Namespace::from_str(#xml_namespace),
+ #xml_name.to_owned(),
+ ),
+ ::xso::exports::rxml::AttrMap::new(),
+ )))
+ }
+ ::core::option::Option::Some(#state_ty_name::Footer) => {
+ self.0 = ::core::option::Option::None;
+ ::core::option::Option::Some(::core::result::Result::Ok(::xso::exports::rxml::Event::EndElement(
+ ::xso::exports::rxml::parser::EventMetrics::zero(),
+ )))
+ }
+ ::core::option::Option::None => ::core::option::Option::None,
+ }
+ }
+ }
+ },
+ into_event_iter_body: quote! {
+ ::core::result::Result::Ok(#event_iter_ty_ident(::core::option::Option::Some(#state_ty_name::Header)))
+ },
+ event_iter_ty_ident: event_iter_ty_ident.clone(),
+ })
+ }
+}