extern crate proc_macro;
use core::str::FromStr;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input, Attribute, Error, Ident, LitInt, Token, TypePath,
};
enum PhantomEntry {
Enum {
attributes: Vec<Attribute>,
name: String,
start: u64,
end: u64,
enum_type: Ident,
variant_list: Vec<String>,
},
Integer {
attributes: Vec<Attribute>,
name: String,
start: u64,
end: u64,
},
Bool {
attributes: Vec<Attribute>,
name: String,
bit: u64,
},
Const {
attributes: Vec<Attribute>,
name: String,
const_ident: Ident,
},
}
struct PhantomFields {
self_member_type: TypePath,
entries: Vec<PhantomEntry>,
}
impl Parse for PhantomFields {
fn parse(input: ParseStream) -> Result<Self> {
let _ = input.parse::<Token![self]>()?;
let _ = input.parse::<Token![.]>()?;
let lit = input.parse::<LitInt>()?;
if lit.value() != 0 {
return Err(Error::new(lit.span(), "Currently only self.0 is supported"));
}
let _ = input.parse::<Token![:]>()?;
let self_member_type: TypePath = input.parse::<TypePath>()?;
let _ = input.parse::<Token![,]>()?;
let mut entries: Vec<PhantomEntry> = vec![];
'entry_loop: loop {
if input.is_empty() {
break;
}
let attributes = input.call(Attribute::parse_outer)?;
let name = input.parse::<Ident>()?.to_string();
let _ = input.parse::<Token![:]>()?;
let lookahead_int_or_ident = input.lookahead1();
if lookahead_int_or_ident.peek(LitInt) {
let start = input.parse::<LitInt>()?.value();
let lookahead_bool_or_span = input.lookahead1();
if lookahead_bool_or_span.peek(Token![,]) {
entries.push(PhantomEntry::Bool {
attributes,
name,
bit: start,
});
let _ = input.parse::<Token![,]>()?;
continue 'entry_loop;
} else if lookahead_bool_or_span.peek(Token![-]) {
let _ = input.parse::<Token![-]>()?;
let end = input.parse::<LitInt>()?.value();
let lookahead = input.lookahead1();
if lookahead.peek(Token![=]) {
let _ = input.parse::<Token![=]>()?;
let enum_type = input.parse::<Ident>()?;
let mut variant_list = vec![];
let _ = input.parse::<Token![<]>()?;
'variant_gather_loop: loop {
variant_list.push(input.parse::<Ident>()?.to_string());
let lookahead = input.lookahead1();
if lookahead.peek(Token![>]) {
let _ = input.parse::<Token![>]>()?;
break 'variant_gather_loop;
} else if lookahead.peek(Token![,]) {
let _ = input.parse::<Token![,]>()?;
continue 'variant_gather_loop;
} else {
return Err(lookahead.error());
}
}
entries.push(PhantomEntry::Enum {
attributes,
name,
start,
end,
enum_type,
variant_list,
});
let _ = input.parse::<Token![,]>()?;
continue 'entry_loop;
} else if lookahead.peek(Token![,]) {
entries.push(PhantomEntry::Integer {
attributes,
name,
start,
end,
});
let _ = input.parse::<Token![,]>()?;
continue 'entry_loop;
} else {
return Err(lookahead.error());
}
} else {
return Err(lookahead_bool_or_span.error());
}
} else if lookahead_int_or_ident.peek(Ident) {
let const_ident = input.parse::<Ident>()?;
entries.push(PhantomEntry::Const {
attributes,
name,
const_ident,
});
let _ = input.parse::<Token![,]>()?;
continue 'entry_loop;
} else {
return Err(lookahead_int_or_ident.error());
}
}
Ok(PhantomFields { self_member_type, entries })
}
}
#[proc_macro]
pub fn phantom_fields(input: TokenStream) -> TokenStream {
let PhantomFields { self_member_type, entries } = parse_macro_input!(input as PhantomFields);
let mut out_text = String::new();
for entry in entries.into_iter() {
match entry {
PhantomEntry::Enum {
attributes,
name,
start,
end,
enum_type,
variant_list,
} => {
for attribute in attributes.into_iter() {
out_text.push_str(&format!("{}\n", TokenStream::from(quote! { #attribute })));
}
let mask_name = Ident::new(&format!("{}_MASK", name.to_uppercase()), Span::call_site());
let read_name = Ident::new(&name.clone(), Span::call_site());
let with_name = Ident::new(&format!("with_{}", name), Span::call_site());
let width = (end - start) + 1;
out_text.push_str(&format!(
"{}\n",
TokenStream::from(quote! {
#[allow(clippy::identity_op)]
pub const #mask_name: #self_member_type = ((1<<(#width))-1) << #start;
#[allow(missing_docs)]
pub fn #read_name(self) -> #enum_type
})
));
out_text.push('{');
out_text.push_str(&format!(
"{}\n",
TokenStream::from(quote! {
match (self.0 & Self::#mask_name) >> #start
})
));
out_text.push('{');
let enum_type_string = enum_type.to_string();
for (i, variant) in variant_list.iter().enumerate() {
out_text.push_str(&format!("{} => {}::{},\n", i, enum_type_string, variant));
}
out_text.push_str("_ => unreachable!(),");
out_text.push_str("} }\n");
out_text.push_str(&format!(
"{}\n",
TokenStream::from(quote! {
#[allow(missing_docs)]
pub const fn #with_name(self, #read_name: #enum_type) -> Self {
Self((self.0 & !Self::#mask_name) | (((#read_name as #self_member_type) << #start) & Self::#mask_name))
}
})
));
}
PhantomEntry::Integer {
attributes,
name,
start,
end,
} => {
for attribute in attributes.into_iter() {
out_text.push_str(&format!("{}\n", TokenStream::from(quote! { #attribute })));
}
let mask_name = Ident::new(&format!("{}_MASK", name.to_uppercase()), Span::call_site());
let read_name = Ident::new(&name.clone(), Span::call_site());
let with_name = Ident::new(&format!("with_{}", name), Span::call_site());
let width = (end - start) + 1;
out_text.push_str(&format!(
"{}\n",
TokenStream::from(quote! {
#[allow(clippy::identity_op)]
pub const #mask_name: #self_member_type = ((1<<(#width))-1) << #start;
#[allow(missing_docs)]
pub const fn #read_name(self) -> #self_member_type {
(self.0 & Self::#mask_name) >> #start
}
#[allow(missing_docs)]
pub const fn #with_name(self, #read_name: #self_member_type) -> Self {
Self((self.0 & !Self::#mask_name) | ((#read_name << #start) & Self::#mask_name))
}
})
));
}
PhantomEntry::Bool { attributes, name, bit } => {
for attribute in attributes.into_iter() {
out_text.push_str(&format!("{}\n", TokenStream::from(quote! { #attribute })));
}
let const_name = Ident::new(&format!("{}_BIT", name.to_uppercase()), Span::call_site());
let read_name = Ident::new(&name.clone(), Span::call_site());
let with_name = Ident::new(&format!("with_{}", name), Span::call_site());
out_text.push_str(&format!(
"{}\n",
TokenStream::from(quote! {
#[allow(clippy::identity_op)]
pub const #const_name: #self_member_type = 1 << #bit;
#[allow(missing_docs)]
pub const fn #read_name(self) -> bool {
(self.0 & Self::#const_name) != 0
}
#[allow(missing_docs)]
pub const fn #with_name(self, bit: bool) -> Self {
Self(self.0 ^ (((#self_member_type::wrapping_sub(0, bit as #self_member_type) ^ self.0) & Self::#const_name)))
}
})
));
}
PhantomEntry::Const {
attributes,
name,
const_ident,
} => {
for attribute in attributes.into_iter() {
out_text.push_str(&format!("{}\n", TokenStream::from(quote! { #attribute })));
}
let read_name = Ident::new(&name.clone(), Span::call_site());
let with_name = Ident::new(&format!("with_{}", name), Span::call_site());
out_text.push_str(&format!(
"{}\n",
TokenStream::from(quote! {
#[allow(missing_docs)]
pub const fn #read_name(self) -> bool {
(self.0 & #const_ident) != 0
}
#[allow(missing_docs)]
pub const fn #with_name(self, bit: bool) -> Self {
Self(self.0 ^ (((#self_member_type::wrapping_sub(0, bit as #self_member_type) ^ self.0) & #const_ident as #self_member_type)))
}
})
));
}
};
}
TokenStream::from_str(&out_text).map_err(|e| panic!("{:?}", e)).unwrap()
}