Switched custom quantities to using a macro for generation.

This commit is contained in:
Zachary Sunforge
2024-07-10 22:06:16 -07:00
parent da5fca74ef
commit a9f46c549e
9 changed files with 197 additions and 229 deletions

View File

@ -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]

View File

@ -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

View File

@ -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<Self> {
let struct_name: Ident = input.parse()?;
input.parse::<Token![,]>()?;
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<TokenStream2> = Vec::new();
for &current_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::<Vec<TokenStream2>>();
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<V: Value>(pub V);
impl<V: Value> Quantity<V> for #struct_name<V> {
#[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<Self> {
#struct_name(self)
}
}
impl <V: Value> #val_ext_trait_name for V {}
#(#conversion_impls)*
};
expanded.into()
}

View File

@ -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<V: Value>(pub V);
impl <V: Value> Quantity<V> for WattsPerSquareMeter<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"W/m²"
}
}
//----- Watter per Square Meter ----------------------------------
quantity_type! {WattsPerSquareMeter, "W/m²"}

View File

@ -43,58 +43,7 @@ pub trait Quantity<V: Value>: Copy + PartialEq + PartialOrd + Add + Sub {
}
}
pub trait Value: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {
//----- Temperature ----------------------------------
#[inline(always)]
fn kelvins(self) -> Kelvins<Self> {
Kelvins(self)
}
#[inline(always)]
fn deci_kelvins(self) -> DeciKelvins<Self> {
DeciKelvins(self)
}
#[inline(always)]
fn degrees_celsius(self) -> DegreesCelsius<Self> {
DegreesCelsius(self)
}
#[inline(always)]
fn deci_degrees_celsius(self) -> DeciDegreesCelsius<Self> {
DeciDegreesCelsius(self)
}
//----- Voltage ----------------------------------
#[inline(always)]
fn volts(self) -> Volts<Self> {
Volts(self)
}
#[inline(always)]
fn millivolts(self) -> MilliVolts<Self> {
MilliVolts(self)
}
//----- Resistance ----------------------------------
#[inline(always)]
fn ohms(self) -> Ohms<Self> {
Ohms(self)
}
//----- Volume rate ----------------------------------
#[inline(always)]
fn liters_per_minute(self) -> LitersPerMinute<Self> {
LitersPerMinute(self)
}
//----- Irradiance ----------------------------------
#[inline(always)]
fn watts_per_square_meter(self) -> WattsPerSquareMeter<Self> {
WattsPerSquareMeter(self)
}
}
pub trait Value: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {}
impl<V> Value for V where V: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {}
#[derive(Copy, Clone)]
@ -161,6 +110,12 @@ mod defmt_impl {
}
}
impl<Q: Quantity<usize>> Format for Fmt<usize, Q> {
fn format(&self, fmt: Formatter) {
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
}
}
impl<Q: Quantity<i8>> Format for Fmt<i8, Q> {
fn format(&self, fmt: Formatter) {
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
@ -191,6 +146,12 @@ mod defmt_impl {
}
}
impl<Q: Quantity<isize>> Format for Fmt<isize, Q> {
fn format(&self, fmt: Formatter) {
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
}
}
impl<Q: Quantity<f32>> Format for Fmt<f32, Q> {
fn format(&self, fmt: Formatter) {
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());

View File

@ -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<V: Value>(pub V);
impl <V: Value> Quantity<V> for Ohms<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"Ω"
}
}
//----- Ohms ----------------------------------
quantity_type! {Ohms, "Ω"}

View File

@ -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<V: Value>(pub V);
impl <V: Value> Quantity<V> for Kelvins<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"K"
}
}
//----- Kelvins ----------------------------------
quantity_type! {Kelvins, "K"}
impl <V: Value> Kelvins<V> {
#[inline]
@ -39,25 +22,8 @@ impl <V: Value> Kelvins<V> {
}
}
// ---------------------------------------------------------------------------------------------------------------------
// ----- Deci Kelvins ------------------------
// ---------------------------------------------------------------------------------------------------------------------
#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)]
#[display(fmt = "{} {}", _0, "Self::symbol()")]
#[repr(transparent)]
pub struct DeciKelvins<V: Value>(pub V);
impl <V: Value> Quantity<V> for DeciKelvins<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"dK"
}
}
//----- Deci Kelvins ----------------------------------
quantity_type! {DeciKelvins, "dK"}
impl<V: Value> DeciKelvins<V> {
#[inline]
@ -67,25 +33,8 @@ impl<V: Value> DeciKelvins<V> {
}
}
// ---------------------------------------------------------------------------------------------------------------------
// ----- Degrees Celsius ------------------------
// ---------------------------------------------------------------------------------------------------------------------
#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)]
#[display(fmt = "{} {}", _0, "Self::symbol()")]
#[repr(transparent)]
pub struct DegreesCelsius<V: Value>(pub V);
impl <V: Value> Quantity<V> for DegreesCelsius<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
""
}
}
//----- Degrees Celsius ----------------------------------
quantity_type! {DegreesCelsius, ""}
impl <V: Value> DegreesCelsius<V> {
#[inline]
@ -102,25 +51,8 @@ impl <V: Value> DegreesCelsius<V> {
}
}
// ---------------------------------------------------------------------------------------------------------------------
// ----- Deci Degrees Celsius ------------------------
// ---------------------------------------------------------------------------------------------------------------------
#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)]
#[display(fmt = "{} {}", _0, "Self::symbol()")]
#[repr(transparent)]
pub struct DeciDegreesCelsius<V: Value>(pub V);
impl <V: Value> Quantity<V> for DeciDegreesCelsius<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"d℃"
}
}
//----- Deci Degrees Celsius ----------------------------------
quantity_type! {DeciDegreesCelsius, "d℃"}
impl<V: Value> DeciDegreesCelsius<V> {
#[inline]

View File

@ -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<V: Value>(pub V);
impl <V: Value> Quantity<V> for Volts<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"V"
}
}
//----- Volts ----------------------------------
quantity_type! {Volts, "V"}
impl<V: Value> Volts<V> {
#[inline]
@ -31,25 +15,8 @@ impl<V: Value> Volts<V> {
}
}
// ---------------------------------------------------------------------------------------------------------------------
// ----- MilliVolts ------------------------
// ---------------------------------------------------------------------------------------------------------------------
#[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)]
#[display(fmt = "{} {}", _0, "Self::symbol()")]
#[repr(transparent)]
pub struct MilliVolts<V: Value>(pub V);
impl <V: Value> Quantity<V> for MilliVolts<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"mV"
}
}
//----- Milli Volts ----------------------------------
quantity_type! {MilliVolts, "mV"}
impl<V: Value> MilliVolts<V> {
pub fn to_volts(self) -> Volts<V> {
@ -69,7 +36,7 @@ mod tests {
#[test]
fn convert_u32() {
let volts: Volts<u32> = 3.volts();
let millivolts: MilliVolts<u32> = 3_000.millivolts();
let millivolts: MilliVolts<u32> = 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<f64> = 3.0.volts();
let millivolts: MilliVolts<f64> = 3_000.0.millivolts();
let millivolts: MilliVolts<f64> = 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);

View File

@ -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<V: Value>(pub V);
impl <V: Value> Quantity<V> for LitersPerMinute<V> {
#[inline(always)]
fn value(self) -> V {
self.0
}
#[inline(always)]
fn symbol() -> &'static str {
"L/min"
}
}
//----- Liters per Minute ----------------------------------
quantity_type! {LitersPerMinute, "L/min"}