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//! Compound (struct or enum variant) field types
8
9use proc_macro2::{Span, TokenStream};
10use quote::{quote, ToTokens};
11use syn::{spanned::Spanned, *};
12
13use rxml_validation::NcName;
14
15use crate::error_message::{self, ParentRef};
16use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
17use crate::scope::{AsItemsScope, FromEventsScope};
18use crate::types::{
19 as_optional_xml_text_fn, as_xml_iter_fn, as_xml_text_fn, default_fn, from_events_fn,
20 from_xml_builder_ty, from_xml_text_fn, item_iter_ty, option_ty, string_ty,
21 text_codec_decode_fn, text_codec_encode_fn,
22};
23
24/// Code slices necessary for declaring and initializing a temporary variable
25/// for parsing purposes.
26pub(crate) struct FieldTempInit {
27 /// The type of the temporary variable.
28 pub(crate) ty: Type,
29
30 /// The initializer for the temporary variable.
31 pub(crate) init: TokenStream,
32}
33
34/// Describe how a struct or enum variant's member is parsed from XML data.
35///
36/// This struct is returned from [`FieldDef::make_builder_part`] and
37/// contains code snippets and instructions for
38/// [`Compound::make_from_events_statemachine`][`crate::compound::Compound::make_from_events_statemachine`]
39/// to parse the field's data from XML.
40pub(crate) enum FieldBuilderPart {
41 /// Parse a field from the item's element's start event.
42 Init {
43 /// Expression and type which extracts the field's data from the
44 /// element's start event.
45 value: FieldTempInit,
46 },
47
48 /// Parse a field from text events.
49 Text {
50 /// Expression and type which initializes a buffer to use during
51 /// parsing.
52 value: FieldTempInit,
53
54 /// Statement which takes text and accumulates it into the temporary
55 /// value declared via `value`.
56 collect: TokenStream,
57
58 /// Expression which evaluates to the field's type, consuming the
59 /// temporary value.
60 finalize: TokenStream,
61 },
62
63 /// Parse a field from child element events.
64 Nested {
65 /// Expression and type which initializes a buffer to use during
66 /// parsing.
67 value: FieldTempInit,
68
69 /// Expression which evaluates to `Result<T, FromEventsError>`,
70 /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`.
71 ///
72 /// `T` must be the type specified in the
73 /// [`Self::Nested::builder`] field.
74 matcher: TokenStream,
75
76 /// Type implementing `xso::FromEventsBuilder` which parses the child
77 /// element.
78 ///
79 /// This type is returned by the expressions in
80 /// [`matcher`][`Self::Nested::matcher`].
81 builder: Type,
82
83 /// Expression which consumes the value stored in the identifier
84 /// [`crate::common::FromEventsScope::substate_result`][`FromEventsScope::substate_result`]
85 /// and somehow collects it into the field declared with
86 /// [`value`][`Self::Nested::value`].
87 collect: TokenStream,
88
89 /// Expression which consumes the data from the field declared with
90 /// [`value`][`Self::Nested::value`] and converts it into the field's
91 /// type.
92 finalize: TokenStream,
93 },
94}
95
96/// Describe how a struct or enum variant's member is converted to XML data.
97///
98/// This struct is returned from [`FieldDef::make_iterator_part`] and
99/// contains code snippets and instructions for
100/// [`Compound::make_into_events_statemachine`][`crate::compound::Compound::make_into_events_statemachine`]
101/// to convert the field's data into XML.
102pub(crate) enum FieldIteratorPart {
103 /// The field is emitted as part of StartElement.
104 Header {
105 /// An expression which consumes the field's value and returns a
106 /// `Item`.
107 generator: TokenStream,
108 },
109
110 /// The field is emitted as text item.
111 Text {
112 /// An expression which consumes the field's value and returns a
113 /// String, which is then emitted as text data.
114 generator: TokenStream,
115 },
116
117 /// The field is emitted as series of items which form a child element.
118 Content {
119 /// Expression and type which initializes the nested iterator.
120 ///
121 /// Note that this is evaluated at construction time of the iterator.
122 /// Fields of this variant do not get access to their original data,
123 /// unless they carry it in the contents of this `value`.
124 value: FieldTempInit,
125
126 /// An expression which uses the value (mutably) and evaluates to
127 /// a Result<Option<Item>, Error>. Once the state returns None, the
128 /// processing will advance to the next state.
129 generator: TokenStream,
130 },
131}
132
133/// Specify how the field is mapped to XML.
134enum FieldKind {
135 /// The field maps to an attribute.
136 Attribute {
137 /// The optional XML namespace of the attribute.
138 xml_namespace: Option<NamespaceRef>,
139
140 /// The XML name of the attribute.
141 xml_name: NameRef,
142
143 // Flag indicating whether the value should be defaulted if the
144 // attribute is absent.
145 default_: Flag,
146 },
147
148 /// The field maps to the character data of the element.
149 Text {
150 /// Optional codec to use
151 codec: Option<Type>,
152 },
153
154 /// The field maps to a child
155 Child {
156 // Flag indicating whether the value should be defaulted if the
157 // child is absent.
158 default_: Flag,
159 },
160}
161
162impl FieldKind {
163 /// Construct a new field implementation from the meta attributes.
164 ///
165 /// `field_ident` is, for some field types, used to infer an XML name if
166 /// it is not specified explicitly.
167 fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
168 match meta {
169 XmlFieldMeta::Attribute {
170 span,
171 namespace,
172 name,
173 default_,
174 } => {
175 let xml_name = match name {
176 Some(v) => v,
177 None => match field_ident {
178 None => return Err(Error::new(
179 span,
180 "attribute name must be explicitly specified using `#[xml(attribute = ..)] on unnamed fields",
181 )),
182 Some(field_ident) => match NcName::try_from(field_ident.to_string()) {
183 Ok(value) => NameRef::Literal {
184 span: field_ident.span(),
185 value,
186 },
187 Err(e) => {
188 return Err(Error::new(
189 field_ident.span(),
190 format!("invalid XML attribute name: {}", e),
191 ))
192 }
193 },
194 }
195 };
196
197 Ok(Self::Attribute {
198 xml_name,
199 xml_namespace: namespace,
200 default_,
201 })
202 }
203
204 XmlFieldMeta::Text { codec } => Ok(Self::Text { codec }),
205
206 XmlFieldMeta::Child { default_ } => Ok(Self::Child { default_ }),
207 }
208 }
209}
210
211/// Definition of a single field in a compound.
212///
213/// See [`Compound`][`crate::compound::Compound`] for more information on
214/// compounds in general.
215pub(crate) struct FieldDef {
216 /// The member identifying the field.
217 member: Member,
218
219 /// The type of the field.
220 ty: Type,
221
222 /// The way the field is mapped to XML.
223 kind: FieldKind,
224}
225
226impl FieldDef {
227 /// Create a new field definition from its declaration.
228 ///
229 /// The `index` must be the zero-based index of the field even for named
230 /// fields.
231 pub(crate) fn from_field(field: &syn::Field, index: u32) -> Result<Self> {
232 let field_span = field.span();
233 let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?;
234
235 let (member, ident) = match field.ident.as_ref() {
236 Some(v) => (Member::Named(v.clone()), Some(v)),
237 None => (
238 Member::Unnamed(Index {
239 index,
240 span: field_span,
241 }),
242 None,
243 ),
244 };
245
246 let ty = field.ty.clone();
247
248 Ok(Self {
249 member,
250 ty,
251 kind: FieldKind::from_meta(meta, ident)?,
252 })
253 }
254
255 /// Access the [`syn::Member`] identifying this field in the original
256 /// type.
257 pub(crate) fn member(&self) -> &Member {
258 &self.member
259 }
260
261 /// Access the field's type.
262 pub(crate) fn ty(&self) -> &Type {
263 &self.ty
264 }
265
266 /// Construct the builder pieces for this field.
267 ///
268 /// `container_name` must be a reference to the compound's type, so that
269 /// it can be used for error messages.
270 pub(crate) fn make_builder_part(
271 &self,
272 scope: &FromEventsScope,
273 container_name: &ParentRef,
274 ) -> Result<FieldBuilderPart> {
275 match self.kind {
276 FieldKind::Attribute {
277 ref xml_name,
278 ref xml_namespace,
279 ref default_,
280 } => {
281 let FromEventsScope { ref attrs, .. } = scope;
282 let ty = self.ty.clone();
283
284 let missing_msg = error_message::on_missing_attribute(container_name, &self.member);
285
286 let xml_namespace = match xml_namespace {
287 Some(v) => v.to_token_stream(),
288 None => quote! {
289 ::xso::exports::rxml::Namespace::none()
290 },
291 };
292
293 let from_xml_text = from_xml_text_fn(ty.clone());
294
295 let on_absent = match default_ {
296 Flag::Absent => quote! {
297 return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
298 },
299 Flag::Present(_) => {
300 let default_ = default_fn(ty.clone());
301 quote! {
302 #default_()
303 }
304 }
305 };
306
307 Ok(FieldBuilderPart::Init {
308 value: FieldTempInit {
309 init: quote! {
310 match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
311 ::core::option::Option::Some(v) => v,
312 ::core::option::Option::None => #on_absent,
313 }
314 },
315 ty: self.ty.clone(),
316 },
317 })
318 }
319
320 FieldKind::Text { ref codec } => {
321 let FromEventsScope { ref text, .. } = scope;
322 let field_access = scope.access_field(&self.member);
323 let finalize = match codec {
324 Some(codec_ty) => {
325 let decode = text_codec_decode_fn(codec_ty.clone(), self.ty.clone());
326 quote! {
327 #decode(#field_access)?
328 }
329 }
330 None => {
331 let from_xml_text = from_xml_text_fn(self.ty.clone());
332 quote! { #from_xml_text(#field_access)? }
333 }
334 };
335
336 Ok(FieldBuilderPart::Text {
337 value: FieldTempInit {
338 init: quote! { ::std::string::String::new() },
339 ty: string_ty(Span::call_site()),
340 },
341 collect: quote! {
342 #field_access.push_str(#text.as_str());
343 },
344 finalize,
345 })
346 }
347
348 FieldKind::Child { ref default_ } => {
349 let FromEventsScope {
350 ref substate_result,
351 ..
352 } = scope;
353 let field_access = scope.access_field(&self.member);
354
355 let missing_msg = error_message::on_missing_child(container_name, &self.member);
356
357 let from_events = from_events_fn(self.ty.clone());
358 let from_xml_builder = from_xml_builder_ty(self.ty.clone());
359
360 let on_absent = match default_ {
361 Flag::Absent => quote! {
362 return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
363 },
364 Flag::Present(_) => {
365 let default_ = default_fn(self.ty.clone());
366 quote! {
367 #default_()
368 }
369 }
370 };
371
372 Ok(FieldBuilderPart::Nested {
373 value: FieldTempInit {
374 init: quote! { ::std::option::Option::None },
375 ty: option_ty(self.ty.clone()),
376 },
377 matcher: quote! {
378 #from_events(name, attrs)
379 },
380 builder: from_xml_builder,
381 collect: quote! {
382 #field_access = ::std::option::Option::Some(#substate_result);
383 },
384 finalize: quote! {
385 match #field_access {
386 ::std::option::Option::Some(value) => value,
387 ::std::option::Option::None => #on_absent,
388 }
389 },
390 })
391 }
392 }
393 }
394
395 /// Construct the iterator pieces for this field.
396 ///
397 /// `bound_name` must be the name to which the field's value is bound in
398 /// the iterator code.
399 pub(crate) fn make_iterator_part(
400 &self,
401 scope: &AsItemsScope,
402 bound_name: &Ident,
403 ) -> Result<FieldIteratorPart> {
404 match self.kind {
405 FieldKind::Attribute {
406 ref xml_name,
407 ref xml_namespace,
408 ..
409 } => {
410 let xml_namespace = match xml_namespace {
411 Some(v) => quote! { ::xso::exports::rxml::Namespace::from(#v) },
412 None => quote! {
413 ::xso::exports::rxml::Namespace::NONE
414 },
415 };
416
417 let as_optional_xml_text = as_optional_xml_text_fn(self.ty.clone());
418
419 Ok(FieldIteratorPart::Header {
420 generator: quote! {
421 #as_optional_xml_text(#bound_name)?.map(|#bound_name| ::xso::Item::Attribute(
422 #xml_namespace,
423 ::std::borrow::Cow::Borrowed(#xml_name),
424 #bound_name,
425 ));
426 },
427 })
428 }
429
430 FieldKind::Text { ref codec } => {
431 let generator = match codec {
432 Some(codec_ty) => {
433 let encode = text_codec_encode_fn(codec_ty.clone(), self.ty.clone());
434 quote! { #encode(#bound_name)? }
435 }
436 None => {
437 let as_xml_text = as_xml_text_fn(self.ty.clone());
438 quote! { ::core::option::Option::Some(#as_xml_text(#bound_name)?) }
439 }
440 };
441
442 Ok(FieldIteratorPart::Text { generator })
443 }
444
445 FieldKind::Child { default_: _ } => {
446 let AsItemsScope { ref lifetime, .. } = scope;
447
448 let as_xml_iter = as_xml_iter_fn(self.ty.clone());
449 let item_iter = item_iter_ty(self.ty.clone(), lifetime.clone());
450
451 Ok(FieldIteratorPart::Content {
452 value: FieldTempInit {
453 init: quote! {
454 #as_xml_iter(#bound_name)?
455 },
456 ty: item_iter,
457 },
458 generator: quote! {
459 #bound_name.next().transpose()
460 },
461 })
462 }
463 }
464 }
465
466 /// Return true if this field's parsing consumes text data.
467 pub(crate) fn is_text_field(&self) -> bool {
468 matches!(self.kind, FieldKind::Text { .. })
469 }
470}