1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#![forbid(unsafe_code)]
8#![warn(missing_docs)]
9#![allow(rustdoc::private_intra_doc_links)]
10#![cfg_attr(docsrs, feature(doc_auto_cfg))]
11/*!
12# Macros for parsing XML into Rust structs, and vice versa
13
14**If you are a user of `xso_proc` or `xso`, please
15return to `xso` for more information**. The documentation of
16`xso_proc` is geared toward developers of `…_macros` and `…_core`.
17
18**You have been warned.**
19*/
20
21extern crate alloc;
22
23// Wondering about RawTokenStream vs. TokenStream?
24// syn mostly works with proc_macro2, while the proc macros themselves use
25// proc_macro.
26use proc_macro::TokenStream as RawTokenStream;
27use proc_macro2::{Span, TokenStream};
28use quote::quote;
29use syn::*;
30
31mod common;
32mod compound;
33mod enums;
34mod error_message;
35mod field;
36mod meta;
37mod scope;
38mod state;
39mod structs;
40mod types;
41
42use common::{AsXmlParts, FromXmlParts, ItemDef};
43
44/// Convert an [`syn::Item`] into the parts relevant for us.
45///
46/// If the item is of an unsupported variant, an appropriate error is
47/// returned.
48fn parse_struct(item: Item) -> Result<(Visibility, Ident, Box<dyn ItemDef>)> {
49 match item {
50 Item::Struct(item) => {
51 let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
52 let def = structs::StructDef::new(&item.ident, meta, &item.fields)?;
53 Ok((item.vis, item.ident, Box::new(def)))
54 }
55 Item::Enum(item) => {
56 let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
57 let def = enums::EnumDef::new(&item.ident, meta, &item.variants)?;
58 Ok((item.vis, item.ident, Box::new(def)))
59 }
60 other => Err(Error::new_spanned(other, "cannot derive on this item")),
61 }
62}
63
64/// Generate a `xso::FromXml` implementation for the given item, or fail with
65/// a proper compiler error.
66fn from_xml_impl(input: Item) -> Result<TokenStream> {
67 let (vis, ident, def) = parse_struct(input)?;
68
69 let name_ident = Ident::new("name", Span::call_site());
70 let attrs_ident = Ident::new("attrs", Span::call_site());
71
72 let FromXmlParts {
73 defs,
74 from_events_body,
75 builder_ty_ident,
76 } = def.make_from_events_builder(&vis, &name_ident, &attrs_ident)?;
77
78 #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
79 let mut result = quote! {
80 #defs
81
82 impl ::xso::FromXml for #ident {
83 type Builder = #builder_ty_ident;
84
85 fn from_events(
86 name: ::xso::exports::rxml::QName,
87 attrs: ::xso::exports::rxml::AttrMap,
88 ) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
89 #from_events_body
90 }
91 }
92 };
93
94 #[cfg(feature = "minidom")]
95 result.extend(quote! {
96 impl ::core::convert::TryFrom<::xso::exports::minidom::Element> for #ident {
97 type Error = ::xso::error::FromElementError;
98
99 fn try_from(other: ::xso::exports::minidom::Element) -> ::core::result::Result<Self, Self::Error> {
100 ::xso::try_from_element(other)
101 }
102 }
103 });
104
105 if def.debug() {
106 println!("{}", result);
107 }
108
109 Ok(result)
110}
111
112/// Macro to derive a `xso::FromXml` implementation on a type.
113///
114/// The user-facing documentation for this macro lives in the `xso` crate.
115#[proc_macro_derive(FromXml, attributes(xml))]
116pub fn from_xml(input: RawTokenStream) -> RawTokenStream {
117 // Shim wrapper around `from_xml_impl` which converts any errors into
118 // actual compiler errors within the resulting token stream.
119 let item = syn::parse_macro_input!(input as Item);
120 match from_xml_impl(item) {
121 Ok(v) => v.into(),
122 Err(e) => e.into_compile_error().into(),
123 }
124}
125
126/// Generate a `xso::AsXml` implementation for the given item, or fail with
127/// a proper compiler error.
128fn as_xml_impl(input: Item) -> Result<TokenStream> {
129 let (vis, ident, def) = parse_struct(input)?;
130
131 let AsXmlParts {
132 defs,
133 as_xml_iter_body,
134 item_iter_ty_lifetime,
135 item_iter_ty,
136 } = def.make_as_xml_iter(&vis)?;
137
138 #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
139 let mut result = quote! {
140 #defs
141
142 impl ::xso::AsXml for #ident {
143 type ItemIter<#item_iter_ty_lifetime> = #item_iter_ty;
144
145 fn as_xml_iter(&self) -> ::core::result::Result<Self::ItemIter<'_>, ::xso::error::Error> {
146 #as_xml_iter_body
147 }
148 }
149 };
150
151 #[cfg(all(feature = "minidom", feature = "panicking-into-impl"))]
152 result.extend(quote! {
153 impl ::core::convert::From<#ident> for ::xso::exports::minidom::Element {
154 fn from(other: #ident) -> Self {
155 ::xso::transform(&other).expect("seamless conversion into minidom::Element")
156 }
157 }
158
159 impl ::core::convert::From<&#ident> for ::xso::exports::minidom::Element {
160 fn from(other: &#ident) -> Self {
161 ::xso::transform(other).expect("seamless conversion into minidom::Element")
162 }
163 }
164 });
165
166 #[cfg(all(feature = "minidom", not(feature = "panicking-into-impl")))]
167 result.extend(quote! {
168 impl ::core::convert::TryFrom<#ident> for ::xso::exports::minidom::Element {
169 type Error = ::xso::error::Error;
170
171 fn try_from(other: #ident) -> ::core::result::Result<Self, Self::Error> {
172 ::xso::transform(&other)
173 }
174 }
175 impl ::core::convert::TryFrom<&#ident> for ::xso::exports::minidom::Element {
176 type Error = ::xso::error::Error;
177
178 fn try_from(other: &#ident) -> ::core::result::Result<Self, Self::Error> {
179 ::xso::transform(other)
180 }
181 }
182 });
183
184 if def.debug() {
185 println!("{}", result);
186 }
187
188 Ok(result)
189}
190
191/// Macro to derive a `xso::AsXml` implementation on a type.
192///
193/// The user-facing documentation for this macro lives in the `xso` crate.
194#[proc_macro_derive(AsXml, attributes(xml))]
195pub fn as_xml(input: RawTokenStream) -> RawTokenStream {
196 // Shim wrapper around `as_xml_impl` which converts any errors into
197 // actual compiler errors within the resulting token stream.
198 let item = syn::parse_macro_input!(input as Item);
199 match as_xml_impl(item) {
200 Ok(v) => v.into(),
201 Err(e) => e.into_compile_error().into(),
202 }
203}