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, NestedMatcher};
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, ref_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 fallback_child_matcher = None;
113 let mut text_handler = None;
114 let mut extra_defs = TokenStream::default();
115 let is_tuple = !output_name.is_path();
116
117 for (i, field) in self.fields.iter().enumerate() {
118 let member = field.member();
119 let builder_field_name = mangle_member(member);
120 let part = field.make_builder_part(&scope, output_name)?;
121 let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
122
123 match part {
124 FieldBuilderPart::Init {
125 value: FieldTempInit { ty, init },
126 } => {
127 builder_data_def.extend(quote! {
128 #builder_field_name: #ty,
129 });
130
131 builder_data_init.extend(quote! {
132 #builder_field_name: #init,
133 });
134
135 if is_tuple {
136 output_cons.extend(quote! {
137 #builder_data_ident.#builder_field_name,
138 });
139 } else {
140 output_cons.extend(quote! {
141 #member: #builder_data_ident.#builder_field_name,
142 });
143 }
144 }
145
146 FieldBuilderPart::Text {
147 value: FieldTempInit { ty, init },
148 collect,
149 finalize,
150 } => {
151 if text_handler.is_some() {
152 // the existence of only one text handler is enforced
153 // by Compound's constructor(s).
154 panic!("more than one field attempts to collect text data");
155 }
156
157 builder_data_def.extend(quote! {
158 #builder_field_name: #ty,
159 });
160 builder_data_init.extend(quote! {
161 #builder_field_name: #init,
162 });
163 text_handler = Some(quote! {
164 #collect
165 ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
166 Self::#default_state_ident { #builder_data_ident }
167 ))
168 });
169
170 if is_tuple {
171 output_cons.extend(quote! {
172 #finalize,
173 });
174 } else {
175 output_cons.extend(quote! {
176 #member: #finalize,
177 });
178 }
179 }
180
181 FieldBuilderPart::Nested {
182 extra_defs: field_extra_defs,
183 value: FieldTempInit { ty, init },
184 matcher,
185 builder,
186 collect,
187 finalize,
188 } => {
189 let feed = feed_fn(builder.clone());
190
191 states.push(State::new_with_builder(
192 state_name.clone(),
193 &builder_data_ident,
194 &builder_data_ty,
195 ).with_field(
196 substate_data,
197 &builder,
198 ).with_mut(substate_data).with_impl(quote! {
199 match #feed(&mut #substate_data, ev)? {
200 ::core::option::Option::Some(#substate_result) => {
201 #collect
202 ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
203 #builder_data_ident,
204 }))
205 }
206 ::core::option::Option::None => {
207 ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
208 #builder_data_ident,
209 #substate_data,
210 }))
211 }
212 }
213 }));
214
215 builder_data_def.extend(quote! {
216 #builder_field_name: #ty,
217 });
218
219 builder_data_init.extend(quote! {
220 #builder_field_name: #init,
221 });
222
223 match matcher {
224 NestedMatcher::Selective(matcher) => {
225 child_matchers.extend(quote! {
226 let (name, attrs) = match #matcher {
227 ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
228 ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e),
229 ::core::result::Result::Ok(#substate_data) => {
230 return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
231 #builder_data_ident,
232 #substate_data,
233 }))
234 }
235 };
236 });
237 }
238 NestedMatcher::Fallback(matcher) => {
239 if let Some((span, _)) = fallback_child_matcher.as_ref() {
240 let mut err = Error::new(
241 field.span(),
242 "more than one field is attempting to consume all unmatched child elements"
243 );
244 err.combine(Error::new(
245 *span,
246 "the previous field collecting all unmatched child elements is here"
247 ));
248 return Err(err);
249 }
250
251 let matcher = quote! {
252 ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
253 #builder_data_ident,
254 #substate_data: { #matcher },
255 }))
256 };
257
258 fallback_child_matcher = Some((field.span(), matcher));
259 }
260 }
261
262 if is_tuple {
263 output_cons.extend(quote! {
264 #finalize,
265 });
266 } else {
267 output_cons.extend(quote! {
268 #member: #finalize,
269 });
270 }
271
272 extra_defs.extend(field_extra_defs);
273 }
274 }
275 }
276
277 let text_handler = match text_handler {
278 Some(v) => v,
279 None => quote! {
280 // note: u8::is_ascii_whitespace includes U+000C, which is not
281 // part of XML's white space definition.'
282 if !::xso::is_xml_whitespace(#text.as_bytes()) {
283 ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
284 } else {
285 ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
286 Self::#default_state_ident { #builder_data_ident }
287 ))
288 }
289 },
290 };
291
292 let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
293 let unknown_child_err = format!("Unknown child in {}.", output_name);
294
295 let output_cons = match output_name {
296 ParentRef::Named(ref path) => {
297 quote! {
298 #path { #output_cons }
299 }
300 }
301 ParentRef::Unnamed { .. } => {
302 quote! {
303 ( #output_cons )
304 }
305 }
306 };
307
308 let child_fallback = match fallback_child_matcher {
309 Some((_, matcher)) => matcher,
310 None => quote! {
311 let _ = (name, attrs);
312 ::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
313 },
314 };
315
316 states.push(State::new_with_builder(
317 default_state_ident.clone(),
318 builder_data_ident,
319 &builder_data_ty,
320 ).with_impl(quote! {
321 match ev {
322 // EndElement in Default state -> done parsing.
323 ::xso::exports::rxml::Event::EndElement(_) => {
324 ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(
325 #output_cons
326 ))
327 }
328 ::xso::exports::rxml::Event::StartElement(_, name, attrs) => {
329 #child_matchers
330 #child_fallback
331 }
332 ::xso::exports::rxml::Event::Text(_, #text) => {
333 #text_handler
334 }
335 // we ignore these: a correct parser only generates
336 // them at document start, and there we want to indeed
337 // not worry about them being in front of the first
338 // element.
339 ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(
340 Self::#default_state_ident { #builder_data_ident }
341 ))
342 }
343 }));
344
345 Ok(FromEventsSubmachine {
346 defs: quote! {
347 #extra_defs
348
349 struct #builder_data_ty {
350 #builder_data_def
351 }
352 },
353 states,
354 init: quote! {
355 let #builder_data_ident = #builder_data_ty {
356 #builder_data_init
357 };
358 if #attrs.len() > 0 {
359 return ::core::result::Result::Err(::xso::error::Error::Other(
360 #unknown_attr_err,
361 ).into());
362 }
363 ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
364 },
365 })
366 }
367
368 /// Make and return a set of states which is used to destructure the
369 /// target type into XML events.
370 ///
371 /// The states are returned as partial state machine. See the return
372 /// type's documentation for details.
373 ///
374 /// **Important:** The returned submachine is not in functional state!
375 /// It's `init` must be modified so that a variable called `name` of type
376 /// `rxml::QName` is in scope.
377 pub(crate) fn make_as_item_iter_statemachine(
378 &self,
379 input_name: &ParentRef,
380 state_ty_ident: &Ident,
381 state_prefix: &str,
382 lifetime: &Lifetime,
383 ) -> Result<AsItemsSubmachine> {
384 let scope = AsItemsScope::new(lifetime, state_ty_ident.clone());
385
386 let element_head_start_state_ident =
387 quote::format_ident!("{}ElementHeadStart", state_prefix);
388 let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix);
389 let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix);
390 let name_ident = quote::format_ident!("name");
391 let ns_ident = quote::format_ident!("ns");
392 let dummy_ident = quote::format_ident!("dummy");
393 let mut states = Vec::new();
394
395 let is_tuple = !input_name.is_path();
396 let mut destructure = TokenStream::default();
397 let mut start_init = TokenStream::default();
398 let mut extra_defs = TokenStream::default();
399
400 states.push(
401 State::new(element_head_start_state_ident.clone())
402 .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone()))
403 .with_field(&ns_ident, &namespace_ty(Span::call_site()))
404 .with_field(
405 &name_ident,
406 &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()),
407 ),
408 );
409
410 let mut element_head_end_idx = states.len();
411 states.push(
412 State::new(element_head_end_state_ident.clone()).with_impl(quote! {
413 ::core::option::Option::Some(::xso::Item::ElementHeadEnd)
414 }),
415 );
416
417 for (i, field) in self.fields.iter().enumerate() {
418 let member = field.member();
419 let bound_name = mangle_member(member);
420 let part = field.make_iterator_part(&scope, input_name, &bound_name)?;
421 let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
422 let ty = scope.borrow(field.ty().clone());
423
424 match part {
425 FieldIteratorPart::Header { generator } => {
426 // we have to make sure that we carry our data around in
427 // all the previous states.
428 for state in &mut states[..element_head_end_idx] {
429 state.add_field(&bound_name, &ty);
430 }
431 states.insert(
432 element_head_end_idx,
433 State::new(state_name)
434 .with_field(&bound_name, &ty)
435 .with_impl(quote! {
436 #generator
437 }),
438 );
439 element_head_end_idx += 1;
440
441 if is_tuple {
442 destructure.extend(quote! {
443 ref #bound_name,
444 });
445 } else {
446 destructure.extend(quote! {
447 #member: ref #bound_name,
448 });
449 }
450 start_init.extend(quote! {
451 #bound_name,
452 });
453 }
454
455 FieldIteratorPart::Text { generator } => {
456 // we have to make sure that we carry our data around in
457 // all the previous states.
458 for state in states.iter_mut() {
459 state.add_field(&bound_name, &ty);
460 }
461 states.push(
462 State::new(state_name)
463 .with_field(&bound_name, &ty)
464 .with_impl(quote! {
465 #generator.map(|value| ::xso::Item::Text(
466 value,
467 ))
468 }),
469 );
470 if is_tuple {
471 destructure.extend(quote! {
472 #bound_name,
473 });
474 } else {
475 destructure.extend(quote! {
476 #member: #bound_name,
477 });
478 }
479 start_init.extend(quote! {
480 #bound_name,
481 });
482 }
483
484 FieldIteratorPart::Content {
485 extra_defs: field_extra_defs,
486 value: FieldTempInit { ty, init },
487 generator,
488 } => {
489 // we have to make sure that we carry our data around in
490 // all the previous states.
491 for state in states.iter_mut() {
492 state.add_field(&bound_name, &ty);
493 }
494
495 states.push(
496 State::new(state_name.clone())
497 .with_field(&bound_name, &ty)
498 .with_mut(&bound_name)
499 .with_impl(quote! {
500 #generator?
501 }),
502 );
503 if is_tuple {
504 destructure.extend(quote! {
505 #bound_name,
506 });
507 } else {
508 destructure.extend(quote! {
509 #member: #bound_name,
510 });
511 }
512 start_init.extend(quote! {
513 #bound_name: #init,
514 });
515
516 extra_defs.extend(field_extra_defs);
517 }
518 }
519 }
520
521 states[0].set_impl(quote! {
522 {
523 ::core::option::Option::Some(::xso::Item::ElementHeadStart(
524 #ns_ident,
525 #name_ident,
526 ))
527 }
528 });
529
530 states.push(
531 State::new(element_foot_state_ident.clone()).with_impl(quote! {
532 ::core::option::Option::Some(::xso::Item::ElementFoot)
533 }),
534 );
535
536 let destructure = match input_name {
537 ParentRef::Named(ref input_path) => quote! {
538 #input_path { #destructure }
539 },
540 ParentRef::Unnamed { .. } => quote! {
541 ( #destructure )
542 },
543 };
544
545 Ok(AsItemsSubmachine {
546 defs: extra_defs,
547 states,
548 destructure,
549 init: quote! {
550 Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
551 },
552 })
553 }
554
555 /// Return a reference to this compound's only field's type.
556 ///
557 /// If the compound does not have exactly one field, this function returns
558 /// None.
559 pub(crate) fn single_ty(&self) -> Option<&Type> {
560 if self.fields.len() > 1 {
561 return None;
562 }
563 self.fields.get(0).map(|x| x.ty())
564 }
565
566 /// Construct a tuple type with this compound's field's types in the same
567 /// order as they appear in the compound.
568 pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
569 TypeTuple {
570 paren_token: token::Paren::default(),
571 elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
572 }
573 }
574
575 /// Construct a tuple type with references to this compound's field's
576 /// types in the same order as they appear in the compound, with the given
577 /// lifetime.
578 pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
579 TypeTuple {
580 paren_token: token::Paren::default(),
581 elems: self
582 .fields
583 .iter()
584 .map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
585 .collect(),
586 }
587 }
588
589 /// Return the number of fields in this compound.
590 pub(crate) fn field_count(&self) -> usize {
591 self.fields.len()
592 }
593}