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