Added prefix-generating macros

pull/2/head
Mark 2023-04-13 11:33:51 -07:00
parent 78b0262f61
commit 3948f10bd7
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
4 changed files with 287 additions and 141 deletions

View File

@ -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}")

View File

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

View File

@ -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 => ""
})
}
}
}

View File

@ -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<FreeUnit, Scalar> { &self.val }
pub fn get_val_mut(&mut self) -> &mut HashMap<FreeUnit, Scalar> { &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<Quantity> {
// 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<Quantity> {
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();