mirror of https://github.com/rm-dr/daisy
Merge branch 'master' of ssh://git.betalupi.com:33/Mark/daisy
commit
dc42138a38
|
@ -28,7 +28,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "daisycalc"
|
name = "daisycalc"
|
||||||
version = "0.2.5"
|
version = "0.2.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"rug",
|
"rug",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "daisycalc"
|
name = "daisycalc"
|
||||||
version = "0.2.5"
|
version = "0.2.8"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "buildscript/main.rs"
|
build = "buildscript/main.rs"
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
|
|
5
TODO.md
5
TODO.md
|
@ -1,14 +1,11 @@
|
||||||
- Fix unit autoconversion (Hz * s)
|
|
||||||
- Minimize parenthesis when printing expressions
|
- Minimize parenthesis when printing expressions
|
||||||
- Sane autoconversion (mi + km)
|
|
||||||
- Consistent errors:
|
- Consistent errors:
|
||||||
- Consistent look
|
- Consistent look
|
||||||
- Linelocation for math errors
|
|
||||||
- Undefined error?
|
- Undefined error?
|
||||||
- Better error when `sin = 2`
|
- Better error when `sin = 2`
|
||||||
- Check syntax when parsing or when evaluating?
|
- Check syntax when parsing or when evaluating?
|
||||||
- list and delete variables
|
- list and delete variables
|
||||||
- redefine variables
|
- sin() with units (rad and deg)
|
||||||
|
|
||||||
## Pre-release
|
## Pre-release
|
||||||
- Commands to list constants, units, and substitutions
|
- Commands to list constants, units, and substitutions
|
||||||
|
|
|
@ -134,6 +134,9 @@ pub fn do_command(
|
||||||
" hyperbolic sin, etc {c}sinh, asinh, csch{r}\r\n",
|
" hyperbolic sin, etc {c}sinh, asinh, csch{r}\r\n",
|
||||||
" hyperbolic cos, etc {c}cosh, acosh, sech{r}\r\n",
|
" hyperbolic cos, etc {c}cosh, acosh, sech{r}\r\n",
|
||||||
" hyperbolic tan, etc {c}tanh, atanh, coth{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"
|
"\n"
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -142,7 +145,7 @@ pub fn do_command(
|
||||||
t = format!("{}{}", color::Fg(color::Magenta), style::Bold)
|
t = format!("{}{}", color::Fg(color::Magenta), style::Bold)
|
||||||
)?;
|
)?;
|
||||||
},
|
},
|
||||||
_ => panic!("Bad command!")
|
_ => unreachable!("Bad command!")
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -13,19 +13,29 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_hist(&mut self, t: Expression) { self.history.push(t); }
|
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 del_var(&mut self, s: &String) { self.variables.remove(s); }
|
||||||
|
|
||||||
pub fn get_variable(&self, s: &String) -> Option<Expression> {
|
pub fn get_variable(&self, s: &String) -> Option<Expression> {
|
||||||
|
|
||||||
let v: Option<&Expression>;
|
let v: Option<&Expression>;
|
||||||
|
|
||||||
if s == "ans" {
|
if s == "ans" {
|
||||||
v = self.history.last();
|
v = self.history.last();
|
||||||
} else {
|
} else {
|
||||||
v = self.variables.get(s);
|
v = self.variables.get(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.is_some() { Some(v.unwrap().clone()) } else { None }
|
if v.is_some() { Some(v.unwrap().clone()) } else { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn valid_varible(&self, s: &str) -> bool {
|
||||||
|
return match s {
|
||||||
|
"ans" => false,
|
||||||
|
_ => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ cfg_if::cfg_if! {
|
||||||
pub use unix::main as main_e;
|
pub use unix::main as main_e;
|
||||||
} else {
|
} else {
|
||||||
pub fn main_e () -> Result<(), std::io::Error> {
|
pub fn main_e () -> Result<(), std::io::Error> {
|
||||||
println!("Not yet implemented.");
|
unimplemented!("Not yet implemented.");
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -59,6 +59,16 @@ fn do_expression(
|
||||||
let Ok(g) = g else {panic!()};
|
let Ok(g) = g else {panic!()};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Evaluate expression
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
RawTerminal::suspend_raw_mode(&stdout).unwrap();
|
||||||
|
let g_evaluated = evaluate(&g, context);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
RawTerminal::activate_raw_mode(&stdout).unwrap();
|
||||||
|
|
||||||
|
// Show output
|
||||||
|
if let Ok(q) = g_evaluated {
|
||||||
// Display parsed string
|
// Display parsed string
|
||||||
write!(
|
write!(
|
||||||
stdout, " {}{}=>{}{} {}\r\n",
|
stdout, " {}{}=>{}{} {}\r\n",
|
||||||
|
@ -67,16 +77,6 @@ fn do_expression(
|
||||||
g.to_string()
|
g.to_string()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
|
|
||||||
// Evaluate expression
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
RawTerminal::suspend_raw_mode(&stdout).unwrap();
|
|
||||||
let g = evaluate(&g, context);
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
RawTerminal::activate_raw_mode(&stdout).unwrap();
|
|
||||||
|
|
||||||
// Show output
|
|
||||||
if let Ok(q) = g {
|
|
||||||
write!(
|
write!(
|
||||||
stdout, "\n {}{}={} {}{}\r\n\n",
|
stdout, "\n {}{}={} {}{}\r\n\n",
|
||||||
style::Bold,
|
style::Bold,
|
||||||
|
@ -88,12 +88,22 @@ fn do_expression(
|
||||||
return Ok(q);
|
return Ok(q);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
match g {
|
match g_evaluated {
|
||||||
Ok(_) => panic!(),
|
Ok(_) => unreachable!(),
|
||||||
|
|
||||||
Err(EvalError::TooBig) => {
|
Err((l, EvalError::TooBig)) => {
|
||||||
write!(
|
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,
|
style::Bold,
|
||||||
color::Fg(color::Red),
|
color::Fg(color::Red),
|
||||||
style::Reset,
|
style::Reset,
|
||||||
|
@ -101,9 +111,19 @@ fn do_expression(
|
||||||
).unwrap();
|
).unwrap();
|
||||||
},
|
},
|
||||||
|
|
||||||
Err(EvalError::ZeroDivision) => {
|
Err((l, EvalError::ZeroDivision)) => {
|
||||||
write!(
|
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,
|
style::Bold,
|
||||||
color::Fg(color::Red),
|
color::Fg(color::Red),
|
||||||
style::Reset,
|
style::Reset,
|
||||||
|
@ -111,9 +131,19 @@ fn do_expression(
|
||||||
).unwrap();
|
).unwrap();
|
||||||
},
|
},
|
||||||
|
|
||||||
Err(EvalError::BadMath) => {
|
Err((l, EvalError::BadMath)) => {
|
||||||
write!(
|
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,
|
style::Bold,
|
||||||
color::Fg(color::Red),
|
color::Fg(color::Red),
|
||||||
style::Reset,
|
style::Reset,
|
||||||
|
@ -121,9 +151,19 @@ fn do_expression(
|
||||||
).unwrap();
|
).unwrap();
|
||||||
},
|
},
|
||||||
|
|
||||||
Err(EvalError::IncompatibleUnit) => {
|
Err((l, EvalError::IncompatibleUnit)) => {
|
||||||
write!(
|
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,
|
style::Bold,
|
||||||
color::Fg(color::Red),
|
color::Fg(color::Red),
|
||||||
style::Reset,
|
style::Reset,
|
||||||
|
@ -131,9 +171,19 @@ fn do_expression(
|
||||||
).unwrap();
|
).unwrap();
|
||||||
},
|
},
|
||||||
|
|
||||||
Err(EvalError::BadDefineName) => {
|
Err((l, EvalError::BadDefineName)) => {
|
||||||
write!(
|
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,
|
style::Bold,
|
||||||
color::Fg(color::Red),
|
color::Fg(color::Red),
|
||||||
style::Reset,
|
style::Reset,
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::parser::Expression;
|
use crate::parser::Expression;
|
||||||
use crate::parser::Operator;
|
use crate::parser::Operator;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::parser::LineLocation;
|
||||||
|
|
||||||
use super::operator::eval_operator;
|
use super::operator::eval_operator;
|
||||||
use super::function::eval_function;
|
use super::function::eval_function;
|
||||||
use super::EvalError;
|
use super::EvalError;
|
||||||
|
|
||||||
pub fn evaluate(t: &Expression, context: &mut Context) -> Result<Expression, EvalError> {
|
pub fn evaluate(t: &Expression, context: &mut Context) -> Result<Expression, (LineLocation, EvalError)> {
|
||||||
|
|
||||||
// Keeps track of our position in the expression tree.
|
// Keeps track of our position in the expression tree.
|
||||||
// For example, the coordinates [0, 2, 1] are interpreted as follows:
|
// For example, the coordinates [0, 2, 1] are interpreted as follows:
|
||||||
|
@ -40,15 +40,23 @@ pub fn evaluate(t: &Expression, context: &mut Context) -> Result<Expression, Eva
|
||||||
|
|
||||||
|
|
||||||
let new = match g {
|
let new = match g {
|
||||||
Expression::Quantity(_) => None,
|
Expression::Quantity(_, _) => None,
|
||||||
|
|
||||||
Expression::Constant(c) => { Some(evaluate(&c.value(), context).unwrap()) },
|
Expression::Constant(_, c) => { Some(evaluate(&c.value(), context).unwrap()) },
|
||||||
Expression::Variable(s) => { context.get_variable(&s) },
|
Expression::Variable(_, s) => { context.get_variable(&s) },
|
||||||
Expression::Operator(Operator::Function(f), v) => { Some(eval_function(&f, &v)?) },
|
Expression::Operator(_, Operator::Function(_), _) => { Some(eval_function(g)?) },
|
||||||
Expression::Operator(o, v) => { eval_operator(&o, &v, context)? },
|
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
|
// Move up the tree
|
||||||
|
@ -59,14 +67,16 @@ pub fn evaluate(t: &Expression, context: &mut Context) -> Result<Expression, Eva
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Move down the tree
|
// Move down the tree
|
||||||
coords.push(0);
|
|
||||||
|
|
||||||
let n = root.get_at_coords(&coords[..]);
|
// Don't evaluate the first argument of a define.
|
||||||
if let Some(n) = n {
|
// This prevents variables from being expanded before a re-assignment.
|
||||||
if let Expression::Operator(Operator::Define, _) = n {
|
if let Expression::Operator(_, Operator::Define, _) = g {
|
||||||
*coords.last_mut().unwrap() += 1;
|
*coords.last_mut().unwrap() += 1;
|
||||||
|
coords.push(0);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
coords.push(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,59 +1,61 @@
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use crate::parser::Expression;
|
use crate::parser::Expression;
|
||||||
use crate::parser::Function;
|
use crate::parser::Function;
|
||||||
|
use crate::parser::Operator;
|
||||||
|
use crate::parser::LineLocation;
|
||||||
use super::EvalError;
|
use super::EvalError;
|
||||||
|
|
||||||
|
pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalError)> {
|
||||||
|
|
||||||
|
let Expression::Operator(loc, Operator::Function(f), args) = g else {panic!()};
|
||||||
|
|
||||||
pub fn eval_function(f: &Function, args: &VecDeque<Expression>) -> Result<Expression, EvalError> {
|
|
||||||
if args.len() != 1 {panic!()};
|
if args.len() != 1 {panic!()};
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
let Expression::Quantity(q) = a else {panic!()};
|
let Expression::Quantity(l, q) = a else {panic!()};
|
||||||
|
|
||||||
|
|
||||||
match f {
|
match f {
|
||||||
Function::NoUnit => { return Ok(Expression::Quantity(q.without_unit())); }
|
Function::NoUnit => { return Ok(Expression::Quantity(*loc + *l, q.without_unit())); }
|
||||||
Function::ToBase => { return Ok(Expression::Quantity(q.convert_to_base())); }
|
Function::ToBase => { return Ok(Expression::Quantity(*loc + *l, q.convert_to_base())); }
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !q.unitless() {
|
if !q.unitless() {
|
||||||
return Err(EvalError::IncompatibleUnit);
|
return Err((*loc + *l, EvalError::IncompatibleUnit));
|
||||||
}
|
}
|
||||||
|
|
||||||
match f {
|
match f {
|
||||||
Function::Abs => { return Ok(Expression::Quantity(q.abs())); },
|
Function::Abs => { return Ok(Expression::Quantity(*loc + *l, q.abs())); },
|
||||||
Function::Floor => { return Ok(Expression::Quantity(q.floor())); },
|
Function::Floor => { return Ok(Expression::Quantity(*loc + *l, q.floor())); },
|
||||||
Function::Ceil => { return Ok(Expression::Quantity(q.ceil())); },
|
Function::Ceil => { return Ok(Expression::Quantity(*loc + *l, q.ceil())); },
|
||||||
Function::Round => { return Ok(Expression::Quantity(q.round())); },
|
Function::Round => { return Ok(Expression::Quantity(*loc + *l, q.round())); },
|
||||||
|
|
||||||
Function::NaturalLog => { return Ok(Expression::Quantity(q.ln())); },
|
Function::NaturalLog => { return Ok(Expression::Quantity(*loc + *l, q.ln())); },
|
||||||
Function::TenLog => { return Ok(Expression::Quantity(q.log10())); },
|
Function::TenLog => { return Ok(Expression::Quantity(*loc + *l, q.log10())); },
|
||||||
|
|
||||||
Function::Sin => { return Ok(Expression::Quantity(q.sin())); },
|
Function::Sin => { return Ok(Expression::Quantity(*loc + *l, q.sin())); },
|
||||||
Function::Cos => { return Ok(Expression::Quantity(q.cos())); },
|
Function::Cos => { return Ok(Expression::Quantity(*loc + *l, q.cos())); },
|
||||||
Function::Tan => { return Ok(Expression::Quantity(q.tan())); },
|
Function::Tan => { return Ok(Expression::Quantity(*loc + *l, q.tan())); },
|
||||||
Function::Asin => { return Ok(Expression::Quantity(q.asin())); },
|
Function::Asin => { return Ok(Expression::Quantity(*loc + *l, q.asin())); },
|
||||||
Function::Acos => { return Ok(Expression::Quantity(q.acos())); },
|
Function::Acos => { return Ok(Expression::Quantity(*loc + *l, q.acos())); },
|
||||||
Function::Atan => { return Ok(Expression::Quantity(q.atan())); },
|
Function::Atan => { return Ok(Expression::Quantity(*loc + *l, q.atan())); },
|
||||||
|
|
||||||
Function::Csc => { return Ok(Expression::Quantity(q.csc())); },
|
Function::Csc => { return Ok(Expression::Quantity(*loc + *l, q.csc())); },
|
||||||
Function::Sec => { return Ok(Expression::Quantity(q.sec())); },
|
Function::Sec => { return Ok(Expression::Quantity(*loc + *l, q.sec())); },
|
||||||
Function::Cot => { return Ok(Expression::Quantity(q.cot())); },
|
Function::Cot => { return Ok(Expression::Quantity(*loc + *l, q.cot())); },
|
||||||
|
|
||||||
Function::Sinh => { return Ok(Expression::Quantity(q.sinh())); },
|
Function::Sinh => { return Ok(Expression::Quantity(*loc + *l, q.sinh())); },
|
||||||
Function::Cosh => { return Ok(Expression::Quantity(q.cosh())); },
|
Function::Cosh => { return Ok(Expression::Quantity(*loc + *l, q.cosh())); },
|
||||||
Function::Tanh => { return Ok(Expression::Quantity(q.tanh())); },
|
Function::Tanh => { return Ok(Expression::Quantity(*loc + *l, q.tanh())); },
|
||||||
Function::Asinh => { return Ok(Expression::Quantity(q.asinh())); },
|
Function::Asinh => { return Ok(Expression::Quantity(*loc + *l, q.asinh())); },
|
||||||
Function::Acosh => { return Ok(Expression::Quantity(q.acosh())); },
|
Function::Acosh => { return Ok(Expression::Quantity(*loc + *l, q.acosh())); },
|
||||||
Function::Atanh => { return Ok(Expression::Quantity(q.atanh())); },
|
Function::Atanh => { return Ok(Expression::Quantity(*loc + *l, q.atanh())); },
|
||||||
|
|
||||||
Function::Csch => { return Ok(Expression::Quantity(q.csch())); },
|
Function::Csch => { return Ok(Expression::Quantity(*loc + *l, q.csch())); },
|
||||||
Function::Sech => { return Ok(Expression::Quantity(q.sech())); },
|
Function::Sech => { return Ok(Expression::Quantity(*loc + *l, q.sech())); },
|
||||||
Function::Coth => { return Ok(Expression::Quantity(q.coth())); },
|
Function::Coth => { return Ok(Expression::Quantity(*loc + *l, q.coth())); },
|
||||||
|
|
||||||
Function::ToBase
|
Function::ToBase
|
||||||
| Function::NoUnit
|
| Function::NoUnit
|
||||||
=> panic!()
|
=> unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,15 @@
|
||||||
use std::collections::VecDeque;
|
use crate::parser::LineLocation;
|
||||||
|
|
||||||
use crate::quantity::Quantity;
|
use crate::quantity::Quantity;
|
||||||
use crate::parser::Operator;
|
use crate::parser::Operator;
|
||||||
use crate::parser::Expression;
|
use crate::parser::Expression;
|
||||||
use super::EvalError;
|
use super::EvalError;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
|
||||||
pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut Context) -> Result<Option<Expression>, EvalError> {
|
|
||||||
|
pub fn eval_operator(g: &Expression, context: &mut Context) -> Result<Option<Expression>, (LineLocation, EvalError)> {
|
||||||
|
|
||||||
|
let Expression::Operator(op_loc, op, args) = g else {panic!()};
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Operator::Function(_) => unreachable!("Functions are handled seperately."),
|
Operator::Function(_) => unreachable!("Functions are handled seperately."),
|
||||||
|
|
||||||
|
@ -14,41 +17,61 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
if args.len() != 2 { panic!() };
|
if args.len() != 2 { panic!() };
|
||||||
let b = &args[1];
|
let b = &args[1];
|
||||||
|
|
||||||
if let Expression::Variable(s) = &args[0] {
|
if let Expression::Variable(l, s) = &args[0] {
|
||||||
context.push_var(s.clone(), b.clone());
|
let r = context.push_var(s.clone(), b.clone());
|
||||||
|
if r.is_err() { return Err((*l, EvalError::BadDefineName)); }
|
||||||
return Ok(Some(b.clone()));
|
return Ok(Some(b.clone()));
|
||||||
} else { return Err(EvalError::BadDefineName); }
|
} else { return Err((args[0].get_linelocation(), EvalError::BadDefineName)); }
|
||||||
},
|
},
|
||||||
|
|
||||||
Operator::Negative => {
|
Operator::Negative => {
|
||||||
if args.len() != 1 { panic!() };
|
if args.len() != 1 { panic!() };
|
||||||
let args = &args[0];
|
let args = &args[0];
|
||||||
|
|
||||||
if let Expression::Quantity(v) = args {
|
if let Expression::Quantity(l, v) = args {
|
||||||
return Ok(Some(Expression::Quantity(-v.clone())));
|
return Ok(Some(Expression::Quantity(*l, -v.clone())));
|
||||||
} else { return Ok(None); }
|
} else { return Ok(None); }
|
||||||
},
|
},
|
||||||
|
|
||||||
Operator::Add => {
|
Operator::Add => {
|
||||||
let mut sum: Quantity;
|
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();
|
sum = s.clone();
|
||||||
|
loc = l.clone();
|
||||||
} else { return Ok(None); };
|
} 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;
|
let mut i: usize = 1;
|
||||||
while i < args.len() {
|
while i < args.len() {
|
||||||
let j = &args[i];
|
let j = &args[i];
|
||||||
if let Expression::Quantity(v) = j {
|
if let Expression::Quantity(l, v) = j {
|
||||||
|
|
||||||
if !sum.unit.compatible_with(&v.unit) {
|
if !sum.unit.compatible_with(&v.unit) {
|
||||||
return Err(EvalError::IncompatibleUnit);
|
incompatible_units = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sum += v.clone();
|
if !incompatible_units { sum += v.clone(); }
|
||||||
} else { return Ok(None); }
|
loc += *l;
|
||||||
|
} else {
|
||||||
|
if incompatible_units {
|
||||||
|
return Err((loc + *op_loc, EvalError::IncompatibleUnit));
|
||||||
|
}
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
i += 1;
|
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 => {
|
Operator::Subtract => {
|
||||||
|
@ -56,9 +79,9 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
let b = &args[1];
|
let b = &args[1];
|
||||||
|
|
||||||
if let Expression::Quantity(a) = a {
|
if let Expression::Quantity(la, a) = a {
|
||||||
if let Expression::Quantity(b) = b {
|
if let Expression::Quantity(lb, b) = b {
|
||||||
return Ok(Some(Expression::Quantity(a.clone() - b.clone())));
|
return Ok(Some(Expression::Quantity(*la + *lb, a.clone() - b.clone())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,10 +95,10 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
let b = &args[1];
|
let b = &args[1];
|
||||||
|
|
||||||
if let Expression::Quantity(a) = a {
|
if let Expression::Quantity(la, a) = a {
|
||||||
if let Expression::Quantity(b) = b {
|
if let Expression::Quantity(lb, b) = b {
|
||||||
if b.is_zero() { return Err(EvalError::ZeroDivision); }
|
if b.is_zero() { return Err((*la + *lb, EvalError::ZeroDivision)); }
|
||||||
return Ok(Some(Expression::Quantity(a.clone() / b.clone())));
|
return Ok(Some(Expression::Quantity(*la + *lb, a.clone() / b.clone())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,14 +107,23 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
|
|
||||||
Operator::ImplicitMultiply |
|
Operator::ImplicitMultiply |
|
||||||
Operator::Multiply => {
|
Operator::Multiply => {
|
||||||
let mut prod = Quantity::new_rational(1f64).unwrap();
|
let mut prod: Quantity;
|
||||||
for i in args.iter() {
|
let mut loc: LineLocation;
|
||||||
let j = i;
|
if let Expression::Quantity(l, s) = &args[0] {
|
||||||
if let Expression::Quantity(v) = j {
|
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();
|
prod *= v.clone();
|
||||||
|
loc += *l;
|
||||||
} else { return Ok(None); }
|
} else { return Ok(None); }
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
return Ok(Some(Expression::Quantity(prod)));
|
return Ok(Some(Expression::Quantity(loc + *op_loc, prod)));
|
||||||
},
|
},
|
||||||
|
|
||||||
Operator::ModuloLong
|
Operator::ModuloLong
|
||||||
|
@ -100,18 +132,18 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
let b = &args[1];
|
let b = &args[1];
|
||||||
|
|
||||||
if let Expression::Quantity(va) = a {
|
if let Expression::Quantity(la, va) = a {
|
||||||
if let Expression::Quantity(vb) = b {
|
if let Expression::Quantity(lb, vb) = b {
|
||||||
|
|
||||||
if !(va.unitless() && vb.unitless()) {
|
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 vb <= &Quantity::new_rational(1f64).unwrap() { return Err((*la + *lb + *op_loc, EvalError::BadMath)); }
|
||||||
if va.fract() != Quantity::new_rational(0f64).unwrap() { return Err(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(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); }
|
||||||
} else { return Ok(None); }
|
} else { return Ok(None); }
|
||||||
},
|
},
|
||||||
|
@ -121,13 +153,13 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
let b = &args[1];
|
let b = &args[1];
|
||||||
|
|
||||||
if let Expression::Quantity(va) = a {
|
if let Expression::Quantity(la, va) = a {
|
||||||
if let Expression::Quantity(vb) = b {
|
if let Expression::Quantity(lb, vb) = b {
|
||||||
let n = va.clone().convert_to(vb.clone());
|
let n = va.clone().convert_to(vb.clone());
|
||||||
if n.is_none() {
|
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); }
|
||||||
} else { return Ok(None); }
|
} else { return Ok(None); }
|
||||||
},
|
},
|
||||||
|
@ -137,11 +169,11 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
if args.len() != 1 { panic!() }
|
if args.len() != 1 { panic!() }
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
|
|
||||||
if let Expression::Quantity(va) = a {
|
if let Expression::Quantity(l, v) = a {
|
||||||
if va.is_negative() { return Err(EvalError::BadMath); }
|
if v.is_negative() { return Err((*l + *op_loc, EvalError::BadMath)); }
|
||||||
let p = va.pow(Quantity::new_rational_from_string("0.5").unwrap());
|
let p = v.pow(Quantity::new_rational_from_string("0.5").unwrap());
|
||||||
if p.is_nan() {return Err(EvalError::BadMath);}
|
if p.is_nan() {return Err((*l + *op_loc, EvalError::BadMath));}
|
||||||
return Ok(Some(Expression::Quantity(p)));
|
return Ok(Some(Expression::Quantity(*l, p)));
|
||||||
} else { return Ok(None); }
|
} else { return Ok(None); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -150,20 +182,20 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
let b = &args[1];
|
let b = &args[1];
|
||||||
|
|
||||||
if let Expression::Quantity(va) = a {
|
if let Expression::Quantity(la, va) = a {
|
||||||
if let Expression::Quantity(vb) = b {
|
if let Expression::Quantity(lb, vb) = b {
|
||||||
|
|
||||||
if !vb.unitless() {
|
if !vb.unitless() {
|
||||||
return Err(EvalError::IncompatibleUnit);
|
return Err((*lb, EvalError::IncompatibleUnit));
|
||||||
}
|
}
|
||||||
|
|
||||||
if va.is_zero() && vb.is_negative() {
|
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());
|
let p = va.pow(vb.clone());
|
||||||
if p.is_nan() {return Err(EvalError::BadMath);}
|
if p.is_nan() {return Err((*la + *lb + *op_loc, EvalError::BadMath));}
|
||||||
return Ok(Some(Expression::Quantity(p)));
|
return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, p)));
|
||||||
} else { return Ok(None); }
|
} else { return Ok(None); }
|
||||||
} else { return Ok(None); }
|
} else { return Ok(None); }
|
||||||
},
|
},
|
||||||
|
@ -172,14 +204,14 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
if args.len() != 1 {panic!()};
|
if args.len() != 1 {panic!()};
|
||||||
let args = &args[0];
|
let args = &args[0];
|
||||||
|
|
||||||
if let Expression::Quantity(v) = args {
|
if let Expression::Quantity(l, v) = args {
|
||||||
|
|
||||||
if !v.unitless() {
|
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.fract().is_zero() { return Err((*l + *op_loc, EvalError::BadMath)); }
|
||||||
if v > &Quantity::new_rational(50_000f64).unwrap() { return Err(EvalError::TooBig); }
|
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 prod = Quantity::new_rational(1f64).unwrap();
|
||||||
let mut u = v.clone();
|
let mut u = v.clone();
|
||||||
|
@ -188,7 +220,7 @@ pub fn eval_operator(op: &Operator, args: &VecDeque<Expression>, context: &mut C
|
||||||
u = u - Quantity::new_rational(1f64).unwrap();
|
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); }
|
} else { return Ok(None); }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,24 +3,26 @@ use crate::quantity::Quantity;
|
||||||
|
|
||||||
use super::Operator;
|
use super::Operator;
|
||||||
use super::Constant;
|
use super::Constant;
|
||||||
|
use super::super::LineLocation;
|
||||||
|
|
||||||
|
|
||||||
/// Expressions represent logical objects in an expession.
|
/// Expressions represent logical objects in an expession.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Variable(String),
|
Variable(LineLocation, String),
|
||||||
Quantity(Quantity),
|
Quantity(LineLocation, Quantity),
|
||||||
Constant(Constant),
|
Constant(LineLocation, Constant),
|
||||||
Operator(Operator, VecDeque<Expression>),
|
Operator(LineLocation, Operator, VecDeque<Expression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Expression {
|
impl ToString for Expression {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Expression::Quantity(v) => v.to_string(),
|
Expression::Quantity(_, v) => v.to_string(),
|
||||||
Expression::Constant(c) => c.to_string(),
|
Expression::Constant(_, c) => c.to_string(),
|
||||||
Expression::Variable(s) => s.clone(),
|
Expression::Variable(_, s) => s.clone(),
|
||||||
Expression::Operator(o,a) => o.print(a)
|
Expression::Operator(_, o,a) => o.print(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,16 +32,16 @@ impl Expression {
|
||||||
// This sometimes leads to different--usually more verbose--behavior.
|
// This sometimes leads to different--usually more verbose--behavior.
|
||||||
pub fn to_string_outer(&self) -> String {
|
pub fn to_string_outer(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Expression::Quantity(v) => v.to_string_outer(),
|
Expression::Quantity(_, v) => v.to_string_outer(),
|
||||||
Expression::Constant(c) => c.to_string(),
|
Expression::Constant(_, c) => c.to_string(),
|
||||||
Expression::Variable(s) => s.clone(),
|
Expression::Variable(_, s) => s.clone(),
|
||||||
Expression::Operator(o,a) => o.print(a)
|
Expression::Operator(_, o,a) => o.print(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_quantity(&self) -> bool {
|
pub fn is_quantity(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Expression::Quantity(_) => true,
|
Expression::Quantity(_,_) => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +49,7 @@ impl Expression {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_args_mut(&mut self) -> Option<&mut VecDeque<Expression>> {
|
pub fn get_args_mut(&mut self) -> Option<&mut VecDeque<Expression>> {
|
||||||
match self {
|
match self {
|
||||||
Expression::Operator(_, ref mut a) => Some(a),
|
Expression::Operator(_, _, ref mut a) => Some(a),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +57,7 @@ impl Expression {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_args(&self) -> Option<&VecDeque<Expression>> {
|
pub fn get_args(&self) -> Option<&VecDeque<Expression>> {
|
||||||
match self {
|
match self {
|
||||||
Expression::Operator(_, ref a) => Some(a),
|
Expression::Operator(_, _, ref a) => Some(a),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,4 +85,23 @@ impl Expression {
|
||||||
}
|
}
|
||||||
return Some(g);
|
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 },
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -110,7 +110,7 @@ impl Operator {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn add_parens_to_arg(&self, arg: &Expression) -> String {
|
fn add_parens_to_arg(&self, arg: &Expression) -> String {
|
||||||
let mut astr: String = arg.to_string();
|
let mut astr: String = arg.to_string();
|
||||||
if let Expression::Operator(o,_) = arg {
|
if let Expression::Operator(_, o,_) = arg {
|
||||||
if o < self {
|
if o < self {
|
||||||
astr = format!("({})", astr);
|
astr = format!("({})", astr);
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ impl Operator {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn add_parens_to_arg_strict(&self, arg: &Expression) -> String {
|
fn add_parens_to_arg_strict(&self, arg: &Expression) -> String {
|
||||||
let mut astr: String = arg.to_string();
|
let mut astr: String = arg.to_string();
|
||||||
if let Expression::Operator(o,_) = arg {
|
if let Expression::Operator(_, o,_) = arg {
|
||||||
if o <= self {
|
if o <= self {
|
||||||
astr = format!("({})", astr);
|
astr = format!("({})", astr);
|
||||||
}
|
}
|
||||||
|
@ -220,15 +220,15 @@ impl Operator {
|
||||||
// multiplied by a unit (like 10 m)
|
// multiplied by a unit (like 10 m)
|
||||||
// Times sign should stay in all other cases.
|
// Times sign should stay in all other cases.
|
||||||
let no_times = {
|
let no_times = {
|
||||||
if let Expression::Quantity(p) = a {
|
if let Expression::Quantity(_, p) = a {
|
||||||
if let Expression::Quantity(q) = b {
|
if let Expression::Quantity(_, q) = b {
|
||||||
p.unitless() && !q.unitless()
|
p.unitless() && !q.unitless()
|
||||||
} else {false}
|
} else {false}
|
||||||
} else {false}
|
} else {false}
|
||||||
};
|
};
|
||||||
|
|
||||||
if no_times {
|
if no_times {
|
||||||
let Expression::Quantity(u) = b else {panic!()};
|
let Expression::Quantity(_, u) = b else {panic!()};
|
||||||
if u.unit.no_space() {
|
if u.unit.no_space() {
|
||||||
return format!("{}{}",
|
return format!("{}{}",
|
||||||
self.add_parens_to_arg_strict(a),
|
self.add_parens_to_arg_strict(a),
|
||||||
|
@ -252,7 +252,7 @@ impl Operator {
|
||||||
let a = &args[0];
|
let a = &args[0];
|
||||||
let b = &args[1];
|
let b = &args[1];
|
||||||
|
|
||||||
if let Expression::Quantity(q) = a {
|
if let Expression::Quantity(_, q) = a {
|
||||||
if q.is_one() {
|
if q.is_one() {
|
||||||
return format!("{}⁻¹",
|
return format!("{}⁻¹",
|
||||||
self.add_parens_to_arg_strict(b)
|
self.add_parens_to_arg_strict(b)
|
||||||
|
|
|
@ -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<Ordering> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,11 @@ mod stage;
|
||||||
mod token;
|
mod token;
|
||||||
mod parsererror;
|
mod parsererror;
|
||||||
mod expression;
|
mod expression;
|
||||||
|
mod linelocation;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
token::Token,
|
token::Token,
|
||||||
parsererror::ParserError,
|
parsererror::ParserError,
|
||||||
parsererror::LineLocation
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
@ -15,6 +15,7 @@ pub use self::{
|
||||||
expression::Constant,
|
expression::Constant,
|
||||||
expression::Operator,
|
expression::Operator,
|
||||||
expression::Function,
|
expression::Function,
|
||||||
|
linelocation::LineLocation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
@ -27,7 +28,6 @@ pub fn parse(
|
||||||
let expressions = stage::tokenize(s);
|
let expressions = stage::tokenize(s);
|
||||||
let (_, expressions) = stage::find_subs(expressions);
|
let (_, expressions) = stage::find_subs(expressions);
|
||||||
let g = stage::groupify(expressions)?;
|
let g = stage::groupify(expressions)?;
|
||||||
|
|
||||||
let g = stage::treeify(g, context)?;
|
let g = stage::treeify(g, context)?;
|
||||||
|
|
||||||
return Ok(g);
|
return Ok(g);
|
||||||
|
@ -50,11 +50,12 @@ pub fn substitute(
|
||||||
|
|
||||||
let l = s.chars().count();
|
let l = s.chars().count();
|
||||||
let expressions = stage::tokenize(s);
|
let expressions = stage::tokenize(s);
|
||||||
let (subs, _) = stage::find_subs(expressions);
|
let (mut subs, _) = stage::find_subs(expressions);
|
||||||
let mut new_c = l - c;
|
let mut new_c = l - c;
|
||||||
|
|
||||||
for r in subs.iter() {
|
while subs.len() > 0 {
|
||||||
// find_subs gives substitutions in reverse order.
|
let r = subs.pop_back().unwrap();
|
||||||
|
// Apply substitutions in reverse order
|
||||||
|
|
||||||
if { // Don't substitute if our cursor is inside the substitution
|
if { // Don't substitute if our cursor is inside the substitution
|
||||||
c >= r.0.pos &&
|
c >= r.0.pos &&
|
||||||
|
|
|
@ -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.
|
/// Types of parser errors.
|
||||||
/// If we cannot parse a string, one of these is returned.
|
/// If we cannot parse a string, one of these is returned.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -9,12 +9,12 @@ use super::super::{
|
||||||
pub fn find_subs(
|
pub fn find_subs(
|
||||||
mut g: VecDeque<Token>,
|
mut g: VecDeque<Token>,
|
||||||
) -> (
|
) -> (
|
||||||
Vec<(LineLocation, String)>,
|
VecDeque<(LineLocation, String)>,
|
||||||
VecDeque<Token>
|
VecDeque<Token>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Array of replacements
|
// 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
|
// New token array, with updated locations
|
||||||
let mut n: VecDeque<Token> = VecDeque::with_capacity(g.len());
|
let mut n: VecDeque<Token> = VecDeque::with_capacity(g.len());
|
||||||
|
@ -22,8 +22,7 @@ pub fn find_subs(
|
||||||
let mut offset: usize = 0;
|
let mut offset: usize = 0;
|
||||||
|
|
||||||
while g.len() > 0 {
|
while g.len() > 0 {
|
||||||
// Read in reverse. Very important!
|
let mut t = g.pop_front().unwrap();
|
||||||
let mut t = g.pop_back().unwrap();
|
|
||||||
|
|
||||||
let target: Option<&str> = match &mut t {
|
let target: Option<&str> = match &mut t {
|
||||||
Token::Operator(_, s) => {
|
Token::Operator(_, s) => {
|
||||||
|
@ -85,11 +84,14 @@ pub fn find_subs(
|
||||||
} else {
|
} else {
|
||||||
let target = target.unwrap();
|
let target = target.unwrap();
|
||||||
let l = t.get_mut_line_location();
|
let l = t.get_mut_line_location();
|
||||||
r.push((l.clone(), String::from(target)));
|
r.push_back((l.clone(), String::from(target)));
|
||||||
*l = LineLocation{ pos: l.pos - offset, len: target.chars().count()};
|
|
||||||
offset += l.len - target.chars().count();
|
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);
|
return (r, n);
|
||||||
|
|
|
@ -194,9 +194,12 @@ pub fn groupify(
|
||||||
Token,
|
Token,
|
||||||
(LineLocation, ParserError)
|
(LineLocation, ParserError)
|
||||||
> {
|
> {
|
||||||
|
|
||||||
|
let last_linelocation = g.back().unwrap().get_line_location().clone();
|
||||||
|
|
||||||
// Vector of grouping levels
|
// Vector of grouping levels
|
||||||
let mut levels: Vec<(LineLocation, VecDeque<Token>)> = Vec::with_capacity(8);
|
let mut levels: Vec<(LineLocation, VecDeque<Token>)> = 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
|
// Makes sure parenthesis are matched
|
||||||
let mut i_level = 0;
|
let mut i_level = 0;
|
||||||
|
@ -212,10 +215,7 @@ pub fn groupify(
|
||||||
},
|
},
|
||||||
|
|
||||||
Token::GroupEnd(l) => {
|
Token::GroupEnd(l) => {
|
||||||
let l = LineLocation {
|
let l = *l_now + l;
|
||||||
pos: l_now.pos,
|
|
||||||
len: l.len + l.pos - l_now.pos
|
|
||||||
};
|
|
||||||
|
|
||||||
if i_level == 0 { return Err((l, ParserError::ExtraCloseParen)) }
|
if i_level == 0 { return Err((l, ParserError::ExtraCloseParen)) }
|
||||||
if v_now.len() == 0 { return Err((l, ParserError::EmptyGroup)) }
|
if v_now.len() == 0 { return Err((l, ParserError::EmptyGroup)) }
|
||||||
|
@ -259,5 +259,5 @@ pub fn groupify(
|
||||||
let (_, mut v) = levels.pop().unwrap();
|
let (_, mut v) = levels.pop().unwrap();
|
||||||
lookback(&mut v)?;
|
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));
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ fn push_token(g: &mut VecDeque<Token>, t: Option<Token>, stop_i: usize) {
|
||||||
|
|
||||||
Token::Group(_,_)
|
Token::Group(_,_)
|
||||||
| Token::Container(_)
|
| Token::Container(_)
|
||||||
=> panic!()
|
=> unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,11 +85,8 @@ fn treeify_binary(
|
||||||
} {
|
} {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
} else {
|
} else {
|
||||||
let tl = *this.get_line_location();
|
let tl = *this.get_line_location() + *l;
|
||||||
return Err((
|
return Err((tl, ParserError::Syntax));
|
||||||
LineLocation{pos: tl.pos, len: l.pos - tl.pos + l.len},
|
|
||||||
ParserError::Syntax
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,22 +123,33 @@ fn treeify_binary(
|
||||||
let left_pre = g_inner.remove(i-1).unwrap();
|
let left_pre = g_inner.remove(i-1).unwrap();
|
||||||
let this_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 right_pre = g_inner.remove(i-1).unwrap();
|
||||||
let left: Expression; let right: Expression;
|
let mut left: Expression; let mut right: Expression;
|
||||||
if let Token::Group(_, _) = right_pre { right = treeify(right_pre, context)?; } else {right = right_pre.to_expression(context)?;}
|
if let Token::Group(l, _) = right_pre {
|
||||||
if let Token::Group(_, _) = left_pre { left = treeify(left_pre, context)?; } else {left = left_pre.to_expression(context)?;}
|
right = treeify(right_pre, context)?;
|
||||||
|
right.set_linelocation(&(right.get_linelocation() + l));
|
||||||
|
} else {
|
||||||
|
right = right_pre.to_expression(context)?;
|
||||||
|
}
|
||||||
|
|
||||||
let o = {
|
if let Token::Group(l, _) = left_pre {
|
||||||
let Token::Operator(_, s) = this_pre else {panic!()};
|
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);
|
let o = Operator::from_string(&s);
|
||||||
if o.is_none() { panic!() }
|
if o.is_none() { panic!() }
|
||||||
o.unwrap()
|
(l, o.unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_token_args: VecDeque<Expression> = VecDeque::with_capacity(2);
|
let mut new_token_args: VecDeque<Expression> = VecDeque::with_capacity(2);
|
||||||
new_token_args.push_back(left);
|
new_token_args.push_back(left);
|
||||||
new_token_args.push_back(right);
|
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);
|
return Ok(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -204,12 +212,8 @@ fn treeify_unary(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Token::Operator(l, _) = next {
|
if let Token::Operator(l, _) = next {
|
||||||
let tl = *this.get_line_location();
|
let tl = *this.get_line_location() + *l;
|
||||||
return Err((
|
return Err((tl, ParserError::Syntax));
|
||||||
LineLocation{pos: tl.pos, len: l.pos - tl.pos + l.len},
|
|
||||||
ParserError::Syntax
|
|
||||||
));
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// This operator
|
// This operator
|
||||||
|
@ -239,28 +243,34 @@ fn treeify_unary(
|
||||||
|
|
||||||
if next_op.is_none() || this_op > next_op.unwrap() {
|
if next_op.is_none() || this_op > next_op.unwrap() {
|
||||||
let this_pre = g_inner.remove(i).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 {
|
if left_associative {
|
||||||
next_pre = g_inner.remove(i-1).unwrap();
|
next_pre = g_inner.remove(i-1).unwrap();
|
||||||
} else {
|
} else {
|
||||||
next_pre = g_inner.remove(i).unwrap();
|
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);
|
let o = Operator::from_string(&s);
|
||||||
if o.is_none() { panic!() }
|
if o.is_none() { panic!() }
|
||||||
o.unwrap()
|
(l, o.unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_token_args: VecDeque<Expression> = VecDeque::with_capacity(3);
|
let mut new_token_args: VecDeque<Expression> = VecDeque::with_capacity(3);
|
||||||
new_token_args.push_back(next);
|
new_token_args.push_back(next);
|
||||||
|
|
||||||
if left_associative {
|
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 {
|
} 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);
|
return Ok(true);
|
||||||
|
|
|
@ -74,20 +74,20 @@ impl Token {
|
||||||
return Err((l, ParserError::BadNumber))
|
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);
|
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);
|
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);
|
let c = context.get_variable(&s);
|
||||||
if c.is_some() { return Ok(Expression::Variable(s)); }
|
if c.is_some() { return Ok(Expression::Variable(l, s)); }
|
||||||
return Ok(Expression::Variable(s));
|
return Ok(Expression::Variable(l, s));
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Container(v) => { return Ok(v); }
|
Token::Container(v) => { return Ok(v); }
|
||||||
|
@ -96,7 +96,7 @@ impl Token {
|
||||||
| Token::GroupStart(_)
|
| Token::GroupStart(_)
|
||||||
| Token::GroupEnd(_)
|
| Token::GroupEnd(_)
|
||||||
| Token::Group(_, _)
|
| Token::Group(_, _)
|
||||||
=> panic!()
|
=> panic!("This token cannot be converted to an expression")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,37 @@ impl Quantity {
|
||||||
return Some(n.mul_no_convert(fa).div_no_convert(fb))
|
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() }
|
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;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(self, other: Self) -> Self::Output {
|
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;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
if self.unit != o.unit {
|
||||||
|
@ -240,7 +271,7 @@ impl Add for Quantity {
|
||||||
|
|
||||||
impl AddAssign for Quantity where {
|
impl AddAssign for Quantity where {
|
||||||
fn add_assign(&mut self, other: Self) {
|
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;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
if self.unit != o.unit {
|
||||||
|
@ -255,7 +286,7 @@ impl Sub for Quantity {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn sub(self, other: Self) -> Self::Output {
|
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;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
if self.unit != o.unit {
|
||||||
|
@ -271,7 +302,7 @@ impl Sub for Quantity {
|
||||||
|
|
||||||
impl SubAssign for Quantity where {
|
impl SubAssign for Quantity where {
|
||||||
fn sub_assign(&mut self, other: Self) {
|
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;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
if self.unit != o.unit {
|
||||||
|
@ -282,24 +313,13 @@ impl SubAssign for Quantity where {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Mul for Quantity {
|
impl Mul for Quantity {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn mul(self, other: Self) -> Self::Output {
|
fn mul(self, other: Self) -> Self::Output {
|
||||||
|
|
||||||
|
|
||||||
let mut o = other;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
o.match_units(&self);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Quantity {
|
Quantity {
|
||||||
scalar: self.scalar * o.scalar,
|
scalar: self.scalar * o.scalar,
|
||||||
|
@ -311,18 +331,8 @@ impl Mul for Quantity {
|
||||||
impl MulAssign for Quantity where {
|
impl MulAssign for Quantity where {
|
||||||
fn mul_assign(&mut self, other: Self) {
|
fn mul_assign(&mut self, other: Self) {
|
||||||
|
|
||||||
|
|
||||||
let mut o = other;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
o.match_units(&self);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scalar *= o.scalar;
|
self.scalar *= o.scalar;
|
||||||
self.unit *= o.unit;
|
self.unit *= o.unit;
|
||||||
|
@ -335,16 +345,7 @@ impl Div for Quantity {
|
||||||
fn div(self, other: Self) -> Self::Output {
|
fn div(self, other: Self) -> Self::Output {
|
||||||
|
|
||||||
let mut o = other;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
o.match_units(&self);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Quantity {
|
Quantity {
|
||||||
scalar: self.scalar / o.scalar,
|
scalar: self.scalar / o.scalar,
|
||||||
|
@ -357,16 +358,7 @@ impl DivAssign for Quantity where {
|
||||||
fn div_assign(&mut self, other: Self) {
|
fn div_assign(&mut self, other: Self) {
|
||||||
|
|
||||||
let mut o = other;
|
let mut o = other;
|
||||||
if self.unit != o.unit {
|
o.match_units(&self);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scalar /= o.scalar;
|
self.scalar /= o.scalar;
|
||||||
self.unit /= o.unit;
|
self.unit /= o.unit;
|
||||||
|
@ -377,8 +369,8 @@ impl Rem<Quantity> for Quantity {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn rem(self, other: Quantity) -> Self::Output {
|
fn rem(self, other: Quantity) -> Self::Output {
|
||||||
if !self.unit.unitless() { panic!() }
|
if !self.unit.unitless() { panic!("Tried to % a quantity with units") }
|
||||||
if !other.unit.unitless() { panic!() }
|
if !other.unit.unitless() { panic!("Tried to % by a quantity with units") }
|
||||||
|
|
||||||
Quantity {
|
Quantity {
|
||||||
scalar: self.scalar % other.scalar,
|
scalar: self.scalar % other.scalar,
|
||||||
|
@ -397,7 +389,7 @@ impl PartialEq for Quantity {
|
||||||
|
|
||||||
impl PartialOrd for Quantity {
|
impl PartialOrd for Quantity {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
if self.unit != other.unit { panic!() }
|
if self.unit != other.unit { panic!("Tried to compare incompatible units") }
|
||||||
self.scalar.partial_cmp(&other.scalar)
|
self.scalar.partial_cmp(&other.scalar)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -52,7 +52,7 @@ impl ToString for Unit {
|
||||||
'7' => '⁷',
|
'7' => '⁷',
|
||||||
'8' => '⁸',
|
'8' => '⁸',
|
||||||
'9' => '⁹',
|
'9' => '⁹',
|
||||||
_ => panic!()
|
_ => unreachable!()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
t.push('·');
|
t.push('·');
|
||||||
|
@ -90,7 +90,7 @@ impl ToString for Unit {
|
||||||
'7' => '⁷',
|
'7' => '⁷',
|
||||||
'8' => '⁸',
|
'8' => '⁸',
|
||||||
'9' => '⁹',
|
'9' => '⁹',
|
||||||
_ => panic!()
|
_ => unreachable!()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
b.push('·');
|
b.push('·');
|
||||||
|
@ -154,6 +154,7 @@ impl Unit {
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if base units are the same
|
// True if base units are the same
|
||||||
|
// compatible <=> can be converted to
|
||||||
pub fn compatible_with(&self, other: &Unit) -> bool {
|
pub fn compatible_with(&self, other: &Unit) -> bool {
|
||||||
let s = self.clone() * self.to_base_factor().unit;
|
let s = self.clone() * self.to_base_factor().unit;
|
||||||
let o = other.clone() * other.to_base_factor().unit;
|
let o = other.clone() * other.to_base_factor().unit;
|
||||||
|
@ -161,42 +162,27 @@ impl Unit {
|
||||||
return o == s;
|
return o == s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if these two units have a common factor
|
|
||||||
pub fn common_factor(&self, other: &Unit) -> Option<Quantity> {
|
|
||||||
|
|
||||||
if self.unitless() || other.unitless() { return None; }
|
// 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.
|
||||||
let mut failed = false;
|
pub fn compatible_with_power(&self, other: &Unit) -> Option<Scalar> {
|
||||||
|
|
||||||
// What to convert `other` to before multiplying
|
|
||||||
let mut factor = Quantity::new_rational_from_string("1").unwrap();
|
|
||||||
let mut flag;
|
let mut flag;
|
||||||
for (us, _) in self.get_val() {
|
let mut pow_factor: Option<Scalar> = None;
|
||||||
|
|
||||||
|
let sbu = self.to_base().unit;
|
||||||
|
let obu = other.to_base().unit;
|
||||||
|
|
||||||
|
for (us, ps) in sbu.get_val() {
|
||||||
flag = false;
|
flag = false;
|
||||||
for (uo, po) in other.get_val() {
|
for (uo, po) in obu.get_val() {
|
||||||
if {
|
if uo.whole == us.whole {
|
||||||
us.to_base().unit.compatible_with(&uo.to_base().unit)
|
if pow_factor.is_none() {
|
||||||
} {
|
pow_factor = Some(po.clone() / ps.clone());
|
||||||
factor.insert_unit(us.clone(), po.clone());
|
} else if let Some(ref f) = pow_factor {
|
||||||
flag = true;
|
if *f != po.clone() / ps.clone() { return None; }
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !flag { failed = true }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
flag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -204,7 +190,25 @@ impl Unit {
|
||||||
if !flag { return None; }
|
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) {
|
pub fn insert(&mut self, u: FreeUnit, p: Scalar) {
|
||||||
|
@ -229,6 +233,7 @@ impl Unit {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a unit `u` so that `self * u` contains only base units.
|
||||||
pub fn to_base_factor(&self) -> Quantity {
|
pub fn to_base_factor(&self) -> Quantity {
|
||||||
let mut q = Quantity::new_rational(1f64).unwrap();
|
let mut q = Quantity::new_rational(1f64).unwrap();
|
||||||
|
|
||||||
|
@ -240,6 +245,7 @@ impl Unit {
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a unit `u` equivalent to `self` that contains only base units.
|
||||||
pub fn to_base(&self) -> Quantity {
|
pub fn to_base(&self) -> Quantity {
|
||||||
let mut q = Quantity::new_rational(1f64).unwrap();
|
let mut q = Quantity::new_rational(1f64).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -210,4 +210,7 @@ fn complex_units() {
|
||||||
good_expr("62.137 mi/h", "100 km/h to mph");
|
good_expr("62.137 mi/h", "100 km/h to mph");
|
||||||
good_expr("20 mi", "10 mph * 2 hours");
|
good_expr("20 mi", "10 mph * 2 hours");
|
||||||
good_expr("120 m", "1 (m/s) * 2 min");
|
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");
|
||||||
}
|
}
|
Loading…
Reference in New Issue