use crate::svd::{
self, Access, BitRange, DimElement, EnumeratedValue, EnumeratedValues, Field, MaybeArray,
ModifiedWriteValues, ReadAction, Register, RegisterProperties, Usage, WriteConstraint,
};
use core::u64;
use log::warn;
use proc_macro2::{Ident, Punct, Spacing, Span, TokenStream};
use quote::{quote, ToTokens};
use std::collections::HashSet;
use std::fmt::Write;
use std::{borrow::Cow, collections::BTreeMap};
use svd_parser::expand::{
derive_enumerated_values, derive_field, BlockPath, EnumPath, FieldPath, Index, RegisterPath,
};
use crate::config::Config;
use crate::util::{
self, ident_to_path, path_segment, replace_suffix, type_path, unsuffixed, FullName,
ToSanitizedCase, U32Ext,
};
use anyhow::{anyhow, Result};
use syn::punctuated::Punctuated;
pub fn render(
register: &Register,
path: &BlockPath,
dpath: Option<RegisterPath>,
index: &Index,
config: &Config,
) -> Result<TokenStream> {
let mut name = util::name_of(register, config.ignore_groups);
if dpath.is_some() {
if let MaybeArray::Array(info, array_info) = register {
if let Some(dim_index) = &array_info.dim_index {
let index: Cow<str> = dim_index.first().unwrap().into();
name = replace_suffix(&info.fullname(config.ignore_groups), &index).into()
}
}
}
let span = Span::call_site();
let name_constant_case = name.to_constant_case_ident(span);
let name_snake_case = name.to_snake_case_ident(span);
let description = util::escape_special_chars(
util::respace(®ister.description.clone().unwrap_or_else(|| {
warn!("Missing description for register {}", register.name);
Default::default()
}))
.as_ref(),
);
if let Some(dpath) = dpath.as_ref() {
let mut derived = if &dpath.block == path {
type_path(Punctuated::new())
} else {
util::block_path_to_ty(&dpath.block, span)
};
let dname = util::name_of(index.registers.get(dpath).unwrap(), config.ignore_groups);
let mut mod_derived = derived.clone();
derived
.path
.segments
.push(path_segment(dname.to_constant_case_ident(span)));
mod_derived
.path
.segments
.push(path_segment(dname.to_snake_case_ident(span)));
Ok(quote! {
pub use #derived as #name_constant_case;
pub use #mod_derived as #name_snake_case;
})
} else {
let regspec_ident = format!("{name}_SPEC").to_constant_case_ident(span);
let access = util::access_of(®ister.properties, register.fields.as_deref());
let accs = if access.can_read() && access.can_write() {
"rw"
} else if access.can_write() {
"w"
} else if access.can_read() {
"r"
} else {
return Err(anyhow!("Incorrect access of register {}", register.name));
};
let mut alias_doc = format!(
"{name} ({accs}) register accessor: {description}\n\n{}",
api_docs(
access.can_read(),
access.can_write(),
register.properties.reset_value.is_some(),
&name_snake_case,
false,
register.read_action,
)?
);
if name_snake_case != "cfg" {
alias_doc += format!(
"\n\nFor information about available fields see [`mod@{name_snake_case}`] module"
)
.as_str();
}
let mut out = TokenStream::new();
out.extend(quote! {
#[doc = #alias_doc]
pub type #name_constant_case = crate::Reg<#name_snake_case::#regspec_ident>;
});
let mod_items = render_register_mod(
register,
access,
&path.new_register(®ister.name),
index,
config,
)?;
out.extend(quote! {
#[doc = #description]
pub mod #name_snake_case {
#mod_items
}
});
Ok(out)
}
}
fn api_docs(
can_read: bool,
can_write: bool,
can_reset: bool,
module: &Ident,
inmodule: bool,
read_action: Option<ReadAction>,
) -> Result<String, std::fmt::Error> {
fn method(s: &str) -> String {
format!("[`{s}`](crate::generic::Reg::{s})")
}
let mut doc = String::new();
if can_read {
write!(
doc,
"You can {} this register and get [`{module}::R`]{}. ",
method("read"),
if inmodule { "(R)" } else { "" },
)?;
if let Some(action) = read_action {
doc.push_str("WARN: ");
doc.push_str(match action {
ReadAction::Clear => "The register is **cleared** (set to zero) following a read operation.",
ReadAction::Set => "The register is **set** (set to ones) following a read operation.",
ReadAction::Modify => "The register is **modified** in some way after a read operation.",
ReadAction::ModifyExternal => "One or more dependent resources other than the current register are immediately affected by a read operation.",
});
}
doc.push(' ');
}
if can_write {
let mut methods = Vec::new();
if can_reset {
methods.push("reset");
methods.push("write");
}
methods.push("write_with_zero");
write!(
doc,
"You can {} this register using [`{module}::W`]{}. ",
methods
.iter()
.map(|m| method(m))
.collect::<Vec<_>>()
.join(", "),
if inmodule { "(W)" } else { "" },
)?;
}
if can_read && can_write {
write!(doc, "You can also {} this register. ", method("modify"),)?;
}
doc.push_str("See [API](https://docs.rs/svd2rust/#read--modify--write-api).");
Ok(doc)
}
pub fn render_register_mod(
register: &Register,
access: Access,
path: &RegisterPath,
index: &Index,
config: &Config,
) -> Result<TokenStream> {
let properties = ®ister.properties;
let name = util::name_of(register, config.ignore_groups);
let span = Span::call_site();
let regspec_ident = format!("{name}_SPEC").to_constant_case_ident(span);
let name_snake_case = name.to_snake_case_ident(span);
let rsize = properties
.size
.ok_or_else(|| anyhow!("Register {} has no `size` field", register.name))?;
let rsize = if rsize < 8 {
8
} else if rsize.is_power_of_two() {
rsize
} else {
rsize.next_power_of_two()
};
let rty = rsize.to_ty()?;
let description = util::escape_special_chars(
util::respace(®ister.description.clone().unwrap_or_else(|| {
warn!("Missing description for register {}", register.name);
Default::default()
}))
.as_ref(),
);
let mut mod_items = TokenStream::new();
let can_read = access.can_read();
let can_write = access.can_write();
let can_reset = properties.reset_value.is_some();
if can_read {
let desc = format!("Register `{}` reader", register.name);
mod_items.extend(quote! {
#[doc = #desc]
pub type R = crate::R<#regspec_ident>;
});
}
if can_write {
let desc = format!("Register `{}` writer", register.name);
mod_items.extend(quote! {
#[doc = #desc]
pub type W = crate::W<#regspec_ident>;
});
}
let mut r_impl_items = TokenStream::new();
let mut r_debug_impl = TokenStream::new();
let mut w_impl_items = TokenStream::new();
let mut zero_to_modify_fields_bitmap = 0;
let mut one_to_modify_fields_bitmap = 0;
let open = Punct::new('{', Spacing::Alone);
let close = Punct::new('}', Spacing::Alone);
let debug_feature = config
.impl_debug_feature
.as_ref()
.map(|feature| quote!(#[cfg(feature=#feature)]));
if let Some(cur_fields) = register.fields.as_ref() {
let cur_fields: Vec<&Field> = cur_fields
.iter()
.filter(|field| field.name.to_lowercase() != "reserved")
.collect();
if !cur_fields.is_empty() {
if config.impl_debug {
r_debug_impl.extend(render_register_mod_debug(
register,
&access,
&cur_fields,
config,
))
}
(
r_impl_items,
w_impl_items,
zero_to_modify_fields_bitmap,
one_to_modify_fields_bitmap,
) = fields(
cur_fields,
®spec_ident,
register.modified_write_values,
access,
properties,
&mut mod_items,
path,
index,
config,
)?;
}
} else if !access.can_read() || register.read_action.is_some() {
r_debug_impl.extend(quote! {
#debug_feature
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "(not readable)")
}
}
});
} else {
r_debug_impl.extend(quote! {
#debug_feature
impl core::fmt::Debug for R {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.bits())
}
}
#debug_feature
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.read(), f)
}
}
});
}
if can_read && !r_impl_items.is_empty() {
mod_items.extend(quote! {
impl R #open #r_impl_items #close
});
}
if !r_debug_impl.is_empty() {
mod_items.extend(quote! {
#r_debug_impl
});
}
if can_write {
mod_items.extend(quote! {
impl W #open
});
mod_items.extend(w_impl_items);
let can_write_safe = !unsafety(
register
.fields
.as_ref()
.and_then(|fields| fields.first())
.and_then(|field| field.write_constraint)
.as_ref(),
rsize,
) || !unsafety(register.write_constraint.as_ref(), rsize);
if can_write_safe {
mod_items.extend(quote! {
#[doc = "Writes raw bits to the register."]
#[inline(always)]
pub fn bits(&mut self, bits: #rty) -> &mut Self {
self.bits = bits;
self
}
});
} else {
mod_items.extend(quote! {
#[inline(always)]
pub unsafe fn bits(&mut self, bits: #rty) -> &mut Self {
self.bits = bits;
self
}
});
}
close.to_tokens(&mut mod_items);
}
let doc = format!(
"{description}\n\n{}",
api_docs(
can_read,
can_write,
can_reset,
&name_snake_case,
true,
register.read_action,
)?
);
mod_items.extend(quote! {
#[doc = #doc]
pub struct #regspec_ident;
impl crate::RegisterSpec for #regspec_ident {
type Ux = #rty;
}
});
if can_read {
let doc = format!("`read()` method returns [`{name_snake_case}::R`](R) reader structure",);
mod_items.extend(quote! {
#[doc = #doc]
impl crate::Readable for #regspec_ident {}
});
}
if can_write {
let doc =
format!("`write(|w| ..)` method takes [`{name_snake_case}::W`](W) writer structure",);
let zero_to_modify_fields_bitmap = util::hex(zero_to_modify_fields_bitmap);
let one_to_modify_fields_bitmap = util::hex(one_to_modify_fields_bitmap);
mod_items.extend(quote! {
#[doc = #doc]
impl crate::Writable for #regspec_ident {
const ZERO_TO_MODIFY_FIELDS_BITMAP: Self::Ux = #zero_to_modify_fields_bitmap;
const ONE_TO_MODIFY_FIELDS_BITMAP: Self::Ux = #one_to_modify_fields_bitmap;
}
});
}
if let Some(rv) = properties.reset_value.map(util::hex) {
let doc = format!("`reset()` method sets {} to value {rv}", register.name);
mod_items.extend(quote! {
#[doc = #doc]
impl crate::Resettable for #regspec_ident {
const RESET_VALUE: Self::Ux = #rv;
}
});
}
Ok(mod_items)
}
fn render_register_mod_debug(
register: &Register,
access: &Access,
cur_fields: &[&Field],
config: &Config,
) -> Result<TokenStream> {
let name = util::name_of(register, config.ignore_groups);
let span = Span::call_site();
let regspec_ident = format!("{name}_SPEC").to_constant_case_ident(span);
let open = Punct::new('{', Spacing::Alone);
let close = Punct::new('}', Spacing::Alone);
let mut r_debug_impl = TokenStream::new();
let debug_feature = config
.impl_debug_feature
.as_ref()
.map(|feature| quote!(#[cfg(feature=#feature)]));
if access.can_read() && register.read_action.is_none() {
r_debug_impl.extend(quote! {
#debug_feature
impl core::fmt::Debug for R #open
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result #open
f.debug_struct(#name)
});
for &f in cur_fields.iter() {
let field_access = match &f.access {
Some(a) => a,
None => access,
};
let bit_or_bits = if f.bit_width() > 1 { "bits" } else { "bit" };
let bit_or_bits = syn::Ident::new(bit_or_bits, span);
log::debug!("register={} field={}", name, f.name);
if field_access.can_read() && f.read_action.is_none() {
if let Field::Array(_, de) = &f {
for suffix in de.indexes() {
let f_name_n = util::replace_suffix(&f.name, &suffix)
.to_snake_case_ident(Span::call_site());
let f_name_n_s = format!("{f_name_n}");
r_debug_impl.extend(quote! {
.field(#f_name_n_s, &format_args!("{}", self.#f_name_n().#bit_or_bits()))
});
}
} else {
let f_name = util::replace_suffix(&f.name, "");
let f_name = f_name.to_snake_case_ident(span);
let f_name_s = format!("{f_name}");
r_debug_impl.extend(quote! {
.field(#f_name_s, &format_args!("{}", self.#f_name().#bit_or_bits()))
});
}
}
}
r_debug_impl.extend(quote! {
.finish()
#close
#close
});
r_debug_impl.extend(quote! {
#debug_feature
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.read(), f)
}
}
});
} else if !access.can_read() || register.read_action.is_some() {
r_debug_impl.extend(quote! {
#debug_feature
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "(not readable)")
}
}
});
} else {
warn!("not implementing debug for {name}");
}
Ok(r_debug_impl)
}
#[allow(clippy::too_many_arguments)]
pub fn fields(
mut fields: Vec<&Field>,
regspec_ident: &Ident,
rmwv: Option<ModifiedWriteValues>,
access: Access,
properties: &RegisterProperties,
mod_items: &mut TokenStream,
rpath: &RegisterPath,
index: &Index,
config: &Config,
) -> Result<(TokenStream, TokenStream, u64, u64)> {
let mut r_impl_items = TokenStream::new();
let mut w_impl_items = TokenStream::new();
let mut zero_to_modify_fields_bitmap = 0u64;
let mut one_to_modify_fields_bitmap = 0u64;
let span = Span::call_site();
let can_read = access.can_read();
let can_write = access.can_write();
fields.sort_by_key(|f| f.bit_offset());
let mut enum_derives = HashSet::new();
let mut reader_derives = HashSet::new();
let mut writer_enum_derives = HashSet::new();
let mut writer_derives = HashSet::new();
let inline = quote! { #[inline(always)] };
for &f in fields.iter() {
let mut f = f.clone();
let mut fdpath = None;
if let Some(dpath) = f.derived_from.take() {
fdpath = derive_field(&mut f, &dpath, rpath, index)?;
}
let fpath = rpath.new_field(&f.name);
let BitRange { offset, width, .. } = f.bit_range;
if f.is_single() && f.name.contains("%s") {
return Err(anyhow!("incorrect field {}", f.name));
}
let name = util::replace_suffix(&f.name, "");
let name_snake_case = if let Field::Array(
_,
DimElement {
dim_name: Some(dim_name),
..
},
) = &f
{
dim_name.to_snake_case_ident(span)
} else {
name.to_snake_case_ident(span)
};
let name_constant_case = name.to_sanitized_constant_case();
let description_raw = f.description.as_deref().unwrap_or(""); let description = util::respace(&util::escape_special_chars(description_raw));
let can_read = can_read
&& (f.access != Some(Access::WriteOnly))
&& (f.access != Some(Access::WriteOnce));
let can_write = can_write && (f.access != Some(Access::ReadOnly));
let mask = u64::MAX >> (64 - width);
let hexmask = &util::digit_or_hex(mask);
let offset = u64::from(offset);
let rv = properties.reset_value.map(|rv| (rv >> offset) & mask);
let fty = width.to_ty()?;
let use_mask;
let use_cast;
if let Some(size) = properties.size {
let size = size.to_ty_width()?;
use_cast = size != width.to_ty_width()?;
use_mask = size != width;
} else {
use_cast = true;
use_mask = true;
};
let mut lookup_results = Vec::new();
for mut ev in f.enumerated_values.clone().into_iter() {
let mut epath = None;
let dpath = ev.derived_from.take();
if let Some(dpath) = dpath {
epath = Some(derive_enumerated_values(&mut ev, &dpath, &fpath, index)?);
if let Some(epath) = epath.as_ref() {
ev = (*index.evs.get(epath).unwrap()).clone();
}
} else if let Some(path) = fdpath.as_ref() {
epath = Some(path.new_enum(ev.name.clone().unwrap_or_else(|| path.name.clone())));
}
lookup_results.push((ev, epath));
}
let mut evs_r = None;
let brief_suffix = if let Field::Array(_, de) = &f {
if let Some(range) = de.indexes_as_range() {
format!("({}-{})", *range.start(), *range.end())
} else {
let suffixes: Vec<_> = de.indexes().collect();
format!("({})", suffixes.join(","))
}
} else {
String::new()
};
if can_read {
let cast = if width == 1 {
quote! { != 0 }
} else {
quote! { as #fty }
};
let value = if offset != 0 {
let offset = &unsuffixed(offset);
quote! { (self.bits >> #offset) }
} else {
quote! { self.bits }
};
let value = if use_mask && use_cast {
quote! { (#value & #hexmask) #cast }
} else if use_mask {
quote! { #value & #hexmask }
} else {
value
};
let field_reader_brief = format!("Field `{name}{brief_suffix}` reader - {description}");
let value_read_ty = if let Some((evs, _)) = lookup_filter(&lookup_results, Usage::Read)
{
if let Some(enum_name) = &evs.name {
format!("{enum_name}_A").to_constant_case_ident(span)
} else {
Ident::new(&format!("{name_constant_case}_A"), span)
}
} else {
fty.clone()
};
let reader_ty = Ident::new(&(name_constant_case.clone() + "_R"), span);
let should_derive_reader = matches!(
lookup_filter(&lookup_results, Usage::Read),
Some((_, None)) | None
);
if should_derive_reader {
let reader = if width == 1 {
if value_read_ty == "bool" {
quote! { crate::BitReader }
} else {
quote! { crate::BitReader<#value_read_ty> }
}
} else if value_read_ty == "u8" {
quote! { crate::FieldReader }
} else {
quote! { crate::FieldReader<#value_read_ty> }
};
let mut readerdoc = field_reader_brief.clone();
if let Some(action) = f.read_action {
readerdoc += match action {
ReadAction::Clear => "\n\nThe field is **cleared** (set to zero) following a read operation.",
ReadAction::Set => "\n\nThe field is **set** (set to ones) following a read operation.",
ReadAction::Modify => "\n\nThe field is **modified** in some way after a read operation.",
ReadAction::ModifyExternal => "\n\nOne or more dependent resources other than the current field are immediately affected by a read operation.",
};
}
mod_items.extend(quote! {
#[doc = #readerdoc]
pub type #reader_ty = #reader;
});
}
let mut enum_items = TokenStream::new();
if let Some((evs, None)) = lookup_filter(&lookup_results, Usage::Read) {
evs_r = Some(evs);
let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
let map = enums_to_map(evs);
let mut def = evs
.default_value()
.and_then(|def| {
minimal_hole(&map, width)
.map(|v| Variant::from_value(v, def, config.pascal_enum_values))
})
.transpose()?;
if variants.len() == 1 << width {
def = None;
} else if variants.len() == (1 << width) - 1 {
if let Some(def) = def.take() {
variants.push(def);
}
}
if variants.is_empty() {
add_with_no_variants(mod_items, &value_read_ty, &fty, &description, rv, config);
} else {
let has_reserved_variant;
if let Some(def) = def.as_ref() {
add_from_variants(
mod_items,
variants.iter().chain(std::iter::once(def)),
&value_read_ty,
&fty,
&description,
rv,
config,
);
has_reserved_variant = false;
} else {
add_from_variants(
mod_items,
variants.iter(),
&value_read_ty,
&fty,
&description,
rv,
config,
);
has_reserved_variant = evs.values.len() != (1 << width);
}
let mut arms = TokenStream::new();
for v in variants.iter().map(|v| {
let i = util::unsuffixed_or_bool(v.value, width);
let pc = &v.pc;
if has_reserved_variant {
quote! { #i => Some(#value_read_ty::#pc), }
} else {
quote! { #i => #value_read_ty::#pc, }
}
}) {
arms.extend(v);
}
if has_reserved_variant {
arms.extend(quote! {
_ => None,
});
} else if let Some(v) = def.as_ref() {
let pc = &v.pc;
arms.extend(quote! {
_ => #value_read_ty::#pc,
});
} else if 1 << width.to_ty_width()? != variants.len() {
arms.extend(quote! {
_ => unreachable!(),
});
}
let ret_ty = if has_reserved_variant {
quote!(Option<#value_read_ty>)
} else {
quote!(#value_read_ty)
};
enum_items.extend(quote! {
#[doc = "Get enumerated values variant"]
#inline
pub const fn variant(&self) -> #ret_ty {
match self.bits {
#arms
}
}
});
for v in &variants {
let pc = &v.pc;
let sc = &v.nksc;
let is_variant = Ident::new(
&if sc.to_string().starts_with('_') {
format!("is{sc}")
} else {
format!("is_{sc}")
},
span,
);
let doc = util::escape_special_chars(&util::respace(&v.doc));
enum_items.extend(quote! {
#[doc = #doc]
#inline
pub fn #is_variant(&self) -> bool {
*self == #value_read_ty::#pc
}
});
}
if let Some(v) = def.as_ref() {
let pc = &v.pc;
let sc = &v.nksc;
let is_variant = Ident::new(
&if sc.to_string().starts_with('_') {
format!("is{sc}")
} else {
format!("is_{sc}")
},
span,
);
let doc = util::escape_special_chars(&util::respace(&v.doc));
enum_items.extend(quote! {
#[doc = #doc]
#inline
pub fn #is_variant(&self) -> bool {
matches!(self.variant(), #value_read_ty::#pc)
}
});
}
}
}
if let Some((evs, Some(base))) = lookup_filter(&lookup_results, Usage::Read) {
evs_r = Some(evs);
let base_field = util::replace_suffix(&base.field.name, "");
let base_r = (base_field + "_R").to_constant_case_ident(span);
if !reader_derives.contains(&reader_ty) {
let base_path = base_syn_path(base, &fpath, &base_r)?;
mod_items.extend(quote! {
#[doc = #field_reader_brief]
pub use #base_path as #reader_ty;
});
reader_derives.insert(reader_ty.clone());
}
if base.register() != fpath.register() {
if !enum_derives.contains(&value_read_ty) {
let base_path = base_syn_path(base, &fpath, &value_read_ty)?;
mod_items.extend(quote! {
#[doc = #description]
pub use #base_path as #value_read_ty;
});
enum_derives.insert(value_read_ty.clone());
}
}
}
if let Field::Array(f, de) = &f {
let increment = de.dim_increment;
let doc = util::replace_suffix(&description, &brief_suffix);
let first_name = svd::array::names(f, de).next().unwrap();
let note = format!("NOTE: `n` is number of field in register. `n == 0` corresponds to `{first_name}` field");
let offset_calc = calculate_offset(increment, offset, true);
let value = quote! { ((self.bits >> #offset_calc) & #hexmask) #cast };
let dim = unsuffixed(de.dim);
let name_snake_case_iter = Ident::new(&format!("{name_snake_case}_iter"), span);
r_impl_items.extend(quote! {
#[doc = #doc]
#[doc = ""]
#[doc = #note]
#inline
pub fn #name_snake_case(&self, n: u8) -> #reader_ty {
#[allow(clippy::no_effect)]
[(); #dim][n as usize];
#reader_ty::new ( #value )
}
#[doc = "Iterator for array of:"]
#[doc = #doc]
#inline
pub fn #name_snake_case_iter(&self) -> impl Iterator<Item = #reader_ty> + '_ {
(0..#dim).map(|n| #reader_ty::new ( #value ))
}
});
for fi in svd::field::expand(f, de) {
let sub_offset = fi.bit_offset() as u64;
let value = if sub_offset != 0 {
let sub_offset = &unsuffixed(sub_offset);
quote! { (self.bits >> #sub_offset) }
} else {
quote! { self.bits }
};
let value = if use_mask && use_cast {
quote! { (#value & #hexmask) #cast }
} else if use_mask {
quote! { #value & #hexmask }
} else {
value
};
let name_snake_case_n = fi.name.to_snake_case_ident(Span::call_site());
let doc = description_with_bits(
fi.description.as_deref().unwrap_or(&fi.name),
sub_offset,
width,
);
r_impl_items.extend(quote! {
#[doc = #doc]
#inline
pub fn #name_snake_case_n(&self) -> #reader_ty {
#reader_ty::new ( #value )
}
});
}
} else {
let doc = description_with_bits(description_raw, offset, width);
r_impl_items.extend(quote! {
#[doc = #doc]
#inline
pub fn #name_snake_case(&self) -> #reader_ty {
#reader_ty::new ( #value )
}
});
}
if !enum_items.is_empty() {
mod_items.extend(quote! {
impl #reader_ty {
#enum_items
}
});
}
}
if can_write {
let mwv = f.modified_write_values.or(rmwv).unwrap_or_default();
let field_writer_brief = format!("Field `{name}{brief_suffix}` writer - {description}");
let value_write_ty =
if let Some((evs, _)) = lookup_filter(&lookup_results, Usage::Write) {
let writer_reader_different_enum = evs_r != Some(evs);
let ty_suffix = if writer_reader_different_enum {
"AW"
} else {
"A"
};
if let Some(enum_name) = &evs.name {
format!("{enum_name}_{ty_suffix}").to_constant_case_ident(span)
} else {
Ident::new(&format!("{name_constant_case}_{ty_suffix}"), span)
}
} else {
fty.clone()
};
let writer_ty = Ident::new(&(name_constant_case.clone() + "_W"), span);
let mut proxy_items = TokenStream::new();
let mut unsafety = unsafety(f.write_constraint.as_ref(), width);
if let Some((evs, None)) = lookup_filter(&lookup_results, Usage::Write) {
let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
let map = enums_to_map(evs);
let mut def = evs
.default_value()
.and_then(|def| {
minimal_hole(&map, width)
.map(|v| Variant::from_value(v, def, config.pascal_enum_values))
})
.transpose()?;
if variants.len() == 1 << width {
} else if let Some(def) = def.take() {
variants.push(def);
unsafety = false;
}
if variants.len() == 1 << width {
unsafety = false;
}
let writer_reader_different_enum = evs_r != Some(evs);
if writer_reader_different_enum {
if variants.is_empty() {
add_with_no_variants(
mod_items,
&value_write_ty,
&fty,
&description,
rv,
config,
);
} else {
add_from_variants(
mod_items,
variants.iter(),
&value_write_ty,
&fty,
&description,
rv,
config,
);
}
}
for v in &variants {
let pc = &v.pc;
let sc = &v.sc;
let doc = util::escape_special_chars(&util::respace(&v.doc));
proxy_items.extend(quote! {
#[doc = #doc]
#inline
pub fn #sc(self) -> &'a mut crate::W<REG> {
self.variant(#value_write_ty::#pc)
}
});
}
}
let should_derive_writer = matches!(
lookup_filter(&lookup_results, Usage::Write),
Some((_, None)) | None
);
if should_derive_writer {
let proxy = if width == 1 {
use ModifiedWriteValues::*;
let wproxy = Ident::new(
match mwv {
Modify | Set | Clear => "BitWriter",
OneToSet => "BitWriter1S",
ZeroToClear => "BitWriter0C",
OneToClear => "BitWriter1C",
ZeroToSet => "BitWriter0C",
OneToToggle => "BitWriter1T",
ZeroToToggle => "BitWriter0T",
},
span,
);
if value_write_ty == "bool" {
quote! { crate::#wproxy<'a, REG> }
} else {
quote! { crate::#wproxy<'a, REG, #value_write_ty> }
}
} else {
let wproxy = Ident::new(
if unsafety {
"FieldWriter"
} else {
"FieldWriterSafe"
},
span,
);
let width = &unsuffixed(width);
if value_write_ty == "u8" {
quote! { crate::#wproxy<'a, REG, #width> }
} else {
quote! { crate::#wproxy<'a, REG, #width, #value_write_ty> }
}
};
mod_items.extend(quote! {
#[doc = #field_writer_brief]
pub type #writer_ty<'a, REG> = #proxy;
});
}
if !proxy_items.is_empty() {
mod_items.extend(if width == 1 {
quote! {
impl<'a, REG> #writer_ty<'a, REG>
where
REG: crate::Writable + crate::RegisterSpec,
{
#proxy_items
}
}
} else {
quote! {
impl<'a, REG> #writer_ty<'a, REG>
where
REG: crate::Writable + crate::RegisterSpec,
REG::Ux: From<#fty>
{
#proxy_items
}
}
});
}
if let Some((evs, Some(base))) = lookup_filter(&lookup_results, Usage::Write) {
let base_field = util::replace_suffix(&base.field.name, "");
let base_w = (base_field + "_W").to_constant_case_ident(span);
if !writer_derives.contains(&writer_ty) {
let base_path = base_syn_path(base, &fpath, &base_w)?;
mod_items.extend(quote! {
#[doc = #field_writer_brief]
pub use #base_path as #writer_ty;
});
writer_derives.insert(writer_ty.clone());
}
if base.register() != fpath.register() {
let writer_reader_different_enum = evs_r != Some(evs);
if writer_reader_different_enum {
if !writer_enum_derives.contains(&value_write_ty) {
let base_path = base_syn_path(base, &fpath, &value_write_ty)?;
mod_items.extend(quote! {
#[doc = #description]
pub use #base_path as #value_write_ty;
});
writer_enum_derives.insert(value_write_ty.clone());
}
}
}
}
if let Field::Array(f, de) = &f {
let increment = de.dim_increment;
let offset_calc = calculate_offset(increment, offset, false);
let doc = &util::replace_suffix(&description, &brief_suffix);
let first_name = svd::array::names(f, de).next().unwrap();
let note = format!("NOTE: `n` is number of field in register. `n == 0` corresponds to `{first_name}` field");
let dim = unsuffixed(de.dim);
w_impl_items.extend(quote! {
#[doc = #doc]
#[doc = ""]
#[doc = #note]
#inline
#[must_use]
pub fn #name_snake_case(&mut self, n: u8) -> #writer_ty<#regspec_ident> {
#[allow(clippy::no_effect)]
[(); #dim][n as usize];
#writer_ty::new(self, #offset_calc)
}
});
for fi in svd::field::expand(f, de) {
let sub_offset = fi.bit_offset() as u64;
let name_snake_case_n = &fi.name.to_snake_case_ident(Span::call_site());
let doc = description_with_bits(
fi.description.as_deref().unwrap_or(&fi.name),
sub_offset,
width,
);
let sub_offset = unsuffixed(sub_offset);
w_impl_items.extend(quote! {
#[doc = #doc]
#inline
#[must_use]
pub fn #name_snake_case_n(&mut self) -> #writer_ty<#regspec_ident> {
#writer_ty::new(self, #sub_offset)
}
});
}
} else {
let doc = description_with_bits(description_raw, offset, width);
let offset = unsuffixed(offset);
w_impl_items.extend(quote! {
#[doc = #doc]
#inline
#[must_use]
pub fn #name_snake_case(&mut self) -> #writer_ty<#regspec_ident> {
#writer_ty::new(self, #offset)
}
});
}
let bitmask = (u64::MAX >> (64 - width)) << offset;
use ModifiedWriteValues::*;
match mwv {
Modify | Set | Clear => {}
OneToSet | OneToClear | OneToToggle => {
one_to_modify_fields_bitmap |= bitmask;
}
ZeroToClear | ZeroToSet | ZeroToToggle => {
zero_to_modify_fields_bitmap |= bitmask;
}
}
}
}
Ok((
r_impl_items,
w_impl_items,
zero_to_modify_fields_bitmap,
one_to_modify_fields_bitmap,
))
}
fn unsafety(write_constraint: Option<&WriteConstraint>, width: u32) -> bool {
match &write_constraint {
Some(&WriteConstraint::Range(range))
if range.min == 0 && range.max == u64::MAX >> (64 - width) =>
{
false
}
None if width == 1 => {
false
}
_ => true,
}
}
struct Variant {
doc: String,
pc: Ident,
nksc: Ident,
sc: Ident,
value: u64,
}
impl Variant {
fn from_enumerated_values(evs: &EnumeratedValues, pc: bool) -> Result<Vec<Self>> {
evs.values
.iter()
.filter(|ev| ev.name.to_lowercase() != "reserved" && !ev.is_default())
.map(|ev| {
let value = ev
.value
.ok_or_else(|| anyhow!("EnumeratedValue {} has no `<value>` entry", ev.name))?;
Self::from_value(value, ev, pc)
})
.collect::<Result<Vec<_>>>()
}
fn from_value(value: u64, ev: &EnumeratedValue, pc: bool) -> Result<Self> {
let span = Span::call_site();
let nksc = ev.name.to_sanitized_not_keyword_snake_case();
let sc = util::sanitize_keyword(nksc.clone());
Ok(Variant {
doc: ev
.description
.clone()
.unwrap_or_else(|| format!("`{value:b}`")),
pc: if pc {
ev.name.to_pascal_case_ident(span)
} else {
ev.name.to_constant_case_ident(span)
},
nksc: Ident::new(&nksc, span),
sc: Ident::new(&sc, span),
value,
})
}
}
fn add_with_no_variants(
mod_items: &mut TokenStream,
pc: &Ident,
fty: &Ident,
desc: &str,
reset_value: Option<u64>,
config: &Config,
) {
let defmt = config
.impl_defmt
.as_ref()
.map(|feature| quote!(#[cfg_attr(feature = #feature, derive(defmt::Format))]));
let cast = if fty == "bool" {
quote! { val.0 as u8 != 0 }
} else {
quote! { val.0 as _ }
};
let desc = if let Some(rv) = reset_value {
format!("{desc}\n\nValue on reset: {rv}")
} else {
desc.to_string()
};
mod_items.extend(quote! {
#[doc = #desc]
#defmt
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct #pc(#fty);
impl From<#pc> for #fty {
#[inline(always)]
fn from(val: #pc) -> Self {
#cast
}
}
});
if fty != "bool" {
mod_items.extend(quote! {
impl crate::FieldSpec for #pc {
type Ux = #fty;
}
});
}
}
fn add_from_variants<'a>(
mod_items: &mut TokenStream,
variants: impl Iterator<Item = &'a Variant>,
pc: &Ident,
fty: &Ident,
desc: &str,
reset_value: Option<u64>,
config: &Config,
) {
let defmt = config
.impl_defmt
.as_ref()
.map(|feature| quote!(#[cfg_attr(feature = #feature, derive(defmt::Format))]));
let (repr, cast) = if fty == "bool" {
(quote! {}, quote! { variant as u8 != 0 })
} else {
(quote! { #[repr(#fty)] }, quote! { variant as _ })
};
let mut vars = TokenStream::new();
for v in variants.map(|v| {
let desc = util::escape_special_chars(&util::respace(&format!("{}: {}", v.value, v.doc)));
let pcv = &v.pc;
let pcval = &unsuffixed(v.value);
quote! {
#[doc = #desc]
#pcv = #pcval,
}
}) {
vars.extend(v);
}
let desc = if let Some(rv) = reset_value {
format!("{desc}\n\nValue on reset: {rv}")
} else {
desc.to_string()
};
mod_items.extend(quote! {
#[doc = #desc]
#defmt
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#repr
pub enum #pc {
#vars
}
impl From<#pc> for #fty {
#[inline(always)]
fn from(variant: #pc) -> Self {
#cast
}
}
});
if fty != "bool" {
mod_items.extend(quote! {
impl crate::FieldSpec for #pc {
type Ux = #fty;
}
});
}
}
fn calculate_offset(increment: u32, offset: u64, with_parentheses: bool) -> TokenStream {
let mut res = quote! { n };
if increment != 1 {
let increment = unsuffixed(increment);
res = quote! { #res * #increment };
}
if offset != 0 {
let offset = &unsuffixed(offset);
res = quote! { #res + #offset };
}
let single_ident = (increment == 1) && (offset == 0);
if with_parentheses && !single_ident {
quote! { (#res) }
} else {
res
}
}
fn description_with_bits(description: &str, offset: u64, width: u32) -> String {
let mut res = if width == 1 {
format!("Bit {offset}")
} else {
format!("Bits {offset}:{}", offset + width as u64 - 1)
};
if !description.is_empty() {
res.push_str(" - ");
res.push_str(&util::respace(&util::escape_special_chars(description)));
}
res
}
fn base_syn_path(
base: &EnumPath,
fpath: &FieldPath,
base_ident: &Ident,
) -> Result<syn::TypePath, syn::Error> {
let span = Span::call_site();
let path = if base.register() == fpath.register() {
ident_to_path(base_ident.clone())
} else if base.register().block == fpath.register().block {
let mut segments = Punctuated::new();
segments.push(path_segment(Ident::new("super", span)));
segments.push(path_segment(base.register().name.to_snake_case_ident(span)));
segments.push(path_segment(base_ident.clone()));
type_path(segments)
} else {
let mut rmod_ = crate::util::register_path_to_ty(base.register(), span);
rmod_.path.segments.push(path_segment(base_ident.clone()));
rmod_
};
Ok(path)
}
fn lookup_filter(
evs: &[(EnumeratedValues, Option<EnumPath>)],
usage: Usage,
) -> Option<&(EnumeratedValues, Option<EnumPath>)> {
evs.iter()
.find(|evsbase| evsbase.0.usage == Some(usage))
.or_else(|| evs.first())
}
fn enums_to_map(evs: &EnumeratedValues) -> BTreeMap<u64, &EnumeratedValue> {
let mut map = BTreeMap::new();
for ev in &evs.values {
if let Some(v) = ev.value {
map.insert(v, ev);
}
}
map
}
fn minimal_hole(map: &BTreeMap<u64, &EnumeratedValue>, width: u32) -> Option<u64> {
(0..(1u64 << width)).find(|&v| !map.contains_key(&v))
}