From 4cdb264be1fe6703bf9338142e7a1c3eca0b6e42 Mon Sep 17 00:00:00 2001 From: Andrey Zgarbul Date: Sat, 3 Feb 2024 14:03:23 +0300 Subject: [PATCH] add ident-format option, fix identificator bugs --- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 2 + Cargo.toml | 2 +- README.md | 2 +- ci/svd2rust-regress/src/svd_test.rs | 6 +- src/config.rs | 103 +++++++++++---- src/generate/device.rs | 31 +++-- src/generate/interrupt.rs | 9 +- src/generate/peripheral.rs | 142 ++++++++++++-------- src/generate/register.rs | 172 ++++++++++++++---------- src/lib.rs | 35 ++++- src/main.rs | 74 ++++++++--- src/util.rs | 196 ++++++++++++++++------------ 13 files changed, 498 insertions(+), 282 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d239663..0ac4693e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: include: - { rust: stable, vendor: Atmel, options: all } - { rust: stable, vendor: Atmel, options: "" } - - { rust: stable, vendor: Freescale, options: all } + - { rust: stable, vendor: Freescale, options: "--strict --atomics" } - { rust: stable, vendor: Freescale, options: "" } - { rust: stable, vendor: Fujitsu, options: "" } - { rust: stable, vendor: Fujitsu, options: "--atomics" } @@ -80,11 +80,11 @@ jobs: - { rust: stable, vendor: Spansion, options: "--atomics" } - { rust: stable, vendor: STMicro, options: "" } - { rust: stable, vendor: STMicro, options: "--atomics" } - - { rust: stable, vendor: STM32-patched, options: "--strict --pascal-enum-values --max-cluster-size --atomics --atomics-feature atomics --impl-debug --impl-defmt defmt" } + - { rust: stable, vendor: STM32-patched, options: "--strict -f enum_value::p: --max-cluster-size --atomics --atomics-feature atomics --impl-debug --impl-defmt defmt" } - { rust: stable, vendor: Toshiba, options: all } - { rust: stable, vendor: Toshiba, options: "" } # Test MSRV - - { rust: 1.70.0, vendor: Nordic, options: "" } + - { rust: 1.74.0, vendor: Nordic, options: "" } # Use nightly for architectures which don't support stable - { rust: nightly, vendor: MSP430, options: "--atomics" } - { rust: nightly, vendor: MSP430, options: "" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 098ac4de..b49cd3d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +- Bump MSRV to 1.74 - Add `base-address-shift` config flag +- Fix case changing bugs, add `--ident-format` (`-f`) option flag ## [v0.31.5] - 2024-01-04 diff --git a/Cargo.toml b/Cargo.toml index 28418c58..b5aa0cc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ name = "svd2rust" repository = "https://github.com/rust-embedded/svd2rust/" version = "0.31.5" readme = "README.md" -rust-version = "1.70" +rust-version = "1.74" [package.metadata.deb] section = "rust" diff --git a/README.md b/README.md index d2f06d4d..f1f02e9e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![GitHub top language](https://img.shields.io/github/languages/top/rust-embedded/svd2rust) -![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.70+-blue.svg) +![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.74+-blue.svg) [![crates.io](https://img.shields.io/crates/v/svd2rust.svg)](https://crates.io/crates/svd2rust) [![crates.io](https://img.shields.io/crates/d/svd2rust.svg)](https://crates.io/crates/svd2rust) [![Released API docs](https://docs.rs/svd2rust/badge.svg)](https://docs.rs/svd2rust) diff --git a/ci/svd2rust-regress/src/svd_test.rs b/ci/svd2rust-regress/src/svd_test.rs index 6f1e0d9f..5ac8bbd9 100644 --- a/ci/svd2rust-regress/src/svd_test.rs +++ b/ci/svd2rust-regress/src/svd_test.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Context, Result}; -use svd2rust::{util::ToSanitizedCase, Target}; +use svd2rust::{util::Case, Target}; use crate::{command::CommandExt, tests::TestCase, Opts, TestAll}; use std::io::prelude::*; @@ -176,7 +176,7 @@ impl TestCase { Ok(val) => val, Err(_) => "rusttester".into(), }; - let chip_dir = output_dir.join(self.name().to_sanitized_snake_case().as_ref()); + let chip_dir = output_dir.join(Case::Snake.sanitize(&self.name()).as_ref()); tracing::span::Span::current() .record("chip_dir", tracing::field::display(chip_dir.display())); if let Err(err) = fs::remove_dir_all(&chip_dir) { @@ -195,7 +195,7 @@ impl TestCase { .env("USER", user) .arg("init") .arg("--name") - .arg(self.name().to_sanitized_snake_case().as_ref()) + .arg(Case::Snake.sanitize(&self.name()).as_ref()) .arg("--vcs") .arg("none") .arg(&chip_dir) diff --git a/src/config.rs b/src/config.rs index 644e59a1..c188940a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,13 @@ use anyhow::{bail, Result}; -use std::path::{Path, PathBuf}; +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, +}; #[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(default))] #[derive(Clone, PartialEq, Eq, Debug, Default)] +#[non_exhaustive] pub struct Config { pub target: Target, pub atomics: bool, @@ -13,7 +18,6 @@ pub struct Config { pub ignore_groups: bool, pub keep_list: bool, pub strict: bool, - pub pascal_enum_values: bool, pub feature_group: bool, pub feature_peripheral: bool, pub max_cluster_size: bool, @@ -135,6 +139,7 @@ pub enum Case { #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(default))] pub struct IdentFormat { + // Ident case. `None` means don't change pub case: Option, pub prefix: String, pub suffix: String, @@ -153,8 +158,8 @@ impl IdentFormat { self.case = Some(Case::Pascal); self } - pub fn scake_case(mut self) -> Self { - self.case = Some(Case::Pascal); + pub fn snake_case(mut self) -> Self { + self.case = Some(Case::Snake); self } pub fn prefix(mut self, prefix: &str) -> Self { @@ -169,32 +174,74 @@ impl IdentFormat { #[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 cluster: IdentFormat, - pub register: IdentFormat, - pub register_spec: IdentFormat, - pub peripheral: IdentFormat, -} +pub struct IdentFormats(HashMap); 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(), - cluster: IdentFormat::default().constant_case(), - register: IdentFormat::default().constant_case(), - register_spec: IdentFormat::default().constant_case().suffix("_SPEC"), - peripheral: IdentFormat::default().constant_case(), - } + let mut map = HashMap::new(); + + map.insert("field_accessor".into(), IdentFormat::default().snake_case()); + map.insert( + "field_reader".into(), + IdentFormat::default().constant_case().suffix("_R"), + ); + map.insert( + "field_writer".into(), + IdentFormat::default().constant_case().suffix("_W"), + ); + map.insert( + "enum_name".into(), + IdentFormat::default().constant_case().suffix("_A"), + ); + map.insert( + "enum_write_name".into(), + IdentFormat::default().constant_case().suffix("_AW"), + ); + map.insert("enum_value".into(), IdentFormat::default().constant_case()); + map.insert( + "enum_value_accessor".into(), + IdentFormat::default().snake_case(), + ); + map.insert("interrupt".into(), IdentFormat::default().constant_case()); + map.insert("cluster".into(), IdentFormat::default().constant_case()); + map.insert( + "cluster_accessor".into(), + IdentFormat::default().snake_case(), + ); + map.insert("cluster_mod".into(), IdentFormat::default().snake_case()); + map.insert("register".into(), IdentFormat::default().constant_case()); + map.insert( + "register_spec".into(), + IdentFormat::default().pascal_case().suffix("_SPEC"), + ); + map.insert( + "register_accessor".into(), + IdentFormat::default().snake_case(), + ); + map.insert("register_mod".into(), IdentFormat::default().snake_case()); + map.insert("peripheral".into(), IdentFormat::default().constant_case()); + map.insert( + "peripheral_singleton".into(), + IdentFormat::default().constant_case(), + ); + map.insert("peripheral_mod".into(), IdentFormat::default().snake_case()); + map.insert( + "peripheral_feature".into(), + IdentFormat::default().snake_case(), + ); + + Self(map) + } +} + +impl Deref for IdentFormats { + type Target = HashMap; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for IdentFormats { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } diff --git a/src/generate/device.rs b/src/generate/device.rs index 5251c7ba..fc87fef2 100644 --- a/src/generate/device.rs +++ b/src/generate/device.rs @@ -8,7 +8,7 @@ use std::io::Write; use std::path::Path; use crate::config::{Config, Target}; -use crate::util::{self, ident, ToSanitizedCase}; +use crate::util::{self, ident}; use anyhow::{Context, Result}; use crate::generate::{interrupt, peripheral}; @@ -192,6 +192,7 @@ pub fn render(d: &Device, config: &Config, device_x: &mut String) -> Result Result Result { let p_name = util::name_of(p, config.ignore_groups); - let p_snake = p_name.to_sanitized_snake_case(); - let p_ty = ident(&p_name, &config.ident_formats.peripheral, span); + let p_feature = feature_format.apply(&p_name); + let p_ty = ident(&p_name, &config, "peripheral", span); + let p_singleton = ident(&p_name, &config, "peripheral_singleton", span); if config.feature_peripheral { - feature_attribute.extend(quote! { #[cfg(feature = #p_snake)] }) + feature_attribute.extend(quote! { #[cfg(feature = #p_feature)] }) }; fields.extend(quote! { #[doc = #p_name] #feature_attribute - pub #p_ty: #p_ty, + pub #p_singleton: #p_ty, }); - exprs.extend(quote!(#feature_attribute #p_ty: #p_ty { _marker: PhantomData },)); + exprs.extend( + quote!(#feature_attribute #p_singleton: #p_ty { _marker: PhantomData },), + ); } Peripheral::Array(p, dim_element) => { for p_name in names(p, dim_element) { - let p_snake = p_name.to_sanitized_snake_case(); - let p_ty = ident(&p_name, &config.ident_formats.peripheral, span); + let p_feature = feature_format.apply(&p_name); + let p_ty = ident(&p_name, &config, "peripheral", span); + let p_singleton = ident(&p_name, &config, "peripheral_singleton", span); if config.feature_peripheral { - feature_attribute.extend(quote! { #[cfg(feature = #p_snake)] }) + feature_attribute.extend(quote! { #[cfg(feature = #p_feature)] }) }; fields.extend(quote! { #[doc = #p_name] #feature_attribute - pub #p_ty: #p_ty, + pub #p_singleton: #p_ty, }); - exprs.extend(quote!(#feature_attribute #p_ty: #p_ty { _marker: PhantomData },)); + exprs.extend( + quote!(#feature_attribute #p_singleton: #p_ty { _marker: PhantomData },), + ); } } } diff --git a/src/generate/interrupt.rs b/src/generate/interrupt.rs index 8c1a2ed6..1c48b06e 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, ident, ToSanitizedCase}; +use crate::util::{self, ident}; use crate::{Config, Target}; use anyhow::Result; @@ -47,6 +47,7 @@ pub fn render( let mut pos = 0; let mut mod_items = TokenStream::new(); let span = Span::call_site(); + let feature_format = config.ident_formats.get("peripheral_feature").unwrap(); for interrupt in &interrupts { while pos < interrupt.0.value { elements.extend(quote!(Vector { _reserved: 0 },)); @@ -54,7 +55,7 @@ pub fn render( } pos += 1; - let i_ty = ident(&interrupt.0.name, &config.ident_formats.interrupt, span); + let i_ty = ident(&interrupt.0.name, &config, "interrupt", span); let description = format!( "{} - {}", interrupt.0.value, @@ -74,13 +75,13 @@ pub fn render( let mut feature_attribute = TokenStream::new(); let mut not_feature_attribute = TokenStream::new(); if config.feature_group && interrupt.1.is_some() { - let feature_name = interrupt.1.as_ref().unwrap().to_sanitized_snake_case(); + let feature_name = feature_format.apply(interrupt.1.as_ref().unwrap()); feature_attribute_flag = true; feature_attribute.extend(quote! { #[cfg(feature = #feature_name)] }); not_feature_attribute.extend(quote! { feature = #feature_name, }); } if config.feature_peripheral { - let feature_name = interrupt.2.to_sanitized_snake_case(); + let feature_name = feature_format.apply(&interrupt.2); feature_attribute_flag = true; feature_attribute.extend(quote! { #[cfg(feature = #feature_name)] }); not_feature_attribute.extend(quote! { feature = #feature_name, }); diff --git a/src/generate/peripheral.rs b/src/generate/peripheral.rs index 5f1cae85..bc4c3fc3 100644 --- a/src/generate/peripheral.rs +++ b/src/generate/peripheral.rs @@ -16,8 +16,7 @@ use quote::{quote, ToTokens}; use syn::{punctuated::Punctuated, Token}; use crate::util::{ - self, ident, 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, BITS_PER_BYTE, }; use anyhow::{anyhow, bail, Context, Result}; @@ -38,21 +37,26 @@ 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 p_ty = ident(&name, &config.ident_formats.peripheral, span); + let p_ty = ident(&name, &config, "peripheral", span); let name_str = p_ty.to_string(); let address = util::hex(p.base_address + config.base_address_shift); let description = util::respace(p.description.as_ref().unwrap_or(&p.name)); - let mod_ty = name.to_snake_case_ident(span); + let mod_ty = ident(&name, config, "peripheral_mod", span); let (derive_regs, base, path) = if let Some(path) = path { - (true, path.peripheral.to_snake_case_ident(span), path) + ( + true, + ident(&path.peripheral, config, "peripheral_mod", span), + path, + ) } else { (false, mod_ty.clone(), BlockPath::new(&p.name)) }; let mut feature_attribute = TokenStream::new(); + let feature_format = config.ident_formats.get("peripheral_feature").unwrap(); if config.feature_group && p.group_name.is_some() { - let feature_name = p.group_name.as_ref().unwrap().to_sanitized_snake_case(); + let feature_name = feature_format.apply(p.group_name.as_ref().unwrap()); feature_attribute.extend(quote! { #[cfg(feature = #feature_name)] }); }; @@ -77,22 +81,24 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result match &p { Peripheral::Array(p, dim) => { - let mut snake_names = Vec::with_capacity(dim.dim as _); + let mut feature_names = Vec::with_capacity(dim.dim as _); for pi in svd::peripheral::expand(p, dim) { let name = &pi.name; let description = pi.description.as_deref().unwrap_or(&p.name); - let p_ty = ident(name, &config.ident_formats.peripheral, span); + let p_ty = ident(name, &config, "peripheral", span); let name_str = p_ty.to_string(); + let doc_alias = (&name_str != name).then(|| quote!(#[doc(alias = #name)])); let address = util::hex(pi.base_address + config.base_address_shift); - let p_snake = name.to_sanitized_snake_case(); - snake_names.push(p_snake.to_string()); + let p_feature = feature_format.apply(name); + feature_names.push(p_feature.to_string()); let mut feature_attribute_n = feature_attribute.clone(); if config.feature_peripheral { - feature_attribute_n.extend(quote! { #[cfg(feature = #p_snake)] }) + feature_attribute_n.extend(quote! { #[cfg(feature = #p_feature)] }) }; // Insert the peripherals structure out.extend(quote! { #[doc = #description] + #doc_alias #feature_attribute_n pub struct #p_ty { _marker: PhantomData<*const ()> } @@ -132,7 +138,7 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result }); } - let feature_any_attribute = quote! {#[cfg(any(#(feature = #snake_names),*))]}; + let feature_any_attribute = quote! {#[cfg(any(#(feature = #feature_names),*))]}; // Derived peripherals may not require re-implementation, and will instead // use a single definition of the non-derived version. @@ -147,9 +153,9 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result } } Peripheral::Single(_) => { - let p_snake = name.to_sanitized_snake_case(); + let p_feature = feature_format.apply(&name); if config.feature_peripheral { - feature_attribute.extend(quote! { #[cfg(feature = #p_snake)] }) + feature_attribute.extend(quote! { #[cfg(feature = #p_feature)] }) }; // Insert the peripheral structure out.extend(quote! { @@ -244,7 +250,8 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result "Pushing {} register or cluster blocks into output", ercs.len() ); - let reg_block = register_or_cluster_block(&ercs, &derive_infos, None, None, config)?; + let reg_block = + register_or_cluster_block(&ercs, &derive_infos, None, "Register block", None, config)?; let open = Punct::new('{', Spacing::Alone); let close = Punct::new('}', Spacing::Alone); @@ -526,6 +533,7 @@ fn register_or_cluster_block( ercs: &[RegisterCluster], derive_infos: &[DeriveInfo], name: Option<&str>, + doc: &str, size: Option, config: &Config, ) -> Result { @@ -630,8 +638,13 @@ fn register_or_cluster_block( } }); + let mut doc_alias = None; let block_ty = if let Some(name) = name { - ident(name, &config.ident_formats.cluster, span) + let ty = ident(name, &config, "cluster", span); + if ty.to_string() != name { + doc_alias = Some(quote!(#[doc(alias = #name)])); + } + ty } else { Ident::new("RegisterBlock", span) }; @@ -645,9 +658,10 @@ fn register_or_cluster_block( }); Ok(quote! { - ///Register block #[repr(C)] #derive_debug + #[doc = #doc] + #doc_alias pub struct #block_ty { #rbfs } @@ -981,12 +995,13 @@ fn expand_cluster(cluster: &Cluster, config: &Config) -> Result { let doc = make_comment(cluster_size, info.address_offset, &description); - let name: Ident = info.name.to_snake_case_ident(Span::call_site()); + let name: Ident = ident(&info.name, &config, "cluster_accessor", span); let syn_field = new_syn_field(name.clone(), ty.clone()); cluster_expanded.push(RegisterBlockField { syn_field, @@ -1029,12 +1044,16 @@ fn expand_cluster(cluster: &Cluster, config: &Config) -> Result Result Result Result Result Result Result { let doc = make_comment(register_size, info.address_offset, &description); - let ty = name_to_ty(&ty_name); - let name = ty_name.to_snake_case_ident(Span::call_site()); + let span = Span::call_site(); + let ty = name_to_ty(ident(&ty_str, &config, "register", span)); + let name: Ident = ident(&ty_name, &config, "register_accessor", span); let syn_field = new_syn_field(name.clone(), ty.clone()); register_expanded.push(RegisterBlockField { syn_field, @@ -1191,7 +1212,7 @@ fn expand_register( // force expansion and rename if we're deriving an array that doesnt start at 0 so we don't get name collisions let index: Cow = if let Some(dim_index) = &array_info.dim_index { - dim_index.first().unwrap().into() + dim_index[0].as_str().into() } else if sequential_indexes_from0 { "0".into() } else { @@ -1210,15 +1231,19 @@ fn expand_register( }; let array_convertible = ac && sequential_addresses; let array_proxy_convertible = ac && disjoint_sequential_addresses; - let ty = name_to_ty(&ty_name); + let span = Span::call_site(); + let ty = name_to_ty(ident(&ty_str, &config, "register", span)); if array_convertible || array_proxy_convertible { - let span = Span::call_site(); - let nb_name_sc = if let Some(dim_name) = array_info.dim_name.as_ref() { - util::fullname(dim_name, &info.alternate_group, config.ignore_groups) - .to_snake_case_ident(span) + let accessor_name = if let Some(dim_name) = array_info.dim_name.as_ref() { + ident( + &util::fullname(dim_name, &info.alternate_group, config.ignore_groups), + &config, + "register_accessor", + span, + ) } else { - ty_name.to_snake_case_ident(span) + ident(&ty_name, &config, "register_accessor", span) }; let doc = make_comment( register_size * array_info.dim, @@ -1229,7 +1254,7 @@ fn expand_register( accessors.push(if array_convertible { ArrayAccessor { doc, - name: nb_name_sc.clone(), + name: accessor_name.clone(), ty: ty.clone(), offset: unsuffixed(info.address_offset), dim: unsuffixed(array_info.dim), @@ -1239,7 +1264,7 @@ fn expand_register( } else { RawArrayAccessor { doc, - name: nb_name_sc.clone(), + name: accessor_name.clone(), ty: ty.clone(), offset: unsuffixed(info.address_offset), dim: unsuffixed(array_info.dim), @@ -1249,9 +1274,12 @@ fn expand_register( }); if !sequential_indexes_from0 || !ends_with_index { for (i, ri) in svd::register::expand(info, array_info).enumerate() { - let idx_name = - util::fullname(&ri.name, &info.alternate_group, config.ignore_groups) - .to_snake_case_ident(span); + let idx_name = ident( + &util::fullname(&ri.name, &info.alternate_group, config.ignore_groups), + &config, + "cluster_accessor", + span, + ); let doc = make_comment( register_size, ri.address_offset, @@ -1263,7 +1291,7 @@ fn expand_register( doc, name: idx_name, ty: ty.clone(), - basename: nb_name_sc.clone(), + basename: accessor_name.clone(), i, } .into(), @@ -1275,7 +1303,7 @@ fn expand_register( } else { zst_type() }; - let syn_field = new_syn_field(nb_name_sc, array_ty); + let syn_field = new_syn_field(accessor_name, array_ty); register_expanded.push(RegisterBlockField { syn_field, offset: info.address_offset, @@ -1293,7 +1321,7 @@ fn expand_register( info.address_offset, ri.description.as_deref().unwrap_or(&ri.name), ); - let name = ri.name.to_snake_case_ident(Span::call_site()); + let name = ident(&ri.name, &config, "register_accessor", span); let syn_field = new_syn_field(name.clone(), ty.clone()); register_expanded.push(RegisterBlockField { @@ -1375,27 +1403,26 @@ fn cluster_block( // name_snake_case needs to take into account array type. let span = Span::call_site(); - let mod_ty = mod_name.to_snake_case_ident(span); - let block_ty = ident(&mod_name, &config.ident_formats.cluster, span); + let mod_ty = ident(&mod_name, config, "cluster_mod", span); + let block_ty = ident(&mod_name, &config, "cluster", span); if let Some(dpath) = dpath { let dparent = dpath.parent().unwrap(); let mut derived = if &dparent == path { type_path(Punctuated::new()) } else { - util::block_path_to_ty(&dparent, span) + util::block_path_to_ty(&dparent, config, span) }; let dname = util::replace_suffix(&index.clusters.get(&dpath).unwrap().name, ""); let mut mod_derived = derived.clone(); - derived.path.segments.push(path_segment(ident( - &dname, - &config.ident_formats.cluster, - span, - ))); + derived + .path + .segments + .push(path_segment(ident(&dname, &config, "cluster", span))); mod_derived .path .segments - .push(path_segment(dname.to_snake_case_ident(span))); + .push(path_segment(ident(&dname, config, "cluster_mod", span))); Ok(quote! { #[doc = #description] @@ -1418,6 +1445,7 @@ fn cluster_block( &c.children, &mod_derive_infos, Some(&mod_name), + &description, cluster_size, config, )?; diff --git a/src/generate/register.rs b/src/generate/register.rs index 21fb1746..5b75711d 100644 --- a/src/generate/register.rs +++ b/src/generate/register.rs @@ -16,13 +16,30 @@ use svd_parser::expand::{ use crate::config::Config; use crate::util::{ self, ident, ident_to_path, path_segment, replace_suffix, type_path, unsuffixed, FullName, - ToSanitizedCase, U32Ext, + 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) + ident(name, &config, "register_spec", span) +} + +fn field_accessor(name: &str, config: &Config, span: Span) -> Ident { + const INTERNALS: [&str; 1] = ["bits"]; + let sc = config + .ident_formats + .get("field_accessor") + .unwrap() + .sanitize(name); + Ident::new( + &(if INTERNALS.contains(&sc.as_ref()) { + sc + "_" + } else { + sc + }), + span, + ) } pub fn render( @@ -43,8 +60,9 @@ pub fn render( } } let span = Span::call_site(); - let reg_ty = ident(&name, &config.ident_formats.register, span); - let mod_ty = name.to_snake_case_ident(span); + let reg_ty = ident(&name, &config, "register", span); + let doc_alias = (®_ty.to_string() != &name).then(|| quote!(#[doc(alias = #name)])); + let mod_ty = ident(&name, config, "register_mod", span); let description = util::escape_special_chars( util::respace(®ister.description.clone().unwrap_or_else(|| { warn!("Missing description for register {}", register.name); @@ -57,19 +75,18 @@ pub fn render( let mut derived = if &dpath.block == path { type_path(Punctuated::new()) } else { - util::block_path_to_ty(&dpath.block, span) + util::block_path_to_ty(&dpath.block, config, 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(ident( - &dname, - &config.ident_formats.register, - span, - ))); + derived + .path + .segments + .push(path_segment(ident(&dname, &config, "register", span))); mod_derived .path .segments - .push(path_segment(dname.to_snake_case_ident(span))); + .push(path_segment(ident(&dname, config, "register_mod", span))); Ok(quote! { pub use #derived as #reg_ty; @@ -107,6 +124,7 @@ pub fn render( let mut out = TokenStream::new(); out.extend(quote! { #[doc = #alias_doc] + #doc_alias pub type #reg_ty = crate::Reg<#mod_ty::#regspec_ty>; }); let mod_items = render_register_mod( @@ -201,7 +219,7 @@ pub fn render_register_mod( let name = util::name_of(register, config.ignore_groups); let span = Span::call_site(); let regspec_ty = regspec(&name, config, span); - let name_snake_case = name.to_snake_case_ident(span); + let mod_ty = ident(&name, config, "register_mod", span); let rsize = properties .size .ok_or_else(|| anyhow!("Register {} has no `size` field", register.name))?; @@ -384,7 +402,7 @@ pub fn render_register_mod( can_read, can_write, can_reset, - &name_snake_case, + &mod_ty, true, register.read_action, )? @@ -400,15 +418,14 @@ pub fn render_register_mod( }); if can_read { - let doc = format!("`read()` method returns [`{name_snake_case}::R`](R) reader structure",); + let doc = format!("`read()` method returns [`{mod_ty}::R`](R) reader structure",); mod_items.extend(quote! { #[doc = #doc] impl crate::Readable for #regspec_ty {} }); } if can_write { - let doc = - format!("`write(|w| ..)` method takes [`{name_snake_case}::W`](W) writer structure",); + let doc = format!("`write(|w| ..)` method takes [`{mod_ty}::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); @@ -469,8 +486,8 @@ fn render_register_mod_debug( 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 = + field_accessor(&util::replace_suffix(&f.name, &suffix), config, span); 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())) @@ -478,7 +495,7 @@ fn render_register_mod_debug( } } else { let f_name = util::replace_suffix(&f.name, ""); - let f_name = f_name.to_snake_case_ident(span); + let f_name = field_accessor(&f_name, config, 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())) @@ -559,18 +576,22 @@ pub fn fields( } let name = util::replace_suffix(&f.name, ""); - let name_snake_case = if let Field::Array( - _, - DimElement { - dim_name: Some(dim_name), - .. + let name_snake_case = field_accessor( + if let Field::Array( + _, + DimElement { + dim_name: Some(dim_name), + .. + }, + ) = &f + { + dim_name + } else { + &name }, - ) = &f - { - dim_name.to_snake_case_ident(span) - } else { - name.to_snake_case_ident(span) - }; + config, + span, + ); 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)); @@ -656,7 +677,8 @@ pub fn fields( { ident( evs.name.as_deref().unwrap_or(&name), - &config.ident_formats.enum_name, + &config, + "enum_name", span, ) } else { @@ -665,7 +687,7 @@ pub fn fields( }; // name of read proxy type - let reader_ty = ident(&name, &config.ident_formats.field_reader, span); + let reader_ty = ident(&name, &config, "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; @@ -826,16 +848,7 @@ pub fn fields( // for each variant defined, we generate an `is_variant` function. 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 is_variant = &v.is_sc; let doc = util::escape_special_chars(&util::respace(&v.doc)); enum_items.extend(quote! { @@ -848,16 +861,7 @@ pub fn fields( } 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 is_variant = &v.is_sc; let doc = util::escape_special_chars(&util::respace(&v.doc)); enum_items.extend(quote! { @@ -878,9 +882,9 @@ 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 = ident(&base_field, &config.ident_formats.field_reader, span); + let base_r = ident(&base_field, &config, "field_reader", span); if !reader_derives.contains(&reader_ty) { - let base_path = base_syn_path(base, &fpath, &base_r)?; + let base_path = base_syn_path(base, &fpath, &base_r, config)?; mod_items.extend(quote! { #[doc = #field_reader_brief] pub use #base_path as #reader_ty; @@ -892,7 +896,7 @@ pub fn fields( if base.register() != fpath.register() { // use the same enum structure name if !enum_derives.contains(&value_read_ty) { - let base_path = base_syn_path(base, &fpath, &value_read_ty)?; + let base_path = base_syn_path(base, &fpath, &value_read_ty, config)?; mod_items.extend(quote! { #[doc = #description] pub use #base_path as #value_read_ty; @@ -944,7 +948,7 @@ pub fn fields( } else { value }; - let name_snake_case_n = fi.name.to_snake_case_ident(Span::call_site()); + let name_snake_case_n = field_accessor(&fi.name, config, span); let doc = description_with_bits( fi.description.as_deref().unwrap_or(&fi.name), sub_offset, @@ -990,18 +994,18 @@ pub fn fields( if let Some((evs, _)) = lookup_filter(&lookup_results, Usage::Write) { let writer_reader_different_enum = evs_r != Some(evs); let fmt = if writer_reader_different_enum { - &config.ident_formats.enum_write_name + "enum_write_name" } else { - &config.ident_formats.enum_name + "enum_name" }; - ident(evs.name.as_deref().unwrap_or(&name), fmt, span) + ident(evs.name.as_deref().unwrap_or(&name), &config, fmt, span) } else { // raw_field_value_write_ty fty.clone() }; // name of write proxy type - let writer_ty = ident(&name, &config.ident_formats.field_writer, span); + let writer_ty = ident(&name, &config, "field_writer", span); let mut proxy_items = TokenStream::new(); let mut unsafety = unsafety(f.write_constraint.as_ref(), width); @@ -1153,9 +1157,9 @@ 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 = ident(&base_field, &config.ident_formats.field_writer, span); + let base_w = ident(&base_field, &config, "field_writer", span); if !writer_derives.contains(&writer_ty) { - let base_path = base_syn_path(base, &fpath, &base_w)?; + let base_path = base_syn_path(base, &fpath, &base_w, config)?; mod_items.extend(quote! { #[doc = #field_writer_brief] pub use #base_path as #writer_ty; @@ -1168,7 +1172,7 @@ pub fn fields( if writer_reader_different_enum { // use the same enum structure name if !writer_enum_derives.contains(&value_write_ty) { - let base_path = base_syn_path(base, &fpath, &value_write_ty)?; + let base_path = base_syn_path(base, &fpath, &value_write_ty, config)?; mod_items.extend(quote! { #[doc = #description] pub use #base_path as #value_write_ty; @@ -1201,7 +1205,7 @@ pub fn fields( 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 name_snake_case_n = field_accessor(&fi.name, config, span); let doc = description_with_bits( fi.description.as_deref().unwrap_or(&fi.name), sub_offset, @@ -1274,7 +1278,7 @@ fn unsafety(write_constraint: Option<&WriteConstraint>, width: u32) -> bool { struct Variant { doc: String, pc: Ident, - nksc: Ident, + is_sc: Ident, sc: Ident, value: u64, } @@ -1296,16 +1300,34 @@ impl Variant { } 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()); + let case = config.ident_formats.get("enum_value_accessor").unwrap(); + let nksc = case.apply(&ev.name); + let is_sc = Ident::new( + &if nksc.to_string().starts_with('_') { + format!("is{nksc}") + } else { + format!("is_{nksc}") + }, + span, + ); + let sc = case.sanitize(&ev.name); + const INTERNALS: [&str; 4] = ["set_bit", "clear_bit", "bit", "bits"]; + let sc = Ident::new( + &(if INTERNALS.contains(&sc.as_ref()) { + sc + "_" + } else { + sc + }), + span, + ); Ok(Variant { doc: ev .description .clone() .unwrap_or_else(|| format!("`{value:b}`")), - pc: ident(&ev.name, &config.ident_formats.enum_value, span), - nksc: Ident::new(&nksc, span), - sc: Ident::new(&sc, span), + pc: ident(&ev.name, &config, "enum_value", span), + is_sc, + sc, value, }) } @@ -1455,6 +1477,7 @@ fn base_syn_path( base: &EnumPath, fpath: &FieldPath, base_ident: &Ident, + config: &Config, ) -> Result { let span = Span::call_site(); let path = if base.register() == fpath.register() { @@ -1462,11 +1485,16 @@ fn base_syn_path( } 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(ident( + &replace_suffix(&base.register().name, ""), + config, + "register_mod", + span, + ))); segments.push(path_segment(base_ident.clone())); type_path(segments) } else { - let mut rmod_ = crate::util::register_path_to_ty(base.register(), span); + let mut rmod_ = crate::util::register_path_to_ty(base.register(), config, span); rmod_.path.segments.push(path_segment(base_ident.clone())); rmod_ }; diff --git a/src/lib.rs b/src/lib.rs index 251d7e8e..d056996d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -548,6 +548,30 @@ //! //! The `--impl-defmt` flag can also be specified to include `defmt::Format` implementations conditionally //! behind the supplied feature name. +//! +//! ## the `--ident-format` flag +//! +//! The `--ident-format type:prefix:case:suffix` (`-f`) flag can also be specified if you want to change +//! default behavior of formatting rust structure and enum names, register access methods, etc. +//! Passingle multiple flags is supported. +//! Supported cases are `snake_case` (pass `snake` or `s`), `PascalCase` (pass `pascal` or `p`), +//! `CONSTANT_CASE` (pass `constant` or `c`) and `leave_CASE_as_in_SVD` (pass `unchanged` or ``). +//! +//! There are identificator formats by default in the table. +//! +//! | IdentifierType | Prefix | Case 0.31 | Suffix | +//! |----------------------------------------------------------------------------------|:------:|:---------:|:-----------:| +//! | field_reader | | constant | _R | +//! | field_writer | | constant | _W | +//! | enum_name | | constant | _A | +//! | enum_write_name | | constant | _AW | +//! | enum_value | | constant | | +//! | interrupt | | constant | | +//! | peripheral_singleton | | constant | | +//! | peripheral
register
cluster | | constant | | +//! | register_spec | | constant | _SPEC | +//! | cluster_accessor
register_accessor
field_accessor
enum_value_accessor | | snake | | +//! | cluster_mod
register_mod
peripheral_mod | | snake | | #![recursion_limit = "128"] use quote::quote; @@ -573,6 +597,8 @@ pub struct DeviceSpecific { use anyhow::{Context, Result}; +use crate::config::IdentFormats; + #[derive(Debug, thiserror::Error)] pub enum SvdError { #[error("Cannot format crate")] @@ -585,10 +611,15 @@ pub enum SvdError { pub fn generate(input: &str, config: &Config) -> Result { use std::fmt::Write; - let device = load_from(input, config)?; + let mut config = config.clone(); + let mut ident_formats = IdentFormats::default(); + ident_formats.extend(config.ident_formats.drain()); + config.ident_formats = ident_formats; + + let device = load_from(input, &config)?; let mut device_x = String::new(); let items = - generate::device::render(&device, config, &mut device_x).map_err(SvdError::Render)?; + generate::device::render(&device, &config, &mut device_x).map_err(SvdError::Render)?; let mut lib_rs = String::new(); writeln!( diff --git a/src/main.rs b/src/main.rs index 6c366fea..b4d02e94 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![recursion_limit = "128"] -use log::{debug, error, info}; +use log::{debug, error, info, warn}; +use svd2rust::config::IdentFormats; +use svd2rust::util::{Case, IdentFormat}; use std::io::Write; use std::process; @@ -18,6 +20,7 @@ use svd2rust::{ fn parse_configs(app: Command) -> Result { use irx_config::parsers::{cmd, toml}; use irx_config::ConfigBuilder; + let ident_formats = app.clone().get_matches(); let irxconfig = ConfigBuilder::default() .append_parser(cmd::ParserBuilder::new(app).exit_on_error(true).build()?) .append_parser( @@ -29,7 +32,41 @@ fn parse_configs(app: Command) -> Result { ) .load()?; - irxconfig.get().map_err(Into::into) + let mut config: Config = irxconfig.get()?; + let mut idf = IdentFormats::default(); + idf.extend(config.ident_formats.drain()); + config.ident_formats = idf; + + if let Some(ident_formats) = ident_formats.get_many::("ident_format") { + for f in ident_formats { + let mut f = f.split(":"); + if let (Some(n), Some(p), Some(c), Some(s)) = (f.next(), f.next(), f.next(), f.next()) { + let case = match c { + "" | "unchanged" | "svd" => None, + "p" | "pascal" | "type" => Some(Case::Pascal), + "s" | "snake" | "lower" => Some(Case::Snake), + "c" | "constant" | "upper" => Some(Case::Constant), + _ => { + warn!("Ident case `{c}` is unknown"); + continue; + } + }; + if let std::collections::hash_map::Entry::Occupied(mut e) = + config.ident_formats.entry(n.into()) + { + e.insert(IdentFormat { + case, + prefix: p.into(), + suffix: s.into(), + }); + } else { + warn!("Ident format name `{n}` is unknown"); + } + } + } + } + + Ok(config) } fn run() -> Result<()> { @@ -119,6 +156,19 @@ fn run() -> Result<()> { .action(ArgAction::SetTrue) .help("Use independent cfg feature flags for each peripheral"), ) + .arg( + Arg::new("ident_format") + .long("ident-format") + .short('f') + .alias("ident_format") + .action(ArgAction::Append) + .long_help( +format!("Specify `-f type:prefix:case:suffix` to change default ident formatting. +Allowed values of `type` are {:?}. +Allowed cases are `unchanged` (''), `pascal` ('p'), `constant` ('c') and `snake` ('s'). +", IdentFormats::default().keys().collect::>()) +), + ) .arg( Arg::new("max_cluster_size") .long("max-cluster-size") @@ -171,14 +221,6 @@ 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 CONSTANT_CASE for enumerated values"), - ) .arg( Arg::new("source_type") .long("source-type") @@ -260,9 +302,6 @@ Ignore this option if you are not building your own FPGA based soft-cores."), 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)?; @@ -292,14 +331,15 @@ Ignore this option if you are not building your own FPGA based soft-cores."), } if config.feature_group || config.feature_peripheral { + let feature_format = config.ident_formats.get("peripheral_feature").unwrap(); let mut features = Vec::new(); if config.feature_group { features.extend( - util::group_names(&device) + util::group_names(&device, &feature_format) .iter() .map(|s| format!("{s} = []\n")), ); - let add_groups: Vec<_> = util::group_names(&device) + let add_groups: Vec<_> = util::group_names(&device, &feature_format) .iter() .map(|s| format!("\"{s}\"")) .collect(); @@ -307,11 +347,11 @@ Ignore this option if you are not building your own FPGA based soft-cores."), } if config.feature_peripheral { features.extend( - util::peripheral_names(&device) + util::peripheral_names(&device, &feature_format) .iter() .map(|s| format!("{s} = []\n")), ); - let add_peripherals: Vec<_> = util::peripheral_names(&device) + let add_peripherals: Vec<_> = util::peripheral_names(&device, &feature_format) .iter() .map(|s| format!("\"{s}\"")) .collect(); diff --git a/src/util.rs b/src/util.rs index fd80df55..2468084b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,10 @@ use std::borrow::Cow; pub use crate::config::{Case, IdentFormat}; -use crate::svd::{Access, Device, Field, RegisterInfo, RegisterProperties}; +use crate::{ + svd::{Access, Device, Field, RegisterInfo, RegisterProperties}, + Config, +}; use html_escape::encode_text_minimal; use inflections::Inflect; use proc_macro2::{Ident, Span, TokenStream}; @@ -21,6 +24,41 @@ pub const BITS_PER_BYTE: u32 = 8; /// that are not valid in Rust ident const BLACKLIST_CHARS: &[char] = &['(', ')', '[', ']', '/', ' ', '-']; +fn to_pascal_case(s: &str) -> String { + if !s.contains('_') { + s.to_pascal_case() + } else { + let mut string = String::new(); + let mut parts = s.split('_').peekable(); + if let Some(&"") = parts.peek() { + string.push('_'); + } + loop { + if let Some(p) = parts.next() { + if p.is_empty() { + continue; + } + string.push_str(&p.to_pascal_case()); + match parts.peek() { + Some(nxt) + if p.ends_with(|s: char| s.is_numeric()) + && nxt.starts_with(|s: char| s.is_numeric()) => + { + string.push('_'); + } + Some(&"") => { + string.push('_'); + } + _ => {} + } + } else { + break; + } + } + string + } +} + impl Case { pub fn cow_to_case<'a>(&self, cow: Cow<'a, str>) -> Cow<'a, str> { match self { @@ -30,7 +68,7 @@ impl Case { }, Self::Pascal => match cow { Cow::Borrowed(s) if s.is_pascal_case() => cow, - _ => cow.to_pascal_case().into(), + _ => to_pascal_case(&cow).into(), }, Self::Snake => match cow { Cow::Borrowed(s) if s.is_snake_case() => cow, @@ -53,78 +91,49 @@ fn sanitize(s: &str) -> Cow<'_, str> { } } -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. - /// - /// Use on name of enumeration values. - fn to_sanitized_pascal_case(&self) -> Cow; - fn to_pascal_case_ident(&self, span: Span) -> Ident { - Ident::new(&self.to_sanitized_pascal_case(), span) - } - /// Convert self into CONSTANT_CASE. - /// - /// Use on name of reader structs, writer structs and enumerations. - fn to_sanitized_constant_case(&self) -> Cow; - fn to_constant_case_ident(&self, span: Span) -> Ident { - Ident::new(&self.to_sanitized_constant_case(), span) - } - /// Convert self into snake_case, must use only if the target is used with extra prefix or suffix. - fn to_sanitized_not_keyword_snake_case(&self) -> Cow; // snake_case - /// Convert self into snake_case target and ensure target is not a Rust keyword. - /// - /// If the sanitized target is a Rust keyword, this function adds an underline `_` - /// to it. - /// - /// Use on name of peripheral modules, register modules and field modules. - fn to_sanitized_snake_case(&self) -> Cow { - let s = self.to_sanitized_not_keyword_snake_case(); - sanitize_keyword(s) - } - fn to_snake_case_ident(&self, span: Span) -> Ident { - Ident::new(&self.to_sanitized_snake_case(), span) - } +pub fn ident(name: &str, config: &Config, fmt: &str, span: Span) -> Ident { + Ident::new( + &config + .ident_formats + .get(fmt) + .expect("Missing {fmt} entry") + .sanitize(name), + span, + ) } -impl ToSanitizedCase for str { - fn to_sanitized_pascal_case(&self) -> Cow { - let s = Case::Pascal.sanitize(self); - if s.as_bytes().first().unwrap_or(&0).is_ascii_digit() { - Cow::from(format!("_{}", s)) +impl IdentFormat { + pub fn apply<'a>(&self, name: &'a str) -> Cow<'a, str> { + let name = match &self.case { + Some(case) => case.sanitize(name), + _ => sanitize(name), + }; + if self.prefix.is_empty() && self.suffix.is_empty() { + name } else { - s + format!("{}{}{}", self.prefix, name, self.suffix).into() } } - fn to_sanitized_constant_case(&self) -> Cow { - let s = Case::Constant.sanitize(self); - if s.as_bytes().first().unwrap_or(&0).is_ascii_digit() { + pub fn sanitize<'a>(&self, name: &'a str) -> Cow<'a, str> { + let s = self.apply(name); + let s = if s.as_bytes().first().unwrap_or(&0).is_ascii_digit() { Cow::from(format!("_{}", s)) } else { s + }; + match self.case { + Some(Case::Snake) | None => sanitize_keyword(s), + _ => s, } } - fn to_sanitized_not_keyword_snake_case(&self) -> Cow { - const INTERNALS: [&str; 4] = ["set_bit", "clear_bit", "bit", "bits"]; - - let s = Case::Snake.sanitize(self); - if s.as_bytes().first().unwrap_or(&0).is_ascii_digit() { - format!("_{}", s).into() - } else if INTERNALS.contains(&s.as_ref()) { - s + "_" - } else { - s - } - } +} + +pub fn ident_str(name: &str, fmt: &IdentFormat) -> String { + let name = match &fmt.case { + Some(case) => case.sanitize(name), + _ => sanitize(name), + }; + format!("{}{}{}", fmt.prefix, name, fmt.suffix) } pub fn sanitize_keyword(sc: Cow) -> Cow { @@ -281,28 +290,43 @@ pub fn zst_type() -> Type { }) } -pub fn name_to_ty(name: &str) -> Type { - let span = Span::call_site(); +pub fn name_to_ty(name: Ident) -> Type { let mut segments = Punctuated::new(); - segments.push(path_segment(name.to_constant_case_ident(span))); + segments.push(path_segment(name)); syn::Type::Path(type_path(segments)) } -pub fn block_path_to_ty(bpath: &svd_parser::expand::BlockPath, span: Span) -> TypePath { +pub fn block_path_to_ty( + bpath: &svd_parser::expand::BlockPath, + config: &Config, + span: Span, +) -> TypePath { let mut segments = Punctuated::new(); segments.push(path_segment(Ident::new("crate", span))); - segments.push(path_segment(bpath.peripheral.to_snake_case_ident(span))); + segments.push(path_segment(ident( + &bpath.peripheral, + config, + "peripheral_mod", + span, + ))); for ps in &bpath.path { - segments.push(path_segment(ps.to_snake_case_ident(span))); + segments.push(path_segment(ident(&ps, config, "cluster_mod", span))); } type_path(segments) } -pub fn register_path_to_ty(rpath: &svd_parser::expand::RegisterPath, span: Span) -> TypePath { - let mut p = block_path_to_ty(&rpath.block, span); - p.path - .segments - .push(path_segment(rpath.name.to_snake_case_ident(span))); +pub fn register_path_to_ty( + rpath: &svd_parser::expand::RegisterPath, + config: &Config, + span: Span, +) -> TypePath { + let mut p = block_path_to_ty(&rpath.block, config, span); + p.path.segments.push(path_segment(ident( + &rpath.name, + config, + "register_mod", + span, + ))); p } @@ -436,32 +460,40 @@ impl FullName for PeripheralInfo { } } -pub fn group_names(d: &Device) -> Vec> { +pub fn group_names<'a>(d: &'a Device, feature_format: &'a IdentFormat) -> Vec> { let set: HashSet<_> = d .peripherals .iter() .filter_map(|p| p.group_name.as_ref()) - .map(|name| name.to_sanitized_snake_case()) + .map(|name| feature_format.apply(name)) .collect(); let mut v: Vec<_> = set.into_iter().collect(); v.sort(); v } -pub fn peripheral_names(d: &Device) -> Vec { +pub fn peripheral_names(d: &Device, feature_format: &IdentFormat) -> Vec { let mut v = Vec::new(); for p in &d.peripherals { match p { Peripheral::Single(info) => { - v.push(replace_suffix(&info.name.to_sanitized_snake_case(), "")); + v.push(replace_suffix(&feature_format.apply(&info.name), "")); } Peripheral::Array(info, dim) => { - v.extend( - svd_rs::array::names(info, dim).map(|n| n.to_sanitized_snake_case().into()), - ); + v.extend(svd_rs::array::names(info, dim).map(|n| feature_format.apply(&n).into())); } } } v.sort(); v } + +#[test] +fn pascalcase() { + assert_eq!(to_pascal_case("_reserved"), "_Reserved"); + assert_eq!(to_pascal_case("_FOO_BAR_"), "_FooBar_"); + assert_eq!(to_pascal_case("FOO_BAR1"), "FooBar1"); + assert_eq!(to_pascal_case("FOO_BAR_1"), "FooBar1"); + assert_eq!(to_pascal_case("FOO_BAR_1_2"), "FooBar1_2"); + assert_eq!(to_pascal_case("FOO_BAR_1_2_"), "FooBar1_2_"); +}