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 the insides of compound structures (structs and enum variants)
8
9use proc_macro2::{Span, TokenStream};
10use quote::quote;
11use syn::{spanned::Spanned, *};
12
13use crate::error_message::ParentRef;
14use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit};
15use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
16use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
17use crate::types::{namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty};
18
19/// A struct or enum variant's contents.
20pub(crate) struct Compound {
21 /// The fields of this compound.
22 fields: Vec<FieldDef>,
23}
24
25impl Compound {
26 /// Construct a compound from fields.
27 pub(crate) fn from_fields(compound_fields: &Fields) -> Result<Self> {
28 let mut fields = Vec::with_capacity(compound_fields.len());
29 let mut text_field = None;
30 for (i, field) in compound_fields.iter().enumerate() {
31 let index = match i.try_into() {
32 Ok(v) => v,
33 // we are converting to u32, are you crazy?!
34 // (u32, because syn::Member::Index needs that.)
35 Err(_) => {
36 return Err(Error::new_spanned(
37 field,
38 "okay, mate, that are way too many fields. get your life together.",
39 ))
40 }
41 };
42 let field = FieldDef::from_field(field, index)?;
43
44 if field.is_text_field() {
45 if let Some(other_field) = text_field.as_ref() {
46 let mut err = Error::new_spanned(
47 field.member(),
48 "only one `#[xml(text)]` field allowed per compound",
49 );
50 err.combine(Error::new(
51 *other_field,
52 "the other `#[xml(text)]` field is here",
53 ));
54 return Err(err);
55 }
56 text_field = Some(field.member().span())
57 }
58
59 fields.push(field);
60 }
61
62 Ok(Self { fields })
63 }
64
65 /// Make and return a set of states which is used to construct the target
66 /// type from XML events.
67 ///
68 /// The states are returned as partial state machine. See the return
69 /// type's documentation for details.
70 pub(crate) fn make_from_events_statemachine(
71 &self,
72 state_ty_ident: &Ident,
73 output_name: &ParentRef,
74 state_prefix: &str,
75 ) -> Result<FromEventsSubmachine> {
76 let scope = FromEventsScope::new();
77 let FromEventsScope {
78 ref attrs,
79 ref builder_data_ident,
80 ref text,
81 ..
82 } = scope;
83
84 let default_state_ident = quote::format_ident!("{}Default", state_prefix);
85 let builder_data_ty: Type = TypePath {
86 qself: None,
87 path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
88 }
89 .into();
90 let mut states = Vec::new();
91
92 let mut builder_data_def = TokenStream::default();
93 let mut builder_data_init = TokenStream::default();
94 let mut output_cons = TokenStream::default();
95 let mut text_handler = None;
96
97 for field in self.fields.iter() {
98 let member = field.member();
99 let builder_field_name = mangle_member(member);
100 let part = field.make_builder_part(&scope, output_name)?;
101
102 match part {
103 FieldBuilderPart::Init {
104 value: FieldTempInit { ty, init },
105 } => {
106 builder_data_def.extend(quote! {
107 #builder_field_name: #ty,
108 });
109
110 builder_data_init.extend(quote! {
111 #builder_field_name: #init,
112 });
113
114 output_cons.extend(quote! {
115 #member: #builder_data_ident.#builder_field_name,
116 });
117 }
118
119 FieldBuilderPart::Text {
120 value: FieldTempInit { ty, init },
121 collect,
122 finalize,
123 } => {
124 if text_handler.is_some() {
125 // the existence of only one text handler is enforced
126 // by Compound's constructor(s).
127 panic!("more than one field attempts to collect text data");
128 }
129
130 builder_data_def.extend(quote! {
131 #builder_field_name: #ty,
132 });
133 builder_data_init.extend(quote! {
134 #builder_field_name: #init,
135 });
136 text_handler = Some(quote! {
137 #collect
138 ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
139 Self::#default_state_ident { #builder_data_ident }
140 ))
141 });
142 output_cons.extend(quote! {
143 #member: #finalize,
144 });
145 }
146 }
147 }
148
149 let text_handler = match text_handler {
150 Some(v) => v,
151 None => quote! {
152 // note: u8::is_ascii_whitespace includes U+000C, which is not
153 // part of XML's white space definition.'
154 if #text.as_bytes().iter().any(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') {
155 ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
156 } else {
157 ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
158 Self::#default_state_ident { #builder_data_ident }
159 ))
160 }
161 },
162 };
163
164 let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
165 let unknown_child_err = format!("Unknown child in {}.", output_name);
166
167 let output_cons = match output_name {
168 ParentRef::Named(ref path) => {
169 quote! {
170 #path { #output_cons }
171 }
172 }
173 };
174
175 states.push(State::new_with_builder(
176 default_state_ident.clone(),
177 builder_data_ident,
178 &builder_data_ty,
179 ).with_impl(quote! {
180 match ev {
181 // EndElement in Default state -> done parsing.
182 ::xso::exports::rxml::Event::EndElement(_) => {
183 ::core::result::Result::Ok(::std::ops::ControlFlow::Continue(
184 #output_cons
185 ))
186 }
187 ::xso::exports::rxml::Event::StartElement(..) => {
188 ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
189 }
190 ::xso::exports::rxml::Event::Text(_, #text) => {
191 #text_handler
192 }
193 // we ignore these: a correct parser only generates
194 // them at document start, and there we want to indeed
195 // not worry about them being in front of the first
196 // element.
197 ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
198 Self::#default_state_ident { #builder_data_ident }
199 ))
200 }
201 }));
202
203 Ok(FromEventsSubmachine {
204 defs: quote! {
205 struct #builder_data_ty {
206 #builder_data_def
207 }
208 },
209 states,
210 init: quote! {
211 let #builder_data_ident = #builder_data_ty {
212 #builder_data_init
213 };
214 if #attrs.len() > 0 {
215 return ::core::result::Result::Err(::xso::error::Error::Other(
216 #unknown_attr_err,
217 ).into());
218 }
219 ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
220 },
221 })
222 }
223
224 /// Make and return a set of states which is used to destructure the
225 /// target type into XML events.
226 ///
227 /// The states are returned as partial state machine. See the return
228 /// type's documentation for details.
229 ///
230 /// **Important:** The returned submachine is not in functional state!
231 /// It's `init` must be modified so that a variable called `name` of type
232 /// `rxml::QName` is in scope.
233 pub(crate) fn make_as_item_iter_statemachine(
234 &self,
235 input_name: &Path,
236 state_prefix: &str,
237 lifetime: &Lifetime,
238 ) -> Result<AsItemsSubmachine> {
239 let scope = AsItemsScope::new(lifetime);
240
241 let element_head_start_state_ident =
242 quote::format_ident!("{}ElementHeadStart", state_prefix);
243 let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
244 let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
245 let name_ident = quote::format_ident!("name");
246 let ns_ident = quote::format_ident!("ns");
247 let dummy_ident = quote::format_ident!("dummy");
248 let mut states = Vec::new();
249
250 let mut destructure = TokenStream::default();
251 let mut start_init = TokenStream::default();
252
253 states.push(
254 State::new(element_head_start_state_ident.clone())
255 .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
256 .with_field(&ns_ident, &namespace_ty(Span::call_site()))
257 .with_field(
258 &name_ident,
259 &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
260 ),
261 );
262
263 let mut element_head_end_idx = states.len();
264 states.push(
265 State::new(element_head_end_state_ident.clone()).with_impl(quote! {
266 ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
267 }),
268 );
269
270 for (i, field) in self.fields.iter().enumerate() {
271 let member = field.member();
272 let bound_name = mangle_member(member);
273 let part = field.make_iterator_part(&bound_name)?;
274 let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
275 let ty = scope.borrow(field.ty().clone());
276
277 match part {
278 FieldIteratorPart::Header { generator } => {
279 // we have to make sure that we carry our data around in
280 // all the previous states.
281 for state in &mut states[..element_head_end_idx] {
282 state.add_field(&bound_name, &ty);
283 }
284 states.insert(
285 element_head_end_idx,
286 State::new(state_name)
287 .with_field(&bound_name, &ty)
288 .with_impl(quote! {
289 #generator
290 }),
291 );
292 element_head_end_idx += 1;
293
294 destructure.extend(quote! {
295 #member: ref #bound_name,
296 });
297 start_init.extend(quote! {
298 #bound_name,
299 });
300 }
301
302 FieldIteratorPart::Text { generator } => {
303 // we have to make sure that we carry our data around in
304 // all the previous states.
305 for state in states.iter_mut() {
306 state.add_field(&bound_name, &ty);
307 }
308 states.push(
309 State::new(state_name)
310 .with_field(&bound_name, &ty)
311 .with_impl(quote! {
312 #generator.map(|value| ::xso::Item::Text(
313 value,
314 ))
315 }),
316 );
317 destructure.extend(quote! {
318 #member: #bound_name,
319 });
320 start_init.extend(quote! {
321 #bound_name,
322 });
323 }
324 }
325 }
326
327 states[0].set_impl(quote! {
328 {
329 ::core::option::Option::Some(::xso::Item::ElementHeadStart(
330 #ns_ident,
331 #name_ident,
332 ))
333 }
334 });
335
336 states.push(
337 State::new(element_foot_state_ident.clone()).with_impl(quote! {
338 ::core::option::Option::Some(::xso::Item::ElementFoot)
339 }),
340 );
341
342 Ok(AsItemsSubmachine {
343 defs: TokenStream::default(),
344 states,
345 destructure: quote! {
346 #input_name { #destructure }
347 },
348 init: quote! {
349 Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
350 },
351 })
352 }
353}