Skip to content

Commit

Permalink
Custom prefix/case/suffix for identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
burrbull committed Jan 2, 2024
1 parent 8deac04 commit 7652114
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 38 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
94 changes: 92 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -28,6 +27,7 @@ pub struct Config {
pub interrupt_link_section: Option<String>,
pub reexport_core_peripherals: bool,
pub reexport_interrupt: bool,
pub ident_formats: IdentFormats,
}

#[allow(clippy::upper_case_acronyms)]
Expand Down Expand Up @@ -116,3 +116,93 @@ 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<Case>,
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<Self, ()> {
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 register_spec: IdentFormat,
pub enum_name: IdentFormat,
pub enum_write_name: IdentFormat,
pub field_reader: IdentFormat,
pub field_writer: IdentFormat,
}

impl Default for IdentFormats {
fn default() -> Self {
Self {
register_spec: IdentFormat::default().constant_case().suffix("_SPEC"),
enum_name: IdentFormat::default().constant_case().suffix("_A"),
enum_write_name: IdentFormat::default().constant_case().suffix("_AW"),
field_reader: IdentFormat::default().constant_case().suffix("_R"),
field_writer: IdentFormat::default().constant_case().suffix("_W"),
}
}
}
45 changes: 21 additions & 24 deletions src/generate/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(&register.properties, register.fields.as_deref());
let accs = if access.can_read() && access.can_write() {
"rw"
Expand Down Expand Up @@ -196,7 +200,7 @@ pub fn render_register_mod(
let properties = &register.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
Expand Down Expand Up @@ -437,7 +441,7 @@ fn render_register_mod_debug(
) -> 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 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();
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -877,7 +879,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! {
Expand Down Expand Up @@ -988,24 +990,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);
Expand Down Expand Up @@ -1158,7 +1155,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! {
Expand Down
35 changes: 23 additions & 12 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -45,16 +39,33 @@ 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, modifier: &IdentFormat, span: Span) -> Ident {
let name = match &modifier.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!("{}{}{}", modifier.prefix, name, modifier.suffix),
span,
)
}

/// Convert self string into specific case without overlapping to svd2rust internal names
pub trait ToSanitizedCase {
/// Convert self into PascalCase.
Expand Down

0 comments on commit 7652114

Please sign in to comment.