From a9f46c549e769f67883e2b71f02b56c3cf346797 Mon Sep 17 00:00:00 2001 From: Zachary Sunforge Date: Wed, 10 Jul 2024 22:06:16 -0700 Subject: [PATCH] Switched custom quantities to using a macro for generation. --- Cargo.toml | 8 ++- generate-quantity/Cargo.toml | 18 +++++ generate-quantity/src/lib.rs | 132 +++++++++++++++++++++++++++++++++++ src/quantity/irradiance.rs | 24 ++----- src/quantity/mod.rs | 65 ++++------------- src/quantity/resistance.rs | 22 +----- src/quantity/temperature.rs | 86 +++-------------------- src/quantity/voltage.rs | 47 ++----------- src/quantity/volume_rate.rs | 24 ++----- 9 files changed, 197 insertions(+), 229 deletions(-) create mode 100644 generate-quantity/Cargo.toml create mode 100644 generate-quantity/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 60fd6ae..8645f17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,12 @@ members = [ # Device types "node", "commander", + # Meta + "generate-quantity" ] [workspace.package] -version = "0.3.5" +version = "0.3.6" edition = "2021" repository = "https://git.bfpower.io/BFPOWER/physical" readme = "README.md" @@ -89,6 +91,8 @@ version = "2.0.*" features = ["extra-traits", "parsing"] [workspace.dependencies.quote] version = "1.0.*" +[workspace.dependencies.proc-macro2] +version = "1.0.*" [workspace.dependencies.trybuild] version = "1.0.*" @@ -114,6 +118,8 @@ lm35 = [] pid = [] stm32 = [] +[dependencies.generate-quantity] +path = "generate-quantity" [dependencies.num-traits] workspace = true [dependencies.derive_more] diff --git a/generate-quantity/Cargo.toml b/generate-quantity/Cargo.toml new file mode 100644 index 0000000..1fedb42 --- /dev/null +++ b/generate-quantity/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "generate-quantity" +description = "Macros for generating physical quantity type" +version.workspace = true +edition.workspace = true +repository.workspace = true +readme.workspace = true +license.workspace = true + +[lib] +proc-macro = true + +[dependencies.quote] +workspace = true +[dependencies.syn] +workspace = true +[dependencies.proc-macro2] +workspace = true \ No newline at end of file diff --git a/generate-quantity/src/lib.rs b/generate-quantity/src/lib.rs new file mode 100644 index 0000000..e16c040 --- /dev/null +++ b/generate-quantity/src/lib.rs @@ -0,0 +1,132 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use std::ops::Deref; +use syn::parse::{Parse, ParseStream}; +use syn::{parse_macro_input, Ident, LitStr, Token}; + +const NUMBER_TYPES: &[&str] = &[ + "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize", "isize", "f32", + "f64", +]; + +// Define the input structure for the macro +struct QuantityInput { + struct_name: Ident, + symbol: LitStr, +} + +// Implement the parsing for the input structure +impl Parse for QuantityInput { + fn parse(input: ParseStream) -> syn::Result { + let struct_name: Ident = input.parse()?; + input.parse::()?; + let symbol: LitStr = input.parse()?; + Ok(QuantityInput { + struct_name, + symbol, + }) + } +} + +/// The following imports must be in scope for this macro to work +/// ``` +/// use physical::quantity::{Quantity, Value}; +/// use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; +/// use generate_quantity::quantity_type; +/// ``` +#[proc_macro] +pub fn quantity_type(input: TokenStream) -> TokenStream { + // Parse the input tokens into the QuantityInput structure + let QuantityInput { + struct_name, + symbol, + } = parse_macro_input!(input as QuantityInput); + + //----- Value Extension ---------------------------------- + let mut val_ext_name = String::new(); + let struct_name_str = struct_name.to_string(); + let mut struct_name_chars = struct_name_str.chars(); + let first = struct_name_chars + .next() + .expect("Struct name cannot be 0 length"); + val_ext_name.push(first.to_ascii_lowercase()); + for character in struct_name_chars { + if character.is_uppercase() { + val_ext_name.push('_'); + val_ext_name.push(character.to_ascii_lowercase()); + } else { + val_ext_name.push(character); + } + } + let val_ext_fn_name = Ident::new(val_ext_name.deref(), struct_name.span()); + let val_ext_trait_name = + Ident::new(format!("{struct_name_str}Val").deref(), struct_name.span()); + + //----- Conversion impls ---------------------------------- + let mut conversion_impls: Vec = Vec::new(); + for ¤t_type in NUMBER_TYPES { + // Generate conversion methods for all primitive types except the current_type + let conversions = NUMBER_TYPES + .iter() + .filter(|&&t| t != current_type) + .map(|&target_type| { + let method_name = Ident::new(&format!("as_{}", target_type), Span::call_site()); + let target_type = Ident::new(target_type, Span::call_site()); + + quote! { + /// Directly [as] cast this quantities value while maintaining the type of the quantity. + #[inline(always)] + pub fn #method_name(self) -> #struct_name<#target_type> { + #struct_name(self.0 as #target_type) + } + } + }) + .collect::>(); + + let current_type = Ident::new(current_type, Span::call_site()); + // Generate the impl block for the struct with the current_type + let expanded = quote! { + impl #struct_name<#current_type> { + #(#conversions)* + } + }; + + conversion_impls.push(expanded); + } + + //----- Output Code ---------------------------------- + let expanded = quote! { + #[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] + #[display(fmt = "{} {}", _0, "Self::symbol()")] + #[repr(transparent)] + pub struct #struct_name(pub V); + + impl Quantity for #struct_name { + #[inline(always)] + fn value(self) -> V { + self.0 + } + + #[inline(always)] + fn symbol() -> &'static str { + #symbol + } + } + + pub trait #val_ext_trait_name : Value { + /// Create a quantity in the given unit from this [Value] + #[inline(always)] + fn #val_ext_fn_name(self) -> #struct_name { + #struct_name(self) + } + } + + impl #val_ext_trait_name for V {} + + #(#conversion_impls)* + }; + + expanded.into() +} diff --git a/src/quantity/irradiance.rs b/src/quantity/irradiance.rs index 297942a..20a041a 100644 --- a/src/quantity/irradiance.rs +++ b/src/quantity/irradiance.rs @@ -1,22 +1,6 @@ -use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; use crate::quantity::{Quantity, Value}; +use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; +use generate_quantity::quantity_type; -// --------------------------------------------------------------------------------------------------------------------- -// ----- Watts per Square Meter ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct WattsPerSquareMeter(pub V); - -impl Quantity for WattsPerSquareMeter { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "W/m²" - } -} \ No newline at end of file +//----- Watter per Square Meter ---------------------------------- +quantity_type! {WattsPerSquareMeter, "W/m²"} diff --git a/src/quantity/mod.rs b/src/quantity/mod.rs index 76fe2ed..f42db51 100644 --- a/src/quantity/mod.rs +++ b/src/quantity/mod.rs @@ -43,58 +43,7 @@ pub trait Quantity: Copy + PartialEq + PartialOrd + Add + Sub { } } -pub trait Value: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display { - //----- Temperature ---------------------------------- - #[inline(always)] - fn kelvins(self) -> Kelvins { - Kelvins(self) - } - - #[inline(always)] - fn deci_kelvins(self) -> DeciKelvins { - DeciKelvins(self) - } - - #[inline(always)] - fn degrees_celsius(self) -> DegreesCelsius { - DegreesCelsius(self) - } - - #[inline(always)] - fn deci_degrees_celsius(self) -> DeciDegreesCelsius { - DeciDegreesCelsius(self) - } - - //----- Voltage ---------------------------------- - #[inline(always)] - fn volts(self) -> Volts { - Volts(self) - } - - #[inline(always)] - fn millivolts(self) -> MilliVolts { - MilliVolts(self) - } - - //----- Resistance ---------------------------------- - #[inline(always)] - fn ohms(self) -> Ohms { - Ohms(self) - } - - //----- Volume rate ---------------------------------- - #[inline(always)] - fn liters_per_minute(self) -> LitersPerMinute { - LitersPerMinute(self) - } - - //----- Irradiance ---------------------------------- - #[inline(always)] - fn watts_per_square_meter(self) -> WattsPerSquareMeter { - WattsPerSquareMeter(self) - } -} - +pub trait Value: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {} impl Value for V where V: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {} #[derive(Copy, Clone)] @@ -161,6 +110,12 @@ mod defmt_impl { } } + impl> Format for Fmt { + fn format(&self, fmt: Formatter) { + write!(fmt, "{} {}", self.quantity.value(), Q::symbol()); + } + } + impl> Format for Fmt { fn format(&self, fmt: Formatter) { write!(fmt, "{} {}", self.quantity.value(), Q::symbol()); @@ -191,6 +146,12 @@ mod defmt_impl { } } + impl> Format for Fmt { + fn format(&self, fmt: Formatter) { + write!(fmt, "{} {}", self.quantity.value(), Q::symbol()); + } + } + impl> Format for Fmt { fn format(&self, fmt: Formatter) { write!(fmt, "{} {}", self.quantity.value(), Q::symbol()); diff --git a/src/quantity/resistance.rs b/src/quantity/resistance.rs index 01b0bee..ecbbd9d 100644 --- a/src/quantity/resistance.rs +++ b/src/quantity/resistance.rs @@ -1,22 +1,6 @@ use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; +use generate_quantity::quantity_type; use crate::quantity::{Quantity, Value}; -// --------------------------------------------------------------------------------------------------------------------- -// ----- Ohms ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct Ohms(pub V); - -impl Quantity for Ohms { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "Ω" - } -} +//----- Ohms ---------------------------------- +quantity_type! {Ohms, "Ω"} diff --git a/src/quantity/temperature.rs b/src/quantity/temperature.rs index 3a8aafd..57dfd98 100644 --- a/src/quantity/temperature.rs +++ b/src/quantity/temperature.rs @@ -1,28 +1,11 @@ use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; -use num_traits::float::FloatCore; +use generate_quantity::quantity_type; use crate::quantity::{DECI, Quantity, Value}; const KELVIN_CELSIUS_OFFSET: f64 = 273.15; -// --------------------------------------------------------------------------------------------------------------------- -// ----- Kelvins ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct Kelvins(pub V); - -impl Quantity for Kelvins { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "K" - } -} +//----- Kelvins ---------------------------------- +quantity_type! {Kelvins, "K"} impl Kelvins { #[inline] @@ -39,25 +22,8 @@ impl Kelvins { } } -// --------------------------------------------------------------------------------------------------------------------- -// ----- Deci Kelvins ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct DeciKelvins(pub V); - -impl Quantity for DeciKelvins { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "dK" - } -} +//----- Deci Kelvins ---------------------------------- +quantity_type! {DeciKelvins, "dK"} impl DeciKelvins { #[inline] @@ -67,25 +33,8 @@ impl DeciKelvins { } } -// --------------------------------------------------------------------------------------------------------------------- -// ----- Degrees Celsius ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct DegreesCelsius(pub V); - -impl Quantity for DegreesCelsius { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "℃" - } -} +//----- Degrees Celsius ---------------------------------- +quantity_type! {DegreesCelsius, "℃"} impl DegreesCelsius { #[inline] @@ -102,25 +51,8 @@ impl DegreesCelsius { } } -// --------------------------------------------------------------------------------------------------------------------- -// ----- Deci Degrees Celsius ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct DeciDegreesCelsius(pub V); - -impl Quantity for DeciDegreesCelsius { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "d℃" - } -} +//----- Deci Degrees Celsius ---------------------------------- +quantity_type! {DeciDegreesCelsius, "d℃"} impl DeciDegreesCelsius { #[inline] diff --git a/src/quantity/voltage.rs b/src/quantity/voltage.rs index 500c32f..ea3887c 100644 --- a/src/quantity/voltage.rs +++ b/src/quantity/voltage.rs @@ -1,27 +1,11 @@ use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; +use generate_quantity::quantity_type; use crate::quantity::{Quantity, Value}; const VOLT_MV_RATIO: u16 = 1_000; -// --------------------------------------------------------------------------------------------------------------------- -// ----- Volts ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct Volts(pub V); - -impl Quantity for Volts { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "V" - } -} +//----- Volts ---------------------------------- +quantity_type! {Volts, "V"} impl Volts { #[inline] @@ -31,25 +15,8 @@ impl Volts { } } -// --------------------------------------------------------------------------------------------------------------------- -// ----- MilliVolts ------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct MilliVolts(pub V); - -impl Quantity for MilliVolts { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "mV" - } -} +//----- Milli Volts ---------------------------------- +quantity_type! {MilliVolts, "mV"} impl MilliVolts { pub fn to_volts(self) -> Volts { @@ -69,7 +36,7 @@ mod tests { #[test] fn convert_u32() { let volts: Volts = 3.volts(); - let millivolts: MilliVolts = 3_000.millivolts(); + let millivolts: MilliVolts = 3_000.milli_volts(); assert_eq!(volts.to_millivolts().0, millivolts.0); assert_eq!(millivolts.to_volts().0, volts.0); @@ -78,7 +45,7 @@ mod tests { #[test] fn convert_f64() { let volts: Volts = 3.0.volts(); - let millivolts: MilliVolts = 3_000.0.millivolts(); + let millivolts: MilliVolts = 3_000.0.milli_volts(); assert_approx_eq!(f64, volts.to_millivolts().0, millivolts.0); assert_approx_eq!(f64, millivolts.to_volts().0, volts.0); diff --git a/src/quantity/volume_rate.rs b/src/quantity/volume_rate.rs index 04cc14a..9646b80 100644 --- a/src/quantity/volume_rate.rs +++ b/src/quantity/volume_rate.rs @@ -1,22 +1,6 @@ -use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; use crate::quantity::{Quantity, Value}; +use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; +use generate_quantity::quantity_type; -// --------------------------------------------------------------------------------------------------------------------- -// ----- Liters per Minute------------------------ -// --------------------------------------------------------------------------------------------------------------------- -#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] -#[display(fmt = "{} {}", _0, "Self::symbol()")] -#[repr(transparent)] -pub struct LitersPerMinute(pub V); - -impl Quantity for LitersPerMinute { - #[inline(always)] - fn value(self) -> V { - self.0 - } - - #[inline(always)] - fn symbol() -> &'static str { - "L/min" - } -} \ No newline at end of file +//----- Liters per Minute ---------------------------------- +quantity_type! {LitersPerMinute, "L/min"}