From 094c96241c0e9ecbdbc7805dbf38e8814718c267 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Mon, 26 Jan 2026 12:09:18 -0500 Subject: [PATCH 01/11] wip wip self --- tests/snapshots.rs | 341 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 330 insertions(+), 11 deletions(-) diff --git a/tests/snapshots.rs b/tests/snapshots.rs index d2f83fb..ea73e09 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -268,22 +268,341 @@ fn compile_test() { #[test] #[ignore = "use `just test-manual` to run"] fn single_file_manual_test() { - let test_path = Path::new("tests/fixtures/shared-test-files/dbc-cantools/choices.dbc"); + let test_path = Path::new("tests/fixtures/shared-test-files/dbc-cantools/big_numbers.dbc"); let test = get_test_info(test_path); let buffer = fs::read(test_path).unwrap(); - let buffer = test.decode(&buffer); - - let out_path = PathBuf::from("./target/manual/manual_test_output.rs"); - fs::create_dir_all(out_path.parent().unwrap()).unwrap(); - Config::builder() + let code = Config::builder() .dbc_name(&test.file_name) - .dbc_content(&buffer) + .dbc_content(&test.decode(&buffer)) .build() - .write_to_file(&out_path) + .generate() .unwrap(); - env::set_var("CARGO_ENCODED_RUSTFLAGS", "--deny=warnings"); - let t = trybuild::TestCases::new(); - t.pass(out_path); + insta::assert_snapshot!( + code, + @r#" + /// The name of the DBC file this code was generated from + #[allow(dead_code)] + pub const DBC_FILE_NAME: &str = "big_numbers"; + /// The version of the DBC file this code was generated from + #[allow(dead_code)] + pub const DBC_FILE_VERSION: &str = ""; + #[allow(unused_imports)] + use core::ops::BitOr; + #[allow(unused_imports)] + use bitvec::prelude::*; + #[allow(unused_imports)] + use embedded_can::{Id, StandardId, ExtendedId}; + /// All messages + #[allow( + clippy::absurd_extreme_comparisons, + clippy::excessive_precision, + clippy::manual_range_contains, + clippy::unnecessary_cast, + clippy::useless_conversion, + unused_comparisons, + unused_variables, + )] + #[derive(Clone)] + pub enum Messages { + /// TheMessage + TheMessage(TheMessage), + /// TheOtherMessage + TheOtherMessage(TheOtherMessage), + } + #[allow( + clippy::absurd_extreme_comparisons, + clippy::excessive_precision, + clippy::manual_range_contains, + clippy::unnecessary_cast, + clippy::useless_conversion, + unused_comparisons, + unused_variables, + )] + impl Messages { + /// Read message from CAN frame + #[inline(never)] + pub fn from_can_message(id: Id, payload: &[u8]) -> Result { + let res = match id { + TheMessage::MESSAGE_ID => { + Messages::TheMessage(TheMessage::try_from(payload)?) + } + TheOtherMessage::MESSAGE_ID => { + Messages::TheOtherMessage(TheOtherMessage::try_from(payload)?) + } + id => return Err(CanError::UnknownMessageId(id)), + }; + Ok(res) + } + } + /// TheMessage + /// + /// - Extended ID: 57 (0x39) + /// - Size: 8 bytes + #[derive(Clone, Copy)] + pub struct TheMessage { + raw: [u8; 8], + } + #[allow( + clippy::absurd_extreme_comparisons, + clippy::excessive_precision, + clippy::manual_range_contains, + clippy::unnecessary_cast, + clippy::useless_conversion, + unused_comparisons, + unused_variables, + )] + impl TheMessage { + pub const MESSAGE_ID: embedded_can::Id = Id::Extended(unsafe { + ExtendedId::new_unchecked(0x39) + }); + pub const THE_SIGNAL_MIN: i8 = 0_i8; + pub const THE_SIGNAL_MAX: i8 = 0_i8; + /// Construct new TheMessage from values + pub fn new(the_signal: i8) -> Result { + let mut res = Self { raw: [0u8; 8] }; + res.set_the_signal(the_signal)?; + Ok(res) + } + /// Access message payload raw value + pub fn raw(&self) -> &[u8; 8] { + &self.raw + } + /// TheSignal + /// + /// - Min: 0 + /// - Max: 0 + /// - Unit: "" + /// - Receivers: Vector__XXX + #[inline(always)] + pub fn the_signal(&self) -> i8 { + self.the_signal_raw() + } + /// Get raw value of TheSignal + /// + /// - Start bit: 0 + /// - Signal size: 8 bits + /// - Factor: 1 + /// - Offset: 0 + /// - Byte order: LittleEndian + /// - Value type: Signed + #[inline(always)] + pub fn the_signal_raw(&self) -> i8 { + let signal = self.raw.view_bits::()[0..8].load_le::(); + let factor = 1; + let signal = signal as i8; + i8::from(signal).saturating_mul(factor).saturating_add(0) + } + /// Set value of TheSignal + #[inline(always)] + pub fn set_the_signal(&mut self, value: i8) -> Result<(), CanError> { + if value < 0_i8 || 0_i8 < value { + return Err(CanError::ParameterOutOfRange { + message_id: TheMessage::MESSAGE_ID, + }); + } + let factor = 1; + let value = value + .checked_sub(0) + .ok_or(CanError::ParameterOutOfRange { + message_id: TheMessage::MESSAGE_ID, + })?; + let value = (value / factor) as i8; + let value = u8::from_ne_bytes(value.to_ne_bytes()); + self.raw.view_bits_mut::()[0..8].store_le(value); + Ok(()) + } + } + impl core::convert::TryFrom<&[u8]> for TheMessage { + type Error = CanError; + #[inline(always)] + fn try_from(payload: &[u8]) -> Result { + if payload.len() != 8 { + return Err(CanError::InvalidPayloadSize); + } + let mut raw = [0u8; 8]; + raw.copy_from_slice(&payload[..8]); + Ok(Self { raw }) + } + } + impl embedded_can::Frame for TheMessage { + fn new(id: impl Into, data: &[u8]) -> Option { + if id.into() != Self::MESSAGE_ID { None } else { data.try_into().ok() } + } + fn new_remote(_id: impl Into, _dlc: usize) -> Option { + unimplemented!() + } + fn is_extended(&self) -> bool { + match self.id() { + Id::Standard(_) => false, + Id::Extended(_) => true, + } + } + fn is_remote_frame(&self) -> bool { + false + } + fn id(&self) -> Id { + Self::MESSAGE_ID + } + fn dlc(&self) -> usize { + self.raw.len() + } + fn data(&self) -> &[u8] { + &self.raw + } + } + /// TheOtherMessage + /// + /// - Standard ID: 42 (0x2a) + /// - Size: 8 bytes + #[derive(Clone, Copy)] + pub struct TheOtherMessage { + raw: [u8; 8], + } + #[allow( + clippy::absurd_extreme_comparisons, + clippy::excessive_precision, + clippy::manual_range_contains, + clippy::unnecessary_cast, + clippy::useless_conversion, + unused_comparisons, + unused_variables, + )] + impl TheOtherMessage { + pub const MESSAGE_ID: embedded_can::Id = Id::Standard(unsafe { + StandardId::new_unchecked(0x2a) + }); + pub const THE_SIGNAL_MIN: i8 = 0_i8; + pub const THE_SIGNAL_MAX: i8 = 0_i8; + /// Construct new TheOtherMessage from values + pub fn new(the_signal: i8) -> Result { + let mut res = Self { raw: [0u8; 8] }; + res.set_the_signal(the_signal)?; + Ok(res) + } + /// Access message payload raw value + pub fn raw(&self) -> &[u8; 8] { + &self.raw + } + /// TheSignal + /// + /// - Min: 0 + /// - Max: 0 + /// - Unit: "" + /// - Receivers: Vector__XXX + #[inline(always)] + pub fn the_signal(&self) -> i8 { + self.the_signal_raw() + } + /// Get raw value of TheSignal + /// + /// - Start bit: 0 + /// - Signal size: 8 bits + /// - Factor: 1 + /// - Offset: 0 + /// - Byte order: LittleEndian + /// - Value type: Signed + #[inline(always)] + pub fn the_signal_raw(&self) -> i8 { + let signal = self.raw.view_bits::()[0..8].load_le::(); + let factor = 1; + let signal = signal as i8; + i8::from(signal).saturating_mul(factor).saturating_add(0) + } + /// Set value of TheSignal + #[inline(always)] + pub fn set_the_signal(&mut self, value: i8) -> Result<(), CanError> { + if value < 0_i8 || 0_i8 < value { + return Err(CanError::ParameterOutOfRange { + message_id: TheOtherMessage::MESSAGE_ID, + }); + } + let factor = 1; + let value = value + .checked_sub(0) + .ok_or(CanError::ParameterOutOfRange { + message_id: TheOtherMessage::MESSAGE_ID, + })?; + let value = (value / factor) as i8; + let value = u8::from_ne_bytes(value.to_ne_bytes()); + self.raw.view_bits_mut::()[0..8].store_le(value); + Ok(()) + } + } + impl core::convert::TryFrom<&[u8]> for TheOtherMessage { + type Error = CanError; + #[inline(always)] + fn try_from(payload: &[u8]) -> Result { + if payload.len() != 8 { + return Err(CanError::InvalidPayloadSize); + } + let mut raw = [0u8; 8]; + raw.copy_from_slice(&payload[..8]); + Ok(Self { raw }) + } + } + impl embedded_can::Frame for TheOtherMessage { + fn new(id: impl Into, data: &[u8]) -> Option { + if id.into() != Self::MESSAGE_ID { None } else { data.try_into().ok() } + } + fn new_remote(_id: impl Into, _dlc: usize) -> Option { + unimplemented!() + } + fn is_extended(&self) -> bool { + match self.id() { + Id::Standard(_) => false, + Id::Extended(_) => true, + } + } + fn is_remote_frame(&self) -> bool { + false + } + fn id(&self) -> Id { + Self::MESSAGE_ID + } + fn dlc(&self) -> usize { + self.raw.len() + } + fn data(&self) -> &[u8] { + &self.raw + } + } + /// This is just to make testing easier + #[allow(dead_code)] + fn main() {} + #[allow(dead_code)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum CanError { + UnknownMessageId(embedded_can::Id), + /// Signal parameter is not within the range + /// defined in the dbc + ParameterOutOfRange { + /// dbc message id + message_id: embedded_can::Id, + }, + InvalidPayloadSize, + /// Multiplexor value not defined in the dbc + InvalidMultiplexor { + /// dbc message id + message_id: embedded_can::Id, + /// Multiplexor value not defined in the dbc + multiplexor: u16, + }, + } + impl core::fmt::Display for CanError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{self:?}") + } + } + "#, + ) + + // let out_path = PathBuf::from("./target/manual/manual_test_output.rs"); + // fs::create_dir_all(out_path.parent().unwrap()).unwrap(); + // fs::write(&out_path, code).unwrap(); + // + // env::set_var("CARGO_ENCODED_RUSTFLAGS", "--deny=warnings"); + // let t = trybuild::TestCases::new(); + // t.pass(out_path); } From 197517b594366c306e8e4038f98fcc1863fd7fab Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Mon, 26 Jan 2026 15:49:49 -0500 Subject: [PATCH 02/11] wip --- Cargo.lock | 1 + Cargo.toml | 3 +- src/feature_config.rs | 57 +- src/lib.rs | 2119 +++++++++++++++++++++++------------------ src/pad.rs | 45 - tests/snapshots.rs | 2 +- 6 files changed, 1229 insertions(+), 998 deletions(-) delete mode 100644 src/pad.rs diff --git a/Cargo.lock b/Cargo.lock index 493a7df..062a434 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,6 +322,7 @@ dependencies = [ "heck", "insta", "prettyplease", + "proc-macro2", "quote", "syn", "test_each_file", diff --git a/Cargo.toml b/Cargo.toml index 33bc6b1..899e7eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ can-dbc = "8.0.1" embedded-can = "0.4.1" heck = "0.5.0" prettyplease = "0.2.37" -quote = "1.0" +proc-macro2 = "1.0.92" +quote = "1.0.44" syn = "2.0.114" typed-builder = "0.23.0" diff --git a/src/feature_config.rs b/src/feature_config.rs index 47247ea..e4a9382 100644 --- a/src/feature_config.rs +++ b/src/feature_config.rs @@ -1,7 +1,4 @@ use std::fmt::Display; -use std::io::Write; - -use anyhow::{Error, Result}; /// Configuration for including features in the code generator. /// @@ -20,31 +17,49 @@ pub enum FeatureConfig<'a> { } impl FeatureConfig<'_> { - pub(crate) fn fmt_attr(&self, w: &mut impl Write, attr: impl Display) -> Result<()> { + /// Generate an attribute token stream (like `#[derive(Debug)]`) + pub(crate) fn to_attr_tokens(&self, attr: impl Display) -> Option { + use quote::quote; + match self { - FeatureConfig::Always => writeln!(w, "#[{attr}]")?, - FeatureConfig::Gated(gate) => writeln!(w, "#[cfg_attr(feature = {gate:?}, {attr})]")?, - FeatureConfig::Never => {} + FeatureConfig::Always => { + let attr_str = attr.to_string(); + let tokens: proc_macro2::TokenStream = attr_str.parse().ok()?; + Some(quote! { #[#tokens] }) + } + FeatureConfig::Gated(gate) => { + let attr_str = attr.to_string(); + let tokens: proc_macro2::TokenStream = attr_str.parse().ok()?; + Some(quote! { #[cfg_attr(feature = #gate, #tokens)] }) + } + FeatureConfig::Never => None, } - Ok(()) } - pub(crate) fn fmt_cfg>( + /// Generate a token stream optionally wrapped in a cfg attribute + pub(crate) fn to_tokens_opt( &self, - mut w: W, - f: impl FnOnce(W) -> Result<(), E>, - ) -> Result<()> { + tokens: proc_macro2::TokenStream, + ) -> Option { + use quote::quote; + match self { - // If config is Never, return immediately without calling `f` - FeatureConfig::Never => return Ok(()), - // If config is Gated, prepend `f` with a cfg guard - FeatureConfig::Gated(gate) => { - writeln!(w, "#[cfg(feature = {gate:?})]")?; - } - // Otherwise, just call `f` - FeatureConfig::Always => {} + FeatureConfig::Never => None, + FeatureConfig::Gated(gate) => Some(quote! { + #[cfg(feature = #gate)] + #tokens + }), + FeatureConfig::Always => Some(tokens), } + } - f(w).map_err(Into::into) + /// Generate allow(dead_code) attribute if needed + pub(crate) fn allow_dead_code_tokens(allow: bool) -> Option { + if allow { + use quote::quote; + Some(quote! { #[allow(dead_code)] }) + } else { + None + } } } diff --git a/src/lib.rs b/src/lib.rs index 5af28d5..11587e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,44 +9,44 @@ mod feature_config; mod keywords; -mod pad; mod signal_type; mod utils; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fs::OpenOptions; -use std::io::{BufWriter, Write}; +use std::io::Write; use std::path::Path; -use anyhow::{anyhow, ensure, Context, Error, Result}; +use anyhow::{anyhow, ensure, Context, Result}; use can_dbc::ByteOrder::{BigEndian, LittleEndian}; -use can_dbc::MultiplexIndicator::{ - MultiplexedSignal, Multiplexor, MultiplexorAndMultiplexedSignal, Plain, -}; +use can_dbc::MultiplexIndicator::{MultiplexedSignal, Multiplexor, Plain}; use can_dbc::ValueType::Signed; use can_dbc::{Dbc, Message, MessageId, Signal, Transmitter, ValDescription, ValueDescription}; use heck::ToSnakeCase; -use quote::ToTokens; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; use typed_builder::TypedBuilder; pub use crate::feature_config::FeatureConfig; -use crate::pad::PadAdapter; use crate::signal_type::ValType; use crate::utils::{ enum_name, enum_variant_name, multiplex_enum_name, multiplexed_enum_variant_name, multiplexed_enum_variant_wrapper_name, MessageExt as _, SignalExt as _, }; -static ALLOW_DEADCODE: &str = "#[allow(dead_code)]"; -static ALLOW_LINTS: &str = r"#[allow( - clippy::absurd_extreme_comparisons, - clippy::excessive_precision, - clippy::manual_range_contains, - clippy::unnecessary_cast, - clippy::useless_conversion, - unused_comparisons, - unused_variables, -)]"; +fn allow_lints() -> TokenStream { + quote! { + #[allow( + clippy::absurd_extreme_comparisons, + clippy::excessive_precision, + clippy::manual_range_contains, + clippy::unnecessary_cast, + clippy::useless_conversion, + unused_comparisons, + unused_variables, + )] + } +} /// Code generator configuration. See module-level docs for an example. #[derive(TypedBuilder)] @@ -99,7 +99,7 @@ pub struct Config<'a> { impl Config<'_> { /// Write Rust structs matching DBC input description to `out` buffer - fn codegen(&self, out: impl Write) -> Result<()> { + fn codegen(&self) -> Result { let dbc = Dbc::try_from(self.dbc_content).map_err(|e| { let msg = "Could not parse dbc file"; if self.debug_prints { @@ -111,652 +111,737 @@ impl Config<'_> { if self.debug_prints { eprintln!("{dbc:#?}"); } - let mut w = BufWriter::new(out); - - writeln!( - w, - "/// The name of the DBC file this code was generated from" - )?; - writeln!(w, "#[allow(dead_code)]")?; - let dbc_name = self.dbc_name.to_token_stream(); - writeln!(w, "pub const DBC_FILE_NAME: &str = {dbc_name};")?; - writeln!( - w, - "/// The version of the DBC file this code was generated from" - )?; - writeln!(w, "#[allow(dead_code)]")?; - let dbc_version = dbc.version.0.to_token_stream(); - writeln!(w, "pub const DBC_FILE_VERSION: &str = {dbc_version};")?; - - writeln!(w, "#[allow(unused_imports)]")?; - writeln!(w, "use core::ops::BitOr;")?; - writeln!(w, "#[allow(unused_imports)]")?; - writeln!(w, "use bitvec::prelude::*;")?; - writeln!(w, "#[allow(unused_imports)]")?; - writeln!(w, "use embedded_can::{{Id, StandardId, ExtendedId}};")?; - - self.impl_arbitrary.fmt_cfg(&mut w, |w| { - writeln!(w, "use arbitrary::{{Arbitrary, Unstructured}};") - })?; - self.impl_serde.fmt_cfg(&mut w, |w| { - writeln!(w, "use serde::{{Serialize, Deserialize}};") - })?; - - writeln!(w)?; + let dbc_name = &self.dbc_name; + let dbc_version = &dbc.version.0; - self.render_dbc(&mut w, &dbc) - .context("could not generate Rust code")?; + let arbitrary_use = self.impl_arbitrary.to_tokens_opt(quote! { + use arbitrary::Arbitrary; + }); - writeln!(w)?; - writeln!(w, "/// This is just to make testing easier")?; - writeln!(w, "#[allow(dead_code)]")?; - writeln!(w, "fn main() {{}}")?; - writeln!(w)?; - self.render_error(&mut w)?; - self.render_arbitrary_helpers(&mut w)?; - writeln!(w)?; + let serde_use = self.impl_serde.to_tokens_opt(quote! { + use serde::{Serialize, Deserialize}; + }); - Ok(()) + let dbc_content = self + .render_dbc(&dbc) + .context("could not generate Rust code")?; + let error_content = self.render_error()?; + let arbitrary_helpers = self.render_arbitrary_helpers()?; + + Ok(quote! { + /// The name of the DBC file this code was generated from + #[allow(dead_code)] + pub const DBC_FILE_NAME: &str = #dbc_name; + /// The version of the DBC file this code was generated from + #[allow(dead_code)] + pub const DBC_FILE_VERSION: &str = #dbc_version; + + #[allow(unused_imports)] + use core::ops::BitOr; + #[allow(unused_imports)] + use bitvec::prelude::*; + #[allow(unused_imports)] + use embedded_can::{Id, StandardId, ExtendedId}; + + #arbitrary_use + #serde_use + + #dbc_content + + /// This is just to make testing easier + #[allow(dead_code)] + fn main() {} + + #error_content + #arbitrary_helpers + }) } - fn render_dbc(&self, w: &mut impl Write, dbc: &Dbc) -> Result<()> { - self.render_root_enum(w, dbc)?; + fn render_dbc(&self, dbc: &Dbc) -> Result { + let root_enum = self.render_root_enum(dbc)?; - for msg in get_relevant_messages(dbc) { - self.render_message(w, msg, dbc) - .with_context(|| format!("write message `{}`", msg.name))?; - writeln!(w)?; - } + let messages = get_relevant_messages(dbc) + .map(|msg| { + self.render_message(msg, dbc) + .with_context(|| format!("write message `{}`", msg.name)) + }) + .collect::>>()?; - Ok(()) + Ok(quote! { + #root_enum + #(#messages)* + }) } - fn render_root_enum(&self, w: &mut impl Write, dbc: &Dbc) -> Result<()> { - writeln!(w, "/// All messages")?; - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; - writeln!(w, "#[derive(Clone)]")?; - self.impl_debug.fmt_attr(w, "derive(Debug)")?; - self.impl_defmt.fmt_attr(w, "derive(defmt::Format)")?; - self.impl_serde.fmt_attr(w, "derive(Serialize)")?; - self.impl_serde.fmt_attr(w, "derive(Deserialize)")?; - writeln!(w, "pub enum Messages {{")?; - { - let mut w = PadAdapter::wrap(w); - for msg in get_relevant_messages(dbc) { - writeln!(w, "/// {}", msg.name)?; - writeln!(w, "{0}({0}),", msg.type_name())?; + fn render_root_enum(&self, dbc: &Dbc) -> Result { + let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + let debug_derive = self.impl_debug.to_attr_tokens("derive(Debug)"); + let defmt_derive = self.impl_defmt.to_attr_tokens("derive(defmt::Format)"); + let serde_derives = self + .impl_serde + .to_attr_tokens("derive(Serialize, Deserialize)"); + let allow_lints = allow_lints(); + + let variants: Vec<_> = get_relevant_messages(dbc) + .map(|msg| { + let msg_type = format_ident!("{}", msg.type_name()); + let doc_str = format!(" {}", &msg.name); // Use message name, not type_name + quote! { + #[doc = #doc_str] + #msg_type(#msg_type) + } + }) + .collect(); + + let from_can_arms: Vec<_> = get_relevant_messages(dbc) + .map(|msg| { + let msg_type = format_ident!("{}", msg.type_name()); + quote! { + #msg_type::MESSAGE_ID => Messages::#msg_type(#msg_type::try_from(payload)?) + } + }) + .collect(); + + let from_can_body = if from_can_arms.is_empty() { + quote! { + Err(CanError::UnknownMessageId(id)) } - } - writeln!(w, "}}")?; - writeln!(w)?; + } else { + quote! { + let res = match id { + #(#from_can_arms,)* + id => return Err(CanError::UnknownMessageId(id)), + }; + Ok(res) + } + }; - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; - writeln!(w, "impl Messages {{")?; - { - let mut w = PadAdapter::wrap(w); - writeln!(w, "/// Read message from CAN frame")?; - writeln!(w, "#[inline(never)]")?; - writeln!( - w, - "pub fn from_can_message(id: Id, payload: &[u8]) -> Result {{", - )?; + Ok(quote! { + /// All messages + #allow_lints + #allow_dead_code + #[derive(Clone)] + #debug_derive + #defmt_derive + #serde_derives + pub enum Messages { + #(#variants,)* + } - { - let mut w = PadAdapter::wrap(&mut w); - let messages: Vec<_> = get_relevant_messages(dbc).collect(); - if messages.is_empty() { - writeln!(w, "Err(CanError::UnknownMessageId(id))")?; - } else { - writeln!(w)?; - writeln!(w, "let res = match id {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - for msg in messages { - writeln!( - w, - "{0}::MESSAGE_ID => Messages::{0}({0}::try_from(payload)?),", - msg.type_name(), - )?; - } - writeln!(w, r"id => return Err(CanError::UnknownMessageId(id)),")?; - } - writeln!(w, "}};")?; - writeln!(w, "Ok(res)")?; + #allow_lints + #allow_dead_code + impl Messages { + /// Read message from CAN frame + #[inline(never)] + pub fn from_can_message(id: Id, payload: &[u8]) -> Result { + #from_can_body } } + }) + } - writeln!(w, "}}")?; - } - writeln!(w, "}}")?; - writeln!(w)?; + fn render_error(&self) -> Result { + let error_impl = self.impl_error.to_tokens_opt(quote! { + impl core::error::Error for CanError {} + }); - Ok(()) + Ok(quote! { + #[allow(dead_code)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum CanError { + UnknownMessageId(embedded_can::Id), + /// Signal parameter is not within the range + /// defined in the dbc + ParameterOutOfRange { + /// dbc message id + message_id: embedded_can::Id, + }, + InvalidPayloadSize, + /// Multiplexor value not defined in the dbc + InvalidMultiplexor { + /// dbc message id + message_id: embedded_can::Id, + /// Multiplexor value not defined in the dbc + multiplexor: u16, + }, + } + + impl core::fmt::Display for CanError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{self:?}") + } + } + + #error_impl + }) } - fn render_message(&self, w: &mut impl Write, msg: &Message, dbc: &Dbc) -> Result<()> { - writeln!(w, "/// {}", msg.name)?; - writeln!(w, "///")?; - match msg.id { - MessageId::Standard(id) => writeln!(w, "/// - Standard ID: {id} (0x{id:x})"), - MessageId::Extended(id) => writeln!(w, "/// - Extended ID: {id} (0x{id:x})"), - }?; - writeln!(w, "/// - Size: {} bytes", msg.size)?; + fn render_message(&self, msg: &Message, dbc: &Dbc) -> Result { + let msg_name = &msg.name; + let msg_type = format_ident!("{}", msg.type_name()); + let msg_size = msg.size as usize; + let msg_size_lit = syn::LitInt::new(&msg_size.to_string(), Span::call_site()); + + // Build message documentation as individual lines (with leading space for prettyplease) + let msg_name_doc = format!(" {}", msg_name); + let id_text = match msg.id { + MessageId::Standard(id) => format!(" - Standard ID: {id} ({id:#x})"), + MessageId::Extended(id) => format!(" - Extended ID: {id} ({id:#x})"), + }; + let size_text = format!(" - Size: {msg_size} bytes"); + + let mut struct_doc_lines = vec![ + quote! { #[doc = #msg_name_doc] }, + quote! { #[doc = ""] }, + quote! { #[doc = #id_text] }, + quote! { #[doc = #size_text] }, + ]; + if let Transmitter::NodeName(transmitter) = &msg.transmitter { - writeln!(w, "/// - Transmitter: {transmitter}")?; + let transmitter_text = format!(" - Transmitter: {transmitter}"); + struct_doc_lines.push(quote! { #[doc = #transmitter_text] }); } + if let Some(comment) = dbc.message_comment(msg.id) { - writeln!(w, "///")?; + struct_doc_lines.push(quote! { #[doc = ""] }); for line in comment.trim().lines() { - writeln!(w, "/// {line}")?; + let line_with_space = format!(" {}", line); + struct_doc_lines.push(quote! { #[doc = #line_with_space] }); } } - writeln!(w, "#[derive(Clone, Copy)]")?; - self.impl_serde.fmt_attr(w, "derive(Serialize)")?; - self.impl_serde.fmt_attr(w, "derive(Deserialize)")?; - writeln!(w, "pub struct {} {{", msg.type_name())?; - { - let mut w = PadAdapter::wrap(w); - self.impl_serde - .fmt_attr(&mut w, "serde(with = \"serde_bytes\")")?; - writeln!(w, "raw: [u8; {}],", msg.size)?; - } - writeln!(w, "}}")?; - writeln!(w)?; - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; - writeln!(w, "impl {} {{", msg.type_name())?; - { - let mut w = PadAdapter::wrap(w); - - writeln!( - w, - "pub const MESSAGE_ID: embedded_can::Id = {};", - match msg.id { - // use StandardId::new().unwrap() once const_option is stable - MessageId::Standard(id) => - format!("Id::Standard(unsafe {{ StandardId::new_unchecked({id:#x})}})"), - MessageId::Extended(id) => - format!("Id::Extended(unsafe {{ ExtendedId::new_unchecked({id:#x})}})"), - } - )?; - writeln!(w)?; + // Struct attributes + let serde_serialize = self.impl_serde.to_attr_tokens("derive(Serialize)"); + let serde_deserialize = self.impl_serde.to_attr_tokens("derive(Deserialize)"); + let serde_with = self + .impl_serde + .to_attr_tokens("serde(with = \"serde_bytes\")"); + + // Message ID constant + let message_id = match msg.id { + MessageId::Standard(id) => { + let id_lit = syn::LitInt::new(&format!("{id:#x}"), Span::call_site()); + quote! { Id::Standard(unsafe { StandardId::new_unchecked(#id_lit) }) } + } + MessageId::Extended(id) => { + let id_lit = syn::LitInt::new(&format!("{id:#x}"), Span::call_site()); + quote! { Id::Extended(unsafe { ExtendedId::new_unchecked(#id_lit) }) } + } + }; - for signal in &msg.signals { + // Signal min/max constants + let signal_constants: Vec<_> = msg + .signals + .iter() + .filter_map(|signal| { let typ = ValType::from_signal(signal); if typ != ValType::Bool { - let sig = signal.field_name().to_uppercase(); - let min = signal.min; - let max = signal.max; - writeln!(w, "pub const {sig}_MIN: {typ} = {min}_{typ};")?; - writeln!(w, "pub const {sig}_MAX: {typ} = {max}_{typ};")?; + let min_name = format_ident!("{}_MIN", signal.field_name().to_uppercase()); + let max_name = format_ident!("{}_MAX", signal.field_name().to_uppercase()); + let typ_ident = format_ident!("{}", typ.to_string()); + + let min_lit = generate_value_literal(signal.min, typ); + let max_lit = generate_value_literal(signal.max, typ); + + Some(quote! { + pub const #min_name: #typ_ident = #min_lit; + pub const #max_name: #typ_ident = #max_lit; + }) + } else { + None } - } - writeln!(w)?; + }) + .collect(); - writeln!(w, "/// Construct new {} from values", msg.name)?; - let args = msg - .signals - .iter() - .filter_map(|signal| { - if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - let field = signal.field_name(); - let typ = ValType::from_signal(signal); - Some(format!("{field}: {typ}")) - } else { - None - } - }) - .collect::>() - .join(", "); - writeln!(w, "pub fn new({args}) -> Result {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - let mutable = if msg.signals.is_empty() { "" } else { "mut " }; - let size = msg.size; - writeln!(w, "let {mutable}res = Self {{ raw: [0u8; {size}] }};")?; - for signal in &msg.signals { - if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - writeln!(w, "res.set_{0}({0})?;", signal.field_name())?; + // New function arguments and setter calls + let new_fn_args: Vec<_> = msg + .signals + .iter() + .filter_map(|signal| { + if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { + let field_name = format_ident!("{}", signal.field_name()); + let typ = ValType::from_signal(signal); + let typ_ident = format_ident!("{}", typ.to_string()); + Some(quote! { #field_name: #typ_ident }) + } else { + None + } + }) + .collect(); + + let new_fn_setters: Vec<_> = msg + .signals + .iter() + .filter_map(|signal| { + if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { + let field_name = format_ident!("{}", signal.field_name()); + let setter_name = format_ident!("set_{}", signal.field_name()); + Some(quote! { res.#setter_name(#field_name)?; }) + } else { + None + } + }) + .collect(); + + let new_fn_mutability = if msg.signals.is_empty() { + quote! {} + } else { + quote! { mut } + }; + + // Render signals + let signal_impls: Result> = msg + .signals + .iter() + .map(|signal| { + if signal.multiplexer_indicator == Multiplexor { + self.render_multiplexor_signal(signal, msg) + .with_context(|| format!("write signal impl `{}`", signal.name)) + } else if signal.multiplexer_indicator == Plain { + self.render_signal(signal, dbc, msg) + .with_context(|| format!("write signal impl `{}`", signal.name)) + } else { + Ok(quote! {}) + } + }) + .collect(); + let signal_impls = signal_impls?; + + // Render embedded can frame impl + let embedded_can_impl = self.render_embedded_can_frame(msg)?; + + // Render debug/defmt/arbitrary impls + let debug_impl = self.impl_debug.to_tokens_opt(render_debug_impl(msg)?); + let defmt_impl = self.impl_defmt.to_tokens_opt(render_defmt_impl(msg)?); + let arbitrary_impl = self + .impl_arbitrary + .to_tokens_opt(self.render_arbitrary(msg)?); + + // Render enums for this message + let enums_for_this_message: Result> = dbc + .value_descriptions + .iter() + .filter_map(|x| { + if let ValueDescription::Signal { + message_id, + name, + value_descriptions, + } = x + { + if *message_id != msg.id { + return None; } + dbc.signal_by_name(*message_id, name) + .map(|v| self.write_enum(v, msg, value_descriptions.as_slice())) + } else { + None } - writeln!(w, "Ok(res)")?; + }) + .collect(); + let enums_for_this_message = enums_for_this_message?; + + // Render multiplexor enums + let multiplexor_enums = msg + .signals + .iter() + .find(|signal| signal.multiplexer_indicator == Multiplexor) + .map(|multiplexor_signal| self.render_multiplexor_enums(dbc, msg, multiplexor_signal)) + .transpose()?; + + let allow_lints = allow_lints(); + let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + + // Create constructor doc string (with leading space) + let new_fn_doc = format!(" Construct new {msg_name} from values"); + + Ok(quote! { + #(#struct_doc_lines)* + #[derive(Clone, Copy)] + #serde_serialize + #serde_deserialize + pub struct #msg_type { + #serde_with + raw: [u8; #msg_size_lit], } - writeln!(w, "}}")?; - writeln!(w)?; - writeln!(w, "/// Access message payload raw value")?; - writeln!(w, "pub fn raw(&self) -> &[u8; {}] {{", msg.size)?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!(w, "&self.raw")?; + #allow_lints + #allow_dead_code + impl #msg_type { + pub const MESSAGE_ID: embedded_can::Id = #message_id; + + #(#signal_constants)* + + #[doc = #new_fn_doc] + pub fn new(#(#new_fn_args),*) -> Result { + let #new_fn_mutability res = Self { raw: [0u8; #msg_size_lit] }; + #(#new_fn_setters)* + Ok(res) + } + + /// Access message payload raw value + pub fn raw(&self) -> &[u8; #msg_size_lit] { + &self.raw + } + + #(#signal_impls)* } - writeln!(w, "}}")?; - writeln!(w)?; - - for signal in &msg.signals { - match signal.multiplexer_indicator { - Plain => self - .render_signal(&mut w, signal, dbc, msg) - .with_context(|| format!("write signal impl `{}`", signal.name))?, - Multiplexor => { - self.render_multiplexor_signal(&mut w, signal, msg)?; + + impl core::convert::TryFrom<&[u8]> for #msg_type { + type Error = CanError; + + #[inline(always)] + fn try_from(payload: &[u8]) -> Result { + if payload.len() != #msg_size_lit { + return Err(CanError::InvalidPayloadSize); } - MultiplexedSignal(_) | MultiplexorAndMultiplexedSignal(_) => {} + let mut raw = [0u8; #msg_size_lit]; + raw.copy_from_slice(&payload[..#msg_size_lit]); + Ok(Self { raw }) } } - } - writeln!(w, "}}")?; - writeln!(w)?; + #embedded_can_impl + #debug_impl + #defmt_impl + #arbitrary_impl + #(#enums_for_this_message)* + #multiplexor_enums + }) + } +} - let typ = msg.type_name(); - writeln!(w, "impl core::convert::TryFrom<&[u8]> for {typ} {{")?; - { - let mut w = PadAdapter::wrap(w); - writeln!(w, "type Error = CanError;")?; - writeln!(w)?; - writeln!(w, "#[inline(always)]")?; - writeln!( - w, - "fn try_from(payload: &[u8]) -> Result {{", - )?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!( - w, - r"if payload.len() != {} {{ return Err(CanError::InvalidPayloadSize); }}", - msg.size, - )?; - writeln!(w, "let mut raw = [0u8; {}];", msg.size)?; - writeln!(w, "raw.copy_from_slice(&payload[..{}]);", msg.size)?; - writeln!(w, "Ok(Self {{ raw }})")?; - } - writeln!(w, "}}")?; +/// Generate a literal token for a signal min/max value. +/// +/// For F32 types, always generates a float literal. +/// For other types, generates an integer literal if the value is an integer within i64 range, +/// otherwise generates a float literal with the integer type suffix. +fn generate_value_literal(value: f64, typ: ValType) -> TokenStream { + match typ { + ValType::F32 => { + let lit = syn::LitFloat::new(&format!("{value}_f32"), Span::call_site()); + quote! { #lit } } - writeln!(w, "}}")?; - writeln!(w)?; - - self.render_embedded_can_frame(w, msg)?; - - self.impl_debug - .fmt_cfg(&mut *w, |w| render_debug_impl(w, msg))?; - self.impl_defmt - .fmt_cfg(&mut *w, |w| render_defmt_impl(w, msg))?; - self.impl_arbitrary - .fmt_cfg(&mut *w, |w| self.render_arbitrary(w, msg))?; - - let enums_for_this_message = dbc.value_descriptions.iter().filter_map(|x| { - if let ValueDescription::Signal { - message_id, - name, - value_descriptions, - } = x - { - if *message_id != msg.id { - return None; - } - dbc.signal_by_name(*message_id, name) - .map(|v| (v, value_descriptions)) + _ => { + let typ_str = typ.to_string().to_lowercase(); + // Check if value is an integer and fits in i64 range + if is_integer(value) && value >= i64::MIN as f64 && value <= i64::MAX as f64 { + let val = value as i64; + let lit = syn::LitInt::new(&format!("{val}_{typ_str}"), Span::call_site()); + quote! { #lit } } else { - None + // Use float literal with integer type suffix for fractional/overflow values + let lit = syn::LitFloat::new(&format!("{value}_{typ_str}"), Span::call_site()); + quote! { #lit } } - }); - for (signal, variants) in enums_for_this_message { - self.write_enum(w, signal, msg, variants.as_slice())?; - } - - let multiplexor_signal = msg - .signals - .iter() - .find(|s| s.multiplexer_indicator == Multiplexor); - - if let Some(multiplexor_signal) = multiplexor_signal { - self.render_multiplexor_enums(w, dbc, msg, multiplexor_signal)?; } - - Ok(()) } +} - fn render_signal( - &self, - w: &mut impl Write, - signal: &Signal, - dbc: &Dbc, - msg: &Message, - ) -> Result<()> { - writeln!(w, "/// {}", signal.name)?; +impl Config<'_> { + fn render_signal(&self, signal: &Signal, dbc: &Dbc, msg: &Message) -> Result { + let signal_name = &signal.name; + let fn_name = format_ident!("{}", signal.field_name()); + let fn_name_raw = format_ident!("{}_raw", signal.field_name()); + + // Build documentation using single multiline format and parse into tokens + let min_doc = signal.min.to_string(); + let max_doc = signal.max.to_string(); + let unit_doc = format!("{:?}", signal.unit); + let receivers_doc = signal.receivers.join(", "); + let start_bit_doc = signal.start_bit.to_string(); + let size_doc = signal.size.to_string(); + let factor_doc = signal.factor.to_string(); + let offset_doc = signal.offset.to_string(); + let byte_order_doc = format!("{:?}", signal.byte_order); + let value_type_doc = format!("{:?}", signal.value_type); + + // Build signal getter doc as doc comment and parse into tokens + let mut signal_doc_text = format!("/// {signal_name}"); if let Some(comment) = dbc.signal_comment(msg.id, &signal.name) { - writeln!(w, "///")?; + signal_doc_text.push_str("\n///"); for line in comment.trim().lines() { - writeln!(w, "/// {line}")?; + signal_doc_text.push_str(&format!("\n/// {line}")); } } - writeln!(w, "///")?; - writeln!(w, "/// - Min: {}", signal.min)?; - writeln!(w, "/// - Max: {}", signal.max)?; - writeln!(w, "/// - Unit: {:?}", signal.unit)?; - writeln!(w, "/// - Receivers: {}", signal.receivers.join(", "))?; - writeln!(w, "#[inline(always)]")?; - let fn_name = signal.field_name(); - if let Some(variants) = dbc.value_descriptions_for_signal(msg.id, &signal.name) { - let type_name = enum_name(msg, signal); + signal_doc_text.push_str(&format!( + "\n/// + /// - Min: {min_doc} + /// - Max: {max_doc} + /// - Unit: {unit_doc} + /// - Receivers: {receivers_doc}" + )); + let signal_doc_tokens: TokenStream = signal_doc_text + .parse() + .map_err(|e| anyhow::anyhow!("Failed to parse signal doc: {}", e))?; + + // Build raw getter doc as doc comment and parse into tokens + let raw_doc_text = format!( + "/// Get raw value of {signal_name} + /// + /// - Start bit: {start_bit_doc} + /// - Signal size: {size_doc} bits + /// - Factor: {factor_doc} + /// - Offset: {offset_doc} + /// - Byte order: {byte_order_doc} + /// - Value type: {value_type_doc}" + ); + let raw_doc_tokens = to_tokens(raw_doc_text)?; + + let typ = ValType::from_signal(signal); + let typ_ident = format_ident!("{}", typ.to_string()); + + // Generate getter function + let getter = if let Some(variants) = dbc.value_descriptions_for_signal(msg.id, &signal.name) + { + let type_name = format_ident!("{}", enum_name(msg, signal)); let signal_ty = ValType::from_signal(signal); let variant_infos = generate_variant_info(variants, signal_ty); - writeln!(w, "pub fn {fn_name}(&self) -> {type_name} {{")?; - { - let mut w = PadAdapter::wrap(w); + // Use signed type for loading when signal is signed and has negative values + let has_negative_values = variants.iter().any(|v| v.id < 0); + let load_type = if signal.value_type == Signed && has_negative_values { + signal_ty + } else { + ValType::from_signal_uint(signal) + }; - // Use signed type for loading when signal is signed and has negative values - let has_negative_values = variants.iter().any(|v| v.id < 0); - let load_type = if signal.value_type == Signed && has_negative_values { - signal_ty - } else { - ValType::from_signal_uint(signal) - }; + let read_expr = read_fn_with_type_tokens(signal, msg, load_type)?; - let read = read_fn_with_type(signal, msg, load_type)?; - writeln!(w, r"let signal = {read};")?; - writeln!(w)?; - writeln!(w, "match signal {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - for info in &variant_infos { - let literal = info.value; - let variant = &info.base_name; - match info.dup_type { - DuplicateType::Unique => { - writeln!(w, "{literal} => {type_name}::{variant},")?; - } - DuplicateType::FirstDuplicate | DuplicateType::Duplicate => { - writeln!(w, "{literal} => {type_name}::{variant}({literal}),")?; - } + let match_arms: Vec<_> = variant_infos + .iter() + .map(|info| { + let literal = syn::LitInt::new(&info.value.to_string(), Span::call_site()); + let variant = format_ident!("{}", &info.base_name); + match info.dup_type { + DuplicateType::Unique => { + quote! { #literal => #type_name::#variant } + } + DuplicateType::FirstDuplicate | DuplicateType::Duplicate => { + quote! { #literal => #type_name::#variant(#literal) } } } - writeln!(w, "_ => {type_name}::_Other(self.{fn_name}_raw()),")?; + }) + .collect(); + + quote! { + #[inline(always)] + pub fn #fn_name(&self) -> #type_name { + let signal = #read_expr; + + match signal { + #(#match_arms,)* + _ => #type_name::_Other(self.#fn_name_raw()), + } } - writeln!(w, "}}")?; } - writeln!(w, "}}")?; - writeln!(w)?; } else { - let typ = ValType::from_signal(signal); - writeln!(w, "pub fn {fn_name}(&self) -> {typ} {{")?; - { - let mut w = PadAdapter::wrap(w); - writeln!(w, "self.{fn_name}_raw()")?; + quote! { + #[inline(always)] + pub fn #fn_name(&self) -> #typ_ident { + self.#fn_name_raw() + } } - writeln!(w, "}}")?; - writeln!(w)?; - } + }; - writeln!(w, "/// Get raw value of {}", signal.name)?; - writeln!(w, "///")?; - writeln!(w, "/// - Start bit: {}", signal.start_bit)?; - writeln!(w, "/// - Signal size: {} bits", signal.size)?; - writeln!(w, "/// - Factor: {}", signal.factor)?; - writeln!(w, "/// - Offset: {}", signal.offset)?; - writeln!(w, "/// - Byte order: {:?}", signal.byte_order)?; - writeln!(w, "/// - Value type: {:?}", signal.value_type)?; - writeln!(w, "#[inline(always)]")?; - let typ = ValType::from_signal(signal); - writeln!(w, "pub fn {fn_name}_raw(&self) -> {typ} {{")?; - { - let mut w = PadAdapter::wrap(w); - signal_from_payload(&mut w, signal, msg).context("signal from payload")?; - } - writeln!(w, "}}")?; - writeln!(w)?; + // Generate raw getter function + let signal_from_payload_body = + signal_from_payload_tokens(signal, msg).context("signal from payload")?; - self.render_set_signal(w, signal, msg)?; + let setter = self.render_set_signal(signal, msg)?; - Ok(()) + Ok(quote! { + #signal_doc_tokens + #getter + + #raw_doc_tokens + #[inline(always)] + pub fn #fn_name_raw(&self) -> #typ_ident { + #signal_from_payload_body + } + + #setter + }) } +} + +fn to_tokens(raw_doc_text: String) -> Result { + raw_doc_text + .parse() + .map_err(|e| anyhow::anyhow!("Failed to parse raw doc: {}", e)) +} - fn render_set_signal(&self, w: &mut impl Write, signal: &Signal, msg: &Message) -> Result<()> { - writeln!(w, "/// Set value of {}", signal.name)?; - writeln!(w, "#[inline(always)]")?; +impl Config<'_> { + fn render_set_signal(&self, signal: &Signal, msg: &Message) -> Result { + let setter_name = format_ident!("set_{}", signal.field_name()); // To avoid accidentally changing the multiplexor value without changing // the signals accordingly this fn is kept private for multiplexors. let visibility = if signal.multiplexer_indicator == Multiplexor { - "" + quote! {} } else { - "pub " + quote! { pub } }; - let field = signal.field_name(); - let typ = ValType::from_signal(signal); - writeln!( - w, - "{visibility}fn set_{field}(&mut self, value: {typ}) -> Result<(), CanError> {{", - )?; + // Doc comment for setter + let setter_doc = if signal.multiplexer_indicator == Multiplexor { + format!(" Set value of {}", signal.name) + } else { + format!(" Set value of {}", signal.name) + }; - { - let mut w = PadAdapter::wrap(w); + let typ = ValType::from_signal(signal); + let typ_ident = format_ident!("{}", typ.to_string()); + let msg_type = format_ident!("{}", msg.type_name()); - if signal.size != 1 { - if let FeatureConfig::Gated(gate) = self.check_ranges { - writeln!(w, r"#[cfg(feature = {gate:?})]")?; - } + // Range check logic + let range_check = if signal.size != 1 { + let min = signal.min; + let max = signal.max; - if let FeatureConfig::Gated(..) | FeatureConfig::Always = self.check_ranges { - let typ = ValType::from_signal(signal); - let min = signal.min; - let max = signal.max; - writeln!(w, r"if value < {min}_{typ} || {max}_{typ} < value {{")?; - - { - let mut w = PadAdapter::wrap(&mut w); - let typ = msg.type_name(); - writeln!( - w, - r"return Err(CanError::ParameterOutOfRange {{ message_id: {typ}::MESSAGE_ID }});", - )?; - } + let min_lit = generate_value_literal(min, typ); + let max_lit = generate_value_literal(max, typ); - writeln!(w, r"}}")?; + let check_code = quote! { + if value < #min_lit || #max_lit < value { + return Err(CanError::ParameterOutOfRange { message_id: #msg_type::MESSAGE_ID }); } - } - signal_to_payload(&mut w, signal, msg).context("signal to payload")?; - } + }; + + self.check_ranges.to_tokens_opt(check_code) + } else { + None + }; - writeln!(w, "}}")?; - writeln!(w)?; + let signal_to_payload_body = signal_to_payload_tokens(signal, msg)?; - Ok(()) + Ok(quote! { + #[doc = #setter_doc] + #[inline(always)] + #visibility fn #setter_name(&mut self, value: #typ_ident) -> Result<(), CanError> { + #range_check + #signal_to_payload_body + } + }) } +} - fn render_multiplexor_signal( - &self, - w: &mut impl Write, - signal: &Signal, - msg: &Message, - ) -> Result<()> { - writeln!(w, "/// Get raw value of {}", signal.name)?; - writeln!(w, "///")?; - writeln!(w, "/// - Start bit: {}", signal.start_bit)?; - writeln!(w, "/// - Signal size: {} bits", signal.size)?; - writeln!(w, "/// - Factor: {}", signal.factor)?; - writeln!(w, "/// - Offset: {}", signal.offset)?; - writeln!(w, "/// - Byte order: {:?}", signal.byte_order)?; - writeln!(w, "/// - Value type: {:?}", signal.value_type)?; - writeln!(w, "#[inline(always)]")?; - let field = signal.field_name(); - let typ = ValType::from_signal(signal); - writeln!(w, "pub fn {field}_raw(&self) -> {typ} {{")?; - { - let mut w = PadAdapter::wrap(w); - signal_from_payload(&mut w, signal, msg).context("signal from payload")?; +fn render_set_signal_multiplexer( + multiplexor: &Signal, + msg: &Message, + switch_index: u64, +) -> Result { + let enum_variant = format_ident!( + "{}", + multiplexed_enum_variant_name(msg, multiplexor, switch_index)? + ); + let setter_name = format_ident!( + "set_{}", + multiplexed_enum_variant_wrapper_name(switch_index).to_snake_case() + ); + let multiplexor_setter = format_ident!("set_{}", multiplexor.field_name()); + let switch_index_lit = syn::LitInt::new(&switch_index.to_string(), Span::call_site()); + + let doc = format!(" Set value of {}", multiplexor.name); + + Ok(quote! { + #[doc = #doc] + #[inline(always)] + pub fn #setter_name(&mut self, value: #enum_variant) -> Result<(), CanError> { + let b0 = BitArray::<_, LocalBits>::new(self.raw); + let b1 = BitArray::<_, LocalBits>::new(value.raw); + self.raw = b0.bitor(b1).into_inner(); + self.#multiplexor_setter(#switch_index_lit)?; + Ok(()) } - writeln!(w, "}}")?; - writeln!(w)?; + }) +} - let field = signal.field_name(); - let typ = multiplex_enum_name(msg, signal)?; - writeln!(w, "pub fn {field}(&mut self) -> Result<{typ}, CanError> {{")?; +impl Config<'_> { + fn render_multiplexor_signal(&self, signal: &Signal, msg: &Message) -> Result { + let field = format_ident!("{}", signal.field_name()); + let field_raw = format_ident!("{}_raw", signal.field_name()); + let typ = ValType::from_signal(signal); + let typ_ident = format_ident!("{}", typ.to_string()); + let enum_type = format_ident!("{}", multiplex_enum_name(msg, signal)?); + + let signal_name = &signal.name; + let start_bit_doc = signal.start_bit.to_string(); + let size_doc = signal.size.to_string(); + let factor_doc = signal.factor.to_string(); + let offset_doc = signal.offset.to_string(); + let byte_order_doc = format!("{:?}", signal.byte_order); + let value_type_doc = format!("{:?}", signal.value_type); + + // Build raw doc as doc comment string and parse into tokens + let raw_doc_text = format!( + "/// Get raw value of {signal_name} + /// + /// - Start bit: {start_bit_doc} + /// - Signal size: {size_doc} bits + /// - Factor: {factor_doc} + /// - Offset: {offset_doc} + /// - Byte order: {byte_order_doc} + /// - Value type: {value_type_doc}" + ); + let raw_doc_tokens: TokenStream = raw_doc_text + .parse() + .map_err(|e| anyhow::anyhow!("Failed to parse multiplexor raw doc: {}", e))?; + + let signal_from_payload_body = signal_from_payload_tokens(signal, msg)?; let multiplexer_indexes: BTreeSet = msg .signals .iter() .filter_map(|s| { if let MultiplexedSignal(index) = &s.multiplexer_indicator { - Some(index) + Some(*index) } else { None } }) - .copied() .collect(); - { - let mut w = PadAdapter::wrap(w); - writeln!(w, "match self.{}_raw() {{", signal.field_name())?; - - { - let mut w = PadAdapter::wrap(&mut w); - for multiplexer_index in &multiplexer_indexes { - writeln!( - w, - "{idx} => Ok({enum_name}::{multiplexed_wrapper_name}({multiplexed_name}{{ raw: self.raw }})),", - idx = multiplexer_index, - enum_name = multiplex_enum_name(msg, signal)?, - multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*multiplexer_index), - multiplexed_name = - multiplexed_enum_variant_name(msg, signal, *multiplexer_index)?, - )?; - } - writeln!( - w, - "multiplexor => Err(CanError::InvalidMultiplexor {{ message_id: {}::MESSAGE_ID, multiplexor: multiplexor.into() }}),", - msg.type_name(), - )?; + let match_arms: Vec<_> = multiplexer_indexes.iter().map(|idx| { + let multiplexed_wrapper_name = format_ident!("{}", multiplexed_enum_variant_wrapper_name(*idx)); + let multiplexed_name = format_ident!("{}", multiplexed_enum_variant_name(msg, signal, *idx).unwrap()); + let idx_lit = syn::LitInt::new(&idx.to_string(), Span::call_site()); + quote! { + #idx_lit => Ok(#enum_type::#multiplexed_wrapper_name(#multiplexed_name { raw: self.raw })) } + }).collect(); - writeln!(w, "}}")?; - } - writeln!(w, "}}")?; - - self.render_set_signal(w, signal, msg)?; - - for switch_index in multiplexer_indexes { - render_set_signal_multiplexer(w, signal, msg, switch_index)?; - } - - Ok(()) - } + let msg_type = format_ident!("{}", msg.type_name()); - fn write_enum( - &self, - w: &mut impl Write, - signal: &Signal, - msg: &Message, - variants: &[ValDescription], - ) -> Result<()> { - let type_name = enum_name(msg, signal); - let signal_ty = ValType::from_signal(signal); + let setter = self.render_set_signal(signal, msg)?; - // Generate variant info to handle duplicates with tuple variants - let variant_infos = generate_variant_info(variants, signal_ty); + let set_multiplexer_fns: Result> = multiplexer_indexes + .iter() + .map(|switch_index| render_set_signal_multiplexer(signal, msg, *switch_index)) + .collect(); + let set_multiplexer_fns = set_multiplexer_fns?; - writeln!(w, "/// Defined values for {}", signal.name)?; - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; - writeln!(w, "#[derive(Clone, Copy, PartialEq)]")?; - self.impl_debug.fmt_attr(w, "derive(Debug)")?; - self.impl_defmt.fmt_attr(w, "derive(defmt::Format)")?; - self.impl_serde.fmt_attr(w, "derive(Serialize)")?; - self.impl_serde.fmt_attr(w, "derive(Deserialize)")?; - writeln!(w, "pub enum {type_name} {{")?; - { - let mut w = PadAdapter::wrap(w); - for info in &variant_infos { - let variant = &info.base_name; - match info.dup_type { - DuplicateType::Unique => writeln!(w, "{variant},")?, - DuplicateType::FirstDuplicate => { - writeln!(w, "{variant}({}),", info.value_type)?; - } - DuplicateType::Duplicate => {} - } + Ok(quote! { + #raw_doc_tokens + #[inline(always)] + pub fn #field_raw(&self) -> #typ_ident { + #signal_from_payload_body } - writeln!(w, "_Other({signal_ty}),")?; - } - writeln!(w, "}}")?; - writeln!(w)?; - writeln!(w, "impl From<{type_name}> for {signal_ty} {{")?; - { - let match_on_raw_type = match ValType::from_signal(signal) { - ValType::Bool => |x: i64| format!("{}", x == 1), - ValType::F32 => |x: i64| format!("{x}_f32"), - _ => |x: i64| format!("{x}"), - }; - - let mut w = PadAdapter::wrap(w); - writeln!(w, "fn from(val: {type_name}) -> {signal_ty} {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!(w, "match val {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - for info in &variant_infos { - match info.dup_type { - DuplicateType::Unique => { - let literal = match_on_raw_type(info.value); - writeln!(w, "{type_name}::{} => {literal},", info.base_name)?; - } - DuplicateType::FirstDuplicate => { - writeln!(w, "{type_name}::{}(v) => v,", info.base_name)?; - } - DuplicateType::Duplicate => {} - } - } - writeln!(w, "{type_name}::_Other(x) => x,")?; + pub fn #field(&mut self) -> Result<#enum_type, CanError> { + match self.#field_raw() { + #(#match_arms,)* + multiplexor => Err(CanError::InvalidMultiplexor { + message_id: #msg_type::MESSAGE_ID, + multiplexor: multiplexor.into() + }), } - writeln!(w, "}}")?; } - writeln!(w, "}}")?; - } - writeln!(w, "}}")?; - writeln!(w)?; - Ok(()) - } -} -fn render_set_signal_multiplexer( - w: &mut impl Write, - multiplexor: &Signal, - msg: &Message, - switch_index: u64, -) -> Result<()> { - writeln!(w, "/// Set value of {}", multiplexor.name)?; - writeln!(w, "#[inline(always)]")?; - writeln!( - w, - "pub fn set_{enum_variant_wrapper}(&mut self, value: {enum_variant}) -> Result<(), CanError> {{", - enum_variant_wrapper = multiplexed_enum_variant_wrapper_name(switch_index).to_snake_case(), - enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?, - )?; - - { - let mut w = PadAdapter::wrap(w); - - writeln!(w, "let b0 = BitArray::<_, LocalBits>::new(self.raw);")?; - writeln!(w, "let b1 = BitArray::<_, LocalBits>::new(value.raw);")?; - writeln!(w, "self.raw = b0.bitor(b1).into_inner();")?; - writeln!(w, "self.set_{}({switch_index})?;", multiplexor.field_name())?; - writeln!(w, "Ok(())")?; + #setter + #(#set_multiplexer_fns)* + }) } - - writeln!(w, "}}")?; - writeln!(w)?; - - Ok(()) } fn be_start_end_bit(signal: &Signal, msg: &Message) -> Result<(u64, u64)> { @@ -805,121 +890,263 @@ fn le_start_end_bit(signal: &Signal, msg: &Message) -> Result<(u64, u64)> { Ok((start_bit, end_bit)) } -fn signal_from_payload(w: &mut impl Write, signal: &Signal, msg: &Message) -> Result<()> { - writeln!(w, r"let signal = {};", read_fn(signal, msg)?)?; - writeln!(w)?; +fn read_fn_with_type_tokens(signal: &Signal, msg: &Message, typ: ValType) -> Result { + let typ_ident = format_ident!("{}", typ.to_string()); + Ok(match signal.byte_order { + LittleEndian => { + let (start, end) = le_start_end_bit(signal, msg)?; + let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + quote! { self.raw.view_bits::()[#start_lit..#end_lit].load_le::<#typ_ident>() } + } + BigEndian => { + let (start, end) = be_start_end_bit(signal, msg)?; + let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + quote! { self.raw.view_bits::()[#start_lit..#end_lit].load_be::<#typ_ident>() } + } + }) +} + +fn signal_from_payload_tokens(signal: &Signal, msg: &Message) -> Result { + let read_expr = read_fn_with_type_tokens(signal, msg, ValType::from_signal_int(signal))?; let typ = ValType::from_signal(signal); - match typ { + let typ_ident = format_ident!("{}", typ.to_string()); + + Ok(match typ { ValType::Bool => { - writeln!(w, "signal == 1")?; + quote! { + let signal = #read_expr; + signal == 1 + } } ValType::F32 => { - // Scaling is always done on floats - writeln!(w, "let factor = {}_f32;", signal.factor)?; - writeln!(w, "let offset = {}_f32;", signal.offset)?; - writeln!(w, "(signal as f32) * factor + offset")?; + let factor = signal.factor; + let offset = signal.offset; + let factor_lit = syn::LitFloat::new(&format!("{factor}_f32"), Span::call_site()); + let offset_lit = syn::LitFloat::new(&format!("{offset}_f32"), Span::call_site()); + quote! { + let signal = #read_expr; + let factor = #factor_lit; + let offset = #offset_lit; + (signal as f32) * factor + offset + } } _ => { - writeln!(w, "let factor = {};", signal.factor)?; - if Some(typ) == ValType::from_signal_uint(signal).unsigned_to_signed() { - // Can't do iNN::from(uNN) if they both fit in the same integer type, - // so cast first - writeln!(w, "let signal = signal as {typ};")?; - } + let factor = signal.factor; + let factor_lit = syn::LitFloat::new(&factor.to_string(), Span::call_site()); + + let signal_cast = if Some(typ) == ValType::from_signal_uint(signal).unsigned_to_signed() + { + quote! { let signal = signal as #typ_ident; } + } else { + quote! {} + }; if signal.offset >= 0.0 { - writeln!( - w, - "{typ}::from(signal).saturating_mul(factor).saturating_add({})", - signal.offset, - )?; + let offset = signal.offset; + let offset_lit = syn::LitFloat::new(&offset.to_string(), Span::call_site()); + quote! { + let signal = #read_expr; + let factor = #factor_lit; + #signal_cast + #typ_ident::from(signal).saturating_mul(factor).saturating_add(#offset_lit) + } } else { - writeln!( - w, - "{typ}::from(signal).saturating_mul(factor).saturating_sub({})", - signal.offset.abs(), - )?; + let offset_abs = signal.offset.abs(); + let offset_lit = syn::LitFloat::new(&offset_abs.to_string(), Span::call_site()); + quote! { + let signal = #read_expr; + let factor = #factor_lit; + #signal_cast + #typ_ident::from(signal).saturating_mul(factor).saturating_sub(#offset_lit) + } } } - } - Ok(()) -} - -fn read_fn(signal: &Signal, msg: &Message) -> Result { - read_fn_with_type(signal, msg, ValType::from_signal_int(signal)) -} - -fn read_fn_with_type(signal: &Signal, msg: &Message, typ: ValType) -> Result { - Ok(match signal.byte_order { - LittleEndian => { - let (start, end) = le_start_end_bit(signal, msg)?; - format!("self.raw.view_bits::()[{start}..{end}].load_le::<{typ}>()") - } - BigEndian => { - let (start, end) = be_start_end_bit(signal, msg)?; - format!("self.raw.view_bits::()[{start}..{end}].load_be::<{typ}>()") - } }) } -fn signal_to_payload(w: &mut impl Write, signal: &Signal, msg: &Message) -> Result<()> { +fn signal_to_payload_tokens(signal: &Signal, msg: &Message) -> Result { let typ = ValType::from_signal(signal); - match typ { + let msg_type = format_ident!("{}", msg.type_name()); + + let value_conversion = match typ { ValType::Bool => { - // Map boolean to byte so we can pack it - writeln!(w, "let value = value as u8;")?; + quote! { let value = value as u8; } } ValType::F32 => { - // Massage value into an int - writeln!(w, "let factor = {}_f32;", signal.factor)?; - writeln!(w, "let offset = {}_f32;", signal.offset)?; - let typ = ValType::from_signal_int(signal); - writeln!(w, "let value = ((value - offset) / factor) as {typ};")?; - writeln!(w)?; + let factor = signal.factor; + let offset = signal.offset; + let factor_lit = syn::LitFloat::new(&format!("{factor}_f32"), Span::call_site()); + let offset_lit = syn::LitFloat::new(&format!("{offset}_f32"), Span::call_site()); + let int_typ = ValType::from_signal_int(signal); + let int_typ_ident = format_ident!("{}", int_typ.to_string()); + quote! { + let factor = #factor_lit; + let offset = #offset_lit; + let value = ((value - offset) / factor) as #int_typ_ident; + } } _ => { - writeln!(w, "let factor = {};", signal.factor)?; + let factor = signal.factor; + let factor_lit = syn::LitFloat::new(&factor.to_string(), Span::call_site()); + let int_typ = ValType::from_signal_int(signal); + let int_typ_ident = format_ident!("{}", int_typ.to_string()); + if signal.offset >= 0.0 { - writeln!(w, "let value = value.checked_sub({})", signal.offset)?; + let offset = signal.offset; + let offset_lit = syn::LitFloat::new(&offset.to_string(), Span::call_site()); + quote! { + let factor = #factor_lit; + let value = value.checked_sub(#offset_lit) + .ok_or(CanError::ParameterOutOfRange { message_id: #msg_type::MESSAGE_ID })?; + let value = (value / factor) as #int_typ_ident; + } } else { - writeln!(w, "let value = value.checked_add({})", signal.offset.abs())?; + let offset_abs = signal.offset.abs(); + let offset_lit = syn::LitFloat::new(&offset_abs.to_string(), Span::call_site()); + quote! { + let factor = #factor_lit; + let value = value.checked_add(#offset_lit) + .ok_or(CanError::ParameterOutOfRange { message_id: #msg_type::MESSAGE_ID })?; + let value = (value / factor) as #int_typ_ident; + } } - writeln!( - w, - " .ok_or(CanError::ParameterOutOfRange {{ message_id: {}::MESSAGE_ID }})?;", - msg.type_name(), - )?; - let typ = ValType::from_signal_int(signal); - writeln!(w, "let value = (value / factor) as {typ};")?; - writeln!(w)?; } - } + }; - if signal.value_type == Signed { - let typ = ValType::from_signal_uint(signal); - writeln!(w, "let value = {typ}::from_ne_bytes(value.to_ne_bytes());")?; - } + let signed_conversion = if signal.value_type == Signed { + let uint_typ = ValType::from_signal_uint(signal); + let uint_typ_ident = format_ident!("{}", uint_typ.to_string()); + quote! { let value = #uint_typ_ident::from_ne_bytes(value.to_ne_bytes()); } + } else { + quote! {} + }; - match signal.byte_order { + let store_expr = match signal.byte_order { LittleEndian => { let (start, end) = le_start_end_bit(signal, msg)?; - writeln!( - w, - r"self.raw.view_bits_mut::()[{start}..{end}].store_le(value);", - )?; + let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + quote! { self.raw.view_bits_mut::()[#start_lit..#end_lit].store_le(value); } } BigEndian => { let (start, end) = be_start_end_bit(signal, msg)?; - writeln!( - w, - r"self.raw.view_bits_mut::()[{start}..{end}].store_be(value);", - )?; + let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + quote! { self.raw.view_bits_mut::()[#start_lit..#end_lit].store_be(value); } } - } + }; - writeln!(w, "Ok(())")?; - Ok(()) + Ok(quote! { + #value_conversion + #signed_conversion + #store_expr + Ok(()) + }) +} + +impl Config<'_> { + fn write_enum( + &self, + signal: &Signal, + msg: &Message, + variants: &[ValDescription], + ) -> Result { + let type_name = format_ident!("{}", enum_name(msg, signal)); + let signal_ty = ValType::from_signal(signal); + let signal_ty_ident = format_ident!("{}", signal_ty.to_string()); + + // Generate variant info to handle duplicates with tuple variants + let variant_infos = generate_variant_info(variants, signal_ty); + + let signal_name = &signal.name; + let doc = format!(" Defined values for {signal_name}"); + + let allow_lints = allow_lints(); + let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + let debug_derive = self.impl_debug.to_attr_tokens("derive(Debug)"); + let defmt_derive = self.impl_defmt.to_attr_tokens("derive(defmt::Format)"); + let serde_serialize = self.impl_serde.to_attr_tokens("derive(Serialize)"); + let serde_deserialize = self.impl_serde.to_attr_tokens("derive(Deserialize)"); + + // Generate enum variants + let enum_variants: Vec<_> = variant_infos + .iter() + .filter_map(|info| { + let variant = format_ident!("{}", &info.base_name); + match info.dup_type { + DuplicateType::Unique => Some(quote! { #variant }), + DuplicateType::FirstDuplicate => { + let value_type = format_ident!("{}", &info.value_type); + Some(quote! { #variant(#value_type) }) + } + DuplicateType::Duplicate => None, + } + }) + .collect(); + + // Generate From impl match arms + let from_match_arms: Vec<_> = variant_infos + .iter() + .filter_map(|info| { + let variant = format_ident!("{}", &info.base_name); + match info.dup_type { + DuplicateType::Unique => { + let literal_value = match signal_ty { + ValType::Bool => { + if info.value == 1 { + quote! { true } + } else { + quote! { false } + } + } + ValType::F32 => { + let val = syn::LitFloat::new( + &format!("{}_f32", info.value), + Span::call_site(), + ); + quote! { #val } + } + _ => { + let val = + syn::LitInt::new(&info.value.to_string(), Span::call_site()); + quote! { #val } + } + }; + Some(quote! { #type_name::#variant => #literal_value }) + } + DuplicateType::FirstDuplicate => Some(quote! { #type_name::#variant(v) => v }), + DuplicateType::Duplicate => None, + } + }) + .collect(); + + Ok(quote! { + #[doc = #doc] + #allow_lints + #allow_dead_code + #[derive(Clone, Copy, PartialEq)] + #debug_derive + #defmt_derive + #serde_serialize + #serde_deserialize + pub enum #type_name { + #(#enum_variants,)* + _Other(#signal_ty_ident), + } + + impl From<#type_name> for #signal_ty_ident { + fn from(val: #type_name) -> #signal_ty_ident { + match val { + #(#from_match_arms,)* + #type_name::_Other(x) => x, + } + } + } + }) + } } enum DuplicateType { @@ -977,340 +1204,377 @@ fn generate_variant_info(variants: &[ValDescription], signal_ty: ValType) -> Vec } impl Config<'_> { - fn render_embedded_can_frame(&self, w: &mut impl Write, msg: &Message) -> Result<()> { - self.impl_embedded_can_frame.fmt_cfg(w, |w| { - writeln!( - w, - "\ -impl embedded_can::Frame for {0} {{ - fn new(id: impl Into, data: &[u8]) -> Option {{ - if id.into() != Self::MESSAGE_ID {{ - None - }} else {{ - data.try_into().ok() - }} - }} - - fn new_remote(_id: impl Into, _dlc: usize) -> Option {{ - unimplemented!() - }} - - fn is_extended(&self) -> bool {{ - match self.id() {{ - Id::Standard(_) => false, - Id::Extended(_) => true, - }} - }} - - fn is_remote_frame(&self) -> bool {{ - false - }} - - fn id(&self) -> Id {{ - Self::MESSAGE_ID - }} - - fn dlc(&self) -> usize {{ - self.raw.len() - }} - - fn data(&self) -> &[u8] {{ - &self.raw - }} -}}", - msg.type_name(), - ) - }) - } -} + fn render_embedded_can_frame(&self, msg: &Message) -> Result> { + let msg_type = format_ident!("{}", msg.type_name()); -fn render_debug_impl(w: &mut impl Write, msg: &Message) -> Result<()> { - let typ = msg.type_name(); - writeln!(w, r"impl core::fmt::Debug for {typ} {{")?; - { - let mut w = PadAdapter::wrap(w); - writeln!( - w, - "fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{", - )?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!(w, r"if f.alternate() {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!(w, r#"f.debug_struct("{typ}")"#)?; - { - let mut w = PadAdapter::wrap(&mut w); - for signal in &msg.signals { - if signal.multiplexer_indicator == Plain { - writeln!(w, r#".field("{0}", &self.{0}())"#, signal.field_name())?; - } + let impl_tokens = quote! { + impl embedded_can::Frame for #msg_type { + fn new(id: impl Into, data: &[u8]) -> Option { + if id.into() != Self::MESSAGE_ID { + None + } else { + data.try_into().ok() } } - writeln!(w, r".finish()")?; - } - writeln!(w, r"}} else {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!(w, r#"f.debug_tuple("{typ}").field(&self.raw).finish()"#)?; - } - writeln!(w, "}}")?; - } - writeln!(w, "}}")?; - } - writeln!(w, "}}")?; - writeln!(w)?; - Ok(()) -} -fn render_defmt_impl(w: &mut impl Write, msg: &Message) -> Result<()> { - let typ = msg.type_name(); - writeln!(w, r"impl defmt::Format for {typ} {{")?; - { - let mut w = PadAdapter::wrap(w); - writeln!(w, "fn format(&self, f: defmt::Formatter) {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!(w, r"defmt::write!(f,")?; - { - let mut w = PadAdapter::wrap(&mut w); - write!(w, r#""{typ} {{{{"#)?; - { - for signal in &msg.signals { - if signal.multiplexer_indicator == Plain { - write!(w, r" {}={{:?}}", signal.name)?; - } - } + fn new_remote(_id: impl Into, _dlc: usize) -> Option { + unimplemented!() } - writeln!(w, r#" }}}}","#)?; - for signal in &msg.signals { - if signal.multiplexer_indicator == Plain { - writeln!(w, "self.{}(),", signal.field_name())?; + fn is_extended(&self) -> bool { + match self.id() { + Id::Standard(_) => false, + Id::Extended(_) => true, } } - writeln!(w, r");")?; + + fn is_remote_frame(&self) -> bool { + false + } + + fn id(&self) -> Id { + Self::MESSAGE_ID + } + + fn dlc(&self) -> usize { + self.raw.len() + } + + fn data(&self) -> &[u8] { + &self.raw + } + } + }; + + Ok(self.impl_embedded_can_frame.to_tokens_opt(impl_tokens)) + } +} + +fn render_debug_impl(msg: &Message) -> Result { + let msg_type = format_ident!("{}", msg.type_name()); + let typ_name = msg.type_name(); + + let debug_fields: Vec<_> = msg + .signals + .iter() + .filter(|signal| signal.multiplexer_indicator == Plain) + .map(|signal| { + let field_name = signal.field_name(); + let field_ident = format_ident!("{}", field_name); + quote! { .field(#field_name, &self.#field_ident()) } + }) + .collect(); + + Ok(quote! { + impl core::fmt::Debug for #msg_type { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if f.alternate() { + f.debug_struct(#typ_name) + #(#debug_fields)* + .finish() + } else { + f.debug_tuple(#typ_name).field(&self.raw).finish() + } } - writeln!(w, "}}")?; } + }) +} + +fn render_defmt_impl(msg: &Message) -> Result { + let msg_type = format_ident!("{}", msg.type_name()); + let typ_name = msg.type_name(); + + let plain_signals: Vec<_> = msg + .signals + .iter() + .filter(|signal| signal.multiplexer_indicator == Plain) + .collect(); + + // Build format string + let mut format_str = format!("{typ_name} {{{{"); + for signal in &plain_signals { + format_str.push_str(&format!(" {}={{:?}}", signal.name)); } - writeln!(w, "}}")?; - writeln!(w)?; - Ok(()) + format_str.push_str(" }}}}"); + + // Build field accessors + let field_accessors: Vec<_> = plain_signals + .iter() + .map(|signal| { + let field_ident = format_ident!("{}", signal.field_name()); + quote! { self.#field_ident() } + }) + .collect(); + + Ok(quote! { + impl defmt::Format for #msg_type { + fn format(&self, f: defmt::Formatter) { + defmt::write!( + f, + #format_str, + #(#field_accessors,)* + ); + } + } + }) } impl Config<'_> { fn render_multiplexor_enums( &self, - w: &mut impl Write, dbc: &Dbc, msg: &Message, multiplexor_signal: &Signal, - ) -> Result<()> { - ensure!( - multiplexor_signal.multiplexer_indicator == Multiplexor, - "signal {} is not the multiplexor", - multiplexor_signal.name, - ); - + ) -> Result { let mut multiplexed_signals = BTreeMap::new(); for signal in &msg.signals { if let MultiplexedSignal(switch_index) = signal.multiplexer_indicator { multiplexed_signals .entry(switch_index) .and_modify(|v: &mut Vec<&Signal>| v.push(signal)) - .or_insert_with(|| vec![&signal]); + .or_insert_with(|| vec![signal]); } } - writeln!(w, "/// Defined values for multiplexed signal {}", msg.name)?; - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; + let doc = format!(" Defined values for multiplexed signal {}", msg.name); + + let enum_name = format_ident!("{}", multiplex_enum_name(msg, multiplexor_signal)?); + + // Generate enum variants + let enum_variants: Vec<_> = multiplexed_signals + .keys() + .map(|switch_index| { + let wrapper_name = + format_ident!("{}", multiplexed_enum_variant_wrapper_name(*switch_index)); + let variant_name = format_ident!( + "{}", + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index).unwrap() + ); + quote! { #wrapper_name(#variant_name) } + }) + .collect(); - self.impl_debug.fmt_attr(w, "derive(Debug)")?; - self.impl_defmt.fmt_attr(w, "derive(defmt::Format)")?; - self.impl_serde.fmt_attr(w, "derive(Serialize)")?; - self.impl_serde.fmt_attr(w, "derive(Deserialize)")?; + // Generate structs for each multiplexed signal + let allow_lints_outer = allow_lints(); + let allow_dead_code_outer = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); - let enum_name = multiplex_enum_name(msg, multiplexor_signal)?; - writeln!(w, "pub enum {enum_name} {{")?; + let struct_defs: Result> = multiplexed_signals + .iter() + .map(|(switch_index, signals)| { + let struct_name = format_ident!( + "{}", + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)? + ); + let msg_size = msg.size as usize; + let msg_size_lit = syn::LitInt::new(&msg_size.to_string(), Span::call_site()); + + let signal_impls: Result> = signals + .iter() + .map(|signal| { + self.render_signal(signal, dbc, msg) + .with_context(|| format!("write signal impl `{}`", signal.name)) + }) + .collect(); + let signal_impls = signal_impls?; - { - let mut w = PadAdapter::wrap(w); - for switch_index in multiplexed_signals.keys() { - writeln!( - w, - "{multiplexed_wrapper_name}({multiplexed_name}),", - multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*switch_index), - multiplexed_name = - multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?, - )?; - } - } - writeln!(w, "}}")?; - writeln!(w)?; - - for (switch_index, multiplexed_signals) in &multiplexed_signals { - let struct_name = - multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?; - - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; - self.impl_debug.fmt_attr(w, "derive(Debug)")?; - self.impl_defmt.fmt_attr(w, "derive(defmt::Format)")?; - self.impl_serde.fmt_attr(w, "derive(Serialize)")?; - self.impl_serde.fmt_attr(w, "derive(Deserialize)")?; - writeln!(w, r"#[derive(Default)]")?; - writeln!(w, "pub struct {struct_name} {{ raw: [u8; {}] }}", msg.size)?; - writeln!(w)?; - - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; - writeln!(w, "impl {struct_name} {{")?; - - writeln!( - w, - "pub fn new() -> Self {{ Self {{ raw: [0u8; {}] }} }}", - msg.size - )?; - - for signal in multiplexed_signals { - self.render_signal(w, signal, dbc, msg)?; - } + let allow_lints_inner = allow_lints_outer.clone(); + let allow_dead_code_inner = allow_dead_code_outer.clone(); - writeln!(w, "}}")?; - writeln!(w)?; - } + let serde_serialize_inner = self.impl_serde.to_attr_tokens("derive(Serialize)"); + let serde_deserialize_inner = self.impl_serde.to_attr_tokens("derive(Deserialize)"); - Ok(()) - } + let allow_lints_inner2 = allow_lints_outer.clone(); + let allow_dead_code_inner2 = allow_dead_code_outer.clone(); - fn render_arbitrary(&self, w: &mut impl Write, msg: &Message) -> Result<()> { - writeln!(w, "{ALLOW_LINTS}")?; - self.write_allow_dead_code(w)?; - let typ = msg.type_name(); - writeln!(w, "impl<'a> Arbitrary<'a> for {typ} {{")?; - { - let filtered_signals: Vec<&Signal> = msg - .signals - .iter() - .filter(|v| matches!(v.multiplexer_indicator, Plain | Multiplexor)) - .collect(); - let mut w = PadAdapter::wrap(w); - writeln!( - w, - "fn arbitrary({}u: &mut Unstructured<'a>) -> Result {{", - if filtered_signals.is_empty() { "_" } else { "" }, - )?; - { - let mut w = PadAdapter::wrap(&mut w); - - for signal in &filtered_signals { - writeln!( - w, - "let {field_name} = {arbitrary_value};", - field_name = signal.field_name(), - arbitrary_value = signal_to_arbitrary(signal), - )?; - } + Ok(quote! { + #allow_lints_inner + #allow_dead_code_inner + #[derive(Default)] + #serde_serialize_inner + #serde_deserialize_inner + pub struct #struct_name { + raw: [u8; #msg_size_lit] + } - let args: Vec = filtered_signals - .iter() - .map(|signal| signal.field_name()) - .collect(); + #allow_lints_inner2 + #allow_dead_code_inner2 + impl #struct_name { + pub fn new() -> Self { + Self { raw: [0u8; #msg_size_lit] } + } - writeln!( - w, - "{typ}::new({args}).map_err(|_| arbitrary::Error::IncorrectFormat)", - typ = msg.type_name(), - args = args.join(","), - )?; + #(#signal_impls)* + } + }) + }) + .collect(); + let struct_defs = struct_defs?; + + Ok(quote! { + #[doc = #doc] + #allow_lints_outer + #allow_dead_code_outer + pub enum #enum_name { + #(#enum_variants,)* } - writeln!(w, "}}")?; - } - writeln!(w, "}}")?; - Ok(()) + #(#struct_defs)* + }) } - fn render_error(&self, w: &mut impl Write) -> Result<()> { - w.write_all( - r#" - #[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum CanError { - UnknownMessageId(embedded_can::Id), - /// Signal parameter is not within the range - /// defined in the dbc - ParameterOutOfRange { - /// dbc message id - message_id: embedded_can::Id, - }, - InvalidPayloadSize, - /// Multiplexor value not defined in the dbc - InvalidMultiplexor { - /// dbc message id - message_id: embedded_can::Id, - /// Multiplexor value not defined in the dbc - multiplexor: u16, - }, -} + fn render_arbitrary(&self, msg: &Message) -> Result { + let allow_lints = allow_lints(); + let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + let msg_type = format_ident!("{}", msg.type_name()); + + let filtered_signals: Vec<&Signal> = msg + .signals + .iter() + .filter(|v| matches!(v.multiplexer_indicator, Plain | Multiplexor)) + .collect(); + + let u_param = if filtered_signals.is_empty() { + quote! { _u } + } else { + quote! { u } + }; -impl core::fmt::Display for CanError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{self:?}") + // Generate signal bindings + let signal_bindings: Vec<_> = filtered_signals + .iter() + .map(|signal| { + let field_name = format_ident!("{}", signal.field_name()); + let value_expr = signal_to_arbitrary_tokens(signal); + quote! { let #field_name = #value_expr; } + }) + .collect(); + + // Generate function arguments + let args: Vec<_> = filtered_signals + .iter() + .map(|signal| format_ident!("{}", signal.field_name())) + .collect(); + + Ok(quote! { + #allow_lints + #allow_dead_code + impl arbitrary::Arbitrary<'_> for #msg_type { + fn arbitrary(#u_param: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + #(#signal_bindings)* + #msg_type::new(#(#args),*) + .map_err(|_| arbitrary::Error::IncorrectFormat) + } + } + }) } -} - "# - .as_bytes(), - )?; - self.impl_error.fmt_cfg(w, |w| { - writeln!(w, "impl core::error::Error for CanError {{}}") + #[allow(dead_code)] + fn render_error(&self) -> Result { + let error_impl = self.impl_error.to_tokens_opt(quote! { + impl core::error::Error for CanError {} + }); + + Ok(quote! { + #[allow(dead_code)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum CanError { + UnknownMessageId(embedded_can::Id), + /// Signal parameter is not within the range + /// defined in the dbc + ParameterOutOfRange { + /// dbc message id + message_id: embedded_can::Id, + }, + InvalidPayloadSize, + /// Multiplexor value not defined in the dbc + InvalidMultiplexor { + /// dbc message id + message_id: embedded_can::Id, + /// Multiplexor value not defined in the dbc + multiplexor: u16, + }, + } + + impl core::fmt::Display for CanError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{self:?}") + } + } + + #error_impl }) } - fn render_arbitrary_helpers(&self, w: &mut impl Write) -> Result<()> { - self.impl_arbitrary.fmt_cfg(&mut *w, |w| { - self.write_allow_dead_code(w)?; - writeln!(w, "trait UnstructuredFloatExt {{")?; - writeln!(w, " fn float_in_range(&mut self, range: core::ops::RangeInclusive) -> arbitrary::Result;")?; - writeln!(w, "}}")?; - writeln!(w)?; - Ok::<_, Error>(()) - })?; + fn render_arbitrary_helpers(&self) -> Result { + let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); - self.impl_arbitrary.fmt_cfg(w, |w| { - writeln!(w, "impl UnstructuredFloatExt for arbitrary::Unstructured<'_> {{")?; - writeln!(w, " fn float_in_range(&mut self, range: core::ops::RangeInclusive) -> arbitrary::Result {{")?; - writeln!(w, " let min = range.start();")?; - writeln!(w, " let max = range.end();")?; - writeln!(w, " let steps = u32::MAX;")?; - writeln!(w, " let factor = (max - min) / (steps as f32);")?; - writeln!(w, " let random_int: u32 = self.int_in_range(0..=steps)?;")?; - writeln!(w, " let random = min + factor * (random_int as f32);")?; - writeln!(w, " Ok(random)")?; - writeln!(w, " }}")?; - writeln!(w, "}}")?; - writeln!(w)?; - Ok::<_, Error>(()) + let trait_def = self.impl_arbitrary.to_tokens_opt(quote! { + #allow_dead_code + trait UnstructuredFloatExt { + fn arbitrary_f32(&mut self) -> arbitrary::Result; + } + }); + + let trait_impl = self.impl_arbitrary.to_tokens_opt(quote! { + impl UnstructuredFloatExt for arbitrary::Unstructured<'_> { + fn arbitrary_f32(&mut self) -> arbitrary::Result { + Ok(f32::from_bits(u32::arbitrary(self)?)) + } + } + }); + + Ok(quote! { + #trait_def + #trait_impl }) } + + // #[allow(dead_code)] + // fn render_arbitrary_helpers(&self) -> Result { + // let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + // + // let trait_def = self.impl_arbitrary.to_tokens_opt(quote! { + // #allow_dead_code + // trait UnstructuredFloatExt { + // fn float_in_range(&mut self, range: core::ops::RangeInclusive) -> arbitrary::Result; + // } + // }); + // + // let trait_impl = self.impl_arbitrary.to_tokens_opt(quote! { + // impl UnstructuredFloatExt for arbitrary::Unstructured<'_> { + // fn float_in_range(&mut self, range: core::ops::RangeInclusive) -> arbitrary::Result { + // let min = range.start(); + // let max = range.end(); + // let steps = u32::MAX; + // let factor = (max - min) / (steps as f32); + // let random_int: u32 = self.int_in_range(0..=steps)?; + // let random = min + factor * (random_int as f32); + // Ok(random) + // } + // } + // }); + // + // Ok(quote! { + // #trait_def + // #trait_impl + // }) + // } + + } -fn signal_to_arbitrary(signal: &Signal) -> String { +fn signal_to_arbitrary_tokens(signal: &Signal) -> TokenStream { let typ = ValType::from_signal(signal); match typ { - ValType::Bool => "u.int_in_range(0..=1)? == 1".to_string(), + ValType::Bool => quote! { u.int_in_range(0..=1)? == 1 }, ValType::F32 => { - let min = signal.min; - let max = signal.max; - format!("u.float_in_range({min}_f32..={max}_f32)?") + quote! { u.arbitrary_f32()? } } _ => { - let min = signal.min; - let max = signal.max; - format!("u.int_in_range({min}..={max})?") + let min = signal.min as i64; + let max = signal.max as i64; + let typ_ident = format_ident!("{}", typ.to_string()); + quote! { u.int_in_range(#min..=#max)? as #typ_ident } } } } @@ -1327,13 +1591,15 @@ fn message_ignored(message: &Message) -> bool { impl Config<'_> { /// Generate Rust structs matching DBC input description and return as String pub fn generate(self) -> Result { - let mut out = Vec::new(); - self.codegen(&mut out)?; - // Parse and re-format the generated code to allow for future - // syn/quote codegen migration. Note that this is inefficient at the moment, - // but this shouldn't be significantly noticeable. - let out = std::str::from_utf8(&out).context("Failed to convert output to str")?; - let file = syn::parse_file(out).context("Failed to parse generated Rust code")?; + let tokens = self.codegen()?; + // Debug: write tokens to stderr for debugging + if std::env::var("DEBUG_TOKENS").is_ok() { + eprintln!("=== Generated TokenStream ==="); + eprintln!("{}", tokens); + eprintln!("=== End TokenStream ==="); + } + let file = + syn::parse2(tokens).context("Failed to parse generated TokenStream as Rust code")?; Ok(prettyplease::unparse(&file)) } @@ -1353,11 +1619,4 @@ impl Config<'_> { self.write(file) } - - fn write_allow_dead_code(&self, w: &mut impl Write) -> Result<()> { - if self.allow_dead_code { - writeln!(w, "{ALLOW_DEADCODE}")?; - } - Ok(()) - } } diff --git a/src/pad.rs b/src/pad.rs deleted file mode 100644 index 81f910d..0000000 --- a/src/pad.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::io::{Result as IoResult, Write}; - -// adopted from libcore's `fmt/builders.rs` - -/// Indents all lines written to this by four spaces -pub struct PadAdapter<'a> { - buf: &'a mut (dyn Write + 'a), - on_newline: bool, -} - -impl<'a> PadAdapter<'a> { - pub(crate) fn wrap<'fmt: 'a>(buf: &'fmt mut (dyn Write + 'a)) -> Self { - PadAdapter { - buf, - on_newline: true, - } - } -} - -impl Write for PadAdapter<'_> { - fn write(&mut self, mut s: &[u8]) -> IoResult { - let len = s.len(); - while !s.is_empty() { - if self.on_newline { - self.buf.write_all(b" ")?; - } - - let split = if let Some(pos) = s.iter().position(|&v| v == b'\n') { - self.on_newline = true; - pos.checked_add(1).unwrap() - } else { - self.on_newline = false; - s.len() - }; - self.buf.write_all(&s[..split])?; - s = &s[split..]; - } - - Ok(len) - } - - fn flush(&mut self) -> IoResult<()> { - Ok(()) - } -} diff --git a/tests/snapshots.rs b/tests/snapshots.rs index ea73e09..7f63e45 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -596,7 +596,7 @@ fn single_file_manual_test() { } } "#, - ) + ); // let out_path = PathBuf::from("./target/manual/manual_test_output.rs"); // fs::create_dir_all(out_path.parent().unwrap()).unwrap(); From d45e6f54ff69ca2aad18c61c4b7c19e20819c002 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Mon, 26 Jan 2026 20:04:10 -0500 Subject: [PATCH 03/11] wip --- src/feature_config.rs | 46 ++--- src/lib.rs | 418 +++++++++++++++++------------------------- 2 files changed, 184 insertions(+), 280 deletions(-) diff --git a/src/feature_config.rs b/src/feature_config.rs index e4a9382..c0e851f 100644 --- a/src/feature_config.rs +++ b/src/feature_config.rs @@ -1,4 +1,5 @@ -use std::fmt::Display; +use proc_macro2::TokenStream; +use quote::quote; /// Configuration for including features in the code generator. /// @@ -18,48 +19,23 @@ pub enum FeatureConfig<'a> { impl FeatureConfig<'_> { /// Generate an attribute token stream (like `#[derive(Debug)]`) - pub(crate) fn to_attr_tokens(&self, attr: impl Display) -> Option { - use quote::quote; - + pub(crate) fn fmt_attr(&self, tokens: &TokenStream) -> TokenStream { match self { - FeatureConfig::Always => { - let attr_str = attr.to_string(); - let tokens: proc_macro2::TokenStream = attr_str.parse().ok()?; - Some(quote! { #[#tokens] }) - } - FeatureConfig::Gated(gate) => { - let attr_str = attr.to_string(); - let tokens: proc_macro2::TokenStream = attr_str.parse().ok()?; - Some(quote! { #[cfg_attr(feature = #gate, #tokens)] }) - } - FeatureConfig::Never => None, + FeatureConfig::Always => quote! { #[#tokens] }, + FeatureConfig::Gated(gate) => quote! { #[cfg_attr(feature = #gate, #tokens)] }, + FeatureConfig::Never => quote! {}, } } /// Generate a token stream optionally wrapped in a cfg attribute - pub(crate) fn to_tokens_opt( - &self, - tokens: proc_macro2::TokenStream, - ) -> Option { - use quote::quote; - + pub(crate) fn fmt_cfg(&self, tokens: TokenStream) -> TokenStream { match self { - FeatureConfig::Never => None, - FeatureConfig::Gated(gate) => Some(quote! { + FeatureConfig::Gated(gate) => quote! { #[cfg(feature = #gate)] #tokens - }), - FeatureConfig::Always => Some(tokens), - } - } - - /// Generate allow(dead_code) attribute if needed - pub(crate) fn allow_dead_code_tokens(allow: bool) -> Option { - if allow { - use quote::quote; - Some(quote! { #[allow(dead_code)] }) - } else { - None + }, + FeatureConfig::Always => tokens, + FeatureConfig::Never => quote! {}, } } } diff --git a/src/lib.rs b/src/lib.rs index 11587e6..b41f2fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,8 @@ mod feature_config; mod keywords; mod signal_type; mod utils; - use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::fmt::Write as _; use std::fs::OpenOptions; use std::io::Write; use std::path::Path; @@ -34,20 +34,6 @@ use crate::utils::{ multiplexed_enum_variant_wrapper_name, MessageExt as _, SignalExt as _, }; -fn allow_lints() -> TokenStream { - quote! { - #[allow( - clippy::absurd_extreme_comparisons, - clippy::excessive_precision, - clippy::manual_range_contains, - clippy::unnecessary_cast, - clippy::useless_conversion, - unused_comparisons, - unused_variables, - )] - } -} - /// Code generator configuration. See module-level docs for an example. #[derive(TypedBuilder)] #[non_exhaustive] @@ -101,11 +87,10 @@ impl Config<'_> { /// Write Rust structs matching DBC input description to `out` buffer fn codegen(&self) -> Result { let dbc = Dbc::try_from(self.dbc_content).map_err(|e| { - let msg = "Could not parse dbc file"; if self.debug_prints { - anyhow!("{msg}: {e:#?}") + anyhow!("Could not parse dbc file: {e:#?}") } else { - anyhow!("{msg}") + anyhow!("Could not parse dbc file") } })?; if self.debug_prints { @@ -114,22 +99,7 @@ impl Config<'_> { let dbc_name = &self.dbc_name; let dbc_version = &dbc.version.0; - - let arbitrary_use = self.impl_arbitrary.to_tokens_opt(quote! { - use arbitrary::Arbitrary; - }); - - let serde_use = self.impl_serde.to_tokens_opt(quote! { - use serde::{Serialize, Deserialize}; - }); - - let dbc_content = self - .render_dbc(&dbc) - .context("could not generate Rust code")?; - let error_content = self.render_error()?; - let arbitrary_helpers = self.render_arbitrary_helpers()?; - - Ok(quote! { + let header = quote! { /// The name of the DBC file this code was generated from #[allow(dead_code)] pub const DBC_FILE_NAME: &str = #dbc_name; @@ -137,16 +107,32 @@ impl Config<'_> { #[allow(dead_code)] pub const DBC_FILE_VERSION: &str = #dbc_version; + }; + + let mut use_statements = quote! { #[allow(unused_imports)] use core::ops::BitOr; #[allow(unused_imports)] use bitvec::prelude::*; #[allow(unused_imports)] use embedded_can::{Id, StandardId, ExtendedId}; + }; + use_statements.extend(self.impl_arbitrary.fmt_cfg(quote! { + use arbitrary::Arbitrary; + })); + use_statements.extend(self.impl_serde.fmt_cfg(quote! { + use serde::{Serialize, Deserialize}; + })); - #arbitrary_use - #serde_use + let dbc_content = self + .render_dbc(&dbc) + .context("could not generate Rust code")?; + let error_content = self.render_error(); + let arbitrary_helpers = self.render_arbitrary_helpers(); + Ok(quote! { + #header + #use_statements #dbc_content /// This is just to make testing easier @@ -159,7 +145,7 @@ impl Config<'_> { } fn render_dbc(&self, dbc: &Dbc) -> Result { - let root_enum = self.render_root_enum(dbc)?; + let root_enum = self.render_root_enum(dbc); let messages = get_relevant_messages(dbc) .map(|msg| { @@ -174,19 +160,19 @@ impl Config<'_> { }) } - fn render_root_enum(&self, dbc: &Dbc) -> Result { - let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); - let debug_derive = self.impl_debug.to_attr_tokens("derive(Debug)"); - let defmt_derive = self.impl_defmt.to_attr_tokens("derive(defmt::Format)"); + fn render_root_enum(&self, dbc: &Dbc) -> TokenStream { + let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); + let debug_derive = self.impl_debug.fmt_attr("e! {derive(Debug)}); + let defmt_derive = self.impl_defmt.fmt_attr("e! {derive(defmt::Format)}); let serde_derives = self .impl_serde - .to_attr_tokens("derive(Serialize, Deserialize)"); + .fmt_attr("e! {derive(Serialize, Deserialize)}); let allow_lints = allow_lints(); let variants: Vec<_> = get_relevant_messages(dbc) .map(|msg| { let msg_type = format_ident!("{}", msg.type_name()); - let doc_str = format!(" {}", &msg.name); // Use message name, not type_name + let doc_str = format!(" {}", msg.name); // Use message name, not type_name quote! { #[doc = #doc_str] #msg_type(#msg_type) @@ -217,7 +203,7 @@ impl Config<'_> { } }; - Ok(quote! { + quote! { /// All messages #allow_lints #allow_dead_code @@ -238,43 +224,7 @@ impl Config<'_> { #from_can_body } } - }) - } - - fn render_error(&self) -> Result { - let error_impl = self.impl_error.to_tokens_opt(quote! { - impl core::error::Error for CanError {} - }); - - Ok(quote! { - #[allow(dead_code)] - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub enum CanError { - UnknownMessageId(embedded_can::Id), - /// Signal parameter is not within the range - /// defined in the dbc - ParameterOutOfRange { - /// dbc message id - message_id: embedded_can::Id, - }, - InvalidPayloadSize, - /// Multiplexor value not defined in the dbc - InvalidMultiplexor { - /// dbc message id - message_id: embedded_can::Id, - /// Multiplexor value not defined in the dbc - multiplexor: u16, - }, - } - - impl core::fmt::Display for CanError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{self:?}") - } - } - - #error_impl - }) + } } fn render_message(&self, msg: &Message, dbc: &Dbc) -> Result { @@ -284,7 +234,7 @@ impl Config<'_> { let msg_size_lit = syn::LitInt::new(&msg_size.to_string(), Span::call_site()); // Build message documentation as individual lines (with leading space for prettyplease) - let msg_name_doc = format!(" {}", msg_name); + let msg_name_doc = format!(" {msg_name}"); let id_text = match msg.id { MessageId::Standard(id) => format!(" - Standard ID: {id} ({id:#x})"), MessageId::Extended(id) => format!(" - Extended ID: {id} ({id:#x})"), @@ -306,17 +256,17 @@ impl Config<'_> { if let Some(comment) = dbc.message_comment(msg.id) { struct_doc_lines.push(quote! { #[doc = ""] }); for line in comment.trim().lines() { - let line_with_space = format!(" {}", line); + let line_with_space = format!(" {line}"); struct_doc_lines.push(quote! { #[doc = #line_with_space] }); } } // Struct attributes - let serde_serialize = self.impl_serde.to_attr_tokens("derive(Serialize)"); - let serde_deserialize = self.impl_serde.to_attr_tokens("derive(Deserialize)"); + let serde_serialize = self.impl_serde.fmt_attr("e! {derive(Serialize)}); + let serde_deserialize = self.impl_serde.fmt_attr("e! {derive(Deserialize)}); let serde_with = self .impl_serde - .to_attr_tokens("serde(with = \"serde_bytes\")"); + .fmt_attr("e! {serde(with = "serde_bytes")}); // Message ID constant let message_id = match msg.id { @@ -336,10 +286,12 @@ impl Config<'_> { .iter() .filter_map(|signal| { let typ = ValType::from_signal(signal); - if typ != ValType::Bool { + if typ == ValType::Bool { + None + } else { let min_name = format_ident!("{}_MIN", signal.field_name().to_uppercase()); let max_name = format_ident!("{}_MAX", signal.field_name().to_uppercase()); - let typ_ident = format_ident!("{}", typ.to_string()); + let typ_ident = format_ident!("{typ}"); let min_lit = generate_value_literal(signal.min, typ); let max_lit = generate_value_literal(signal.max, typ); @@ -348,8 +300,6 @@ impl Config<'_> { pub const #min_name: #typ_ident = #min_lit; pub const #max_name: #typ_ident = #max_lit; }) - } else { - None } }) .collect(); @@ -362,7 +312,7 @@ impl Config<'_> { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { let field_name = format_ident!("{}", signal.field_name()); let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{}", typ.to_string()); + let typ_ident = format_ident!("{typ}"); Some(quote! { #field_name: #typ_ident }) } else { None @@ -409,17 +359,15 @@ impl Config<'_> { let signal_impls = signal_impls?; // Render embedded can frame impl - let embedded_can_impl = self.render_embedded_can_frame(msg)?; + let embedded_can_impl = self.render_embedded_can_frame(msg); // Render debug/defmt/arbitrary impls - let debug_impl = self.impl_debug.to_tokens_opt(render_debug_impl(msg)?); - let defmt_impl = self.impl_defmt.to_tokens_opt(render_defmt_impl(msg)?); - let arbitrary_impl = self - .impl_arbitrary - .to_tokens_opt(self.render_arbitrary(msg)?); + let debug_impl = self.impl_debug.fmt_cfg(render_debug_impl(msg)); + let defmt_impl = self.impl_defmt.fmt_cfg(render_defmt_impl(msg)); + let arbitrary_impl = self.impl_arbitrary.fmt_cfg(self.render_arbitrary(msg)); // Render enums for this message - let enums_for_this_message: Result> = dbc + let enums_for_this_message: Vec<_> = dbc .value_descriptions .iter() .filter_map(|x| { @@ -439,7 +387,6 @@ impl Config<'_> { } }) .collect(); - let enums_for_this_message = enums_for_this_message?; // Render multiplexor enums let multiplexor_enums = msg @@ -450,7 +397,7 @@ impl Config<'_> { .transpose()?; let allow_lints = allow_lints(); - let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); // Create constructor doc string (with leading space) let new_fn_doc = format!(" Construct new {msg_name} from values"); @@ -509,36 +456,7 @@ impl Config<'_> { #multiplexor_enums }) } -} -/// Generate a literal token for a signal min/max value. -/// -/// For F32 types, always generates a float literal. -/// For other types, generates an integer literal if the value is an integer within i64 range, -/// otherwise generates a float literal with the integer type suffix. -fn generate_value_literal(value: f64, typ: ValType) -> TokenStream { - match typ { - ValType::F32 => { - let lit = syn::LitFloat::new(&format!("{value}_f32"), Span::call_site()); - quote! { #lit } - } - _ => { - let typ_str = typ.to_string().to_lowercase(); - // Check if value is an integer and fits in i64 range - if is_integer(value) && value >= i64::MIN as f64 && value <= i64::MAX as f64 { - let val = value as i64; - let lit = syn::LitInt::new(&format!("{val}_{typ_str}"), Span::call_site()); - quote! { #lit } - } else { - // Use float literal with integer type suffix for fractional/overflow values - let lit = syn::LitFloat::new(&format!("{value}_{typ_str}"), Span::call_site()); - quote! { #lit } - } - } - } -} - -impl Config<'_> { fn render_signal(&self, signal: &Signal, dbc: &Dbc, msg: &Message) -> Result { let signal_name = &signal.name; let fn_name = format_ident!("{}", signal.field_name()); @@ -557,23 +475,24 @@ impl Config<'_> { let value_type_doc = format!("{:?}", signal.value_type); // Build signal getter doc as doc comment and parse into tokens - let mut signal_doc_text = format!("/// {signal_name}"); + let mut signal_doc_text = format!("/// {signal_name}\n"); if let Some(comment) = dbc.signal_comment(msg.id, &signal.name) { - signal_doc_text.push_str("\n///"); + signal_doc_text.push_str("///\n"); for line in comment.trim().lines() { - signal_doc_text.push_str(&format!("\n/// {line}")); + let _ = writeln!(signal_doc_text, "/// {line}"); } } - signal_doc_text.push_str(&format!( - "\n/// + let _ = writeln!( + signal_doc_text, + "/// /// - Min: {min_doc} /// - Max: {max_doc} /// - Unit: {unit_doc} /// - Receivers: {receivers_doc}" - )); + ); let signal_doc_tokens: TokenStream = signal_doc_text .parse() - .map_err(|e| anyhow::anyhow!("Failed to parse signal doc: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to parse signal doc: {e}"))?; // Build raw getter doc as doc comment and parse into tokens let raw_doc_text = format!( @@ -586,10 +505,10 @@ impl Config<'_> { /// - Byte order: {byte_order_doc} /// - Value type: {value_type_doc}" ); - let raw_doc_tokens = to_tokens(raw_doc_text)?; + let raw_doc_tokens = to_tokens(&raw_doc_text)?; let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{}", typ.to_string()); + let typ_ident = format_ident!("{typ}"); // Generate getter function let getter = if let Some(variants) = dbc.value_descriptions_for_signal(msg.id, &signal.name) @@ -606,13 +525,13 @@ impl Config<'_> { ValType::from_signal_uint(signal) }; - let read_expr = read_fn_with_type_tokens(signal, msg, load_type)?; + let read_expr = read_fn_with_type(signal, msg, load_type)?; let match_arms: Vec<_> = variant_infos .iter() .map(|info| { let literal = syn::LitInt::new(&info.value.to_string(), Span::call_site()); - let variant = format_ident!("{}", &info.base_name); + let variant = format_ident!("{}", info.base_name); match info.dup_type { DuplicateType::Unique => { quote! { #literal => #type_name::#variant } @@ -646,7 +565,7 @@ impl Config<'_> { // Generate raw getter function let signal_from_payload_body = - signal_from_payload_tokens(signal, msg).context("signal from payload")?; + signal_from_payload(signal, msg).context("signal from payload")?; let setter = self.render_set_signal(signal, msg)?; @@ -665,10 +584,10 @@ impl Config<'_> { } } -fn to_tokens(raw_doc_text: String) -> Result { +fn to_tokens(raw_doc_text: &str) -> Result { raw_doc_text .parse() - .map_err(|e| anyhow::anyhow!("Failed to parse raw doc: {}", e)) + .map_err(|e| anyhow::anyhow!("Failed to parse raw doc: {e}")) } impl Config<'_> { @@ -683,19 +602,15 @@ impl Config<'_> { quote! { pub } }; - // Doc comment for setter - let setter_doc = if signal.multiplexer_indicator == Multiplexor { - format!(" Set value of {}", signal.name) - } else { - format!(" Set value of {}", signal.name) - }; - + let setter_doc = format!(" Set value of {}", signal.name); let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{}", typ.to_string()); + let typ_ident = format_ident!("{typ}"); let msg_type = format_ident!("{}", msg.type_name()); // Range check logic - let range_check = if signal.size != 1 { + let range_check = if signal.size == 1 { + quote! {} + } else { let min = signal.min; let max = signal.max; @@ -708,12 +623,10 @@ impl Config<'_> { } }; - self.check_ranges.to_tokens_opt(check_code) - } else { - None + self.check_ranges.fmt_cfg(check_code) }; - let signal_to_payload_body = signal_to_payload_tokens(signal, msg)?; + let signal_to_payload_body = signal_to_payload(signal, msg)?; Ok(quote! { #[doc = #setter_doc] @@ -762,7 +675,7 @@ impl Config<'_> { let field = format_ident!("{}", signal.field_name()); let field_raw = format_ident!("{}_raw", signal.field_name()); let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{}", typ.to_string()); + let typ_ident = format_ident!("{typ}"); let enum_type = format_ident!("{}", multiplex_enum_name(msg, signal)?); let signal_name = &signal.name; @@ -786,9 +699,9 @@ impl Config<'_> { ); let raw_doc_tokens: TokenStream = raw_doc_text .parse() - .map_err(|e| anyhow::anyhow!("Failed to parse multiplexor raw doc: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to parse multiplexor raw doc: {e}"))?; - let signal_from_payload_body = signal_from_payload_tokens(signal, msg)?; + let signal_from_payload_body = signal_from_payload(signal, msg)?; let multiplexer_indexes: BTreeSet = msg .signals @@ -890,8 +803,8 @@ fn le_start_end_bit(signal: &Signal, msg: &Message) -> Result<(u64, u64)> { Ok((start_bit, end_bit)) } -fn read_fn_with_type_tokens(signal: &Signal, msg: &Message, typ: ValType) -> Result { - let typ_ident = format_ident!("{}", typ.to_string()); +fn read_fn_with_type(signal: &Signal, msg: &Message, typ: ValType) -> Result { + let typ_ident = format_ident!("{typ}"); Ok(match signal.byte_order { LittleEndian => { let (start, end) = le_start_end_bit(signal, msg)?; @@ -908,11 +821,11 @@ fn read_fn_with_type_tokens(signal: &Signal, msg: &Message, typ: ValType) -> Res }) } -fn signal_from_payload_tokens(signal: &Signal, msg: &Message) -> Result { - let read_expr = read_fn_with_type_tokens(signal, msg, ValType::from_signal_int(signal))?; +fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { + let read_expr = read_fn_with_type(signal, msg, ValType::from_signal_int(signal))?; let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{}", typ.to_string()); + let typ_ident = format_ident!("{typ}"); Ok(match typ { ValType::Bool => { @@ -967,7 +880,7 @@ fn signal_from_payload_tokens(signal: &Signal, msg: &Message) -> Result Result { +fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let typ = ValType::from_signal(signal); let msg_type = format_ident!("{}", msg.type_name()); @@ -981,7 +894,7 @@ fn signal_to_payload_tokens(signal: &Signal, msg: &Message) -> Result Result= 0.0 { let offset = signal.offset; @@ -1018,7 +931,7 @@ fn signal_to_payload_tokens(signal: &Signal, msg: &Message) -> Result { signal: &Signal, msg: &Message, variants: &[ValDescription], - ) -> Result { + ) -> TokenStream { let type_name = format_ident!("{}", enum_name(msg, signal)); let signal_ty = ValType::from_signal(signal); - let signal_ty_ident = format_ident!("{}", signal_ty.to_string()); + let signal_ty_ident = format_ident!("{signal_ty}"); // Generate variant info to handle duplicates with tuple variants let variant_infos = generate_variant_info(variants, signal_ty); @@ -1065,21 +978,21 @@ impl Config<'_> { let doc = format!(" Defined values for {signal_name}"); let allow_lints = allow_lints(); - let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); - let debug_derive = self.impl_debug.to_attr_tokens("derive(Debug)"); - let defmt_derive = self.impl_defmt.to_attr_tokens("derive(defmt::Format)"); - let serde_serialize = self.impl_serde.to_attr_tokens("derive(Serialize)"); - let serde_deserialize = self.impl_serde.to_attr_tokens("derive(Deserialize)"); + let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); + let debug_derive = self.impl_debug.fmt_attr("e! {derive(Debug)}); + let defmt_derive = self.impl_defmt.fmt_attr("e! {derive(defmt::Format)}); + let serde_serialize = self.impl_serde.fmt_attr("e! {derive(Serialize)}); + let serde_deserialize = self.impl_serde.fmt_attr("e! {derive(Deserialize)}); // Generate enum variants let enum_variants: Vec<_> = variant_infos .iter() .filter_map(|info| { - let variant = format_ident!("{}", &info.base_name); + let variant = format_ident!("{}", info.base_name); match info.dup_type { DuplicateType::Unique => Some(quote! { #variant }), DuplicateType::FirstDuplicate => { - let value_type = format_ident!("{}", &info.value_type); + let value_type = format_ident!("{}", info.value_type); Some(quote! { #variant(#value_type) }) } DuplicateType::Duplicate => None, @@ -1091,7 +1004,7 @@ impl Config<'_> { let from_match_arms: Vec<_> = variant_infos .iter() .filter_map(|info| { - let variant = format_ident!("{}", &info.base_name); + let variant = format_ident!("{}", info.base_name); match info.dup_type { DuplicateType::Unique => { let literal_value = match signal_ty { @@ -1123,7 +1036,7 @@ impl Config<'_> { }) .collect(); - Ok(quote! { + quote! { #[doc = #doc] #allow_lints #allow_dead_code @@ -1145,7 +1058,7 @@ impl Config<'_> { } } } - }) + } } } @@ -1204,7 +1117,7 @@ fn generate_variant_info(variants: &[ValDescription], signal_ty: ValType) -> Vec } impl Config<'_> { - fn render_embedded_can_frame(&self, msg: &Message) -> Result> { + fn render_embedded_can_frame(&self, msg: &Message) -> TokenStream { let msg_type = format_ident!("{}", msg.type_name()); let impl_tokens = quote! { @@ -1246,11 +1159,11 @@ impl Config<'_> { } }; - Ok(self.impl_embedded_can_frame.to_tokens_opt(impl_tokens)) + self.impl_embedded_can_frame.fmt_cfg(impl_tokens) } } -fn render_debug_impl(msg: &Message) -> Result { +fn render_debug_impl(msg: &Message) -> TokenStream { let msg_type = format_ident!("{}", msg.type_name()); let typ_name = msg.type_name(); @@ -1260,12 +1173,12 @@ fn render_debug_impl(msg: &Message) -> Result { .filter(|signal| signal.multiplexer_indicator == Plain) .map(|signal| { let field_name = signal.field_name(); - let field_ident = format_ident!("{}", field_name); + let field_ident = format_ident!("{field_name}"); quote! { .field(#field_name, &self.#field_ident()) } }) .collect(); - Ok(quote! { + quote! { impl core::fmt::Debug for #msg_type { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if f.alternate() { @@ -1277,10 +1190,10 @@ fn render_debug_impl(msg: &Message) -> Result { } } } - }) + } } -fn render_defmt_impl(msg: &Message) -> Result { +fn render_defmt_impl(msg: &Message) -> TokenStream { let msg_type = format_ident!("{}", msg.type_name()); let typ_name = msg.type_name(); @@ -1293,7 +1206,7 @@ fn render_defmt_impl(msg: &Message) -> Result { // Build format string let mut format_str = format!("{typ_name} {{{{"); for signal in &plain_signals { - format_str.push_str(&format!(" {}={{:?}}", signal.name)); + let _ = write!(format_str, " {}={{:?}}", signal.name); } format_str.push_str(" }}}}"); @@ -1306,7 +1219,7 @@ fn render_defmt_impl(msg: &Message) -> Result { }) .collect(); - Ok(quote! { + quote! { impl defmt::Format for #msg_type { fn format(&self, f: defmt::Formatter) { defmt::write!( @@ -1316,7 +1229,7 @@ fn render_defmt_impl(msg: &Message) -> Result { ); } } - }) + } } impl Config<'_> { @@ -1356,7 +1269,7 @@ impl Config<'_> { // Generate structs for each multiplexed signal let allow_lints_outer = allow_lints(); - let allow_dead_code_outer = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code_outer = allow_dead_code_tokens(self.allow_dead_code); let struct_defs: Result> = multiplexed_signals .iter() @@ -1380,8 +1293,9 @@ impl Config<'_> { let allow_lints_inner = allow_lints_outer.clone(); let allow_dead_code_inner = allow_dead_code_outer.clone(); - let serde_serialize_inner = self.impl_serde.to_attr_tokens("derive(Serialize)"); - let serde_deserialize_inner = self.impl_serde.to_attr_tokens("derive(Deserialize)"); + let serde_serialize_inner = self.impl_serde.fmt_attr("e! {derive(Serialize)}); + let serde_deserialize_inner = + self.impl_serde.fmt_attr("e! {derive(Deserialize)}); let allow_lints_inner2 = allow_lints_outer.clone(); let allow_dead_code_inner2 = allow_dead_code_outer.clone(); @@ -1422,9 +1336,9 @@ impl Config<'_> { }) } - fn render_arbitrary(&self, msg: &Message) -> Result { + fn render_arbitrary(&self, msg: &Message) -> TokenStream { let allow_lints = allow_lints(); - let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); let msg_type = format_ident!("{}", msg.type_name()); let filtered_signals: Vec<&Signal> = msg @@ -1444,7 +1358,7 @@ impl Config<'_> { .iter() .map(|signal| { let field_name = format_ident!("{}", signal.field_name()); - let value_expr = signal_to_arbitrary_tokens(signal); + let value_expr = signal_to_arbitrary(signal); quote! { let #field_name = #value_expr; } }) .collect(); @@ -1455,7 +1369,7 @@ impl Config<'_> { .map(|signal| format_ident!("{}", signal.field_name())) .collect(); - Ok(quote! { + quote! { #allow_lints #allow_dead_code impl arbitrary::Arbitrary<'_> for #msg_type { @@ -1465,16 +1379,15 @@ impl Config<'_> { .map_err(|_| arbitrary::Error::IncorrectFormat) } } - }) + } } - #[allow(dead_code)] - fn render_error(&self) -> Result { - let error_impl = self.impl_error.to_tokens_opt(quote! { + fn render_error(&self) -> TokenStream { + let error_impl = self.impl_error.fmt_cfg(quote! { impl core::error::Error for CanError {} }); - Ok(quote! { + quote! { #[allow(dead_code)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CanError { @@ -1502,20 +1415,20 @@ impl Config<'_> { } #error_impl - }) + } } - fn render_arbitrary_helpers(&self) -> Result { - let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); + fn render_arbitrary_helpers(&self) -> TokenStream { + let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let trait_def = self.impl_arbitrary.to_tokens_opt(quote! { + let trait_def = self.impl_arbitrary.fmt_cfg(quote! { #allow_dead_code trait UnstructuredFloatExt { fn arbitrary_f32(&mut self) -> arbitrary::Result; } }); - let trait_impl = self.impl_arbitrary.to_tokens_opt(quote! { + let trait_impl = self.impl_arbitrary.fmt_cfg(quote! { impl UnstructuredFloatExt for arbitrary::Unstructured<'_> { fn arbitrary_f32(&mut self) -> arbitrary::Result { Ok(f32::from_bits(u32::arbitrary(self)?)) @@ -1523,47 +1436,14 @@ impl Config<'_> { } }); - Ok(quote! { + quote! { #trait_def #trait_impl - }) + } } - - // #[allow(dead_code)] - // fn render_arbitrary_helpers(&self) -> Result { - // let allow_dead_code = FeatureConfig::allow_dead_code_tokens(self.allow_dead_code); - // - // let trait_def = self.impl_arbitrary.to_tokens_opt(quote! { - // #allow_dead_code - // trait UnstructuredFloatExt { - // fn float_in_range(&mut self, range: core::ops::RangeInclusive) -> arbitrary::Result; - // } - // }); - // - // let trait_impl = self.impl_arbitrary.to_tokens_opt(quote! { - // impl UnstructuredFloatExt for arbitrary::Unstructured<'_> { - // fn float_in_range(&mut self, range: core::ops::RangeInclusive) -> arbitrary::Result { - // let min = range.start(); - // let max = range.end(); - // let steps = u32::MAX; - // let factor = (max - min) / (steps as f32); - // let random_int: u32 = self.int_in_range(0..=steps)?; - // let random = min + factor * (random_int as f32); - // Ok(random) - // } - // } - // }); - // - // Ok(quote! { - // #trait_def - // #trait_impl - // }) - // } - - } -fn signal_to_arbitrary_tokens(signal: &Signal) -> TokenStream { +fn signal_to_arbitrary(signal: &Signal) -> TokenStream { let typ = ValType::from_signal(signal); match typ { ValType::Bool => quote! { u.int_in_range(0..=1)? == 1 }, @@ -1573,7 +1453,7 @@ fn signal_to_arbitrary_tokens(signal: &Signal) -> TokenStream { _ => { let min = signal.min as i64; let max = signal.max as i64; - let typ_ident = format_ident!("{}", typ.to_string()); + let typ_ident = format_ident!("{typ}"); quote! { u.int_in_range(#min..=#max)? as #typ_ident } } } @@ -1595,7 +1475,7 @@ impl Config<'_> { // Debug: write tokens to stderr for debugging if std::env::var("DEBUG_TOKENS").is_ok() { eprintln!("=== Generated TokenStream ==="); - eprintln!("{}", tokens); + eprintln!("{tokens}"); eprintln!("=== End TokenStream ==="); } let file = @@ -1620,3 +1500,51 @@ impl Config<'_> { self.write(file) } } + +fn allow_lints() -> TokenStream { + quote! { + #[allow( + clippy::absurd_extreme_comparisons, + clippy::excessive_precision, + clippy::manual_range_contains, + clippy::unnecessary_cast, + clippy::useless_conversion, + unused_comparisons, + unused_variables, + )] + } +} + +/// Generate a literal token for a signal min/max value. +/// +/// For F32 types, always generates a float literal. +/// For other types, generates an integer literal if the value is an integer within i64 range, +/// otherwise generates a float literal with the integer type suffix. +fn generate_value_literal(value: f64, typ: ValType) -> TokenStream { + if typ == ValType::F32 { + let lit = syn::LitFloat::new(&format!("{value}_f32"), Span::call_site()); + quote! { #lit } + } else { + let typ_str = typ.to_string().to_lowercase(); + // Check if value is an integer and fits in i64 range + if is_integer(value) && value >= i64::MIN as f64 && value <= i64::MAX as f64 { + let val = value as i64; + let lit = syn::LitInt::new(&format!("{val}_{typ_str}"), Span::call_site()); + quote! { #lit } + } else { + // Use float literal with integer type suffix for fractional/overflow values + let lit = syn::LitFloat::new(&format!("{value}_{typ_str}"), Span::call_site()); + quote! { #lit } + } + } +} + +/// Generate `[allow(dead_code)]` attribute if needed +fn allow_dead_code_tokens(allow: bool) -> Option { + if allow { + use quote::quote; + Some(quote! { #[allow(dead_code)] }) + } else { + None + } +} From 4f7e769ab397bbc35801df71599d07352631c1f7 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Mon, 26 Jan 2026 20:15:47 -0500 Subject: [PATCH 04/11] wip --- src/feature_config.rs | 6 +-- src/lib.rs | 123 ++++++++++++++++++++---------------------- 2 files changed, 62 insertions(+), 67 deletions(-) diff --git a/src/feature_config.rs b/src/feature_config.rs index c0e851f..fc97d92 100644 --- a/src/feature_config.rs +++ b/src/feature_config.rs @@ -19,7 +19,7 @@ pub enum FeatureConfig<'a> { impl FeatureConfig<'_> { /// Generate an attribute token stream (like `#[derive(Debug)]`) - pub(crate) fn fmt_attr(&self, tokens: &TokenStream) -> TokenStream { + pub(crate) fn attr(&self, tokens: &TokenStream) -> TokenStream { match self { FeatureConfig::Always => quote! { #[#tokens] }, FeatureConfig::Gated(gate) => quote! { #[cfg_attr(feature = #gate, #tokens)] }, @@ -28,13 +28,13 @@ impl FeatureConfig<'_> { } /// Generate a token stream optionally wrapped in a cfg attribute - pub(crate) fn fmt_cfg(&self, tokens: TokenStream) -> TokenStream { + pub(crate) fn if_cfg(&self, tokens: TokenStream) -> TokenStream { match self { + FeatureConfig::Always => tokens, FeatureConfig::Gated(gate) => quote! { #[cfg(feature = #gate)] #tokens }, - FeatureConfig::Always => tokens, FeatureConfig::Never => quote! {}, } } diff --git a/src/lib.rs b/src/lib.rs index b41f2fc..e349ba5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ use can_dbc::{Dbc, Message, MessageId, Signal, Transmitter, ValDescription, Valu use heck::ToSnakeCase; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; +use syn::{parse2, LitFloat, LitInt}; use typed_builder::TypedBuilder; pub use crate::feature_config::FeatureConfig; @@ -117,10 +118,11 @@ impl Config<'_> { #[allow(unused_imports)] use embedded_can::{Id, StandardId, ExtendedId}; }; - use_statements.extend(self.impl_arbitrary.fmt_cfg(quote! { - use arbitrary::Arbitrary; - })); - use_statements.extend(self.impl_serde.fmt_cfg(quote! { + use_statements.extend( + self.impl_arbitrary + .if_cfg(quote! { use arbitrary::Arbitrary; }), + ); + use_statements.extend(self.impl_serde.if_cfg(quote! { use serde::{Serialize, Deserialize}; })); @@ -162,11 +164,11 @@ impl Config<'_> { fn render_root_enum(&self, dbc: &Dbc) -> TokenStream { let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let debug_derive = self.impl_debug.fmt_attr("e! {derive(Debug)}); - let defmt_derive = self.impl_defmt.fmt_attr("e! {derive(defmt::Format)}); + let debug_derive = self.impl_debug.attr("e! { derive(Debug) }); + let defmt_derive = self.impl_defmt.attr("e! { derive(defmt::Format) }); let serde_derives = self .impl_serde - .fmt_attr("e! {derive(Serialize, Deserialize)}); + .attr("e! { derive(Serialize, Deserialize) }); let allow_lints = allow_lints(); let variants: Vec<_> = get_relevant_messages(dbc) @@ -183,16 +185,12 @@ impl Config<'_> { let from_can_arms: Vec<_> = get_relevant_messages(dbc) .map(|msg| { let msg_type = format_ident!("{}", msg.type_name()); - quote! { - #msg_type::MESSAGE_ID => Messages::#msg_type(#msg_type::try_from(payload)?) - } + quote! { #msg_type::MESSAGE_ID => Messages::#msg_type(#msg_type::try_from(payload)?) } }) .collect(); let from_can_body = if from_can_arms.is_empty() { - quote! { - Err(CanError::UnknownMessageId(id)) - } + quote! { Err(CanError::UnknownMessageId(id)) } } else { quote! { let res = match id { @@ -231,7 +229,7 @@ impl Config<'_> { let msg_name = &msg.name; let msg_type = format_ident!("{}", msg.type_name()); let msg_size = msg.size as usize; - let msg_size_lit = syn::LitInt::new(&msg_size.to_string(), Span::call_site()); + let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); // Build message documentation as individual lines (with leading space for prettyplease) let msg_name_doc = format!(" {msg_name}"); @@ -262,20 +260,20 @@ impl Config<'_> { } // Struct attributes - let serde_serialize = self.impl_serde.fmt_attr("e! {derive(Serialize)}); - let serde_deserialize = self.impl_serde.fmt_attr("e! {derive(Deserialize)}); + let serde_serialize = self.impl_serde.attr("e! { derive(Serialize) }); + let serde_deserialize = self.impl_serde.attr("e! { derive(Deserialize) }); let serde_with = self .impl_serde - .fmt_attr("e! {serde(with = "serde_bytes")}); + .attr("e! { serde(with = "serde_bytes") }); // Message ID constant let message_id = match msg.id { MessageId::Standard(id) => { - let id_lit = syn::LitInt::new(&format!("{id:#x}"), Span::call_site()); + let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); quote! { Id::Standard(unsafe { StandardId::new_unchecked(#id_lit) }) } } MessageId::Extended(id) => { - let id_lit = syn::LitInt::new(&format!("{id:#x}"), Span::call_site()); + let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); quote! { Id::Extended(unsafe { ExtendedId::new_unchecked(#id_lit) }) } } }; @@ -362,9 +360,9 @@ impl Config<'_> { let embedded_can_impl = self.render_embedded_can_frame(msg); // Render debug/defmt/arbitrary impls - let debug_impl = self.impl_debug.fmt_cfg(render_debug_impl(msg)); - let defmt_impl = self.impl_defmt.fmt_cfg(render_defmt_impl(msg)); - let arbitrary_impl = self.impl_arbitrary.fmt_cfg(self.render_arbitrary(msg)); + let debug_impl = self.impl_debug.if_cfg(render_debug_impl(msg)); + let defmt_impl = self.impl_defmt.if_cfg(render_defmt_impl(msg)); + let arbitrary_impl = self.impl_arbitrary.if_cfg(self.render_arbitrary(msg)); // Render enums for this message let enums_for_this_message: Vec<_> = dbc @@ -530,7 +528,7 @@ impl Config<'_> { let match_arms: Vec<_> = variant_infos .iter() .map(|info| { - let literal = syn::LitInt::new(&info.value.to_string(), Span::call_site()); + let literal = LitInt::new(&info.value.to_string(), Span::call_site()); let variant = format_ident!("{}", info.base_name); match info.dup_type { DuplicateType::Unique => { @@ -623,7 +621,7 @@ impl Config<'_> { } }; - self.check_ranges.fmt_cfg(check_code) + self.check_ranges.if_cfg(check_code) }; let signal_to_payload_body = signal_to_payload(signal, msg)?; @@ -653,7 +651,7 @@ fn render_set_signal_multiplexer( multiplexed_enum_variant_wrapper_name(switch_index).to_snake_case() ); let multiplexor_setter = format_ident!("set_{}", multiplexor.field_name()); - let switch_index_lit = syn::LitInt::new(&switch_index.to_string(), Span::call_site()); + let switch_index_lit = LitInt::new(&switch_index.to_string(), Span::call_site()); let doc = format!(" Set value of {}", multiplexor.name); @@ -718,7 +716,7 @@ impl Config<'_> { let match_arms: Vec<_> = multiplexer_indexes.iter().map(|idx| { let multiplexed_wrapper_name = format_ident!("{}", multiplexed_enum_variant_wrapper_name(*idx)); let multiplexed_name = format_ident!("{}", multiplexed_enum_variant_name(msg, signal, *idx).unwrap()); - let idx_lit = syn::LitInt::new(&idx.to_string(), Span::call_site()); + let idx_lit = LitInt::new(&idx.to_string(), Span::call_site()); quote! { #idx_lit => Ok(#enum_type::#multiplexed_wrapper_name(#multiplexed_name { raw: self.raw })) } @@ -808,14 +806,14 @@ fn read_fn_with_type(signal: &Signal, msg: &Message, typ: ValType) -> Result { let (start, end) = le_start_end_bit(signal, msg)?; - let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = LitInt::new(&end.to_string(), Span::call_site()); quote! { self.raw.view_bits::()[#start_lit..#end_lit].load_le::<#typ_ident>() } } BigEndian => { let (start, end) = be_start_end_bit(signal, msg)?; - let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = LitInt::new(&end.to_string(), Span::call_site()); quote! { self.raw.view_bits::()[#start_lit..#end_lit].load_be::<#typ_ident>() } } }) @@ -837,8 +835,8 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { ValType::F32 => { let factor = signal.factor; let offset = signal.offset; - let factor_lit = syn::LitFloat::new(&format!("{factor}_f32"), Span::call_site()); - let offset_lit = syn::LitFloat::new(&format!("{offset}_f32"), Span::call_site()); + let factor_lit = LitFloat::new(&format!("{factor}_f32"), Span::call_site()); + let offset_lit = LitFloat::new(&format!("{offset}_f32"), Span::call_site()); quote! { let signal = #read_expr; let factor = #factor_lit; @@ -848,7 +846,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { } _ => { let factor = signal.factor; - let factor_lit = syn::LitFloat::new(&factor.to_string(), Span::call_site()); + let factor_lit = LitFloat::new(&factor.to_string(), Span::call_site()); let signal_cast = if Some(typ) == ValType::from_signal_uint(signal).unsigned_to_signed() { @@ -859,7 +857,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { if signal.offset >= 0.0 { let offset = signal.offset; - let offset_lit = syn::LitFloat::new(&offset.to_string(), Span::call_site()); + let offset_lit = LitFloat::new(&offset.to_string(), Span::call_site()); quote! { let signal = #read_expr; let factor = #factor_lit; @@ -868,7 +866,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { } } else { let offset_abs = signal.offset.abs(); - let offset_lit = syn::LitFloat::new(&offset_abs.to_string(), Span::call_site()); + let offset_lit = LitFloat::new(&offset_abs.to_string(), Span::call_site()); quote! { let signal = #read_expr; let factor = #factor_lit; @@ -891,8 +889,8 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { ValType::F32 => { let factor = signal.factor; let offset = signal.offset; - let factor_lit = syn::LitFloat::new(&format!("{factor}_f32"), Span::call_site()); - let offset_lit = syn::LitFloat::new(&format!("{offset}_f32"), Span::call_site()); + let factor_lit = LitFloat::new(&format!("{factor}_f32"), Span::call_site()); + let offset_lit = LitFloat::new(&format!("{offset}_f32"), Span::call_site()); let int_typ = ValType::from_signal_int(signal); let int_typ_ident = format_ident!("{int_typ}"); quote! { @@ -903,13 +901,13 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { } _ => { let factor = signal.factor; - let factor_lit = syn::LitFloat::new(&factor.to_string(), Span::call_site()); + let factor_lit = LitFloat::new(&factor.to_string(), Span::call_site()); let int_typ = ValType::from_signal_int(signal); let int_typ_ident = format_ident!("{int_typ}"); if signal.offset >= 0.0 { let offset = signal.offset; - let offset_lit = syn::LitFloat::new(&offset.to_string(), Span::call_site()); + let offset_lit = LitFloat::new(&offset.to_string(), Span::call_site()); quote! { let factor = #factor_lit; let value = value.checked_sub(#offset_lit) @@ -918,7 +916,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { } } else { let offset_abs = signal.offset.abs(); - let offset_lit = syn::LitFloat::new(&offset_abs.to_string(), Span::call_site()); + let offset_lit = LitFloat::new(&offset_abs.to_string(), Span::call_site()); quote! { let factor = #factor_lit; let value = value.checked_add(#offset_lit) @@ -940,14 +938,14 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let store_expr = match signal.byte_order { LittleEndian => { let (start, end) = le_start_end_bit(signal, msg)?; - let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = LitInt::new(&end.to_string(), Span::call_site()); quote! { self.raw.view_bits_mut::()[#start_lit..#end_lit].store_le(value); } } BigEndian => { let (start, end) = be_start_end_bit(signal, msg)?; - let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = syn::LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = LitInt::new(&start.to_string(), Span::call_site()); + let end_lit = LitInt::new(&end.to_string(), Span::call_site()); quote! { self.raw.view_bits_mut::()[#start_lit..#end_lit].store_be(value); } } }; @@ -979,10 +977,10 @@ impl Config<'_> { let allow_lints = allow_lints(); let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let debug_derive = self.impl_debug.fmt_attr("e! {derive(Debug)}); - let defmt_derive = self.impl_defmt.fmt_attr("e! {derive(defmt::Format)}); - let serde_serialize = self.impl_serde.fmt_attr("e! {derive(Serialize)}); - let serde_deserialize = self.impl_serde.fmt_attr("e! {derive(Deserialize)}); + let debug_derive = self.impl_debug.attr("e! { derive(Debug) }); + let defmt_derive = self.impl_defmt.attr("e! { derive(defmt::Format) }); + let serde_serialize = self.impl_serde.attr("e! { derive(Serialize) }); + let serde_deserialize = self.impl_serde.attr("e! { derive(Deserialize) }); // Generate enum variants let enum_variants: Vec<_> = variant_infos @@ -1016,15 +1014,14 @@ impl Config<'_> { } } ValType::F32 => { - let val = syn::LitFloat::new( + let val = LitFloat::new( &format!("{}_f32", info.value), Span::call_site(), ); quote! { #val } } _ => { - let val = - syn::LitInt::new(&info.value.to_string(), Span::call_site()); + let val = LitInt::new(&info.value.to_string(), Span::call_site()); quote! { #val } } }; @@ -1159,7 +1156,7 @@ impl Config<'_> { } }; - self.impl_embedded_can_frame.fmt_cfg(impl_tokens) + self.impl_embedded_can_frame.if_cfg(impl_tokens) } } @@ -1279,7 +1276,7 @@ impl Config<'_> { multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)? ); let msg_size = msg.size as usize; - let msg_size_lit = syn::LitInt::new(&msg_size.to_string(), Span::call_site()); + let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); let signal_impls: Result> = signals .iter() @@ -1293,9 +1290,8 @@ impl Config<'_> { let allow_lints_inner = allow_lints_outer.clone(); let allow_dead_code_inner = allow_dead_code_outer.clone(); - let serde_serialize_inner = self.impl_serde.fmt_attr("e! {derive(Serialize)}); - let serde_deserialize_inner = - self.impl_serde.fmt_attr("e! {derive(Deserialize)}); + let serde_serialize_inner = self.impl_serde.attr("e! { derive(Serialize) }); + let serde_deserialize_inner = self.impl_serde.attr("e! { derive(Deserialize) }); let allow_lints_inner2 = allow_lints_outer.clone(); let allow_dead_code_inner2 = allow_dead_code_outer.clone(); @@ -1383,7 +1379,7 @@ impl Config<'_> { } fn render_error(&self) -> TokenStream { - let error_impl = self.impl_error.fmt_cfg(quote! { + let error_impl = self.impl_error.if_cfg(quote! { impl core::error::Error for CanError {} }); @@ -1421,14 +1417,14 @@ impl Config<'_> { fn render_arbitrary_helpers(&self) -> TokenStream { let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let trait_def = self.impl_arbitrary.fmt_cfg(quote! { + let trait_def = self.impl_arbitrary.if_cfg(quote! { #allow_dead_code trait UnstructuredFloatExt { fn arbitrary_f32(&mut self) -> arbitrary::Result; } }); - let trait_impl = self.impl_arbitrary.fmt_cfg(quote! { + let trait_impl = self.impl_arbitrary.if_cfg(quote! { impl UnstructuredFloatExt for arbitrary::Unstructured<'_> { fn arbitrary_f32(&mut self) -> arbitrary::Result { Ok(f32::from_bits(u32::arbitrary(self)?)) @@ -1478,8 +1474,7 @@ impl Config<'_> { eprintln!("{tokens}"); eprintln!("=== End TokenStream ==="); } - let file = - syn::parse2(tokens).context("Failed to parse generated TokenStream as Rust code")?; + let file = parse2(tokens).context("Failed to parse generated TokenStream as Rust code")?; Ok(prettyplease::unparse(&file)) } @@ -1522,18 +1517,18 @@ fn allow_lints() -> TokenStream { /// otherwise generates a float literal with the integer type suffix. fn generate_value_literal(value: f64, typ: ValType) -> TokenStream { if typ == ValType::F32 { - let lit = syn::LitFloat::new(&format!("{value}_f32"), Span::call_site()); + let lit = LitFloat::new(&format!("{value}_f32"), Span::call_site()); quote! { #lit } } else { let typ_str = typ.to_string().to_lowercase(); // Check if value is an integer and fits in i64 range if is_integer(value) && value >= i64::MIN as f64 && value <= i64::MAX as f64 { let val = value as i64; - let lit = syn::LitInt::new(&format!("{val}_{typ_str}"), Span::call_site()); + let lit = LitInt::new(&format!("{val}_{typ_str}"), Span::call_site()); quote! { #lit } } else { // Use float literal with integer type suffix for fractional/overflow values - let lit = syn::LitFloat::new(&format!("{value}_{typ_str}"), Span::call_site()); + let lit = LitFloat::new(&format!("{value}_{typ_str}"), Span::call_site()); quote! { #lit } } } From 214164ec930cff03f4c4f29120681a7e8142c89a Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 27 Jan 2026 01:05:16 -0500 Subject: [PATCH 05/11] wip --- src/lib.rs | 311 ++++++++++++++++++++------------------------- src/signal_type.rs | 7 + src/utils.rs | 56 ++++++-- 3 files changed, 189 insertions(+), 185 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e349ba5..ac10a01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,7 +173,7 @@ impl Config<'_> { let variants: Vec<_> = get_relevant_messages(dbc) .map(|msg| { - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); let doc_str = format!(" {}", msg.name); // Use message name, not type_name quote! { #[doc = #doc_str] @@ -184,7 +184,7 @@ impl Config<'_> { let from_can_arms: Vec<_> = get_relevant_messages(dbc) .map(|msg| { - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); quote! { #msg_type::MESSAGE_ID => Messages::#msg_type(#msg_type::try_from(payload)?) } }) .collect(); @@ -226,38 +226,36 @@ impl Config<'_> { } fn render_message(&self, msg: &Message, dbc: &Dbc) -> Result { - let msg_name = &msg.name; - let msg_type = format_ident!("{}", msg.type_name()); - let msg_size = msg.size as usize; - let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); - - // Build message documentation as individual lines (with leading space for prettyplease) - let msg_name_doc = format!(" {msg_name}"); - let id_text = match msg.id { - MessageId::Standard(id) => format!(" - Standard ID: {id} ({id:#x})"), - MessageId::Extended(id) => format!(" - Extended ID: {id} ({id:#x})"), + let mut doc = String::new(); + writeln!(doc, "/// {}", msg.name)?; + writeln!(doc, "///")?; + let message_id = match msg.id { + MessageId::Standard(id) => { + writeln!(doc, "/// - Standard ID: {id} (0x{id:x})")?; + let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); + quote! { Id::Standard(unsafe { StandardId::new_unchecked(#id_lit) }) } + } + MessageId::Extended(id) => { + writeln!(doc, "/// - Extended ID: {id} (0x{id:x})")?; + let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); + quote! { Id::Extended(unsafe { ExtendedId::new_unchecked(#id_lit) }) } + } }; - let size_text = format!(" - Size: {msg_size} bytes"); - - let mut struct_doc_lines = vec![ - quote! { #[doc = #msg_name_doc] }, - quote! { #[doc = ""] }, - quote! { #[doc = #id_text] }, - quote! { #[doc = #size_text] }, - ]; - + writeln!(doc, "/// - Size: {} bytes", msg.size)?; if let Transmitter::NodeName(transmitter) = &msg.transmitter { - let transmitter_text = format!(" - Transmitter: {transmitter}"); - struct_doc_lines.push(quote! { #[doc = #transmitter_text] }); + writeln!(doc, "/// - Transmitter: {transmitter}")?; } - if let Some(comment) = dbc.message_comment(msg.id) { - struct_doc_lines.push(quote! { #[doc = ""] }); + writeln!(doc, "///")?; for line in comment.trim().lines() { - let line_with_space = format!(" {line}"); - struct_doc_lines.push(quote! { #[doc = #line_with_space] }); + writeln!(doc, "/// {line}")?; } } + let struct_doc = to_tokens(&doc).context("message doc to tokens")?; + + let msg_type = msg.type_name().to_ident(); + let msg_size = msg.size as usize; + let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); // Struct attributes let serde_serialize = self.impl_serde.attr("e! { derive(Serialize) }); @@ -266,18 +264,6 @@ impl Config<'_> { .impl_serde .attr("e! { serde(with = "serde_bytes") }); - // Message ID constant - let message_id = match msg.id { - MessageId::Standard(id) => { - let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); - quote! { Id::Standard(unsafe { StandardId::new_unchecked(#id_lit) }) } - } - MessageId::Extended(id) => { - let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); - quote! { Id::Extended(unsafe { ExtendedId::new_unchecked(#id_lit) }) } - } - }; - // Signal min/max constants let signal_constants: Vec<_> = msg .signals @@ -287,9 +273,9 @@ impl Config<'_> { if typ == ValType::Bool { None } else { - let min_name = format_ident!("{}_MIN", signal.field_name().to_uppercase()); - let max_name = format_ident!("{}_MAX", signal.field_name().to_uppercase()); - let typ_ident = format_ident!("{typ}"); + let min_name = signal.const_name("_MIN").to_ident(); + let max_name = signal.const_name("_MAX").to_ident(); + let typ_ident = typ.to_ident(); let min_lit = generate_value_literal(signal.min, typ); let max_lit = generate_value_literal(signal.max, typ); @@ -308,9 +294,9 @@ impl Config<'_> { .iter() .filter_map(|signal| { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - let field_name = format_ident!("{}", signal.field_name()); + let field_name = signal.field_name().to_ident(); let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{typ}"); + let typ_ident = typ.to_ident(); Some(quote! { #field_name: #typ_ident }) } else { None @@ -323,8 +309,8 @@ impl Config<'_> { .iter() .filter_map(|signal| { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - let field_name = format_ident!("{}", signal.field_name()); - let setter_name = format_ident!("set_{}", signal.field_name()); + let field_name = signal.field_name().to_ident(); + let setter_name = signal.field_name2("set_", "").to_ident(); Some(quote! { res.#setter_name(#field_name)?; }) } else { None @@ -397,11 +383,10 @@ impl Config<'_> { let allow_lints = allow_lints(); let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - // Create constructor doc string (with leading space) - let new_fn_doc = format!(" Construct new {msg_name} from values"); + let new_fn_doc = to_tokens(&format!("/// Construct new {} from values", msg.name))?; Ok(quote! { - #(#struct_doc_lines)* + #struct_doc #[derive(Clone, Copy)] #serde_serialize #serde_deserialize @@ -417,7 +402,7 @@ impl Config<'_> { #(#signal_constants)* - #[doc = #new_fn_doc] + #new_fn_doc pub fn new(#(#new_fn_args),*) -> Result { let #new_fn_mutability res = Self { raw: [0u8; #msg_size_lit] }; #(#new_fn_setters)* @@ -457,61 +442,39 @@ impl Config<'_> { fn render_signal(&self, signal: &Signal, dbc: &Dbc, msg: &Message) -> Result { let signal_name = &signal.name; - let fn_name = format_ident!("{}", signal.field_name()); - let fn_name_raw = format_ident!("{}_raw", signal.field_name()); - - // Build documentation using single multiline format and parse into tokens - let min_doc = signal.min.to_string(); - let max_doc = signal.max.to_string(); - let unit_doc = format!("{:?}", signal.unit); - let receivers_doc = signal.receivers.join(", "); - let start_bit_doc = signal.start_bit.to_string(); - let size_doc = signal.size.to_string(); - let factor_doc = signal.factor.to_string(); - let offset_doc = signal.offset.to_string(); - let byte_order_doc = format!("{:?}", signal.byte_order); - let value_type_doc = format!("{:?}", signal.value_type); + let fn_name = signal.field_name().to_ident(); + let fn_name_raw = signal.field_name2("", "_raw").to_ident(); // Build signal getter doc as doc comment and parse into tokens - let mut signal_doc_text = format!("/// {signal_name}\n"); + let mut doc = format!("/// {signal_name}\n"); if let Some(comment) = dbc.signal_comment(msg.id, &signal.name) { - signal_doc_text.push_str("///\n"); + doc.push_str("///\n"); for line in comment.trim().lines() { - let _ = writeln!(signal_doc_text, "/// {line}"); + let _ = writeln!(doc, "/// {line}"); } } let _ = writeln!( - signal_doc_text, - "/// - /// - Min: {min_doc} - /// - Max: {max_doc} - /// - Unit: {unit_doc} - /// - Receivers: {receivers_doc}" - ); - let signal_doc_tokens: TokenStream = signal_doc_text - .parse() - .map_err(|e| anyhow::anyhow!("Failed to parse signal doc: {e}"))?; - - // Build raw getter doc as doc comment and parse into tokens - let raw_doc_text = format!( - "/// Get raw value of {signal_name} - /// - /// - Start bit: {start_bit_doc} - /// - Signal size: {size_doc} bits - /// - Factor: {factor_doc} - /// - Offset: {offset_doc} - /// - Byte order: {byte_order_doc} - /// - Value type: {value_type_doc}" + doc, + "\ +/// +/// - Min: {} +/// - Max: {} +/// - Unit: {:?} +/// - Receivers: {}", + signal.min, + signal.max, + signal.unit, + signal.receivers.join(", ") ); - let raw_doc_tokens = to_tokens(&raw_doc_text)?; + let signal_doc = to_tokens(&doc)?; let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{typ}"); + let typ = typ.to_ident(); // Generate getter function let getter = if let Some(variants) = dbc.value_descriptions_for_signal(msg.id, &signal.name) { - let type_name = format_ident!("{}", enum_name(msg, signal)); + let type_name = enum_name(msg, signal).to_ident(); let signal_ty = ValType::from_signal(signal); let variant_infos = generate_variant_info(variants, signal_ty); @@ -529,7 +492,7 @@ impl Config<'_> { .iter() .map(|info| { let literal = LitInt::new(&info.value.to_string(), Span::call_site()); - let variant = format_ident!("{}", info.base_name); + let variant = info.base_name.to_ident(); match info.dup_type { DuplicateType::Unique => { quote! { #literal => #type_name::#variant } @@ -555,40 +518,47 @@ impl Config<'_> { } else { quote! { #[inline(always)] - pub fn #fn_name(&self) -> #typ_ident { + pub fn #fn_name(&self) -> #typ { self.#fn_name_raw() } } }; - // Generate raw getter function - let signal_from_payload_body = - signal_from_payload(signal, msg).context("signal from payload")?; - + let raw_doc_text = format!( + "\ +/// Get raw value of {signal_name} +/// +/// - Start bit: {} +/// - Signal size: {} bits +/// - Factor: {} +/// - Offset: {} +/// - Byte order: {:?} +/// - Value type: {:?}", + signal.start_bit, + signal.size, + signal.factor, + signal.offset, + signal.byte_order, + signal.value_type + ); + let fn_name_doc = to_tokens(&raw_doc_text)?; + let fn_body = signal_from_payload(signal, msg).context("signal from payload")?; let setter = self.render_set_signal(signal, msg)?; Ok(quote! { - #signal_doc_tokens + #signal_doc #getter - #raw_doc_tokens + #fn_name_doc #[inline(always)] - pub fn #fn_name_raw(&self) -> #typ_ident { - #signal_from_payload_body + pub fn #fn_name_raw(&self) -> #typ { + #fn_body } #setter }) } -} - -fn to_tokens(raw_doc_text: &str) -> Result { - raw_doc_text - .parse() - .map_err(|e| anyhow::anyhow!("Failed to parse raw doc: {e}")) -} -impl Config<'_> { fn render_set_signal(&self, signal: &Signal, msg: &Message) -> Result { let setter_name = format_ident!("set_{}", signal.field_name()); @@ -602,18 +572,15 @@ impl Config<'_> { let setter_doc = format!(" Set value of {}", signal.name); let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{typ}"); - let msg_type = format_ident!("{}", msg.type_name()); + let typ_ident = typ.to_ident(); + let msg_type = msg.type_name().to_ident(); // Range check logic let range_check = if signal.size == 1 { quote! {} } else { - let min = signal.min; - let max = signal.max; - - let min_lit = generate_value_literal(min, typ); - let max_lit = generate_value_literal(max, typ); + let min_lit = generate_value_literal(signal.min, typ); + let max_lit = generate_value_literal(signal.max, typ); let check_code = quote! { if value < #min_lit || #max_lit < value { @@ -642,10 +609,7 @@ fn render_set_signal_multiplexer( msg: &Message, switch_index: u64, ) -> Result { - let enum_variant = format_ident!( - "{}", - multiplexed_enum_variant_name(msg, multiplexor, switch_index)? - ); + let enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?.to_ident(); let setter_name = format_ident!( "set_{}", multiplexed_enum_variant_wrapper_name(switch_index).to_snake_case() @@ -653,10 +617,10 @@ fn render_set_signal_multiplexer( let multiplexor_setter = format_ident!("set_{}", multiplexor.field_name()); let switch_index_lit = LitInt::new(&switch_index.to_string(), Span::call_site()); - let doc = format!(" Set value of {}", multiplexor.name); + let doc = to_tokens(&format!("/// Set value of {}", multiplexor.name))?; Ok(quote! { - #[doc = #doc] + #doc #[inline(always)] pub fn #setter_name(&mut self, value: #enum_variant) -> Result<(), CanError> { let b0 = BitArray::<_, LocalBits>::new(self.raw); @@ -670,11 +634,11 @@ fn render_set_signal_multiplexer( impl Config<'_> { fn render_multiplexor_signal(&self, signal: &Signal, msg: &Message) -> Result { - let field = format_ident!("{}", signal.field_name()); + let field = signal.field_name().to_ident(); let field_raw = format_ident!("{}_raw", signal.field_name()); let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{typ}"); - let enum_type = format_ident!("{}", multiplex_enum_name(msg, signal)?); + let typ_ident = typ.to_ident(); + let enum_type = multiplex_enum_name(msg, signal)?.to_ident(); let signal_name = &signal.name; let start_bit_doc = signal.start_bit.to_string(); @@ -686,18 +650,16 @@ impl Config<'_> { // Build raw doc as doc comment string and parse into tokens let raw_doc_text = format!( - "/// Get raw value of {signal_name} - /// - /// - Start bit: {start_bit_doc} - /// - Signal size: {size_doc} bits - /// - Factor: {factor_doc} - /// - Offset: {offset_doc} - /// - Byte order: {byte_order_doc} - /// - Value type: {value_type_doc}" + "/// Get raw value of {signal_name}\n\ + ///\n\ + /// - Start bit: {start_bit_doc}\n\ + /// - Signal size: {size_doc} bits\n\ + /// - Factor: {factor_doc}\n\ + /// - Offset: {offset_doc}\n\ + /// - Byte order: {byte_order_doc}\n\ + /// - Value type: {value_type_doc}" ); - let raw_doc_tokens: TokenStream = raw_doc_text - .parse() - .map_err(|e| anyhow::anyhow!("Failed to parse multiplexor raw doc: {e}"))?; + let field_raw_doc = to_tokens(&raw_doc_text)?; let signal_from_payload_body = signal_from_payload(signal, msg)?; @@ -714,15 +676,15 @@ impl Config<'_> { .collect(); let match_arms: Vec<_> = multiplexer_indexes.iter().map(|idx| { - let multiplexed_wrapper_name = format_ident!("{}", multiplexed_enum_variant_wrapper_name(*idx)); - let multiplexed_name = format_ident!("{}", multiplexed_enum_variant_name(msg, signal, *idx).unwrap()); + let multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*idx).to_ident(); + let multiplexed_name = multiplexed_enum_variant_name(msg, signal, *idx).unwrap().to_ident(); let idx_lit = LitInt::new(&idx.to_string(), Span::call_site()); quote! { #idx_lit => Ok(#enum_type::#multiplexed_wrapper_name(#multiplexed_name { raw: self.raw })) } }).collect(); - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); let setter = self.render_set_signal(signal, msg)?; @@ -733,7 +695,7 @@ impl Config<'_> { let set_multiplexer_fns = set_multiplexer_fns?; Ok(quote! { - #raw_doc_tokens + #field_raw_doc #[inline(always)] pub fn #field_raw(&self) -> #typ_ident { #signal_from_payload_body @@ -802,7 +764,7 @@ fn le_start_end_bit(signal: &Signal, msg: &Message) -> Result<(u64, u64)> { } fn read_fn_with_type(signal: &Signal, msg: &Message, typ: ValType) -> Result { - let typ_ident = format_ident!("{typ}"); + let typ_ident = typ.to_ident(); Ok(match signal.byte_order { LittleEndian => { let (start, end) = le_start_end_bit(signal, msg)?; @@ -823,7 +785,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { let read_expr = read_fn_with_type(signal, msg, ValType::from_signal_int(signal))?; let typ = ValType::from_signal(signal); - let typ_ident = format_ident!("{typ}"); + let typ_ident = typ.to_ident(); Ok(match typ { ValType::Bool => { @@ -880,7 +842,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let typ = ValType::from_signal(signal); - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); let value_conversion = match typ { ValType::Bool => { @@ -892,7 +854,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let factor_lit = LitFloat::new(&format!("{factor}_f32"), Span::call_site()); let offset_lit = LitFloat::new(&format!("{offset}_f32"), Span::call_site()); let int_typ = ValType::from_signal_int(signal); - let int_typ_ident = format_ident!("{int_typ}"); + let int_typ_ident = int_typ.to_ident(); quote! { let factor = #factor_lit; let offset = #offset_lit; @@ -903,7 +865,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let factor = signal.factor; let factor_lit = LitFloat::new(&factor.to_string(), Span::call_site()); let int_typ = ValType::from_signal_int(signal); - let int_typ_ident = format_ident!("{int_typ}"); + let int_typ_ident = int_typ.to_ident(); if signal.offset >= 0.0 { let offset = signal.offset; @@ -929,7 +891,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let signed_conversion = if signal.value_type == Signed { let uint_typ = ValType::from_signal_uint(signal); - let uint_typ_ident = format_ident!("{uint_typ}"); + let uint_typ_ident = uint_typ.to_ident(); quote! { let value = #uint_typ_ident::from_ne_bytes(value.to_ne_bytes()); } } else { quote! {} @@ -965,9 +927,9 @@ impl Config<'_> { msg: &Message, variants: &[ValDescription], ) -> TokenStream { - let type_name = format_ident!("{}", enum_name(msg, signal)); + let type_name = enum_name(msg, signal).to_ident(); let signal_ty = ValType::from_signal(signal); - let signal_ty_ident = format_ident!("{signal_ty}"); + let signal_ty_ident = signal_ty.to_ident(); // Generate variant info to handle duplicates with tuple variants let variant_infos = generate_variant_info(variants, signal_ty); @@ -986,11 +948,11 @@ impl Config<'_> { let enum_variants: Vec<_> = variant_infos .iter() .filter_map(|info| { - let variant = format_ident!("{}", info.base_name); + let variant = info.base_name.to_ident(); match info.dup_type { DuplicateType::Unique => Some(quote! { #variant }), DuplicateType::FirstDuplicate => { - let value_type = format_ident!("{}", info.value_type); + let value_type = info.value_type.to_ident(); Some(quote! { #variant(#value_type) }) } DuplicateType::Duplicate => None, @@ -1002,7 +964,7 @@ impl Config<'_> { let from_match_arms: Vec<_> = variant_infos .iter() .filter_map(|info| { - let variant = format_ident!("{}", info.base_name); + let variant = info.base_name.to_ident(); match info.dup_type { DuplicateType::Unique => { let literal_value = match signal_ty { @@ -1115,7 +1077,7 @@ fn generate_variant_info(variants: &[ValDescription], signal_ty: ValType) -> Vec impl Config<'_> { fn render_embedded_can_frame(&self, msg: &Message) -> TokenStream { - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); let impl_tokens = quote! { impl embedded_can::Frame for #msg_type { @@ -1161,7 +1123,7 @@ impl Config<'_> { } fn render_debug_impl(msg: &Message) -> TokenStream { - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); let typ_name = msg.type_name(); let debug_fields: Vec<_> = msg @@ -1170,7 +1132,7 @@ fn render_debug_impl(msg: &Message) -> TokenStream { .filter(|signal| signal.multiplexer_indicator == Plain) .map(|signal| { let field_name = signal.field_name(); - let field_ident = format_ident!("{field_name}"); + let field_ident = signal.field_name().to_ident(); quote! { .field(#field_name, &self.#field_ident()) } }) .collect(); @@ -1191,7 +1153,7 @@ fn render_debug_impl(msg: &Message) -> TokenStream { } fn render_defmt_impl(msg: &Message) -> TokenStream { - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); let typ_name = msg.type_name(); let plain_signals: Vec<_> = msg @@ -1211,7 +1173,7 @@ fn render_defmt_impl(msg: &Message) -> TokenStream { let field_accessors: Vec<_> = plain_signals .iter() .map(|signal| { - let field_ident = format_ident!("{}", signal.field_name()); + let field_ident = signal.field_name().to_ident(); quote! { self.#field_ident() } }) .collect(); @@ -1248,21 +1210,20 @@ impl Config<'_> { let doc = format!(" Defined values for multiplexed signal {}", msg.name); - let enum_name = format_ident!("{}", multiplex_enum_name(msg, multiplexor_signal)?); + let enum_name = multiplex_enum_name(msg, multiplexor_signal)?.to_ident(); // Generate enum variants - let enum_variants: Vec<_> = multiplexed_signals + let enum_variants = multiplexed_signals .keys() .map(|switch_index| { - let wrapper_name = - format_ident!("{}", multiplexed_enum_variant_wrapper_name(*switch_index)); - let variant_name = format_ident!( - "{}", - multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index).unwrap() - ); - quote! { #wrapper_name(#variant_name) } + let wrapper_name = multiplexed_enum_variant_wrapper_name(*switch_index).to_ident(); + let variant_name = + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)? + .to_ident(); + + Ok(quote! { #wrapper_name(#variant_name) }) }) - .collect(); + .collect::>>()?; // Generate structs for each multiplexed signal let allow_lints_outer = allow_lints(); @@ -1271,10 +1232,9 @@ impl Config<'_> { let struct_defs: Result> = multiplexed_signals .iter() .map(|(switch_index, signals)| { - let struct_name = format_ident!( - "{}", + let struct_name = multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)? - ); + .to_ident(); let msg_size = msg.size as usize; let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); @@ -1335,7 +1295,7 @@ impl Config<'_> { fn render_arbitrary(&self, msg: &Message) -> TokenStream { let allow_lints = allow_lints(); let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let msg_type = format_ident!("{}", msg.type_name()); + let msg_type = msg.type_name().to_ident(); let filtered_signals: Vec<&Signal> = msg .signals @@ -1353,7 +1313,7 @@ impl Config<'_> { let signal_bindings: Vec<_> = filtered_signals .iter() .map(|signal| { - let field_name = format_ident!("{}", signal.field_name()); + let field_name = signal.field_name().to_ident(); let value_expr = signal_to_arbitrary(signal); quote! { let #field_name = #value_expr; } }) @@ -1362,7 +1322,7 @@ impl Config<'_> { // Generate function arguments let args: Vec<_> = filtered_signals .iter() - .map(|signal| format_ident!("{}", signal.field_name())) + .map(|signal| signal.field_name().to_ident()) .collect(); quote! { @@ -1449,7 +1409,7 @@ fn signal_to_arbitrary(signal: &Signal) -> TokenStream { _ => { let min = signal.min as i64; let max = signal.max as i64; - let typ_ident = format_ident!("{typ}"); + let typ_ident = typ.to_ident(); quote! { u.int_in_range(#min..=#max)? as #typ_ident } } } @@ -1543,3 +1503,8 @@ fn allow_dead_code_tokens(allow: bool) -> Option { None } } + +fn to_tokens(code: &str) -> Result { + code.parse() + .map_err(|e| anyhow::anyhow!("Unable to parse {code}\n{e}")) +} diff --git a/src/signal_type.rs b/src/signal_type.rs index 60e87c8..fd87791 100644 --- a/src/signal_type.rs +++ b/src/signal_type.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use can_dbc::Signal; use can_dbc::ValueType::{Signed, Unsigned}; +use quote::IdentFragment; use IntSize::{Size128, Size16, Size32, Size64, Size8}; use ValType::{Bool, SignedInt, UnsignedInt, F32}; @@ -66,6 +67,12 @@ impl Display for ValType { } } +impl IdentFragment for ValType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + impl ValType { pub(crate) fn unsigned_to_signed(self) -> Option { if let UnsignedInt(size) = self { diff --git a/src/utils.rs b/src/utils.rs index c787c88..569affc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,11 @@ use anyhow::{ensure, Result}; use can_dbc::MultiplexIndicator::Multiplexor; +use std::fmt::Display; + use can_dbc::{Message, Signal}; -use heck::{ToPascalCase, ToSnakeCase}; +use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase}; +use proc_macro2::Ident; +use quote::{format_ident, IdentFragment}; use crate::keywords; @@ -50,12 +54,33 @@ pub fn multiplexed_enum_variant_name( } pub trait SignalExt { - fn field_name(&self) -> String; + fn get_name(&self) -> &str; + fn field_name(&self) -> String { + sanitize_name(self.get_name(), ToSnakeCase::to_snake_case) + } + fn field_name2(&self, prefix: &str, suffix: &str) -> String { + format!( + "{prefix}{}{suffix}", + sanitize_name(self.get_name(), ToSnakeCase::to_snake_case) + ) + } + fn const_name(&self, suffix: &str) -> String { + let tmp: String; + sanitize_name( + if suffix.is_empty() { + self.get_name() + } else { + tmp = format!("{}{suffix}", self.get_name()); + &tmp + }, + ToShoutySnakeCase::to_shouty_snake_case, + ) + } } impl SignalExt for Signal { - fn field_name(&self) -> String { - sanitize_name(&self.name, "x", ToSnakeCase::to_snake_case) + fn get_name(&self) -> &str { + &self.name } } @@ -65,27 +90,34 @@ pub trait MessageExt { impl MessageExt for Message { fn type_name(&self) -> String { - sanitize_name(&self.name, "X", ToPascalCase::to_pascal_case) + sanitize_name(&self.name, ToPascalCase::to_pascal_case) } } -pub fn sanitize_name(x: &str, prefix: &str, to_case: fn(&str) -> String) -> String { +pub fn sanitize_name(x: &str, to_case: fn(&str) -> String) -> String { if keywords::is_keyword(x) || !x.starts_with(|c: char| c.is_ascii_alphabetic()) { - format!("{prefix}{}", to_case(x)) + format!("{}{}", to_case("x"), to_case(x)) } else { to_case(x) } } -pub fn type_name(x: &str) -> String { - sanitize_name(x, "X", ToPascalCase::to_pascal_case) -} - pub fn enum_variant_name(x: &str) -> String { - type_name(x) // enum variant and type encoding are identical + sanitize_name(x, ToPascalCase::to_pascal_case) } /// Check if a floating point value is an integer pub fn is_integer(val: f64) -> bool { val.fract().abs() < f64::EPSILON } + +/// A trait to convert a type to a proc-macro Ident +pub trait ToIdent { + fn to_ident(&self) -> Ident; +} + +impl ToIdent for T { + fn to_ident(&self) -> Ident { + format_ident!("{self}") + } +} From f584c5d660d279e96e4868c185ba5fc489237c33 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 27 Jan 2026 01:21:13 -0500 Subject: [PATCH 06/11] wip --- src/lib.rs | 152 +++++++++++++++++++++++++-------------------------- src/utils.rs | 24 +++++++- 2 files changed, 95 insertions(+), 81 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ac10a01..967bdf1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,9 @@ use crate::utils::{ enum_name, enum_variant_name, multiplex_enum_name, multiplexed_enum_variant_name, multiplexed_enum_variant_wrapper_name, MessageExt as _, SignalExt as _, }; +use crate::utils::{ + enum_variant_name, is_integer, MessageExt as _, SignalExt as _, ToIdent as _, Tokens as _, +}; /// Code generator configuration. See module-level docs for an example. #[derive(TypedBuilder)] @@ -173,7 +176,7 @@ impl Config<'_> { let variants: Vec<_> = get_relevant_messages(dbc) .map(|msg| { - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let doc_str = format!(" {}", msg.name); // Use message name, not type_name quote! { #[doc = #doc_str] @@ -184,7 +187,7 @@ impl Config<'_> { let from_can_arms: Vec<_> = get_relevant_messages(dbc) .map(|msg| { - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); quote! { #msg_type::MESSAGE_ID => Messages::#msg_type(#msg_type::try_from(payload)?) } }) .collect(); @@ -251,9 +254,9 @@ impl Config<'_> { writeln!(doc, "/// {line}")?; } } - let struct_doc = to_tokens(&doc).context("message doc to tokens")?; + let struct_doc = doc.tokens().context("message doc to tokens")?; - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let msg_size = msg.size as usize; let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); @@ -273,9 +276,9 @@ impl Config<'_> { if typ == ValType::Bool { None } else { - let min_name = signal.const_name("_MIN").to_ident(); - let max_name = signal.const_name("_MAX").to_ident(); - let typ_ident = typ.to_ident(); + let min_name = signal.const_name("_MIN").ident(); + let max_name = signal.const_name("_MAX").ident(); + let typ_ident = typ.ident(); let min_lit = generate_value_literal(signal.min, typ); let max_lit = generate_value_literal(signal.max, typ); @@ -294,9 +297,9 @@ impl Config<'_> { .iter() .filter_map(|signal| { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - let field_name = signal.field_name().to_ident(); + let field_name = signal.field_name().ident(); let typ = ValType::from_signal(signal); - let typ_ident = typ.to_ident(); + let typ_ident = typ.ident(); Some(quote! { #field_name: #typ_ident }) } else { None @@ -309,8 +312,8 @@ impl Config<'_> { .iter() .filter_map(|signal| { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - let field_name = signal.field_name().to_ident(); - let setter_name = signal.field_name2("set_", "").to_ident(); + let field_name = signal.field_name().ident(); + let setter_name = signal.field_name2("set_", "").ident(); Some(quote! { res.#setter_name(#field_name)?; }) } else { None @@ -383,7 +386,7 @@ impl Config<'_> { let allow_lints = allow_lints(); let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let new_fn_doc = to_tokens(&format!("/// Construct new {} from values", msg.name))?; + let new_fn_doc = format!("/// Construct new {} from values", msg.name).tokens()?; Ok(quote! { #struct_doc @@ -442,8 +445,8 @@ impl Config<'_> { fn render_signal(&self, signal: &Signal, dbc: &Dbc, msg: &Message) -> Result { let signal_name = &signal.name; - let fn_name = signal.field_name().to_ident(); - let fn_name_raw = signal.field_name2("", "_raw").to_ident(); + let fn_name = signal.field_name().ident(); + let fn_name_raw = signal.field_name2("", "_raw").ident(); // Build signal getter doc as doc comment and parse into tokens let mut doc = format!("/// {signal_name}\n"); @@ -466,15 +469,15 @@ impl Config<'_> { signal.unit, signal.receivers.join(", ") ); - let signal_doc = to_tokens(&doc)?; + let signal_doc = doc.tokens()?; let typ = ValType::from_signal(signal); - let typ = typ.to_ident(); + let typ = typ.ident(); // Generate getter function let getter = if let Some(variants) = dbc.value_descriptions_for_signal(msg.id, &signal.name) { - let type_name = enum_name(msg, signal).to_ident(); + let type_name = enum_name(msg, signal).ident(); let signal_ty = ValType::from_signal(signal); let variant_infos = generate_variant_info(variants, signal_ty); @@ -492,7 +495,7 @@ impl Config<'_> { .iter() .map(|info| { let literal = LitInt::new(&info.value.to_string(), Span::call_site()); - let variant = info.base_name.to_ident(); + let variant = info.base_name.ident(); match info.dup_type { DuplicateType::Unique => { quote! { #literal => #type_name::#variant } @@ -541,7 +544,7 @@ impl Config<'_> { signal.byte_order, signal.value_type ); - let fn_name_doc = to_tokens(&raw_doc_text)?; + let fn_name_doc = raw_doc_text.tokens()?; let fn_body = signal_from_payload(signal, msg).context("signal from payload")?; let setter = self.render_set_signal(signal, msg)?; @@ -560,7 +563,7 @@ impl Config<'_> { } fn render_set_signal(&self, signal: &Signal, msg: &Message) -> Result { - let setter_name = format_ident!("set_{}", signal.field_name()); + let setter_name = signal.field_name2("set_", "").ident(); // To avoid accidentally changing the multiplexor value without changing // the signals accordingly this fn is kept private for multiplexors. @@ -572,8 +575,8 @@ impl Config<'_> { let setter_doc = format!(" Set value of {}", signal.name); let typ = ValType::from_signal(signal); - let typ_ident = typ.to_ident(); - let msg_type = msg.type_name().to_ident(); + let typ_ident = typ.ident(); + let msg_type = msg.type_name().ident(); // Range check logic let range_check = if signal.size == 1 { @@ -609,7 +612,7 @@ fn render_set_signal_multiplexer( msg: &Message, switch_index: u64, ) -> Result { - let enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?.to_ident(); + let enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?.ident(); let setter_name = format_ident!( "set_{}", multiplexed_enum_variant_wrapper_name(switch_index).to_snake_case() @@ -617,7 +620,7 @@ fn render_set_signal_multiplexer( let multiplexor_setter = format_ident!("set_{}", multiplexor.field_name()); let switch_index_lit = LitInt::new(&switch_index.to_string(), Span::call_site()); - let doc = to_tokens(&format!("/// Set value of {}", multiplexor.name))?; + let doc = format!("/// Set value of {}", multiplexor.name).tokens()?; Ok(quote! { #doc @@ -634,32 +637,32 @@ fn render_set_signal_multiplexer( impl Config<'_> { fn render_multiplexor_signal(&self, signal: &Signal, msg: &Message) -> Result { - let field = signal.field_name().to_ident(); + let field = signal.field_name().ident(); let field_raw = format_ident!("{}_raw", signal.field_name()); let typ = ValType::from_signal(signal); - let typ_ident = typ.to_ident(); - let enum_type = multiplex_enum_name(msg, signal)?.to_ident(); - - let signal_name = &signal.name; - let start_bit_doc = signal.start_bit.to_string(); - let size_doc = signal.size.to_string(); - let factor_doc = signal.factor.to_string(); - let offset_doc = signal.offset.to_string(); - let byte_order_doc = format!("{:?}", signal.byte_order); - let value_type_doc = format!("{:?}", signal.value_type); + let typ_ident = typ.ident(); + let enum_type = multiplex_enum_name(msg, signal)?.ident(); // Build raw doc as doc comment string and parse into tokens let raw_doc_text = format!( - "/// Get raw value of {signal_name}\n\ - ///\n\ - /// - Start bit: {start_bit_doc}\n\ - /// - Signal size: {size_doc} bits\n\ - /// - Factor: {factor_doc}\n\ - /// - Offset: {offset_doc}\n\ - /// - Byte order: {byte_order_doc}\n\ - /// - Value type: {value_type_doc}" + "\ +/// Get raw value of {} +/// +/// - Start bit: {} +/// - Signal size: {} bits +/// - Factor: {} +/// - Offset: {} +/// - Byte order: {:?} +/// - Value type: {:?}", + signal.name, + signal.start_bit, + signal.size, + signal.factor, + signal.offset, + signal.byte_order, + signal.value_type ); - let field_raw_doc = to_tokens(&raw_doc_text)?; + let field_raw_doc = raw_doc_text.tokens()?; let signal_from_payload_body = signal_from_payload(signal, msg)?; @@ -676,15 +679,15 @@ impl Config<'_> { .collect(); let match_arms: Vec<_> = multiplexer_indexes.iter().map(|idx| { - let multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*idx).to_ident(); - let multiplexed_name = multiplexed_enum_variant_name(msg, signal, *idx).unwrap().to_ident(); + let multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*idx).ident(); + let multiplexed_name = multiplexed_enum_variant_name(msg, signal, *idx).unwrap().ident(); let idx_lit = LitInt::new(&idx.to_string(), Span::call_site()); quote! { #idx_lit => Ok(#enum_type::#multiplexed_wrapper_name(#multiplexed_name { raw: self.raw })) } }).collect(); - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let setter = self.render_set_signal(signal, msg)?; @@ -764,7 +767,7 @@ fn le_start_end_bit(signal: &Signal, msg: &Message) -> Result<(u64, u64)> { } fn read_fn_with_type(signal: &Signal, msg: &Message, typ: ValType) -> Result { - let typ_ident = typ.to_ident(); + let typ_ident = typ.ident(); Ok(match signal.byte_order { LittleEndian => { let (start, end) = le_start_end_bit(signal, msg)?; @@ -785,7 +788,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { let read_expr = read_fn_with_type(signal, msg, ValType::from_signal_int(signal))?; let typ = ValType::from_signal(signal); - let typ_ident = typ.to_ident(); + let typ_ident = typ.ident(); Ok(match typ { ValType::Bool => { @@ -842,7 +845,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let typ = ValType::from_signal(signal); - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let value_conversion = match typ { ValType::Bool => { @@ -854,7 +857,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let factor_lit = LitFloat::new(&format!("{factor}_f32"), Span::call_site()); let offset_lit = LitFloat::new(&format!("{offset}_f32"), Span::call_site()); let int_typ = ValType::from_signal_int(signal); - let int_typ_ident = int_typ.to_ident(); + let int_typ_ident = int_typ.ident(); quote! { let factor = #factor_lit; let offset = #offset_lit; @@ -865,7 +868,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let factor = signal.factor; let factor_lit = LitFloat::new(&factor.to_string(), Span::call_site()); let int_typ = ValType::from_signal_int(signal); - let int_typ_ident = int_typ.to_ident(); + let int_typ_ident = int_typ.ident(); if signal.offset >= 0.0 { let offset = signal.offset; @@ -891,7 +894,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let signed_conversion = if signal.value_type == Signed { let uint_typ = ValType::from_signal_uint(signal); - let uint_typ_ident = uint_typ.to_ident(); + let uint_typ_ident = uint_typ.ident(); quote! { let value = #uint_typ_ident::from_ne_bytes(value.to_ne_bytes()); } } else { quote! {} @@ -927,9 +930,9 @@ impl Config<'_> { msg: &Message, variants: &[ValDescription], ) -> TokenStream { - let type_name = enum_name(msg, signal).to_ident(); + let type_name = enum_name(msg, signal).ident(); let signal_ty = ValType::from_signal(signal); - let signal_ty_ident = signal_ty.to_ident(); + let signal_ty_ident = signal_ty.ident(); // Generate variant info to handle duplicates with tuple variants let variant_infos = generate_variant_info(variants, signal_ty); @@ -948,11 +951,11 @@ impl Config<'_> { let enum_variants: Vec<_> = variant_infos .iter() .filter_map(|info| { - let variant = info.base_name.to_ident(); + let variant = info.base_name.ident(); match info.dup_type { DuplicateType::Unique => Some(quote! { #variant }), DuplicateType::FirstDuplicate => { - let value_type = info.value_type.to_ident(); + let value_type = info.value_type.ident(); Some(quote! { #variant(#value_type) }) } DuplicateType::Duplicate => None, @@ -964,7 +967,7 @@ impl Config<'_> { let from_match_arms: Vec<_> = variant_infos .iter() .filter_map(|info| { - let variant = info.base_name.to_ident(); + let variant = info.base_name.ident(); match info.dup_type { DuplicateType::Unique => { let literal_value = match signal_ty { @@ -1077,7 +1080,7 @@ fn generate_variant_info(variants: &[ValDescription], signal_ty: ValType) -> Vec impl Config<'_> { fn render_embedded_can_frame(&self, msg: &Message) -> TokenStream { - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let impl_tokens = quote! { impl embedded_can::Frame for #msg_type { @@ -1123,7 +1126,7 @@ impl Config<'_> { } fn render_debug_impl(msg: &Message) -> TokenStream { - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let typ_name = msg.type_name(); let debug_fields: Vec<_> = msg @@ -1132,7 +1135,7 @@ fn render_debug_impl(msg: &Message) -> TokenStream { .filter(|signal| signal.multiplexer_indicator == Plain) .map(|signal| { let field_name = signal.field_name(); - let field_ident = signal.field_name().to_ident(); + let field_ident = signal.field_name().ident(); quote! { .field(#field_name, &self.#field_ident()) } }) .collect(); @@ -1153,7 +1156,7 @@ fn render_debug_impl(msg: &Message) -> TokenStream { } fn render_defmt_impl(msg: &Message) -> TokenStream { - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let typ_name = msg.type_name(); let plain_signals: Vec<_> = msg @@ -1173,7 +1176,7 @@ fn render_defmt_impl(msg: &Message) -> TokenStream { let field_accessors: Vec<_> = plain_signals .iter() .map(|signal| { - let field_ident = signal.field_name().to_ident(); + let field_ident = signal.field_name().ident(); quote! { self.#field_ident() } }) .collect(); @@ -1210,16 +1213,15 @@ impl Config<'_> { let doc = format!(" Defined values for multiplexed signal {}", msg.name); - let enum_name = multiplex_enum_name(msg, multiplexor_signal)?.to_ident(); + let enum_name = multiplex_enum_name(msg, multiplexor_signal)?.ident(); // Generate enum variants let enum_variants = multiplexed_signals .keys() .map(|switch_index| { - let wrapper_name = multiplexed_enum_variant_wrapper_name(*switch_index).to_ident(); + let wrapper_name = multiplexed_enum_variant_wrapper_name(*switch_index).ident(); let variant_name = - multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)? - .to_ident(); + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?.ident(); Ok(quote! { #wrapper_name(#variant_name) }) }) @@ -1233,8 +1235,7 @@ impl Config<'_> { .iter() .map(|(switch_index, signals)| { let struct_name = - multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)? - .to_ident(); + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?.ident(); let msg_size = msg.size as usize; let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); @@ -1295,7 +1296,7 @@ impl Config<'_> { fn render_arbitrary(&self, msg: &Message) -> TokenStream { let allow_lints = allow_lints(); let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let msg_type = msg.type_name().to_ident(); + let msg_type = msg.type_name().ident(); let filtered_signals: Vec<&Signal> = msg .signals @@ -1313,7 +1314,7 @@ impl Config<'_> { let signal_bindings: Vec<_> = filtered_signals .iter() .map(|signal| { - let field_name = signal.field_name().to_ident(); + let field_name = signal.field_name().ident(); let value_expr = signal_to_arbitrary(signal); quote! { let #field_name = #value_expr; } }) @@ -1322,7 +1323,7 @@ impl Config<'_> { // Generate function arguments let args: Vec<_> = filtered_signals .iter() - .map(|signal| signal.field_name().to_ident()) + .map(|signal| signal.field_name().ident()) .collect(); quote! { @@ -1409,7 +1410,7 @@ fn signal_to_arbitrary(signal: &Signal) -> TokenStream { _ => { let min = signal.min as i64; let max = signal.max as i64; - let typ_ident = typ.to_ident(); + let typ_ident = typ.ident(); quote! { u.int_in_range(#min..=#max)? as #typ_ident } } } @@ -1503,8 +1504,3 @@ fn allow_dead_code_tokens(allow: bool) -> Option { None } } - -fn to_tokens(code: &str) -> Result { - code.parse() - .map_err(|e| anyhow::anyhow!("Unable to parse {code}\n{e}")) -} diff --git a/src/utils.rs b/src/utils.rs index 569affc..757f988 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,7 +4,7 @@ use std::fmt::Display; use can_dbc::{Message, Signal}; use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase}; -use proc_macro2::Ident; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, IdentFragment}; use crate::keywords; @@ -113,11 +113,29 @@ pub fn is_integer(val: f64) -> bool { /// A trait to convert a type to a proc-macro Ident pub trait ToIdent { - fn to_ident(&self) -> Ident; + fn ident(&self) -> Ident; } impl ToIdent for T { - fn to_ident(&self) -> Ident { + fn ident(&self) -> Ident { format_ident!("{self}") } } + +/// A trait to convert a type to a proc-macro Ident +pub trait Tokens { + fn tokens(&self) -> anyhow::Result; +} + +impl Tokens for &str { + fn tokens(&self) -> anyhow::Result { + self.parse() + .map_err(|e| anyhow::anyhow!("Unable to parse {self}\n{e}")) + } +} + +impl Tokens for String { + fn tokens(&self) -> anyhow::Result { + self.as_str().tokens() + } +} From 990fd4f58c5f9dbddb117cf60d5fc907dd822c71 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Wed, 28 Jan 2026 08:49:40 -0500 Subject: [PATCH 07/11] wip --- src/lib.rs | 93 ++++++++++++++++++++++++---------------------------- src/utils.rs | 50 +++++++++++++++------------- 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 967bdf1..01ae540 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,20 +22,17 @@ use can_dbc::ByteOrder::{BigEndian, LittleEndian}; use can_dbc::MultiplexIndicator::{MultiplexedSignal, Multiplexor, Plain}; use can_dbc::ValueType::Signed; use can_dbc::{Dbc, Message, MessageId, Signal, Transmitter, ValDescription, ValueDescription}; -use heck::ToSnakeCase; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::quote; use syn::{parse2, LitFloat, LitInt}; use typed_builder::TypedBuilder; pub use crate::feature_config::FeatureConfig; use crate::signal_type::ValType; use crate::utils::{ - enum_name, enum_variant_name, multiplex_enum_name, multiplexed_enum_variant_name, - multiplexed_enum_variant_wrapper_name, MessageExt as _, SignalExt as _, -}; -use crate::utils::{ - enum_variant_name, is_integer, MessageExt as _, SignalExt as _, ToIdent as _, Tokens as _, + enum_name, enum_variant_name, is_integer, multiplex_enum_name, multiplexed_enum_variant_name, + multiplexed_enum_variant_wrapper_name, multiplexed_enum_variant_wrapper_setter_name, + MessageExt as _, SignalExt as _, ToIdent as _, Tokens as _, }; /// Code generator configuration. See module-level docs for an example. @@ -176,7 +173,7 @@ impl Config<'_> { let variants: Vec<_> = get_relevant_messages(dbc) .map(|msg| { - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); let doc_str = format!(" {}", msg.name); // Use message name, not type_name quote! { #[doc = #doc_str] @@ -187,7 +184,7 @@ impl Config<'_> { let from_can_arms: Vec<_> = get_relevant_messages(dbc) .map(|msg| { - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); quote! { #msg_type::MESSAGE_ID => Messages::#msg_type(#msg_type::try_from(payload)?) } }) .collect(); @@ -256,7 +253,7 @@ impl Config<'_> { } let struct_doc = doc.tokens().context("message doc to tokens")?; - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); let msg_size = msg.size as usize; let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); @@ -276,10 +273,9 @@ impl Config<'_> { if typ == ValType::Bool { None } else { - let min_name = signal.const_name("_MIN").ident(); - let max_name = signal.const_name("_MAX").ident(); + let min_name = signal.const_name("_MIN"); + let max_name = signal.const_name("_MAX"); let typ_ident = typ.ident(); - let min_lit = generate_value_literal(signal.min, typ); let max_lit = generate_value_literal(signal.max, typ); @@ -297,7 +293,7 @@ impl Config<'_> { .iter() .filter_map(|signal| { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - let field_name = signal.field_name().ident(); + let field_name = signal.field_name(); let typ = ValType::from_signal(signal); let typ_ident = typ.ident(); Some(quote! { #field_name: #typ_ident }) @@ -312,8 +308,8 @@ impl Config<'_> { .iter() .filter_map(|signal| { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { - let field_name = signal.field_name().ident(); - let setter_name = signal.field_name2("set_", "").ident(); + let field_name = signal.field_name(); + let setter_name = signal.field_name2("set_", ""); Some(quote! { res.#setter_name(#field_name)?; }) } else { None @@ -445,8 +441,8 @@ impl Config<'_> { fn render_signal(&self, signal: &Signal, dbc: &Dbc, msg: &Message) -> Result { let signal_name = &signal.name; - let fn_name = signal.field_name().ident(); - let fn_name_raw = signal.field_name2("", "_raw").ident(); + let fn_name = signal.field_name(); + let fn_name_raw = signal.field_name2("", "_raw"); // Build signal getter doc as doc comment and parse into tokens let mut doc = format!("/// {signal_name}\n"); @@ -477,7 +473,7 @@ impl Config<'_> { // Generate getter function let getter = if let Some(variants) = dbc.value_descriptions_for_signal(msg.id, &signal.name) { - let type_name = enum_name(msg, signal).ident(); + let type_name = enum_name(msg, signal); let signal_ty = ValType::from_signal(signal); let variant_infos = generate_variant_info(variants, signal_ty); @@ -563,7 +559,7 @@ impl Config<'_> { } fn render_set_signal(&self, signal: &Signal, msg: &Message) -> Result { - let setter_name = signal.field_name2("set_", "").ident(); + let setter_name = signal.field_name2("set_", ""); // To avoid accidentally changing the multiplexor value without changing // the signals accordingly this fn is kept private for multiplexors. @@ -576,7 +572,7 @@ impl Config<'_> { let setter_doc = format!(" Set value of {}", signal.name); let typ = ValType::from_signal(signal); let typ_ident = typ.ident(); - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); // Range check logic let range_check = if signal.size == 1 { @@ -612,12 +608,9 @@ fn render_set_signal_multiplexer( msg: &Message, switch_index: u64, ) -> Result { - let enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?.ident(); - let setter_name = format_ident!( - "set_{}", - multiplexed_enum_variant_wrapper_name(switch_index).to_snake_case() - ); - let multiplexor_setter = format_ident!("set_{}", multiplexor.field_name()); + let enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?; + let setter_name = multiplexed_enum_variant_wrapper_setter_name(switch_index); + let multiplexor_setter = multiplexor.field_name2("set_", ""); let switch_index_lit = LitInt::new(&switch_index.to_string(), Span::call_site()); let doc = format!("/// Set value of {}", multiplexor.name).tokens()?; @@ -637,11 +630,11 @@ fn render_set_signal_multiplexer( impl Config<'_> { fn render_multiplexor_signal(&self, signal: &Signal, msg: &Message) -> Result { - let field = signal.field_name().ident(); - let field_raw = format_ident!("{}_raw", signal.field_name()); + let field = signal.field_name(); + let field_raw = signal.field_name2("", "_raw"); let typ = ValType::from_signal(signal); let typ_ident = typ.ident(); - let enum_type = multiplex_enum_name(msg, signal)?.ident(); + let enum_type = multiplex_enum_name(msg, signal)?; // Build raw doc as doc comment string and parse into tokens let raw_doc_text = format!( @@ -679,15 +672,15 @@ impl Config<'_> { .collect(); let match_arms: Vec<_> = multiplexer_indexes.iter().map(|idx| { - let multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*idx).ident(); - let multiplexed_name = multiplexed_enum_variant_name(msg, signal, *idx).unwrap().ident(); + let multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*idx); + let multiplexed_name = multiplexed_enum_variant_name(msg, signal, *idx).unwrap(); let idx_lit = LitInt::new(&idx.to_string(), Span::call_site()); quote! { #idx_lit => Ok(#enum_type::#multiplexed_wrapper_name(#multiplexed_name { raw: self.raw })) } }).collect(); - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); let setter = self.render_set_signal(signal, msg)?; @@ -845,7 +838,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let typ = ValType::from_signal(signal); - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); let value_conversion = match typ { ValType::Bool => { @@ -930,7 +923,7 @@ impl Config<'_> { msg: &Message, variants: &[ValDescription], ) -> TokenStream { - let type_name = enum_name(msg, signal).ident(); + let type_name = enum_name(msg, signal); let signal_ty = ValType::from_signal(signal); let signal_ty_ident = signal_ty.ident(); @@ -1080,7 +1073,7 @@ fn generate_variant_info(variants: &[ValDescription], signal_ty: ValType) -> Vec impl Config<'_> { fn render_embedded_can_frame(&self, msg: &Message) -> TokenStream { - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); let impl_tokens = quote! { impl embedded_can::Frame for #msg_type { @@ -1126,16 +1119,16 @@ impl Config<'_> { } fn render_debug_impl(msg: &Message) -> TokenStream { - let msg_type = msg.type_name().ident(); - let typ_name = msg.type_name(); + let msg_type = msg.type_name(); + let typ_name = msg_type.to_string(); let debug_fields: Vec<_> = msg .signals .iter() .filter(|signal| signal.multiplexer_indicator == Plain) .map(|signal| { - let field_name = signal.field_name(); - let field_ident = signal.field_name().ident(); + let field_ident = signal.field_name(); + let field_name = field_ident.to_string(); quote! { .field(#field_name, &self.#field_ident()) } }) .collect(); @@ -1156,8 +1149,8 @@ fn render_debug_impl(msg: &Message) -> TokenStream { } fn render_defmt_impl(msg: &Message) -> TokenStream { - let msg_type = msg.type_name().ident(); - let typ_name = msg.type_name(); + let msg_type = msg.type_name(); + let typ_name = msg_type.to_string(); let plain_signals: Vec<_> = msg .signals @@ -1176,7 +1169,7 @@ fn render_defmt_impl(msg: &Message) -> TokenStream { let field_accessors: Vec<_> = plain_signals .iter() .map(|signal| { - let field_ident = signal.field_name().ident(); + let field_ident = signal.field_name(); quote! { self.#field_ident() } }) .collect(); @@ -1213,15 +1206,15 @@ impl Config<'_> { let doc = format!(" Defined values for multiplexed signal {}", msg.name); - let enum_name = multiplex_enum_name(msg, multiplexor_signal)?.ident(); + let enum_name = multiplex_enum_name(msg, multiplexor_signal)?; // Generate enum variants let enum_variants = multiplexed_signals .keys() .map(|switch_index| { - let wrapper_name = multiplexed_enum_variant_wrapper_name(*switch_index).ident(); + let wrapper_name = multiplexed_enum_variant_wrapper_name(*switch_index); let variant_name = - multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?.ident(); + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?; Ok(quote! { #wrapper_name(#variant_name) }) }) @@ -1235,7 +1228,7 @@ impl Config<'_> { .iter() .map(|(switch_index, signals)| { let struct_name = - multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?.ident(); + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?; let msg_size = msg.size as usize; let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); @@ -1296,7 +1289,7 @@ impl Config<'_> { fn render_arbitrary(&self, msg: &Message) -> TokenStream { let allow_lints = allow_lints(); let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); - let msg_type = msg.type_name().ident(); + let msg_type = msg.type_name(); let filtered_signals: Vec<&Signal> = msg .signals @@ -1314,7 +1307,7 @@ impl Config<'_> { let signal_bindings: Vec<_> = filtered_signals .iter() .map(|signal| { - let field_name = signal.field_name().ident(); + let field_name = signal.field_name(); let value_expr = signal_to_arbitrary(signal); quote! { let #field_name = #value_expr; } }) @@ -1323,7 +1316,7 @@ impl Config<'_> { // Generate function arguments let args: Vec<_> = filtered_signals .iter() - .map(|signal| signal.field_name().ident()) + .map(|signal| signal.field_name()) .collect(); quote! { diff --git a/src/utils.rs b/src/utils.rs index 757f988..a37269d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ -use anyhow::{ensure, Result}; -use can_dbc::MultiplexIndicator::Multiplexor; use std::fmt::Display; +use anyhow::{anyhow, ensure, Result}; +use can_dbc::MultiplexIndicator::Multiplexor; use can_dbc::{Message, Signal}; use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase}; use proc_macro2::{Ident, TokenStream}; @@ -9,7 +9,7 @@ use quote::{format_ident, IdentFragment}; use crate::keywords; -pub fn enum_name(msg: &Message, signal: &Signal) -> String { +pub fn enum_name(msg: &Message, signal: &Signal) -> Ident { // this turns signal `_4DRIVE` into `4drive` let signal_name = signal .name @@ -17,14 +17,18 @@ pub fn enum_name(msg: &Message, signal: &Signal) -> String { .to_pascal_case(); let msg_name = enum_variant_name(&msg.name); - format!("{msg_name}{signal_name}") + format_ident!("{msg_name}{signal_name}") +} + +pub fn multiplexed_enum_variant_wrapper_name(switch_index: u64) -> Ident { + format_ident!("M{switch_index}") } -pub fn multiplexed_enum_variant_wrapper_name(switch_index: u64) -> String { - format!("M{switch_index}") +pub fn multiplexed_enum_variant_wrapper_setter_name(switch_index: u64) -> Ident { + format_ident!("set_m{switch_index}") } -pub fn multiplex_enum_name(msg: &Message, multiplexor: &Signal) -> Result { +pub fn multiplex_enum_name(msg: &Message, multiplexor: &Signal) -> Result { ensure!( matches!(multiplexor.multiplexer_indicator, Multiplexor), "signal {multiplexor:?} is not the multiplexor", @@ -33,14 +37,14 @@ pub fn multiplex_enum_name(msg: &Message, multiplexor: &Signal) -> Result Result { +) -> Result { ensure!( matches!(multiplexor.multiplexer_indicator, Multiplexor), "signal {multiplexor:?} is not the multiplexor", @@ -50,21 +54,21 @@ pub fn multiplexed_enum_variant_name( "{}{}M{switch_index}", msg.name.to_pascal_case(), multiplexor.name.to_pascal_case(), - )) + ).ident()) } pub trait SignalExt { fn get_name(&self) -> &str; - fn field_name(&self) -> String { - sanitize_name(self.get_name(), ToSnakeCase::to_snake_case) + fn field_name(&self) -> Ident { + sanitize_name(self.get_name(), ToSnakeCase::to_snake_case).ident() } - fn field_name2(&self, prefix: &str, suffix: &str) -> String { + fn field_name2(&self, prefix: &str, suffix: &str) -> Ident { format!( "{prefix}{}{suffix}", sanitize_name(self.get_name(), ToSnakeCase::to_snake_case) - ) + ).ident() } - fn const_name(&self, suffix: &str) -> String { + fn const_name(&self, suffix: &str) -> Ident { let tmp: String; sanitize_name( if suffix.is_empty() { @@ -74,7 +78,7 @@ pub trait SignalExt { &tmp }, ToShoutySnakeCase::to_shouty_snake_case, - ) + ).ident() } } @@ -85,12 +89,12 @@ impl SignalExt for Signal { } pub trait MessageExt { - fn type_name(&self) -> String; + fn type_name(&self) -> Ident; } impl MessageExt for Message { - fn type_name(&self) -> String { - sanitize_name(&self.name, ToPascalCase::to_pascal_case) + fn type_name(&self) -> Ident { + sanitize_name(&self.name, ToPascalCase::to_pascal_case).ident() } } @@ -124,18 +128,18 @@ impl ToIdent for T { /// A trait to convert a type to a proc-macro Ident pub trait Tokens { - fn tokens(&self) -> anyhow::Result; + fn tokens(&self) -> Result; } impl Tokens for &str { - fn tokens(&self) -> anyhow::Result { + fn tokens(&self) -> Result { self.parse() - .map_err(|e| anyhow::anyhow!("Unable to parse {self}\n{e}")) + .map_err(|e| anyhow!("Unable to parse {self}\n{e}")) } } impl Tokens for String { - fn tokens(&self) -> anyhow::Result { + fn tokens(&self) -> Result { self.as_str().tokens() } } From 32b22c7d2d5fef3c27d761ed815d452995c5bcbe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:08:00 +0000 Subject: [PATCH 08/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/utils.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index a37269d..84a818b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -37,7 +37,8 @@ pub fn multiplex_enum_name(msg: &Message, multiplexor: &Signal) -> Result "{}{}Index", msg.name.to_pascal_case(), multiplexor.name.to_pascal_case(), - ).ident()) + ) + .ident()) } pub fn multiplexed_enum_variant_name( @@ -54,7 +55,8 @@ pub fn multiplexed_enum_variant_name( "{}{}M{switch_index}", msg.name.to_pascal_case(), multiplexor.name.to_pascal_case(), - ).ident()) + ) + .ident()) } pub trait SignalExt { @@ -66,7 +68,8 @@ pub trait SignalExt { format!( "{prefix}{}{suffix}", sanitize_name(self.get_name(), ToSnakeCase::to_snake_case) - ).ident() + ) + .ident() } fn const_name(&self, suffix: &str) -> Ident { let tmp: String; @@ -78,7 +81,8 @@ pub trait SignalExt { &tmp }, ToShoutySnakeCase::to_shouty_snake_case, - ).ident() + ) + .ident() } } From f4075858f25fc6dfa7dea13289d6501c11329539 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 29 Jan 2026 17:19:32 -0500 Subject: [PATCH 09/11] rename field_name2 to field_name_ext --- src/lib.rs | 10 +++++----- src/utils.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 01ae540..05d7fe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,7 +309,7 @@ impl Config<'_> { .filter_map(|signal| { if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { let field_name = signal.field_name(); - let setter_name = signal.field_name2("set_", ""); + let setter_name = signal.field_name_ext("set_", ""); Some(quote! { res.#setter_name(#field_name)?; }) } else { None @@ -442,7 +442,7 @@ impl Config<'_> { fn render_signal(&self, signal: &Signal, dbc: &Dbc, msg: &Message) -> Result { let signal_name = &signal.name; let fn_name = signal.field_name(); - let fn_name_raw = signal.field_name2("", "_raw"); + let fn_name_raw = signal.field_name_ext("", "_raw"); // Build signal getter doc as doc comment and parse into tokens let mut doc = format!("/// {signal_name}\n"); @@ -559,7 +559,7 @@ impl Config<'_> { } fn render_set_signal(&self, signal: &Signal, msg: &Message) -> Result { - let setter_name = signal.field_name2("set_", ""); + let setter_name = signal.field_name_ext("set_", ""); // To avoid accidentally changing the multiplexor value without changing // the signals accordingly this fn is kept private for multiplexors. @@ -610,7 +610,7 @@ fn render_set_signal_multiplexer( ) -> Result { let enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?; let setter_name = multiplexed_enum_variant_wrapper_setter_name(switch_index); - let multiplexor_setter = multiplexor.field_name2("set_", ""); + let multiplexor_setter = multiplexor.field_name_ext("set_", ""); let switch_index_lit = LitInt::new(&switch_index.to_string(), Span::call_site()); let doc = format!("/// Set value of {}", multiplexor.name).tokens()?; @@ -631,7 +631,7 @@ fn render_set_signal_multiplexer( impl Config<'_> { fn render_multiplexor_signal(&self, signal: &Signal, msg: &Message) -> Result { let field = signal.field_name(); - let field_raw = signal.field_name2("", "_raw"); + let field_raw = signal.field_name_ext("", "_raw"); let typ = ValType::from_signal(signal); let typ_ident = typ.ident(); let enum_type = multiplex_enum_name(msg, signal)?; diff --git a/src/utils.rs b/src/utils.rs index 84a818b..1ddd32b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -64,7 +64,7 @@ pub trait SignalExt { fn field_name(&self) -> Ident { sanitize_name(self.get_name(), ToSnakeCase::to_snake_case).ident() } - fn field_name2(&self, prefix: &str, suffix: &str) -> Ident { + fn field_name_ext(&self, prefix: &str, suffix: &str) -> Ident { format!( "{prefix}{}{suffix}", sanitize_name(self.get_name(), ToSnakeCase::to_snake_case) From 4bb31ff4240416d296cf9bb5b565f4811314fd14 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 29 Jan 2026 18:07:05 -0500 Subject: [PATCH 10/11] wip --- src/lib.rs | 105 +++++++++++++++++++++------------------------------ src/utils.rs | 11 +++++- 2 files changed, 54 insertions(+), 62 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 05d7fe0..af1c7ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,17 +22,18 @@ use can_dbc::ByteOrder::{BigEndian, LittleEndian}; use can_dbc::MultiplexIndicator::{MultiplexedSignal, Multiplexor, Plain}; use can_dbc::ValueType::Signed; use can_dbc::{Dbc, Message, MessageId, Signal, Transmitter, ValDescription, ValueDescription}; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; -use syn::{parse2, LitFloat, LitInt}; +use syn::parse2; use typed_builder::TypedBuilder; pub use crate::feature_config::FeatureConfig; use crate::signal_type::ValType; use crate::utils::{ - enum_name, enum_variant_name, is_integer, multiplex_enum_name, multiplexed_enum_variant_name, - multiplexed_enum_variant_wrapper_name, multiplexed_enum_variant_wrapper_setter_name, - MessageExt as _, SignalExt as _, ToIdent as _, Tokens as _, + enum_name, enum_variant_name, is_integer, lit_float, lit_int, multiplex_enum_name, + multiplexed_enum_variant_name, multiplexed_enum_variant_wrapper_name, + multiplexed_enum_variant_wrapper_setter_name, MessageExt as _, SignalExt as _, ToIdent as _, + Tokens as _, }; /// Code generator configuration. See module-level docs for an example. @@ -232,12 +233,12 @@ impl Config<'_> { let message_id = match msg.id { MessageId::Standard(id) => { writeln!(doc, "/// - Standard ID: {id} (0x{id:x})")?; - let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); + let id_lit = lit_int(format!("{id:#x}")); quote! { Id::Standard(unsafe { StandardId::new_unchecked(#id_lit) }) } } MessageId::Extended(id) => { writeln!(doc, "/// - Extended ID: {id} (0x{id:x})")?; - let id_lit = LitInt::new(&format!("{id:#x}"), Span::call_site()); + let id_lit = lit_int(format!("{id:#x}")); quote! { Id::Extended(unsafe { ExtendedId::new_unchecked(#id_lit) }) } } }; @@ -254,8 +255,7 @@ impl Config<'_> { let struct_doc = doc.tokens().context("message doc to tokens")?; let msg_type = msg.type_name(); - let msg_size = msg.size as usize; - let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); + let msg_size_lit = lit_int(msg.size as usize); // Struct attributes let serde_serialize = self.impl_serde.attr("e! { derive(Serialize) }); @@ -490,7 +490,7 @@ impl Config<'_> { let match_arms: Vec<_> = variant_infos .iter() .map(|info| { - let literal = LitInt::new(&info.value.to_string(), Span::call_site()); + let literal = lit_int(info.value); let variant = info.base_name.ident(); match info.dup_type { DuplicateType::Unique => { @@ -611,7 +611,7 @@ fn render_set_signal_multiplexer( let enum_variant = multiplexed_enum_variant_name(msg, multiplexor, switch_index)?; let setter_name = multiplexed_enum_variant_wrapper_setter_name(switch_index); let multiplexor_setter = multiplexor.field_name_ext("set_", ""); - let switch_index_lit = LitInt::new(&switch_index.to_string(), Span::call_site()); + let switch_index_lit = lit_int(switch_index); let doc = format!("/// Set value of {}", multiplexor.name).tokens()?; @@ -674,7 +674,7 @@ impl Config<'_> { let match_arms: Vec<_> = multiplexer_indexes.iter().map(|idx| { let multiplexed_wrapper_name = multiplexed_enum_variant_wrapper_name(*idx); let multiplexed_name = multiplexed_enum_variant_name(msg, signal, *idx).unwrap(); - let idx_lit = LitInt::new(&idx.to_string(), Span::call_site()); + let idx_lit = lit_int(idx); quote! { #idx_lit => Ok(#enum_type::#multiplexed_wrapper_name(#multiplexed_name { raw: self.raw })) } @@ -764,14 +764,14 @@ fn read_fn_with_type(signal: &Signal, msg: &Message, typ: ValType) -> Result { let (start, end) = le_start_end_bit(signal, msg)?; - let start_lit = LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = lit_int(start); + let end_lit = lit_int(end); quote! { self.raw.view_bits::()[#start_lit..#end_lit].load_le::<#typ_ident>() } } BigEndian => { let (start, end) = be_start_end_bit(signal, msg)?; - let start_lit = LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = lit_int(start); + let end_lit = lit_int(end); quote! { self.raw.view_bits::()[#start_lit..#end_lit].load_be::<#typ_ident>() } } }) @@ -791,10 +791,8 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { } } ValType::F32 => { - let factor = signal.factor; - let offset = signal.offset; - let factor_lit = LitFloat::new(&format!("{factor}_f32"), Span::call_site()); - let offset_lit = LitFloat::new(&format!("{offset}_f32"), Span::call_site()); + let factor_lit = lit_float(format!("{}_f32", signal.factor)); + let offset_lit = lit_float(format!("{}_f32", signal.offset)); quote! { let signal = #read_expr; let factor = #factor_lit; @@ -803,9 +801,6 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { } } _ => { - let factor = signal.factor; - let factor_lit = LitFloat::new(&factor.to_string(), Span::call_site()); - let signal_cast = if Some(typ) == ValType::from_signal_uint(signal).unsigned_to_signed() { quote! { let signal = signal as #typ_ident; } @@ -813,9 +808,9 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { quote! {} }; + let factor_lit = lit_float(signal.factor); if signal.offset >= 0.0 { - let offset = signal.offset; - let offset_lit = LitFloat::new(&offset.to_string(), Span::call_site()); + let offset_lit = lit_float(signal.offset); quote! { let signal = #read_expr; let factor = #factor_lit; @@ -823,8 +818,7 @@ fn signal_from_payload(signal: &Signal, msg: &Message) -> Result { #typ_ident::from(signal).saturating_mul(factor).saturating_add(#offset_lit) } } else { - let offset_abs = signal.offset.abs(); - let offset_lit = LitFloat::new(&offset_abs.to_string(), Span::call_site()); + let offset_lit = lit_float(signal.offset.abs()); quote! { let signal = #read_expr; let factor = #factor_lit; @@ -845,10 +839,8 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { quote! { let value = value as u8; } } ValType::F32 => { - let factor = signal.factor; - let offset = signal.offset; - let factor_lit = LitFloat::new(&format!("{factor}_f32"), Span::call_site()); - let offset_lit = LitFloat::new(&format!("{offset}_f32"), Span::call_site()); + let factor_lit = lit_float(format!("{}_f32", signal.factor)); + let offset_lit = lit_float(format!("{}_f32", signal.offset)); let int_typ = ValType::from_signal_int(signal); let int_typ_ident = int_typ.ident(); quote! { @@ -858,14 +850,12 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { } } _ => { - let factor = signal.factor; - let factor_lit = LitFloat::new(&factor.to_string(), Span::call_site()); + let factor_lit = lit_float(signal.factor); let int_typ = ValType::from_signal_int(signal); let int_typ_ident = int_typ.ident(); if signal.offset >= 0.0 { - let offset = signal.offset; - let offset_lit = LitFloat::new(&offset.to_string(), Span::call_site()); + let offset_lit = lit_float(signal.offset); quote! { let factor = #factor_lit; let value = value.checked_sub(#offset_lit) @@ -873,8 +863,7 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let value = (value / factor) as #int_typ_ident; } } else { - let offset_abs = signal.offset.abs(); - let offset_lit = LitFloat::new(&offset_abs.to_string(), Span::call_site()); + let offset_lit = lit_float(signal.offset.abs()); quote! { let factor = #factor_lit; let value = value.checked_add(#offset_lit) @@ -896,14 +885,14 @@ fn signal_to_payload(signal: &Signal, msg: &Message) -> Result { let store_expr = match signal.byte_order { LittleEndian => { let (start, end) = le_start_end_bit(signal, msg)?; - let start_lit = LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = lit_int(start); + let end_lit = lit_int(end); quote! { self.raw.view_bits_mut::()[#start_lit..#end_lit].store_le(value); } } BigEndian => { let (start, end) = be_start_end_bit(signal, msg)?; - let start_lit = LitInt::new(&start.to_string(), Span::call_site()); - let end_lit = LitInt::new(&end.to_string(), Span::call_site()); + let start_lit = lit_int(start); + let end_lit = lit_int(end); quote! { self.raw.view_bits_mut::()[#start_lit..#end_lit].store_be(value); } } }; @@ -972,14 +961,11 @@ impl Config<'_> { } } ValType::F32 => { - let val = LitFloat::new( - &format!("{}_f32", info.value), - Span::call_site(), - ); + let val = lit_float(format!("{}_f32", info.value)); quote! { #val } } _ => { - let val = LitInt::new(&info.value.to_string(), Span::call_site()); + let val = lit_int(info.value); quote! { #val } } }; @@ -1229,8 +1215,7 @@ impl Config<'_> { .map(|(switch_index, signals)| { let struct_name = multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?; - let msg_size = msg.size as usize; - let msg_size_lit = LitInt::new(&msg_size.to_string(), Span::call_site()); + let msg_size_lit = lit_int(msg.size as usize); let signal_impls: Result> = signals .iter() @@ -1372,18 +1357,18 @@ impl Config<'_> { let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); let trait_def = self.impl_arbitrary.if_cfg(quote! { - #allow_dead_code - trait UnstructuredFloatExt { - fn arbitrary_f32(&mut self) -> arbitrary::Result; - } + #allow_dead_code + trait UnstructuredFloatExt { + fn arbitrary_f32(&mut self) -> arbitrary::Result; + } }); let trait_impl = self.impl_arbitrary.if_cfg(quote! { - impl UnstructuredFloatExt for arbitrary::Unstructured<'_> { - fn arbitrary_f32(&mut self) -> arbitrary::Result { - Ok(f32::from_bits(u32::arbitrary(self)?)) + impl UnstructuredFloatExt for arbitrary::Unstructured<'_> { + fn arbitrary_f32(&mut self) -> arbitrary::Result { + Ok(f32::from_bits(u32::arbitrary(self)?)) + } } - } }); quote! { @@ -1471,18 +1456,17 @@ fn allow_lints() -> TokenStream { /// otherwise generates a float literal with the integer type suffix. fn generate_value_literal(value: f64, typ: ValType) -> TokenStream { if typ == ValType::F32 { - let lit = LitFloat::new(&format!("{value}_f32"), Span::call_site()); + let lit = lit_float(format!("{value}_f32")); quote! { #lit } } else { let typ_str = typ.to_string().to_lowercase(); // Check if value is an integer and fits in i64 range if is_integer(value) && value >= i64::MIN as f64 && value <= i64::MAX as f64 { - let val = value as i64; - let lit = LitInt::new(&format!("{val}_{typ_str}"), Span::call_site()); + let lit = lit_int(format!("{}_{typ_str}", value as i64)); quote! { #lit } } else { // Use float literal with integer type suffix for fractional/overflow values - let lit = LitFloat::new(&format!("{value}_{typ_str}"), Span::call_site()); + let lit = lit_float(format!("{value}_{typ_str}")); quote! { #lit } } } @@ -1491,7 +1475,6 @@ fn generate_value_literal(value: f64, typ: ValType) -> TokenStream { /// Generate `[allow(dead_code)]` attribute if needed fn allow_dead_code_tokens(allow: bool) -> Option { if allow { - use quote::quote; Some(quote! { #[allow(dead_code)] }) } else { None diff --git a/src/utils.rs b/src/utils.rs index 1ddd32b..aded1ce 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,8 +4,9 @@ use anyhow::{anyhow, ensure, Result}; use can_dbc::MultiplexIndicator::Multiplexor; use can_dbc::{Message, Signal}; use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase}; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, IdentFragment}; +use syn::{LitFloat, LitInt}; use crate::keywords; @@ -147,3 +148,11 @@ impl Tokens for String { self.as_str().tokens() } } + +pub fn lit_int(value: T) -> LitInt { + LitInt::new(&value.to_string(), Span::call_site()) +} + +pub fn lit_float(value: T) -> LitFloat { + LitFloat::new(&value.to_string(), Span::call_site()) +} From 388a6880bb2a83775500eab4dbae205fe5c74b40 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 29 Jan 2026 18:32:23 -0500 Subject: [PATCH 11/11] wip --- src/lib.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index af1c7ea..c91f78e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,7 @@ impl Config<'_> { } fn render_root_enum(&self, dbc: &Dbc) -> TokenStream { - let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code = self.allow_dead_code(); let debug_derive = self.impl_debug.attr("e! { derive(Debug) }); let defmt_derive = self.impl_defmt.attr("e! { derive(defmt::Format) }); let serde_derives = self @@ -380,7 +380,7 @@ impl Config<'_> { .transpose()?; let allow_lints = allow_lints(); - let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code = self.allow_dead_code(); let new_fn_doc = format!("/// Construct new {} from values", msg.name).tokens()?; @@ -711,6 +711,15 @@ impl Config<'_> { #(#set_multiplexer_fns)* }) } + + /// Generate `[allow(dead_code)]` attribute if needed + fn allow_dead_code(&self) -> Option { + if self.allow_dead_code { + Some(quote! { #[allow(dead_code)] }) + } else { + None + } + } } fn be_start_end_bit(signal: &Signal, msg: &Message) -> Result<(u64, u64)> { @@ -923,7 +932,7 @@ impl Config<'_> { let doc = format!(" Defined values for {signal_name}"); let allow_lints = allow_lints(); - let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code = self.allow_dead_code(); let debug_derive = self.impl_debug.attr("e! { derive(Debug) }); let defmt_derive = self.impl_defmt.attr("e! { derive(defmt::Format) }); let serde_serialize = self.impl_serde.attr("e! { derive(Serialize) }); @@ -1208,7 +1217,7 @@ impl Config<'_> { // Generate structs for each multiplexed signal let allow_lints_outer = allow_lints(); - let allow_dead_code_outer = allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code_outer = self.allow_dead_code(); let struct_defs: Result> = multiplexed_signals .iter() @@ -1273,7 +1282,7 @@ impl Config<'_> { fn render_arbitrary(&self, msg: &Message) -> TokenStream { let allow_lints = allow_lints(); - let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code = self.allow_dead_code(); let msg_type = msg.type_name(); let filtered_signals: Vec<&Signal> = msg @@ -1354,7 +1363,7 @@ impl Config<'_> { } fn render_arbitrary_helpers(&self) -> TokenStream { - let allow_dead_code = allow_dead_code_tokens(self.allow_dead_code); + let allow_dead_code = self.allow_dead_code(); let trait_def = self.impl_arbitrary.if_cfg(quote! { #allow_dead_code @@ -1471,12 +1480,3 @@ fn generate_value_literal(value: f64, typ: ValType) -> TokenStream { } } } - -/// Generate `[allow(dead_code)]` attribute if needed -fn allow_dead_code_tokens(allow: bool) -> Option { - if allow { - Some(quote! { #[allow(dead_code)] }) - } else { - None - } -}