From 0e597dd21d527d43848750d3e8467d676cc1ae2c Mon Sep 17 00:00:00 2001 From: Mike Boutin <mike.boutin@gmail.com> Date: Mon, 5 Oct 2020 21:05:41 -0400 Subject: [PATCH] Allow new units to be defined using `unit!` outside of `quantity!`. The new `unit!` macro allows for new units to be defined outside of the `quantity!` macro. Units defined using this macro will not be included in the quantity unit enum or associated functions, or in the `FromStr` implementation. Using this macro will create submodules for the underlying storage types that are enabled (e.g. `mod f32`). Resolves #173. --- Cargo.toml | 4 + README.md | 2 + examples/unit.rs | 34 ++++++ src/lib.rs | 3 + src/quantity.rs | 215 ++++++------------------------------- src/system.rs | 4 +- src/unit.rs | 274 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 350 insertions(+), 186 deletions(-) create mode 100644 examples/unit.rs create mode 100644 src/unit.rs diff --git a/Cargo.toml b/Cargo.toml index 20b02cd4..61218894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,3 +91,7 @@ required-features = ["f32"] [[example]] name = "si" required-features = ["f32", "si"] + +[[example]] +name = "unit" +required-features = ["f32", "si"] diff --git a/README.md b/README.md index e66b8e2c..a01b7e4e 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ See the [examples](examples) directory for more advanced usage: different set of base units. See the [Design](#design) section for implications of choosing different base units. * [mks.rs](examples/mks.rs) -- Shows how to create a custom system of quantities. + * [unit.rs](examples/unit.rs) -- Shows how to add new units to existing quantities in the + pre-build SI system. ## Features `uom` has multiple `Cargo` features for controlling available underlying storage types, the diff --git a/examples/unit.rs b/examples/unit.rs new file mode 100644 index 00000000..a3c4179b --- /dev/null +++ b/examples/unit.rs @@ -0,0 +1,34 @@ +//! Example showing how to use the `unit!` macro to add new units to existing quantities. +//! +//! [Pull requests](https://github.com/iliekturtles/uom/pulls) for new units are always greatly +//! appreciated. + +#[macro_use] +extern crate uom; + +use uom::fmt::DisplayStyle::*; +use uom::si::f32::*; +use uom::si::length::meter; + +unit! { + system: uom::si; + quantity: uom::si::length; + + @smoot: 1.702; "smoot", "smoot", "smoots"; +} + +fn main() { + let l1 = Length::new::<meter>(15.0); + let l2 = Length::new::<smoot>(1.0); + + println!( + "{} = {}", + l1.into_format_args(meter, Abbreviation), + l1.into_format_args(smoot, Abbreviation) + ); + println!( + "{} = {}", + l2.into_format_args(smoot, Abbreviation), + l2.into_format_args(meter, Abbreviation) + ); +} diff --git a/src/lib.rs b/src/lib.rs index 0125f5db..b30f2ca8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -356,6 +356,9 @@ mod system; #[macro_use] mod quantity; +#[macro_use] +mod unit; + #[cfg(feature = "si")] #[macro_use] pub mod si; diff --git a/src/quantity.rs b/src/quantity.rs index 3464b899..f278ad6b 100644 --- a/src/quantity.rs +++ b/src/quantity.rs @@ -99,42 +99,30 @@ macro_rules! quantity { ( $(#[$quantity_attr:meta])* quantity: $quantity:ident; $description:expr; $(#[$dim_attr:meta])* dimension: $system:ident<$($dimension:ident),+>; - units { - $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; - $abbreviation:expr, $singular:expr, $plural:expr;)+ - } - ) => { - quantity! { - $(#[$quantity_attr])* quantity: $quantity; $description; - $(#[$dim_attr])* dimension: $system<$($dimension),+>; - kind: dyn $crate::Kind; - units { - $($(#[$unit_attr])* @$unit: $($conversion),+; $abbreviation, $singular, $plural;)+ - } - } - }; - ( - $(#[$quantity_attr:meta])* quantity: $quantity:ident; $description:expr; - $(#[$dim_attr:meta])* dimension: $system:ident<$($dimension:ident),+>; - kind: $kind:ty; + $(kind: $kind:ty;)? units { $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; $abbreviation:expr, $singular:expr, $plural:expr;)+ } ) => { + mod __system { + pub use super::super::*; + } + $(#[$dim_attr])* - pub type Dimension = super::$system<$($crate::typenum::$dimension),+, $kind>; + pub type Dimension = __system::$system<$($crate::typenum::$dimension),+, + quantity!(@kind $($kind)?)>; $(#[$quantity_attr])* /// /// ## Generic Parameters /// * `U`: Base units. /// * `V`: Underlying storage type. - pub type $quantity<U, V> = super::Quantity<Dimension, U, V>; + pub type $quantity<U, V> = __system::Quantity<Dimension, U, V>; /// Marker trait to identify measurement units for the quantity. See /// [`Unit`](../trait.Unit.html). - pub trait Unit: super::Unit {} + pub trait Unit: __system::Unit {} /// Trait to identify [units][units] which have a [conversion factor][factor] for the /// `Quantity`. See [`Conversion<V>`](../../trait.Conversion.html). @@ -150,131 +138,9 @@ macro_rules! quantity { { } - $(quantity!(@unit $(#[$unit_attr])* @$unit $plural); - - impl super::Unit for $unit { - #[inline(always)] - fn abbreviation() -> &'static str { - $abbreviation - } - - #[inline(always)] - fn singular() -> &'static str { - $singular - } - - #[inline(always)] - fn plural() -> &'static str { - $plural - } - } - - impl Unit for $unit {})+ - - storage_types! { - types: Float; - - $(impl $crate::Conversion<V> for super::$unit { - type T = V; - - #[inline(always)] - fn coefficient() -> Self::T { - quantity!(@coefficient $($conversion),+) - } - - #[inline(always)] - #[allow(unused_variables)] - fn constant(op: $crate::ConstantOp) -> Self::T { - quantity!(@constant op $($conversion),+) - } - } - - impl super::Conversion<V> for super::$unit {})+ - } - - storage_types! { - types: PrimInt, BigInt; - pub type T = $crate::num::rational::Ratio<V>; - - #[inline(always)] - fn from_f64(value: f64) -> T { - <T as $crate::num::FromPrimitive>::from_f64(value).unwrap() - } - - $(impl $crate::Conversion<V> for super::$unit { - type T = T; - - #[inline(always)] - fn coefficient() -> Self::T { - from_f64(quantity!(@coefficient $($conversion),+)) - } - - #[inline(always)] - #[allow(unused_variables)] - fn constant(op: $crate::ConstantOp) -> Self::T { - from_f64(quantity!(@constant op $($conversion),+)) - } - } - - impl super::Conversion<V> for super::$unit {})+ - } - - storage_types! { - types: BigUint; - pub type T = $crate::num::rational::Ratio<V>; - - #[inline(always)] - fn from_f64(value: f64) -> T { - use $crate::num::FromPrimitive; - - let c = $crate::num::rational::Ratio::<$crate::num::BigInt>::from_f64(value) - .unwrap(); - - T::new(c.numer().to_biguint().unwrap(), c.denom().to_biguint().unwrap()) - } - - $(impl $crate::Conversion<V> for super::$unit { - type T = T; - - #[inline(always)] - fn coefficient() -> Self::T { - from_f64(quantity!(@coefficient $($conversion),+)) - } - - #[inline(always)] - #[allow(unused_variables)] - fn constant(op: $crate::ConstantOp) -> Self::T { - from_f64(quantity!(@constant op $($conversion),+)) - } - } - - impl super::Conversion<V> for super::$unit {})+ - } - - storage_types! { - types: Ratio; - - #[inline(always)] - fn from_f64(value: f64) -> V { - <V as $crate::num::FromPrimitive>::from_f64(value).unwrap() - } - - $(impl $crate::Conversion<V> for super::$unit { - type T = V; - - #[inline(always)] - fn coefficient() -> Self::T { - from_f64(quantity!(@coefficient $($conversion),+)) - } - - #[inline(always)] - #[allow(unused_variables)] - fn constant(op: $crate::ConstantOp) -> Self::T { - from_f64(quantity!(@constant op $($conversion),+)) - } - } - - impl super::Conversion<V> for super::$unit {})+ + unit! { + @units $($(#[$unit_attr])* @$unit: $($conversion),+; + $abbreviation, $singular, $plural;)+ } /// Quantity description. @@ -302,7 +168,7 @@ macro_rules! quantity { #[allow(dead_code)] pub fn abbreviation(&self) -> &'static str { match self { - $(Units::$unit(_) => <$unit as super::Unit>::abbreviation(),)+ + $(Units::$unit(_) => <$unit as __system::Unit>::abbreviation(),)+ Units::__nonexhaustive => "unknown", } @@ -312,7 +178,7 @@ macro_rules! quantity { #[allow(dead_code)] pub fn singular(&self) -> &'static str { match self { - $(Units::$unit(_) => <$unit as super::Unit>::singular(),)+ + $(Units::$unit(_) => <$unit as __system::Unit>::singular(),)+ Units::__nonexhaustive => "unknown", } @@ -322,7 +188,7 @@ macro_rules! quantity { #[allow(dead_code)] pub fn plural(&self) -> &'static str { match self { - $(Units::$unit(_) => <$unit as super::Unit>::plural(),)+ + $(Units::$unit(_) => <$unit as __system::Unit>::plural(),)+ Units::__nonexhaustive => "unknown", } @@ -341,7 +207,7 @@ macro_rules! quantity { impl<U, V> $quantity<U, V> where - U: super::Units<V> + ?Sized, + U: __system::Units<V> + ?Sized, V: $crate::num::Num + $crate::Conversion<V>, { /// Create a new quantity from the given value and measurement unit. @@ -356,7 +222,7 @@ macro_rules! quantity { $quantity { dimension: $crate::lib::marker::PhantomData, units: $crate::lib::marker::PhantomData, - value: super::to_base::<Dimension, U, V, N>(&v), + value: __system::to_base::<Dimension, U, V, N>(&v), } } @@ -369,7 +235,7 @@ macro_rules! quantity { where N: Unit + $crate::Conversion<V, T = V::T>, { - super::from_base::<Dimension, U, V, N>(&self.value) + __system::from_base::<Dimension, U, V, N>(&self.value) } /// Returns the largest integer less than or equal to a number in the given @@ -470,11 +336,11 @@ macro_rules! quantity { pub fn format_args<N>( unit: N, style: $crate::fmt::DisplayStyle - ) -> super::fmt::Arguments<Dimension, N> + ) -> __system::fmt::Arguments<Dimension, N> where N: Unit { - super::fmt::Arguments { + __system::fmt::Arguments { dimension: $crate::lib::marker::PhantomData, unit, style, @@ -510,12 +376,12 @@ macro_rules! quantity { self, unit: N, style: $crate::fmt::DisplayStyle - ) -> super::fmt::QuantityArguments<Dimension, U, V, N> + ) -> __system::fmt::QuantityArguments<Dimension, U, V, N> where N: Unit { - super::fmt::QuantityArguments { - arguments: super::fmt::Arguments { + __system::fmt::QuantityArguments { + arguments: __system::fmt::Arguments { dimension: $crate::lib::marker::PhantomData, unit, style, @@ -525,9 +391,9 @@ macro_rules! quantity { } } - impl<N> super::fmt::Arguments<Dimension, N> + impl<N> __system::fmt::Arguments<Dimension, N> where - N: super::Unit + Unit, + N: __system::Unit + Unit, { /// Specifies a quantity to display. /// @@ -537,12 +403,12 @@ macro_rules! quantity { pub fn with<U, V>( self, quantity: $quantity<U, V> - ) -> super::fmt::QuantityArguments<Dimension, U, V, N> + ) -> __system::fmt::QuantityArguments<Dimension, U, V, N> where - U: super::Units<V> + ?Sized, + U: __system::Units<V> + ?Sized, V: $crate::num::Num + $crate::Conversion<V>, { - super::fmt::QuantityArguments { + __system::fmt::QuantityArguments { arguments: self, quantity, } @@ -556,7 +422,7 @@ macro_rules! quantity { impl<U> FromStr for super::super::$quantity<U, V> where - U: super::super::super::Units<V> + ?Sized, + U: super::super::__system::Units<V> + ?Sized, { type Err = $crate::str::ParseQuantityError; @@ -576,25 +442,6 @@ macro_rules! quantity { } } }; - (@unit $(#[$unit_attr:meta])+ @$unit:ident $plural:expr) => { - $(#[$unit_attr])* - #[allow(non_camel_case_types)] - #[derive(Clone, Copy, Debug, Hash)] - pub struct $unit; - }; - (@unit @$unit:ident $plural:expr) => { - #[doc = $plural] - #[allow(non_camel_case_types)] - #[derive(Clone, Copy, Debug, Hash)] - pub struct $unit; - }; - (@coefficient $factor:expr, $const:expr) => { $factor }; - (@coefficient $factor:expr) => { $factor }; - (@constant $op:ident $factor:expr, $const:expr) => { $const }; - (@constant $op:ident $factor:expr) => { - match $op { - $crate::ConstantOp::Add => -0.0, - $crate::ConstantOp::Sub => 0.0, - } - }; + (@kind $kind:ty) => { $kind }; + (@kind) => { dyn $crate::Kind }; } diff --git a/src/system.rs b/src/system.rs index 006f3de3..0e8ad421 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1486,8 +1486,8 @@ macro_rules! system { /// Macro to implement [`quantity`](si/struct.Quantity.html) type aliases for a specific /// [system of units][units] and value storage type. /// - /// * `$path`: Path to the module where the [`system!`](macro.system.html) macro was run - /// (e.g. `::uom::si`). + /// * `$system`: Path to the module where the [`system!`](macro.system.html) macro was run + /// (e.g. `uom::si`). /// * `$V`: Underlying value storage type (e.g. `f32`). /// * `$U`: Optional. Base units. Pass as a tuple with the desired units: `(meter, kilogram, /// second, ampere, kelvin, mole, candela)`. The system's base units will be used if no diff --git a/src/unit.rs b/src/unit.rs new file mode 100644 index 00000000..3f2fcf60 --- /dev/null +++ b/src/unit.rs @@ -0,0 +1,274 @@ +/// Macro to implement a set of [measurement units][measurement]. Note that units manually defined +/// using this macro will not be included in the quantity unit enum or associated functions, or in +/// the `FromStr` implementation. Using this macro will create submodules for the underlying storage +/// types that are enabled (e.g. `mod f32`). `@...` match arms are considered private. +/// +/// When using the pre-built [SI](si) system included with `uom` this macro allows for new units to +/// quickly be defined without requiring a release. [Pull requests](pr) to add new units upstream +/// area always greatly appreciated. +/// +/// * `$system`: Path to the module where the [`system!`](macro.system.html) macro was run (e.g. +/// `uom::si`). +/// * `quantity`: Path to the module where the [`quantity!`](macro.quantity.html) macro was run +/// (e.g. `uom::si::length`). +/// * `$unit`: Unit name (e.g. `meter`, `foot`). +/// * `$conversion`: Conversion (coefficient and constant factor) from the unit to the base unit of +/// the quantity (e.g. `3.048_E-1` to convert `foot` to `meter`. `1.0_E0, 273.15_E0` to convert +/// `celsius` to `kelvin`.). The coefficient is required and the constant factor is optional. +/// Note that using a unit with a non-zero constant factor is not currently supported as a base +/// unit. +/// * `$abbreviation`: Unit abbreviation (e.g. `"m"`). +/// * `$singular`: Singular unit description (e.g. `"meter"`). +/// * `$plural`: Plural unit description (e.g. `"meters"`). +/// +/// An example invocation is given below to add kilometers to length in a meter-kilogram-second +/// system. The `#[macro_use]` attribute must be used when including the `uom` crate to make the +/// `unit!` macro available. +/// +/// ``` +/// #[macro_use] +/// extern crate uom; +/// +/// # fn main() { } +/// # mod mks { +/// # #[macro_use] +/// # mod length { +/// # quantity! { +/// # /// Length (base unit meter, m). +/// # quantity: Length; "length"; +/// # /// Length dimension, m. +/// # dimension: Q<P1 /*length*/, Z0 /*mass*/, Z0 /*time*/>; +/// # units { +/// # @meter: 1.0E0; "m", "meter", "meters"; +/// # @foot: 3.048E-1; "ft", "foot", "feet"; +/// # } +/// # } +/// # } +/// # #[macro_use] +/// # mod mass { +/// # quantity! { +/// # /// Mass (base unit kilogram, kg). +/// # quantity: Mass; "mass"; +/// # /// Mass dimension, kg. +/// # dimension: Q<Z0 /*length*/, P1 /*mass*/, Z0 /*time*/>; +/// # units { +/// # @kilogram: 1.0; "kg", "kilogram", "kilograms"; +/// # } +/// # } +/// # } +/// # #[macro_use] +/// # mod time { +/// # quantity! { +/// # /// Time (base unit second, s). +/// # quantity: Time; "time"; +/// # /// Time dimension, s. +/// # dimension: Q<Z0 /*length*/, Z0 /*mass*/, P1 /*time*/>; +/// # units { +/// # @second: 1.0; "s", "second", "seconds"; +/// # } +/// # } +/// # } +/// # system! { +/// # /// System of quantities, Q. +/// # quantities: Q { +/// # length: meter, L; +/// # mass: kilogram, M; +/// # time: second, T; +/// # } +/// # /// System of units, U. +/// # units: U { +/// # mod length::Length, +/// # mod mass::Mass, +/// # mod time::Time, +/// # } +/// # } +/// # mod f32 { +/// # Q!(crate::mks, f32/*, (centimeter, gram, second)*/); +/// # } +/// # mod unit { +/// unit! { +/// system: crate::mks; +/// quantity: crate::mks::length; +/// +/// @kilometer: 1.0E-03; "km", "kilometer", "kilometers"; +/// } +/// # } +/// # } +/// ``` +/// +/// [si]: http://jcgm.bipm.org/vim/en/1.16.html +/// [quantity]: http://jcgm.bipm.org/vim/en/1.1.html +/// [measurement]: http://jcgm.bipm.org/vim/en/1.9.html +/// [kind]: https://jcgm.bipm.org/vim/en/1.2.html +/// [pr]: https://github.com/iliekturtles/uom/pulls +#[macro_export] +macro_rules! unit { + ( + system: $system:path; + quantity: $quantity:path; + + $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; + $abbreviation:expr, $singular:expr, $plural:expr;)+ + ) => { + use $system as __system; + use $quantity as __quantity; + use __quantity::{Conversion, Unit}; + + unit!(@units $($(#[$unit_attr])* @$unit: $($conversion),+; + $abbreviation, $singular, $plural;)+); + }; + ( + @units $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; + $abbreviation:expr, $singular:expr, $plural:expr;)+ + ) => { + $(unit!(@unit $(#[$unit_attr])* @$unit $plural); + + impl __system::Unit for $unit { + #[inline(always)] + fn abbreviation() -> &'static str { + $abbreviation + } + + #[inline(always)] + fn singular() -> &'static str { + $singular + } + + #[inline(always)] + fn plural() -> &'static str { + $plural + } + } + + impl Unit for $unit {})+ + + storage_types! { + types: Float; + + $(impl $crate::Conversion<V> for super::$unit { + type T = V; + + #[inline(always)] + #[allow(clippy::inconsistent_digit_grouping)] + fn coefficient() -> Self::T { + unit!(@coefficient $($conversion),+) + } + + #[inline(always)] + #[allow(unused_variables)] + #[allow(clippy::inconsistent_digit_grouping)] + fn constant(op: $crate::ConstantOp) -> Self::T { + unit!(@constant op $($conversion),+) + } + } + + impl super::Conversion<V> for super::$unit {})+ + } + + storage_types! { + types: PrimInt, BigInt; + pub type T = $crate::num::rational::Ratio<V>; + + #[inline(always)] + fn from_f64(value: f64) -> T { + <T as $crate::num::FromPrimitive>::from_f64(value).unwrap() + } + + $(impl $crate::Conversion<V> for super::$unit { + type T = T; + + #[inline(always)] + fn coefficient() -> Self::T { + from_f64(unit!(@coefficient $($conversion),+)) + } + + #[inline(always)] + #[allow(unused_variables)] + fn constant(op: $crate::ConstantOp) -> Self::T { + from_f64(unit!(@constant op $($conversion),+)) + } + } + + impl super::Conversion<V> for super::$unit {})+ + } + + storage_types! { + types: BigUint; + pub type T = $crate::num::rational::Ratio<V>; + + #[inline(always)] + fn from_f64(value: f64) -> T { + use $crate::num::FromPrimitive; + + let c = $crate::num::rational::Ratio::<$crate::num::BigInt>::from_f64(value) + .unwrap(); + + T::new(c.numer().to_biguint().unwrap(), c.denom().to_biguint().unwrap()) + } + + $(impl $crate::Conversion<V> for super::$unit { + type T = T; + + #[inline(always)] + fn coefficient() -> Self::T { + from_f64(unit!(@coefficient $($conversion),+)) + } + + #[inline(always)] + #[allow(unused_variables)] + fn constant(op: $crate::ConstantOp) -> Self::T { + from_f64(unit!(@constant op $($conversion),+)) + } + } + + impl super::Conversion<V> for super::$unit {})+ + } + + storage_types! { + types: Ratio; + + #[inline(always)] + fn from_f64(value: f64) -> V { + <V as $crate::num::FromPrimitive>::from_f64(value).unwrap() + } + + $(impl $crate::Conversion<V> for super::$unit { + type T = V; + + #[inline(always)] + fn coefficient() -> Self::T { + from_f64(unit!(@coefficient $($conversion),+)) + } + + #[inline(always)] + #[allow(unused_variables)] + fn constant(op: $crate::ConstantOp) -> Self::T { + from_f64(unit!(@constant op $($conversion),+)) + } + } + + impl super::Conversion<V> for super::$unit {})+ + } + }; + (@unit $(#[$unit_attr:meta])+ @$unit:ident $plural:expr) => { + $(#[$unit_attr])* + #[allow(non_camel_case_types)] + #[derive(Clone, Copy, Debug, Hash)] + pub struct $unit; + }; + (@unit @$unit:ident $plural:expr) => { + #[doc = $plural] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy, Debug, Hash)] + pub struct $unit; + }; + (@coefficient $factor:expr, $const:expr) => { $factor }; + (@coefficient $factor:expr) => { $factor }; + (@constant $op:ident $factor:expr, $const:expr) => { $const }; + (@constant $op:ident $factor:expr) => { + match $op { + $crate::ConstantOp::Add => -0.0, + $crate::ConstantOp::Sub => 0.0, + } + }; +}