diff --git a/Cargo.lock b/Cargo.lock index 5c310ac..088597e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "daisycalc" -version = "0.2.5" +version = "0.2.8" dependencies = [ "cfg-if", "rug", diff --git a/Cargo.toml b/Cargo.toml index b1fec27..9104008 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daisycalc" -version = "0.2.5" +version = "0.2.8" edition = "2021" build = "buildscript/main.rs" license-file = "LICENSE" diff --git a/TODO.md b/TODO.md index e71e136..e0bb361 100644 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,11 @@ - - Fix unit autoconversion (Hz * s) - Minimize parenthesis when printing expressions - - Sane autoconversion (mi + km) - Consistent errors: - Consistent look - - Linelocation for math errors - Undefined error? - Better error when `sin = 2` - Check syntax when parsing or when evaluating? - list and delete variables - - redefine variables + - sin() with units (rad and deg) ## Pre-release - Commands to list constants, units, and substitutions diff --git a/src/command/mod.rs b/src/command/mod.rs index 5e0d345..e55c6dd 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -134,6 +134,9 @@ pub fn do_command( " hyperbolic sin, etc {c}sinh, asinh, csch{r}\r\n", " hyperbolic cos, etc {c}cosh, acosh, sech{r}\r\n", " hyperbolic tan, etc {c}tanh, atanh, coth{r}\r\n", + "\r\n", + " convert to base unit {c}tobase(quantity){r}\r\n", + " remove units {c}nounit(quantity){r}\r\n", "\n" ), @@ -142,7 +145,7 @@ pub fn do_command( t = format!("{}{}", color::Fg(color::Magenta), style::Bold) )?; }, - _ => panic!("Bad command!") + _ => unreachable!("Bad command!") }; return Ok(()); diff --git a/src/context.rs b/src/context.rs index f6d3af1..f44d8be 100644 --- a/src/context.rs +++ b/src/context.rs @@ -13,19 +13,29 @@ impl Context { } pub fn push_hist(&mut self, t: Expression) { self.history.push(t); } - pub fn push_var(&mut self, s: String, t: Expression) { self.variables.insert(s, t); } + pub fn push_var(&mut self, s: String, t: Expression) -> Result<(), ()> { + if self.valid_varible(&s) { + self.variables.insert(s, t); + return Ok(()); + } else { return Err(()); } + } pub fn del_var(&mut self, s: &String) { self.variables.remove(s); } pub fn get_variable(&self, s: &String) -> Option { - let v: Option<&Expression>; - if s == "ans" { v = self.history.last(); } else { v = self.variables.get(s); } - if v.is_some() { Some(v.unwrap().clone()) } else { None } } + + pub fn valid_varible(&self, s: &str) -> bool { + return match s { + "ans" => false, + _ => true + } + } + } diff --git a/src/entry/mod.rs b/src/entry/mod.rs index 798a76c..10003b7 100644 --- a/src/entry/mod.rs +++ b/src/entry/mod.rs @@ -5,8 +5,7 @@ cfg_if::cfg_if! { pub use unix::main as main_e; } else { pub fn main_e () -> Result<(), std::io::Error> { - println!("Not yet implemented."); - Ok(()) + unimplemented!("Not yet implemented."); } } } \ No newline at end of file diff --git a/src/entry/unix/unix.rs b/src/entry/unix/unix.rs index 35b7335..bf7b400 100644 --- a/src/entry/unix/unix.rs +++ b/src/entry/unix/unix.rs @@ -59,24 +59,24 @@ fn do_expression( let Ok(g) = g else {panic!()}; - // Display parsed string - write!( - stdout, " {}{}=>{}{} {}\r\n", - style::Bold, color::Fg(color::Magenta), - style::Reset, color::Fg(color::Reset), - g.to_string() - ).unwrap(); - // Evaluate expression #[cfg(debug_assertions)] RawTerminal::suspend_raw_mode(&stdout).unwrap(); - let g = evaluate(&g, context); + let g_evaluated = evaluate(&g, context); #[cfg(debug_assertions)] RawTerminal::activate_raw_mode(&stdout).unwrap(); // Show output - if let Ok(q) = g { + if let Ok(q) = g_evaluated { + // Display parsed string + write!( + stdout, " {}{}=>{}{} {}\r\n", + style::Bold, color::Fg(color::Magenta), + style::Reset, color::Fg(color::Reset), + g.to_string() + ).unwrap(); + write!( stdout, "\n {}{}={} {}{}\r\n\n", style::Bold, @@ -88,12 +88,22 @@ fn do_expression( return Ok(q); } else { - match g { - Ok(_) => panic!(), + match g_evaluated { + Ok(_) => unreachable!(), - Err(EvalError::TooBig) => { + Err((l, EvalError::TooBig)) => { write!( - stdout, "\n {}{}Mathematical Error: {}Number too big{}\r\n\n", + stdout, "{}{}{}{}{}{}\r\n", + color::Fg(color::Red), + style::Bold, + " ".repeat(l.pos + 4), + "^".repeat(l.len), + color::Fg(color::Reset), + style::Reset, + ).unwrap(); + + write!( + stdout, " {}{}Mathematical Error: {}Number too big{}\r\n\n", style::Bold, color::Fg(color::Red), style::Reset, @@ -101,9 +111,19 @@ fn do_expression( ).unwrap(); }, - Err(EvalError::ZeroDivision) => { + Err((l, EvalError::ZeroDivision)) => { write!( - stdout, "\n {}{}Mathematical Error: {}Division by zero{}\r\n\n", + stdout, "{}{}{}{}{}{}\r\n", + color::Fg(color::Red), + style::Bold, + " ".repeat(l.pos + 4), + "^".repeat(l.len), + color::Fg(color::Reset), + style::Reset, + ).unwrap(); + + write!( + stdout, " {}{}Mathematical Error: {}Division by zero{}\r\n\n", style::Bold, color::Fg(color::Red), style::Reset, @@ -111,9 +131,19 @@ fn do_expression( ).unwrap(); }, - Err(EvalError::BadMath) => { + Err((l, EvalError::BadMath)) => { write!( - stdout, "\n {}{}Mathematical Error: {}Failed to evaluate expression{}\r\n\n", + stdout, "{}{}{}{}{}{}\r\n", + color::Fg(color::Red), + style::Bold, + " ".repeat(l.pos + 4), + "^".repeat(l.len), + color::Fg(color::Reset), + style::Reset, + ).unwrap(); + + write!( + stdout, " {}{}Mathematical Error: {}Failed to evaluate expression{}\r\n\n", style::Bold, color::Fg(color::Red), style::Reset, @@ -121,9 +151,19 @@ fn do_expression( ).unwrap(); }, - Err(EvalError::IncompatibleUnit) => { + Err((l, EvalError::IncompatibleUnit)) => { write!( - stdout, "\n {}{}Evaluation Error: {}Incompatible units{}\r\n\n", + stdout, "{}{}{}{}{}{}\r\n", + color::Fg(color::Red), + style::Bold, + " ".repeat(l.pos + 4), + "^".repeat(l.len), + color::Fg(color::Reset), + style::Reset, + ).unwrap(); + + write!( + stdout, " {}{}Evaluation Error: {}Incompatible units{}\r\n\n", style::Bold, color::Fg(color::Red), style::Reset, @@ -131,9 +171,19 @@ fn do_expression( ).unwrap(); }, - Err(EvalError::BadDefineName) => { + Err((l, EvalError::BadDefineName)) => { write!( - stdout, "\n {}{}Evaluation Error: {}Invalid variable name{}\r\n\n", + stdout, "{}{}{}{}{}{}\r\n", + color::Fg(color::Red), + style::Bold, + " ".repeat(l.pos + 4), + "^".repeat(l.len), + color::Fg(color::Reset), + style::Reset, + ).unwrap(); + + write!( + stdout, " {}{}Evaluation Error: {}Invalid variable name{}\r\n\n", style::Bold, color::Fg(color::Red), style::Reset, diff --git a/src/evaluate/evaluate.rs b/src/evaluate/evaluate.rs index cc5cdb1..54b2456 100644 --- a/src/evaluate/evaluate.rs +++ b/src/evaluate/evaluate.rs @@ -1,13 +1,13 @@ use crate::parser::Expression; use crate::parser::Operator; use crate::context::Context; - +use crate::parser::LineLocation; use super::operator::eval_operator; use super::function::eval_function; use super::EvalError; -pub fn evaluate(t: &Expression, context: &mut Context) -> Result { +pub fn evaluate(t: &Expression, context: &mut Context) -> Result { // Keeps track of our position in the expression tree. // For example, the coordinates [0, 2, 1] are interpreted as follows: @@ -40,15 +40,23 @@ pub fn evaluate(t: &Expression, context: &mut Context) -> Result None, + Expression::Quantity(_, _) => None, - Expression::Constant(c) => { Some(evaluate(&c.value(), context).unwrap()) }, - Expression::Variable(s) => { context.get_variable(&s) }, - Expression::Operator(Operator::Function(f), v) => { Some(eval_function(&f, &v)?) }, - Expression::Operator(o, v) => { eval_operator(&o, &v, context)? }, + Expression::Constant(_, c) => { Some(evaluate(&c.value(), context).unwrap()) }, + Expression::Variable(_, s) => { context.get_variable(&s) }, + Expression::Operator(_, Operator::Function(_), _) => { Some(eval_function(g)?) }, + Expression::Operator(_, _, _) => { eval_operator(g, context)? }, }; - if new.is_some() { *g = new.unwrap()} + if let Some(mut new) = new { + if let Expression::Constant(_,_) = g { + // Fix constant line location. + // Constant expansion does not change the location of a value, + // but other operations might. + new.set_linelocation(&g.get_linelocation()); + } + *g = new; + } // Move up the tree @@ -59,14 +67,16 @@ pub fn evaluate(t: &Expression, context: &mut Context) -> Result Result { + + let Expression::Operator(loc, Operator::Function(f), args) = g else {panic!()}; -pub fn eval_function(f: &Function, args: &VecDeque) -> Result { if args.len() != 1 {panic!()}; let a = &args[0]; - let Expression::Quantity(q) = a else {panic!()}; + let Expression::Quantity(l, q) = a else {panic!()}; match f { - Function::NoUnit => { return Ok(Expression::Quantity(q.without_unit())); } - Function::ToBase => { return Ok(Expression::Quantity(q.convert_to_base())); } + Function::NoUnit => { return Ok(Expression::Quantity(*loc + *l, q.without_unit())); } + Function::ToBase => { return Ok(Expression::Quantity(*loc + *l, q.convert_to_base())); } _ => {} } if !q.unitless() { - return Err(EvalError::IncompatibleUnit); + return Err((*loc + *l, EvalError::IncompatibleUnit)); } match f { - Function::Abs => { return Ok(Expression::Quantity(q.abs())); }, - Function::Floor => { return Ok(Expression::Quantity(q.floor())); }, - Function::Ceil => { return Ok(Expression::Quantity(q.ceil())); }, - Function::Round => { return Ok(Expression::Quantity(q.round())); }, + Function::Abs => { return Ok(Expression::Quantity(*loc + *l, q.abs())); }, + Function::Floor => { return Ok(Expression::Quantity(*loc + *l, q.floor())); }, + Function::Ceil => { return Ok(Expression::Quantity(*loc + *l, q.ceil())); }, + Function::Round => { return Ok(Expression::Quantity(*loc + *l, q.round())); }, - Function::NaturalLog => { return Ok(Expression::Quantity(q.ln())); }, - Function::TenLog => { return Ok(Expression::Quantity(q.log10())); }, + Function::NaturalLog => { return Ok(Expression::Quantity(*loc + *l, q.ln())); }, + Function::TenLog => { return Ok(Expression::Quantity(*loc + *l, q.log10())); }, - Function::Sin => { return Ok(Expression::Quantity(q.sin())); }, - Function::Cos => { return Ok(Expression::Quantity(q.cos())); }, - Function::Tan => { return Ok(Expression::Quantity(q.tan())); }, - Function::Asin => { return Ok(Expression::Quantity(q.asin())); }, - Function::Acos => { return Ok(Expression::Quantity(q.acos())); }, - Function::Atan => { return Ok(Expression::Quantity(q.atan())); }, + Function::Sin => { return Ok(Expression::Quantity(*loc + *l, q.sin())); }, + Function::Cos => { return Ok(Expression::Quantity(*loc + *l, q.cos())); }, + Function::Tan => { return Ok(Expression::Quantity(*loc + *l, q.tan())); }, + Function::Asin => { return Ok(Expression::Quantity(*loc + *l, q.asin())); }, + Function::Acos => { return Ok(Expression::Quantity(*loc + *l, q.acos())); }, + Function::Atan => { return Ok(Expression::Quantity(*loc + *l, q.atan())); }, - Function::Csc => { return Ok(Expression::Quantity(q.csc())); }, - Function::Sec => { return Ok(Expression::Quantity(q.sec())); }, - Function::Cot => { return Ok(Expression::Quantity(q.cot())); }, + Function::Csc => { return Ok(Expression::Quantity(*loc + *l, q.csc())); }, + Function::Sec => { return Ok(Expression::Quantity(*loc + *l, q.sec())); }, + Function::Cot => { return Ok(Expression::Quantity(*loc + *l, q.cot())); }, - Function::Sinh => { return Ok(Expression::Quantity(q.sinh())); }, - Function::Cosh => { return Ok(Expression::Quantity(q.cosh())); }, - Function::Tanh => { return Ok(Expression::Quantity(q.tanh())); }, - Function::Asinh => { return Ok(Expression::Quantity(q.asinh())); }, - Function::Acosh => { return Ok(Expression::Quantity(q.acosh())); }, - Function::Atanh => { return Ok(Expression::Quantity(q.atanh())); }, + Function::Sinh => { return Ok(Expression::Quantity(*loc + *l, q.sinh())); }, + Function::Cosh => { return Ok(Expression::Quantity(*loc + *l, q.cosh())); }, + Function::Tanh => { return Ok(Expression::Quantity(*loc + *l, q.tanh())); }, + Function::Asinh => { return Ok(Expression::Quantity(*loc + *l, q.asinh())); }, + Function::Acosh => { return Ok(Expression::Quantity(*loc + *l, q.acosh())); }, + Function::Atanh => { return Ok(Expression::Quantity(*loc + *l, q.atanh())); }, - Function::Csch => { return Ok(Expression::Quantity(q.csch())); }, - Function::Sech => { return Ok(Expression::Quantity(q.sech())); }, - Function::Coth => { return Ok(Expression::Quantity(q.coth())); }, + Function::Csch => { return Ok(Expression::Quantity(*loc + *l, q.csch())); }, + Function::Sech => { return Ok(Expression::Quantity(*loc + *l, q.sech())); }, + Function::Coth => { return Ok(Expression::Quantity(*loc + *l, q.coth())); }, Function::ToBase | Function::NoUnit - => panic!() + => unreachable!() } } \ No newline at end of file diff --git a/src/evaluate/operator.rs b/src/evaluate/operator.rs index a21d88a..fff2404 100644 --- a/src/evaluate/operator.rs +++ b/src/evaluate/operator.rs @@ -1,12 +1,15 @@ -use std::collections::VecDeque; - +use crate::parser::LineLocation; use crate::quantity::Quantity; use crate::parser::Operator; use crate::parser::Expression; use super::EvalError; use crate::context::Context; -pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut Context) -> Result, EvalError> { + +pub fn eval_operator(g: &Expression, context: &mut Context) -> Result, (LineLocation, EvalError)> { + + let Expression::Operator(op_loc, op, args) = g else {panic!()}; + match op { Operator::Function(_) => unreachable!("Functions are handled seperately."), @@ -14,41 +17,61 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C if args.len() != 2 { panic!() }; let b = &args[1]; - if let Expression::Variable(s) = &args[0] { - context.push_var(s.clone(), b.clone()); + if let Expression::Variable(l, s) = &args[0] { + let r = context.push_var(s.clone(), b.clone()); + if r.is_err() { return Err((*l, EvalError::BadDefineName)); } return Ok(Some(b.clone())); - } else { return Err(EvalError::BadDefineName); } + } else { return Err((args[0].get_linelocation(), EvalError::BadDefineName)); } }, Operator::Negative => { if args.len() != 1 { panic!() }; let args = &args[0]; - if let Expression::Quantity(v) = args { - return Ok(Some(Expression::Quantity(-v.clone()))); + if let Expression::Quantity(l, v) = args { + return Ok(Some(Expression::Quantity(*l, -v.clone()))); } else { return Ok(None); } }, Operator::Add => { let mut sum: Quantity; - if let Expression::Quantity(s) = &args[0] { + let mut loc: LineLocation; + if let Expression::Quantity(l, s) = &args[0] { sum = s.clone(); + loc = l.clone(); } else { return Ok(None); }; + + // Flag that is set to true if we find incompatible units. + // We don't stop right away because we need to add all linelocations + // to show a pretty error. + let mut incompatible_units = false; + let mut i: usize = 1; while i < args.len() { let j = &args[i]; - if let Expression::Quantity(v) = j { + if let Expression::Quantity(l, v) = j { if !sum.unit.compatible_with(&v.unit) { - return Err(EvalError::IncompatibleUnit); + incompatible_units = true; } - sum += v.clone(); - } else { return Ok(None); } + if !incompatible_units { sum += v.clone(); } + loc += *l; + } else { + if incompatible_units { + return Err((loc + *op_loc, EvalError::IncompatibleUnit)); + } + return Ok(None); + } i += 1; } - return Ok(Some(Expression::Quantity(sum))); + + if incompatible_units { + return Err((loc + *op_loc, EvalError::IncompatibleUnit)); + } + + return Ok(Some(Expression::Quantity(loc + *op_loc, sum))); }, Operator::Subtract => { @@ -56,9 +79,9 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C let a = &args[0]; let b = &args[1]; - if let Expression::Quantity(a) = a { - if let Expression::Quantity(b) = b { - return Ok(Some(Expression::Quantity(a.clone() - b.clone()))); + if let Expression::Quantity(la, a) = a { + if let Expression::Quantity(lb, b) = b { + return Ok(Some(Expression::Quantity(*la + *lb, a.clone() - b.clone()))); } } @@ -72,10 +95,10 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C let a = &args[0]; let b = &args[1]; - if let Expression::Quantity(a) = a { - if let Expression::Quantity(b) = b { - if b.is_zero() { return Err(EvalError::ZeroDivision); } - return Ok(Some(Expression::Quantity(a.clone() / b.clone()))); + if let Expression::Quantity(la, a) = a { + if let Expression::Quantity(lb, b) = b { + if b.is_zero() { return Err((*la + *lb, EvalError::ZeroDivision)); } + return Ok(Some(Expression::Quantity(*la + *lb, a.clone() / b.clone()))); } } @@ -84,14 +107,23 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C Operator::ImplicitMultiply | Operator::Multiply => { - let mut prod = Quantity::new_rational(1f64).unwrap(); - for i in args.iter() { - let j = i; - if let Expression::Quantity(v) = j { + let mut prod: Quantity; + let mut loc: LineLocation; + if let Expression::Quantity(l, s) = &args[0] { + prod = s.clone(); + loc = l.clone(); + } else { return Ok(None); }; + + let mut i: usize = 1; + while i < args.len() { + let j = &args[i]; + if let Expression::Quantity(l, v) = j { prod *= v.clone(); + loc += *l; } else { return Ok(None); } + i += 1; } - return Ok(Some(Expression::Quantity(prod))); + return Ok(Some(Expression::Quantity(loc + *op_loc, prod))); }, Operator::ModuloLong @@ -100,18 +132,18 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C let a = &args[0]; let b = &args[1]; - if let Expression::Quantity(va) = a { - if let Expression::Quantity(vb) = b { + if let Expression::Quantity(la, va) = a { + if let Expression::Quantity(lb, vb) = b { if !(va.unitless() && vb.unitless()) { - return Err(EvalError::IncompatibleUnit); + return Err((*la + *lb + *op_loc, EvalError::IncompatibleUnit)); } - if vb <= &Quantity::new_rational(1f64).unwrap() { return Err(EvalError::BadMath); } - if va.fract() != Quantity::new_rational(0f64).unwrap() { return Err(EvalError::BadMath); } - if vb.fract() != Quantity::new_rational(0f64).unwrap() { return Err(EvalError::BadMath); } + if vb <= &Quantity::new_rational(1f64).unwrap() { return Err((*la + *lb + *op_loc, EvalError::BadMath)); } + if va.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, EvalError::BadMath)); } + if vb.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, EvalError::BadMath)); } - return Ok(Some(Expression::Quantity(va.clone() % vb.clone()))); + return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, va.clone() % vb.clone()))); } else { return Ok(None); } } else { return Ok(None); } }, @@ -121,13 +153,13 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C let a = &args[0]; let b = &args[1]; - if let Expression::Quantity(va) = a { - if let Expression::Quantity(vb) = b { + if let Expression::Quantity(la, va) = a { + if let Expression::Quantity(lb, vb) = b { let n = va.clone().convert_to(vb.clone()); if n.is_none() { - return Err(EvalError::IncompatibleUnit); + return Err((*la + *lb + *op_loc, EvalError::IncompatibleUnit)); } - return Ok(Some(Expression::Quantity(n.unwrap()))); + return Ok(Some(Expression::Quantity(*la + *lb, n.unwrap()))); } else { return Ok(None); } } else { return Ok(None); } }, @@ -137,11 +169,11 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C if args.len() != 1 { panic!() } let a = &args[0]; - if let Expression::Quantity(va) = a { - if va.is_negative() { return Err(EvalError::BadMath); } - let p = va.pow(Quantity::new_rational_from_string("0.5").unwrap()); - if p.is_nan() {return Err(EvalError::BadMath);} - return Ok(Some(Expression::Quantity(p))); + if let Expression::Quantity(l, v) = a { + if v.is_negative() { return Err((*l + *op_loc, EvalError::BadMath)); } + let p = v.pow(Quantity::new_rational_from_string("0.5").unwrap()); + if p.is_nan() {return Err((*l + *op_loc, EvalError::BadMath));} + return Ok(Some(Expression::Quantity(*l, p))); } else { return Ok(None); } }, @@ -150,20 +182,20 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C let a = &args[0]; let b = &args[1]; - if let Expression::Quantity(va) = a { - if let Expression::Quantity(vb) = b { + if let Expression::Quantity(la, va) = a { + if let Expression::Quantity(lb, vb) = b { if !vb.unitless() { - return Err(EvalError::IncompatibleUnit); + return Err((*lb, EvalError::IncompatibleUnit)); } if va.is_zero() && vb.is_negative() { - return Err(EvalError::ZeroDivision); + return Err((*la + *lb + *op_loc, EvalError::ZeroDivision)); } let p = va.pow(vb.clone()); - if p.is_nan() {return Err(EvalError::BadMath);} - return Ok(Some(Expression::Quantity(p))); + if p.is_nan() {return Err((*la + *lb + *op_loc, EvalError::BadMath));} + return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, p))); } else { return Ok(None); } } else { return Ok(None); } }, @@ -172,14 +204,14 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C if args.len() != 1 {panic!()}; let args = &args[0]; - if let Expression::Quantity(v) = args { + if let Expression::Quantity(l, v) = args { if !v.unitless() { - return Err(EvalError::IncompatibleUnit); + return Err((*l + *op_loc, EvalError::IncompatibleUnit)); } - if !v.fract().is_zero() { return Err(EvalError::BadMath); } - if v > &Quantity::new_rational(50_000f64).unwrap() { return Err(EvalError::TooBig); } + if !v.fract().is_zero() { return Err((*l + *op_loc, EvalError::BadMath)); } + if v > &Quantity::new_rational(50_000f64).unwrap() { return Err((*l + *op_loc, EvalError::TooBig)); } let mut prod = Quantity::new_rational(1f64).unwrap(); let mut u = v.clone(); @@ -188,7 +220,7 @@ pub fn eval_operator(op: &Operator, args: &VecDeque, context: &mut C u = u - Quantity::new_rational(1f64).unwrap(); } - return Ok(Some(Expression::Quantity(prod))); + return Ok(Some(Expression::Quantity(*l + *op_loc, prod))); } else { return Ok(None); } } }; diff --git a/src/parser/expression/expression.rs b/src/parser/expression/expression.rs index 8150356..11a6e21 100644 --- a/src/parser/expression/expression.rs +++ b/src/parser/expression/expression.rs @@ -3,24 +3,26 @@ use crate::quantity::Quantity; use super::Operator; use super::Constant; +use super::super::LineLocation; + /// Expressions represent logical objects in an expession. #[derive(Debug)] #[derive(Clone)] pub enum Expression { - Variable(String), - Quantity(Quantity), - Constant(Constant), - Operator(Operator, VecDeque), + Variable(LineLocation, String), + Quantity(LineLocation, Quantity), + Constant(LineLocation, Constant), + Operator(LineLocation, Operator, VecDeque), } impl ToString for Expression { fn to_string(&self) -> String { match self { - Expression::Quantity(v) => v.to_string(), - Expression::Constant(c) => c.to_string(), - Expression::Variable(s) => s.clone(), - Expression::Operator(o,a) => o.print(a) + Expression::Quantity(_, v) => v.to_string(), + Expression::Constant(_, c) => c.to_string(), + Expression::Variable(_, s) => s.clone(), + Expression::Operator(_, o,a) => o.print(a) } } } @@ -30,16 +32,16 @@ impl Expression { // This sometimes leads to different--usually more verbose--behavior. pub fn to_string_outer(&self) -> String { match self { - Expression::Quantity(v) => v.to_string_outer(), - Expression::Constant(c) => c.to_string(), - Expression::Variable(s) => s.clone(), - Expression::Operator(o,a) => o.print(a) + Expression::Quantity(_, v) => v.to_string_outer(), + Expression::Constant(_, c) => c.to_string(), + Expression::Variable(_, s) => s.clone(), + Expression::Operator(_, o,a) => o.print(a) } } pub fn is_quantity(&self) -> bool { match self { - Expression::Quantity(_) => true, + Expression::Quantity(_,_) => true, _ => false } } @@ -47,7 +49,7 @@ impl Expression { #[inline(always)] pub fn get_args_mut(&mut self) -> Option<&mut VecDeque> { match self { - Expression::Operator(_, ref mut a) => Some(a), + Expression::Operator(_, _, ref mut a) => Some(a), _ => None } } @@ -55,7 +57,7 @@ impl Expression { #[inline(always)] pub fn get_args(&self) -> Option<&VecDeque> { match self { - Expression::Operator(_, ref a) => Some(a), + Expression::Operator(_, _, ref a) => Some(a), _ => None } } @@ -83,4 +85,23 @@ impl Expression { } return Some(g); } + + pub fn get_linelocation(&self) -> LineLocation { + match self { + Expression::Quantity(l, _) + | Expression::Constant(l, _) + | Expression::Variable(l, _) + | Expression::Operator(l, _,_) + => { l.clone() } + } + } + + pub fn set_linelocation(&mut self, loc: &LineLocation) { + match self { + Expression::Quantity(l, _) => { *l = *loc }, + Expression::Constant(l, _) => { *l = *loc }, + Expression::Variable(l, _) => { *l = *loc }, + Expression::Operator(l, _,_) => { *l = *loc }, + } + } } \ No newline at end of file diff --git a/src/parser/expression/operator.rs b/src/parser/expression/operator.rs index a03bb4f..e5bffc0 100644 --- a/src/parser/expression/operator.rs +++ b/src/parser/expression/operator.rs @@ -110,7 +110,7 @@ impl Operator { #[inline(always)] fn add_parens_to_arg(&self, arg: &Expression) -> String { let mut astr: String = arg.to_string(); - if let Expression::Operator(o,_) = arg { + if let Expression::Operator(_, o,_) = arg { if o < self { astr = format!("({})", astr); } @@ -121,7 +121,7 @@ impl Operator { #[inline(always)] fn add_parens_to_arg_strict(&self, arg: &Expression) -> String { let mut astr: String = arg.to_string(); - if let Expression::Operator(o,_) = arg { + if let Expression::Operator(_, o,_) = arg { if o <= self { astr = format!("({})", astr); } @@ -220,15 +220,15 @@ impl Operator { // multiplied by a unit (like 10 m) // Times sign should stay in all other cases. let no_times = { - if let Expression::Quantity(p) = a { - if let Expression::Quantity(q) = b { + if let Expression::Quantity(_, p) = a { + if let Expression::Quantity(_, q) = b { p.unitless() && !q.unitless() } else {false} } else {false} }; if no_times { - let Expression::Quantity(u) = b else {panic!()}; + let Expression::Quantity(_, u) = b else {panic!()}; if u.unit.no_space() { return format!("{}{}", self.add_parens_to_arg_strict(a), @@ -252,7 +252,7 @@ impl Operator { let a = &args[0]; let b = &args[1]; - if let Expression::Quantity(q) = a { + if let Expression::Quantity(_, q) = a { if q.is_one() { return format!("{}⁻¹", self.add_parens_to_arg_strict(b) diff --git a/src/parser/linelocation.rs b/src/parser/linelocation.rs new file mode 100644 index 0000000..c48a203 --- /dev/null +++ b/src/parser/linelocation.rs @@ -0,0 +1,59 @@ +use std::cmp; +use std::ops::Add; +use std::ops::AddAssign; +use std::cmp::Ordering; + +/// Specifies the location of a token in an input string. +#[derive(Debug)] +#[derive(Copy, Clone)] +pub struct LineLocation { + pub pos: usize, + pub len: usize +} + +impl LineLocation { + pub fn zero(&self) -> bool { + return self.pos == 0 && self.len == 0 + } +} + +impl PartialEq for LineLocation { + fn eq(&self, other: &Self) -> bool { + self.pos == other.pos && + self.len ==other.len + } +} + +impl PartialOrd for LineLocation { + fn partial_cmp(&self, other: &Self) -> Option { + return self.pos.partial_cmp(&other.pos); + } +} + +impl Add for LineLocation { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + if self.zero() { return other; } + if other.zero() { return self; } + + let start = cmp::min(self.pos, other.pos); + let end = cmp::max(self.pos+self.len-1, other.pos+other.len-1); + return LineLocation{ + pos: start, + len: end - start+1 + }; + } +} + +impl AddAssign for LineLocation where { + fn add_assign(&mut self, other: Self) { + if self.zero() {*self = other} + if other.zero() { return } + + let start = cmp::min(self.pos, other.pos); + let end = cmp::max(self.pos+self.len-1, other.pos+other.len-1); + self.pos = start; + self.len = end - start + 1; + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 495c1f5..7717f11 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,11 +3,11 @@ mod stage; mod token; mod parsererror; mod expression; +mod linelocation; use self::{ token::Token, parsererror::ParserError, - parsererror::LineLocation }; pub use self::{ @@ -15,6 +15,7 @@ pub use self::{ expression::Constant, expression::Operator, expression::Function, + linelocation::LineLocation, }; use crate::context::Context; @@ -27,7 +28,6 @@ pub fn parse( let expressions = stage::tokenize(s); let (_, expressions) = stage::find_subs(expressions); let g = stage::groupify(expressions)?; - let g = stage::treeify(g, context)?; return Ok(g); @@ -50,11 +50,12 @@ pub fn substitute( let l = s.chars().count(); let expressions = stage::tokenize(s); - let (subs, _) = stage::find_subs(expressions); + let (mut subs, _) = stage::find_subs(expressions); let mut new_c = l - c; - for r in subs.iter() { - // find_subs gives substitutions in reverse order. + while subs.len() > 0 { + let r = subs.pop_back().unwrap(); + // Apply substitutions in reverse order if { // Don't substitute if our cursor is inside the substitution c >= r.0.pos && diff --git a/src/parser/parsererror.rs b/src/parser/parsererror.rs index 05e6779..15639d2 100644 --- a/src/parser/parsererror.rs +++ b/src/parser/parsererror.rs @@ -1,11 +1,3 @@ -/// Specifies the location of a token in an input string. -#[derive(Debug)] -#[derive(Copy, Clone)] -pub struct LineLocation { - pub pos: usize, - pub len: usize -} - /// Types of parser errors. /// If we cannot parse a string, one of these is returned. #[derive(Debug)] diff --git a/src/parser/stage/find_subs.rs b/src/parser/stage/find_subs.rs index aceb668..841ad43 100644 --- a/src/parser/stage/find_subs.rs +++ b/src/parser/stage/find_subs.rs @@ -9,12 +9,12 @@ use super::super::{ pub fn find_subs( mut g: VecDeque, ) -> ( - Vec<(LineLocation, String)>, + VecDeque<(LineLocation, String)>, VecDeque ) { // Array of replacements - let mut r: Vec<(LineLocation, String)> = Vec::with_capacity(8); + let mut r: VecDeque<(LineLocation, String)> = VecDeque::with_capacity(8); // New token array, with updated locations let mut n: VecDeque = VecDeque::with_capacity(g.len()); @@ -22,8 +22,7 @@ pub fn find_subs( let mut offset: usize = 0; while g.len() > 0 { - // Read in reverse. Very important! - let mut t = g.pop_back().unwrap(); + let mut t = g.pop_front().unwrap(); let target: Option<&str> = match &mut t { Token::Operator(_, s) => { @@ -85,11 +84,14 @@ pub fn find_subs( } else { let target = target.unwrap(); let l = t.get_mut_line_location(); - r.push((l.clone(), String::from(target))); - *l = LineLocation{ pos: l.pos - offset, len: target.chars().count()}; - offset += l.len - target.chars().count(); + r.push_back((l.clone(), String::from(target))); + + let old_len = l.len; + let new_len = target.chars().count(); + *l = LineLocation{ pos: l.pos - offset, len: new_len}; + offset += old_len - new_len; } - n.push_front(t); + n.push_back(t); } return (r, n); diff --git a/src/parser/stage/groupify.rs b/src/parser/stage/groupify.rs index 4c8cc5d..930909c 100644 --- a/src/parser/stage/groupify.rs +++ b/src/parser/stage/groupify.rs @@ -194,9 +194,12 @@ pub fn groupify( Token, (LineLocation, ParserError) > { + + let last_linelocation = g.back().unwrap().get_line_location().clone(); + // Vector of grouping levels let mut levels: Vec<(LineLocation, VecDeque)> = Vec::with_capacity(8); - levels.push((LineLocation{pos: 0, len: 0}, VecDeque::with_capacity(8))); + levels.push((LineLocation{pos: 0, len: last_linelocation.pos + last_linelocation.len}, VecDeque::with_capacity(8))); // Makes sure parenthesis are matched let mut i_level = 0; @@ -212,10 +215,7 @@ pub fn groupify( }, Token::GroupEnd(l) => { - let l = LineLocation { - pos: l_now.pos, - len: l.len + l.pos - l_now.pos - }; + let l = *l_now + l; if i_level == 0 { return Err((l, ParserError::ExtraCloseParen)) } if v_now.len() == 0 { return Err((l, ParserError::EmptyGroup)) } @@ -259,5 +259,5 @@ pub fn groupify( let (_, mut v) = levels.pop().unwrap(); lookback(&mut v)?; - return Ok(Token::Group(LineLocation{pos:0, len:0}, v)); + return Ok(Token::Group(LineLocation{pos:0, len:last_linelocation.pos + last_linelocation.len}, v)); } \ No newline at end of file diff --git a/src/parser/stage/tokenize.rs b/src/parser/stage/tokenize.rs index 00e5ce0..470631d 100644 --- a/src/parser/stage/tokenize.rs +++ b/src/parser/stage/tokenize.rs @@ -28,7 +28,7 @@ fn push_token(g: &mut VecDeque, t: Option, stop_i: usize) { Token::Group(_,_) | Token::Container(_) - => panic!() + => unreachable!() }; diff --git a/src/parser/stage/treeify.rs b/src/parser/stage/treeify.rs index ce7d63d..6d626c6 100644 --- a/src/parser/stage/treeify.rs +++ b/src/parser/stage/treeify.rs @@ -85,11 +85,8 @@ fn treeify_binary( } { return Ok(false); } else { - let tl = *this.get_line_location(); - return Err(( - LineLocation{pos: tl.pos, len: l.pos - tl.pos + l.len}, - ParserError::Syntax - )); + let tl = *this.get_line_location() + *l; + return Err((tl, ParserError::Syntax)); } } @@ -126,22 +123,33 @@ fn treeify_binary( let left_pre = g_inner.remove(i-1).unwrap(); let this_pre = g_inner.remove(i-1).unwrap(); let right_pre = g_inner.remove(i-1).unwrap(); - let left: Expression; let right: Expression; - if let Token::Group(_, _) = right_pre { right = treeify(right_pre, context)?; } else {right = right_pre.to_expression(context)?;} - if let Token::Group(_, _) = left_pre { left = treeify(left_pre, context)?; } else {left = left_pre.to_expression(context)?;} + let mut left: Expression; let mut right: Expression; + if let Token::Group(l, _) = right_pre { + right = treeify(right_pre, context)?; + right.set_linelocation(&(right.get_linelocation() + l)); + } else { + right = right_pre.to_expression(context)?; + } - let o = { - let Token::Operator(_, s) = this_pre else {panic!()}; + if let Token::Group(l, _) = left_pre { + left = treeify(left_pre, context)?; + left.set_linelocation(&(left.get_linelocation() + l)); + } else { + left = left_pre.to_expression(context)?; + } + + let (l, o) = { + let Token::Operator(l, s) = this_pre else {panic!()}; let o = Operator::from_string(&s); if o.is_none() { panic!() } - o.unwrap() + (l, o.unwrap()) }; let mut new_token_args: VecDeque = VecDeque::with_capacity(2); new_token_args.push_back(left); new_token_args.push_back(right); - g_inner.insert(i-1, Token::Container(Expression::Operator(o, new_token_args))); + g_inner.insert(i-1, Token::Container(Expression::Operator(l, o, new_token_args))); return Ok(true); } else { @@ -204,12 +212,8 @@ fn treeify_unary( } if let Token::Operator(l, _) = next { - let tl = *this.get_line_location(); - return Err(( - LineLocation{pos: tl.pos, len: l.pos - tl.pos + l.len}, - ParserError::Syntax - )); - + let tl = *this.get_line_location() + *l; + return Err((tl, ParserError::Syntax)); } else { // This operator @@ -239,28 +243,34 @@ fn treeify_unary( if next_op.is_none() || this_op > next_op.unwrap() { let this_pre = g_inner.remove(i).unwrap(); - let next_pre: Token; let next: Expression; + let next_pre: Token; let mut next: Expression; if left_associative { next_pre = g_inner.remove(i-1).unwrap(); } else { next_pre = g_inner.remove(i).unwrap(); } - if let Token::Group(_, _) = next_pre { next = treeify(next_pre, context)?; } else { next = next_pre.to_expression(context)? } + if let Token::Group(l, _) = next_pre { + next = treeify(next_pre, context)?; + next.set_linelocation(&(next.get_linelocation() + l)); + } else { + next = next_pre.to_expression(context)?; + } - let o = { - let Token::Operator(_, s) = this_pre else {panic!()}; + + let (l, o) = { + let Token::Operator(l, s) = this_pre else {panic!()}; let o = Operator::from_string(&s); if o.is_none() { panic!() } - o.unwrap() + (l, o.unwrap()) }; let mut new_token_args: VecDeque = VecDeque::with_capacity(3); new_token_args.push_back(next); if left_associative { - g_inner.insert(i-1, Token::Container(Expression::Operator(o, new_token_args))); + g_inner.insert(i-1, Token::Container(Expression::Operator(l, o, new_token_args))); } else { - g_inner.insert(i, Token::Container(Expression::Operator(o, new_token_args))); + g_inner.insert(i, Token::Container(Expression::Operator(l, o, new_token_args))); } return Ok(true); diff --git a/src/parser/token.rs b/src/parser/token.rs index eb54a69..f119b03 100644 --- a/src/parser/token.rs +++ b/src/parser/token.rs @@ -74,20 +74,20 @@ impl Token { return Err((l, ParserError::BadNumber)) } - return Ok(Expression::Quantity(r.unwrap())); + return Ok(Expression::Quantity(l, r.unwrap())); }, - Token::Word(_l, s) => { + Token::Word(l, s) => { let c = Constant::from_string(&s); - if c.is_some() { return Ok(Expression::Constant(c.unwrap())); } + if c.is_some() { return Ok(Expression::Constant(l, c.unwrap())); } let c = Unit::from_string(&s); - if c.is_some() { return Ok(Expression::Quantity(c.unwrap())); } + if c.is_some() { return Ok(Expression::Quantity(l, c.unwrap())); } let c = context.get_variable(&s); - if c.is_some() { return Ok(Expression::Variable(s)); } - return Ok(Expression::Variable(s)); + if c.is_some() { return Ok(Expression::Variable(l, s)); } + return Ok(Expression::Variable(l, s)); } Token::Container(v) => { return Ok(v); } @@ -96,7 +96,7 @@ impl Token { | Token::GroupStart(_) | Token::GroupEnd(_) | Token::Group(_, _) - => panic!() + => panic!("This token cannot be converted to an expression") }; } diff --git a/src/quantity/quantity.rs b/src/quantity/quantity.rs index 1c2ec2d..d8cd4ab 100644 --- a/src/quantity/quantity.rs +++ b/src/quantity/quantity.rs @@ -111,6 +111,37 @@ impl Quantity { return Some(n.mul_no_convert(fa).div_no_convert(fb)) } + pub fn match_units(&mut self, other: &Quantity) { + + let mut new_units = Quantity::new_rational_from_string("1").unwrap(); + let mut flag; + + // Check every unit in `self` + for (us, ps) in self.unit.get_val() { + flag = false; + + // Check if `us` matches some unit in `other` + for (uo, _) in other.unit.get_val() { + // Use generalized compatible_with check to match reciprocal units + // (for example, 1Hz * 1 sec.) + let f = Unit::from_free(*uo).compatible_with_power(&Unit::from_free(*us)); + if f.is_none() { continue; } + let f = f.unwrap(); + + new_units.insert_unit(uo.clone(), ps.clone() * f); + flag = true; + break; + } + if !flag { + // If no unit in `other` matches `us`, don't convert `us` + new_units.insert_unit(us.clone(), ps.clone()); + } + } + + // Convert self to new units + *self = self.convert_to(new_units).unwrap(); + } + pub fn convert_to_base(&self) -> Quantity { self.convert_to(self.unit.to_base()).unwrap() } } @@ -224,7 +255,7 @@ impl Add for Quantity { type Output = Self; fn add(self, other: Self) -> Self::Output { - if !self.unit.compatible_with(&other.unit) { panic!() } + if !self.unit.compatible_with(&other.unit) { panic!("Tried to add incompatible units") } let mut o = other; if self.unit != o.unit { @@ -240,7 +271,7 @@ impl Add for Quantity { impl AddAssign for Quantity where { fn add_assign(&mut self, other: Self) { - if !self.unit.compatible_with(&other.unit) { panic!() } + if !self.unit.compatible_with(&other.unit) { panic!("Tried to addassign incompatible units") } let mut o = other; if self.unit != o.unit { @@ -255,7 +286,7 @@ impl Sub for Quantity { type Output = Self; fn sub(self, other: Self) -> Self::Output { - if !self.unit.compatible_with(&other.unit) { panic!() } + if !self.unit.compatible_with(&other.unit) { panic!("Tried to subtract incompatible units") } let mut o = other; if self.unit != o.unit { @@ -271,7 +302,7 @@ impl Sub for Quantity { impl SubAssign for Quantity where { fn sub_assign(&mut self, other: Self) { - if !self.unit.compatible_with(&other.unit) { panic!() } + if !self.unit.compatible_with(&other.unit) { panic!("Tried to subassign incompatible units") } let mut o = other; if self.unit != o.unit { @@ -282,24 +313,13 @@ impl SubAssign for Quantity where { } } - impl Mul for Quantity { type Output = Self; fn mul(self, other: Self) -> Self::Output { - let mut o = other; - if self.unit != o.unit { - if o.unit.compatible_with(&self.unit) { - o = o.convert_to(self.clone()).unwrap() - } else { - let cf = self.unit.common_factor(&o.unit); - if let Some(f) = cf { - o = o.convert_to(f).unwrap(); - } - } - } + o.match_units(&self); Quantity { scalar: self.scalar * o.scalar, @@ -311,18 +331,8 @@ impl Mul for Quantity { impl MulAssign for Quantity where { fn mul_assign(&mut self, other: Self) { - let mut o = other; - if self.unit != o.unit { - if o.unit.compatible_with(&self.unit) { - o = o.convert_to(self.clone()).unwrap() - } else { - let cf = self.unit.common_factor(&o.unit); - if let Some(f) = cf { - o = o.convert_to(f).unwrap(); - } - } - } + o.match_units(&self); self.scalar *= o.scalar; self.unit *= o.unit; @@ -335,16 +345,7 @@ impl Div for Quantity { fn div(self, other: Self) -> Self::Output { let mut o = other; - if self.unit != o.unit { - if o.unit.compatible_with(&self.unit) { - o = o.convert_to(self.clone()).unwrap() - } else { - let cf = self.unit.common_factor(&o.unit); - if let Some(f) = cf { - o = o.convert_to(f).unwrap(); - } - } - } + o.match_units(&self); Quantity { scalar: self.scalar / o.scalar, @@ -357,16 +358,7 @@ impl DivAssign for Quantity where { fn div_assign(&mut self, other: Self) { let mut o = other; - if self.unit != o.unit { - if o.unit.compatible_with(&self.unit) { - o = o.convert_to(self.clone()).unwrap() - } else { - let cf = self.unit.common_factor(&o.unit); - if let Some(f) = cf { - o = o.convert_to(f).unwrap(); - } - } - } + o.match_units(&self); self.scalar /= o.scalar; self.unit /= o.unit; @@ -377,8 +369,8 @@ impl Rem for Quantity { type Output = Self; fn rem(self, other: Quantity) -> Self::Output { - if !self.unit.unitless() { panic!() } - if !other.unit.unitless() { panic!() } + if !self.unit.unitless() { panic!("Tried to % a quantity with units") } + if !other.unit.unitless() { panic!("Tried to % by a quantity with units") } Quantity { scalar: self.scalar % other.scalar, @@ -397,7 +389,7 @@ impl PartialEq for Quantity { impl PartialOrd for Quantity { fn partial_cmp(&self, other: &Self) -> Option { - if self.unit != other.unit { panic!() } + if self.unit != other.unit { panic!("Tried to compare incompatible units") } self.scalar.partial_cmp(&other.scalar) } } \ No newline at end of file diff --git a/src/quantity/unit/unit.rs b/src/quantity/unit/unit.rs index 6faa62a..b068504 100644 --- a/src/quantity/unit/unit.rs +++ b/src/quantity/unit/unit.rs @@ -52,7 +52,7 @@ impl ToString for Unit { '7' => '⁷', '8' => '⁸', '9' => '⁹', - _ => panic!() + _ => unreachable!() }); } t.push('·'); @@ -90,7 +90,7 @@ impl ToString for Unit { '7' => '⁷', '8' => '⁸', '9' => '⁹', - _ => panic!() + _ => unreachable!() }); } b.push('·'); @@ -154,6 +154,7 @@ impl Unit { } // True if base units are the same + // compatible <=> can be converted to pub fn compatible_with(&self, other: &Unit) -> bool { let s = self.clone() * self.to_base_factor().unit; let o = other.clone() * other.to_base_factor().unit; @@ -161,42 +162,27 @@ impl Unit { return o == s; } - // True if these two units have a common factor - pub fn common_factor(&self, other: &Unit) -> Option { - if self.unitless() || other.unitless() { return None; } - - - let mut failed = false; - - // What to convert `other` to before multiplying - let mut factor = Quantity::new_rational_from_string("1").unwrap(); + // True if all base units are the same AND there is a constant factor between their powers. + // This is a generalization of `compatible_with`. `compatible_with` is true iff + // `compatible_with_power` is one. + pub fn compatible_with_power(&self, other: &Unit) -> Option { let mut flag; - for (us, _) in self.get_val() { + let mut pow_factor: Option = None; + + let sbu = self.to_base().unit; + let obu = other.to_base().unit; + + for (us, ps) in sbu.get_val() { flag = false; - for (uo, po) in other.get_val() { - if { - us.to_base().unit.compatible_with(&uo.to_base().unit) - } { - factor.insert_unit(us.clone(), po.clone()); - flag = true; - break; - } - } - if !flag { failed = true } - } + for (uo, po) in obu.get_val() { + if uo.whole == us.whole { + if pow_factor.is_none() { + pow_factor = Some(po.clone() / ps.clone()); + } else if let Some(ref f) = pow_factor { + if *f != po.clone() / ps.clone() { return None; } + } - if !failed { return Some(factor);} - - - let mut factor = Quantity::new_rational_from_string("1").unwrap(); - for (uo, po) in other.get_val() { - flag = false; - for (us, _) in self.get_val() { - if { - us.to_base().unit.compatible_with(&uo.to_base().unit) - } { - factor.insert_unit(us.clone(), po.clone()); flag = true; break; } @@ -204,7 +190,25 @@ impl Unit { if !flag { return None; } } - return Some(factor); + pow_factor = None; + for (uo, po) in obu.get_val() { + flag = false; + for (us, ps) in sbu.get_val() { + if uo.whole == us.whole { + if pow_factor.is_none() { + pow_factor = Some(po.clone() / ps.clone()); + } else if let Some(ref f) = pow_factor { + if *f != po.clone() / ps.clone() { return None; } + } + + flag = true; + break; + } + } + if !flag { return None; } + } + + return pow_factor; } pub fn insert(&mut self, u: FreeUnit, p: Scalar) { @@ -229,6 +233,7 @@ impl Unit { return u; } + // Returns a unit `u` so that `self * u` contains only base units. pub fn to_base_factor(&self) -> Quantity { let mut q = Quantity::new_rational(1f64).unwrap(); @@ -240,6 +245,7 @@ impl Unit { return q; } + // Returns a unit `u` equivalent to `self` that contains only base units. pub fn to_base(&self) -> Quantity { let mut q = Quantity::new_rational(1f64).unwrap(); diff --git a/src/tests.rs b/src/tests.rs index 0eebedf..19e94f9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -210,4 +210,7 @@ fn complex_units() { good_expr("62.137 mi/h", "100 km/h to mph"); good_expr("20 mi", "10 mph * 2 hours"); good_expr("120 m", "1 (m/s) * 2 min"); + good_expr("120 m", "(2 min) * (1 m/s)"); + good_expr("180", "1 Hz * 3 min"); + good_expr("3600", "1 hour * 1 Hz"); } \ No newline at end of file