use crate::consts::{MAV_CMD, MAV_CMD_MISSION_SUBSETS};
use crate::conventions::{
description_doc_comment_line, dialect_mod_name, enum_bitmask_entry_name, enum_entry_name,
enum_mod_name, enum_rust_name, enum_specta_name,
};
use crate::conventions::{microservice_doc_mention, microservice_enum_specta_name};
use crate::specs::dialects::dialect::enums::{
EnumImplModuleSpec, EnumInheritedModuleSpec, EnumsRootModuleSpec,
};
use crate::specs::Spec;
use crate::templates::helpers::{make_serde_derive_annotation, make_specta_derive_annotation};
use quote::{format_ident, quote};
pub(crate) fn enums_root_module(spec: &EnumsRootModuleSpec) -> syn::File {
let module_doc_comment = match spec.msrv_name() {
Some(msrv_name) => {
format!(
"# MAVLink enums for {} microservice of `{}` dialect",
microservice_doc_mention(msrv_name),
spec.dialect_canonical_name()
)
}
None => format!(
" MAVLink enums of `{}` dialect.",
spec.dialect_canonical_name()
),
};
let enum_modules_and_imports = spec.enums().iter().map(|enm| {
let enum_mod_name = format_ident!("{}", enum_mod_name(enm.name()));
let enum_rust_name = format_ident!("{}", enum_rust_name(enm.name()));
quote! {
pub mod #enum_mod_name;
pub use #enum_mod_name::#enum_rust_name;
}
});
syn::parse2(quote! {
#![doc = #module_doc_comment]
#(#enum_modules_and_imports)*
})
.unwrap()
}
pub(crate) fn enum_module(spec: &EnumImplModuleSpec) -> syn::File {
let module_doc_comment = match spec.msrv_name() {
Some(msrv_name) => {
format!(
"# MAVLink `{}` enum implementation for {} microservice of `{}` dialect",
spec.name(),
microservice_doc_mention(msrv_name),
spec.dialect().name(),
)
}
None => format!(
" MAVLink `{}` enum implementation for `{}` dialect.",
spec.name(),
spec.dialect().name(),
),
};
let mavspec_import = spec.params().mavspec_import();
let bitmask_impl = make_bitmask_enum(spec);
let enum_impl = make_enum(spec);
syn::parse2(quote! {
#![doc = #module_doc_comment]
#mavspec_import
#bitmask_impl
#enum_impl
})
.unwrap()
}
fn make_bitmask_enum(spec: &EnumImplModuleSpec) -> proc_macro2::TokenStream {
let leading_doc_comment = match spec.msrv_name() {
Some(msrv_name) => {
format!(
"# MAVLink bitmask enum `{}` of {} microservice for `{}` dialect",
spec.name(),
microservice_doc_mention(msrv_name),
spec.dialect().name(),
)
}
None => format!(
" MAVLink bitmask enum `{}` for `{}` dialect.",
spec.name(),
spec.dialect().name(),
),
};
let specta_name = match spec.msrv_name() {
Some(msrv_name) => microservice_enum_specta_name(msrv_name, spec.name(), None),
None => enum_specta_name(spec.name(), spec.dialect().canonical_name()),
};
let description_doc_comments = spec.description().iter().map(description_doc_comment_line);
let derive_serde = make_serde_derive_annotation(spec.params().serde);
let derive_specta =
make_specta_derive_annotation(spec.params().specta, Some(specta_name.as_str()));
let enum_ident = format_ident!("{}", enum_rust_name(spec.name()));
let enum_inferred_type = format_ident!("{}", spec.inferred_type().rust_type());
let entry_consts = spec.entries().iter().map(|entry| {
let name_doc_comment = format!("`{}` flag.", entry.name());
let description_doc_comments = entry.description().iter().map(description_doc_comment_line);
let flag_ident = format_ident!("{}", enum_bitmask_entry_name(entry.name_stripped()));
let flag_value = entry.value_expr();
quote! {
#[doc = #name_doc_comment]
#(#description_doc_comments)*
const #flag_ident = #flag_value;
}
});
if spec.is_bitmask() {
quote! {
use mavspec::rust::spec::bitflags::bitflags;
#[allow(rustdoc::bare_urls)]
#[allow(rustdoc::broken_intra_doc_links)]
#[allow(rustdoc::invalid_rust_codeblocks)]
#[doc = #leading_doc_comment]
#(#description_doc_comments)*
#[derive(core::marker::Copy, core::clone::Clone, core::fmt::Debug, core::default::Default, core::cmp::PartialEq)]
#derive_specta
#derive_serde
pub struct #enum_ident(#enum_inferred_type);
bitflags! {
impl #enum_ident: #enum_inferred_type {
#(#entry_consts)*
}
}
}
} else {
quote!()
}
}
fn make_enum(spec: &EnumImplModuleSpec) -> proc_macro2::TokenStream {
if spec.is_bitmask() {
quote!()
} else {
let leading_doc_comment = match spec.msrv_name() {
Some(msrv_name) => {
format!(
"# MAVLink enum `{}` of {} microservice for `{}` dialect",
spec.name(),
microservice_doc_mention(msrv_name),
spec.dialect().name(),
)
}
None => format!(
" MAVLink enum `{}` for `{}` dialect.",
spec.name(),
spec.dialect().name(),
),
};
let specta_name = match spec.msrv_name() {
Some(msrv_name) => microservice_enum_specta_name(msrv_name, spec.name(), None),
None => enum_specta_name(spec.name(), spec.dialect().canonical_name()),
};
let description_doc_comments = spec.description().iter().map(description_doc_comment_line);
let derive_serde = make_serde_derive_annotation(spec.params().serde);
let derive_specta =
make_specta_derive_annotation(spec.params().specta, Some(specta_name.as_str()));
let enum_ident = format_ident!("{}", enum_rust_name(spec.name()));
let enum_inferred_type = format_ident!("{}", spec.inferred_type().rust_type());
let enum_variants = spec.entries().iter().map(|entry| {
let name_doc_comment = format!(" MAVLink enum entry `{}`.", entry.name());
let description_doc_comments =
entry.description().iter().map(description_doc_comment_line);
let entry_ident = format_ident!("{}", enum_entry_name(entry.name_stripped()));
let entry_value = entry.value_expr();
quote! {
#[doc = #name_doc_comment]
#(#description_doc_comments)*
#entry_ident = #entry_value,
}
});
let default = if !spec.entries().is_empty() {
quote! {
#[default]
}
} else {
quote!()
};
let conversions = generate_enum_conventions(spec);
let metadata = generate_enum_metadata(spec);
quote! {
#[allow(rustdoc::bare_urls)]
#[allow(rustdoc::broken_intra_doc_links)]
#[allow(rustdoc::invalid_rust_codeblocks)]
#[doc = #leading_doc_comment]
#(#description_doc_comments)*
#[derive(mavspec::rust::derive::Enum)]
#[derive(core::marker::Copy, core::clone::Clone, core::fmt::Debug, core::default::Default, core::cmp::PartialEq)]
#[repr(#enum_inferred_type)]
#derive_specta
#derive_serde
pub enum #enum_ident {
#default
#(#enum_variants)*
}
#conversions
#metadata
}
}
}
pub(crate) fn enum_inherited_module(spec: &EnumInheritedModuleSpec) -> syn::File {
let module_doc_comment = format!(
" MAVLink enum `{}` of `{}` dialect inherited from `{}` dialect.",
spec.name(),
spec.dialect_canonical_name(),
spec.original_dialect_name()
);
let module_doc_comment = match spec.msrv_name() {
Some(msrv_name) => format!(
" MAVLink enum `{}` of {} microservice for `{}` dialect inherited from `{}` dialect.",
spec.name(),
microservice_doc_mention(msrv_name),
spec.dialect_canonical_name(),
spec.original_dialect_name()
),
None => module_doc_comment,
};
let enum_ident = format_ident!("{}", enum_rust_name(spec.name()));
let original_dialect_mod_ident =
format_ident!("{}", dialect_mod_name(spec.original_dialect_name()));
let original_dialect_import_path = quote! { super::super::super::#original_dialect_mod_ident };
let original_dialect_import_path = match spec.msrv_name() {
Some(_) => quote! { super::super::#original_dialect_import_path },
None => original_dialect_import_path,
};
let enum_mod_ident = format_ident!("{}", enum_mod_name(spec.name()));
let enum_doc_comment = format!(" Originally defined in [`{original_dialect_mod_ident}::enums::{enum_mod_ident}`](dialect::enums::{enum_ident})");
syn::parse2(quote! {
#![doc = #module_doc_comment]
use #original_dialect_import_path as dialect;
#[doc = #enum_doc_comment]
pub type #enum_ident = dialect::enums::#enum_mod_ident::#enum_ident;
})
.unwrap()
}
fn generate_enum_conventions(spec: &EnumImplModuleSpec) -> proc_macro2::TokenStream {
match spec.msrv() {
Some(msrv) => {
let enum_ident = format_ident!("{}", enum_rust_name(spec.name()));
let enum_name = spec.name().to_string();
let mav_cmd_conversion = if MAV_CMD_MISSION_SUBSETS.contains(&spec.name()) {
let mav_cmd_enum_ident = format_ident!("{}", enum_rust_name(MAV_CMD));
let from_sub_enum_to_mav_cmd_match_arms = spec.entries().iter().map(|entry| {
let sub_entry_ident =
format_ident!("{}", enum_entry_name(entry.name_stripped()));
let mav_cmd_entry = spec.dialect()
.get_enum_by_name(MAV_CMD)
.expect("'MAV_CMD' should be in dialect")
.get_entry_by_name(entry.name())
.expect("'MAV_CMD' should contain an entry");
let mav_cmd_entry_ident = format_ident!("{}", enum_entry_name(mav_cmd_entry.name_stripped()));
quote! {
#enum_ident::#sub_entry_ident => super::#mav_cmd_enum_ident::#mav_cmd_entry_ident,
}
});
let from_mav_cmd_to_sub_enum_match_arms = spec.entries().iter().map(|entry| {
let sub_entry_ident =
format_ident!("{}", enum_entry_name(entry.name_stripped()));
let mav_cmd_entry = spec.dialect()
.get_enum_by_name(MAV_CMD)
.expect("'MAV_CMD' should be in dialect")
.get_entry_by_name(entry.name())
.expect("'MAV_CMD' should contain an entry");
let mav_cmd_entry_ident = format_ident!("{}", enum_entry_name(mav_cmd_entry.name_stripped()));
quote! {
super::#mav_cmd_enum_ident::#mav_cmd_entry_ident => #enum_ident::#sub_entry_ident,
}
});
quote! {
impl core::convert::From<#enum_ident> for super::#mav_cmd_enum_ident {
fn from(value: #enum_ident) -> Self {
#[allow(unreachable_patterns)]
match value {
#(#from_sub_enum_to_mav_cmd_match_arms)*
_ => unreachable!()
}
}
}
impl core::convert::TryFrom<super::#mav_cmd_enum_ident> for #enum_ident {
type Error = mavspec::rust::spec::SpecError;
fn try_from(value: super::#mav_cmd_enum_ident) -> Result<Self, Self::Error> {
#[allow(unreachable_patterns)]
Ok(match value {
#(#from_mav_cmd_to_sub_enum_match_arms)*
_ => return Err(Self::Error::InvalidEnumValue {
enum_name: #enum_name
}),
})
}
}
}
} else {
quote! {}
};
let parent_enum_conversions = if msrv.parent().contains_enum_with_name(spec.name()) {
let parent_enum_ident = quote! { super::super::super::super::enums::#enum_ident };
let from_enum_to_parent_match_arms = spec.entries().iter().map(|entry| {
let entry_ident = format_ident!("{}", enum_entry_name(entry.name_stripped()));
quote! {
#enum_ident::#entry_ident => #parent_enum_ident::#entry_ident,
}
});
let from_parent_enum_to_enum_match_arms = spec.entries().iter().map(|entry| {
let entry_ident = format_ident!("{}", enum_entry_name(entry.name_stripped()));
quote! {
#parent_enum_ident::#entry_ident => #enum_ident::#entry_ident,
}
});
quote! {
impl core::convert::From<#enum_ident> for #parent_enum_ident {
fn from(value: #enum_ident) -> Self {
#[allow(unreachable_patterns)]
match value {
#(#from_enum_to_parent_match_arms)*
_ => unreachable!()
}
}
}
impl core::convert::TryFrom<#parent_enum_ident> for #enum_ident {
type Error = mavspec::rust::spec::SpecError;
fn try_from(value: #parent_enum_ident) -> Result<Self, Self::Error> {
Ok(match value {
#(#from_parent_enum_to_enum_match_arms)*
_ => return Err(Self::Error::InvalidEnumValue{
enum_name: #enum_name
}),
})
}
}
}
} else {
quote! {}
};
quote! {
#parent_enum_conversions
#mav_cmd_conversion
}
}
None => quote! {},
}
}
fn generate_enum_metadata(spec: &EnumImplModuleSpec) -> proc_macro2::TokenStream {
if !spec.params().metadata {
return quote! {};
}
let name = spec.name();
let enum_ident = format_ident!("{}", enum_rust_name(spec.name()));
let entry_variants = spec.entries().iter().map(|entry| {
let entry_ident = format_ident!("{}", enum_entry_name(entry.name_stripped()));
quote! {
Self::#entry_ident
}
});
let enum_inferred_type = format_ident!("{}", spec.inferred_type().rust_type());
quote! {
impl #enum_ident {
#[inline]
pub fn name() -> &'static str {
#name
}
pub fn entries() -> impl Iterator<Item=Self> {
[#(#entry_variants,)*].iter().copied()
}
#[inline]
pub fn value(&self) -> #enum_inferred_type {
self.clone() as #enum_inferred_type
}
}
}
}