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