From d1f2458825bdbbf560e121556a00ca6a8fda9947 Mon Sep 17 00:00:00 2001 From: Andrey Zgarbul Date: Tue, 2 Jan 2024 10:27:12 +0300 Subject: [PATCH] Custom prefix/case/suffix for identifiers --- CHANGELOG.md | 2 + src/config.rs | 100 ++++++++++++++++++++++++++++++++++++- src/generate/device.rs | 21 ++++---- src/generate/interrupt.rs | 15 +++--- src/generate/peripheral.rs | 32 ++++++------ src/generate/register.rs | 67 +++++++++++-------------- src/main.rs | 6 ++- src/util.rs | 32 +++++++----- 8 files changed, 189 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2acc92e3..cf546140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +- Custom prefix/case/suffix for identifiers + ## [v0.31.3] - 2023-12-25 - Add `svd::Device` validation after parsing by `serde` diff --git a/src/config.rs b/src/config.rs index 7656bf43..084debfb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,8 @@ use anyhow::{bail, Result}; use std::path::{Path, PathBuf}; -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(default))] #[derive(Clone, PartialEq, Eq, Debug, Default)] -#[cfg_attr(feature = "serde", serde(default))] pub struct Config { pub target: Target, pub atomics: bool, @@ -28,6 +27,7 @@ pub struct Config { pub interrupt_link_section: Option, pub reexport_core_peripherals: bool, pub reexport_interrupt: bool, + pub ident_formats: IdentFormats, } #[allow(clippy::upper_case_acronyms)] @@ -116,3 +116,99 @@ impl SourceType { .unwrap_or_default() } } + +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize), + serde(rename_all = "lowercase") +)] +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub enum Case { + #[default] + Constant, + Pascal, + Snake, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(default))] +pub struct IdentFormat { + pub case: Option, + pub prefix: String, + pub suffix: String, +} + +impl IdentFormat { + pub fn case(mut self, case: Case) -> Self { + self.case = Some(case); + self + } + pub fn constant_case(mut self) -> Self { + self.case = Some(Case::Constant); + self + } + pub fn pascal_case(mut self) -> Self { + self.case = Some(Case::Pascal); + self + } + pub fn scake_case(mut self) -> Self { + self.case = Some(Case::Pascal); + self + } + pub fn prefix(mut self, prefix: &str) -> Self { + self.prefix = prefix.into(); + self + } + pub fn suffix(mut self, suffix: &str) -> Self { + self.suffix = suffix.into(); + self + } + pub fn parse(s: &str) -> Result { + let mut it = s.split(":"); + match (it.next(), it.next(), it.next(), it.next()) { + (Some(prefix), Some(case), Some(suffix), None) => { + let case = match case { + "C" | "CONSTANT" => Some(Case::Constant), + "P" | "Pascal" => Some(Case::Pascal), + "S" | "snake" => Some(Case::Snake), + "_" => None, + _ => return Err(()), + }; + Ok(Self { + case, + prefix: prefix.into(), + suffix: suffix.into(), + }) + } + _ => Err(()), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(default))] +pub struct IdentFormats { + pub field_reader: IdentFormat, + pub field_writer: IdentFormat, + pub enum_name: IdentFormat, + pub enum_write_name: IdentFormat, + pub enum_value: IdentFormat, + pub interrupt: IdentFormat, + pub register_spec: IdentFormat, + pub peripheral: IdentFormat, +} + +impl Default for IdentFormats { + fn default() -> Self { + Self { + field_reader: IdentFormat::default().constant_case().suffix("_R"), + field_writer: IdentFormat::default().constant_case().suffix("_W"), + enum_name: IdentFormat::default().constant_case().suffix("_A"), + enum_write_name: IdentFormat::default().constant_case().suffix("_AW"), + enum_value: IdentFormat::default().constant_case(), + interrupt: IdentFormat::default().constant_case(), + register_spec: IdentFormat::default().constant_case().suffix("_SPEC"), + peripheral: IdentFormat::default().constant_case(), + } + } +} diff --git a/src/generate/device.rs b/src/generate/device.rs index 4683e2cd..8ce9105a 100644 --- a/src/generate/device.rs +++ b/src/generate/device.rs @@ -1,5 +1,5 @@ use crate::svd::{array::names, Device, Peripheral}; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use log::debug; @@ -9,7 +9,7 @@ use std::io::Write; use std::path::Path; use crate::config::{Config, Target}; -use crate::util::{self, ToSanitizedCase}; +use crate::util::{self, ident, ToSanitizedCase}; use anyhow::{Context, Result}; use crate::generate::{interrupt, peripheral}; @@ -231,26 +231,27 @@ pub fn render(d: &Device, config: &Config, device_x: &mut String) -> Result { let p_name = util::name_of(p, config.ignore_groups); let p_snake = p_name.to_sanitized_snake_case(); - let p = p_name.to_sanitized_constant_case(); - let id = Ident::new(&p, Span::call_site()); + let p_ty = ident(&p_name, &config.ident_formats.peripheral, span); if config.feature_peripheral { feature_attribute.extend(quote! { #[cfg(feature = #p_snake)] }) }; fields.extend(quote! { - #[doc = #p] + #[doc = #p_name] #feature_attribute - pub #id: #id, + pub #p_ty: #p_ty, }); - exprs.extend(quote!(#feature_attribute #id: #id { _marker: PhantomData },)); + exprs.extend(quote!(#feature_attribute #p_ty: #p_ty { _marker: PhantomData },)); } Peripheral::Array(_p, dim_element) => { let p_names: Vec> = names(p, dim_element).map(|n| n.into()).collect(); - let p = p_names.iter().map(|p| p.to_sanitized_constant_case()); - let ids_f = p.clone().map(|p| Ident::new(&p, Span::call_site())); + let ids_f = p_names + .iter() + .map(|p| ident(p, &config.ident_formats.peripheral, span)); let ids_e = ids_f.clone(); let feature_attribute = p_names .iter() @@ -265,7 +266,7 @@ pub fn render(d: &Device, config: &Config, device_x: &mut String) -> Result>(); fields.extend(quote! { #( - #[doc = #p] + #[doc = #p_names] #feature_attribute pub #ids_f: #ids_f, )* diff --git a/src/generate/interrupt.rs b/src/generate/interrupt.rs index c54e1d6b..8c1a2ed6 100644 --- a/src/generate/interrupt.rs +++ b/src/generate/interrupt.rs @@ -5,7 +5,7 @@ use crate::svd::Peripheral; use proc_macro2::{Span, TokenStream}; use quote::quote; -use crate::util::{self, ToSanitizedCase}; +use crate::util::{self, ident, ToSanitizedCase}; use crate::{Config, Target}; use anyhow::Result; @@ -46,6 +46,7 @@ pub fn render( // Current position in the vector table let mut pos = 0; let mut mod_items = TokenStream::new(); + let span = Span::call_site(); for interrupt in &interrupts { while pos < interrupt.0.value { elements.extend(quote!(Vector { _reserved: 0 },)); @@ -53,7 +54,7 @@ pub fn render( } pos += 1; - let name_constant_case = interrupt.0.name.to_constant_case_ident(Span::call_site()); + let i_ty = ident(&interrupt.0.name, &config.ident_formats.interrupt, span); let description = format!( "{} - {}", interrupt.0.value, @@ -89,12 +90,12 @@ pub fn render( variants.extend(quote! { #[doc = #description] #feature_attribute - #name_constant_case = #value, + #i_ty = #value, }); from_arms.extend(quote! { #feature_attribute - #value => Ok(Interrupt::#name_constant_case), + #value => Ok(Interrupt::#i_ty), }); if feature_attribute_flag { @@ -102,12 +103,12 @@ pub fn render( #not_feature_attribute Vector { _reserved: 0 }, #feature_attribute - Vector { _handler: #name_constant_case }, + Vector { _handler: #i_ty }, }); } else { - elements.extend(quote!(Vector { _handler: #name_constant_case },)); + elements.extend(quote!(Vector { _handler: #i_ty },)); } - names.push(name_constant_case); + names.push(i_ty); names_cfg_attr.push(feature_attribute); } diff --git a/src/generate/peripheral.rs b/src/generate/peripheral.rs index 64b884f2..4318476f 100644 --- a/src/generate/peripheral.rs +++ b/src/generate/peripheral.rs @@ -16,8 +16,8 @@ use quote::{quote, ToTokens}; use syn::{punctuated::Punctuated, Token}; use crate::util::{ - self, name_to_ty, path_segment, type_path, unsuffixed, zst_type, FullName, ToSanitizedCase, - BITS_PER_BYTE, + self, ident, name_to_ty, path_segment, type_path, unsuffixed, zst_type, FullName, + ToSanitizedCase, BITS_PER_BYTE, }; use anyhow::{anyhow, bail, Context, Result}; @@ -38,8 +38,8 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result let name = util::name_of(&p, config.ignore_groups); let span = Span::call_site(); - let name_str = name.to_sanitized_constant_case(); - let name_constant_case = Ident::new(&name_str, span); + let p_ty = ident(&name, &config.ident_formats.peripheral, span); + let name_str = p_ty.to_string(); let address = util::hex(p.base_address); let description = util::respace(p.description.as_ref().unwrap_or(&p.name)); @@ -81,8 +81,8 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result for pi in svd::peripheral::expand(p, dim) { let name = &pi.name; let description = pi.description.as_deref().unwrap_or(&p.name); - let name_str = name.to_sanitized_constant_case(); - let name_constant_case = Ident::new(name, span); + let p_ty = ident(name, &config.ident_formats.peripheral, span); + let name_str = p_ty.to_string(); let address = util::hex(pi.base_address); let p_snake = name.to_sanitized_snake_case(); snake_names.push(p_snake.to_string()); @@ -94,13 +94,13 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result out.extend(quote! { #[doc = #description] #feature_attribute_n - pub struct #name_constant_case { _marker: PhantomData<*const ()> } + pub struct #p_ty { _marker: PhantomData<*const ()> } #feature_attribute_n - unsafe impl Send for #name_constant_case {} + unsafe impl Send for #p_ty {} #feature_attribute_n - impl #name_constant_case { + impl #p_ty { ///Pointer to the register block pub const PTR: *const #base::RegisterBlock = #address as *const _; @@ -114,7 +114,7 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result } #feature_attribute_n - impl Deref for #name_constant_case { + impl Deref for #p_ty { type Target = #base::RegisterBlock; #[inline(always)] @@ -124,7 +124,7 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result } #feature_attribute_n - impl core::fmt::Debug for #name_constant_case { + impl core::fmt::Debug for #p_ty { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct(#name_str).finish() } @@ -155,13 +155,13 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result out.extend(quote! { #[doc = #description] #feature_attribute - pub struct #name_constant_case { _marker: PhantomData<*const ()> } + pub struct #p_ty { _marker: PhantomData<*const ()> } #feature_attribute - unsafe impl Send for #name_constant_case {} + unsafe impl Send for #p_ty {} #feature_attribute - impl #name_constant_case { + impl #p_ty { ///Pointer to the register block pub const PTR: *const #base::RegisterBlock = #address as *const _; @@ -175,7 +175,7 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result } #feature_attribute - impl Deref for #name_constant_case { + impl Deref for #p_ty { type Target = #base::RegisterBlock; #[inline(always)] @@ -185,7 +185,7 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result } #feature_attribute - impl core::fmt::Debug for #name_constant_case { + impl core::fmt::Debug for #p_ty { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct(#name_str).finish() } diff --git a/src/generate/register.rs b/src/generate/register.rs index 7ae49c20..f06ff8b6 100644 --- a/src/generate/register.rs +++ b/src/generate/register.rs @@ -15,12 +15,16 @@ use svd_parser::expand::{ use crate::config::Config; use crate::util::{ - self, ident_to_path, path_segment, replace_suffix, type_path, unsuffixed, FullName, + self, ident, ident_to_path, path_segment, replace_suffix, type_path, unsuffixed, FullName, ToSanitizedCase, U32Ext, }; use anyhow::{anyhow, Result}; use syn::punctuated::Punctuated; +fn regspec(name: &str, config: &Config, span: Span) -> Ident { + ident(name, &config.ident_formats.register_spec, span) +} + pub fn render( register: &Register, path: &BlockPath, @@ -71,7 +75,7 @@ pub fn render( pub use #mod_derived as #name_snake_case; }) } else { - let regspec_ident = format!("{name}_SPEC").to_constant_case_ident(span); + let regspec_ident = regspec(&name, config, span); let access = util::access_of(®ister.properties, register.fields.as_deref()); let accs = if access.can_read() && access.can_write() { "rw" @@ -196,7 +200,7 @@ pub fn render_register_mod( 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 regspec_ident = regspec(&name, config, span); let name_snake_case = name.to_snake_case_ident(span); let rsize = properties .size @@ -437,7 +441,7 @@ fn render_register_mod_debug( ) -> Result { 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 regspec_ident = regspec(&name, config, span); let open = Punct::new('{', Spacing::Alone); let close = Punct::new('}', Spacing::Alone); let mut r_debug_impl = TokenStream::new(); @@ -567,7 +571,6 @@ pub fn fields( } 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(""); // raw description, if absent using empty string let description = util::respace(&util::escape_special_chars(description_raw)); @@ -651,19 +654,18 @@ pub fn fields( // in enumeratedValues if it's an enumeration, or from field name directly if it's not. 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 { - // derived_field_value_read_ty - Ident::new(&format!("{name_constant_case}_A"), span) - } + ident( + evs.name.as_deref().unwrap_or(&name), + &config.ident_formats.enum_name, + span, + ) } else { // raw_field_value_read_ty fty.clone() }; // name of read proxy type - let reader_ty = Ident::new(&(name_constant_case.clone() + "_R"), span); + let reader_ty = ident(&name, &config.ident_formats.field_reader, span); // if it's enumeratedValues and it's derived from base, don't derive the read proxy // as the base has already dealt with this; @@ -715,14 +717,13 @@ pub fn fields( evs_r = Some(evs); // parse enum variants from enumeratedValues svd record - let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?; + let mut variants = Variant::from_enumerated_values(evs, config)?; 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)) + minimal_hole(&map, width).map(|v| Variant::from_value(v, def, config)) }) .transpose()?; if variants.len() == 1 << width { @@ -877,7 +878,7 @@ pub fn fields( evs_r = Some(evs); // generate pub use field_1 reader as field_2 reader let base_field = util::replace_suffix(&base.field.name, ""); - let base_r = (base_field + "_R").to_constant_case_ident(span); + let base_r = ident(&base_field, &config.ident_formats.field_reader, span); if !reader_derives.contains(&reader_ty) { let base_path = base_syn_path(base, &fpath, &base_r)?; mod_items.extend(quote! { @@ -988,24 +989,19 @@ pub fn fields( 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" + let fmt = if writer_reader_different_enum { + &config.ident_formats.enum_write_name } else { - "A" + &config.ident_formats.enum_name }; - if let Some(enum_name) = &evs.name { - format!("{enum_name}_{ty_suffix}").to_constant_case_ident(span) - } else { - // derived_field_value_write_ty - Ident::new(&format!("{name_constant_case}_{ty_suffix}"), span) - } + ident(evs.name.as_deref().unwrap_or(&name), fmt, span) } else { // raw_field_value_write_ty fty.clone() }; // name of write proxy type - let writer_ty = Ident::new(&(name_constant_case.clone() + "_W"), span); + let writer_ty = ident(&name, &config.ident_formats.field_writer, span); let mut proxy_items = TokenStream::new(); let mut unsafety = unsafety(f.write_constraint.as_ref(), width); @@ -1013,13 +1009,12 @@ pub fn fields( // if we writes to enumeratedValues, generate its structure if it differs from read structure. if let Some((evs, None)) = lookup_filter(&lookup_results, Usage::Write) { // parse variants from enumeratedValues svd record - let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?; + let mut variants = Variant::from_enumerated_values(evs, config)?; 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)) + minimal_hole(&map, width).map(|v| Variant::from_value(v, def, config)) }) .transpose()?; if variants.len() == 1 << width { @@ -1158,7 +1153,7 @@ pub fn fields( // generate pub use field_1 writer as field_2 writer let base_field = util::replace_suffix(&base.field.name, ""); - let base_w = (base_field + "_W").to_constant_case_ident(span); + let base_w = ident(&base_field, &config.ident_formats.field_writer, span); if !writer_derives.contains(&writer_ty) { let base_path = base_syn_path(base, &fpath, &base_w)?; mod_items.extend(quote! { @@ -1285,7 +1280,7 @@ struct Variant { } impl Variant { - fn from_enumerated_values(evs: &EnumeratedValues, pc: bool) -> Result> { + fn from_enumerated_values(evs: &EnumeratedValues, config: &Config) -> Result> { evs.values .iter() // filter out all reserved variants, as we should not @@ -1295,11 +1290,11 @@ impl Variant { let value = ev .value .ok_or_else(|| anyhow!("EnumeratedValue {} has no `` entry", ev.name))?; - Self::from_value(value, ev, pc) + Self::from_value(value, ev, config) }) .collect::>>() } - fn from_value(value: u64, ev: &EnumeratedValue, pc: bool) -> Result { + fn from_value(value: u64, ev: &EnumeratedValue, config: &Config) -> Result { let span = Span::call_site(); let nksc = ev.name.to_sanitized_not_keyword_snake_case(); let sc = util::sanitize_keyword(nksc.clone()); @@ -1308,11 +1303,7 @@ impl Variant { .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) - }, + pc: ident(&ev.name, &config.ident_formats.enum_value, span), nksc: Ident::new(&nksc, span), sc: Ident::new(&sc, span), value, diff --git a/src/main.rs b/src/main.rs index 9d1761e0..c9a6f379 100755 --- a/src/main.rs +++ b/src/main.rs @@ -171,12 +171,13 @@ fn run() -> Result<()> { .action(ArgAction::SetTrue) .help("Make advanced checks due to parsing SVD"), ) + // TODO: deprecate .arg( Arg::new("pascal_enum_values") .long("pascal-enum-values") .alias("pascal_enum_values") .action(ArgAction::SetTrue) - .help("Use PascalCase in stead of UPPER_CASE for enumerated values"), + .help("Use PascalCase in stead of CONSTANT_CASE for enumerated values"), ) .arg( Arg::new("source_type") @@ -248,6 +249,9 @@ fn run() -> Result<()> { config.source_type = SourceType::from_path(file) } let path = config.output_dir.as_deref().unwrap_or(Path::new(".")); + if config.pascal_enum_values { + config.ident_formats.enum_value.case = Some(svd2rust::config::Case::Pascal); + } info!("Parsing device from SVD file"); let device = load_from(input, &config)?; diff --git a/src/util.rs b/src/util.rs index 547d879a..fd80df55 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +pub use crate::config::{Case, IdentFormat}; use crate::svd::{Access, Device, Field, RegisterInfo, RegisterProperties}; use html_escape::encode_text_minimal; use inflections::Inflect; @@ -20,13 +21,6 @@ pub const BITS_PER_BYTE: u32 = 8; /// that are not valid in Rust ident const BLACKLIST_CHARS: &[char] = &['(', ')', '[', ']', '/', ' ', '-']; -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Case { - Constant, - Pascal, - Snake, -} - impl Case { pub fn cow_to_case<'a>(&self, cow: Cow<'a, str>) -> Cow<'a, str> { match self { @@ -45,16 +39,30 @@ impl Case { } } pub fn sanitize<'a>(&self, s: &'a str) -> Cow<'a, str> { - let s = if s.contains(BLACKLIST_CHARS) { - Cow::Owned(s.replace(BLACKLIST_CHARS, "")) - } else { - s.into() - }; + let s = sanitize(s); self.cow_to_case(s) } } +fn sanitize(s: &str) -> Cow<'_, str> { + if s.contains(BLACKLIST_CHARS) { + Cow::Owned(s.replace(BLACKLIST_CHARS, "")) + } else { + s.into() + } +} + +pub fn ident(name: &str, fmt: &IdentFormat, span: Span) -> Ident { + let name = match &fmt.case { + Some(Case::Constant) => name.to_sanitized_constant_case(), + Some(Case::Pascal) => name.to_sanitized_pascal_case(), + Some(Case::Snake) => name.to_sanitized_snake_case(), + _ => sanitize(name), + }; + Ident::new(&format!("{}{}{}", fmt.prefix, name, fmt.suffix), span) +} + /// Convert self string into specific case without overlapping to svd2rust internal names pub trait ToSanitizedCase { /// Convert self into PascalCase.