From 3948f10bd744c8b1a71b0810ec73324cd33a5bd4 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Apr 2023 11:33:51 -0700 Subject: [PATCH] Added prefix-generating macros --- src/quantity/unit/freeunit.rs | 114 ++++++++---------------- src/quantity/unit/mod.rs | 157 +++++++++++++++++++++++++++++++++- src/quantity/unit/prefix.rs | 33 ++++++- src/quantity/unit/unit.rs | 124 +++++++++++++-------------- 4 files changed, 287 insertions(+), 141 deletions(-) diff --git a/src/quantity/unit/freeunit.rs b/src/quantity/unit/freeunit.rs index f3615b3..6fd4bcf 100644 --- a/src/quantity/unit/freeunit.rs +++ b/src/quantity/unit/freeunit.rs @@ -5,6 +5,7 @@ use crate::quantity::Quantity; use super::UnitBase; use super::Prefix; use super::Unit; +use super::unit_db; #[derive(Debug)] @@ -28,27 +29,45 @@ impl PartialEq for FreeUnit { } -macro_rules! quick_base_factor { - (float, $u:expr, $s:expr, $( ($x:expr, $p:expr) ),* ) => { +macro_rules! unpack_base_factor { + ( + $unit:expr, + $display_string:expr, + base + ) => { None }; + + ( + $unit:expr, + $display_string:expr, + float, + $value:expr, + $( ($u:expr, $p:expr) ),* + ) => { Some(Quantity { - scalar: Scalar::new_float_from_string($s).unwrap(), + scalar: Scalar::new_float_from_string($value).unwrap(), unit: Unit::from_array(&[ $( - (FreeUnit::from_base($x), Scalar::new_rational($p).unwrap()), + (FreeUnit::from_base($u), Scalar::new_rational($p).unwrap()), )* - (FreeUnit::from_base($u), Scalar::new_rational(-1f64).unwrap()) + (FreeUnit::from_base($unit), Scalar::new_rational(-1f64).unwrap()) ]) }) }; - (rational, $u:expr, $s:expr, $( ($x:expr, $p:expr) ),* ) => { + ( + $unit:expr, + $display_string:expr, + rational, + $value:expr, + $( ($u:expr, $p:expr) ),* + ) => { Some(Quantity { - scalar: Scalar::new_float_from_string($s).unwrap(), + scalar: Scalar::new_rational_from_string($value).unwrap(), unit: Unit::from_array(&[ $( - (FreeUnit::from_base($x), Scalar::new_rational($p).unwrap()), + (FreeUnit::from_base($u), Scalar::new_rational($p).unwrap()), )* - (FreeUnit::from_base($u), Scalar::new_rational(-1f64).unwrap()) + (FreeUnit::from_base($unit), Scalar::new_rational(-1f64).unwrap()) ]) }) }; @@ -70,57 +89,8 @@ impl FreeUnit { pub fn to_base_factor(&self) -> Quantity { - let q = match self.base { - // Returns the unit we need to multiply by to get a base - // unit, or `None` if this is already a base unit. - // - // Example: - // 1 foot = 0.3048 m, - // so 1 ft * (0.3084 m / ft) will give meters. - // - // The units here MUST be in terms of base units. - // If they aren't, things will break. - - UnitBase::Foot => quick_base_factor!(float, - UnitBase::Foot, - "0.3048", - (UnitBase::Meter, 1f64) - ), - - UnitBase::Inch => quick_base_factor!(float, - UnitBase::Inch, - "0.0254", - (UnitBase::Meter, 1f64) - ), - - UnitBase::Mile => quick_base_factor!(rational, - UnitBase::Mile, - "1609", - (UnitBase::Meter, 1f64) - ), - - UnitBase::Minute => quick_base_factor!(rational, - UnitBase::Minute, - "60", - (UnitBase::Second, 1f64) - ), - - UnitBase::Hour => quick_base_factor!(rational, - UnitBase::Hour, - "3600", - (UnitBase::Second, 1f64) - ), - - UnitBase::Day => quick_base_factor!(rational, - UnitBase::Day, - "86400", - (UnitBase::Second, 1f64) - ), - - // Only base units should be missing a conversion factor. - _ => None - }; + let q = unit_db!(self.base, unpack_base_factor); let mut q = q.unwrap_or(Quantity::new_rational_from_string("1").unwrap()); let mut p = self.prefix.to_ratio(); @@ -133,26 +103,18 @@ impl FreeUnit { } + +macro_rules! unpack_string { + ( + $u:expr, $s:expr, + $( $_:expr ),* + ) => { $s }; +} + impl ToString for FreeUnit { fn to_string(&self) -> String { - let s = match self.base { - UnitBase::Second => "s", - UnitBase::Meter => "m", - UnitBase::Gram => "g", - UnitBase::Ampere => "a", - UnitBase::Kelvin => "k", - UnitBase::Mole => "mol", - UnitBase::Candela => "c", - - UnitBase::Foot => "ft", - UnitBase::Inch => "in", - UnitBase::Mile => "mile", - - UnitBase::Hour => "hour", - UnitBase::Minute => "min", - UnitBase::Day => "day", - }; + let s = unit_db!(self.base, unpack_string); let p = self.prefix.to_string(); format!("{p}{s}") diff --git a/src/quantity/unit/mod.rs b/src/quantity/unit/mod.rs index 352682e..2bf69a4 100644 --- a/src/quantity/unit/mod.rs +++ b/src/quantity/unit/mod.rs @@ -9,7 +9,6 @@ pub use unit::Unit; pub use freeunit::FreeUnit; - #[derive(Hash)] #[derive(Debug)] #[derive(Copy, Clone)] @@ -38,3 +37,159 @@ pub enum UnitBase { } +// SI prefix list: +// ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + +// X macro, used in Unit.from_string() +// +// Format is as follows: +// (Unit, string from, (prefixes_to_generate)) +// Prefixes must be valid prefixes as defined in +// Prefix::str_to_prefix. +pub (self) use prefix::str_to_prefix; +macro_rules! fromstring_db { + ($X:ident) => { + $X!( + // No prefix + (UnitBase::Meter, "meter"), + (UnitBase::Foot, "ft"), + (UnitBase::Mile, "mile"), + (UnitBase::Hour, "hour"), + (UnitBase::Minute, "min"), + (UnitBase::Day, "day"), + (UnitBase::Second, "sec"), + + + (UnitBase::Meter, "m", + ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + ), + + (UnitBase::Second, "s", + ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + ), + + (UnitBase::Gram, "g", + ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + ), + + (UnitBase::Ampere, "a", + ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + ), + + (UnitBase::Kelvin, "k", + ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + ), + + (UnitBase::Mole, "mol", + ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + ), + + (UnitBase::Candela, "c", + ("Q","R","Y","Z","E","P","T","G","M","k","h","da","d","c","m","u","n","p","f","a","z","y","r","q") + ) + ) + } +} +pub (self) use fromstring_db; + + +// X macro, used in the following functions: +// - FreeUnit.to_base_factor() +// - FreeUnit.to_string() +// +// Read below comments for explanation. +macro_rules! unit_db { + ($a:expr, $X:ident) => { + match $a { + + UnitBase::Second => $X!( + + UnitBase::Second, // Repeat the name of this base unit + "s", // String to display for this unit + + // "base", "float", or "rational." + // if base, this is a base unit and has no conversion factor. + // if float or rational, this is not a base unit. See below. + base + ), + UnitBase::Meter => $X!( + UnitBase::Meter, "m", + base + ), + UnitBase::Gram => $X!( + UnitBase::Gram, "g", + base + ), + UnitBase::Ampere => $X!( + UnitBase::Ampere, "a", + base + ), + UnitBase::Kelvin => $X!( + UnitBase::Kelvin, "k", + base + ), + UnitBase::Mole => $X!( + UnitBase::Mole, "mol", + base + ), + UnitBase::Candela => $X!( + UnitBase::Candela, "c", + base + ), + + + + UnitBase::Minute => $X!( + UnitBase::Minute, "min", + + // "rational" and "float" determine what kind of Quantity + // this unit's conversion factor will be. Use "rational" + // if it is exact, and "float" if it is an approximation. + rational, + + + // The next two lines are interpreted as follows: + // One Minute = 60 Seconds. + + // The value + "60", + // The unit. Can be repeated for compound units. + // MUST BE BASE UNITS. + (UnitBase::Second, 1f64) + ), + + UnitBase::Hour => $X!( + UnitBase::Hour, "hour", + rational, "3600", + (UnitBase::Second, 1f64) + ), + + UnitBase::Day => $X!( + UnitBase::Day, "day", + rational, "86400", + (UnitBase::Second, 1f64) + ), + + UnitBase::Foot => $X!( + UnitBase::Foot, "ft", + float, "0.3048", + (UnitBase::Meter, 1f64) + ), + + UnitBase::Inch => $X!( + UnitBase::Inch, "in", + float, "0.0254", + (UnitBase::Meter, 1f64) + ), + + UnitBase::Mile => $X!( + UnitBase::Mile, "mile", + float, "1609", + (UnitBase::Meter, 1f64) + ), + } + + + } +} +pub (self) use unit_db; \ No newline at end of file diff --git a/src/quantity/unit/prefix.rs b/src/quantity/unit/prefix.rs index 5d1b6f2..26bfa1a 100644 --- a/src/quantity/unit/prefix.rs +++ b/src/quantity/unit/prefix.rs @@ -75,6 +75,37 @@ impl Prefix { } + +macro_rules! str_to_prefix { + ("") => {Prefix::None}; + ("Q") => {Prefix::Quetta}; + ("R") => {Prefix::Ronna}; + ("Y") => {Prefix::Yotta}; + ("Z") => {Prefix::Zetta}; + ("E") => {Prefix::Exa}; + ("P") => {Prefix::Peta}; + ("T") => {Prefix::Tera}; + ("G") => {Prefix::Giga}; + ("M") => {Prefix::Mega}; + ("k") => {Prefix::Kilo}; + ("h") => {Prefix::Hecto}; + ("da") => {Prefix::Deka}; + ("d") => {Prefix::Deci}; + ("c") => {Prefix::Centi}; + ("m") => {Prefix::Milli}; + ("u") => {Prefix::Micro}; + ("n") => {Prefix::Nano}; + ("p") => {Prefix::Pico}; + ("f") => {Prefix::Femto}; + ("a") => {Prefix::Atto}; + ("z") => {Prefix::Zepto}; + ("y") => {Prefix::Yocto}; + ("r") => {Prefix::Ronto}; + ("q") => {Prefix::Quecto}; +} +pub (super) use str_to_prefix; + + impl ToString for Prefix { fn to_string(&self) -> String { String::from(match self { @@ -107,4 +138,4 @@ impl ToString for Prefix { Prefix::None => "" }) } -} +} \ No newline at end of file diff --git a/src/quantity/unit/unit.rs b/src/quantity/unit/unit.rs index 4bf44a6..84821e7 100644 --- a/src/quantity/unit/unit.rs +++ b/src/quantity/unit/unit.rs @@ -6,9 +6,11 @@ use std::ops::{ use crate::quantity::Scalar; use crate::quantity::Quantity; +use super::FreeUnit; use super::UnitBase; use super::Prefix; -use super::FreeUnit; +use super::fromstring_db; +use super::str_to_prefix; #[derive(Debug)] #[derive(Clone)] @@ -74,6 +76,15 @@ impl Unit { } } + pub fn from_free(f: FreeUnit) -> Unit { + let mut u = Unit { + val: HashMap::new() + }; + + u.insert(f, Scalar::new_rational(1f64).unwrap()); + return u; + } + pub fn get_val(&self) -> &HashMap { &self.val } pub fn get_val_mut(&mut self) -> &mut HashMap { &mut self.val } pub fn unitless(&self) -> bool { self.get_val().len() == 0 } @@ -149,68 +160,6 @@ impl Unit { return u; } - - pub fn from_string(s: &str) -> Option { - // Base Units - let b = match s { - "m" => Some(UnitBase::Meter), - "s" => Some(UnitBase::Second), - "sec" => Some(UnitBase::Second), - "g" => Some(UnitBase::Gram), - "a" => Some(UnitBase::Ampere), - "k" => Some(UnitBase::Kelvin), - "mol" => Some(UnitBase::Mole), - "c" => Some(UnitBase::Candela), - "ft" => Some(UnitBase::Foot), - "mile" => Some(UnitBase::Mile), - "hour" => Some(UnitBase::Hour), - "min" => Some(UnitBase::Minute), - "day" => Some(UnitBase::Day), - _ => { None } - }; - - if b.is_some() { - let mut u = Unit::new(); - let b = FreeUnit::from_base(b.unwrap()); - - u.insert(b, Scalar::new_rational(1f64).unwrap()); - - let mut q = Quantity::new_rational(1f64).unwrap(); - q.set_unit(u); - - return Some(q); - }; - - if b.is_none() { - if s == "kg" { - let mut u = Unit::new(); - let b = FreeUnit::from_base_prefix(UnitBase::Gram, Prefix::Kilo); - - u.insert(b, Scalar::new_rational(1f64).unwrap()); - - let mut q = Quantity::new_rational(1f64).unwrap(); - q.set_unit(u); - - return Some(q); - } - - if s == "km" { - let mut u = Unit::new(); - let b = FreeUnit::from_base_prefix(UnitBase::Meter, Prefix::Kilo); - - u.insert(b, Scalar::new_rational(1f64).unwrap()); - - let mut q = Quantity::new_rational(1f64).unwrap(); - q.set_unit(u); - - return Some(q); - } - } - - return None; - } - - pub fn to_base_factor(&self) -> Quantity { let mut q = Quantity::new_rational(1f64).unwrap(); @@ -224,6 +173,55 @@ impl Unit { } + +impl Unit { + pub fn from_string(s: &str) -> Option { + macro_rules! unpack_fromstring { + ( + $( + ( + $unit:expr, + $string:literal + $(, ( + $( $prefix:tt ),* + ))? + ) + ),* + ) => { + // Build match statement for each unit and prefix + match s { + $( + // No prefix--every unit has this + $string => Some(FreeUnit::from_base($unit)), + + // Arms for prefixes + $($( + concat!( + $prefix, + $string + ) => Some(FreeUnit::from_base_prefix($unit, str_to_prefix!($prefix))), + )*)* + )* + _ => None + } + }; + } + + // Big match statement + let b = fromstring_db!(unpack_fromstring); + + if b.is_none() { return None; } + let b = Unit::from_free(b.unwrap()); + let mut q = Quantity::new_rational(1f64).unwrap(); + q.set_unit(b); + return Some(q); + + } +} + + + + impl PartialEq for Unit { fn eq(&self, other: &Self) -> bool { let v = self.get_val();