use darling::ast::NestedMeta;
use darling::FromMeta;
use proc_macro as pc;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use syn::{spanned::Spanned, Expr, Type};
#[derive(Debug, FromMeta)]
#[darling(and_then = "Self::validate_mode")]
struct RegisterArgs {
address: Expr,
#[darling(default)]
mode: String,
#[darling(default)]
spi_codec: Option<Type>,
#[darling(default)]
i2c_codec: Option<Type>,
}
impl RegisterArgs {
fn validate_mode(self) -> darling::Result<Self> {
match self.mode.as_str() {
"r" | "w" | "rw" => Ok(self),
_ => Err(darling::Error::custom("Unknown mode '{}'").with_span(&self.mode)),
}
}
}
#[proc_macro_attribute]
pub fn register(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream {
match register_impl(args.into(), input.into()) {
Ok(result) => result.into(),
Err(e) => e.into_compile_error().into(),
}
}
fn register_impl(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
let args_span = args.span();
let args = RegisterArgs::from_list(&NestedMeta::parse_meta_list(args)?)?;
let input = syn::parse2::<syn::ItemStruct>(input)?;
if !input.attrs.iter().any(|x| x.path().is_ident("bondrewd")) {
return Err(syn::Error::new(
args_span,
"A register definition must also include a #[bondrewd()] bitfield spec",
));
}
let ident = input.ident;
let debug_format_str = format!("{} ({{:?}}) => {{:?}}", ident);
let vis = input.vis;
let fields = input.fields.clone();
let attrs: TokenStream = input.attrs.iter().map(ToTokens::to_token_stream).collect();
let docattrs: TokenStream = input
.attrs
.iter()
.filter(|x| x.path().is_ident("doc"))
.map(ToTokens::to_token_stream)
.collect();
let bitfield_ident = format_ident!("{}Bitfield", ident);
let mut forward_fns = quote! {};
for field in input.fields {
let type_ident = field.ty;
let field_name = field
.ident
.ok_or_else(|| syn::Error::new(args_span, "A field contains a field without an identifier"))?;
let field_docattrs: TokenStream = field
.attrs
.iter()
.filter(|x| x.path().is_ident("doc"))
.map(ToTokens::to_token_stream)
.collect();
let read_field_name = format_ident!("read_{field_name}");
let read_comment = format!("Retrieves the value of [`{bitfield_ident}::{field_name}`] from this register:");
let write_field_name = format_ident!("write_{field_name}");
let write_comment = format!("Updates the value of [`{bitfield_ident}::{field_name}`] in this register:");
let with_field_name = format_ident!("with_{field_name}");
let with_comment =
format!("Updates the value of [`{bitfield_ident}::{field_name}`] in this register and allows chaining:");
forward_fns = quote! {
#forward_fns
#[doc = #read_comment]
#[doc = ""]
#field_docattrs
#[inline]
pub fn #read_field_name(&self) -> #type_ident {
#bitfield_ident::#read_field_name(&self.data)
}
#[doc = #write_comment]
#[doc = ""]
#field_docattrs
#[inline]
pub fn #write_field_name(&mut self, #field_name: #type_ident) {
#bitfield_ident::#write_field_name(&mut self.data, #field_name)
}
#[doc = #with_comment]
#[doc = ""]
#field_docattrs
#[inline]
pub fn #with_field_name(mut self, #field_name: #type_ident) -> Self {
#bitfield_ident::#write_field_name(&mut self.data, #field_name);
self
}
};
}
let read_all_comment = format!("Unpack all fields and return them as a [`{bitfield_ident}`]. If you don't need all fields, this is more expensive than just using the appropriate `read_*` functions directly.");
let write_all_comment = format!("Pack all fields in the given [`{bitfield_ident}`] representation. If you only want to write some fields, this is more expensive than just using the appropriate `write_*` functions directly.");
let address = args.address;
let is_read = matches!(args.mode.as_str(), "r" | "rw");
let is_write = matches!(args.mode.as_str(), "w" | "rw");
let spi_codec = args
.spi_codec
.unwrap_or_else(|| syn::parse_str::<syn::Type>("embedded_registers::spi::codecs::NoCodec").unwrap());
let i2c_codec = args
.i2c_codec
.unwrap_or_else(|| syn::parse_str::<syn::Type>("embedded_registers::i2c::codecs::NoCodec").unwrap());
let mut output = quote! {
#[derive(bondrewd::Bitfields, Clone, Default, PartialEq, Eq, core::fmt::Debug, defmt::Format)]
#attrs
#vis struct #bitfield_ident
#fields
#[derive(Copy, Clone, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(transparent)]
#docattrs
pub struct #ident {
pub data: [u8; <#bitfield_ident as bondrewd::Bitfields<_>>::BYTE_SIZE],
}
impl #ident {
#forward_fns
#[inline]
#[doc = #write_all_comment]
pub fn new(value: #bitfield_ident) -> Self {
use bondrewd::Bitfields;
Self {
data: value.into_bytes(),
}
}
#[inline]
#[doc = #read_all_comment]
pub fn read_all(&self) -> #bitfield_ident {
use bondrewd::Bitfields;
#bitfield_ident::from_bytes(self.data)
}
#[inline]
#[doc = #write_all_comment]
pub fn write_all(&mut self, value: #bitfield_ident) {
use bondrewd::Bitfields;
self.data = value.into_bytes();
}
}
impl Default for #ident {
fn default() -> Self {
use bondrewd::Bitfields;
Self {
data: #bitfield_ident::default().into_bytes(),
}
}
}
impl core::fmt::Debug for #ident {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
#debug_format_str,
self.data,
#bitfield_ident::from(self)
)
}
}
impl defmt::Format for #ident {
fn format(&self, f: defmt::Formatter) {
defmt::write!(
f,
#debug_format_str,
self.data,
#bitfield_ident::from(self)
)
}
}
impl embedded_registers::Register for #ident {
type Bitfield = #bitfield_ident;
type SpiCodec = #spi_codec;
type I2cCodec = #i2c_codec;
const REGISTER_SIZE: usize = <#bitfield_ident as bondrewd::Bitfields<_>>::BYTE_SIZE;
const ADDRESS: u64 = #address;
#[inline]
fn data(&self) -> &[u8] {
&self.data
}
#[inline]
fn data_mut(&mut self) -> &mut [u8] {
&mut self.data
}
}
impl AsRef<#bitfield_ident> for #bitfield_ident {
#[inline]
fn as_ref(&self) -> &#bitfield_ident {
self
}
}
impl AsRef<#ident> for #ident {
#[inline]
fn as_ref(&self) -> &#ident {
self
}
}
impl From<&#ident> for #bitfield_ident {
#[inline]
fn from(val: &#ident) -> Self {
use bondrewd::Bitfields;
#bitfield_ident::from_bytes(val.data)
}
}
impl From<&#bitfield_ident> for #ident {
#[inline]
fn from(value: &#bitfield_ident) -> Self {
use bondrewd::Bitfields;
Self {
data: value.clone().into_bytes(),
}
}
}
};
if is_read {
output.append_all(quote! {
impl embedded_registers::ReadableRegister for #ident {}
});
}
if is_write {
output.append_all(quote! {
impl embedded_registers::WritableRegister for #ident {}
});
}
Ok(output)
}