From de3e8549b6677e34e4fa3fc838e0c7ddb9808ff7 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Thu, 2 Jan 2025 10:28:21 +0100 Subject: [PATCH] Add option to totally remove defmt support from generated code. --- src/generate/device.rs | 10 ++++-- src/generate/enumm.rs | 39 ++++++++++++++------- src/generate/fieldset.rs | 42 ++++++++++++---------- src/generate/mod.rs | 56 ++++++++++++++++++++++++++++- src/main.rs | 76 +++++++++++++++++++++++++++++++++++++--- 5 files changed, 185 insertions(+), 38 deletions(-) diff --git a/src/generate/device.rs b/src/generate/device.rs index b4a2674..dd546fb 100644 --- a/src/generate/device.rs +++ b/src/generate/device.rs @@ -17,7 +17,7 @@ pub fn render_device_x(_ir: &IR, d: &Device) -> Result { Ok(device_x) } -pub fn render(_opts: &super::Options, ir: &IR, d: &Device, path: &str) -> Result { +pub fn render(opts: &super::Options, ir: &IR, d: &Device, path: &str) -> Result { let mut out = TokenStream::new(); let span = Span::call_site(); @@ -78,9 +78,15 @@ pub fn render(_opts: &super::Options, ir: &IR, d: &Device, path: &str) -> Result } let n = util::unsuffixed(pos as u64); + let defmt = opts.defmt_feature.as_ref().map(|defmt_feature| { + quote! { + #[cfg_attr(feature = #defmt_feature, derive(defmt::Format))] + } + }); + out.extend(quote!( #[derive(Copy, Clone, Debug, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #defmt pub enum Interrupt { #interrupts } diff --git a/src/generate/enumm.rs b/src/generate/enumm.rs index 104fc7b..602a2ad 100644 --- a/src/generate/enumm.rs +++ b/src/generate/enumm.rs @@ -10,7 +10,7 @@ use crate::util; use super::sorted; -pub fn render(_opts: &super::Options, _ir: &IR, e: &Enum, path: &str) -> Result { +pub fn render(opts: &super::Options, _ir: &IR, e: &Enum, path: &str) -> Result { let span = Span::call_site(); // For very "sparse" enums, generate a newtype wrapping the uX. @@ -50,6 +50,22 @@ pub fn render(_opts: &super::Options, _ir: &IR, e: &Enum, path: &str) -> Result< )); } + let defmt = opts.defmt_feature.as_ref().map(|defmt_feature| { + quote! { + #[cfg(feature = #defmt_feature)] + impl defmt::Format for #name { + fn format(&self, f: defmt::Formatter) { + match self.0 { + #( + #item_values => defmt::write!(f, #item_names_str), + )* + other => defmt::write!(f, "0x{:02X}", other), + } + } + } + } + }); + out.extend(quote! { #doc #[repr(transparent)] @@ -81,17 +97,8 @@ pub fn render(_opts: &super::Options, _ir: &IR, e: &Enum, path: &str) -> Result< } } - #[cfg(feature = "defmt")] - impl defmt::Format for #name { - fn format(&self, f: defmt::Formatter) { - match self.0 { - #( - #item_values => defmt::write!("{}", #item_names_str), - )* - other => defmt::write!(f, "0x{:02X}", other), - } - } - } + #defmt + }); } else { let variants: BTreeMap<_, _> = e.variants.iter().map(|v| (v.value, v)).collect(); @@ -114,11 +121,17 @@ pub fn render(_opts: &super::Options, _ir: &IR, e: &Enum, path: &str) -> Result< } } + let defmt = opts.defmt_feature.as_ref().map(|defmt_feature| { + quote! { + #[cfg_attr(feature = #defmt_feature, derive(defmt::Format))] + } + }); + out.extend(quote! { #doc #[repr(#ty)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #defmt pub enum #name { #items } diff --git a/src/generate/fieldset.rs b/src/generate/fieldset.rs index 7b542ce..891a7e3 100644 --- a/src/generate/fieldset.rs +++ b/src/generate/fieldset.rs @@ -8,7 +8,7 @@ use crate::util; use super::sorted; -pub fn render(_opts: &super::Options, ir: &IR, fs: &FieldSet, path: &str) -> Result { +pub fn render(opts: &super::Options, ir: &IR, fs: &FieldSet, path: &str) -> Result { let span = Span::call_site(); let mut items = TokenStream::new(); let mut field_names = Vec::with_capacity(fs.fields.len()); @@ -189,6 +189,28 @@ pub fn render(_opts: &super::Options, ir: &IR, fs: &FieldSet, path: &str) -> Res let name = Ident::new(name, span); let doc = util::doc(&fs.description); + let impl_defmt_format = opts.defmt_feature.as_ref().map(|defmt_feature| { + quote! { + #[cfg(feature = #defmt_feature)] + impl defmt::Format for #name { + fn format(&self, f: defmt::Formatter) { + #[derive(defmt::Format)] + struct #name { + #( + #field_names: #field_types, + )* + } + let proxy = #name { + #( + #field_names: #field_getters, + )* + }; + defmt::write!(f, "{}", proxy) + } + } + } + }); + let out = quote! { #doc #[repr(transparent)] @@ -216,23 +238,7 @@ pub fn render(_opts: &super::Options, ir: &IR, fs: &FieldSet, path: &str) -> Res } } - #[cfg(feature = "defmt")] - impl defmt::Format for #name { - fn format(&self, f: defmt::Formatter) { - #[derive(defmt::Format)] - struct #name { - #( - #field_names: #field_types, - )* - } - let proxy = #name { - #( - #field_names: #field_getters, - )* - }; - defmt::write!(f, "{}", proxy) - } - } + #impl_defmt_format }; Ok(out) diff --git a/src/generate/mod.rs b/src/generate/mod.rs index ddf659d..15214e7 100644 --- a/src/generate/mod.rs +++ b/src/generate/mod.rs @@ -59,22 +59,76 @@ impl Module { } } +#[derive(Debug, Default)] pub enum CommonModule { + #[default] Builtin, External(TokenStream), } +/// Options for the code generator. +/// +/// See the individual methods for the different options you can change. +#[derive(Debug)] pub struct Options { - pub common_module: CommonModule, + common_module: CommonModule, + defmt_feature: Option, +} + +impl Default for Options { + fn default() -> Self { + Self::new() + } } impl Options { + /// Create new options with all values set to the default. + /// + /// This will use a builtin common module, + /// and adds `defmt` support to the generated code gated behind a `feature = "defmt"` flag. + pub fn new() -> Self { + Self { + common_module: CommonModule::Builtin, + defmt_feature: Some("defmt".into()), + } + } + + /// Get the path to the common module. fn common_path(&self) -> TokenStream { match &self.common_module { CommonModule::Builtin => TokenStream::from_str("crate::common").unwrap(), CommonModule::External(path) => path.clone(), } } + + /// Get the configuration of the common module. + pub fn common_module(&self) -> &CommonModule { + &self.common_module + } + + /// Set the common module to use. + /// + /// Specify [`CommonModule::Builtin`] for a built-in common module, + /// or [`CommonModule::External`] to use an external common module. + pub fn with_common_module(mut self, common_module: CommonModule) -> Self { + self.common_module = common_module; + self + } + + /// Set the feature for adding defmt support in the generated code. + /// + /// You can fully remove `defmt` support in the generated code by specifying `None`. + pub fn with_defmt_feature(mut self, defmt_feature: Option) -> Self { + self.defmt_feature = defmt_feature; + self + } + + /// Get the feature flag used to enable/disable `defmt` support in the generated code. + /// + /// If set to `None`, no `defmt` support will be added at all to the generated code. + pub fn defmt_feature(&self) -> Option<&str> { + self.defmt_feature.as_deref() + } } pub fn render(ir: &IR, opts: &Options) -> Result { diff --git a/src/main.rs b/src/main.rs index 1002a29..f01c66f 100755 --- a/src/main.rs +++ b/src/main.rs @@ -80,6 +80,19 @@ struct Generate { /// Transforms file path #[clap(long)] transform: Vec, + /// Use an external `common` module. + #[clap(long)] + #[clap(value_name = "MODULE_PATH")] + common_module: Option, + /// Specify the feature name used in the generated code to conditionally enable defmt support. + #[clap(long)] + #[clap(value_name = "FEATURE")] + #[clap(default_value = "defmt")] + #[clap(conflicts_with = "no_defmt")] + defmt_feature: String, + /// Do not add defmt support to the generated code at all. + #[clap(long)] + no_defmt: bool, } /// Reformat a YAML @@ -122,6 +135,10 @@ struct GenBlock { /// Output YAML path #[clap(short, long)] output: String, + /// Use an external `common` module. + #[clap(long)] + #[clap(value_name = "MODULE_PATH")] + common_module: Option, } fn main() -> Result<()> { @@ -256,9 +273,17 @@ fn gen(args: Generate) -> Result<()> { apply_transform(&mut ir, transform)?; } - let generate_opts = generate::Options { - common_module: generate::CommonModule::Builtin, + let common_module = match args.common_module { + None => generate::CommonModule::Builtin, + Some(module) => generate::CommonModule::External(module.tokens()), + }; + let defmt_feature = match args.no_defmt { + true => None, + false => Some(args.defmt_feature), }; + let generate_opts = generate::Options::default() + .with_common_module(common_module) + .with_defmt_feature(defmt_feature); let items = generate::render(&ir, &generate_opts).unwrap(); fs::write("lib.rs", items.to_string())?; @@ -379,9 +404,11 @@ fn gen_block(args: GenBlock) -> Result<()> { // Ensure consistent sort order in the YAML. chiptool::transform::sort::Sort {}.run(&mut ir).unwrap(); - let generate_opts = generate::Options { - common_module: generate::CommonModule::Builtin, + let common_module = match args.common_module { + None => generate::CommonModule::Builtin, + Some(module) => generate::CommonModule::External(module.tokens()), }; + let generate_opts = generate::Options::default().with_common_module(common_module); let items = generate::render(&ir, &generate_opts).unwrap(); fs::write(&args.output, items.to_string())?; @@ -410,3 +437,44 @@ fn apply_transform>(ir: &mut IR, p: P) -> anyhow::Resu Ok(()) } + +/// Struct holding a valid module path as a string. +/// +/// Implements `FromStr` so it can be used directly as command line argument. +#[derive(Clone)] +struct ModulePath { + path: String, +} + +impl ModulePath { + /// Get the module path as a TokenStream. + fn tokens(&self) -> proc_macro2::TokenStream { + self.path.parse().unwrap() + } +} + +impl std::str::FromStr for ModulePath { + type Err = anyhow::Error; + + fn from_str(data: &str) -> Result { + data.parse::() + .map_err(|e| anyhow::anyhow!("{e}"))?; + + for (i, component) in data.split("::").enumerate() { + if component.is_empty() && i != 0 { + anyhow::bail!("path components can not be empty") + } + for (i, c) in component.chars().enumerate() { + if c.is_alphabetic() || c == '_' { + continue; + } + if i > 0 && c.is_alphanumeric() { + continue; + } + anyhow::bail!("path components may only consist of letters, digits and underscore") + } + } + + Ok(Self { path: data.into() }) + } +}