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..fc97d92 100644 --- a/src/feature_config.rs +++ b/src/feature_config.rs @@ -1,7 +1,5 @@ -use std::fmt::Display; -use std::io::Write; - -use anyhow::{Error, Result}; +use proc_macro2::TokenStream; +use quote::quote; /// Configuration for including features in the code generator. /// @@ -20,31 +18,24 @@ 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 attr(&self, tokens: &TokenStream) -> TokenStream { match self { - FeatureConfig::Always => writeln!(w, "#[{attr}]")?, - FeatureConfig::Gated(gate) => writeln!(w, "#[cfg_attr(feature = {gate:?}, {attr})]")?, - FeatureConfig::Never => {} + FeatureConfig::Always => quote! { #[#tokens] }, + FeatureConfig::Gated(gate) => quote! { #[cfg_attr(feature = #gate, #tokens)] }, + FeatureConfig::Never => quote! {}, } - Ok(()) } - pub(crate) fn fmt_cfg>( - &self, - mut w: W, - f: impl FnOnce(W) -> Result<(), E>, - ) -> Result<()> { + /// Generate a token stream optionally wrapped in a cfg attribute + pub(crate) fn if_cfg(&self, tokens: TokenStream) -> TokenStream { 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::Always => tokens, + FeatureConfig::Gated(gate) => quote! { + #[cfg(feature = #gate)] + #tokens + }, + FeatureConfig::Never => quote! {}, } - - f(w).map_err(Into::into) } } diff --git a/src/lib.rs b/src/lib.rs index 5af28d5..c91f78e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,45 +9,33 @@ mod feature_config; mod keywords; -mod pad; mod signal_type; mod utils; - use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::fmt::Write as _; 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::TokenStream; +use quote::quote; +use syn::parse2; 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 _, + 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 _, }; -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, -)]"; - /// Code generator configuration. See module-level docs for an example. #[derive(TypedBuilder)] #[non_exhaustive] @@ -99,664 +87,639 @@ 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 { - anyhow!("{msg}: {e:#?}") + anyhow!("Could not parse dbc file: {e:#?}") } else { - anyhow!("{msg}") + anyhow!("Could not parse dbc file") } })?; 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}};") - })?; + let dbc_name = &self.dbc_name; + let dbc_version = &dbc.version.0; + 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; + /// The version of the DBC file this code was generated from + #[allow(dead_code)] + pub const DBC_FILE_VERSION: &str = #dbc_version; + + }; - writeln!(w)?; + 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 + .if_cfg(quote! { use arbitrary::Arbitrary; }), + ); + use_statements.extend(self.impl_serde.if_cfg(quote! { + use serde::{Serialize, Deserialize}; + })); - self.render_dbc(&mut w, &dbc) + 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(); - 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)?; + Ok(quote! { + #header + #use_statements + #dbc_content - Ok(()) + /// 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) -> TokenStream { + 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 + .impl_serde + .attr("e! { derive(Serialize, Deserialize) }); + let allow_lints = allow_lints(); + + let variants: Vec<_> = get_relevant_messages(dbc) + .map(|msg| { + let msg_type = 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 = 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)) } + } else { + quote! { + let res = match id { + #(#from_can_arms,)* + id => return Err(CanError::UnknownMessageId(id)), + }; + Ok(res) } - } - writeln!(w, "}}")?; - writeln!(w)?; + }; - 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 {{", - )?; + 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)?; - - Ok(()) } - 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 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 = 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 = lit_int(format!("{id:#x}")); + quote! { Id::Extended(unsafe { ExtendedId::new_unchecked(#id_lit) }) } + } + }; + writeln!(doc, "/// - Size: {} bytes", msg.size)?; if let Transmitter::NodeName(transmitter) = &msg.transmitter { - writeln!(w, "/// - Transmitter: {transmitter}")?; + writeln!(doc, "/// - Transmitter: {transmitter}")?; } if let Some(comment) = dbc.message_comment(msg.id) { - writeln!(w, "///")?; + writeln!(doc, "///")?; for line in comment.trim().lines() { - writeln!(w, "/// {line}")?; + writeln!(doc, "/// {line}")?; } } - 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)?; + let struct_doc = doc.tokens().context("message doc to tokens")?; - 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)?; + let msg_type = msg.type_name(); + let msg_size_lit = lit_int(msg.size as usize); - for signal in &msg.signals { + // Struct attributes + 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 + .attr("e! { serde(with = "serde_bytes") }); + + // 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};")?; + if typ == ValType::Bool { + None + } else { + 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); + + Some(quote! { + pub const #min_name: #typ_ident = #min_lit; + pub const #max_name: #typ_ident = #max_lit; + }) } - } - 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 = signal.field_name(); + let typ = ValType::from_signal(signal); + let typ_ident = typ.ident(); + Some(quote! { #field_name: #typ_ident }) + } else { + None } - writeln!(w, "Ok(res)")?; - } - writeln!(w, "}}")?; - writeln!(w)?; + }) + .collect(); - 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")?; - } - 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)?; + let new_fn_setters: Vec<_> = msg + .signals + .iter() + .filter_map(|signal| { + if matches!(signal.multiplexer_indicator, Plain | Multiplexor) { + let field_name = signal.field_name(); + let setter_name = signal.field_name_ext("set_", ""); + 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.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 + .value_descriptions + .iter() + .filter_map(|x| { + if let ValueDescription::Signal { + message_id, + name, + value_descriptions, + } = x + { + if *message_id != msg.id { + return None; } - MultiplexedSignal(_) | MultiplexorAndMultiplexedSignal(_) => {} + dbc.signal_by_name(*message_id, name) + .map(|v| self.write_enum(v, msg, value_descriptions.as_slice())) + } else { + None } + }) + .collect(); + + // 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 = self.allow_dead_code(); + + let new_fn_doc = format!("/// Construct new {} from values", msg.name).tokens()?; + + Ok(quote! { + #struct_doc + #[derive(Clone, Copy)] + #serde_serialize + #serde_deserialize + pub struct #msg_type { + #serde_with + raw: [u8; #msg_size_lit], } - } - writeln!(w, "}}")?; - writeln!(w)?; + #allow_lints + #allow_dead_code + impl #msg_type { + pub const MESSAGE_ID: embedded_can::Id = #message_id; - 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, "}}")?; - } - 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; + #(#signal_constants)* + + #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) } - dbc.signal_by_name(*message_id, name) - .map(|v| (v, value_descriptions)) - } else { - None + + /// Access message payload raw value + pub fn raw(&self) -> &[u8; #msg_size_lit] { + &self.raw + } + + #(#signal_impls)* } - }); - 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); + impl core::convert::TryFrom<&[u8]> for #msg_type { + type Error = CanError; - if let Some(multiplexor_signal) = multiplexor_signal { - self.render_multiplexor_enums(w, dbc, msg, multiplexor_signal)?; - } + #[inline(always)] + fn try_from(payload: &[u8]) -> Result { + if payload.len() != #msg_size_lit { + return Err(CanError::InvalidPayloadSize); + } + let mut raw = [0u8; #msg_size_lit]; + raw.copy_from_slice(&payload[..#msg_size_lit]); + Ok(Self { raw }) + } + } - Ok(()) + #embedded_can_impl + #debug_impl + #defmt_impl + #arbitrary_impl + #(#enums_for_this_message)* + #multiplexor_enums + }) } - fn render_signal( - &self, - w: &mut impl Write, - signal: &Signal, - dbc: &Dbc, - msg: &Message, - ) -> Result<()> { - writeln!(w, "/// {}", signal.name)?; + 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_name_ext("", "_raw"); + + // Build signal getter doc as doc comment and parse into tokens + let mut doc = format!("/// {signal_name}\n"); if let Some(comment) = dbc.signal_comment(msg.id, &signal.name) { - writeln!(w, "///")?; + doc.push_str("///\n"); for line in comment.trim().lines() { - writeln!(w, "/// {line}")?; + let _ = writeln!(doc, "/// {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 _ = writeln!( + doc, + "\ +/// +/// - Min: {} +/// - Max: {} +/// - Unit: {:?} +/// - Receivers: {}", + signal.min, + signal.max, + signal.unit, + signal.receivers.join(", ") + ); + let signal_doc = doc.tokens()?; + + let typ = ValType::from_signal(signal); + 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); 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(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 = lit_int(info.value); + let variant = info.base_name.ident(); + 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 { + 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)?; + }; - self.render_set_signal(w, signal, msg)?; + 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 = raw_doc_text.tokens()?; + let fn_body = signal_from_payload(signal, msg).context("signal from payload")?; + let setter = self.render_set_signal(signal, msg)?; + + Ok(quote! { + #signal_doc + #getter + + #fn_name_doc + #[inline(always)] + pub fn #fn_name_raw(&self) -> #typ { + #fn_body + } - Ok(()) + #setter + }) } - 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)]")?; + fn render_set_signal(&self, signal: &Signal, msg: &Message) -> Result { + 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. let visibility = if signal.multiplexer_indicator == Multiplexor { - "" + quote! {} } else { - "pub " + quote! { pub } }; - let field = signal.field_name(); + let setter_doc = format!(" Set value of {}", signal.name); let typ = ValType::from_signal(signal); - writeln!( - w, - "{visibility}fn set_{field}(&mut self, value: {typ}) -> Result<(), CanError> {{", - )?; + let typ_ident = typ.ident(); + let msg_type = msg.type_name(); - { - let mut w = PadAdapter::wrap(w); + // Range check logic + let range_check = if signal.size == 1 { + quote! {} + } else { + let min_lit = generate_value_literal(signal.min, typ); + let max_lit = generate_value_literal(signal.max, typ); - if signal.size != 1 { - if let FeatureConfig::Gated(gate) = self.check_ranges { - writeln!(w, r"#[cfg(feature = {gate:?})]")?; + let check_code = quote! { + if value < #min_lit || #max_lit < value { + return Err(CanError::ParameterOutOfRange { message_id: #msg_type::MESSAGE_ID }); } + }; - 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 }});", - )?; - } - - writeln!(w, r"}}")?; - } - } - signal_to_payload(&mut w, signal, msg).context("signal to payload")?; - } + self.check_ranges.if_cfg(check_code) + }; - writeln!(w, "}}")?; - writeln!(w)?; + let signal_to_payload_body = signal_to_payload(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 = 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 = lit_int(switch_index); + + let doc = format!("/// Set value of {}", multiplexor.name).tokens()?; + + Ok(quote! { + #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)?; + }) +} +impl Config<'_> { + fn render_multiplexor_signal(&self, signal: &Signal, msg: &Message) -> Result { let field = signal.field_name(); - let typ = multiplex_enum_name(msg, signal)?; - writeln!(w, "pub fn {field}(&mut self) -> Result<{typ}, CanError> {{")?; + 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)?; + + // Build raw doc as doc comment string and parse into tokens + let raw_doc_text = format!( + "\ +/// 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 = raw_doc_text.tokens()?; + + let signal_from_payload_body = signal_from_payload(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 = multiplexed_enum_variant_wrapper_name(*idx); + let multiplexed_name = multiplexed_enum_variant_name(msg, signal, *idx).unwrap(); + let idx_lit = lit_int(idx); + 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)?; + let msg_type = msg.type_name(); - for switch_index in multiplexer_indexes { - render_set_signal_multiplexer(w, signal, msg, switch_index)?; - } + let setter = self.render_set_signal(signal, msg)?; - Ok(()) - } - - 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); - - // 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! { + #field_raw_doc + #[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(()) + /// 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)> { @@ -805,121 +768,248 @@ 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(signal: &Signal, msg: &Message, typ: ValType) -> Result { + let typ_ident = typ.ident(); + Ok(match signal.byte_order { + LittleEndian => { + let (start, end) = le_start_end_bit(signal, msg)?; + 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 = lit_int(start); + let end_lit = lit_int(end); + quote! { self.raw.view_bits::()[#start_lit..#end_lit].load_be::<#typ_ident>() } + } + }) +} + +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); - match typ { + let typ_ident = typ.ident(); + + 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_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; + 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 signal_cast = if Some(typ) == ValType::from_signal_uint(signal).unsigned_to_signed() + { + quote! { let signal = signal as #typ_ident; } + } else { + quote! {} + }; + let factor_lit = lit_float(signal.factor); if signal.offset >= 0.0 { - writeln!( - w, - "{typ}::from(signal).saturating_mul(factor).saturating_add({})", - signal.offset, - )?; + let offset_lit = lit_float(signal.offset); + 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_lit = lit_float(signal.offset.abs()); + 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(signal: &Signal, msg: &Message) -> Result { let typ = ValType::from_signal(signal); - match typ { + let msg_type = 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_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! { + 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_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 { - writeln!(w, "let value = value.checked_sub({})", signal.offset)?; + let offset_lit = lit_float(signal.offset); + 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_lit = lit_float(signal.offset.abs()); + 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 = uint_typ.ident(); + 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 = 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)?; - writeln!( - w, - r"self.raw.view_bits_mut::()[{start}..{end}].store_be(value);", - )?; + 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); } } - } + }; - 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], + ) -> TokenStream { + let type_name = enum_name(msg, signal); + let signal_ty = ValType::from_signal(signal); + 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); + + let signal_name = &signal.name; + let doc = format!(" Defined values for {signal_name}"); + + let allow_lints = allow_lints(); + 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) }); + let serde_deserialize = self.impl_serde.attr("e! { derive(Deserialize) }); + + // Generate enum variants + let enum_variants: Vec<_> = variant_infos + .iter() + .filter_map(|info| { + let variant = info.base_name.ident(); + match info.dup_type { + DuplicateType::Unique => Some(quote! { #variant }), + DuplicateType::FirstDuplicate => { + let value_type = info.value_type.ident(); + 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 = info.base_name.ident(); + 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 = lit_float(format!("{}_f32", info.value)); + quote! { #val } + } + _ => { + let val = lit_int(info.value); + quote! { #val } + } + }; + Some(quote! { #type_name::#variant => #literal_value }) + } + DuplicateType::FirstDuplicate => Some(quote! { #type_name::#variant(v) => v }), + DuplicateType::Duplicate => None, + } + }) + .collect(); + + 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 +1067,338 @@ 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) -> TokenStream { + let msg_type = 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()")?; + + 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 + } } - writeln!(w, r"}} else {{")?; - { - let mut w = PadAdapter::wrap(&mut w); - writeln!(w, r#"f.debug_tuple("{typ}").field(&self.raw).finish()"#)?; + }; + + self.impl_embedded_can_frame.if_cfg(impl_tokens) + } +} + +fn render_debug_impl(msg: &Message) -> TokenStream { + 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_ident = signal.field_name(); + let field_name = field_ident.to_string(); + quote! { .field(#field_name, &self.#field_ident()) } + }) + .collect(); + + 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, "}}")?; } - 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)?; - } - } - } - writeln!(w, r#" }}}}","#)?; +fn render_defmt_impl(msg: &Message) -> TokenStream { + let msg_type = msg.type_name(); + let typ_name = msg_type.to_string(); - for signal in &msg.signals { - if signal.multiplexer_indicator == Plain { - writeln!(w, "self.{}(),", signal.field_name())?; - } - } - writeln!(w, r");")?; + 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 { + let _ = write!(format_str, " {}={{:?}}", signal.name); + } + format_str.push_str(" }}}}"); + + // Build field accessors + let field_accessors: Vec<_> = plain_signals + .iter() + .map(|signal| { + let field_ident = signal.field_name(); + quote! { self.#field_ident() } + }) + .collect(); + + quote! { + impl defmt::Format for #msg_type { + fn format(&self, f: defmt::Formatter) { + defmt::write!( + f, + #format_str, + #(#field_accessors,)* + ); } - writeln!(w, "}}")?; } } - writeln!(w, "}}")?; - writeln!(w)?; - Ok(()) } 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)?; - - 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)")?; + let doc = format!(" Defined values for multiplexed signal {}", msg.name); let enum_name = multiplex_enum_name(msg, multiplexor_signal)?; - writeln!(w, "pub enum {enum_name} {{")?; - { - 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)?; - } + // Generate enum variants + let enum_variants = multiplexed_signals + .keys() + .map(|switch_index| { + let wrapper_name = multiplexed_enum_variant_wrapper_name(*switch_index); + let variant_name = + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?; - writeln!(w, "}}")?; - writeln!(w)?; - } + Ok(quote! { #wrapper_name(#variant_name) }) + }) + .collect::>>()?; - Ok(()) - } + // Generate structs for each multiplexed signal + let allow_lints_outer = allow_lints(); + let allow_dead_code_outer = self.allow_dead_code(); - 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), - )?; - } + let struct_defs: Result> = multiplexed_signals + .iter() + .map(|(switch_index, signals)| { + let struct_name = + multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?; + let msg_size_lit = lit_int(msg.size as usize); - let args: Vec = filtered_signals + let signal_impls: Result> = signals .iter() - .map(|signal| signal.field_name()) + .map(|signal| { + self.render_signal(signal, dbc, msg) + .with_context(|| format!("write signal impl `{}`", signal.name)) + }) .collect(); + let signal_impls = signal_impls?; + + 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.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(); + + 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] + } - writeln!( - w, - "{typ}::new({args}).map_err(|_| arbitrary::Error::IncorrectFormat)", - typ = msg.type_name(), - args = args.join(","), - )?; + #allow_lints_inner2 + #allow_dead_code_inner2 + impl #struct_name { + pub fn new() -> Self { + Self { raw: [0u8; #msg_size_lit] } + } + + #(#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) -> TokenStream { + let allow_lints = allow_lints(); + let allow_dead_code = self.allow_dead_code(); + let msg_type = msg.type_name(); + + let filtered_signals: Vec<&Signal> = msg + .signals + .iter() + .filter(|v| matches!(v.multiplexer_indicator, Plain | Multiplexor)) + .collect(); -impl core::fmt::Display for CanError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{self:?}") + let u_param = if filtered_signals.is_empty() { + quote! { _u } + } else { + quote! { u } + }; + + // Generate signal bindings + let signal_bindings: Vec<_> = filtered_signals + .iter() + .map(|signal| { + let field_name = signal.field_name(); + let value_expr = signal_to_arbitrary(signal); + quote! { let #field_name = #value_expr; } + }) + .collect(); + + // Generate function arguments + let args: Vec<_> = filtered_signals + .iter() + .map(|signal| signal.field_name()) + .collect(); + + 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 {{}}") - }) + fn render_error(&self) -> TokenStream { + let error_impl = self.impl_error.if_cfg(quote! { + impl core::error::Error for CanError {} + }); + + 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) -> TokenStream { + let allow_dead_code = 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.if_cfg(quote! { + #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)?)) + } + } + }); + + quote! { + #trait_def + #trait_impl + } } } -fn signal_to_arbitrary(signal: &Signal) -> String { +fn signal_to_arbitrary(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 = typ.ident(); + quote! { u.int_in_range(#min..=#max)? as #typ_ident } } } } @@ -1327,13 +1415,14 @@ 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 = parse2(tokens).context("Failed to parse generated TokenStream as Rust code")?; Ok(prettyplease::unparse(&file)) } @@ -1353,11 +1442,41 @@ 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}")?; +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 = 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 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 = lit_float(format!("{value}_{typ_str}")); + quote! { #lit } } - 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/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..aded1ce 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,11 +1,16 @@ -use anyhow::{ensure, Result}; +use std::fmt::Display; + +use anyhow::{anyhow, ensure, Result}; use can_dbc::MultiplexIndicator::Multiplexor; use can_dbc::{Message, Signal}; -use heck::{ToPascalCase, ToSnakeCase}; +use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{format_ident, IdentFragment}; +use syn::{LitFloat, LitInt}; 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 @@ -13,14 +18,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", @@ -29,14 +38,15 @@ pub fn multiplex_enum_name(msg: &Message, multiplexor: &Signal) -> Result Result { +) -> Result { ensure!( matches!(multiplexor.multiplexer_indicator, Multiplexor), "signal {multiplexor:?} is not the multiplexor", @@ -46,46 +56,103 @@ pub fn multiplexed_enum_variant_name( "{}{}M{switch_index}", msg.name.to_pascal_case(), multiplexor.name.to_pascal_case(), - )) + ) + .ident()) } pub trait SignalExt { - fn field_name(&self) -> String; + fn get_name(&self) -> &str; + fn field_name(&self) -> Ident { + sanitize_name(self.get_name(), ToSnakeCase::to_snake_case).ident() + } + fn field_name_ext(&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) -> Ident { + 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, + ) + .ident() + } } 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 } } 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, "X", ToPascalCase::to_pascal_case) + fn type_name(&self) -> Ident { + sanitize_name(&self.name, ToPascalCase::to_pascal_case).ident() } } -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 ident(&self) -> Ident; +} + +impl ToIdent for T { + fn ident(&self) -> Ident { + format_ident!("{self}") + } +} + +/// A trait to convert a type to a proc-macro Ident +pub trait Tokens { + fn tokens(&self) -> Result; +} + +impl Tokens for &str { + fn tokens(&self) -> Result { + self.parse() + .map_err(|e| anyhow!("Unable to parse {self}\n{e}")) + } +} + +impl Tokens for String { + fn tokens(&self) -> Result { + 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()) +} diff --git a/tests/snapshots.rs b/tests/snapshots.rs index d2f83fb..7f63e45 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); }