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//! Handling of structs
8
9use proc_macro2::{Span, TokenStream};
10use quote::quote;
11use syn::*;
12
13use crate::compound::Compound;
14use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
15
16/// Parts necessary to construct a `::xso::FromXml` implementation.
17pub(crate) struct FromXmlParts {
18 /// Additional items necessary for the implementation.
19 pub(crate) defs: TokenStream,
20
21 /// The body of the `::xso::FromXml::from_xml` function.
22 pub(crate) from_events_body: TokenStream,
23
24 /// The name of the type which is the `::xso::FromXml::Builder`.
25 pub(crate) builder_ty_ident: Ident,
26}
27
28/// Parts necessary to construct a `::xso::AsXml` implementation.
29pub(crate) struct AsXmlParts {
30 /// Additional items necessary for the implementation.
31 pub(crate) defs: TokenStream,
32
33 /// The body of the `::xso::AsXml::as_xml_iter` function.
34 pub(crate) as_xml_iter_body: TokenStream,
35
36 /// The type which is the `::xso::AsXml::ItemIter`.
37 pub(crate) item_iter_ty: Type,
38
39 /// The lifetime name used in `item_iter_ty`.
40 pub(crate) item_iter_ty_lifetime: Lifetime,
41}
42
43/// Definition of a struct and how to parse it.
44pub(crate) struct StructDef {
45 /// The XML namespace of the element to map the struct to.
46 namespace: NamespaceRef,
47
48 /// The XML name of the element to map the struct to.
49 name: NameRef,
50
51 /// The field(s) of this struct.
52 inner: Compound,
53
54 /// Name of the target type.
55 target_ty_ident: Ident,
56
57 /// Name of the builder type.
58 builder_ty_ident: Ident,
59
60 /// Name of the iterator type.
61 item_iter_ty_ident: Ident,
62
63 /// Flag whether debug mode is enabled.
64 debug: bool,
65}
66
67impl StructDef {
68 /// Create a new struct from its name, meta, and fields.
69 pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
70 let Some(namespace) = meta.namespace else {
71 return Err(Error::new(meta.span, "`namespace` is required on structs"));
72 };
73
74 let Some(name) = meta.name else {
75 return Err(Error::new(meta.span, "`name` is required on structs"));
76 };
77
78 Ok(Self {
79 namespace,
80 name,
81 inner: Compound::from_fields(fields)?,
82 target_ty_ident: ident.clone(),
83 builder_ty_ident: quote::format_ident!("{}FromXmlBuilder", ident),
84 item_iter_ty_ident: quote::format_ident!("{}AsXmlIterator", ident),
85 debug: meta.debug.is_set(),
86 })
87 }
88
89 pub(crate) fn make_from_events_builder(
90 &self,
91 vis: &Visibility,
92 name_ident: &Ident,
93 attrs_ident: &Ident,
94 ) -> Result<FromXmlParts> {
95 let xml_namespace = &self.namespace;
96 let xml_name = &self.name;
97
98 let target_ty_ident = &self.target_ty_ident;
99 let builder_ty_ident = &self.builder_ty_ident;
100 let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
101
102 let defs = self
103 .inner
104 .make_from_events_statemachine(
105 &state_ty_ident,
106 &Path::from(target_ty_ident.clone()).into(),
107 "Struct",
108 )?
109 .with_augmented_init(|init| {
110 quote! {
111 if name.0 != #xml_namespace || name.1 != #xml_name {
112 ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
113 name,
114 attrs,
115 })
116 } else {
117 #init
118 }
119 }
120 })
121 .compile()
122 .render(
123 vis,
124 builder_ty_ident,
125 &state_ty_ident,
126 &TypePath {
127 qself: None,
128 path: target_ty_ident.clone().into(),
129 }
130 .into(),
131 )?;
132
133 Ok(FromXmlParts {
134 defs,
135 from_events_body: quote! {
136 #builder_ty_ident::new(#name_ident, #attrs_ident)
137 },
138 builder_ty_ident: builder_ty_ident.clone(),
139 })
140 }
141
142 pub(crate) fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
143 let xml_namespace = &self.namespace;
144 let xml_name = &self.name;
145
146 let target_ty_ident = &self.target_ty_ident;
147 let item_iter_ty_ident = &self.item_iter_ty_ident;
148 let item_iter_ty_lifetime = Lifetime {
149 apostrophe: Span::call_site(),
150 ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
151 };
152 let item_iter_ty = Type::Path(TypePath {
153 qself: None,
154 path: Path {
155 leading_colon: None,
156 segments: [PathSegment {
157 ident: item_iter_ty_ident.clone(),
158 arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
159 colon2_token: None,
160 lt_token: token::Lt {
161 spans: [Span::call_site()],
162 },
163 args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
164 .into_iter()
165 .collect(),
166 gt_token: token::Gt {
167 spans: [Span::call_site()],
168 },
169 }),
170 }]
171 .into_iter()
172 .collect(),
173 },
174 });
175 let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
176
177 let defs = self
178 .inner
179 .make_as_item_iter_statemachine(
180 &target_ty_ident.clone().into(),
181 "Struct",
182 &item_iter_ty_lifetime,
183 )?
184 .with_augmented_init(|init| {
185 quote! {
186 let name = (
187 ::xso::exports::rxml::Namespace::from(#xml_namespace),
188 ::std::borrow::Cow::Borrowed(#xml_name),
189 );
190 #init
191 }
192 })
193 .compile()
194 .render(
195 vis,
196 &TypePath {
197 qself: None,
198 path: target_ty_ident.clone().into(),
199 }
200 .into(),
201 &state_ty_ident,
202 &item_iter_ty_lifetime,
203 &item_iter_ty,
204 )?;
205
206 Ok(AsXmlParts {
207 defs,
208 as_xml_iter_body: quote! {
209 #item_iter_ty_ident::new(self)
210 },
211 item_iter_ty,
212 item_iter_ty_lifetime,
213 })
214 }
215
216 pub(crate) fn debug(&self) -> bool {
217 self.debug
218 }
219}