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::meta::NamespaceRef;
16use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
17use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
18use crate::types::{feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty};
19
20/// A struct or enum variant's contents.
21pub(crate) struct Compound {
22 /// The fields of this compound.
23 fields: Vec<FieldDef>,
24}
25
26impl Compound {
27 /// Construct a compound from processed field definitions.
28 pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
29 compound_fields: I,
30 ) -> Result<Self> {
31 let compound_fields = compound_fields.into_iter();
32 let size_hint = compound_fields.size_hint();
33 let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
34 let mut text_field = None;
35 for field in compound_fields {
36 let field = field?;
37
38 if field.is_text_field() {
39 if let Some(other_field) = text_field.as_ref() {
40 let mut err = Error::new_spanned(
41 field.member(),
42 "only one `#[xml(text)]` field allowed per compound",
43 );
44 err.combine(Error::new(
45 *other_field,
46 "the other `#[xml(text)]` field is here",
47 ));
48 return Err(err);
49 }
50 text_field = Some(field.member().span())
51 }
52
53 fields.push(field);
54 }
55 Ok(Self { fields })
56 }
57
58 /// Construct a compound from fields.
59 pub(crate) fn from_fields(
60 compound_fields: &Fields,
61 container_namespace: &NamespaceRef,
62 ) -> Result<Self> {
63 Self::from_field_defs(compound_fields.iter().enumerate().map(|(i, field)| {
64 let index = match i.try_into() {
65 Ok(v) => v,
66 // we are converting to u32, are you crazy?!
67 // (u32, because syn::Member::Index needs that.)
68 Err(_) => {
69 return Err(Error::new_spanned(
70 field,
71 "okay, mate, that are way too many fields. get your life together.",
72 ))
73 }
74 };
75 FieldDef::from_field(field, index, container_namespace)
76 }))
77 }
78
79 /// Make and return a set of states which is used to construct the target
80 /// type from XML events.
81 ///
82 /// The states are returned as partial state machine. See the return
83 /// type's documentation for details.
84 pub(crate) fn make_from_events_statemachine(
85 &self,
86 state_ty_ident: &Ident,
87 output_name: &ParentRef,
88 state_prefix: &str,
89 ) -> Result<FromEventsSubmachine> {
90 let scope = FromEventsScope::new(state_ty_ident.clone());
91 let FromEventsScope {
92 ref attrs,
93 ref builder_data_ident,
94 ref text,
95 ref substate_data,
96 ref substate_result,
97 ..
98 } = scope;
99
100 let default_state_ident = quote::format_ident!("{}Default", state_prefix);
101 let builder_data_ty: Type = TypePath {
102 qself: None,
103 path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
104 }
105 .into();
106 let mut states = Vec::new();
107
108 let mut builder_data_def = TokenStream::default();
109 let mut builder_data_init = TokenStream::default();
110 let mut output_cons = TokenStream::default();
111 let mut child_matchers = TokenStream::default();
112 let mut text_handler = None;
113 let mut extra_defs = TokenStream::default();
114 let is_tuple = !output_name.is_path();
115
116 for (i, field) in self.fields.iter().enumerate() {
117 let member = field.member();
118 let builder_field_name = mangle_member(member);
119 let part = field.make_builder_part(&scope, output_name)?;
120 let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
121
122 match part {
123 FieldBuilderPart::Init {
124 value: FieldTempInit { ty, init },
125 } => {
126 builder_data_def.extend(quote! {
127 #builder_field_name: #ty,
128 });
129
130 builder_data_init.extend(quote! {
131 #builder_field_name: #init,
132 });
133
134 if is_tuple {
135 output_cons.extend(quote! {
136 #builder_data_ident.#builder_field_name,
137 });
138 } else {
139 output_cons.extend(quote! {
140 #member: #builder_data_ident.#builder_field_name,
141 });
142 }
143 }
144
145 FieldBuilderPart::Text {
146 value: FieldTempInit { ty, init },
147 collect,
148 finalize,
149 } => {
150 if text_handler.is_some() {
151 // the existence of only one text handler is enforced
152 // by Compound's constructor(s).
153 panic!("more than one field attempts to collect text data");
154 }
155
156 builder_data_def.extend(quote! {
157 #builder_field_name: #ty,
158 });
159 builder_data_init.extend(quote! {
160 #builder_field_name: #init,
161 });
162 text_handler = Some(quote! {
163 #collect
164 ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
165 Self::#default_state_ident { #builder_data_ident }
166 ))
167 });
168
169 if is_tuple {
170 output_cons.extend(quote! {
171 #finalize,
172 });
173 } else {
174 output_cons.extend(quote! {
175 #member: #finalize,
176 });
177 }
178 }
179
180 FieldBuilderPart::Nested {
181 extra_defs: field_extra_defs,
182 value: FieldTempInit { ty, init },
183 matcher,
184 builder,
185 collect,
186 finalize,
187 } => {
188 let feed = feed_fn(builder.clone());
189
190 states.push(State::new_with_builder(
191 state_name.clone(),
192 &builder_data_ident,
193 &builder_data_ty,
194 ).with_field(
195 substate_data,
196 &builder,
197 ).with_mut(substate_data).with_impl(quote! {
198 match #feed(&mut #substate_data, ev)? {
199 ::std::option::Option::Some(#substate_result) => {
200 #collect
201 ::std::result::Result::Ok(::std::ops::ControlFlow::Break(Self::#default_state_ident {
202 #builder_data_ident,
203 }))
204 }
205 ::std::option::Option::None => {
206 ::std::result::Result::Ok(::std::ops::ControlFlow::Break(Self::#state_name {
207 #builder_data_ident,
208 #substate_data,
209 }))
210 }
211 }
212 }));
213
214 builder_data_def.extend(quote! {
215 #builder_field_name: #ty,
216 });
217
218 builder_data_init.extend(quote! {
219 #builder_field_name: #init,
220 });
221
222 child_matchers.extend(quote! {
223 let (name, attrs) = match #matcher {
224 ::std::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
225 ::std::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::std::result::Result::Err(e),
226 ::std::result::Result::Ok(#substate_data) => {
227 return ::std::result::Result::Ok(::std::ops::ControlFlow::Break(Self::#state_name {
228 #builder_data_ident,
229 #substate_data,
230 }))
231 }
232 };
233 });
234
235 if is_tuple {
236 output_cons.extend(quote! {
237 #finalize,
238 });
239 } else {
240 output_cons.extend(quote! {
241 #member: #finalize,
242 });
243 }
244
245 extra_defs.extend(field_extra_defs);
246 }
247 }
248 }
249
250 let text_handler = match text_handler {
251 Some(v) => v,
252 None => quote! {
253 // note: u8::is_ascii_whitespace includes U+000C, which is not
254 // part of XML's white space definition.'
255 if #text.as_bytes().iter().any(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') {
256 ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
257 } else {
258 ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
259 Self::#default_state_ident { #builder_data_ident }
260 ))
261 }
262 },
263 };
264
265 let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
266 let unknown_child_err = format!("Unknown child in {}.", output_name);
267
268 let output_cons = match output_name {
269 ParentRef::Named(ref path) => {
270 quote! {
271 #path { #output_cons }
272 }
273 }
274 ParentRef::Unnamed { .. } => {
275 quote! {
276 ( #output_cons )
277 }
278 }
279 };
280
281 states.push(State::new_with_builder(
282 default_state_ident.clone(),
283 builder_data_ident,
284 &builder_data_ty,
285 ).with_impl(quote! {
286 match ev {
287 // EndElement in Default state -> done parsing.
288 ::xso::exports::rxml::Event::EndElement(_) => {
289 ::core::result::Result::Ok(::std::ops::ControlFlow::Continue(
290 #output_cons
291 ))
292 }
293 ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
294 #child_matchers
295 let _ = (name, attrs);
296 ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
297 }
298 ::xso::exports::rxml::Event::Text(_, #text) => {
299 #text_handler
300 }
301 // we ignore these: a correct parser only generates
302 // them at document start, and there we want to indeed
303 // not worry about them being in front of the first
304 // element.
305 ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
306 Self::#default_state_ident { #builder_data_ident }
307 ))
308 }
309 }));
310
311 Ok(FromEventsSubmachine {
312 defs: quote! {
313 #extra_defs
314
315 struct #builder_data_ty {
316 #builder_data_def
317 }
318 },
319 states,
320 init: quote! {
321 let #builder_data_ident = #builder_data_ty {
322 #builder_data_init
323 };
324 if #attrs.len() > 0 {
325 return ::core::result::Result::Err(::xso::error::Error::Other(
326 #unknown_attr_err,
327 ).into());
328 }
329 ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
330 },
331 })
332 }
333
334 /// Make and return a set of states which is used to destructure the
335 /// target type into XML events.
336 ///
337 /// The states are returned as partial state machine. See the return
338 /// type's documentation for details.
339 ///
340 /// **Important:** The returned submachine is not in functional state!
341 /// It's `init` must be modified so that a variable called `name` of type
342 /// `rxml::QName` is in scope.
343 pub(crate) fn make_as_item_iter_statemachine(
344 &self,
345 input_name: &ParentRef,
346 state_ty_ident: &Ident,
347 state_prefix: &str,
348 lifetime: &Lifetime,
349 ) -> Result<AsItemsSubmachine> {
350 let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
351
352 let element_head_start_state_ident =
353 quote::format_ident!("{}ElementHeadStart", state_prefix);
354 let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
355 let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
356 let name_ident = quote::format_ident!("name");
357 let ns_ident = quote::format_ident!("ns");
358 let dummy_ident = quote::format_ident!("dummy");
359 let mut states = Vec::new();
360
361 let is_tuple = !input_name.is_path();
362 let mut destructure = TokenStream::default();
363 let mut start_init = TokenStream::default();
364 let mut extra_defs = TokenStream::default();
365
366 states.push(
367 State::new(element_head_start_state_ident.clone())
368 .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
369 .with_field(&ns_ident, &namespace_ty(Span::call_site()))
370 .with_field(
371 &name_ident,
372 &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
373 ),
374 );
375
376 let mut element_head_end_idx = states.len();
377 states.push(
378 State::new(element_head_end_state_ident.clone()).with_impl(quote! {
379 ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
380 }),
381 );
382
383 for (i, field) in self.fields.iter().enumerate() {
384 let member = field.member();
385 let bound_name = mangle_member(member);
386 let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
387 let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
388 let ty = scope.borrow(field.ty().clone());
389
390 match part {
391 FieldIteratorPart::Header { generator } => {
392 // we have to make sure that we carry our data around in
393 // all the previous states.
394 for state in &mut states[..element_head_end_idx] {
395 state.add_field(&bound_name, &ty);
396 }
397 states.insert(
398 element_head_end_idx,
399 State::new(state_name)
400 .with_field(&bound_name, &ty)
401 .with_impl(quote! {
402 #generator
403 }),
404 );
405 element_head_end_idx += 1;
406
407 if is_tuple {
408 destructure.extend(quote! {
409 ref #bound_name,
410 });
411 } else {
412 destructure.extend(quote! {
413 #member: ref #bound_name,
414 });
415 }
416 start_init.extend(quote! {
417 #bound_name,
418 });
419 }
420
421 FieldIteratorPart::Text { generator } => {
422 // we have to make sure that we carry our data around in
423 // all the previous states.
424 for state in states.iter_mut() {
425 state.add_field(&bound_name, &ty);
426 }
427 states.push(
428 State::new(state_name)
429 .with_field(&bound_name, &ty)
430 .with_impl(quote! {
431 #generator.map(|value| ::xso::Item::Text(
432 value,
433 ))
434 }),
435 );
436 if is_tuple {
437 destructure.extend(quote! {
438 #bound_name,
439 });
440 } else {
441 destructure.extend(quote! {
442 #member: #bound_name,
443 });
444 }
445 start_init.extend(quote! {
446 #bound_name,
447 });
448 }
449
450 FieldIteratorPart::Content {
451 extra_defs: field_extra_defs,
452 value: FieldTempInit { ty, init },
453 generator,
454 } => {
455 // we have to make sure that we carry our data around in
456 // all the previous states.
457 for state in states.iter_mut() {
458 state.add_field(&bound_name, &ty);
459 }
460
461 states.push(
462 State::new(state_name.clone())
463 .with_field(&bound_name, &ty)
464 .with_mut(&bound_name)
465 .with_impl(quote! {
466 #generator?
467 }),
468 );
469 if is_tuple {
470 destructure.extend(quote! {
471 #bound_name,
472 });
473 } else {
474 destructure.extend(quote! {
475 #member: #bound_name,
476 });
477 }
478 start_init.extend(quote! {
479 #bound_name: #init,
480 });
481
482 extra_defs.extend(field_extra_defs);
483 }
484 }
485 }
486
487 states[0].set_impl(quote! {
488 {
489 ::core::option::Option::Some(::xso::Item::ElementHeadStart(
490 #ns_ident,
491 #name_ident,
492 ))
493 }
494 });
495
496 states.push(
497 State::new(element_foot_state_ident.clone()).with_impl(quote! {
498 ::core::option::Option::Some(::xso::Item::ElementFoot)
499 }),
500 );
501
502 let destructure = match input_name {
503 ParentRef::Named(ref input_path) => quote! {
504 #input_path { #destructure }
505 },
506 ParentRef::Unnamed { .. } => quote! {
507 ( #destructure )
508 },
509 };
510
511 Ok(AsItemsSubmachine {
512 defs: extra_defs,
513 states,
514 destructure,
515 init: quote! {
516 Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
517 },
518 })
519 }
520}