use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::*;
use crate::common::{
build_prepare, build_validate, make_builder_name, make_iterator_name, FromEventsParts,
FromEventsStateMachine, IntoEventIterParts, IntoEventsStateMachine, ItemDef, ScopeNamespace,
};
use crate::compound::Compound;
use crate::error_message::ParentRef;
use crate::meta::{Flag, Name, NamespaceRef, StaticNamespace, XmlCompoundMeta};
use crate::structs::StructInner;
fn compile_str_matched_from_events_variants(
scope_namespace: ScopeNamespace,
variants: &[StrMatchedVariant],
state_ty_ident: &Ident,
output_ident: &Ident,
) -> Result<(FromEventsStateMachine, Option<TokenStream>)> {
let mut statemachine = FromEventsStateMachine::default();
let mut fallback = None;
for (i, variant) in variants.iter().enumerate() {
let ident = &variant.ident;
let value = &variant.value;
let prefix = format!("Variant{}{}", i, variant.ident);
let output_name = ParentRef::Named(Path {
leading_colon: None,
segments: [
PathSegment::from(output_ident.clone()),
ident.clone().into(),
]
.into_iter()
.collect(),
});
let mut part = variant.inner.build_from_events_builder(
scope_namespace,
state_ty_ident,
&output_name,
&prefix,
)?;
if variant.fallback.is_set() {
let init = &part.init;
fallback = Some(quote! {
_ => #init,
})
}
part.augment_init(|init| {
quote! {
#value => #init,
}
});
statemachine.merge(part);
}
Ok((statemachine, fallback))
}
fn compile_str_matched_into_events_variants(
scope_namespace: ScopeNamespace,
variants: &[StrMatchedVariant],
input_ident: &Ident,
state_ty_ident: &Ident,
attrs_ident: Option<&Ident>,
mut init_builder: impl FnMut(&TokenStream, &LitStr) -> TokenStream,
) -> Result<IntoEventsStateMachine> {
let mut statemachine = IntoEventsStateMachine::default();
for (i, variant) in variants.iter().enumerate() {
let ident = &variant.ident;
let value = &variant.value;
let prefix = format!("Variant{}{}", i, variant.ident);
let input_name = ParentRef::Named(Path {
leading_colon: None,
segments: [PathSegment::from(input_ident.clone()), ident.clone().into()]
.into_iter()
.collect(),
});
let mut part = variant.inner.build_into_events_iterator(
scope_namespace,
&input_name,
&prefix,
state_ty_ident,
attrs_ident,
)?;
part.augment_init(|init| init_builder(init, value));
statemachine.merge(part);
}
Ok(statemachine)
}
#[derive(Debug)]
struct StrMatchedVariant {
value: LitStr,
ident: Ident,
fallback: Flag,
inner: Compound,
}
impl StrMatchedVariant {
fn prevalidate(meta: &mut XmlCompoundMeta) -> Result<()> {
if let Some(namespace) = meta.namespace.take() {
return Err(Error::new_spanned(
namespace,
"`namespace` not allowed on enum variants.",
));
}
if let Some(attribute) = meta.attribute.take() {
return Err(Error::new_spanned(
attribute,
"`attribute` not allowed on enum variants.",
));
}
if let Flag::Present(transparent) = meta.transparent.take() {
return Err(Error::new(
transparent,
"`transparent` not allowed on enum variants in enums with a fixed namespace.",
));
}
if let Flag::Present(exhaustive) = meta.exhaustive.take() {
return Err(Error::new(
exhaustive,
"`exhaustive` not allowed on enum variants.",
));
}
if let Some(validate) = meta.validate.take() {
return Err(Error::new_spanned(
validate,
"`validate` not allowed on enum variants.",
));
}
if let Some(prepare) = meta.prepare.take() {
return Err(Error::new_spanned(
prepare,
"`validate` not allowed on enum variants.",
));
}
if let Flag::Present(debug) = meta.debug.take() {
return Err(Error::new(debug, "`debug` not allowed on enum variants."));
}
Ok(())
}
fn new_xml_name_switched(variant: &Variant) -> Result<Self> {
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
Self::prevalidate(&mut meta)?;
if let Some(value) = meta.value {
return Err(Error::new_spanned(
value,
"`value` not allowed on enum variants inside enums matching on XML name.",
));
}
let name = if let Some(name) = meta.name {
name.into()
} else {
return Err(Error::new(
meta.span,
"`name` is required on enum variants in enums with a fixed namespace.",
));
};
let fallback = meta.fallback;
Ok(Self {
value: name,
ident: variant.ident.clone(),
fallback,
inner: Compound::from_fields(
meta.on_unknown_child,
meta.on_unknown_attribute,
&variant.fields,
)?,
})
}
fn new_xml_attribute_switched(variant: &Variant) -> Result<Self> {
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
Self::prevalidate(&mut meta)?;
if let Some(name) = meta.name {
return Err(Error::new_spanned(
name,
"`name` not allowed on enum variants inside enums matching on attribute values.",
));
}
let Some(value) = meta.value else {
return Err(Error::new(
meta.span,
"`value` is required on enum variants in enums matching on attribute values.",
));
};
let fallback = meta.fallback;
Ok(Self {
value,
ident: variant.ident.clone(),
fallback,
inner: Compound::from_fields(
meta.on_unknown_child,
meta.on_unknown_attribute,
&variant.fields,
)?,
})
}
}
#[derive(Debug)]
struct DynamicVariant {
ident: Ident,
inner: StructInner,
}
impl DynamicVariant {
fn new(variant: &Variant) -> Result<Self> {
let ident = variant.ident.clone();
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
if let Flag::Present(fallback) = meta.fallback.take() {
return Err(Error::new(
fallback,
"`fallback` cannot be used in dynamic enums.",
));
}
if let Flag::Present(exhaustive) = meta.exhaustive.take() {
return Err(Error::new(
exhaustive,
"`exhaustive` not allowed on enum variants.",
));
}
if let Some(validate) = meta.validate.take() {
return Err(Error::new_spanned(
validate,
"`validate` not allowed on enum variants.",
));
}
if let Some(prepare) = meta.prepare.take() {
return Err(Error::new_spanned(
prepare,
"`validate` not allowed on enum variants.",
));
}
if let Flag::Present(debug) = meta.debug.take() {
return Err(Error::new(debug, "`debug` not allowed on enum variants."));
}
let inner = StructInner::new(meta, &variant.fields)?;
Ok(Self { ident, inner })
}
}
#[derive(Debug)]
struct XmlNameSwitched {
exhaustive: Flag,
namespace: StaticNamespace,
variants: Vec<StrMatchedVariant>,
}
impl XmlNameSwitched {
fn new<'x, I: Iterator<Item = &'x Variant>>(
exhaustive: Flag,
namespace: StaticNamespace,
input: I,
) -> Result<Self> {
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
let mut had_fallback = false;
for variant in input {
let variant = StrMatchedVariant::new_xml_name_switched(variant)?;
if let Flag::Present(fallback) = variant.fallback {
if had_fallback {
return Err(syn::Error::new(
fallback,
"only one variant may be a fallback variant",
));
}
had_fallback = true;
}
variants.push(variant);
}
if let Flag::Present(exhaustive) = exhaustive {
if had_fallback {
return Err(syn::Error::new(exhaustive, "exhaustive cannot be sensibly combined with a fallback variant. choose one or the other."));
}
}
Ok(Self {
exhaustive,
namespace,
variants,
})
}
fn build_from_events_builder(
&self,
vis: &Visibility,
from_events_ty_ident: &Ident,
output_ty: &Type,
output_ident: &Ident,
validate: Stmt,
) -> Result<TokenStream> {
let scope_namespace = ScopeNamespace::Static(&self.namespace);
let xml_namespace = &self.namespace;
let state_ty_ident = quote::format_ident!("{}State", from_events_ty_ident);
let (statemachine, fallback) = compile_str_matched_from_events_variants(
scope_namespace,
&self.variants,
&state_ty_ident,
output_ident,
)?;
let fallback = fallback.unwrap_or_else(|| {
match self.exhaustive {
Flag::Absent => quote! {
_ => ::std::result::Result::Err(::xso::FromEventsError::Mismatch { name, attrs })
},
Flag::Present(_) => quote! {
_ => ::std::result::Result::Err(::xso::FromEventsError::Invalid(::xso::error::Error::ParseError(concat!("This is not a ", stringify!(#output_ident), " element.")))),
},
}
});
statemachine.render_match(
vis,
from_events_ty_ident,
&state_ty_ident,
output_ty,
quote! {
if name.0 != #xml_namespace {
return ::std::result::Result::Err(::xso::FromEventsError::Mismatch { name, attrs });
}
{ let _ = &mut attrs; }
let value = name.1.as_str();
},
fallback,
Some(validate),
)
}
fn build_into_events_iterator(
&self,
vis: &Visibility,
into_events_ty_ident: &Ident,
input_ty: &Type,
input_ident: &Ident,
) -> Result<TokenStream> {
let scope_namespace = ScopeNamespace::Static(&self.namespace);
let xml_namespace = &self.namespace;
let state_ty_ident = quote::format_ident!("{}State", into_events_ty_ident);
compile_str_matched_into_events_variants(
scope_namespace,
&self.variants,
input_ident,
&state_ty_ident,
None,
|init, value| {
quote! {
{
let qname = (
::xso::exports::rxml::Namespace::try_from(#xml_namespace)?,
::xso::exports::rxml::NcName::try_from(#value)?,
);
#init
}
}
},
)?
.render(vis, input_ty, &state_ty_ident, into_events_ty_ident)
}
}
#[derive(Debug)]
struct XmlAttributeSwitched {
namespace: StaticNamespace,
name: Name,
attribute_name: LitStr,
normalize_with: Option<Path>,
variants: Vec<StrMatchedVariant>,
}
impl XmlAttributeSwitched {
fn new<'x, I: Iterator<Item = &'x Variant>>(
exhaustive: Flag,
namespace: StaticNamespace,
name: Name,
attribute_name: LitStr,
normalize_with: Option<Path>,
input: I,
) -> Result<Self> {
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
let mut had_fallback = false;
for variant in input {
let variant = StrMatchedVariant::new_xml_attribute_switched(variant)?;
if let Flag::Present(fallback) = variant.fallback {
if had_fallback {
return Err(syn::Error::new(
fallback,
"only one variant may be a fallback variant",
));
}
had_fallback = true;
}
variants.push(variant);
}
if let Flag::Present(exhaustive) = exhaustive {
if had_fallback {
return Err(syn::Error::new(exhaustive, "exhaustive cannot be sensibly combined with a fallback variant. choose one or the other."));
}
} else {
if !had_fallback {
return Err(syn::Error::new_spanned(
attribute_name,
"enums switching on an attribute must be marked exhaustive or have a fallback variant."
));
}
}
Ok(Self {
namespace,
name,
attribute_name,
normalize_with,
variants,
})
}
fn build_from_events_builder(
&self,
vis: &Visibility,
from_events_ty_ident: &Ident,
output_ty: &Type,
output_ident: &Ident,
validate: Stmt,
) -> Result<TokenStream> {
let scope_namespace = ScopeNamespace::Static(&self.namespace);
let state_ty_ident = quote::format_ident!("{}State", from_events_ty_ident);
let (statemachine, fallback) = compile_str_matched_from_events_variants(
scope_namespace,
&self.variants,
&state_ty_ident,
output_ident,
)?;
let fallback = fallback.unwrap_or_else(|| {
quote! {
_ => ::std::result::Result::Err(::xso::FromEventsError::Invalid(::xso::error::Error::ParseError(concat!("This is not a ", stringify!(#output_ident), " element."))))
}
});
let namespace = &self.namespace;
let xml_name = &self.name;
let attribute_name = &self.attribute_name;
let on_missing = format!(
"Required discriminator attribute '{}' on enum {} element missing.",
attribute_name.value(),
output_ident,
);
let normalize = match self.normalize_with {
Some(ref normalize_with) => quote! {
let value = #normalize_with(&value);
let value = ::std::ops::Deref::deref(&value);
},
None => quote! {
let value = value.as_str();
},
};
statemachine.render_match(
vis,
from_events_ty_ident,
&state_ty_ident,
output_ty,
quote! {
if name.0 != #namespace || name.1 != #xml_name {
return ::std::result::Result::Err(::xso::FromEventsError::Mismatch { name, attrs });
}
let Some(value) = attrs.remove(
::xso::exports::rxml::Namespace::none(),
#attribute_name,
) else {
return ::std::result::Result::Err(::xso::FromEventsError::Invalid(::xso::error::Error::ParseError(#on_missing)));
};
#normalize
},
fallback,
Some(validate),
)
}
fn build_into_events_iterator(
&self,
vis: &Visibility,
into_events_ty_ident: &Ident,
input_ty: &Type,
input_ident: &Ident,
) -> Result<TokenStream> {
let attrs_ident = Ident::new("attrs", Span::call_site());
let xml_namespace = &self.namespace;
let xml_name = &self.name;
let attribute = &self.attribute_name;
let state_ty_ident = quote::format_ident!("{}State", into_events_ty_ident);
compile_str_matched_into_events_variants(
ScopeNamespace::Static(&self.namespace),
&self.variants,
input_ident,
&state_ty_ident,
Some(&attrs_ident),
|init, value| {
quote! {
{
let qname = (
::xso::exports::rxml::Namespace::try_from(#xml_namespace)?,
::xso::exports::rxml::NcName::try_from(#xml_name)?,
);
let mut #attrs_ident = ::xso::exports::rxml::AttrMap::new();
#attrs_ident.insert(
::xso::exports::rxml::Namespace::NONE,
::xso::exports::rxml::NcName::try_from(#attribute)?,
::std::string::String::from(#value),
);
#init
}
}
},
)?
.render(vis, input_ty, &state_ty_ident, into_events_ty_ident)
}
}
#[derive(Debug)]
struct Dynamic {
variants: Vec<DynamicVariant>,
}
impl Dynamic {
fn new<'x, I: Iterator<Item = &'x Variant>>(input: I) -> Result<Self> {
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
for variant in input {
let variant = DynamicVariant::new(variant)?;
variants.push(variant);
}
Ok(Self { variants })
}
fn build_from_events_builder(
&self,
vis: &Visibility,
from_events_ty_ident: &Ident,
output_ty: &Type,
output_ident: &Ident,
validate: Stmt,
) -> Result<TokenStream> {
let state_ty_ident = quote::format_ident!("{}State", from_events_ty_ident);
let mut statemachine = FromEventsStateMachine::default();
for (i, variant) in self.variants.iter().enumerate() {
let ident = &variant.ident;
let prefix = format!("Variant{}{}", i, variant.ident);
let variant_name = ParentRef::Named(Path {
leading_colon: None,
segments: [
PathSegment::from(output_ident.clone()),
ident.clone().into(),
]
.into_iter()
.collect(),
});
let part =
variant
.inner
.build_from_events_builder(&state_ty_ident, &variant_name, &prefix)?;
statemachine.merge(part);
}
statemachine.render(
vis,
from_events_ty_ident,
&state_ty_ident,
output_ty,
Some(validate),
)
}
fn build_into_events_iterator(
&self,
vis: &Visibility,
into_events_ty_ident: &Ident,
input_ty: &Type,
input_ident: &Ident,
) -> Result<TokenStream> {
let state_ty_ident = quote::format_ident!("{}State", into_events_ty_ident);
let mut statemachine = IntoEventsStateMachine::default();
for (i, variant) in self.variants.iter().enumerate() {
let ident = &variant.ident;
let prefix = format!("Variant{}{}", i, variant.ident);
let input_name = ParentRef::Named(Path {
leading_colon: None,
segments: [PathSegment::from(input_ident.clone()), ident.clone().into()]
.into_iter()
.collect(),
});
statemachine.merge(variant.inner.build_into_events_iterator(
&input_name,
&state_ty_ident,
&prefix,
)?);
}
statemachine.render(vis, input_ty, &state_ty_ident, into_events_ty_ident)
}
}
#[derive(Debug)]
enum EnumInner {
XmlNameSwitched(XmlNameSwitched),
XmlAttributeSwitched(XmlAttributeSwitched),
Dynamic(Dynamic),
}
impl EnumInner {
fn build_from_events_builder(
&self,
vis: &Visibility,
from_events_ty_ident: &Ident,
output_ty: &Type,
output_ident: &Ident,
validate: Stmt,
) -> Result<TokenStream> {
match self {
Self::XmlNameSwitched(inner) => inner.build_from_events_builder(
vis,
from_events_ty_ident,
output_ty,
output_ident,
validate,
),
Self::XmlAttributeSwitched(inner) => inner.build_from_events_builder(
vis,
from_events_ty_ident,
output_ty,
output_ident,
validate,
),
Self::Dynamic(inner) => inner.build_from_events_builder(
vis,
from_events_ty_ident,
output_ty,
output_ident,
validate,
),
}
}
fn build_into_events_iterator(
&self,
vis: &Visibility,
into_events_ty_ident: &Ident,
input_ty: &Type,
input_ident: &Ident,
) -> Result<TokenStream> {
match self {
Self::XmlNameSwitched(inner) => {
inner.build_into_events_iterator(vis, into_events_ty_ident, input_ty, input_ident)
}
Self::XmlAttributeSwitched(inner) => {
inner.build_into_events_iterator(vis, into_events_ty_ident, input_ty, input_ident)
}
Self::Dynamic(inner) => {
inner.build_into_events_iterator(vis, into_events_ty_ident, input_ty, input_ident)
}
}
}
}
#[derive(Debug)]
pub(crate) struct EnumDef {
validate: Option<Path>,
prepare: Option<Path>,
inner: EnumInner,
builder: Ident,
iterator: Ident,
#[cfg_attr(not(feature = "debug"), allow(dead_code))]
debug: Flag,
}
impl EnumDef {
fn new<'x, I: Iterator<Item = &'x Variant>>(
ident: &Ident,
meta: Option<XmlCompoundMeta>,
input: I,
) -> Result<Self> {
let meta = match meta {
Some(v) => v,
None => XmlCompoundMeta::empty(Span::call_site()),
};
let prepare = meta.prepare;
let validate = meta.validate;
let debug = meta.debug;
let builder = meta
.builder_name
.unwrap_or_else(|| make_builder_name(&ident));
let iterator = meta
.iterator_name
.unwrap_or_else(|| make_iterator_name(&ident));
if let Flag::Present(fallback) = meta.fallback {
return Err(syn::Error::new(
fallback,
"`fallback` is not allowed on enums (only on enum variants)",
));
}
if let Flag::Present(transparent) = meta.transparent {
return Err(syn::Error::new(
transparent,
"`transparent` cannot be set set on enums (only on enum variants)",
));
}
if let Some(value) = meta.value {
return Err(syn::Error::new_spanned(
value,
"`value` is not allowed on enums",
));
}
let namespace = match meta.namespace {
None => {
if let Some(name) = meta.name {
return Err(syn::Error::new_spanned(
name,
"`name` cannot be set without `namespace` or on dynamic enums",
));
}
if let Flag::Present(exhaustive) = meta.exhaustive {
return Err(syn::Error::new(
exhaustive,
"`transparent` cannot be set set on dynamic enums",
));
}
if let Some(normalize_with) = meta.normalize_with {
return Err(syn::Error::new_spanned(
normalize_with,
"`normalize_with` option is only allowed on attribute value switched enums",
));
};
return Ok(Self {
validate,
prepare,
debug,
builder,
iterator,
inner: EnumInner::Dynamic(Dynamic::new(input)?),
});
}
Some(NamespaceRef::Static(ns)) => ns,
Some(NamespaceRef::Dyn(ns)) => {
return Err(syn::Error::new_spanned(
ns,
"`#[xml(namespace = dyn)]` cannot be used on enums.",
))
}
Some(NamespaceRef::Super(ns)) => {
return Err(syn::Error::new_spanned(
ns,
"`#[xml(namespace = super)]` cannot be used on enums.",
))
}
};
let exhaustive = meta.exhaustive;
let name = match meta.name {
None => {
if let Some(normalize_with) = meta.normalize_with {
return Err(syn::Error::new_spanned(
normalize_with,
"`normalize_with` option is only allowed on attribute value switched enums",
));
};
return Ok(Self {
prepare,
validate,
debug,
builder,
iterator,
inner: EnumInner::XmlNameSwitched(XmlNameSwitched::new(
exhaustive, namespace, input,
)?),
});
}
Some(name) => name,
};
let Some(attribute_name) = meta.attribute else {
return Err(syn::Error::new(
meta.span,
"`attribute` option with the name of the XML attribute to match is required for enums matching on attribute value",
));
};
Ok(Self {
prepare,
validate,
debug,
builder,
iterator,
inner: EnumInner::XmlAttributeSwitched(XmlAttributeSwitched::new(
exhaustive,
namespace,
name.into(),
attribute_name,
meta.normalize_with,
input,
)?),
})
}
}
impl ItemDef for EnumDef {
fn build_from_events_builder(
&self,
vis: &Visibility,
output_ty: &Type,
output_name: &ParentRef,
name_ident: &Ident,
attrs_ident: &Ident,
) -> Result<FromEventsParts> {
let validate = build_validate(self.validate.as_ref());
let builder_ty_ident = &self.builder;
let Some(output_ident) = output_name.try_as_ident() else {
panic!("EnumDef::build_from_events_builder cannot be called with non-ident ParentRef");
};
let struct_def = self.inner.build_from_events_builder(
vis,
builder_ty_ident,
output_ty,
output_ident,
validate,
)?;
Ok(FromEventsParts {
struct_def,
from_events_body: quote! {
#builder_ty_ident::new(#name_ident, #attrs_ident)
},
builder_ty_ident: builder_ty_ident.clone(),
})
}
fn build_into_events_iterator(
&self,
vis: &Visibility,
input_ty: &Type,
input_name: &ParentRef,
self_ident: &Ident,
) -> Result<IntoEventIterParts> {
let Some(input_ident) = input_name.try_as_ident() else {
panic!("EnumDef::build_into_element cannot be called with non-ident ParentRef");
};
let prepare = build_prepare(self.prepare.as_ref(), self_ident);
let event_iter_ty_ident = &self.iterator;
let struct_def = self.inner.build_into_events_iterator(
vis,
event_iter_ty_ident,
input_ty,
input_ident,
)?;
Ok(IntoEventIterParts {
struct_def,
into_event_iter_body: quote! {
#prepare
#event_iter_ty_ident::new(#self_ident)
},
event_iter_ty_ident: event_iter_ty_ident.clone(),
})
}
fn build_dyn_namespace(&self) -> Result<TokenStream> {
Err(Error::new(
Span::call_site(),
"DynNamespace cannot be derived on enums (yet)",
))
}
fn debug_mode(&self) -> bool {
self.debug.is_set()
}
}
pub(crate) fn parse_enum(item: &syn::ItemEnum) -> Result<Box<dyn ItemDef>> {
let mut meta = XmlCompoundMeta::try_parse_from_attributes(&item.attrs)?;
let wrapped_with = meta.as_mut().map(|x| (x.wrapped_with.take(), x.span));
let mut def = Box::new(EnumDef::new(&item.ident, meta, item.variants.iter())?);
if let Some((Some(wrapped_with), span)) = wrapped_with {
let mut builder = quote::format_ident!("{}Wrapped", def.builder);
let mut iterator = quote::format_ident!("{}Wrapped", def.iterator);
std::mem::swap(&mut builder, &mut def.builder);
std::mem::swap(&mut iterator, &mut def.iterator);
crate::wrapped::wrap(
&span,
wrapped_with,
&item.ident,
builder,
iterator,
def as Box<dyn ItemDef>,
)
.map(|x| x as Box<dyn ItemDef>)
} else {
Ok(def as Box<dyn ItemDef>)
}
}