From 600c5f76cca73b8f7a219367a6bf1f35bf2c06e7 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Apr 2023 11:48:15 -0700 Subject: [PATCH] Added unit conversion prototype --- src/parser/mod.rs | 1 - src/quantity/quantity.rs | 21 +++++++++++ src/quantity/unit.rs | 77 +++++++++++++++++++++++++++++++--------- src/tokens/operator.rs | 28 +++++++++++++++ 4 files changed, 110 insertions(+), 17 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0ba96b4..fa64ec1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13,7 +13,6 @@ use crate::parser::treeify::treeify; use crate::parser::find_subs::find_subs; use crate::quantity::Quantity; -use crate::quantity::Unit; use crate::tokens::Token; diff --git a/src/quantity/quantity.rs b/src/quantity/quantity.rs index 02194b4..2535703 100644 --- a/src/quantity/quantity.rs +++ b/src/quantity/quantity.rs @@ -82,6 +82,13 @@ impl Quantity { }); } + pub fn from_scalar(s: Scalar) -> Quantity { + return Quantity{ + v: s, + u: Unit::new() + }; + } + pub fn insert_unit(&mut self, ui: BaseUnit, pi: Scalar) { self.u.insert(ui, pi) } pub fn set_unit(&mut self, u: Unit) { self.u = u; } @@ -96,6 +103,9 @@ impl Quantity { "mol" => Some(BaseUnit::Mole), "c" => Some(BaseUnit::Candela), "ft" => Some(BaseUnit::Foot), + "mile" => Some(BaseUnit::Mile), + "hour" => Some(BaseUnit::Hour), + "min" => Some(BaseUnit::Minute), _ => { None } }; @@ -112,6 +122,17 @@ impl Quantity { return None; } + pub fn convert_to(self, other: Quantity) -> Option { + let fa = self.u.to_base_factor(); + let fb = other.u.to_base_factor(); + let r = self * fa / fb; + + // If this didn't work, units are incompatible + if r.u != other.u { return None; }; + + return Some(r); + } + } diff --git a/src/quantity/unit.rs b/src/quantity/unit.rs index e774780..ada72e7 100644 --- a/src/quantity/unit.rs +++ b/src/quantity/unit.rs @@ -21,30 +21,59 @@ pub enum BaseUnit { Candela, Foot, + Mile, + Minute, + Hour } impl BaseUnit { - pub fn is_base(&self) -> bool { - match self { - BaseUnit::Second - | BaseUnit::Meter - | BaseUnit::Kilogram - | BaseUnit::Ampere - | BaseUnit::Kelvin - | BaseUnit::Mole - | BaseUnit::Candela - => true, - - _ => false - } - } - pub fn to_base(&self) -> Option { match self { + + // 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. BaseUnit::Foot => Some(Quantity { v: Scalar::new_float_from_string("0.3048").unwrap(), - u: Unit::from_array(&[(BaseUnit::Meter, Scalar::new_rational(1f64).unwrap())]) + u: Unit::from_array(&[ + (BaseUnit::Meter, Scalar::new_rational(1f64).unwrap()), + (BaseUnit::Foot, Scalar::new_rational(-1f64).unwrap()) + ]) }), + + BaseUnit::Mile => Some(Quantity { + v: Scalar::new_float_from_string("1609").unwrap(), + u: Unit::from_array(&[ + (BaseUnit::Meter, Scalar::new_rational(1f64).unwrap()), + (BaseUnit::Mile, Scalar::new_rational(-1f64).unwrap()) + ]) + }), + + + BaseUnit::Minute => Some(Quantity { + v: Scalar::new_rational_from_string("60").unwrap(), + u: Unit::from_array(&[ + (BaseUnit::Second, Scalar::new_rational(1f64).unwrap()), + (BaseUnit::Minute, Scalar::new_rational(-1f64).unwrap()) + ]) + }), + + + BaseUnit::Hour => Some(Quantity { + v: Scalar::new_rational_from_string("3600").unwrap(), + u: Unit::from_array(&[ + (BaseUnit::Second, Scalar::new_rational(1f64).unwrap()), + (BaseUnit::Hour, Scalar::new_rational(-1f64).unwrap()) + ]) + }), + + // Only base units should be missing a conversion factor. _ => None } } @@ -88,6 +117,9 @@ impl ToString for Unit { BaseUnit::Candela => "c", BaseUnit::Foot => "ft", + BaseUnit::Mile => "mile", + BaseUnit::Hour => "hour", + BaseUnit::Minute => "min", }; if *p == Scalar::new_rational(1f64).unwrap() { @@ -158,6 +190,19 @@ impl Unit { }; return u; } + + pub fn to_base_factor(&self) -> Quantity { + let mut q = Quantity::new_rational(1f64).unwrap(); + + for (u, p) in self.val.iter() { + let b = u.to_base(); + if b.is_some() { + q *= b.unwrap().pow(Quantity::from_scalar(p.clone())); + } + } + + return q; + } } diff --git a/src/tokens/operator.rs b/src/tokens/operator.rs index 4ee34a9..ce73f73 100644 --- a/src/tokens/operator.rs +++ b/src/tokens/operator.rs @@ -13,6 +13,7 @@ use crate::quantity::Quantity; #[repr(usize)] pub enum Operator { ModuloLong = 0, // Mod invoked with "mod" + UnitConvert, Subtract, Add, Divide, @@ -99,6 +100,14 @@ impl Operator { ); }, + Operator::UnitConvert => { + return format!( + "{} to {}", + self.add_parens_to_arg(&args[0]), + self.add_parens_to_arg(&args[1]) + ); + }, + Operator::Modulo => { return format!( "{} % {}", @@ -211,6 +220,7 @@ impl Operator { "i*" => {Some( Operator::ImplicitMultiply )}, "%" => {Some( Operator::Modulo )}, "mod" => {Some( Operator::ModuloLong )}, + "to" => {Some( Operator::UnitConvert )}, "^"|"**" => {Some( Operator::Power )}, "!" => {Some( Operator::Factorial )}, "sqrt"|"rt"|"√" => {Some( Operator::Sqrt )}, @@ -327,6 +337,7 @@ impl Operator { | Operator::Modulo | Operator::Power | Operator::ModuloLong + | Operator::UnitConvert => { Token::Operator(self, args) }, } } @@ -420,6 +431,23 @@ impl Operator{ } else { panic!(); } }, + Operator::UnitConvert + => { + if args.len() != 2 {panic!()}; + let a = args[0].as_number(); + let b = args[1].as_number(); + + if let Token::Number(va) = a { + if let Token::Number(vb) = b { + let n = va.convert_to(vb); + if n.is_none() { + return Err(EvalError::IncompatibleUnit); + } + return Ok(Token::Number(n.unwrap())); + } else { panic!(); } + } else { panic!(); } + }, + Operator::Power => { if args.len() != 2 {panic!()}; let a = args[0].as_number();