xso_proc-0.2.0/.cargo_vcs_info.json0000644000000001460000000000100126700ustar { "git": { "sha1": "5ab4f395e071b3fa2d6c1524ae16bd5dc2ccf0f1" }, "path_in_vcs": "xso-proc" }xso_proc-0.2.0/Cargo.lock0000644000000054250000000000100106500ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "castaway" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "compact_str" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" dependencies = [ "castaway", "cfg-if", "itoa", "ryu", "static_assertions", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rxml_validation" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" dependencies = [ "compact_str", ] [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "xso_proc" version = "0.2.0" dependencies = [ "proc-macro2", "quote", "rxml_validation", "syn", ] xso_proc-0.2.0/Cargo.toml0000644000000024620000000000100106710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.83" name = "xso_proc" version = "0.2.0" authors = ["Jonas Schäfer "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Macro implementation of #[derive(FromXml, AsXml)]" homepage = "https://xmpp.rs" readme = "README.md" keywords = [ "xso", "derive", "serialization", ] license = "MPL-2.0" repository = "https://gitlab.com/xmpp-rs/xmpp-rs" [features] minidom = [] panicking-into-impl = ["minidom"] [lib] name = "xso_proc" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.rxml_validation] version = "0.11" features = ["std"] default-features = false [dependencies.syn] version = "2" features = [ "full", "extra-traits", ] xso_proc-0.2.0/Cargo.toml.orig000064400000000000000000000012511046102023000143450ustar 00000000000000[package] name = "xso_proc" version = "0.2.0" authors = [ "Jonas Schäfer ", ] description = "Macro implementation of #[derive(FromXml, AsXml)]" homepage = "https://xmpp.rs" repository = "https://gitlab.com/xmpp-rs/xmpp-rs" keywords = ["xso", "derive", "serialization"] license = "MPL-2.0" edition = "2021" # we need const_refs_to_static, stabilized with 1.83 rust-version = "1.83" [lib] proc-macro = true [dependencies] quote = "1" syn = { version = "2", features = ["full", "extra-traits"] } proc-macro2 = "1" rxml_validation = { version = "0.11", default-features = false, features = ["std"] } [features] panicking-into-impl = ["minidom"] minidom = [] xso_proc-0.2.0/ChangeLog000064400000000000000000000004621046102023000132330ustar 00000000000000Version NEXT: 0000-00-00 Jonas Schäfer * Please see the `xso` crate for the changelog of `xso-proc`. For discoverability, the changes to the derive macros are listed there. Version 0.1.0: 2024-07-25 Jonas Schäfer * Initial release of this crate xso_proc-0.2.0/README.md000064400000000000000000000005721046102023000127420ustar 00000000000000xso_proc -- derive macros for XML parsing ========================================= What’s this? ------------ This crate provides the derive macros exported by the [xso](https://crates.io/crates/xso) crate. Please see that crate's README and documentation for more information. What license is it under? ------------------------- MPL-2.0 or later, see the `LICENSE` file. xso_proc-0.2.0/src/common.rs000064400000000000000000000065111046102023000141070ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Definitions common to both enums and structs use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::*; /// Template which renders to a `xso::fromxml::XmlNameMatcher` value. pub(crate) enum XmlNameMatcher { /// Renders as `xso::fromxml::XmlNameMatcher::Any`. #[allow(dead_code)] // We keep it for completeness. Any, /// Renders as `xso::fromxml::XmlNameMatcher::InNamespace(#0)`. InNamespace(TokenStream), /// Renders as `xso::fromxml::XmlNameMatcher::Specific(#0, #1)`. Specific(TokenStream, TokenStream), /// Renders as `#0`. /// /// This is an escape hatch for more complicated constructs, e.g. when /// a superset of multiple matchers is required. Custom(TokenStream), } impl ToTokens for XmlNameMatcher { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Any => tokens.extend(quote! { ::xso::fromxml::XmlNameMatcher::<'static>::Any }), Self::InNamespace(ref namespace) => tokens.extend(quote! { ::xso::fromxml::XmlNameMatcher::<'static>::InNamespace(#namespace) }), Self::Specific(ref namespace, ref name) => tokens.extend(quote! { ::xso::fromxml::XmlNameMatcher::<'static>::Specific(#namespace, #name) }), Self::Custom(ref stream) => tokens.extend(stream.clone()), } } } /// Parts necessary to construct a `::xso::FromXml` implementation. pub(crate) struct FromXmlParts { /// Additional items necessary for the implementation. pub(crate) defs: TokenStream, /// The body of the `::xso::FromXml::from_xml` function. pub(crate) from_events_body: TokenStream, /// The name of the type which is the `::xso::FromXml::Builder`. pub(crate) builder_ty_ident: Ident, /// The `XmlNameMatcher` to pre-select elements for this implementation. pub(crate) name_matcher: XmlNameMatcher, } /// Parts necessary to construct a `::xso::AsXml` implementation. pub(crate) struct AsXmlParts { /// Additional items necessary for the implementation. pub(crate) defs: TokenStream, /// The body of the `::xso::AsXml::as_xml_iter` function. pub(crate) as_xml_iter_body: TokenStream, /// The type which is the `::xso::AsXml::ItemIter`. pub(crate) item_iter_ty: Type, /// The lifetime name used in `item_iter_ty`. pub(crate) item_iter_ty_lifetime: Lifetime, } /// Trait describing the definition of the XML (de-)serialisation for an item /// (enum or struct). pub(crate) trait ItemDef { /// Construct the parts necessary for the caller to build an /// `xso::FromXml` implementation for the item. fn make_from_events_builder( &self, vis: &Visibility, name_ident: &Ident, attrs_ident: &Ident, ) -> Result; /// Construct the parts necessary for the caller to build an `xso::AsXml` /// implementation for the item. fn make_as_xml_iter(&self, vis: &Visibility) -> Result; /// Return true iff the user requested debug output. fn debug(&self) -> bool; } xso_proc-0.2.0/src/compound.rs000064400000000000000000001104751046102023000144500ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Handling of the insides of compound structures (structs and enum variants) use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, *}; use std::collections::{hash_map::Entry, HashMap}; use crate::error_message::{FieldName, ParentRef}; use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit, NestedMatcher}; use crate::meta::{DiscardSpec, Flag, NameRef, NamespaceRef, QNameRef}; use crate::scope::{mangle_member, AsItemsScope, FromEventsScope}; use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State}; use crate::types::{ default_fn, discard_builder_ty, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty, ref_ty, unknown_attribute_policy_path, unknown_child_policy_path, }; fn resolve_policy(policy: Option, mut enum_ref: Path) -> Expr { match policy { Some(ident) => { enum_ref.segments.push(ident.into()); Expr::Path(ExprPath { attrs: Vec::new(), qself: None, path: enum_ref, }) } None => { let default_fn = default_fn(Type::Path(TypePath { qself: None, path: enum_ref, })); Expr::Call(ExprCall { attrs: Vec::new(), func: Box::new(default_fn), paren_token: token::Paren::default(), args: punctuated::Punctuated::new(), }) } } } /// A struct or enum variant's contents. pub(crate) struct Compound { /// The fields of this compound. fields: Vec, /// Policy defining how to handle unknown attributes. unknown_attribute_policy: Expr, /// Policy defining how to handle unknown children. unknown_child_policy: Expr, /// Attributes to discard. discard_attr: Vec<(Option, NameRef)>, /// Text to discard. discard_text: Flag, /// Attribute qualified names which are selected by fields. /// /// This is used to generate code which asserts, at compile time, that no /// two fields select the same XML attribute. selected_attributes: Vec<(QNameRef, Member)>, } impl Compound { /// Construct a compound from processed field definitions. pub(crate) fn from_field_defs>>( compound_fields: I, unknown_attribute_policy: Option, unknown_child_policy: Option, discard: Vec, ) -> Result { let unknown_attribute_policy = resolve_policy( unknown_attribute_policy, unknown_attribute_policy_path(Span::call_site()), ); let unknown_child_policy = resolve_policy( unknown_child_policy, unknown_child_policy_path(Span::call_site()), ); let compound_fields = compound_fields.into_iter(); let size_hint = compound_fields.size_hint(); let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0)); let mut text_field = None; let mut selected_attributes: HashMap = HashMap::new(); for field in compound_fields { let field = field?; if field.is_text_field() { if let Some(other_field) = text_field.as_ref() { let mut err = Error::new_spanned( field.member(), "only one `#[xml(text)]` field allowed per compound", ); err.combine(Error::new( *other_field, "the other `#[xml(text)]` field is here", )); return Err(err); } text_field = Some(field.member().span()) } if let Some(qname) = field.captures_attribute() { let span = field.span(); match selected_attributes.entry(qname) { Entry::Occupied(o) => { let mut err = Error::new( span, "this field XML field matches the same attribute as another field", ); err.combine(Error::new( o.get().span(), "the other field matching the same attribute is here", )); return Err(err); } Entry::Vacant(v) => { v.insert(field.member().clone()); } } } fields.push(field); } let mut discard_text = Flag::Absent; let mut discard_attr = Vec::new(); for spec in discard { match spec { DiscardSpec::Text { span } => { if let Some(field) = text_field.as_ref() { let mut err = Error::new( *field, "cannot combine `#[xml(text)]` field with `discard(text)`", ); err.combine(Error::new( spec.span(), "the discard(text) attribute is here", )); return Err(err); } if let Flag::Present(other) = discard_text { let mut err = Error::new( span, "only one `discard(text)` meta is allowed per compound", ); err.combine(Error::new(other, "the discard(text) meta is here")); return Err(err); } discard_text = Flag::Present(span); } DiscardSpec::Attribute { qname: QNameRef { namespace, name }, span, } => { let xml_namespace = namespace; let xml_name = match name { Some(v) => v, None => { return Err(Error::new( span, "discard(attribute) must specify a name, e.g. via discard(attribute = \"some-name\")", )); } }; discard_attr.push((xml_namespace, xml_name)); } } } Ok(Self { fields, unknown_attribute_policy, unknown_child_policy, discard_attr, discard_text, selected_attributes: selected_attributes.into_iter().collect(), }) } /// Construct a compound from fields. pub(crate) fn from_fields( compound_fields: &Fields, container_namespace: &NamespaceRef, unknown_attribute_policy: Option, unknown_child_policy: Option, discard: Vec, ) -> Result { Self::from_field_defs( compound_fields.iter().enumerate().map(|(i, field)| { let index = match i.try_into() { Ok(v) => v, // we are converting to u32, are you crazy?! // (u32, because syn::Member::Index needs that.) Err(_) => { return Err(Error::new_spanned( field, "okay, mate, that are way too many fields. get your life together.", )) } }; FieldDef::from_field(field, index, container_namespace) }), unknown_attribute_policy, unknown_child_policy, discard, ) } /// Generate code which, at compile time, asserts that all attributes /// which are selected by this compound are disjunct. /// /// NOTE: this needs rustc 1.83 or newer for `const_refs_to_static`. fn assert_disjunct_attributes(&self) -> TokenStream { let mut checks = TokenStream::default(); // Comparison is commutative, so we *could* reduce this to n^2/2 // comparisons instead of n*(n-1). However, by comparing every field // with every other field and emitting check code for that, we can // point at both fields in the error messages. for (i, (qname_a, member_a)) in self.selected_attributes.iter().enumerate() { for (j, (qname_b, member_b)) in self.selected_attributes.iter().enumerate() { if i == j { continue; } // Flip a and b around if a is later than b. // This way, the error message is the same for both // conflicting fields. Note that we always take the span of // `a` though, so that the two errors point at different // fields. let span = member_a.span(); let (member_a, member_b) = if i > j { (member_b, member_a) } else { (member_a, member_b) }; if qname_a.namespace.is_some() != qname_b.namespace.is_some() { // cannot ever match. continue; } let Some((name_a, name_b)) = qname_a.name.as_ref().zip(qname_b.name.as_ref()) else { panic!("selected attribute has no XML local name"); }; let mut check = quote! { ::xso::exports::const_str_eq(#name_a.as_str(), #name_b.as_str()) }; let namespaces = qname_a.namespace.as_ref().zip(qname_b.namespace.as_ref()); if let Some((ns_a, ns_b)) = namespaces { check.extend(quote! { && ::xso::exports::const_str_eq(#ns_a, #ns_b) }); }; let attr_a = if let Some(namespace_a) = qname_a.namespace.as_ref() { format!("{{{}}}{}", namespace_a, name_a) } else { format!("{}", name_a) }; let attr_b = if let Some(namespace_b) = qname_b.namespace.as_ref() { format!("{{{}}}{}", namespace_b, name_b) } else { format!("{}", name_b) }; // We need to escape `{` and `}` because we feed it into // a generated `panic!()` as message template below. We cannot // do something like `panic!("member {} […]", #field_a, […])`, // because rustc does not allow that in a const context. // (It *did* briefly (and unintentionally, see // https://github.com/rust-lang/rust/issues/140585 ) allow // that in version 1.86, but that was rolled back. let attr_a = attr_a.replace('{', "{{").replace('}', "}}"); let attr_b = attr_b.replace('{', "{{").replace('}', "}}"); let field_a = FieldName(&member_a) .to_string() .replace('{', "{{") .replace('}', "}}"); let field_b = FieldName(&member_b) .to_string() .replace('{', "{{") .replace('}', "}}"); // By assigning the checks to a `const`, we ensure that they // are in fact evaluated at compile time, even if that constant // is never used. checks.extend(quote_spanned! {span=> const _: () = { if #check { panic!(concat!("member ", #field_a, " and member ", #field_b, " match the same XML attribute: ", #attr_a, " == ", #attr_b)); } }; }) } } checks } /// Make and return a set of states which is used to construct the target /// type from XML events. /// /// The states are returned as partial state machine. See the return /// type's documentation for details. pub(crate) fn make_from_events_statemachine( &self, state_ty_ident: &Ident, output_name: &ParentRef, state_prefix: &str, ) -> Result { let scope = FromEventsScope::new(state_ty_ident.clone()); let FromEventsScope { ref attrs, ref builder_data_ident, ref text, ref substate_data, ref substate_result, .. } = scope; let default_state_ident = quote::format_ident!("{}Default", state_prefix); let discard_state_ident = quote::format_ident!("{}Discard", state_prefix); let builder_data_ty: Type = TypePath { qself: None, path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(), } .into(); let mut states = Vec::new(); let mut builder_data_def = TokenStream::default(); let mut builder_data_init = TokenStream::default(); let mut output_cons = TokenStream::default(); let mut child_matchers = TokenStream::default(); let mut fallback_child_matcher = None; let mut text_handler = if self.discard_text.is_set() { Some(quote! { ::core::result::Result::Ok(::core::ops::ControlFlow::Break( Self::#default_state_ident { #builder_data_ident } )) }) } else { None }; let mut extra_defs = TokenStream::default(); let is_tuple = !output_name.is_path(); for (i, field) in self.fields.iter().enumerate() { let member = field.member(); let builder_field_name = mangle_member(member); let part = field.make_builder_part(&scope, output_name)?; let state_name = quote::format_ident!("{}Field{}", state_prefix, i); match part { FieldBuilderPart::Init { value: FieldTempInit { ty, init }, } => { builder_data_def.extend(quote! { #builder_field_name: #ty, }); builder_data_init.extend(quote! { #builder_field_name: #init, }); if is_tuple { output_cons.extend(quote! { #builder_data_ident.#builder_field_name, }); } else { output_cons.extend(quote! { #member: #builder_data_ident.#builder_field_name, }); } } FieldBuilderPart::Text { value: FieldTempInit { ty, init }, collect, finalize, } => { if text_handler.is_some() { // the existence of only one text handler is enforced // by Compound's constructor(s). panic!("more than one field attempts to collect text data"); } builder_data_def.extend(quote! { #builder_field_name: #ty, }); builder_data_init.extend(quote! { #builder_field_name: #init, }); text_handler = Some(quote! { #collect ::core::result::Result::Ok(::core::ops::ControlFlow::Break( Self::#default_state_ident { #builder_data_ident } )) }); if is_tuple { output_cons.extend(quote! { #finalize, }); } else { output_cons.extend(quote! { #member: #finalize, }); } } FieldBuilderPart::Nested { extra_defs: field_extra_defs, value: FieldTempInit { ty, init }, matcher, builder, collect, finalize, } => { let feed = feed_fn(builder.clone()); let mut substate_data_ident = substate_data.clone(); substate_data_ident.set_span(ty.span()); states.push(State::new_with_builder( state_name.clone(), builder_data_ident, &builder_data_ty, ).with_field( &substate_data_ident, &builder, ).with_mut(substate_data).with_impl(quote! { match #feed(&mut #substate_data, ev, ctx)? { ::core::option::Option::Some(#substate_result) => { #collect ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident { #builder_data_ident, })) } ::core::option::Option::None => { ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name { #builder_data_ident, #substate_data, })) } } })); builder_data_def.extend(quote! { #builder_field_name: #ty, }); builder_data_init.extend(quote! { #builder_field_name: #init, }); match matcher { NestedMatcher::Selective(matcher) => { child_matchers.extend(quote! { let (name, attrs) = match #matcher { ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs), ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(e), ::core::result::Result::Ok(#substate_data) => { return ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name { #builder_data_ident, #substate_data, })) } }; }); } NestedMatcher::Fallback(matcher) => { if let Some((span, _)) = fallback_child_matcher.as_ref() { let mut err = Error::new( field.span(), "more than one field is attempting to consume all unmatched child elements" ); err.combine(Error::new( *span, "the previous field collecting all unmatched child elements is here" )); return Err(err); } let matcher = quote! { ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name { #builder_data_ident, #substate_data: { #matcher }, })) }; fallback_child_matcher = Some((field.span(), matcher)); } } if is_tuple { output_cons.extend(quote! { #finalize, }); } else { output_cons.extend(quote! { #member: #finalize, }); } extra_defs.extend(field_extra_defs); } } } // We always implicitly discard the `xml:lang` attribute. Its // processing is handled using the `#[xml(language)]` meta. let mut discard_attr = quote! { let _ = #attrs.remove(::xso::exports::rxml::Namespace::xml(), "lang"); }; for (xml_namespace, xml_name) in self.discard_attr.iter() { let xml_namespace = match xml_namespace { Some(v) => v.to_token_stream(), None => quote! { ::xso::exports::rxml::Namespace::none() }, }; discard_attr.extend(quote! { let _ = #attrs.remove(#xml_namespace, #xml_name); }); } let text_handler = match text_handler { Some(v) => v, None => quote! { // note: u8::is_ascii_whitespace includes U+000C, which is not // part of XML's white space definition.' if !::xso::is_xml_whitespace(#text.as_bytes()) { ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into())) } else { ::core::result::Result::Ok(::core::ops::ControlFlow::Break( Self::#default_state_ident { #builder_data_ident } )) } }, }; let unknown_attr_err = format!("Unknown attribute in {}.", output_name); let unknown_child_err = format!("Unknown child in {}.", output_name); let unknown_child_policy = &self.unknown_child_policy; let output_cons = match output_name { ParentRef::Named(ref path) => { quote! { #path { #output_cons } } } ParentRef::Unnamed { .. } => { quote! { ( #output_cons ) } } }; let discard_builder_ty = discard_builder_ty(Span::call_site()); let discard_feed = feed_fn(discard_builder_ty.clone()); let child_fallback = match fallback_child_matcher { Some((_, matcher)) => matcher, None => quote! { let _ = (name, attrs); let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?; ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident { #builder_data_ident, #substate_data: #discard_builder_ty::new(), })) }, }; states.push(State::new_with_builder( discard_state_ident.clone(), builder_data_ident, &builder_data_ty, ).with_field( substate_data, &discard_builder_ty, ).with_mut(substate_data).with_impl(quote! { match #discard_feed(&mut #substate_data, ev, ctx)? { ::core::option::Option::Some(#substate_result) => { ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident { #builder_data_ident, })) } ::core::option::Option::None => { ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident { #builder_data_ident, #substate_data, })) } } })); states.push(State::new_with_builder( default_state_ident.clone(), builder_data_ident, &builder_data_ty, ).with_impl(quote! { match ev { // EndElement in Default state -> done parsing. ::xso::exports::rxml::Event::EndElement(_) => { ::core::result::Result::Ok(::core::ops::ControlFlow::Continue( #output_cons )) } ::xso::exports::rxml::Event::StartElement(_, name, attrs) => { #child_matchers #child_fallback } ::xso::exports::rxml::Event::Text(_, #text) => { #text_handler } // we ignore these: a correct parser only generates // them at document start, and there we want to indeed // not worry about them being in front of the first // element. ::xso::exports::rxml::Event::XmlDeclaration(_, ::xso::exports::rxml::XmlVersion::V1_0) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break( Self::#default_state_ident { #builder_data_ident } )) } })); let unknown_attribute_policy = &self.unknown_attribute_policy; let checks = self.assert_disjunct_attributes(); Ok(FromEventsSubmachine { defs: quote! { #extra_defs #checks struct #builder_data_ty { #builder_data_def } }, states, init: quote! { let #builder_data_ident = #builder_data_ty { #builder_data_init }; #discard_attr if #attrs.len() > 0 { let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?; } ::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident }) }, }) } /// Make and return a set of states which is used to destructure the /// target type into XML events. /// /// The states are returned as partial state machine. See the return /// type's documentation for details. /// /// **Important:** The returned submachine is not in functional state! /// It's `init` must be modified so that a variable called `name` of type /// `rxml::QName` is in scope. pub(crate) fn make_as_item_iter_statemachine( &self, input_name: &ParentRef, state_ty_ident: &Ident, state_prefix: &str, lifetime: &Lifetime, ) -> Result { let scope = AsItemsScope::new(lifetime, state_ty_ident.clone()); let element_head_start_state_ident = quote::format_ident!("{}ElementHeadStart", state_prefix); let element_head_end_state_ident = quote::format_ident!("{}ElementHeadEnd", state_prefix); let element_foot_state_ident = quote::format_ident!("{}ElementFoot", state_prefix); let name_ident = quote::format_ident!("name"); let ns_ident = quote::format_ident!("ns"); let dummy_ident = quote::format_ident!("dummy"); let mut header_states = Vec::new(); let mut body_states = Vec::new(); let is_tuple = !input_name.is_path(); let mut destructure = TokenStream::default(); let mut start_init = TokenStream::default(); let mut extra_defs = TokenStream::default(); header_states.push( State::new(element_head_start_state_ident.clone()) .with_field(&dummy_ident, &phantom_lifetime_ty(lifetime.clone())) .with_field(&ns_ident, &namespace_ty(Span::call_site())) .with_field( &name_ident, &ncnamestr_cow_ty(Span::call_site(), lifetime.clone()), ), ); body_states.push(( None, State::new(element_head_end_state_ident.clone()).with_impl(quote! { ::core::option::Option::Some(::xso::Item::ElementHeadEnd) }), )); for (i, field) in self.fields.iter().enumerate() { let member = field.member(); let bound_name = mangle_member(member); let part = field.make_iterator_part(&scope, input_name, &bound_name)?; let state_name = quote::format_ident!("{}Field{}", state_prefix, i); let ty = scope.borrow(field.ty().clone()); match part { FieldIteratorPart::Header { generator } => { // We have to make sure that we carry our data around in // all the previous states. // For header states, it is sufficient to do it here. // For body states, we have to do it in a separate loop // below to correctly handle the case when a field with a // body state is placed before a field with a header // state. for state in header_states.iter_mut() { state.add_field(&bound_name, &ty); } header_states.push( State::new(state_name) .with_field(&bound_name, &ty) .with_impl(quote! { #generator }), ); if is_tuple { destructure.extend(quote! { ref #bound_name, }); } else { destructure.extend(quote! { #member: ref #bound_name, }); } start_init.extend(quote! { #bound_name, }); } FieldIteratorPart::Text { generator } => { // We have to make sure that we carry our data around in // all the previous body states. // We also have to make sure that our data is carried // by all *header* states, but we can only do that once // we have visited them all, so that happens at the bottom // of the loop. for (_, state) in body_states.iter_mut() { state.add_field(&bound_name, &ty); } let state = State::new(state_name) .with_field(&bound_name, &ty) .with_impl(quote! { #generator.map(|value| ::xso::Item::Text( value, )) }); if is_tuple { destructure.extend(quote! { #bound_name, }); } else { destructure.extend(quote! { #member: #bound_name, }); } start_init.extend(quote! { #bound_name, }); body_states.push((Some((bound_name, ty)), state)); } FieldIteratorPart::Content { extra_defs: field_extra_defs, value: FieldTempInit { ty, init }, generator, } => { // We have to make sure that we carry our data around in // all the previous body states. // We also have to make sure that our data is carried // by all *header* states, but we can only do that once // we have visited them all, so that happens at the bottom // of the loop. for (_, state) in body_states.iter_mut() { state.add_field(&bound_name, &ty); } let state = State::new(state_name.clone()) .with_field(&bound_name, &ty) .with_mut(&bound_name) .with_impl(quote! { #generator? }); if is_tuple { destructure.extend(quote! { #bound_name, }); } else { destructure.extend(quote! { #member: #bound_name, }); } start_init.extend(quote! { #bound_name: #init, }); extra_defs.extend(field_extra_defs); body_states.push((Some((bound_name, ty)), state)); } } } header_states[0].set_impl(quote! { { ::core::option::Option::Some(::xso::Item::ElementHeadStart( #ns_ident, #name_ident, )) } }); for (data, _) in body_states.iter() { if let Some((bound_name, ty)) = data.as_ref() { for state in header_states.iter_mut() { state.add_field(bound_name, ty); } } } header_states.extend(body_states.into_iter().map(|(_, state)| state)); let mut states = header_states; states.push( State::new(element_foot_state_ident.clone()).with_impl(quote! { ::core::option::Option::Some(::xso::Item::ElementFoot) }), ); let destructure = match input_name { ParentRef::Named(ref input_path) => quote! { #input_path { #destructure } }, ParentRef::Unnamed { .. } => quote! { ( #destructure ) }, }; let checks = self.assert_disjunct_attributes(); extra_defs.extend(checks); Ok(AsItemsSubmachine { defs: extra_defs, states, destructure, init: quote! { Self::#element_head_start_state_ident { #dummy_ident: ::core::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init } }, }) } /// Return a reference to this compound's only field's type. /// /// If the compound does not have exactly one field, this function returns /// None. pub(crate) fn single_ty(&self) -> Option<&Type> { if self.fields.len() > 1 { return None; } self.fields.first().map(|x| x.ty()) } /// Construct a tuple type with this compound's field's types in the same /// order as they appear in the compound. pub(crate) fn to_tuple_ty(&self) -> TypeTuple { TypeTuple { paren_token: token::Paren::default(), elems: self.fields.iter().map(|x| x.ty().clone()).collect(), } } /// Construct a tuple type with this compound's field's types in the same /// order as they appear in the compound. pub(crate) fn to_single_or_tuple_ty(&self) -> Type { match self.single_ty() { None => self.to_tuple_ty().into(), Some(v) => v.clone(), } } /// Construct a tuple type with references to this compound's field's /// types in the same order as they appear in the compound, with the given /// lifetime. pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple { TypeTuple { paren_token: token::Paren::default(), elems: self .fields .iter() .map(|x| ref_ty(x.ty().clone(), lifetime.clone())) .collect(), } } /// Return the number of fields in this compound. pub(crate) fn field_count(&self) -> usize { self.fields.len() } } xso_proc-0.2.0/src/enums.rs000064400000000000000000001056231046102023000137520ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Handling of enums use std::collections::HashMap; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::*; use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher}; use crate::compound::Compound; use crate::error_message::ParentRef; use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta}; use crate::state::{AsItemsStateMachine, FromEventsMatchMode, FromEventsStateMachine}; use crate::structs::StructInner; use crate::types::{ref_ty, ty_from_ident}; /// The definition of an enum variant, switched on the XML element's name, /// inside a [`NameSwitchedEnum`]. struct NameVariant { /// The XML name of the element to map the enum variant to. name: NameRef, /// The name of the variant ident: Ident, /// The field(s) of this struct. inner: Compound, } impl NameVariant { /// Construct a new name-selected variant from its declaration. fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result { // We destructure here so that we get informed when new fields are // added and can handle them, either by processing them or raising // an error if they are present. let XmlCompoundMeta { span: meta_span, qname: QNameRef { namespace, name }, exhaustive, debug, builder, iterator, on_unknown_attribute, on_unknown_child, transparent, discard, deserialize_callback, attribute, value, } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?; reject_key!(debug flag not on "enum variants" only on "enums and structs"); reject_key!(exhaustive flag not on "enum variants" only on "enums"); reject_key!(namespace not on "enum variants" only on "enums and structs"); reject_key!(builder not on "enum variants" only on "enums and structs"); reject_key!(iterator not on "enum variants" only on "enums and structs"); reject_key!(transparent flag not on "named enum variants" only on "structs"); reject_key!(deserialize_callback not on "enum variants" only on "enums and structs"); reject_key!(attribute not on "enum variants" only on "attribute-switched enums"); reject_key!(value not on "name-switched enum variants" only on "attribute-switched enum variants"); let Some(name) = name else { return Err(Error::new( meta_span, "`name` is required on name-switched enum variants", )); }; Ok(Self { name, ident: decl.ident.clone(), inner: Compound::from_fields( &decl.fields, enum_namespace, on_unknown_attribute, on_unknown_child, discard, )?, }) } fn make_from_events_statemachine( &self, enum_ident: &Ident, state_ty_ident: &Ident, ) -> Result { let xml_name = &self.name; Ok(self .inner .make_from_events_statemachine( state_ty_ident, &ParentRef::Named(Path { leading_colon: None, segments: [ PathSegment::from(enum_ident.clone()), self.ident.clone().into(), ] .into_iter() .collect(), }), &self.ident.to_string(), )? .with_augmented_init(|init| { quote! { if name.1 != #xml_name { ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs, }) } else { #init } } }) .compile()) } fn make_as_item_iter_statemachine( &self, xml_namespace: &NamespaceRef, enum_ident: &Ident, state_ty_ident: &Ident, item_iter_ty_lifetime: &Lifetime, ) -> Result { let xml_name = &self.name; Ok(self .inner .make_as_item_iter_statemachine( &ParentRef::Named(Path { leading_colon: None, segments: [ PathSegment::from(enum_ident.clone()), self.ident.clone().into(), ] .into_iter() .collect(), }), state_ty_ident, &self.ident.to_string(), item_iter_ty_lifetime, )? .with_augmented_init(|init| { quote! { let name = ( ::xso::exports::rxml::Namespace::from(#xml_namespace), ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name), ); #init } }) .compile()) } } /// The definition of a enum which switches based on the XML element name, /// with the XML namespace fixed. struct NameSwitchedEnum { /// The XML namespace of the element to map the enum to. namespace: NamespaceRef, /// The variants of the enum. variants: Vec, /// Flag indicating whether the enum is exhaustive. exhaustive: bool, } impl NameSwitchedEnum { fn new<'x, I: IntoIterator>( namespace: NamespaceRef, exhaustive: Flag, variant_iter: I, ) -> Result { let mut variants = Vec::new(); let mut seen_names = HashMap::new(); for variant in variant_iter { let variant = NameVariant::new(variant, &namespace)?; if let Some(other) = seen_names.get(&variant.name) { return Err(Error::new_spanned( variant.name, format!( "duplicate `name` in enum: variants {} and {} have the same XML name", other, variant.ident ), )); } seen_names.insert(variant.name.clone(), variant.ident.clone()); variants.push(variant); } Ok(Self { namespace, variants, exhaustive: exhaustive.is_set(), }) } /// Provide the `XmlNameMatcher` template for this enum. /// /// Name-switched enums always return a matcher in the namespace of the /// elements they match against. fn xml_name_matcher(&self) -> Result { Ok(XmlNameMatcher::InNamespace( self.namespace.to_token_stream(), )) } /// Build the deserialisation statemachine for the name-switched enum. fn make_from_events_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, ) -> Result { let xml_namespace = &self.namespace; let mut statemachine = FromEventsStateMachine::new(); for variant in self.variants.iter() { statemachine .merge(variant.make_from_events_statemachine(target_ty_ident, state_ty_ident)?); } statemachine.set_pre_init(quote! { if name.0 != #xml_namespace { return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs, }) } }); if self.exhaustive { let mismatch_err = format!("This is not a {} element.", target_ty_ident); statemachine.set_fallback(quote! { ::core::result::Result::Err(::xso::error::FromEventsError::Invalid( ::xso::error::Error::Other(#mismatch_err), )) }) } Ok(statemachine) } /// Build the serialisation statemachine for the name-switched enum. fn make_as_item_iter_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, item_iter_ty_lifetime: &Lifetime, ) -> Result { let mut statemachine = AsItemsStateMachine::new(); for variant in self.variants.iter() { statemachine.merge(variant.make_as_item_iter_statemachine( &self.namespace, target_ty_ident, state_ty_ident, item_iter_ty_lifetime, )?); } Ok(statemachine) } } /// The definition of an enum variant, switched on the XML element's /// attribute's value, inside a [`AttributeSwitchedEnum`]. struct ValueVariant { /// The verbatim value to match. value: String, /// The identifier of the variant ident: Ident, /// The field(s) of the variant. inner: Compound, } impl ValueVariant { /// Construct a new value-selected variant from its declaration. fn new(decl: &Variant, enum_namespace: &NamespaceRef) -> Result { // We destructure here so that we get informed when new fields are // added and can handle them, either by processing them or raising // an error if they are present. let XmlCompoundMeta { span: meta_span, qname: QNameRef { namespace, name }, exhaustive, debug, builder, iterator, on_unknown_attribute, on_unknown_child, transparent, discard, deserialize_callback, attribute, value, } = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?; reject_key!(debug flag not on "enum variants" only on "enums and structs"); reject_key!(exhaustive flag not on "enum variants" only on "enums"); reject_key!(namespace not on "enum variants" only on "enums and structs"); reject_key!(name not on "attribute-switched enum variants" only on "enums, structs and name-switched enum variants"); reject_key!(builder not on "enum variants" only on "enums and structs"); reject_key!(iterator not on "enum variants" only on "enums and structs"); reject_key!(transparent flag not on "named enum variants" only on "structs"); reject_key!(deserialize_callback not on "enum variants" only on "enums and structs"); reject_key!(attribute not on "enum variants" only on "attribute-switched enums"); let Some(value) = value else { return Err(Error::new( meta_span, "`value` is required on attribute-switched enum variants", )); }; Ok(Self { value: value.value(), ident: decl.ident.clone(), inner: Compound::from_fields( &decl.fields, enum_namespace, on_unknown_attribute, on_unknown_child, discard, )?, }) } fn make_from_events_statemachine( &self, enum_ident: &Ident, state_ty_ident: &Ident, ) -> Result { let value = &self.value; Ok(self .inner .make_from_events_statemachine( state_ty_ident, &ParentRef::Named(Path { leading_colon: None, segments: [ PathSegment::from(enum_ident.clone()), self.ident.clone().into(), ] .into_iter() .collect(), }), &self.ident.to_string(), )? .with_augmented_init(|init| { quote! { #value => { #init }, } }) .compile()) } fn make_as_item_iter_statemachine( &self, elem_namespace: &NamespaceRef, elem_name: &NameRef, attr_namespace: &TokenStream, attr_name: &NameRef, enum_ident: &Ident, state_ty_ident: &Ident, item_iter_ty_lifetime: &Lifetime, ) -> Result { let attr_value = &self.value; Ok(self .inner .make_as_item_iter_statemachine( &ParentRef::Named(Path { leading_colon: None, segments: [ PathSegment::from(enum_ident.clone()), self.ident.clone().into(), ] .into_iter() .collect(), }), state_ty_ident, &self.ident.to_string(), item_iter_ty_lifetime, )? .with_augmented_init(|init| { quote! { let name = ( ::xso::exports::rxml::Namespace::from(#elem_namespace), ::xso::exports::alloc::borrow::Cow::Borrowed(#elem_name), ); #init } }) .with_extra_header_state( // Note: we convert the identifier to a string here to prevent // its Span from leaking into the new identifier. quote::format_ident!("{}Discriminator", &self.ident.to_string()), quote! { ::xso::Item::Attribute( #attr_namespace, ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_name), ::xso::exports::alloc::borrow::Cow::Borrowed(#attr_value), ) }, ) .compile()) } } /// The definition of an enum where each variant represents a different value /// of a fixed attribute. struct AttributeSwitchedEnum { /// The XML namespace of the element. elem_namespace: NamespaceRef, /// The XML name of the element. elem_name: NameRef, /// The XML namespace of the attribute, or None if the attribute isn't /// namespaced.' attr_namespace: Option, /// The XML name of the attribute. attr_name: NameRef, /// Enum variant definitions variants: Vec, } impl AttributeSwitchedEnum { fn new<'x, I: IntoIterator>( elem_namespace: NamespaceRef, elem_name: NameRef, attr_namespace: Option, attr_name: NameRef, variant_iter: I, ) -> Result { let mut variants = Vec::new(); let mut seen_values = HashMap::new(); for variant in variant_iter { let variant = ValueVariant::new(variant, &elem_namespace)?; if let Some(other) = seen_values.get(&variant.value) { return Err(Error::new_spanned( variant.value, format!( "duplicate `value` in enum: variants {} and {} have the same attribute value", other, variant.ident ), )); } seen_values.insert(variant.value.clone(), variant.ident.clone()); variants.push(variant); } Ok(Self { elem_namespace, elem_name, attr_namespace, attr_name, variants, }) } /// Provide the `XmlNameMatcher` template for this enum. /// /// Attribute-switched enums always return a matcher specific to the /// element they operate on. fn xml_name_matcher(&self) -> Result { Ok(XmlNameMatcher::Specific( self.elem_namespace.to_token_stream(), self.elem_name.to_token_stream(), )) } /// Build the deserialisation statemachine for the attribute-switched enum. fn make_from_events_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, ) -> Result { let elem_namespace = &self.elem_namespace; let elem_name = &self.elem_name; let attr_namespace = match self.attr_namespace.as_ref() { Some(v) => v.to_token_stream(), None => quote! { ::xso::exports::rxml::Namespace::none() }, }; let attr_name = &self.attr_name; let mut statemachine = FromEventsStateMachine::new(); for variant in self.variants.iter() { statemachine .merge(variant.make_from_events_statemachine(target_ty_ident, state_ty_ident)?); } statemachine.set_pre_init(quote! { let attr = { if name.0 != #elem_namespace || name.1 != #elem_name { return ::core::result::Result::Err( ::xso::error::FromEventsError::Mismatch { name, attrs, }, ); } let ::core::option::Option::Some(attr) = attrs.remove(#attr_namespace, #attr_name) else { return ::core::result::Result::Err( ::xso::error::FromEventsError::Invalid( ::xso::error::Error::Other("Missing discriminator attribute.") ), ); }; attr }; }); statemachine.set_fallback(quote! { ::core::result::Result::Err( ::xso::error::FromEventsError::Invalid( ::xso::error::Error::Other("Unknown value for discriminator attribute.") ), ) }); statemachine.set_match_mode(FromEventsMatchMode::Matched { expr: quote! { attr.as_str() }, }); Ok(statemachine) } /// Build the serialisation statemachine for the attribute-switched enum. fn make_as_item_iter_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, item_iter_ty_lifetime: &Lifetime, ) -> Result { let attr_namespace = match self.attr_namespace.as_ref() { Some(v) => v.to_token_stream(), None => quote! { ::xso::exports::rxml::Namespace::NONE }, }; let mut statemachine = AsItemsStateMachine::new(); for variant in self.variants.iter() { statemachine.merge(variant.make_as_item_iter_statemachine( &self.elem_namespace, &self.elem_name, &attr_namespace, &self.attr_name, target_ty_ident, state_ty_ident, item_iter_ty_lifetime, )?); } Ok(statemachine) } } /// The definition of an enum variant in a [`DynamicEnum`]. struct DynamicVariant { /// The identifier of the enum variant. ident: Ident, /// The definition of the struct-like which resembles the enum variant. inner: StructInner, } impl DynamicVariant { fn new(variant: &Variant) -> Result { let ident = variant.ident.clone(); let meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?; // We destructure here so that we get informed when new fields are // added and can handle them, either by processing them or raising // an error if they are present. let XmlCompoundMeta { span: _, qname: _, // used by StructInner ref exhaustive, ref debug, ref builder, ref iterator, on_unknown_attribute: _, // used by StructInner on_unknown_child: _, // used by StructInner transparent: _, // used by StructInner discard: _, // used by StructInner ref deserialize_callback, ref attribute, ref value, } = meta; reject_key!(debug flag not on "enum variants" only on "enums and structs"); reject_key!(exhaustive flag not on "enum variants" only on "enums"); reject_key!(builder not on "enum variants" only on "enums and structs"); reject_key!(iterator not on "enum variants" only on "enums and structs"); reject_key!(deserialize_callback not on "enum variants" only on "enums and structs"); reject_key!(attribute not on "enum variants" only on "attribute-switched enums"); reject_key!(value not on "dynamic enum variants" only on "attribute-switched enum variants"); let inner = StructInner::new(meta, &variant.fields)?; Ok(Self { ident, inner }) } } /// The definition of an enum where each variant is a completely unrelated /// possible XML subtree. struct DynamicEnum { /// The enum variants. variants: Vec, } impl DynamicEnum { fn new<'x, I: IntoIterator>(variant_iter: I) -> Result { let mut variants = Vec::new(); for variant in variant_iter { variants.push(DynamicVariant::new(variant)?); } Ok(Self { variants }) } /// Provide the `XmlNameMatcher` template for this dynamic enum. /// /// Dynamic enums return the superset of the matchers of their variants, /// which in many cases will be XmlNameMatcher::Any. fn xml_name_matcher(&self) -> Result { let mut iter = self.variants.iter(); let Some(first) = iter.next() else { // Since the enum has no variants, it cannot match anything. We // use an empty namespace and an empty name, which will never // match. return Ok(XmlNameMatcher::Custom(quote! { ::xso::fromxml::XmlNameMatcher::<'static>::Specific("", "") })); }; let first_matcher = first.inner.xml_name_matcher()?; let mut composition = quote! { #first_matcher }; for variant in iter { let next_matcher = variant.inner.xml_name_matcher()?; composition.extend(quote! { .superset(#next_matcher) }); } Ok(XmlNameMatcher::Custom(composition)) } /// Build the deserialisation statemachine for the dynamic enum. fn make_from_events_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, ) -> Result { let mut statemachine = FromEventsStateMachine::new(); for variant in self.variants.iter() { let submachine = variant.inner.make_from_events_statemachine( state_ty_ident, &ParentRef::Named(Path { leading_colon: None, segments: [ PathSegment::from(target_ty_ident.clone()), variant.ident.clone().into(), ] .into_iter() .collect(), }), &variant.ident.to_string(), )?; statemachine.merge(submachine.compile()); } Ok(statemachine) } /// Build the serialisation statemachine for the dynamic enum. fn make_as_item_iter_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, item_iter_ty_lifetime: &Lifetime, ) -> Result { let mut statemachine = AsItemsStateMachine::new(); for variant in self.variants.iter() { let submachine = variant.inner.make_as_item_iter_statemachine( &ParentRef::Named(Path { leading_colon: None, segments: [ PathSegment::from(target_ty_ident.clone()), variant.ident.clone().into(), ] .into_iter() .collect(), }), state_ty_ident, &variant.ident.to_string(), item_iter_ty_lifetime, )?; statemachine.merge(submachine.compile()); } Ok(statemachine) } } /// The definition of an enum. enum EnumInner { /// The enum switches based on the XML name of the element, with the XML /// namespace fixed. NameSwitched(NameSwitchedEnum), /// The enum switches based on the value of an attribute of the XML /// element. AttributeSwitched(AttributeSwitchedEnum), /// The enum consists of variants with entirely unrelated XML structures. Dynamic(DynamicEnum), } impl EnumInner { fn new<'x, I: IntoIterator>( meta: XmlCompoundMeta, variant_iter: I, ) -> Result { // We destructure here so that we get informed when new fields are // added and can handle them, either by processing them or raising // an error if they are present. let XmlCompoundMeta { span, qname: QNameRef { namespace, name }, exhaustive, debug, builder, iterator, on_unknown_attribute, on_unknown_child, transparent, discard, deserialize_callback, attribute, value, } = meta; // These must've been cleared by the caller. Because these being set // is a programming error (in xso-proc) and not a usage error, we // assert here instead of using reject_key!. assert!(builder.is_none()); assert!(iterator.is_none()); assert!(!debug.is_set()); assert!(deserialize_callback.is_none()); reject_key!(transparent flag not on "enums" only on "structs"); reject_key!(on_unknown_attribute not on "enums" only on "enum variants and structs"); reject_key!(on_unknown_child not on "enums" only on "enum variants and structs"); reject_key!(discard vec not on "enums" only on "enum variants and structs"); reject_key!(value not on "enums" only on "attribute-switched enum variants"); if let Some(attribute) = attribute { let Some(attr_name) = attribute.qname.name else { return Err(Error::new( attribute.span, "missing `name` for `attribute` key", )); }; let attr_namespace = attribute.qname.namespace; let Some(elem_namespace) = namespace else { let mut error = Error::new(span, "missing `namespace` key on attribute-switched enum"); error.combine(Error::new( attribute.span, "enum is attribute-switched because of the `attribute` key here", )); return Err(error); }; let Some(elem_name) = name else { let mut error = Error::new(span, "missing `name` key on attribute-switched enum"); error.combine(Error::new( attribute.span, "enum is attribute-switched because of the `attribute` key here", )); return Err(error); }; if !exhaustive.is_set() { return Err(Error::new( span, "attribute-switched enums must be marked as `exhaustive`. non-exhaustive attribute-switched enums are not supported." )); } Ok(Self::AttributeSwitched(AttributeSwitchedEnum::new( elem_namespace, elem_name, attr_namespace, attr_name, variant_iter, )?)) } else if let Some(namespace) = namespace { reject_key!(name not on "name-switched enums" only on "their variants"); Ok(Self::NameSwitched(NameSwitchedEnum::new( namespace, exhaustive, variant_iter, )?)) } else { reject_key!(name not on "dynamic enums" only on "their variants"); reject_key!(exhaustive flag not on "dynamic enums" only on "name-switched enums"); Ok(Self::Dynamic(DynamicEnum::new(variant_iter)?)) } } /// Provide the `XmlNameMatcher` template for this enum. fn xml_name_matcher(&self) -> Result { match self { Self::NameSwitched(ref inner) => inner.xml_name_matcher(), Self::AttributeSwitched(ref inner) => inner.xml_name_matcher(), Self::Dynamic(ref inner) => inner.xml_name_matcher(), } } /// Build the deserialisation statemachine for the enum. fn make_from_events_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, ) -> Result { match self { Self::NameSwitched(ref inner) => { inner.make_from_events_statemachine(target_ty_ident, state_ty_ident) } Self::AttributeSwitched(ref inner) => { inner.make_from_events_statemachine(target_ty_ident, state_ty_ident) } Self::Dynamic(ref inner) => { inner.make_from_events_statemachine(target_ty_ident, state_ty_ident) } } } /// Build the serialisation statemachine for the enum. fn make_as_item_iter_statemachine( &self, target_ty_ident: &Ident, state_ty_ident: &Ident, item_iter_ty_lifetime: &Lifetime, ) -> Result { match self { Self::NameSwitched(ref inner) => inner.make_as_item_iter_statemachine( target_ty_ident, state_ty_ident, item_iter_ty_lifetime, ), Self::AttributeSwitched(ref inner) => inner.make_as_item_iter_statemachine( target_ty_ident, state_ty_ident, item_iter_ty_lifetime, ), Self::Dynamic(ref inner) => inner.make_as_item_iter_statemachine( target_ty_ident, state_ty_ident, item_iter_ty_lifetime, ), } } } /// Definition of an enum and how to parse it. pub(crate) struct EnumDef { /// Implementation of the enum itself inner: EnumInner, /// Name of the target type. target_ty_ident: Ident, /// Name of the builder type. builder_ty_ident: Ident, /// Name of the iterator type. item_iter_ty_ident: Ident, /// Flag whether debug mode is enabled. debug: bool, /// Optional validator function to call. deserialize_callback: Option, } impl EnumDef { /// Create a new enum from its name, meta, and variants. pub(crate) fn new<'x, I: IntoIterator>( ident: &Ident, mut meta: XmlCompoundMeta, variant_iter: I, ) -> Result { let builder_ty_ident = match meta.builder.take() { Some(v) => v, None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()), }; let item_iter_ty_ident = match meta.iterator.take() { Some(v) => v, None => quote::format_ident!("{}AsXmlIterator", ident.to_string()), }; let debug = meta.debug.take().is_set(); let deserialize_callback = meta.deserialize_callback.take(); Ok(Self { inner: EnumInner::new(meta, variant_iter)?, target_ty_ident: ident.clone(), builder_ty_ident, item_iter_ty_ident, debug, deserialize_callback, }) } } impl ItemDef for EnumDef { fn make_from_events_builder( &self, vis: &Visibility, name_ident: &Ident, attrs_ident: &Ident, ) -> Result { let target_ty_ident = &self.target_ty_ident; let builder_ty_ident = &self.builder_ty_ident; let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident); let defs = self .inner .make_from_events_statemachine(target_ty_ident, &state_ty_ident)? .render( vis, builder_ty_ident, &state_ty_ident, &TypePath { qself: None, path: target_ty_ident.clone().into(), } .into(), self.deserialize_callback.as_ref(), )?; Ok(FromXmlParts { defs, from_events_body: quote! { #builder_ty_ident::new(#name_ident, #attrs_ident, ctx) }, builder_ty_ident: builder_ty_ident.clone(), name_matcher: self.inner.xml_name_matcher()?, }) } fn make_as_xml_iter(&self, vis: &Visibility) -> Result { let target_ty_ident = &self.target_ty_ident; let item_iter_ty_ident = &self.item_iter_ty_ident; let item_iter_ty_lifetime = Lifetime { apostrophe: Span::call_site(), ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()), }; let item_iter_ty = Type::Path(TypePath { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: item_iter_ty_ident.clone(), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: token::Lt { spans: [Span::call_site()], }, args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())] .into_iter() .collect(), gt_token: token::Gt { spans: [Span::call_site()], }, }), }] .into_iter() .collect(), }, }); let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident); let defs = self .inner .make_as_item_iter_statemachine( target_ty_ident, &state_ty_ident, &item_iter_ty_lifetime, )? .render( vis, &ref_ty( ty_from_ident(target_ty_ident.clone()).into(), item_iter_ty_lifetime.clone(), ), &state_ty_ident, &item_iter_ty_lifetime, &item_iter_ty, )?; Ok(AsXmlParts { defs, as_xml_iter_body: quote! { #item_iter_ty_ident::new(self) }, item_iter_ty, item_iter_ty_lifetime, }) } fn debug(&self) -> bool { self.debug } } xso_proc-0.2.0/src/error_message.rs000064400000000000000000000117351046102023000154600ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Infrastructure for contextual error messages use core::fmt; use syn::*; /// Reference to a compound field's parent /// /// This reference can be converted to a hopefully-useful human-readable /// string via [`core::fmt::Display`]. #[derive(Clone, Debug)] pub(super) enum ParentRef { /// The parent is addressable by a path, e.g. a struct type or enum /// variant. Named(Path), /// The parent is not addressable by a path, because it is in fact an /// ephemeral structure. /// /// Used to reference the ephemeral structures used by fields declared /// with `#[xml(extract(..))]`. Unnamed { /// The parent's ref. /// /// For extracts, this refers to the compound where the field with /// the extract is declared. parent: Box, /// The field inside that parent. /// /// For extracts, this refers to the compound field where the extract /// is declared. field: Member, }, } impl From for ParentRef { fn from(other: Path) -> Self { Self::Named(other) } } impl From<&Path> for ParentRef { fn from(other: &Path) -> Self { Self::Named(other.clone()) } } impl fmt::Display for ParentRef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Named(name) => { let mut first = true; for segment in name.segments.iter() { if !first || name.leading_colon.is_some() { write!(f, "::")?; } first = false; write!(f, "{}", segment.ident)?; } write!(f, " element") } Self::Unnamed { parent, field } => { write!(f, "extraction for {} in {}", FieldName(field), parent) } } } } impl ParentRef { /// Create a new `ParentRef` for a member inside this one. /// /// Returns a [`Self::Unnamed`] with `self` as parent and `member` as /// field. pub(crate) fn child(&self, member: Member) -> Self { match self { Self::Named { .. } | Self::Unnamed { .. } => Self::Unnamed { parent: Box::new(self.clone()), field: member, }, } } /// Return true if and only if this ParentRef can be addressed as a path. pub(crate) fn is_path(&self) -> bool { match self { Self::Named { .. } => true, Self::Unnamed { .. } => false, } } } /// Wrapper around `Path` with a more human-readable, collapsed version of /// `Path`. pub(crate) struct PrettyPath<'x>(pub &'x Path); impl fmt::Display for PrettyPath<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut first = self.0.leading_colon.is_none(); for segment in self.0.segments.iter() { if !first { f.write_str("::")?; } write!(f, "{}", segment.ident)?; first = false; } Ok(()) } } /// Ephemeral struct to create a nice human-readable representation of /// [`syn::Member`]. /// /// It implements [`core::fmt::Display`] for that purpose and is otherwise of /// little use. #[repr(transparent)] pub(crate) struct FieldName<'x>(pub &'x Member); impl fmt::Display for FieldName<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { Member::Named(v) => write!(f, "field '{}'", v), Member::Unnamed(v) => write!(f, "unnamed field {}", v.index), } } } /// Create a string error message for a missing attribute. /// /// `parent_name` should point at the compound which is being parsed and /// `field` should be the field to which the attribute belongs. pub(super) fn on_missing_attribute(parent_name: &ParentRef, field: &Member) -> String { format!( "Required attribute {} on {} missing.", FieldName(field), parent_name ) } /// Create a string error message for a missing child element. /// /// `parent_name` should point at the compound which is being parsed and /// `field` should be the field to which the child belongs. pub(super) fn on_missing_child(parent_name: &ParentRef, field: &Member) -> String { format!("Missing child {} in {}.", FieldName(field), parent_name) } /// Create a string error message for a duplicate child element. /// /// `parent_name` should point at the compound which is being parsed and /// `field` should be the field to which the child belongs. pub(super) fn on_duplicate_child(parent_name: &ParentRef, field: &Member) -> String { format!( "{} must not have more than one child in {}.", parent_name, FieldName(field) ) } xso_proc-0.2.0/src/field/attribute.rs000064400000000000000000000147231046102023000157110ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! This module concerns the processing of attributes. //! //! In particular, it provides the `#[xml(attribute)]` implementation. use proc_macro2::Span; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, *}; use std::borrow::Cow; use crate::error_message::{self, ParentRef}; use crate::meta::{Flag, NameRef, NamespaceRef, QNameRef, XMLNS_XML}; use crate::scope::{AsItemsScope, FromEventsScope}; use crate::types::{ as_optional_xml_text_fn, default_fn, from_xml_text_fn, text_codec_decode_fn, text_codec_encode_fn, }; use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit}; /// Subtype for attribute-matching fields. pub(super) enum AttributeFieldKind { /// Matches any attribute Generic { /// The optional XML namespace of the attribute. xml_namespace: Option, /// The XML name of the attribute. xml_name: NameRef, }, /// Matches `xml:lang` XmlLang, } impl AttributeFieldKind { fn matcher(&self) -> (Cow<'_, Option>, Cow<'_, NameRef>) { match self { Self::Generic { ref xml_namespace, ref xml_name, } => (Cow::Borrowed(xml_namespace), Cow::Borrowed(xml_name)), Self::XmlLang => ( Cow::Owned(Some(NamespaceRef::fudge(XMLNS_XML, Span::call_site()))), Cow::Owned(NameRef::fudge( rxml_validation::NcName::try_from("lang").unwrap(), Span::call_site(), )), ), } } fn qname_ref(&self) -> QNameRef { let (namespace, name) = self.matcher(); QNameRef { namespace: namespace.into_owned(), name: Some(name.into_owned()), } } } /// The field maps to an attribute. pub(super) struct AttributeField { /// Subtype pub(super) kind: AttributeFieldKind, /// Flag indicating whether the value should be defaulted if the /// attribute is absent. pub(super) default_: Flag, /// Optional codec to use. pub(super) codec: Option, } impl Field for AttributeField { fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, member: &Member, ty: &Type, ) -> Result { let FromEventsScope { ref attrs, .. } = scope; let ty = ty.clone(); let fetch = match self.kind { AttributeFieldKind::Generic { ref xml_namespace, ref xml_name, } => { let xml_namespace = match xml_namespace { Some(v) => v.to_token_stream(), None => quote! { ::xso::exports::rxml::Namespace::none() }, }; quote! { #attrs.remove(#xml_namespace, #xml_name) } } AttributeFieldKind::XmlLang => { quote! { ctx.language().map(::xso::exports::alloc::borrow::ToOwned::to_owned) } } }; let finalize = match self.codec { Some(ref codec) => { let span = codec.span(); let decode = text_codec_decode_fn(ty.clone(), span); quote_spanned! { span=> |value| #decode(&#codec, value) } } None => { let from_xml_text = from_xml_text_fn(ty.clone()); quote! { #from_xml_text } } }; let missing_msg = error_message::on_missing_attribute(container_name, member); let on_absent = match self.default_ { Flag::Absent => quote! { return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()) }, Flag::Present(_) => { let default_ = default_fn(ty.clone()); quote! { #default_() } } }; Ok(FieldBuilderPart::Init { value: FieldTempInit { init: quote! { match #fetch.map(#finalize).transpose()? { ::core::option::Option::Some(v) => v, ::core::option::Option::None => #on_absent, } }, ty: ty.clone(), }, }) } fn make_iterator_part( &self, _scope: &AsItemsScope, _container_name: &ParentRef, bound_name: &Ident, _member: &Member, ty: &Type, ) -> Result { let (xml_namespace, xml_name) = self.kind.matcher(); let xml_namespace = match xml_namespace.as_ref() { Some(ref v) => quote! { ::xso::exports::rxml::Namespace::from(#v) }, None => quote! { ::xso::exports::rxml::Namespace::NONE }, }; let generator = match self.codec { Some(ref codec) => { let span = codec.span(); let encode = text_codec_encode_fn(ty.clone(), span); // NOTE: We need to fudge the span of `bound_name` here, // because its span points outside the macro (the identifier // of the field), which means that quote_spanned will not // override it, which would make the error message ugly. let mut bound_name = bound_name.clone(); bound_name.set_span(span); quote_spanned! { span=> #encode(&#codec, #bound_name)? } } None => { let as_optional_xml_text = as_optional_xml_text_fn(ty.clone()); quote! { #as_optional_xml_text(#bound_name)? } } }; Ok(FieldIteratorPart::Header { generator: quote! { #generator.map(|#bound_name| ::xso::Item::Attribute( #xml_namespace, ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name), #bound_name, )); }, }) } fn captures_attribute(&self) -> Option { Some(self.kind.qname_ref()) } } xso_proc-0.2.0/src/field/child.rs000064400000000000000000000465521046102023000147760ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! This module concerns the processing of typed child elements. //! //! In particular, it provides both `#[xml(extract)]` and `#[xml(child)]` //! implementations in a single type. use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, *}; use crate::compound::Compound; use crate::error_message::{self, ParentRef}; use crate::meta::{AmountConstraint, Flag, NameRef, NamespaceRef}; use crate::scope::{AsItemsScope, FromEventsScope}; use crate::types::{ as_xml_iter_fn, default_fn, extend_fn, from_events_fn, from_xml_builder_ty, into_iterator_into_iter_fn, into_iterator_item_ty, into_iterator_iter_ty, item_iter_ty, option_as_xml_ty, option_ty, ref_ty, ty_from_ident, }; use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher}; /// The field maps to a child pub(super) struct ChildField { /// Flag indicating whether the value should be defaulted if the /// child is absent. pub(super) default_: Flag, /// Number of child elements allowed. pub(super) amount: AmountConstraint, /// If set, the child element is not parsed as a field implementing /// `FromXml` / `AsXml`, but instead its contents are extracted. pub(super) extract: Option, } impl Field for ChildField { fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, member: &Member, ty: &Type, ) -> Result { let (element_ty, is_container) = match self.amount { AmountConstraint::FixedSingle(_) => (ty.clone(), false), AmountConstraint::Any(_) => (into_iterator_item_ty(ty.clone()), true), }; let (extra_defs, matcher, fetch, builder) = match self.extract { Some(ref extract) => extract.make_from_xml_builder_parts( scope, container_name, member, is_container, ty, )?, None => { let FromEventsScope { ref substate_result, .. } = scope; let from_events = from_events_fn(element_ty.clone()); let span = element_ty.span(); let matcher = quote_spanned! { span=> #from_events(name, attrs, ctx) }; let builder = from_xml_builder_ty(element_ty.clone()); ( TokenStream::default(), matcher, quote! { #substate_result }, builder, ) } }; let field_access = scope.access_field(member); match self.amount { AmountConstraint::FixedSingle(_) => { let missing_msg = error_message::on_missing_child(container_name, member); let duplicate_msg = error_message::on_duplicate_child(container_name, member); let on_absent = match self.default_ { Flag::Absent => quote! { return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()) }, Flag::Present(_) => { let default_ = default_fn(element_ty.clone()); quote! { #default_() } } }; Ok(FieldBuilderPart::Nested { extra_defs, value: FieldTempInit { init: quote! { ::core::option::Option::None }, ty: option_ty(ty.clone()), }, matcher: NestedMatcher::Selective(quote! { match #matcher { ::core::result::Result::Ok(v) => if #field_access.is_some() { ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(::xso::error::Error::Other(#duplicate_msg))) } else { ::core::result::Result::Ok(v) }, ::core::result::Result::Err(e) => ::core::result::Result::Err(e), } }), builder, collect: quote! { #field_access = ::core::option::Option::Some(#fetch); }, finalize: quote! { match #field_access { ::core::option::Option::Some(value) => value, ::core::option::Option::None => #on_absent, } }, }) } AmountConstraint::Any(_) => { let ty_extend = extend_fn(ty.clone(), element_ty.clone()); let ty_default = default_fn(ty.clone()); Ok(FieldBuilderPart::Nested { extra_defs, value: FieldTempInit { init: quote! { #ty_default() }, ty: ty.clone(), }, matcher: NestedMatcher::Selective(matcher), builder, collect: quote! { #ty_extend(&mut #field_access, [#fetch]); }, finalize: quote! { #field_access }, }) } } } fn make_iterator_part( &self, scope: &AsItemsScope, container_name: &ParentRef, bound_name: &Ident, member: &Member, ty: &Type, ) -> Result { let AsItemsScope { ref lifetime, .. } = scope; let (item_ty, is_container) = match self.amount { AmountConstraint::FixedSingle(_) => (ty.clone(), false), AmountConstraint::Any(_) => { // This should give us the type of element stored in the // collection. (into_iterator_item_ty(ty.clone()), true) } }; let (extra_defs, init, iter_ty) = match self.extract { Some(ref extract) => extract.make_as_item_iter_parts( scope, ty, container_name, bound_name, member, is_container, )?, None => { let as_xml_iter = as_xml_iter_fn(item_ty.clone()); let item_iter = item_iter_ty(item_ty.clone(), lifetime.clone()); let span = item_ty.span(); ( TokenStream::default(), quote_spanned! { span=> #as_xml_iter(#bound_name)? }, item_iter, ) } }; match self.amount { AmountConstraint::FixedSingle(_) => Ok(FieldIteratorPart::Content { extra_defs, value: FieldTempInit { init, ty: iter_ty }, generator: quote! { #bound_name.next().transpose() }, }), AmountConstraint::Any(_) => { // This is the collection type we actually work // with -- as_xml_iter uses references after all. let ty = ref_ty(ty.clone(), lifetime.clone()); // But the iterator for iterating over the elements // inside the collection must use the ref type. let element_iter = into_iterator_iter_ty(ty.clone()); // And likewise the into_iter impl. let into_iter = into_iterator_into_iter_fn(ty.clone()); let state_ty = Type::Tuple(TypeTuple { paren_token: token::Paren::default(), elems: [element_iter, option_ty(iter_ty)].into_iter().collect(), }); Ok(FieldIteratorPart::Content { extra_defs, value: FieldTempInit { init: quote! { (#into_iter(#bound_name), ::core::option::Option::None) }, ty: state_ty, }, generator: quote! { loop { if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() { if let ::core::option::Option::Some(item) = current.next() { break ::core::option::Option::Some(item).transpose(); } } if let ::core::option::Option::Some(item) = #bound_name.0.next() { #bound_name.1 = ::core::option::Option::Some({ let #bound_name = item; #init }); } else { break ::core::result::Result::Ok(::core::option::Option::None) } } }, }) } } } } /// Definition of what to extract from a child element. pub(super) struct ExtractDef { /// The XML namespace of the child to extract data from. pub(super) xml_namespace: NamespaceRef, /// The XML name of the child to extract data from. pub(super) xml_name: NameRef, /// Compound which contains the arguments of the `extract(..)` meta /// (except the `from`), transformed into a struct with unnamed /// fields. /// /// This is used to generate the parsing/serialisation code, by /// essentially "declaring" a shim struct, as if it were a real Rust /// struct, and using the result of the parsing process directly for /// the field on which the `extract(..)` option was used, instead of /// putting it into a Rust struct. pub(super) parts: Compound, } impl ExtractDef { /// Construct /// [`FieldBuilderPart::Nested::extra_defs`], /// [`FieldBuilderPart::Nested::matcher`], /// an expression which pulls the extraction result from /// `substate_result`, /// and the [`FieldBuilderPart::Nested::builder`] type. fn make_from_xml_builder_parts( &self, scope: &FromEventsScope, container_name: &ParentRef, member: &Member, collecting_into_container: bool, output_ty: &Type, ) -> Result<(TokenStream, TokenStream, TokenStream, Type)> { let FromEventsScope { ref substate_result, .. } = scope; let xml_namespace = &self.xml_namespace; let xml_name = &self.xml_name; let from_xml_builder_ty_ident = scope.make_member_type_name(member, "FromXmlBuilder"); let state_ty_ident = quote::format_ident!("{}State", from_xml_builder_ty_ident,); let extra_defs = self.parts.make_from_events_statemachine( &state_ty_ident, &container_name.child(member.clone()), "", )?.with_augmented_init(|init| quote! { if name.0 == #xml_namespace && name.1 == #xml_name { #init } else { ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) } }).compile().render( &Visibility::Inherited, &from_xml_builder_ty_ident, &state_ty_ident, &self.parts.to_tuple_ty().into(), None, )?; let from_xml_builder_ty = ty_from_ident(from_xml_builder_ty_ident.clone()).into(); let matcher = quote! { #state_ty_ident::new(name, attrs, ctx).map(|x| #from_xml_builder_ty_ident(::core::option::Option::Some(x))) }; let inner_ty = self.parts.to_single_or_tuple_ty(); let fetch = if self.parts.field_count() == 1 { // If we extract only a single field, we automatically unwrap the // tuple, because that behaviour is more obvious to users. quote! { #substate_result.0 } } else { // If we extract more than one field, we pass the value down as // the tuple that it is. quote! { #substate_result } }; let fetch = if collecting_into_container { // This is for symmetry with the AsXml implementation part. Please // see there for why we cannot do option magic in the collection // case. fetch } else { // This little ".into()" here goes a long way. It relies on one of // the most underrated trait implementations in the standard // library: `impl From for Option`, which creates a // `Some(_)` from a `T`. Why is it so great? Because there is also // `impl From> for Option` (obviously), which is just // a move. So even without knowing the exact type of the substate // result and the field, we can make an "downcast" to `Option` // if the field is of type `Option`, and it does the right // thing no matter whether the extracted field is of type // `Option` or `T`. // // And then, type inference does the rest: There is ambiguity // there, of course, if we call `.into()` on a value of type // `Option`: Should Rust wrap it into another layer of // `Option`, or should it just move the value? The answer lies in // the type constraint imposed by the place the value is *used*, // which is strictly bound by the field's type (so there is, in // fact, no ambiguity). So this works all kinds of magic. quote_spanned! { output_ty.span()=> <#output_ty as ::core::convert::From::<#inner_ty>>::from(#fetch) } }; Ok((extra_defs, matcher, fetch, from_xml_builder_ty)) } /// Construct /// [`FieldIteratorPart::Content::extra_defs`], /// the [`FieldIteratorPart::Content::value`] init, /// and the iterator type. fn make_as_item_iter_parts( &self, scope: &AsItemsScope, input_ty: &Type, container_name: &ParentRef, bound_name: &Ident, member: &Member, iterating_container: bool, ) -> Result<(TokenStream, TokenStream, Type)> { let AsItemsScope { ref lifetime, .. } = scope; let xml_namespace = &self.xml_namespace; let xml_name = &self.xml_name; let item_iter_ty_ident = scope.make_member_type_name(member, "AsXmlIterator"); let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident,); let mut item_iter_ty = ty_from_ident(item_iter_ty_ident.clone()); item_iter_ty.path.segments[0].arguments = PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: token::Lt::default(), args: [GenericArgument::Lifetime(lifetime.clone())] .into_iter() .collect(), gt_token: token::Gt::default(), }); let item_iter_ty = item_iter_ty.into(); let tuple_ty = self.parts.to_ref_tuple_ty(lifetime); let (repack, inner_ty) = match self.parts.single_ty() { Some(single_ty) => ( quote! { #bound_name, }, ref_ty(single_ty.clone(), lifetime.clone()), ), None => { let mut repack_tuple = TokenStream::default(); // The cast here is sound, because the constructor of Compound // already asserts that there are less than 2^32 fields (with // what I think is a great error message, go check it out). for i in 0..(tuple_ty.elems.len() as u32) { let index = Index { index: i, span: Span::call_site(), }; repack_tuple.extend(quote! { &#bound_name.#index, }) } let ref_tuple_ty = ref_ty(self.parts.to_tuple_ty().into(), lifetime.clone()); (repack_tuple, ref_tuple_ty) } }; let extra_defs = self .parts .make_as_item_iter_statemachine( &container_name.child(member.clone()), &state_ty_ident, "", lifetime, )? .with_augmented_init(|init| { quote! { let name = ( ::xso::exports::rxml::Namespace::from(#xml_namespace), ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name), ); #init } }) .compile() .render( &Visibility::Inherited, &tuple_ty.into(), &state_ty_ident, lifetime, &item_iter_ty, )?; let (make_iter, item_iter_ty) = if iterating_container { // When we are iterating a container, the container's iterator's // item type may either be `&(A, B, ...)` or `(&A, &B, ...)`. // Unfortunately, to be able to handle both, we need to omit the // magic Option cast, because we cannot specify the type of the // argument of the `.map(...)` closure and rust is not able to // infer that type because the repacking is too opaque. // // However, this is not much of a loss, because it doesn't really // make sense to have the option cast there, anyway: optional // elements in a container would be weird. ( quote! { #item_iter_ty_ident::new((#repack))? }, item_iter_ty, ) } else { // Again we exploit the extreme usefulness of the // `impl From for Option`. We already wrote extensively // about that in [`make_from_xml_builder_parts`] implementation // corresponding to this code above, and we will not repeat it // here. // These sections with quote_spanned are used to improve error // messages on type mismatches. Without these, the rustc errors // will point at `#[derive(AsXml)]` only, instead of directly // pointing at the sources of those types. let cast = quote_spanned! { input_ty.span()=> ::core::option::Option::from(#bound_name) }; let type_assert = quote_spanned! { inner_ty.span()=> ::core::option::Option<#inner_ty> }; ( quote! { ::xso::asxml::OptionAsXml::new({ let x: #type_assert = #cast; x.map(|#bound_name: #inner_ty| { #item_iter_ty_ident::new((#repack)) })}.transpose()?) }, option_as_xml_ty(item_iter_ty), ) }; Ok((extra_defs, make_iter, item_iter_ty)) } } xso_proc-0.2.0/src/field/element.rs000064400000000000000000000161001046102023000153260ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! This module concerns the processing of untyped `minidom::Element` //! children. //! //! In particular, it provides the `#[xml(element)]` implementation. use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::*; use crate::error_message::{self, ParentRef}; use crate::meta::{AmountConstraint, Flag}; use crate::scope::{AsItemsScope, FromEventsScope}; use crate::types::{ as_xml_iter_fn, default_fn, element_ty, from_events_fn, from_xml_builder_ty, into_iterator_into_iter_fn, into_iterator_item_ty, into_iterator_iter_ty, item_iter_ty, option_ty, ref_ty, }; use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher}; pub(super) struct ElementField { /// Flag indicating whether the value should be defaulted if the /// child is absent. pub(super) default_: Flag, /// Number of child elements allowed. pub(super) amount: AmountConstraint, } impl Field for ElementField { fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, member: &Member, ty: &Type, ) -> Result { let element_ty = match self.amount { AmountConstraint::FixedSingle(_) => ty.clone(), AmountConstraint::Any(_) => into_iterator_item_ty(ty.clone()), }; let FromEventsScope { ref substate_result, .. } = scope; let from_events = from_events_fn(element_ty.clone()); let extra_defs = TokenStream::default(); let field_access = scope.access_field(member); let default_fn = default_fn(ty.clone()); let builder = from_xml_builder_ty(element_ty.clone()); match self.amount { AmountConstraint::FixedSingle(_) => { let missing_msg = error_message::on_missing_child(container_name, member); let on_absent = match self.default_ { Flag::Absent => quote! { return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()) }, Flag::Present(_) => { quote! { #default_fn() } } }; Ok(FieldBuilderPart::Nested { extra_defs, value: FieldTempInit { init: quote! { ::core::option::Option::None }, ty: option_ty(ty.clone()), }, matcher: NestedMatcher::Selective(quote! { if #field_access.is_some() { ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) } else { #from_events(name, attrs, ctx) } }), builder, collect: quote! { #field_access = ::core::option::Option::Some(#substate_result); }, finalize: quote! { match #field_access { ::core::option::Option::Some(value) => value, ::core::option::Option::None => #on_absent, } }, }) } AmountConstraint::Any(_) => Ok(FieldBuilderPart::Nested { extra_defs, value: FieldTempInit { init: quote! { #default_fn() }, ty: ty.clone(), }, matcher: NestedMatcher::Fallback(quote! { #builder::new(name, attrs) }), builder, collect: quote! { <#ty as ::core::iter::Extend::<#element_ty>>::extend(&mut #field_access, [#substate_result]); }, finalize: quote! { #field_access }, }), } } fn make_iterator_part( &self, scope: &AsItemsScope, _container_name: &ParentRef, bound_name: &Ident, _member: &Member, ty: &Type, ) -> Result { let AsItemsScope { ref lifetime, .. } = scope; let item_ty = match self.amount { AmountConstraint::FixedSingle(_) => ty.clone(), AmountConstraint::Any(_) => { // This should give us the type of element stored in the // collection. into_iterator_item_ty(ty.clone()) } }; let element_ty = element_ty(Span::call_site()); let iter_ty = item_iter_ty(element_ty.clone(), lifetime.clone()); let element_iter = into_iterator_iter_ty(ref_ty(ty.clone(), lifetime.clone())); let into_iter = into_iterator_into_iter_fn(ref_ty(ty.clone(), lifetime.clone())); let state_ty = Type::Tuple(TypeTuple { paren_token: token::Paren::default(), elems: [element_iter, option_ty(iter_ty.clone())] .into_iter() .collect(), }); let extra_defs = TokenStream::default(); let as_xml_iter = as_xml_iter_fn(item_ty.clone()); let init = quote! { #as_xml_iter(#bound_name)? }; let iter_ty = item_iter_ty(item_ty.clone(), lifetime.clone()); match self.amount { AmountConstraint::FixedSingle(_) => Ok(FieldIteratorPart::Content { extra_defs, value: FieldTempInit { init, ty: iter_ty }, generator: quote! { #bound_name.next().transpose() }, }), AmountConstraint::Any(_) => Ok(FieldIteratorPart::Content { extra_defs, value: FieldTempInit { init: quote! { (#into_iter(#bound_name), ::core::option::Option::None) }, ty: state_ty, }, generator: quote! { loop { if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() { if let ::core::option::Option::Some(item) = current.next() { break ::core::option::Option::Some(item).transpose(); } } if let ::core::option::Option::Some(item) = #bound_name.0.next() { #bound_name.1 = ::core::option::Option::Some( <#element_ty as ::xso::AsXml>::as_xml_iter(item)? ); } else { break ::core::result::Result::Ok(::core::option::Option::None) } } }, }), } } } xso_proc-0.2.0/src/field/flag.rs000064400000000000000000000113141046102023000146100ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! This module concerns the processing of flag-style children. //! //! In particular, it provides the `#[xml(flag)]` implementation. use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::*; use crate::error_message::{FieldName, ParentRef}; use crate::meta::{NameRef, NamespaceRef}; use crate::scope::{AsItemsScope, FromEventsScope}; use crate::types::{bool_ty, empty_builder_ty, u8_ty}; use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher}; /// The field maps to a child element, the presence of which is represented as boolean. pub(super) struct FlagField { /// The XML namespace of the child element. pub(super) xml_namespace: NamespaceRef, /// The XML name of the child element. pub(super) xml_name: NameRef, } impl Field for FlagField { fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, member: &Member, _ty: &Type, ) -> Result { let field_access = scope.access_field(member); let unknown_attr_err = format!( "Unknown attribute in flag child {} in {}.", FieldName(member), container_name ); let unknown_child_err = format!( "Unknown child in flag child {} in {}.", FieldName(member), container_name ); let unknown_text_err = format!( "Unexpected text in flag child {} in {}.", FieldName(member), container_name ); let xml_namespace = &self.xml_namespace; let xml_name = &self.xml_name; Ok(FieldBuilderPart::Nested { extra_defs: TokenStream::new(), value: FieldTempInit { ty: bool_ty(Span::call_site()), init: quote! { false }, }, matcher: NestedMatcher::Selective(quote! { if name.0 == #xml_namespace && name.1 == #xml_name { ::xso::fromxml::Empty { attributeerr: #unknown_attr_err, childerr: #unknown_child_err, texterr: #unknown_text_err, }.start(attrs).map_err( ::xso::error::FromEventsError::Invalid ) } else { ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs, }) } }), builder: empty_builder_ty(Span::call_site()), collect: quote! { #field_access = true; }, finalize: quote! { #field_access }, }) } fn make_iterator_part( &self, _scope: &AsItemsScope, _container_name: &ParentRef, bound_name: &Ident, _member: &Member, _ty: &Type, ) -> Result { let xml_namespace = &self.xml_namespace; let xml_name = &self.xml_name; Ok(FieldIteratorPart::Content { extra_defs: TokenStream::new(), value: FieldTempInit { init: quote! { if *#bound_name { 3 } else { 1 } }, ty: u8_ty(Span::call_site()), }, generator: quote! { { // using wrapping_sub will make the match below crash // with unreachable!() in case we messed up somewhere. #bound_name = #bound_name.wrapping_sub(1); match #bound_name { 0 => ::core::result::Result::<_, ::xso::error::Error>::Ok(::core::option::Option::None), 1 => ::core::result::Result::Ok(::core::option::Option::Some( ::xso::Item::ElementFoot )), 2 => ::core::result::Result::Ok(::core::option::Option::Some( ::xso::Item::ElementHeadStart( ::xso::exports::rxml::Namespace::from(#xml_namespace), ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name), ) )), _ => unreachable!(), } } }, }) } } xso_proc-0.2.0/src/field/mod.rs000064400000000000000000000510421046102023000144600ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Compound (struct or enum variant) field types use proc_macro2::{Span, TokenStream}; use syn::{spanned::Spanned, *}; use rxml_validation::NcName; use crate::compound::Compound; use crate::error_message::ParentRef; use crate::meta::{ AmountConstraint, AttributeKind, Flag, NameRef, NamespaceRef, QNameRef, XmlFieldMeta, }; use crate::scope::{AsItemsScope, FromEventsScope}; mod attribute; mod child; #[cfg(feature = "minidom")] mod element; mod flag; mod text; use self::attribute::{AttributeField, AttributeFieldKind}; use self::child::{ChildField, ExtractDef}; #[cfg(feature = "minidom")] use self::element::ElementField; use self::flag::FlagField; use self::text::TextField; /// Code slices necessary for declaring and initializing a temporary variable /// for parsing purposes. pub(crate) struct FieldTempInit { /// The type of the temporary variable. pub(crate) ty: Type, /// The initializer for the temporary variable. pub(crate) init: TokenStream, } /// Configure how a nested field builder selects child elements. pub(crate) enum NestedMatcher { /// Matches a specific child element fallabily. Selective( /// Expression which evaluates to `Result`, /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`. /// /// If the `name` and `attrs` allow starting to parse the child /// element as a value of this field, `Ok(_)` must be returned. If /// the `name` and `attrs` are those of an element which *could* /// be a value of this field, but they have invalid contents, /// `Err(FromEventsError::Invalid(_))` must be returned. Depending /// on the field kind, it may also be acceptable to return the /// `Invalid` variant if the data is valid, but no further child /// element can be accepted into the value. /// /// Otherwise, the `name` and `attrs` must be returned *unchanged* in /// a `FromEventsError::Mismatch { .. }` variant. In that case, the /// implementation in `Compound` will let the next field attempt to /// parse the child element. /// /// `T` must be the type specified in the /// [`FieldBuilderPart::Nested::builder`] field. TokenStream, ), #[cfg_attr(not(feature = "minidom"), allow(dead_code))] /// Matches any child element not matched by another matcher. /// /// Only a single field may use this variant, otherwise an error is /// raised during execution of the proc macro. Fallback( /// Expression which evaluates to `T` (or `return`s an error), /// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`. /// /// Unlike the [`Selective`][`Self::Selective`] variant, this /// expression must always evaluate to an instance of `T`. If that is /// not possible, the expression must diverge, most commonly using /// `return` with a `Err::<_, xso::error::Error>(_)`. /// /// `T` must be the type specified in the /// [`FieldBuilderPart::Nested::builder`] field. TokenStream, ), } /// Describe how a struct or enum variant's member is parsed from XML data. /// /// This struct is returned from [`FieldDef::make_builder_part`] and /// contains code snippets and instructions for /// [`Compound::make_from_events_statemachine`][`crate::compound::Compound::make_from_events_statemachine`] /// to parse the field's data from XML. pub(crate) enum FieldBuilderPart { /// Parse a field from the item's element's start event. Init { /// Expression and type which extracts the field's data from the /// element's start event. value: FieldTempInit, }, /// Parse a field from text events. Text { /// Expression and type which initializes a buffer to use during /// parsing. value: FieldTempInit, /// Statement which takes text and accumulates it into the temporary /// value declared via `value`. collect: TokenStream, /// Expression which evaluates to the field's type, consuming the /// temporary value. finalize: TokenStream, }, /// Parse a field from child element events. Nested { /// Additional definition items which need to be inserted at module /// level for the rest of the implementation to work. extra_defs: TokenStream, /// Expression and type which initializes a buffer to use during /// parsing. value: FieldTempInit, /// Configure child matching behaviour for this field. See /// [`NestedMatcher`] for options. matcher: NestedMatcher, /// Type implementing `xso::FromEventsBuilder` which parses the child /// element. /// /// This type is returned by the expressions in /// [`matcher`][`Self::Nested::matcher`]. builder: Type, /// Expression which consumes the value stored in the identifier /// [`crate::common::FromEventsScope::substate_result`][`FromEventsScope::substate_result`] /// and somehow collects it into the field declared with /// [`value`][`Self::Nested::value`]. collect: TokenStream, /// Expression which consumes the data from the field declared with /// [`value`][`Self::Nested::value`] and converts it into the field's /// type. finalize: TokenStream, }, } /// Describe how a struct or enum variant's member is converted to XML data. /// /// This struct is returned from [`FieldDef::make_iterator_part`] and /// contains code snippets and instructions for /// [`Compound::make_into_events_statemachine`][`crate::compound::Compound::make_into_events_statemachine`] /// to convert the field's data into XML. pub(crate) enum FieldIteratorPart { /// The field is emitted as part of StartElement. Header { /// An expression which consumes the field's value and returns a /// `Item`. generator: TokenStream, }, /// The field is emitted as text item. Text { /// An expression which consumes the field's value and returns a /// String, which is then emitted as text data. generator: TokenStream, }, /// The field is emitted as series of items which form a child element. Content { /// Additional definition items which need to be inserted at module /// level for the rest of the implementation to work. extra_defs: TokenStream, /// Expression and type which initializes the nested iterator. /// /// Note that this is evaluated at construction time of the iterator. /// Fields of this variant do not get access to their original data, /// unless they carry it in the contents of this `value`. value: FieldTempInit, /// An expression which uses the value (mutably) and evaluates to /// a Result, Error>. Once the state returns None, the /// processing will advance to the next state. generator: TokenStream, }, } trait Field { /// Construct the builder pieces for this field. /// /// `container_name` must be a reference to the compound's type, so that /// it can be used for error messages. /// /// `member` and `ty` refer to the field itself. fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, member: &Member, ty: &Type, ) -> Result; /// Construct the iterator pieces for this field. /// /// `bound_name` must be the name to which the field's value is bound in /// the iterator code. /// /// `member` and `ty` refer to the field itself. /// /// `bound_name` is the name under which the field's value is accessible /// in the various parts of the code. fn make_iterator_part( &self, scope: &AsItemsScope, container_name: &ParentRef, bound_name: &Ident, member: &Member, ty: &Type, ) -> Result; /// Return true if and only if this field captures text content. fn captures_text(&self) -> bool { false } /// Return a QNameRef if the field captures an attribute. fn captures_attribute(&self) -> Option { None } } fn default_name(span: Span, name: Option, field_ident: Option<&Ident>) -> Result { match name { Some(v) => Ok(v), None => match field_ident { None => Err(Error::new( span, "name must be explicitly specified with the `name` key on unnamed fields", )), Some(field_ident) => match NcName::try_from(field_ident.to_string()) { Ok(value) => Ok(NameRef::Literal { span: field_ident.span(), value, }), Err(e) => Err(Error::new( field_ident.span(), format!("invalid XML name: {}", e), )), }, }, } } /// Construct a new field implementation from the meta attributes. /// /// `field_ident` is, for some field types, used to infer an XML name if /// it is not specified explicitly. /// /// `field_ty` is needed for type inference on extracted fields. /// /// `container_namespace` is used in some cases to insert a default /// namespace. fn new_field( meta: XmlFieldMeta, field_ident: Option<&Ident>, field_ty: &Type, container_namespace: &NamespaceRef, ) -> Result> { match meta { XmlFieldMeta::Attribute { span, kind: AttributeKind::Generic(QNameRef { name, namespace }), default_, type_, codec, } => { let xml_name = default_name(span, name, field_ident)?; // This would've been taken via `XmlFieldMeta::take_type` if // this field was within an extract where a `type_` is legal // to have. if let Some(type_) = type_ { return Err(Error::new_spanned( type_, "specifying `type_` on fields inside structs and enum variants is redundant and not allowed." )); } Ok(Box::new(AttributeField { kind: AttributeFieldKind::Generic { xml_name, xml_namespace: namespace, }, default_, codec, })) } XmlFieldMeta::Attribute { span: _, kind: AttributeKind::XmlLang, default_, type_, codec, } => { // This would've been taken via `XmlFieldMeta::take_type` if // this field was within an extract where a `type_` is legal // to have. if let Some(type_) = type_ { return Err(Error::new_spanned( type_, "specifying `type_` on fields inside structs and enum variants is redundant and not allowed." )); } Ok(Box::new(AttributeField { kind: AttributeFieldKind::XmlLang, default_, codec, })) } XmlFieldMeta::Text { span: _, codec, type_, } => { // This would've been taken via `XmlFieldMeta::take_type` if // this field was within an extract where a `type_` is legal // to have. if let Some(type_) = type_ { return Err(Error::new_spanned( type_, "specifying `type_` on fields inside structs and enum variants is redundant and not allowed." )); } Ok(Box::new(TextField { codec })) } XmlFieldMeta::Child { span: _, default_, amount, } => { if let Some(AmountConstraint::Any(ref amount_span)) = amount { if let Flag::Present(ref flag_span) = default_ { let mut err = Error::new(*flag_span, "`default` has no meaning for child collections"); err.combine(Error::new( *amount_span, "the field is treated as a collection because of this `n` value", )); return Err(err); } } Ok(Box::new(ChildField { default_, amount: amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())), extract: None, })) } XmlFieldMeta::Extract { span, default_, qname: QNameRef { namespace, name }, amount, fields, on_unknown_attribute, on_unknown_child, } => { let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone()); let xml_name = default_name(span, name, field_ident)?; let amount = amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())); match amount { AmountConstraint::Any(ref amount) => { if let Flag::Present(default_) = default_ { let mut err = Error::new( default_, "default cannot be set when collecting into a collection", ); err.combine(Error::new( *amount, "`n` was set to a non-1 value here, which enables collection logic", )); return Err(err); } } AmountConstraint::FixedSingle(_) => (), } let mut field_defs = Vec::new(); let allow_inference = matches!(amount, AmountConstraint::FixedSingle(_)) && fields.len() == 1; for (i, mut field) in fields.into_iter().enumerate() { let field_ty = match field.take_type() { Some(v) => v, None => { if allow_inference { field_ty.clone() } else { return Err(Error::new( field.span(), "extracted field must specify a type explicitly when extracting into a collection or when extracting more than one field." )); } } }; field_defs.push(FieldDef::from_extract( field, i as u32, &field_ty, &xml_namespace, )); } let parts = Compound::from_field_defs( field_defs, on_unknown_attribute, on_unknown_child, vec![], )?; Ok(Box::new(ChildField { default_, amount, extract: Some(ExtractDef { xml_namespace, xml_name, parts, }), })) } #[cfg(feature = "minidom")] XmlFieldMeta::Element { span, default_, amount, } => Ok(Box::new(ElementField { default_, amount: amount.unwrap_or(AmountConstraint::FixedSingle(span)), })), #[cfg(not(feature = "minidom"))] XmlFieldMeta::Element { span, amount, default_, } => { let _ = amount; let _ = default_; Err(Error::new( span, "#[xml(element)] requires xso to be built with the \"minidom\" feature.", )) } XmlFieldMeta::Flag { span, qname: QNameRef { namespace, name }, } => { let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone()); let xml_name = default_name(span, name, field_ident)?; Ok(Box::new(FlagField { xml_namespace, xml_name, })) } } } /// Definition of a single field in a compound. /// /// See [`Compound`][`crate::compound::Compound`] for more information on /// compounds in general. pub(crate) struct FieldDef { /// A span which refers to the field's definition. span: Span, /// The member identifying the field. member: Member, /// The type of the field. ty: Type, /// The way the field is mapped to XML. inner: Box, } impl FieldDef { /// Create a new field definition from its declaration. /// /// The `index` must be the zero-based index of the field even for named /// fields. pub(crate) fn from_field( field: &syn::Field, index: u32, container_namespace: &NamespaceRef, ) -> Result { let (member, ident) = match field.ident.as_ref() { Some(v) => (Member::Named(v.clone()), Some(v)), None => ( Member::Unnamed(Index { index, // We use the type's span here, because `field.span()` // will visually point at the `#[xml(..)]` meta, which is // not helpful when glancing at error messages referring // to the field itself. span: field.ty.span(), }), None, ), }; // This will either be the field's identifier's span (for named // fields) or the field's type (for unnamed fields), which should give // the user a good visual feedback about which field an error message // is. let field_span = member.span(); let meta = XmlFieldMeta::parse_from_attributes(&field.attrs, &field_span)?; let ty = field.ty.clone(); Ok(Self { span: field_span, inner: new_field(meta, ident, &ty, container_namespace)?, member, ty, }) } /// Create a new field definition from its declaration. /// /// The `index` must be the zero-based index of the field even for named /// fields. pub(crate) fn from_extract( meta: XmlFieldMeta, index: u32, ty: &Type, container_namespace: &NamespaceRef, ) -> Result { let span = meta.span(); Ok(Self { span, member: Member::Unnamed(Index { index, span }), ty: ty.clone(), inner: new_field(meta, None, ty, container_namespace)?, }) } /// Access the [`syn::Member`] identifying this field in the original /// type. pub(crate) fn member(&self) -> &Member { &self.member } /// Access the field's type. pub(crate) fn ty(&self) -> &Type { &self.ty } /// Construct the builder pieces for this field. /// /// `container_name` must be a reference to the compound's type, so that /// it can be used for error messages. pub(crate) fn make_builder_part( &self, scope: &FromEventsScope, container_name: &ParentRef, ) -> Result { self.inner .make_builder_part(scope, container_name, &self.member, &self.ty) } /// Construct the iterator pieces for this field. /// /// `bound_name` must be the name to which the field's value is bound in /// the iterator code. pub(crate) fn make_iterator_part( &self, scope: &AsItemsScope, container_name: &ParentRef, bound_name: &Ident, ) -> Result { self.inner .make_iterator_part(scope, container_name, bound_name, &self.member, &self.ty) } /// Return true if this field's parsing consumes text data. pub(crate) fn is_text_field(&self) -> bool { self.inner.captures_text() } /// Return a QNameRef if the field captures an attribute. pub(crate) fn captures_attribute(&self) -> Option { self.inner.captures_attribute() } /// Return a span which points at the field's definition. pub(crate) fn span(&self) -> Span { self.span } } xso_proc-0.2.0/src/field/text.rs000064400000000000000000000063231046102023000146670ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! This module concerns the processing of text content. //! //! In particular, it provides the `#[xml(text)]` implementation. use proc_macro2::Span; use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, *}; use crate::error_message::ParentRef; use crate::scope::{AsItemsScope, FromEventsScope}; use crate::types::{ as_xml_text_fn, from_xml_text_fn, string_ty, text_codec_decode_fn, text_codec_encode_fn, }; use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit}; /// The field maps to the character data of the element. pub(super) struct TextField { /// Optional codec to use pub(super) codec: Option, } impl Field for TextField { fn make_builder_part( &self, scope: &FromEventsScope, _container_name: &ParentRef, member: &Member, ty: &Type, ) -> Result { let FromEventsScope { ref text, .. } = scope; let field_access = scope.access_field(member); let finalize = match self.codec { Some(ref codec) => { let span = codec.span(); let decode = text_codec_decode_fn(ty.clone(), span); quote_spanned! { span=> #decode(&#codec, #field_access)? } } None => { let from_xml_text = from_xml_text_fn(ty.clone()); quote! { #from_xml_text(#field_access)? } } }; Ok(FieldBuilderPart::Text { value: FieldTempInit { init: quote! { ::xso::exports::alloc::string::String::new() }, ty: string_ty(Span::call_site()), }, collect: quote! { #field_access.push_str(#text.as_str()); }, finalize, }) } fn make_iterator_part( &self, _scope: &AsItemsScope, _container_name: &ParentRef, bound_name: &Ident, _member: &Member, ty: &Type, ) -> Result { let generator = match self.codec { Some(ref codec) => { let span = codec.span(); let encode = text_codec_encode_fn(ty.clone(), span); // NOTE: We need to fudge the span of `bound_name` here, // because its span points outside the macro (the identifier // of the field), which means that quote_spanned will not // override it, which would make the error message ugly. let mut bound_name = bound_name.clone(); bound_name.set_span(span); quote_spanned! { span=> #encode(&#codec, #bound_name)? } } None => { let as_xml_text = as_xml_text_fn(ty.clone()); quote! { ::core::option::Option::Some(#as_xml_text(#bound_name)?) } } }; Ok(FieldIteratorPart::Text { generator }) } fn captures_text(&self) -> bool { true } } xso_proc-0.2.0/src/lib.rs000064400000000000000000000152421046102023000133660ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![forbid(unsafe_code)] #![warn(missing_docs)] #![allow(rustdoc::private_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] /*! # Macros for parsing XML into Rust structs, and vice versa **If you are a user of `xso_proc` or `xso`, please return to `xso` for more information**. The documentation of `xso_proc` is geared toward developers of `…_macros` and `…_core`. **You have been warned.** */ // Wondering about RawTokenStream vs. TokenStream? // syn mostly works with proc_macro2, while the proc macros themselves use // proc_macro. use proc_macro::TokenStream as RawTokenStream; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::*; mod common; mod compound; mod enums; mod error_message; mod field; mod meta; mod scope; mod state; mod structs; mod types; use common::{AsXmlParts, FromXmlParts, ItemDef}; /// Convert an [`syn::Item`] into the parts relevant for us. /// /// If the item is of an unsupported variant, an appropriate error is /// returned. fn parse_struct(item: Item) -> Result<(Visibility, Ident, Box)> { match item { Item::Struct(item) => { let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?; let def = structs::StructDef::new(&item.ident, meta, &item.fields)?; Ok((item.vis, item.ident, Box::new(def))) } Item::Enum(item) => { let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?; let def = enums::EnumDef::new(&item.ident, meta, &item.variants)?; Ok((item.vis, item.ident, Box::new(def))) } other => Err(Error::new_spanned(other, "cannot derive on this item")), } } /// Generate a `xso::FromXml` implementation for the given item, or fail with /// a proper compiler error. fn from_xml_impl(input: Item) -> Result { let (vis, ident, def) = parse_struct(input)?; let name_ident = Ident::new("name", Span::call_site()); let attrs_ident = Ident::new("attrs", Span::call_site()); let FromXmlParts { defs, from_events_body, builder_ty_ident, name_matcher, } = def.make_from_events_builder(&vis, &name_ident, &attrs_ident)?; #[cfg_attr(not(feature = "minidom"), allow(unused_mut))] let mut result = quote! { #defs impl ::xso::FromXml for #ident { type Builder = #builder_ty_ident; fn from_events( name: ::xso::exports::rxml::QName, attrs: ::xso::exports::rxml::AttrMap, ctx: &::xso::Context<'_>, ) -> ::core::result::Result { #from_events_body } fn xml_name_matcher() -> ::xso::fromxml::XmlNameMatcher<'static> { #name_matcher } } }; #[cfg(feature = "minidom")] result.extend(quote! { impl ::core::convert::TryFrom<::xso::exports::minidom::Element> for #ident { type Error = ::xso::error::FromElementError; fn try_from(other: ::xso::exports::minidom::Element) -> ::core::result::Result { ::xso::try_from_element(other) } } }); if def.debug() { println!("{}", result); } Ok(result) } /// Macro to derive a `xso::FromXml` implementation on a type. /// /// The user-facing documentation for this macro lives in the `xso` crate. #[proc_macro_derive(FromXml, attributes(xml))] pub fn from_xml(input: RawTokenStream) -> RawTokenStream { // Shim wrapper around `from_xml_impl` which converts any errors into // actual compiler errors within the resulting token stream. let item = syn::parse_macro_input!(input as Item); match from_xml_impl(item) { Ok(v) => v.into(), Err(e) => e.into_compile_error().into(), } } /// Generate a `xso::AsXml` implementation for the given item, or fail with /// a proper compiler error. fn as_xml_impl(input: Item) -> Result { let (vis, ident, def) = parse_struct(input)?; let AsXmlParts { defs, as_xml_iter_body, item_iter_ty_lifetime, item_iter_ty, } = def.make_as_xml_iter(&vis)?; #[cfg_attr(not(feature = "minidom"), allow(unused_mut))] let mut result = quote! { #defs impl ::xso::AsXml for #ident { type ItemIter<#item_iter_ty_lifetime> = #item_iter_ty; fn as_xml_iter(&self) -> ::core::result::Result, ::xso::error::Error> { #as_xml_iter_body } } }; #[cfg(all(feature = "minidom", feature = "panicking-into-impl"))] result.extend(quote! { impl ::core::convert::From<#ident> for ::xso::exports::minidom::Element { fn from(other: #ident) -> Self { ::xso::transform(&other).expect("seamless conversion into minidom::Element") } } impl ::core::convert::From<&#ident> for ::xso::exports::minidom::Element { fn from(other: &#ident) -> Self { ::xso::transform(other).expect("seamless conversion into minidom::Element") } } }); #[cfg(all(feature = "minidom", not(feature = "panicking-into-impl")))] result.extend(quote! { impl ::core::convert::TryFrom<#ident> for ::xso::exports::minidom::Element { type Error = ::xso::error::Error; fn try_from(other: #ident) -> ::core::result::Result { ::xso::transform(&other) } } impl ::core::convert::TryFrom<&#ident> for ::xso::exports::minidom::Element { type Error = ::xso::error::Error; fn try_from(other: &#ident) -> ::core::result::Result { ::xso::transform(other) } } }); if def.debug() { println!("{}", result); } Ok(result) } /// Macro to derive a `xso::AsXml` implementation on a type. /// /// The user-facing documentation for this macro lives in the `xso` crate. #[proc_macro_derive(AsXml, attributes(xml))] pub fn as_xml(input: RawTokenStream) -> RawTokenStream { // Shim wrapper around `as_xml_impl` which converts any errors into // actual compiler errors within the resulting token stream. let item = syn::parse_macro_input!(input as Item); match as_xml_impl(item) { Ok(v) => v.into(), Err(e) => e.into_compile_error().into(), } } xso_proc-0.2.0/src/meta.rs000064400000000000000000001424601046102023000135510ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! # Parse Rust attributes //! //! This module is concerned with parsing attributes from the Rust "meta" //! annotations on structs, enums, enum variants and fields. use core::fmt; use core::hash::{Hash, Hasher}; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::{meta::ParseNestedMeta, spanned::Spanned, *}; use rxml_validation::NcName; use crate::error_message::PrettyPath; /// XML core namespace URI (for the `xml:` prefix) pub const XMLNS_XML: &str = "http://www.w3.org/XML/1998/namespace"; /// XML namespace URI (for the `xmlns:` prefix) pub const XMLNS_XMLNS: &str = "http://www.w3.org/2000/xmlns/"; macro_rules! reject_key { ($key:ident not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => { if let Some(ref $key) = $key { #[allow(unused_imports)] use syn::spanned::Spanned as _; return Err(Error::new( $key.span(), concat!( "`", stringify!($key), "` is not allowed on ", $not_allowed_on, $( " (only on ", $only_allowed_on, ")", )? ), )); } }; ($key:ident flag not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => { if let Flag::Present(ref $key) = $key { return Err(Error::new( *$key, concat!( "`", stringify!($key), "` is not allowed on ", $not_allowed_on, $( " (only on ", $only_allowed_on, ")", )? ), )); } }; ($key:ident vec not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => { if let Some(ref $key) = $key.first() { return Err(Error::new( $key.span(), concat!( "`", stringify!($key), "` is not allowed on ", $not_allowed_on, $( " (only on ", $only_allowed_on, ")", )? ), )); } }; } pub(crate) use reject_key; /// Value for the `#[xml(namespace = ..)]` attribute. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) enum NamespaceRef { /// The XML namespace is specified as a string literal. LitStr(LitStr), /// The XML namespace is specified as a path. Path(Path), } impl NamespaceRef { pub(crate) fn fudge(value: &str, span: Span) -> Self { Self::LitStr(LitStr::new(value, span)) } } impl fmt::Display for NamespaceRef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::LitStr(s) => write!(f, "{}", s.value()), Self::Path(ref p) => write!(f, "<{}>", PrettyPath(p)), } } } impl syn::parse::Parse for NamespaceRef { fn parse(input: syn::parse::ParseStream<'_>) -> Result { if input.peek(syn::LitStr) { Ok(Self::LitStr(input.parse()?)) } else { Ok(Self::Path(input.parse()?)) } } } impl quote::ToTokens for NamespaceRef { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::LitStr(ref lit) => lit.to_tokens(tokens), Self::Path(ref path) => path.to_tokens(tokens), } } } /// Value for the `#[xml(name = .. )]` attribute. #[derive(Debug, Clone)] pub(crate) enum NameRef { /// The XML name is specified as a string literal. Literal { /// The validated XML name. value: NcName, /// The span of the original [`syn::LitStr`]. span: Span, }, /// The XML name is specified as a path. Path(Path), } impl NameRef { pub(crate) fn fudge(value: NcName, span: Span) -> Self { Self::Literal { value, span } } } impl fmt::Display for NameRef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Literal { value, .. } => write!(f, "{}", value.as_str()), Self::Path(ref p) => write!(f, "<{}>", PrettyPath(p)), } } } impl Hash for NameRef { fn hash(&self, h: &mut H) { match self { Self::Literal { ref value, .. } => value.hash(h), Self::Path(ref path) => path.hash(h), } } } impl PartialEq for NameRef { fn eq(&self, other: &NameRef) -> bool { match self { Self::Literal { value: ref my_value, .. } => match other { Self::Literal { value: ref other_value, .. } => my_value == other_value, _ => false, }, Self::Path(ref my_path) => match other { Self::Path(ref other_path) => my_path == other_path, _ => false, }, } } } impl Eq for NameRef {} impl syn::parse::Parse for NameRef { fn parse(input: syn::parse::ParseStream<'_>) -> Result { if input.peek(syn::LitStr) { let s: LitStr = input.parse()?; let span = s.span(); match NcName::try_from(s.value()) { Ok(value) => Ok(Self::Literal { value, span }), Err(e) => Err(Error::new(span, format!("not a valid XML name: {}", e))), } } else { let p: Path = input.parse()?; Ok(Self::Path(p)) } } } impl quote::ToTokens for NameRef { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Literal { ref value, span } => { let span = *span; let value = value.as_str(); let value = quote_spanned! { span=> #value }; // SAFETY: self.0 is a known-good NcName, so converting it to an // NcNameStr is known to be safe. // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe // block as that would then in fact trip a `#[deny(unsafe_code)]` lint // at the use site of the macro. tokens.extend(quote! { unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) } }) } Self::Path(ref path) => path.to_tokens(tokens), } } } /// Represents the amount constraint used with child elements. /// /// Currently, this only supports "one" (literal `1`) or "any amount" (`..`). /// In the future, we might want to add support for any range pattern for /// `usize` and any positive integer literal. #[derive(Debug)] pub(crate) enum AmountConstraint { /// Equivalent to `1` #[allow(dead_code)] FixedSingle(Span), /// Equivalent to `..`. Any(Span), } impl syn::parse::Parse for AmountConstraint { fn parse(input: syn::parse::ParseStream<'_>) -> Result { if input.peek(LitInt) && !input.peek2(token::DotDot) && !input.peek2(token::DotDotEq) { let lit: LitInt = input.parse()?; let value: usize = lit.base10_parse()?; if value == 1 { Ok(Self::FixedSingle(lit.span())) } else { Err(Error::new(lit.span(), "only `1` and `..` are allowed here")) } } else { let p: PatRange = input.parse()?; if let Some(attr) = p.attrs.first() { return Err(Error::new_spanned(attr, "attributes not allowed here")); } if let Some(start) = p.start.as_ref() { return Err(Error::new_spanned( start, "only full ranges (`..`) are allowed here", )); } if let Some(end) = p.end.as_ref() { return Err(Error::new_spanned( end, "only full ranges (`..`) are allowed here", )); } Ok(Self::Any(p.span())) } } } /// Represents a boolean flag from a `#[xml(..)]` attribute meta. #[derive(Clone, Copy, Debug)] pub(crate) enum Flag { /// The flag is not set. Absent, /// The flag was set. Present( /// The span of the syntax element which enabled the flag. /// /// This is used to generate useful error messages by pointing at the /// specific place the flag was activated. #[allow(dead_code)] Span, ), } impl Flag { /// Return true if the flag is set, false otherwise. pub(crate) fn is_set(&self) -> bool { match self { Self::Absent => false, Self::Present(_) => true, } } /// Like `Option::take`, but for flags. pub(crate) fn take(&mut self) -> Self { let mut result = Flag::Absent; core::mem::swap(&mut result, self); result } } impl From for Flag { fn from(other: T) -> Flag { Flag::Present(other.span()) } } /// A pair of `namespace` and `name` keys. #[derive(Debug, Default, PartialEq, Eq, Hash)] pub(crate) struct QNameRef { /// The XML namespace supplied. pub(crate) namespace: Option, /// The XML name supplied. pub(crate) name: Option, } impl QNameRef { /// Attempt to incrementally parse this QNameRef. /// /// If `meta` contains either `namespace` or `name` keys, they are /// processed and either `Ok(None)` or an error is returned. /// /// If no matching key is found, `Ok(Some(meta))` is returned for further /// processing. fn parse_incremental_from_meta<'x>( &mut self, meta: ParseNestedMeta<'x>, ) -> Result>> { if meta.path.is_ident("name") { if self.name.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `name` key")); } let value = meta.value()?; let name_span = value.span(); let (new_namespace, new_name) = parse_prefixed_name(value)?; if let Some(new_namespace) = new_namespace { if let Some(namespace) = self.namespace.as_ref() { let mut error = Error::new( name_span, "cannot combine `namespace` key with prefixed `name`", ); error.combine(Error::new_spanned(namespace, "`namespace` was set here")); return Err(error); } self.namespace = Some(new_namespace); } self.name = Some(new_name); Ok(None) } else if meta.path.is_ident("namespace") { if self.namespace.is_some() { return Err(Error::new_spanned( meta.path, "duplicate `namespace` key or `name` key has prefix", )); } self.namespace = Some(meta.value()?.parse()?); Ok(None) } else { Ok(Some(meta)) } } } /// Identifies XML content to discard. #[derive(Debug)] pub(crate) enum DiscardSpec { /// `#[xml(discard(attribute..))]` Attribute { /// The span of the nested meta from which this was parsed. /// /// This is useful for error messages. span: Span, /// The value assigned to `namespace` and `name` fields inside /// `#[xml(discard(attribute(..)))]`, if any. qname: QNameRef, }, /// `#[xml(discard(text))]` Text { /// The span of the nested meta from which this was parsed. /// /// This is useful for error messages. span: Span, }, } impl DiscardSpec { pub(crate) fn span(&self) -> Span { match self { Self::Attribute { ref span, .. } => *span, Self::Text { ref span, .. } => *span, } } } impl TryFrom for DiscardSpec { type Error = syn::Error; fn try_from(other: XmlFieldMeta) -> Result { match other { XmlFieldMeta::Attribute { span, kind: AttributeKind::Generic(qname), default_, type_, codec, } => { reject_key!(default_ flag not on "discard specifications" only on "fields"); reject_key!(type_ not on "discard specifications" only on "fields"); reject_key!(codec not on "discard specifications" only on "fields"); Ok(Self::Attribute { span, qname }) } XmlFieldMeta::Text { span, type_, codec } => { reject_key!(type_ not on "discard specifications" only on "fields"); reject_key!(codec not on "discard specifications" only on "fields"); Ok(Self::Text { span }) } other => Err(Error::new( other.span(), "cannot discard this kind of child", )), } } } /// Wrapper around `QNameRef` which saves additional span information. #[derive(Debug)] pub(crate) struct SpannedQNameRef { /// The span which created the (potentially empty) ref. pub span: Span, /// The ref itself. pub qname: QNameRef, } impl SpannedQNameRef { pub(crate) fn span(&self) -> Span { self.span } } /// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum. #[derive(Debug)] pub(crate) struct XmlCompoundMeta { /// The span of the `#[xml(..)]` meta from which this was parsed. /// /// This is useful for error messages. pub(crate) span: Span, /// The value assigned to `namespace` and `name` fields inside /// `#[xml(..)]`, if any. pub(crate) qname: QNameRef, /// The debug flag. pub(crate) debug: Flag, /// The value assigned to `builder` inside `#[xml(..)]`, if any. pub(crate) builder: Option, /// The value assigned to `iterator` inside `#[xml(..)]`, if any. pub(crate) iterator: Option, /// The value assigned to `on_unknown_attribute` inside `#[xml(..)]`, if /// any. pub(crate) on_unknown_attribute: Option, /// The value assigned to `on_unknown_child` inside `#[xml(..)]`, if /// any. pub(crate) on_unknown_child: Option, /// The exhaustive flag. pub(crate) exhaustive: Flag, /// The transparent flag. pub(crate) transparent: Flag, /// Items to discard. pub(crate) discard: Vec, /// The value assigned to `deserialize_callback` inside `#[xml(..)]`, if any. pub(crate) deserialize_callback: Option, /// The value assigned to `attribute` inside `#[xml(..)]`, if any. pub(crate) attribute: Option, /// The value assigned to `value` inside `#[xml(..)]`, if any. pub(crate) value: Option, } impl XmlCompoundMeta { /// Parse the meta values from a `#[xml(..)]` attribute. /// /// Undefined options or options with incompatible values are rejected /// with an appropriate compile-time error. fn parse_from_attribute(attr: &Attribute) -> Result { let mut qname = QNameRef::default(); let mut builder = None; let mut iterator = None; let mut on_unknown_attribute = None; let mut on_unknown_child = None; let mut debug = Flag::Absent; let mut exhaustive = Flag::Absent; let mut transparent = Flag::Absent; let mut discard = Vec::new(); let mut deserialize_callback = None; let mut attribute = None; let mut value = None; attr.parse_nested_meta(|meta| { if meta.path.is_ident("debug") { if debug.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `debug` key")); } debug = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("builder") { if builder.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `builder` key")); } builder = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("iterator") { if iterator.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `iterator` key")); } iterator = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("on_unknown_attribute") { if on_unknown_attribute.is_some() { return Err(Error::new_spanned( meta.path, "duplicate `on_unknown_attribute` key", )); } on_unknown_attribute = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("on_unknown_child") { if on_unknown_child.is_some() { return Err(Error::new_spanned( meta.path, "duplicate `on_unknown_child` key", )); } on_unknown_child = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("exhaustive") { if exhaustive.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `exhaustive` key")); } exhaustive = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("transparent") { if transparent.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `transparent` key")); } transparent = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("discard") { meta.parse_nested_meta(|meta| { discard.push(XmlFieldMeta::parse_from_meta(meta)?.try_into()?); Ok(()) })?; Ok(()) } else if meta.path.is_ident("deserialize_callback") { if deserialize_callback.is_some() { return Err(Error::new_spanned( meta.path, "duplicate `deserialize_callback` key", )); } deserialize_callback = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("attribute") { if attribute.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `attribute` key")); } let span = meta.path.span(); let qname = if meta.input.peek(Token![=]) { let (namespace, name) = parse_prefixed_name(meta.value()?)?; QNameRef { name: Some(name), namespace, } } else { let mut qname = QNameRef::default(); meta.parse_nested_meta(|meta| { match qname.parse_incremental_from_meta(meta)? { None => Ok(()), Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")), } })?; qname }; attribute = Some(SpannedQNameRef { qname, span }); Ok(()) } else if meta.path.is_ident("value") { if value.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `value` key")); } value = Some(meta.value()?.parse()?); Ok(()) } else { match qname.parse_incremental_from_meta(meta)? { None => Ok(()), Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")), } } })?; Ok(Self { span: attr.span(), qname, debug, builder, iterator, on_unknown_attribute, on_unknown_child, exhaustive, transparent, discard, deserialize_callback, attribute, value, }) } /// Search through `attrs` for a single `#[xml(..)]` attribute and parse /// it. /// /// Undefined options or options with incompatible values are rejected /// with an appropriate compile-time error. /// /// If more than one `#[xml(..)]` attribute is found, an error is /// emitted. /// /// If no `#[xml(..)]` attribute is found, `None` is returned. pub(crate) fn try_parse_from_attributes(attrs: &[Attribute]) -> Result> { let mut result = None; for attr in attrs { if !attr.path().is_ident("xml") { continue; } if result.is_some() { return Err(syn::Error::new_spanned( attr.path(), "only one #[xml(..)] per struct or enum variant allowed", )); } result = Some(Self::parse_from_attribute(attr)?); } Ok(result) } /// Search through `attrs` for a single `#[xml(..)]` attribute and parse /// it. /// /// Undefined options or options with incompatible values are rejected /// with an appropriate compile-time error. /// /// If more than one or no `#[xml(..)]` attribute is found, an error is /// emitted. pub(crate) fn parse_from_attributes(attrs: &[Attribute]) -> Result { match Self::try_parse_from_attributes(attrs)? { Some(v) => Ok(v), None => Err(syn::Error::new( Span::call_site(), "#[xml(..)] attribute required on struct or enum variant", )), } } } /// Return true if the tokens the cursor points at are a valid type path /// prefix. /// /// This does not advance the parse stream. /// /// If the tokens *do* look like a type path, a Span which points at the first /// `<` encountered is returned. This can be used for a helpful error message /// in case parsing the type path does then fail. fn maybe_type_path(p: parse::ParseStream<'_>) -> (bool, Option) { // ParseStream cursors do not advance the stream, but they are also rather // unwieldy to use. Prepare for a lot of `let .. = ..`. let cursor = if p.peek(token::PathSep) { // If we have a path separator, we need to skip that initially. We // do this by skipping two punctuations. We use unwrap() here because // we already know for sure that we see two punctuation items (because // of the peek). p.cursor().punct().unwrap().1.punct().unwrap().1 } else { // No `::` initially, so we just take what we have. p.cursor() }; // Now we loop over `$ident::` segments. If we find anything but a `:` // after the ident, we exit. Depending on *what* we find, we either exit // true or false, but see for yourself. let mut cursor = cursor; loop { // Here we look for the identifier, but we do not care for its // contents. let Some((_, new_cursor)) = cursor.ident() else { return (false, None); }; cursor = new_cursor; // Now we see what actually follows the ident (it must be punctuation // for it to be a type path...) let Some((punct, new_cursor)) = cursor.punct() else { return (false, None); }; cursor = new_cursor; match punct.as_char() { // Looks like a `foo<..`, we treat that as a type path for the // reasons stated in [`parse_codec_expr`]'s doc. '<' => return (true, Some(punct.span())), // Continue looking ahead: looks like a path separator. ':' => (), // Anything else (such as `,` (separating another argument most // likely), or `.` (a method call?)) we treat as "not a type // path". _ => return (false, None), } // If we are here, we saw a `:`. Look for the second one. let Some((punct, new_cursor)) = cursor.punct() else { return (false, None); }; cursor = new_cursor; if punct.as_char() != ':' { // If it is not another `:`, it cannot be a type path. return (false, None); } // And round and round and round it goes. // We will terminate eventually because the cursor will return None // on any of the lookups because parse streams are (hopefully!) // finite. Most likely, we'll however encounter a `<` or other non-`:` // punctuation first. } } /// Parse expressions passed to `codec`. /// /// Those will generally be paths to unit type constructors (such as `Foo`) /// or references to static values or chains of function calls. /// /// In the case of unit type constructors for generic types, users may type /// for example `FixedHex<20>`, thinking they are writing a type path. However, /// while `FixedHex<20>` is indeed a valid type path, it is not a valid /// expression for a unit type constructor. Instead it is parsed as /// `FixedHex < 20` and then a syntax error. /// /// We however know that `Foo < Bar` is never a valid expression for a type. /// Thus, we can be smart about this and inject the `::` at the right place /// automatically. fn parse_codec_expr(p: parse::ParseStream<'_>) -> Result<(Expr, Option)> { let (maybe_type_path, punct_span) = maybe_type_path(p); if maybe_type_path { let helpful_error = punct_span.map(|span| Error::new(span, "help: try inserting a `::` before this `<`")); let mut type_path: TypePath = match p.parse() { Ok(v) => v, Err(mut e) => match helpful_error { Some(help) => { e.combine(help); return Err(e); } None => return Err(e), }, }; // We got a type path -- so we now inject the `::` before any `<` as // needed. for segment in type_path.path.segments.iter_mut() { if let PathArguments::AngleBracketed(ref mut arguments) = segment.arguments { let span = arguments.span(); arguments.colon2_token.get_or_insert(token::PathSep { spans: [span, span], }); } } Ok(( Expr::Path(ExprPath { attrs: Vec::new(), qself: type_path.qself, path: type_path.path, }), helpful_error, )) } else { p.parse().map(|x| (x, None)) } } /// Parse an XML name while resolving built-in namespace prefixes. fn parse_prefixed_name( value: syn::parse::ParseStream<'_>, ) -> Result<(Option, NameRef)> { if !value.peek(LitStr) { // if we don't have a string literal next, we delegate to the default // `NameRef` parser. return Ok((None, value.parse()?)); } let name: LitStr = value.parse()?; let name_span = name.span(); let (prefix, name) = match name .value() .try_into() .and_then(|name: rxml_validation::Name| name.split_name()) { Ok(v) => v, Err(e) => { return Err(Error::new( name_span, format!("not a valid XML name: {}", e), )) } }; let name = NameRef::Literal { value: name, span: name_span, }; if let Some(prefix) = prefix { let namespace_uri = match prefix.as_str() { "xml" => XMLNS_XML, "xmlns" => XMLNS_XMLNS, other => return Err(Error::new( name_span, format!("prefix `{}` is not a built-in prefix and cannot be used. specify the desired namespace using the `namespace` key instead.", other) )), }; Ok((Some(NamespaceRef::fudge(namespace_uri, name_span)), name)) } else { Ok((None, name)) } } /// XML attribute subtypes for `#[xml(attribute)]` and `#[xml(lang)]`. #[derive(Debug)] pub(crate) enum AttributeKind { /// Any generic attribute (`#[xml(attribute)]`). Generic(QNameRef), /// The special `xml:lang` attribute (`#[xml(lang)]`). XmlLang, } /// Contents of an `#[xml(..)]` attribute on a struct or enum variant member. #[derive(Debug)] pub(crate) enum XmlFieldMeta { /// `#[xml(attribute)]`, `#[xml(attribute = ..)]` or `#[xml(attribute(..))]`, `#[xml(lang)]` Attribute { /// The span of the `#[xml(attribute)]` meta from which this was parsed. /// /// This is useful for error messages. span: Span, /// Attribute subtype (normal vs. `xml:lang`). kind: AttributeKind, /// The `default` flag. default_: Flag, /// An explicit type override, only usable within extracts. type_: Option, /// The path to the optional codec type. codec: Option, }, /// `#[xml(text)]` Text { /// The span of the `#[xml(text)]` meta from which this was parsed. /// /// This is useful for error messages. span: Span, /// The path to the optional codec type. codec: Option, /// An explicit type override, only usable within extracts. type_: Option, }, /// `#[xml(child)` Child { /// The span of the `#[xml(child)]` meta from which this was parsed. /// /// This is useful for error messages. span: Span, /// The `default` flag. default_: Flag, /// The `n` flag. amount: Option, }, /// `#[xml(extract)] Extract { /// The span of the `#[xml(extract)]` meta from which this was parsed. /// /// This is useful for error messages. span: Span, /// The namespace/name keys. qname: QNameRef, /// The `n` flag. amount: Option, /// The `default` flag. default_: Flag, /// The `fields` nested meta. fields: Vec, /// The `on_unknown_attribute` value. on_unknown_attribute: Option, /// The `on_unknown_child` value. on_unknown_child: Option, }, /// `#[xml(element)]` Element { /// The span of the `#[xml(element)]` meta from which this was parsed. /// /// This is useful for error messages. span: Span, /// The `default` flag. default_: Flag, /// The `n` flag. amount: Option, }, /// `#[xml(flag)] Flag { /// The span of the `#[xml(flag)]` meta from which this was parsed. /// /// This is useful for error messages. span: Span, /// The namespace/name keys. qname: QNameRef, }, } impl XmlFieldMeta { /// Parse a `#[xml(attribute(..))]` meta. /// /// That meta can have three distinct syntax styles: /// - argument-less: `#[xml(attribute)]` /// - shorthand: `#[xml(attribute = ..)]` /// - full: `#[xml(attribute(..))]` fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result { if meta.input.peek(Token![=]) { // shorthand syntax let (namespace, name) = parse_prefixed_name(meta.value()?)?; Ok(Self::Attribute { span: meta.path.span(), kind: AttributeKind::Generic(QNameRef { name: Some(name), namespace, }), default_: Flag::Absent, type_: None, codec: None, }) } else if meta.input.peek(syn::token::Paren) { // full syntax let mut qname = QNameRef::default(); let mut default_ = Flag::Absent; let mut type_ = None; let mut codec = None; meta.parse_nested_meta(|meta| { if meta.path.is_ident("default") { if default_.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `default` key")); } default_ = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("type_") { if type_.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `type_` key")); } type_ = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("codec") { if codec.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `codec` key")); } let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?; // See the comment at the top of text_from_meta() below for why we // do this. let lookahead = meta.input.lookahead1(); if !lookahead.peek(Token![,]) && !meta.input.is_empty() { if let Some(helpful_error) = helpful_error { let mut e = lookahead.error(); e.combine(helpful_error); return Err(e); } } codec = Some(new_codec); Ok(()) } else { match qname.parse_incremental_from_meta(meta)? { None => Ok(()), Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")), } } })?; Ok(Self::Attribute { span: meta.path.span(), kind: AttributeKind::Generic(qname), default_, type_, codec, }) } else { // argument-less syntax Ok(Self::Attribute { span: meta.path.span(), kind: AttributeKind::Generic(QNameRef::default()), default_: Flag::Absent, type_: None, codec: None, }) } } /// Parse a `#[xml(text)]` meta. fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result { if meta.input.peek(Token![=]) { let (codec, helpful_error) = parse_codec_expr(meta.value()?)?; // A meta value can only be followed by either a `,`, or the end // of the parse stream (because of the delimited group ending). // Hence we check we are there. And if we are *not* there, we emit // an error straight away, with the helpful addition from the // `parse_codec_expr` if we have it. // // If we do not do this, the user gets a rather confusing // "expected `,`" message if the `maybe_type_path` guess was // wrong. let lookahead = meta.input.lookahead1(); if !lookahead.peek(Token![,]) && !meta.input.is_empty() { if let Some(helpful_error) = helpful_error { let mut e = lookahead.error(); e.combine(helpful_error); return Err(e); } } Ok(Self::Text { span: meta.path.span(), type_: None, codec: Some(codec), }) } else if meta.input.peek(syn::token::Paren) { let mut codec: Option = None; let mut type_: Option = None; meta.parse_nested_meta(|meta| { if meta.path.is_ident("codec") { if codec.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `codec` key")); } let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?; // See above (at the top-ish of this function) for why we // do this. let lookahead = meta.input.lookahead1(); if !lookahead.peek(Token![,]) && !meta.input.is_empty() { if let Some(helpful_error) = helpful_error { let mut e = lookahead.error(); e.combine(helpful_error); return Err(e); } } codec = Some(new_codec); Ok(()) } else if meta.path.is_ident("type_") { if type_.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `type_` key")); } type_ = Some(meta.value()?.parse()?); Ok(()) } else { Err(Error::new_spanned(meta.path, "unsupported key")) } })?; Ok(Self::Text { span: meta.path.span(), type_, codec, }) } else { Ok(Self::Text { span: meta.path.span(), type_: None, codec: None, }) } } /// Parse a `#[xml(child)]` meta. fn child_from_meta(meta: ParseNestedMeta<'_>) -> Result { if meta.input.peek(syn::token::Paren) { let mut default_ = Flag::Absent; let mut amount = None; meta.parse_nested_meta(|meta| { if meta.path.is_ident("default") { if default_.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `default` key")); } default_ = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("n") { if amount.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `n` key")); } amount = Some(meta.value()?.parse()?); Ok(()) } else { Err(Error::new_spanned(meta.path, "unsupported key")) } })?; Ok(Self::Child { span: meta.path.span(), default_, amount, }) } else { Ok(Self::Child { span: meta.path.span(), default_: Flag::Absent, amount: None, }) } } /// Parse a `#[xml(extract)]` meta. fn extract_from_meta(meta: ParseNestedMeta<'_>) -> Result { let mut qname = QNameRef::default(); let mut fields = None; let mut amount = None; let mut on_unknown_attribute = None; let mut on_unknown_child = None; let mut default_ = Flag::Absent; meta.parse_nested_meta(|meta| { if meta.path.is_ident("default") { if default_.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `default` key")); } default_ = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("fields") { if let Some((fields_span, _)) = fields.as_ref() { let mut error = Error::new_spanned(meta.path, "duplicate `fields` meta"); error.combine(Error::new(*fields_span, "previous `fields` meta was here")); return Err(error); } let mut new_fields = Vec::new(); meta.parse_nested_meta(|meta| { new_fields.push(XmlFieldMeta::parse_from_meta(meta)?); Ok(()) })?; fields = Some((meta.path.span(), new_fields)); Ok(()) } else if meta.path.is_ident("n") { if amount.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `n` key")); } amount = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("on_unknown_attribute") { if on_unknown_attribute.is_some() { return Err(Error::new_spanned( meta.path, "duplicate `on_unknown_attribute` key", )); } on_unknown_attribute = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("on_unknown_child") { if on_unknown_child.is_some() { return Err(Error::new_spanned( meta.path, "duplicate `on_unknown_child` key", )); } on_unknown_child = Some(meta.value()?.parse()?); Ok(()) } else { match qname.parse_incremental_from_meta(meta)? { None => Ok(()), Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")), } } })?; let fields = fields.map(|(_, x)| x).unwrap_or_else(Vec::new); Ok(Self::Extract { span: meta.path.span(), default_, qname, fields, amount, on_unknown_attribute, on_unknown_child, }) } /// Parse a `#[xml(element)]` meta. fn element_from_meta(meta: ParseNestedMeta<'_>) -> Result { let mut amount = None; let mut default_ = Flag::Absent; if meta.input.peek(syn::token::Paren) { meta.parse_nested_meta(|meta| { if meta.path.is_ident("default") { if default_.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `default` key")); } default_ = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("n") { if amount.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `n` key")); } amount = Some(meta.value()?.parse()?); Ok(()) } else { Err(Error::new_spanned(meta.path, "unsupported key")) } })?; } Ok(Self::Element { span: meta.path.span(), default_, amount, }) } /// Parse a `#[xml(flag)]` meta. fn flag_from_meta(meta: ParseNestedMeta<'_>) -> Result { let mut qname = QNameRef::default(); if meta.input.peek(syn::token::Paren) { meta.parse_nested_meta(|meta| match qname.parse_incremental_from_meta(meta)? { None => Ok(()), Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")), })?; } Ok(Self::Flag { span: meta.path.span(), qname, }) } /// Parse a `#[xml(lang)]` meta. fn lang_from_meta(meta: ParseNestedMeta<'_>) -> Result { let mut default_ = Flag::Absent; let mut type_ = None; let mut codec = None; if meta.input.peek(syn::token::Paren) { meta.parse_nested_meta(|meta| { if meta.path.is_ident("default") { if default_.is_set() { return Err(Error::new_spanned(meta.path, "duplicate `default` key")); } default_ = (&meta.path).into(); Ok(()) } else if meta.path.is_ident("type_") { if type_.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `type_` key")); } type_ = Some(meta.value()?.parse()?); Ok(()) } else if meta.path.is_ident("codec") { if codec.is_some() { return Err(Error::new_spanned(meta.path, "duplicate `codec` key")); } let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?; // See the comment at the top of text_from_meta() below for why we // do this. let lookahead = meta.input.lookahead1(); if !lookahead.peek(Token![,]) && !meta.input.is_empty() { if let Some(helpful_error) = helpful_error { let mut e = lookahead.error(); e.combine(helpful_error); return Err(e); } } codec = Some(new_codec); Ok(()) } else { Err(Error::new_spanned(meta.path, "unsupported key")) } })?; } Ok(Self::Attribute { span: meta.path.span(), kind: AttributeKind::XmlLang, default_, type_, codec, }) } /// Parse [`Self`] from a nestd meta, switching on the identifier /// of that nested meta. fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result { if meta.path.is_ident("attribute") { Self::attribute_from_meta(meta) } else if meta.path.is_ident("text") { Self::text_from_meta(meta) } else if meta.path.is_ident("child") { Self::child_from_meta(meta) } else if meta.path.is_ident("extract") { Self::extract_from_meta(meta) } else if meta.path.is_ident("element") { Self::element_from_meta(meta) } else if meta.path.is_ident("flag") { Self::flag_from_meta(meta) } else if meta.path.is_ident("lang") { Self::lang_from_meta(meta) } else { Err(Error::new_spanned(meta.path, "unsupported field meta")) } } /// Parse an `#[xml(..)]` meta on a field. /// /// This switches based on the first identifier within the `#[xml(..)]` /// meta and generates an enum variant accordingly. /// /// Only a single nested meta is allowed; more than one will be /// rejected with an appropriate compile-time error. /// /// If no meta is contained at all, a compile-time error is generated. /// /// Undefined options or options with incompatible values are rejected /// with an appropriate compile-time error. pub(crate) fn parse_from_attribute(attr: &Attribute) -> Result { let mut result: Option = None; attr.parse_nested_meta(|meta| { if result.is_some() { return Err(Error::new_spanned( meta.path, "multiple field type specifiers are not supported", )); } result = Some(Self::parse_from_meta(meta)?); Ok(()) })?; if let Some(result) = result { Ok(result) } else { Err(Error::new_spanned( attr, "missing field type specifier within `#[xml(..)]`", )) } } /// Find and parse a `#[xml(..)]` meta on a field. /// /// This invokes [`Self::parse_from_attribute`] internally on the first /// encountered `#[xml(..)]` meta. /// /// If not exactly one `#[xml(..)]` meta is encountered, an error is /// returned. The error is spanned to `err_span`. pub(crate) fn parse_from_attributes(attrs: &[Attribute], err_span: &Span) -> Result { let mut result: Option = None; for attr in attrs { if !attr.path().is_ident("xml") { continue; } if result.is_some() { return Err(Error::new_spanned( attr, "only one #[xml(..)] attribute per field allowed.", )); } result = Some(Self::parse_from_attribute(attr)?); } if let Some(result) = result { Ok(result) } else { Err(Error::new(*err_span, "missing #[xml(..)] meta on field")) } } /// Return a span which points at the meta which constructed this /// XmlFieldMeta. pub(crate) fn span(&self) -> Span { match self { Self::Attribute { ref span, .. } => *span, Self::Child { ref span, .. } => *span, Self::Text { ref span, .. } => *span, Self::Extract { ref span, .. } => *span, Self::Element { ref span, .. } => *span, Self::Flag { ref span, .. } => *span, } } /// Extract an explicit type specification if it exists. pub(crate) fn take_type(&mut self) -> Option { match self { Self::Attribute { ref mut type_, .. } => type_.take(), Self::Text { ref mut type_, .. } => type_.take(), _ => None, } } } xso_proc-0.2.0/src/scope.rs000064400000000000000000000137141046102023000137330ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Identifiers used within generated code. use proc_macro2::Span; use syn::*; use crate::types::ref_ty; /// Container struct for various identifiers used throughout the parser code. /// /// This struct is passed around from the [`crate::compound::Compound`] /// downward to the code generators in order to ensure that everyone is on the /// same page about which identifiers are used for what. /// /// The recommended usage is to bind the names which are needed into the local /// scope like this: /// /// ```text /// # let scope = FromEventsScope::new(); /// let FromEventsScope { /// ref attrs, /// .. /// } = scope; /// ``` pub(crate) struct FromEventsScope { /// Accesses the `AttrMap` from code in /// [`crate::field::FieldBuilderPart::Init`]. pub(crate) attrs: Ident, /// Accesses the `String` of a `rxml::Event::Text` event from code in /// [`crate::field::FieldBuilderPart::Text`]. pub(crate) text: Ident, /// Accesses the builder data during parsing. /// /// This should not be used directly outside [`crate::compound`]. Most of /// the time, using [`Self::access_field`] is the correct way to access /// the builder data. pub(crate) builder_data_ident: Ident, /// Accesses the result produced by a nested state's builder type. /// /// See [`crate::field::FieldBuilderPart::Nested`]. pub(crate) substate_data: Ident, /// Accesses the result produced by a nested state's builder type. /// /// See [`crate::field::FieldBuilderPart::Nested`]. pub(crate) substate_result: Ident, /// Prefix which should be used for any types which are declared, to /// ensure they don't collide with other names. pub(crate) type_prefix: Ident, } impl FromEventsScope { /// Create a fresh scope with all necessary identifiers. pub(crate) fn new(type_prefix: Ident) -> Self { // Sadly, `Ident::new` is not `const`, so we have to create even the // well-known identifiers from scratch all the time. Self { attrs: Ident::new("attrs", Span::call_site()), text: Ident::new("__xso_proc_macro_text_data", Span::call_site()), builder_data_ident: Ident::new("__xso_proc_macro_builder_data", Span::call_site()), substate_data: Ident::new("__xso_proc_macro_substate_data", Span::call_site()), substate_result: Ident::new("__xso_proc_macro_substate_result", Span::call_site()), type_prefix, } } /// Generate an expression which accesses the temporary value for the /// given `member` during parsing. pub(crate) fn access_field(&self, member: &Member) -> Expr { Expr::Field(ExprField { attrs: Vec::new(), base: Box::new(Expr::Path(ExprPath { attrs: Vec::new(), qself: None, path: self.builder_data_ident.clone().into(), })), dot_token: syn::token::Dot { spans: [Span::call_site()], }, member: Member::Named(mangle_member(member)), }) } /// Generate an ident with proper scope and span from the type prefix and /// the given member and actual type name. /// /// Due to being merged with the type prefix of this scope and the given /// member, this type name is guaranteed to be unique for unique values of /// `name`. pub(crate) fn make_member_type_name(&self, member: &Member, name: &str) -> Ident { quote::format_ident!( "{}Member{}{}", self.type_prefix, match member { Member::Named(ref ident) => ident.to_string(), Member::Unnamed(Index { index, .. }) => index.to_string(), }, name, ) } } /// Container struct for various identifiers used throughout the generator /// code. /// /// This struct is passed around from the [`crate::compound::Compound`] /// downward to the code generators in order to ensure that everyone is on the /// same page about which identifiers are used for what. /// /// See [`FromEventsScope`] for recommendations on the usage. pub(crate) struct AsItemsScope { /// Lifetime for data borrowed by the implementation. pub(crate) lifetime: Lifetime, /// Prefix which should be used for any types which are declared, to /// ensure they don't collide with other names. pub(crate) type_prefix: Ident, } impl AsItemsScope { /// Create a fresh scope with all necessary identifiers. pub(crate) fn new(lifetime: &Lifetime, type_prefix: Ident) -> Self { Self { lifetime: lifetime.clone(), type_prefix, } } /// Create a reference to `ty`, borrowed for the lifetime of the AsXml /// impl. pub(crate) fn borrow(&self, ty: Type) -> Type { ref_ty(ty, self.lifetime.clone()) } /// Generate an ident with proper scope and span from the type prefix and /// the given member and actual type name. /// /// Due to being merged with the type prefix of this scope and the given /// member, this type name is guaranteed to be unique for unique values of /// `name`. pub(crate) fn make_member_type_name(&self, member: &Member, name: &str) -> Ident { quote::format_ident!( "{}Member{}{}", self.type_prefix, match member { Member::Named(ref ident) => ident.to_string(), Member::Unnamed(Index { index, .. }) => index.to_string(), }, name, ) } } pub(crate) fn mangle_member(member: &Member) -> Ident { match member { Member::Named(member) => quote::format_ident!("f{}", member), Member::Unnamed(member) => quote::format_ident!("f_u{}", member.index), } } xso_proc-0.2.0/src/state.rs000064400000000000000000001035371046102023000137450ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! State machines for parsing and serialising of structs and enums. use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::*; /// A single state in a parser or serializer state machine. #[derive(Clone)] pub(crate) struct State { /// Name of the state enum variant for this state. name: Ident, /// Declaration of members of the state enum in this state. decl: TokenStream, /// Destructuring of members of the state enum in this state. destructure: TokenStream, /// Right-hand-side of the match arm for this state. advance_body: TokenStream, /// If set, that identifier will be bound mutably. uses_mut: Option, } impl State { /// Create a new state with the a builder data field. /// /// This is a convenience wrapper around `new()` and `add_field()`. This /// wrapper, or its equivalent, **must** be used for states used in /// [`FromEventsStateMachine`] state machines, as those expect that the /// first field is the builder data at render time. pub(crate) fn new_with_builder( name: Ident, builder_data_ident: &Ident, builder_data_ty: &Type, ) -> Self { let mut result = Self::new(name); result.add_field(builder_data_ident, builder_data_ty); result } /// Create a new, empty state. /// /// Note that an empty state will generate invalid code. At the very /// least, a body must be added using [`Self::set_impl`] or /// [`Self::with_impl`]. The various state machines may also have /// additional requirements. pub(crate) fn new(name: Ident) -> Self { Self { name, decl: TokenStream::default(), destructure: TokenStream::default(), advance_body: TokenStream::default(), uses_mut: None, } } /// Add a field to this state's data. /// /// - `name` is the name under which the data will be accessible in the /// state's implementation. /// - `ty` must be the data field's type. pub(crate) fn add_field(&mut self, name: &Ident, ty: &Type) { self.decl.extend(quote! { #name: #ty, }); self.destructure.extend(quote! { #name, }); } /// Modify the state to include another field and return the modified /// state. /// /// This is a consume-and-return-style version of [`Self::add_field`]. pub(crate) fn with_field(mut self, name: &Ident, ty: &Type) -> Self { self.add_field(name, ty); self } /// Set the `advance` implementation of this state. /// /// `body` must be the body of the right hand side of the match arm for /// the `advance` implementation of the state machine. /// /// See [`FromEventsStateMachine::advance_match_arms`] and /// [`AsItemsSubmachine::compile`] for the respective /// requirements on the implementations. pub(crate) fn with_impl(mut self, body: TokenStream) -> Self { self.advance_body = body; self } /// Override the current `advance` implementation of this state. /// /// This is an in-place version of [`Self::with_impl`]. pub(crate) fn set_impl(&mut self, body: TokenStream) { self.advance_body = body; } /// Modify the state to mark the given field as mutable and return the /// modified state. pub(crate) fn with_mut(mut self, ident: &Ident) -> Self { assert!(self.uses_mut.is_none()); self.uses_mut = Some(ident.clone()); self } } /// A partial [`FromEventsStateMachine`] which only covers the builder for a /// single compound. /// /// See [`FromEventsStateMachine`] for more information on the state machines /// in general. pub(crate) struct FromEventsSubmachine { /// Additional items necessary for the statemachine. pub(crate) defs: TokenStream, /// States and state transition implementations. pub(crate) states: Vec, /// Initializer snippet. /// /// The structure needed here depends on the /// [`FromEventsStateMachine::mode`] field of the final state machine this /// submachine is compiled to or merged into. pub(crate) init: TokenStream, } impl FromEventsSubmachine { /// Convert a partial state machine into a full state machine. /// /// This converts the abstract [`State`] items into token /// streams for the respective parts of the state machine (the state /// definitions and the match arms), rendering them effectively immutable. pub(crate) fn compile(self) -> FromEventsStateMachine { let mut state_defs = TokenStream::default(); let mut advance_match_arms = TokenStream::default(); for state in self.states { let State { name, decl, destructure, advance_body, uses_mut, } = state; state_defs.extend(quote! { #name { #decl }, }); let binding = if let Some(uses_mut) = uses_mut.as_ref() { quote! { let mut #uses_mut = #uses_mut; } } else { TokenStream::default() }; // XXX: nasty hack, but works: the first member of the enum always // exists and it always is the builder data, which we always need // mutably available. So we can just prefix the destructuring // token stream with `mut` to make that first member mutable. advance_match_arms.extend(quote! { Self::#name { mut #destructure } => { #binding #advance_body } }); } FromEventsStateMachine { defs: self.defs, state_defs, advance_match_arms, variants: vec![FromEventsEntryPoint { init: self.init }], mode: FromEventsMatchMode::default(), pre_init: TokenStream::default(), fallback: None, } } /// Update the [`init`][`Self::init`] field in-place. /// /// The function will receive a reference to the current `init` value, /// allowing to create "wrappers" around that existing code. pub(crate) fn with_augmented_init TokenStream>( mut self, f: F, ) -> Self { let new_init = f(&self.init); self.init = new_init; self } } /// A partial [`AsItemsStateMachine`] which only covers the builder for a /// single compound. /// /// See [`AsItemsStateMachine`] for more information on the state machines /// in general. pub(crate) struct AsItemsSubmachine { /// Additional items necessary for the statemachine. pub(crate) defs: TokenStream, /// States and state transition implementations. pub(crate) states: Vec, /// A pattern match which destructures the target type into its parts, for /// use by `init`. pub(crate) destructure: TokenStream, /// An expression which uses the names bound in `destructure` to create a /// an instance of the state enum. /// /// The state enum type is available as `Self` in that context. pub(crate) init: TokenStream, } impl AsItemsSubmachine { /// Convert a partial state machine into a full state machine. /// /// This converts the abstract [`State`] items into token /// streams for the respective parts of the state machine (the state /// definitions and the match arms), rendering them effectively immutable. /// /// This requires that the [`State::advance_body`] token streams evaluate /// to an `Option`. If it evaluates to `Some(.)`, that is /// emitted from the iterator. If it evaluates to `None`, the `advance` /// implementation is called again. /// /// Each state implementation is augmented to also enter the next state, /// causing the iterator to terminate eventually. pub(crate) fn compile(self) -> AsItemsStateMachine { let mut state_defs = TokenStream::default(); let mut advance_match_arms = TokenStream::default(); for (i, state) in self.states.iter().enumerate() { let State { ref name, ref decl, ref destructure, ref advance_body, ref uses_mut, } = state; let footer = match self.states.get(i + 1) { Some(State { name: ref next_name, destructure: ref construct_next, .. }) => { quote! { ::core::result::Result::Ok((::core::option::Option::Some(Self::#next_name { #construct_next }), item)) } } // final state -> exit the state machine None => { quote! { ::core::result::Result::Ok((::core::option::Option::None, item)) } } }; state_defs.extend(quote! { #name { #decl }, }); if let Some(uses_mut) = uses_mut.as_ref() { // the variant is non-consuming, meaning it can be called // multiple times and it uses the identifier in `uses_mut` // mutably. // the transition is only triggered when it emits a None // item // (we cannot do this at the place the `State` is constructed, // because we don't yet know all its fields then; it must be // done here.) advance_match_arms.extend(quote! { Self::#name { #destructure } => { let mut #uses_mut = #uses_mut; match #advance_body { ::core::option::Option::Some(item) => { ::core::result::Result::Ok((::core::option::Option::Some(Self::#name { #destructure }), ::core::option::Option::Some(item))) }, item => { #footer }, } } }); } else { // if the variant is consuming, it can only be called once. // it may or may not emit an event, but the transition is // always triggered advance_match_arms.extend(quote! { Self::#name { #destructure } => { let item = #advance_body; #footer } }); } } AsItemsStateMachine { defs: self.defs, state_defs, advance_match_arms, variants: vec![AsItemsEntryPoint { init: self.init, destructure: self.destructure, }], } } /// Insert a state into the element header state chain. /// /// It is not possible to add, remove or access a field from within this /// state; all data from the first state will be passed through this state /// unaltered. /// /// `name` must be the unambiguous name of the state. /// /// `item` must be an expression which generates the `Item` to be emitted /// from this state. pub(crate) fn with_extra_header_state(mut self, name: Ident, item: TokenStream) -> Self { // We always have at least three states: ElementHeadStart, // ElementHeadEnd, ElementFoot. assert!(self.states.len() >= 3); // We then use the first state after the ElementHeadStart as template. // It does not really matter which one we use, as long as we do not // use ElementHeadStart (because that would be before the element // header) or any state after the ElementHeadEnd. let mut template = self.states[1].clone(); // Override all fields but decl and destructure. Those define the data // which is carried by the State, and we must keep that intact to pass // the data through the state machine unaltered. template.name = name; template.uses_mut = None; template.advance_body = quote! { ::core::option::Option::Some(#item) }; self.states.insert(1, template); self } /// Update the [`init`][`Self::init`] field in-place. /// /// The function will receive a reference to the current `init` value, /// allowing to create "wrappers" around that existing code. pub(crate) fn with_augmented_init TokenStream>( mut self, f: F, ) -> Self { let new_init = f(&self.init); self.init = new_init; self } } /// Container for a single entrypoint into a [`FromEventsStateMachine`]. pub(crate) struct FromEventsEntryPoint { pub(crate) init: TokenStream, } /// A single variant's entrypoint into the event iterator. pub(crate) struct AsItemsEntryPoint { /// A pattern match which destructures the target type into its parts, for /// use by `init`. destructure: TokenStream, /// An expression which uses the names bound in `destructure` to create a /// an instance of the state enum. /// /// The state enum type is available as `Self` in that context. init: TokenStream, } /// Control how a state machine attempts to match elements. #[derive(Default)] pub(crate) enum FromEventsMatchMode { /// Element matching works by chaining the individual submachine's init /// code. /// /// Each submachine has to provide an expression which evaluates to a /// `Result<#state_ty_ident, xso::FromEventsError>` in its /// [`FromEventsSubmachine::init`] code. /// /// If the expression evaluates to /// `Err(FromEventsError::Mismatch { .. })`, the next submachine in merge /// order is tried. If no further submachines exist, the /// [`fallback`][`FromEventsStateMachine::fallback`] code /// is injected (or the mismatch is returned if no fallback code exists.) /// /// This is the default mode. #[default] Chained, /// Element matching works with a `match .. { .. }` block. /// /// Each submachine has to provide a match arm (`pattern => result,`) /// snippet in its [`FromEventsSubmachine::init`] code. The `result` must /// evaluate to a `Result<#state_ty_ident, xso::FromEventsError>`. /// /// The statemachine will generate a final arm (`_ => #fallback,`) using /// the [`fallback`][`FromEventsStateMachine::fallback`] code /// if it exists, and a normal `Mismatch` error otherwise. /// /// The `pattern` must be compatible to the `expr` provided here. Matched { /// Expression which the arms match against. expr: TokenStream, }, } /// # State machine to implement `xso::FromEventsBuilder` /// /// This struct represents a state machine consisting of the following parts: /// /// - Extra dependencies ([`Self::defs`]) /// - States ([`Self::state_defs`]) /// - Transitions ([`Self::advance_match_arms`]) /// - Entrypoints ([`Self::variants`]) /// /// Such a state machine is best constructed by constructing one or /// more [`FromEventsSubmachine`] structs and converting/merging them using /// `into()` and [`merge`][`Self::merge`]. /// /// A state machine has an output type (corresponding to /// `xso::FromEventsBuilder::Output`), which is however only implicitly defined /// by the expressions generated in the `advance_match_arms`. That means that /// merging submachines with different output types works, but will then generate /// code which will fail to compile. /// /// When converted to Rust code, the state machine will manifest as (among other /// things) an enum type which contains all states and which has an `advance` /// method. That method consumes the enum value and returns either a new enum /// value, an error, or the output type of the state machine. #[derive(Default)] pub(crate) struct FromEventsStateMachine { /// Extra items which are needed for the state machine implementation. defs: TokenStream, /// Extra code run during pre-init phase. pre_init: TokenStream, /// Code to run as fallback if none of the branches matched the start /// event. /// /// If absent, a `FromEventsError::Mismatch` is generated. fallback: Option, /// Configure how the element is matched. /// /// This controls the syntax required in each submachine's /// [`FromEventsSubmachine::init`]. See [`FromEventsMatchMode`] for /// details. mode: FromEventsMatchMode, /// A sequence of enum variant declarations, separated and terminated by /// commas. state_defs: TokenStream, /// A sequence of `match self { .. }` arms, where `self` is the state /// enumeration type. /// /// Each match arm must either diverge or evaluate to a /// `Result, xso::error::Error>`, where `State` /// is the state enumeration and `Output` is the state machine's output /// type. advance_match_arms: TokenStream, /// The different entrypoints for the state machine. /// /// This may only contain more than one element if an enumeration is being /// constructed by the resulting state machine. variants: Vec, } impl FromEventsStateMachine { /// Create a new, empty state machine. pub(crate) fn new() -> Self { Self { defs: TokenStream::default(), state_defs: TokenStream::default(), advance_match_arms: TokenStream::default(), pre_init: TokenStream::default(), fallback: None, mode: FromEventsMatchMode::default(), variants: Vec::new(), } } /// Merge another state machine into this state machine. /// /// This *discards* the other state machine's pre-init code. pub(crate) fn merge(&mut self, other: FromEventsStateMachine) { assert!(other.fallback.is_none()); self.defs.extend(other.defs); self.state_defs.extend(other.state_defs); self.advance_match_arms.extend(other.advance_match_arms); self.variants.extend(other.variants); } /// Set additional code to inject at the head of the `new` method for the /// builder. /// /// This can be used to do preliminary checks and is commonly used with /// specifically-formed init codes on the variants. pub(crate) fn set_pre_init(&mut self, code: TokenStream) { self.pre_init = code; } /// Set the fallback code to use if none of the branches matches the start /// event. /// /// By default, a `FromEventsError::Mismatch` is generated. pub(crate) fn set_fallback(&mut self, code: TokenStream) { self.fallback = Some(code); } /// Set the matching mode for the rendered state machine. /// /// See [`FromEventsMatchMode`] for details. pub(crate) fn set_match_mode(&mut self, mode: FromEventsMatchMode) { self.mode = mode; } /// Render the state machine as a token stream. /// /// The token stream contains the following pieces: /// - Any definitions necessary for the statemachine to operate /// - The state enum /// - The builder struct /// - The `xso::FromEventsBuilder` impl on the builder struct /// - A `fn new(rxml::QName, rxml::AttrMap) -> Result` on the /// builder struct. pub(crate) fn render( self, vis: &Visibility, builder_ty_ident: &Ident, state_ty_ident: &Ident, output_ty: &Type, validate_fn: Option<&Path>, ) -> Result { let Self { defs, state_defs, advance_match_arms, variants, pre_init, fallback, mode, } = self; let output_ty_ref = make_ty_ref(output_ty); let docstr = format!("Build a {0} from XML events.\n\nThis type is generated using the [`macro@xso::FromXml`] derive macro and implements [`xso::FromEventsBuilder`] for {0}.", output_ty_ref); let validate_call = match validate_fn { None => quote! { // needed to avoid unused_mut warning. let _ = &mut value; }, Some(validate_fn) => { quote! { #validate_fn(&mut value)?; } } }; let fallback = fallback.unwrap_or_else(|| { quote! { ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) } }); let new_body = match mode { FromEventsMatchMode::Chained => { let mut init_body = pre_init; for variant in variants { let FromEventsEntryPoint { init } = variant; init_body.extend(quote! { let (name, mut attrs) = match { { let _ = &mut attrs; } #init } { ::core::result::Result::Ok(v) => return ::core::result::Result::Ok(v), ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)), ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs), }; }) } quote! { #init_body { let _ = &mut attrs; } #fallback } } FromEventsMatchMode::Matched { expr } => { let mut match_body = TokenStream::default(); for variant in variants { let FromEventsEntryPoint { init } = variant; match_body.extend(init); } quote! { #pre_init match #expr { #match_body _ => #fallback, } } } }; Ok(quote! { #defs enum #state_ty_ident { #state_defs } impl #state_ty_ident { fn advance(mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::ops::ControlFlow, ::xso::error::Error> { match self { #advance_match_arms }.and_then(|__ok| { match __ok { ::core::ops::ControlFlow::Break(st) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(st)), ::core::ops::ControlFlow::Continue(result) => { ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(result)) } } }) } } impl #builder_ty_ident { fn new( name: ::xso::exports::rxml::QName, attrs: ::xso::exports::rxml::AttrMap, ctx: &::xso::Context<'_>, ) -> ::core::result::Result { #state_ty_ident::new(name, attrs, ctx).map(|ok| Self(::core::option::Option::Some(ok))) } } #[doc = #docstr] #[doc(hidden)] #vis struct #builder_ty_ident(::core::option::Option<#state_ty_ident>); impl ::xso::FromEventsBuilder for #builder_ty_ident { type Output = #output_ty; fn feed(&mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::option::Option, ::xso::error::Error> { let inner = self.0.take().expect("feed called after completion"); match inner.advance(ev, ctx)? { ::core::ops::ControlFlow::Continue(mut value) => { #validate_call ::core::result::Result::Ok(::core::option::Option::Some(value)) } ::core::ops::ControlFlow::Break(st) => { self.0 = ::core::option::Option::Some(st); ::core::result::Result::Ok(::core::option::Option::None) } } } } impl #state_ty_ident { fn new( name: ::xso::exports::rxml::QName, mut attrs: ::xso::exports::rxml::AttrMap, ctx: &::xso::Context<'_>, ) -> ::core::result::Result { #new_body } } }) } } /// # State machine to implement an `Iterator`. /// /// This struct represents a state machine consisting of the following parts: /// /// - Extra dependencies ([`Self::defs`]) /// - States ([`Self::state_defs`]) /// - Transitions ([`Self::advance_match_arms`]) /// - Entrypoints ([`Self::variants`]) /// /// Such a state machine is best constructed by constructing one or /// more [`FromEventsSubmachine`] structs and converting/merging them using /// `into()` and [`merge`][`Self::merge`]. /// /// A state machine has an output type (corresponding to /// `xso::FromEventsBuilder::Output`), which is however only implicitly defined /// by the expressions generated in the `advance_match_arms`. That means that /// merging submachines with different output types works, but will then generate /// code which will fail to compile. /// /// When converted to Rust code, the state machine will manifest as (among other /// things) an enum type which contains all states and which has an `advance` /// method. That method consumes the enum value and returns either a new enum /// value, an error, or the output type of the state machine. #[derive(Default)] pub(crate) struct AsItemsStateMachine { /// Extra items which are needed for the state machine implementation. defs: TokenStream, /// A sequence of enum variant declarations, separated and terminated by /// commas. state_defs: TokenStream, /// A sequence of `match self { .. }` arms, where `self` is the state /// enumeration type. /// /// Each match arm must either diverge or evaluate to a /// `Result<(Option, Option), xso::error::Error>`, where /// where `State` is the state enumeration. /// /// If `Some(.)` is returned for the event, that event is emitted. If /// `None` is returned for the event, the advance implementation is called /// again after switching to the state returned in the `Option` /// field. /// /// If `None` is returned for the `Option`, the iterator /// terminates yielding the `Option` value directly (even if it is /// `None`). After the iterator has terminated, it yields `None` /// indefinitely. advance_match_arms: TokenStream, /// The different entrypoints for the state machine. /// /// This may only contain more than one element if an enumeration is being /// serialised by the resulting state machine. variants: Vec, } impl AsItemsStateMachine { /// Create a new, empty state machine. pub(crate) fn new() -> Self { Self { defs: TokenStream::default(), state_defs: TokenStream::default(), advance_match_arms: TokenStream::default(), variants: Vec::new(), } } /// Merge another state machine into this state machine. pub(crate) fn merge(&mut self, other: AsItemsStateMachine) { self.defs.extend(other.defs); self.state_defs.extend(other.state_defs); self.advance_match_arms.extend(other.advance_match_arms); self.variants.extend(other.variants); } /// Render the state machine as a token stream. /// /// The token stream contains the following pieces: /// - Any definitions necessary for the statemachine to operate /// - The state enum /// - The iterator struct /// - The `Iterator` impl on the builder struct /// - A `fn new(T) -> Result` on the iterator struct. pub(crate) fn render( self, vis: &Visibility, input_ty_ref: &Type, state_ty_ident: &Ident, item_iter_ty_lifetime: &Lifetime, item_iter_ty: &Type, ) -> Result { let Self { defs, state_defs, advance_match_arms, mut variants, } = self; let input_ty_ref_text = make_ty_ref(input_ty_ref); let docstr = format!("Convert a {0} into XML events.\n\nThis type is generated using the [`macro@xso::AsXml`] derive macro and implements [`core::iter:Iterator`] for {0}.", input_ty_ref_text); let init_body = if variants.len() == 1 { let AsItemsEntryPoint { destructure, init } = variants.remove(0); quote! { { let #destructure = value; #init } } } else { let mut match_arms = TokenStream::default(); for AsItemsEntryPoint { destructure, init } in variants { match_arms.extend(quote! { #destructure => { #init } }); } quote! { match value { #match_arms } } }; Ok(quote! { #defs enum #state_ty_ident<#item_iter_ty_lifetime> { #state_defs } impl<#item_iter_ty_lifetime> #state_ty_ident<#item_iter_ty_lifetime> { fn advance(mut self) -> ::core::result::Result<(::core::option::Option, ::core::option::Option<::xso::Item<#item_iter_ty_lifetime>>), ::xso::error::Error> { match self { #advance_match_arms } } fn new( value: #input_ty_ref, ) -> ::core::result::Result { ::core::result::Result::Ok(#init_body) } } #[doc = #docstr] #[doc(hidden)] #vis struct #item_iter_ty(::core::option::Option<#state_ty_ident<#item_iter_ty_lifetime>>); impl<#item_iter_ty_lifetime> ::core::iter::Iterator for #item_iter_ty { type Item = ::core::result::Result<::xso::Item<#item_iter_ty_lifetime>, ::xso::error::Error>; fn next(&mut self) -> ::core::option::Option { let mut state = self.0.take()?; loop { let (next_state, item) = match state.advance() { ::core::result::Result::Ok(v) => v, ::core::result::Result::Err(e) => return ::core::option::Option::Some(::core::result::Result::Err(e)), }; if let ::core::option::Option::Some(item) = item { self.0 = next_state; return ::core::option::Option::Some(::core::result::Result::Ok(item)); } // no event, do we have a state? if let ::core::option::Option::Some(st) = next_state { // we do: try again! state = st; continue; } else { // we don't: end of iterator! self.0 = ::core::option::Option::None; return ::core::option::Option::None; } } } } impl<#item_iter_ty_lifetime> #item_iter_ty { fn new(value: #input_ty_ref) -> ::core::result::Result { #state_ty_ident::new(value).map(|ok| Self(::core::option::Option::Some(ok))) } } }) } } /// Construct a path for an intradoc link from a given type. fn doc_link_path(ty: &Type) -> Option { match ty { Type::Path(ref ty) => { let (mut buf, offset) = match ty.qself { Some(ref qself) => { let mut buf = doc_link_path(&qself.ty)?; buf.push_str("::"); (buf, qself.position) } None => { let mut buf = String::new(); if ty.path.leading_colon.is_some() { buf.push_str("::"); } (buf, 0) } }; let last = ty.path.segments.len() - 1; for i in offset..ty.path.segments.len() { let segment = &ty.path.segments[i]; buf.push_str(&segment.ident.to_string()); if i < last { buf.push_str("::"); } } Some(buf) } Type::Reference(TypeReference { ref elem, .. }) => doc_link_path(elem), _ => None, } } /// Create a markdown snippet which references the given type as cleanly as /// possible. /// /// This is used in documentation generation functions. /// /// Not all types can be linked to; those which cannot be linked to will /// simply be wrapped in backticks. fn make_ty_ref(ty: &Type) -> String { match doc_link_path(ty) { Some(mut path) => { path.reserve(4); path.insert_str(0, "[`"); path.push_str("`]"); path } None => format!("`{}`", ty.to_token_stream()), } } xso_proc-0.2.0/src/structs.rs000064400000000000000000000417501046102023000143320ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Handling of structs use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{spanned::Spanned, *}; use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher}; use crate::compound::Compound; use crate::error_message::ParentRef; use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta}; use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State}; use crate::types::{ as_xml_iter_fn, feed_fn, from_events_fn, from_xml_builder_ty, item_iter_ty, ref_ty, ty_from_ident, }; /// The inner parts of the struct. /// /// This contains all data necessary for the matching logic. pub(crate) enum StructInner { /// Single-field struct declared with `#[xml(transparent)]`. /// /// Transparent struct delegate all parsing and serialising to their /// only field, which is why they do not need to store a lot of /// information and come with extra restrictions, such as: /// /// - no XML namespace can be declared (it is determined by inner type) /// - no XML name can be declared (it is determined by inner type) /// - there must be only exactly one field /// - that field has no `#[xml]` attribute Transparent { /// The member identifier of the only field. member: Member, /// Type of the only field. ty: Type, }, /// A compound of fields, *not* declared as transparent. /// /// This can be a unit, tuple-like, or named struct. Compound { /// The XML namespace of the element to map the struct to. xml_namespace: NamespaceRef, /// The XML name of the element to map the struct to. xml_name: NameRef, /// The field(s) of this struct. inner: Compound, }, } impl StructInner { pub(crate) fn new(meta: XmlCompoundMeta, fields: &Fields) -> Result { // We destructure here so that we get informed when new fields are // added and can handle them, either by processing them or raising // an error if they are present. let XmlCompoundMeta { span: meta_span, qname: QNameRef { namespace, name }, exhaustive, debug, builder, iterator, on_unknown_attribute, on_unknown_child, transparent, discard, deserialize_callback, attribute, value, } = meta; // These must've been cleared by the caller. Because these being set // is a programming error (in xso-proc) and not a usage error, we // assert here instead of using reject_key!. assert!(builder.is_none()); assert!(iterator.is_none()); assert!(!debug.is_set()); assert!(deserialize_callback.is_none()); reject_key!(exhaustive flag not on "structs" only on "enums"); reject_key!(attribute not on "structs" only on "enums"); reject_key!(value not on "structs" only on "attribute-switched enum variants"); if let Flag::Present(_) = transparent { reject_key!(namespace not on "transparent structs"); reject_key!(name not on "transparent structs"); reject_key!(on_unknown_attribute not on "transparent structs"); reject_key!(on_unknown_child not on "transparent structs"); reject_key!(discard vec not on "transparent structs"); let fields_span = fields.span(); let fields = match fields { Fields::Unit => { return Err(Error::new( fields_span, "transparent structs or enum variants must have exactly one field", )) } Fields::Named(FieldsNamed { named: ref fields, .. }) | Fields::Unnamed(FieldsUnnamed { unnamed: ref fields, .. }) => fields, }; if fields.len() != 1 { return Err(Error::new( fields_span, "transparent structs or enum variants must have exactly one field", )); } let field = &fields[0]; for attr in field.attrs.iter() { if attr.meta.path().is_ident("xml") { return Err(Error::new_spanned( attr, "#[xml(..)] attributes are not allowed inside transparent structs", )); } } let member = match field.ident.as_ref() { Some(v) => Member::Named(v.clone()), None => Member::Unnamed(Index { span: field.ty.span(), index: 0, }), }; let ty = field.ty.clone(); Ok(Self::Transparent { ty, member }) } else { let Some(xml_namespace) = namespace else { return Err(Error::new( meta_span, "`namespace` is required on non-transparent structs", )); }; let Some(xml_name) = name else { return Err(Error::new( meta_span, "`name` is required on non-transparent structs", )); }; Ok(Self::Compound { inner: Compound::from_fields( fields, &xml_namespace, on_unknown_attribute, on_unknown_child, discard, )?, xml_namespace, xml_name, }) } } pub(crate) fn xml_name_matcher(&self) -> Result { match self { Self::Transparent { ty, .. } => Ok(XmlNameMatcher::Custom(quote! { <#ty as ::xso::FromXml>::xml_name_matcher() })), Self::Compound { xml_namespace, xml_name, .. } => Ok(XmlNameMatcher::Specific( xml_namespace.to_token_stream(), xml_name.to_token_stream(), )), } } pub(crate) fn make_from_events_statemachine( &self, state_ty_ident: &Ident, output_name: &ParentRef, state_prefix: &str, ) -> Result { match self { Self::Transparent { ty, member } => { let from_xml_builder_ty = from_xml_builder_ty(ty.clone()); let from_events_fn = from_events_fn(ty.clone()); let feed_fn = feed_fn(from_xml_builder_ty.clone()); let output_cons = match output_name { ParentRef::Named(ref path) => quote! { #path { #member: result } }, ParentRef::Unnamed { .. } => quote! { ( result, ) }, }; let state_name = quote::format_ident!("{}Default", state_prefix); let builder_data_ident = quote::format_ident!("__xso_data"); // Here, we generate a partial statemachine which really only // proxies the FromXmlBuilder implementation of the inner // type. Ok(FromEventsSubmachine { defs: TokenStream::default(), states: vec![ State::new_with_builder( state_name.clone(), &builder_data_ident, &from_xml_builder_ty, ) .with_impl(quote! { match #feed_fn(&mut #builder_data_ident, ev, ctx)? { ::core::option::Option::Some(result) => { ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons)) } ::core::option::Option::None => { ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name { #builder_data_ident, })) } } }) ], init: quote! { #from_events_fn(name, attrs, ctx).map(|#builder_data_ident| Self::#state_name { #builder_data_ident }) }, }) } Self::Compound { ref inner, ref xml_namespace, ref xml_name, } => Ok(inner .make_from_events_statemachine(state_ty_ident, output_name, state_prefix)? .with_augmented_init(|init| { quote! { if name.0 != #xml_namespace || name.1 != #xml_name { ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs, }) } else { #init } } })), } } pub(crate) fn make_as_item_iter_statemachine( &self, input_name: &ParentRef, state_ty_ident: &Ident, state_prefix: &str, item_iter_ty_lifetime: &Lifetime, ) -> Result { match self { Self::Transparent { ty, member } => { let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone()); let as_xml_iter_fn = as_xml_iter_fn(ty.clone()); let state_name = quote::format_ident!("{}Default", state_prefix); let iter_ident = quote::format_ident!("__xso_data"); let destructure = match input_name { ParentRef::Named(ref path) => quote! { #path { #member: #iter_ident } }, ParentRef::Unnamed { .. } => quote! { (#iter_ident, ) }, }; // Here, we generate a partial statemachine which really only // proxies the AsXml iterator implementation from the inner // type. Ok(AsItemsSubmachine { defs: TokenStream::default(), states: vec![State::new_with_builder( state_name.clone(), &iter_ident, &item_iter_ty, ) .with_mut(&iter_ident) .with_impl(quote! { #iter_ident.next().transpose()? })], destructure, init: quote! { #as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })? }, }) } Self::Compound { ref inner, ref xml_namespace, ref xml_name, } => Ok(inner .make_as_item_iter_statemachine( input_name, state_ty_ident, state_prefix, item_iter_ty_lifetime, )? .with_augmented_init(|init| { quote! { let name = ( ::xso::exports::rxml::Namespace::from(#xml_namespace), ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name), ); #init } })), } } } /// Definition of a struct and how to parse it. pub(crate) struct StructDef { /// Name of the target type. target_ty_ident: Ident, /// Name of the builder type. builder_ty_ident: Ident, /// Name of the iterator type. item_iter_ty_ident: Ident, /// Flag whether debug mode is enabled. debug: bool, /// The matching logic and contents of the struct. inner: StructInner, /// Optional validator function to call. deserialize_callback: Option, } impl StructDef { /// Create a new struct from its name, meta, and fields. pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result { let builder_ty_ident = match meta.builder.take() { Some(v) => v, None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()), }; let item_iter_ty_ident = match meta.iterator.take() { Some(v) => v, None => quote::format_ident!("{}AsXmlIterator", ident.to_string()), }; let debug = meta.debug.take(); let deserialize_callback = meta.deserialize_callback.take(); let inner = StructInner::new(meta, fields)?; Ok(Self { inner, target_ty_ident: ident.clone(), builder_ty_ident, item_iter_ty_ident, debug: debug.is_set(), deserialize_callback, }) } } impl ItemDef for StructDef { fn make_from_events_builder( &self, vis: &Visibility, name_ident: &Ident, attrs_ident: &Ident, ) -> Result { let target_ty_ident = &self.target_ty_ident; let builder_ty_ident = &self.builder_ty_ident; let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident); let defs = self .inner .make_from_events_statemachine( &state_ty_ident, &Path::from(target_ty_ident.clone()).into(), "Struct", )? .compile() .render( vis, builder_ty_ident, &state_ty_ident, &TypePath { qself: None, path: target_ty_ident.clone().into(), } .into(), self.deserialize_callback.as_ref(), )?; Ok(FromXmlParts { defs, from_events_body: quote! { #builder_ty_ident::new(#name_ident, #attrs_ident, ctx) }, builder_ty_ident: builder_ty_ident.clone(), name_matcher: self.inner.xml_name_matcher()?, }) } fn make_as_xml_iter(&self, vis: &Visibility) -> Result { let target_ty_ident = &self.target_ty_ident; let item_iter_ty_ident = &self.item_iter_ty_ident; let item_iter_ty_lifetime = Lifetime { apostrophe: Span::call_site(), ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()), }; let item_iter_ty = Type::Path(TypePath { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: item_iter_ty_ident.clone(), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: token::Lt { spans: [Span::call_site()], }, args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())] .into_iter() .collect(), gt_token: token::Gt { spans: [Span::call_site()], }, }), }] .into_iter() .collect(), }, }); let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident); let defs = self .inner .make_as_item_iter_statemachine( &Path::from(target_ty_ident.clone()).into(), &state_ty_ident, "Struct", &item_iter_ty_lifetime, )? .compile() .render( vis, &ref_ty( ty_from_ident(target_ty_ident.clone()).into(), item_iter_ty_lifetime.clone(), ), &state_ty_ident, &item_iter_ty_lifetime, &item_iter_ty, )?; Ok(AsXmlParts { defs, as_xml_iter_body: quote! { #item_iter_ty_ident::new(self) }, item_iter_ty, item_iter_ty_lifetime, }) } fn debug(&self) -> bool { self.debug } } xso_proc-0.2.0/src/types.rs000064400000000000000000001007551046102023000137700ustar 00000000000000// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Module with specific [`syn::Type`] constructors. use proc_macro2::Span; use syn::{spanned::Spanned, *}; /// Construct a [`syn::Type`] referring to `::xso::exports::rxml::Namespace`. pub(crate) fn namespace_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("exports", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("rxml", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("Namespace", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to `::xso::exports::rxml::NcNameStr`. pub(crate) fn ncnamestr_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("exports", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("rxml", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("NcNameStr", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to `Cow<#lifetime, #ty>`. pub(crate) fn cow_ty(ty: Type, lifetime: Lifetime) -> Type { let span = ty.span(); Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("exports", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("alloc", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("borrow", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("Cow", span), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: token::Lt { spans: [span] }, args: [ GenericArgument::Lifetime(lifetime), GenericArgument::Type(ty), ] .into_iter() .collect(), gt_token: token::Gt { spans: [span] }, }), }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to /// `Cow<#lifetime, ::rxml::NcNameStr>`. pub(crate) fn ncnamestr_cow_ty(ty_span: Span, lifetime: Lifetime) -> Type { cow_ty(ncnamestr_ty(ty_span), lifetime) } /// Construct a [`syn::Expr`] referring to /// `<#ty as ::xso::FromXmlText>::from_xml_text`. pub(crate) fn from_xml_text_fn(ty: Type) -> Expr { let span = ty.span(); Expr::Path(ExprPath { attrs: Vec::new(), qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(ty), position: 2, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("FromXmlText", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("from_xml_text", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Expr`] referring to /// `<#ty as ::xso::AsOptionalXmlText>::as_optional_xml_text`. pub(crate) fn as_optional_xml_text_fn(ty: Type) -> Expr { let span = ty.span(); Expr::Path(ExprPath { attrs: Vec::new(), qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(ty), position: 2, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("AsOptionalXmlText", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("as_optional_xml_text", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Expr`] referring to /// `<#of_ty as ::core::default::Default>::default`. pub(crate) fn default_fn(of_ty: Type) -> Expr { let span = of_ty.span(); Expr::Path(ExprPath { attrs: Vec::new(), qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(of_ty), position: 3, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("core", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("default", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("Default", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("default", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to `::alloc::string::String`. pub(crate) fn string_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("exports", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("alloc", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("string", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("String", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Expr`] referring to /// `<#ty as ::xso::AsXmlText>::as_xml_text`. pub(crate) fn as_xml_text_fn(ty: Type) -> Expr { let span = ty.span(); Expr::Path(ExprPath { attrs: Vec::new(), qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(ty), position: 2, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("AsXmlText", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("as_xml_text", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Path`] referring to `::xso::TextCodec::<#for_ty>`, /// returning the span of `for_ty` alongside it. /// /// The span used is `codec_span`, in order to ensure that error messages /// about a missing implementation point at the codec, not at the type. fn text_codec_of(for_ty: Type, codec_span: Span) -> Path { let span = codec_span; Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("TextCodec", span), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: Some(syn::token::PathSep { spans: [span, span], }), lt_token: syn::token::Lt { spans: [span] }, args: [GenericArgument::Type(for_ty)].into_iter().collect(), gt_token: syn::token::Gt { spans: [span] }, }), }, ] .into_iter() .collect(), } } /// Construct a [`syn::Expr`] referring to /// `::xso::TextCodec::<#for_ty>::encode`. /// /// The span used is `codec_span`, in order to ensure that error messages /// about a missing implementation point at the codec, not at the type. pub(crate) fn text_codec_encode_fn(for_ty: Type, codec_span: Span) -> Expr { let mut path = text_codec_of(for_ty, codec_span); path.segments.push(PathSegment { ident: Ident::new("encode", codec_span), arguments: PathArguments::None, }); Expr::Path(ExprPath { attrs: Vec::new(), qself: None, path, }) } /// Construct a [`syn::Expr`] referring to /// `::xso::TextCodec::<#for_ty>::decode`. /// /// The span used is `codec_span`, in order to ensure that error messages /// about a missing implementation point at the codec, not at the type. pub(crate) fn text_codec_decode_fn(for_ty: Type, codec_span: Span) -> Expr { let mut path = text_codec_of(for_ty, codec_span); path.segments.push(PathSegment { ident: Ident::new("decode", codec_span), arguments: PathArguments::None, }); Expr::Path(ExprPath { attrs: Vec::new(), qself: None, path, }) } /// Construct a [`syn::Type`] for `&#lifetime #ty`. pub(crate) fn ref_ty(ty: Type, lifetime: Lifetime) -> Type { let span = ty.span(); Type::Reference(TypeReference { and_token: token::And { spans: [span] }, lifetime: Some(lifetime), mutability: None, elem: Box::new(ty), }) } /// Construct a [`syn::Type`] referring to /// `::core::marker::PhantomData<&#lifetime ()>`. pub(crate) fn phantom_lifetime_ty(lifetime: Lifetime) -> Type { let span = lifetime.span(); let dummy = Type::Tuple(TypeTuple { paren_token: token::Paren::default(), elems: punctuated::Punctuated::default(), }); Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("core", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("marker", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("PhantomData", span), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: token::Lt { spans: [span] }, args: [GenericArgument::Type(ref_ty(dummy, lifetime))] .into_iter() .collect(), gt_token: token::Gt { spans: [span] }, }), }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::TypePath`] referring to /// `<#of_ty as ::xso::FromXml>`. fn from_xml_of(of_ty: Type) -> (Span, TypePath) { let span = of_ty.span(); ( span, TypePath { qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(of_ty), position: 2, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("FromXml", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }, ) } /// Construct a [`syn::Type`] referring to /// `<#of_ty as ::xso::FromXml>::Builder`. pub(crate) fn from_xml_builder_ty(of_ty: Type) -> Type { let (span, mut ty) = from_xml_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("Builder", span), arguments: PathArguments::None, }); Type::Path(ty) } /// Construct a [`syn::Expr`] referring to /// `<#of_ty as ::xso::FromXml>::from_events`. pub(crate) fn from_events_fn(of_ty: Type) -> Expr { let (span, mut ty) = from_xml_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("from_events", span), arguments: PathArguments::None, }); Expr::Path(ExprPath { attrs: Vec::new(), qself: ty.qself, path: ty.path, }) } /// Construct a [`syn::Type`] which wraps the given `ty` in /// `::core::option::Option<_>`. pub(crate) fn option_ty(ty: Type) -> Type { let span = ty.span(); Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("core", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("option", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("Option", span), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: syn::token::Lt { spans: [span] }, args: [GenericArgument::Type(ty)].into_iter().collect(), gt_token: syn::token::Gt { spans: [span] }, }), }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::TypePath`] referring to /// `<#of_ty as ::xso::FromEventsBuilder>`. fn from_events_builder_of(of_ty: Type) -> (Span, TypePath) { let span = of_ty.span(); ( span, TypePath { qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(of_ty), position: 2, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("FromEventsBuilder", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }, ) } /// Construct a [`syn::Expr`] referring to /// `<#of_ty as ::xso::FromEventsBuilder>::feed`. pub(crate) fn feed_fn(of_ty: Type) -> Expr { let (span, mut ty) = from_events_builder_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("feed", span), arguments: PathArguments::None, }); Expr::Path(ExprPath { attrs: Vec::new(), qself: ty.qself, path: ty.path, }) } fn as_xml_of(of_ty: Type) -> (Span, TypePath) { let span = of_ty.span(); ( span, TypePath { qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(of_ty), position: 2, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("AsXml", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }, ) } /// Construct a [`syn::Expr`] referring to /// `<#of_ty as ::xso::AsXml>::as_xml_iter`. pub(crate) fn as_xml_iter_fn(of_ty: Type) -> Expr { let (span, mut ty) = as_xml_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("as_xml_iter", span), arguments: PathArguments::None, }); Expr::Path(ExprPath { attrs: Vec::new(), qself: ty.qself, path: ty.path, }) } /// Construct a [`syn::Type`] referring to /// `<#of_ty as ::xso::AsXml>::ItemIter`. pub(crate) fn item_iter_ty(of_ty: Type, lifetime: Lifetime) -> Type { let (span, mut ty) = as_xml_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("ItemIter", span), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: token::Lt { spans: [span] }, args: [GenericArgument::Lifetime(lifetime)].into_iter().collect(), gt_token: token::Gt { spans: [span] }, }), }); Type::Path(ty) } /// Construct a [`syn::TypePath`] referring to `<#of_ty as IntoIterator>`. fn into_iterator_of(of_ty: Type) -> (Span, TypePath) { let span = of_ty.span(); ( span, TypePath { qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(of_ty), position: 3, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("core", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("iter", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("IntoIterator", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }, ) } /// Construct a [`syn::Type`] referring to /// `<#of_ty as IntoIterator>::IntoIter`. pub(crate) fn into_iterator_iter_ty(of_ty: Type) -> Type { let (span, mut ty) = into_iterator_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("IntoIter", span), arguments: PathArguments::None, }); Type::Path(ty) } /// Construct a [`syn::Type`] referring to /// `<#of_ty as IntoIterator>::Item`. pub(crate) fn into_iterator_item_ty(of_ty: Type) -> Type { let (span, mut ty) = into_iterator_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("Item", span), arguments: PathArguments::None, }); Type::Path(ty) } /// Construct a [`syn::Expr`] referring to /// `<#of_ty as IntoIterator>::into_iter`. pub(crate) fn into_iterator_into_iter_fn(of_ty: Type) -> Expr { let (span, mut ty) = into_iterator_of(of_ty); ty.path.segments.push(PathSegment { ident: Ident::new("into_iter", span), arguments: PathArguments::None, }); Expr::Path(ExprPath { attrs: Vec::new(), qself: ty.qself, path: ty.path, }) } /// Construct a [`syn::Expr`] referring to /// `<#of_ty as ::core::iter::Extend>::extend`. pub(crate) fn extend_fn(of_ty: Type, item_ty: Type) -> Expr { let span = of_ty.span(); Expr::Path(ExprPath { attrs: Vec::new(), qself: Some(QSelf { lt_token: syn::token::Lt { spans: [span] }, ty: Box::new(of_ty), position: 3, as_token: Some(syn::token::As { span }), gt_token: syn::token::Gt { spans: [span] }, }), path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("core", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("iter", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("Extend", span), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: Some(syn::token::PathSep { spans: [span, span], }), lt_token: syn::token::Lt { spans: [span] }, args: [GenericArgument::Type(item_ty)].into_iter().collect(), gt_token: syn::token::Gt { spans: [span] }, }), }, PathSegment { ident: Ident::new("extend", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::TypePath`] which references the given type name. pub(crate) fn ty_from_ident(ident: Ident) -> TypePath { TypePath { qself: None, path: ident.into(), } } /// Construct a [`syn::Type`] referring to `xso::OptionAsXml<#inner_ty>`. pub(crate) fn option_as_xml_ty(inner_ty: Type) -> Type { let span = inner_ty.span(); Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("asxml", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("OptionAsXml", span), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, lt_token: token::Lt { spans: [span] }, args: [GenericArgument::Type(inner_ty)].into_iter().collect(), gt_token: token::Gt { spans: [span] }, }), }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to `::xso::exports::minidom::Element`. #[cfg(feature = "minidom")] pub(crate) fn element_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("exports", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("minidom", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("Element", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Path`] referring to `::xso::UnknownAttributePolicy`. pub(crate) fn unknown_attribute_policy_path(span: Span) -> Path { Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("UnknownAttributePolicy", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), } } /// Construct a [`syn::Path`] referring to `::xso::UnknownChildPolicy`. pub(crate) fn unknown_child_policy_path(span: Span) -> Path { Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("UnknownChildPolicy", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), } } /// Construct a [`syn::Type`] referring to `::xso::fromxml::Discard`. pub(crate) fn discard_builder_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("fromxml", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("Discard", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to the built-in `bool` type. /// /// Note that we go through `xso::exports::CoreBool` for that, because there seems /// to be no way to access built-in types once they have been shadowed in a /// scope. pub(crate) fn bool_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("exports", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("CoreBool", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to the built-in `u8` type. /// /// Note that we go through `xso::exports::CoreU8` for that, because there seems /// to be no way to access built-in types once they have been shadowed in a /// scope. pub(crate) fn u8_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("exports", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("CoreU8", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) } /// Construct a [`syn::Type`] referring to `::xso::fromxml::EmptyBuilder`. pub(crate) fn empty_builder_ty(span: Span) -> Type { Type::Path(TypePath { qself: None, path: Path { leading_colon: Some(syn::token::PathSep { spans: [span, span], }), segments: [ PathSegment { ident: Ident::new("xso", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("fromxml", span), arguments: PathArguments::None, }, PathSegment { ident: Ident::new("EmptyBuilder", span), arguments: PathArguments::None, }, ] .into_iter() .collect(), }, }) }