flag.rs

  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//! This module concerns the processing of flag-style children.
  8//!
  9//! In particular, it provides the `#[xml(flag)]` implementation.
 10
 11use proc_macro2::{Span, TokenStream};
 12use quote::quote;
 13use syn::*;
 14
 15use crate::error_message::{FieldName, ParentRef};
 16use crate::meta::{NameRef, NamespaceRef};
 17use crate::scope::{AsItemsScope, FromEventsScope};
 18use crate::types::{bool_ty, empty_builder_ty, u8_ty};
 19
 20use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher};
 21
 22/// The field maps to a child element, the presence of which is represented as boolean.
 23pub(super) struct FlagField {
 24    /// The XML namespace of the child element.
 25    pub(super) xml_namespace: NamespaceRef,
 26
 27    /// The XML name of the child element.
 28    pub(super) xml_name: NameRef,
 29}
 30
 31impl Field for FlagField {
 32    fn make_builder_part(
 33        &self,
 34        scope: &FromEventsScope,
 35        container_name: &ParentRef,
 36        member: &Member,
 37        _ty: &Type,
 38    ) -> Result<FieldBuilderPart> {
 39        let field_access = scope.access_field(member);
 40
 41        let unknown_attr_err = format!(
 42            "Unknown attribute in flag child {} in {}.",
 43            FieldName(member),
 44            container_name
 45        );
 46        let unknown_child_err = format!(
 47            "Unknown child in flag child {} in {}.",
 48            FieldName(member),
 49            container_name
 50        );
 51        let unknown_text_err = format!(
 52            "Unexpected text in flag child {} in {}.",
 53            FieldName(member),
 54            container_name
 55        );
 56
 57        let xml_namespace = &self.xml_namespace;
 58        let xml_name = &self.xml_name;
 59
 60        Ok(FieldBuilderPart::Nested {
 61            extra_defs: TokenStream::new(),
 62            value: FieldTempInit {
 63                ty: bool_ty(Span::call_site()),
 64                init: quote! { false },
 65            },
 66            matcher: NestedMatcher::Selective(quote! {
 67                if name.0 == #xml_namespace && name.1 == #xml_name {
 68                    ::xso::fromxml::Empty {
 69                        attributeerr: #unknown_attr_err,
 70                        childerr: #unknown_child_err,
 71                        texterr: #unknown_text_err,
 72                    }.start(attrs).map_err(
 73                        ::xso::error::FromEventsError::Invalid
 74                    )
 75                } else {
 76                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
 77                        name,
 78                        attrs,
 79                    })
 80                }
 81            }),
 82            builder: empty_builder_ty(Span::call_site()),
 83            collect: quote! {
 84                #field_access = true;
 85            },
 86            finalize: quote! {
 87                #field_access
 88            },
 89        })
 90    }
 91
 92    fn make_iterator_part(
 93        &self,
 94        _scope: &AsItemsScope,
 95        _container_name: &ParentRef,
 96        bound_name: &Ident,
 97        _member: &Member,
 98        _ty: &Type,
 99    ) -> Result<FieldIteratorPart> {
100        let xml_namespace = &self.xml_namespace;
101        let xml_name = &self.xml_name;
102
103        Ok(FieldIteratorPart::Content {
104            extra_defs: TokenStream::new(),
105            value: FieldTempInit {
106                init: quote! {
107                    if *#bound_name {
108                        3
109                    } else {
110                        1
111                    }
112                },
113                ty: u8_ty(Span::call_site()),
114            },
115            generator: quote! {
116                {
117                    // using wrapping_sub will make the match below crash
118                    // with unreachable!() in case we messed up somewhere.
119                    #bound_name = #bound_name.wrapping_sub(1);
120                    match #bound_name {
121                        0 => ::core::result::Result::<_, ::xso::error::Error>::Ok(::core::option::Option::None),
122                        1 => ::core::result::Result::Ok(::core::option::Option::Some(
123                            ::xso::Item::ElementFoot
124                        )),
125                        2 => ::core::result::Result::Ok(::core::option::Option::Some(
126                            ::xso::Item::ElementHeadStart(
127                                ::xso::exports::rxml::Namespace::from(#xml_namespace),
128                                ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
129                            )
130                        )),
131                        _ => unreachable!(),
132                    }
133                }
134            },
135        })
136    }
137}