use {
proc_macro::TokenStream,
proc_macro2::Span,
quote::quote,
syn::{
parse_macro_input,
Data::{Enum, Struct, Union},
DeriveInput,
Fields::{self, Named, Unnamed},
FieldsNamed, FieldsUnnamed, Ident, Index, Variant,
},
};
fn of_named_fields(n: &Ident, named_fields: &FieldsNamed) -> proc_macro2::TokenStream {
let initialize = named_fields.named.iter().map(|f| {
let name = f.ident.as_ref().unwrap();
quote! {
#name: ::cvlr::nondet::nondet(),
}
});
quote! {
#n {
#( #initialize )*
}
}
}
fn of_unnamed_fields(n: &Ident, unnamed: &FieldsUnnamed) -> proc_macro2::TokenStream {
let initialize = unnamed.unnamed.iter().map(|_| {
quote! { ::cvlr::nondet::nondet(), }
});
quote! {
#n (
#( #initialize )*
)
}
}
fn of_enum_variant(variant: &Variant, enum_name: &Ident) -> proc_macro2::TokenStream {
let variant_name = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#enum_name::#variant_name
},
Fields::Unnamed(unnamed) => {
let initialize = unnamed.unnamed.iter().map(|_| {
quote! { ::cvlr::nondet::nondet(), }
});
quote! {
#enum_name::#variant_name(
#( #initialize )*
)
}
}
Fields::Named(named) => {
let initialize = named.named.iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
quote! {
#field_name: ::cvlr::nondet::nondet(),
}
});
quote! {
#enum_name::#variant_name {
#( #initialize )*
}
}
}
}
}
#[proc_macro_derive(Nondet)]
pub fn derive_nondet(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = input.ident;
match input.data {
Enum(data_enum) => {
let variants = &data_enum.variants;
let variant_count = variants.len();
if variant_count == 0 {
return quote! {
compile_error!("Enum must have at least one variant");
}
.into();
}
let mut match_arms = Vec::new();
for (index, variant) in variants.iter().enumerate() {
let variant_expr = of_enum_variant(variant, &name);
if index == variant_count - 1 {
match_arms.push(quote! {
_ => #variant_expr,
});
} else {
let index_lit = index as u64;
match_arms.push(quote! {
#index_lit => #variant_expr,
});
}
}
quote! {
impl ::cvlr::nondet::Nondet for #name {
fn nondet() -> #name {
match ::cvlr::nondet::nondet::<u64>() {
#( #match_arms )*
}
}
}
}
.into()
}
Union(_) => {
todo!("Union not supported yet")
}
Struct(ds) => match ds.fields {
Fields::Unit => quote! {
impl ::cvlr::nondet::Nondet for #name {
fn nondet() -> #name {
#name
}
}
}
.into(),
Named(named) => {
let init = of_named_fields(&name, &named);
quote! {
impl ::cvlr::nondet::Nondet for #name {
fn nondet() -> #name {
#init
}
}
}
.into()
}
Unnamed(fields) => {
let init = of_unnamed_fields(&name, &fields);
quote! {
impl ::cvlr::nondet::Nondet for #name {
fn nondet() -> #name {
#init
}
}
}
.into()
}
},
}
}
#[proc_macro_derive(CvlrLog)]
pub fn derive_cvlr_log(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = input.ident;
match input.data {
Enum(data_enum) => {
let variants = &data_enum.variants;
let match_arms: Vec<_> = variants.iter().map(|variant| {
let variant_name = &variant.ident;
let variant_name_str = variant_name.to_string();
match &variant.fields {
Fields::Unit => {
quote! {
#name::#variant_name => {
logger.log_str(tag, #variant_name_str);
}
}
}
Fields::Unnamed(unnamed) => {
let field_bindings: Vec<_> = unnamed.unnamed.iter().enumerate().map(|(index, _f)| {
syn::Ident::new(&format!("field{}", index), Span::call_site())
}).collect();
let field_logs: Vec<_> = unnamed.unnamed.iter().enumerate().map(|(index, _f)| {
let field_binding = &field_bindings[index];
let field_index_str = index.to_string();
quote! {
::cvlr::log::cvlr_log_with(#field_index_str, &#field_binding, logger);
}
}).collect();
quote! {
#name::#variant_name(#(ref #field_bindings),*) => {
logger.log_scope_start(tag);
logger.log_str(tag, #variant_name_str);
#( #field_logs )*
logger.log_scope_end(tag);
}
}
}
Fields::Named(named) => {
let field_logs: Vec<_> = named.named.iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_name_str = field_name.to_string();
quote! {
::cvlr::log::cvlr_log_with(#field_name_str, &#field_name, logger);
}
}).collect();
let field_names: Vec<_> = named.named.iter().map(|f| {
f.ident.as_ref().unwrap()
}).collect();
quote! {
#name::#variant_name { #(ref #field_names),* } => {
logger.log_scope_start(tag);
logger.log_str(tag, #variant_name_str);
#( #field_logs )*
logger.log_scope_end(tag);
}
}
}
}
}).collect();
quote! {
impl ::cvlr::log::CvlrLog for #name {
#[inline(always)]
fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
match self {
#( #match_arms )*
}
}
}
}
.into()
}
Union(_) => quote! {
compile_error!("CvlrLog derive is only supported for structs");
}
.into(),
Struct(ds) => {
match ds.fields {
Fields::Unit => quote! {
impl ::cvlr::log::CvlrLog for #name {
#[inline(always)]
fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
logger.log_scope_start(tag);
logger.log_scope_end(tag);
}
}
}
.into(),
Fields::Unnamed(unnamed) => {
let field_logs: Vec<_> = unnamed.unnamed.iter().enumerate().map(|(index, _f)| {
let field_index = Index::from(index);
let field_index_str = index.to_string();
quote! {
::cvlr::log::cvlr_log_with(#field_index_str, &self.#field_index, logger);
}
}).collect();
quote! {
impl ::cvlr::log::CvlrLog for #name {
#[inline(always)]
fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
logger.log_scope_start(tag);
#( #field_logs )*
logger.log_scope_end(tag);
}
}
}
.into()
}
Fields::Named(named) => {
let field_logs: Vec<_> = named.named.iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_name_str = field_name.to_string();
quote! {
::cvlr::log::cvlr_log_with(#field_name_str, &self.#field_name, logger);
}
}).collect();
quote! {
impl ::cvlr::log::CvlrLog for #name {
#[inline(always)]
fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
logger.log_scope_start(tag);
#( #field_logs )*
logger.log_scope_end(tag);
}
}
}
.into()
}
}
}
}
}