mirror of
https://github.com/tiffany352/rink-rs
synced 2024-11-10 13:44:15 +00:00
Implement suffixes for celsius and fahrenheit
This commit is contained in:
parent
a7e847781b
commit
aec9dad521
3 changed files with 208 additions and 96 deletions
232
src/eval.rs
232
src/eval.rs
|
@ -247,7 +247,7 @@ impl Context {
|
|||
/// Evaluates an expression to compute its value, *excluding* `->`
|
||||
/// conversions.
|
||||
pub fn eval(&self, expr: &::unit_defs::Expr) -> Result<Value, String> {
|
||||
use unit_defs::Expr;
|
||||
use unit_defs::{Expr, SuffixOp};
|
||||
|
||||
macro_rules! operator {
|
||||
($left:ident $op:ident $opname:tt $right:ident) => {{
|
||||
|
@ -272,12 +272,43 @@ impl Context {
|
|||
Expr::Date(ref date) => date::try_decode(date, self).map(Value::DateTime),
|
||||
Expr::Neg(ref expr) => self.eval(&**expr).and_then(|v| -&v),
|
||||
Expr::Plus(ref expr) => self.eval(&**expr),
|
||||
Expr::DegC => Err(format!("°C is an operator")),
|
||||
Expr::DegF => Err(format!("°F is an operator")),
|
||||
|
||||
Expr::Frac(ref left, ref right) => operator!(left div / right),
|
||||
Expr::Add(ref left, ref right) => operator!(left add + right),
|
||||
Expr::Sub(ref left, ref right) => operator!(left sub - right),
|
||||
Expr::Pow(ref left, ref right) => operator!(left pow ^ right),
|
||||
|
||||
Expr::Suffix(SuffixOp::Celsius, ref left) => {
|
||||
let left = try!(self.eval(&**left));
|
||||
let left = match left {
|
||||
Value::Number(left) => left,
|
||||
_ => return Err(format!("Left-hand side of celsius literal must be number"))
|
||||
};
|
||||
if left.1 != BTreeMap::new() {
|
||||
Err(format!("Left-hand side of celsius literal must be dimensionless"))
|
||||
} else {
|
||||
let left = (&left * &self.lookup("kelvin").expect("Missing kelvin unit")).unwrap();
|
||||
Ok(Value::Number((&left + &self.lookup("zerocelsius")
|
||||
.expect("Missing zerocelsius constant")).unwrap()))
|
||||
}
|
||||
},
|
||||
Expr::Suffix(SuffixOp::Fahrenheit, ref left) => {
|
||||
let left = try!(self.eval(&**left));
|
||||
let left = match left {
|
||||
Value::Number(left) => left,
|
||||
_ => return Err(format!("Left-hand side of fahrenheit literal must be number"))
|
||||
};
|
||||
if left.1 != BTreeMap::new() {
|
||||
Err(format!("Left-hand side of fahrenheit literal must be dimensionless"))
|
||||
} else {
|
||||
let left = (&left * &self.lookup("Rankine").expect("Missing rankine unit")).unwrap();
|
||||
Ok(Value::Number((&left + &self.lookup("zerofahrenheit")
|
||||
.expect("Missing zerofahrenheit constant")).unwrap()))
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: A type might not implement * on Number, and this would fail
|
||||
Expr::Mul(ref args) => args.iter().fold(Ok(Value::Number(Number::one())), |a, b| {
|
||||
a.and_then(|a| {
|
||||
|
@ -354,6 +385,8 @@ impl Context {
|
|||
},
|
||||
Expr::Neg(ref v) => self.eval_unit_name(v),
|
||||
Expr::Plus(ref v) => self.eval_unit_name(v),
|
||||
Expr::Suffix(_, _) | Expr::DegC | Expr::DegF =>
|
||||
Err(format!("Temperature conversions must not be compound units")),
|
||||
Expr::Date(_) => Err(format!("Dates are not allowed in the right hand side of conversions")),
|
||||
Expr::Convert(_, _) => Err(format!("Conversions are not allowed in the right hand of conversions")),
|
||||
Expr::Error(ref e) => Err(e.clone()),
|
||||
|
@ -364,6 +397,88 @@ impl Context {
|
|||
pub fn eval_outer(&self, expr: &::unit_defs::Expr) -> Result<String, String> {
|
||||
use unit_defs::Expr;
|
||||
|
||||
let conformance_err = |top: &Number, bottom: &Number| -> String {
|
||||
use std::io::Write;
|
||||
|
||||
let mut buf = vec![];
|
||||
let width = 12;
|
||||
let mut topu = top.clone();
|
||||
topu.0 = Mpq::one();
|
||||
let mut bottomu = bottom.clone();
|
||||
bottomu.0 = Mpq::one();
|
||||
let left = topu.show(self);
|
||||
let right = bottomu.show(self);
|
||||
if self.short_output {
|
||||
writeln!(buf, "Conformance error [ {left} || {right} ]",
|
||||
left=left, right=right).unwrap();
|
||||
} else {
|
||||
writeln!(buf, concat!("Conformance error\n",
|
||||
"{:>width$}: {left}\n",
|
||||
"{:>width$}: {right}"),
|
||||
"Left side", "Right side", left=left, right=right, width=width).unwrap();
|
||||
}
|
||||
let diff = (&topu / &bottomu).unwrap();
|
||||
let (recip, desc) = self.describe_unit(&diff.invert());
|
||||
let word = match recip {
|
||||
false => "multiply",
|
||||
true => "divide"
|
||||
};
|
||||
writeln!(buf, "{:>width$}: {word} left side by {}", "Suggestion",
|
||||
desc.trim(), width=width, word=word).unwrap();
|
||||
let (recip, desc) = self.describe_unit(&diff);
|
||||
let word = match recip {
|
||||
false => "multiply",
|
||||
true => "divide"
|
||||
};
|
||||
writeln!(buf, "{:>width$} {word} right side by {}", "",
|
||||
desc.trim(), width=width, word=word).unwrap();
|
||||
|
||||
String::from_utf8(buf).unwrap()
|
||||
};
|
||||
|
||||
let show = |raw: &Number, bottom: &Number, bottom_name: BTreeMap<String, isize>| -> String {
|
||||
let number = raw.show_number_part();
|
||||
let mut unit_top = vec![];
|
||||
let mut unit_frac = vec![];
|
||||
for (name, exp) in bottom_name.into_iter() {
|
||||
if exp < 0 {
|
||||
unit_frac.push((name, -exp));
|
||||
} else {
|
||||
unit_top.push((name, exp));
|
||||
}
|
||||
}
|
||||
let unit_top = unit_top.into_iter().fold(String::new(), |mut acc, (name, exp)| {
|
||||
acc.push(' ');
|
||||
acc.push_str(&*name);
|
||||
if exp != 1 {
|
||||
acc.push_str(&*format!("^{}", exp));
|
||||
}
|
||||
acc
|
||||
});
|
||||
let unit_frac = unit_frac.into_iter().fold(String::new(), |mut acc, (name, exp)| {
|
||||
if acc.len() > 0 {
|
||||
acc.push(' ');
|
||||
}
|
||||
acc.push_str(&*name);
|
||||
if exp != 1 {
|
||||
acc.push_str(&*format!("^{}", exp));
|
||||
}
|
||||
acc
|
||||
});
|
||||
let unit_frac = if unit_frac.len() > 0 {
|
||||
format!(" / {}", unit_frac)
|
||||
} else {
|
||||
unit_frac
|
||||
};
|
||||
let reduced = match self.describe_unit(&bottom) {
|
||||
(false, v) => v,
|
||||
(true, v) => format!("1 / {}", v)
|
||||
};
|
||||
format!("{number}{unit_top}{unit_frac} ({reduced})",
|
||||
number=number, unit_top=unit_top,
|
||||
unit_frac=unit_frac, reduced=reduced)
|
||||
};
|
||||
|
||||
match *expr {
|
||||
Expr::Convert(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom), self.eval_unit_name(&**bottom)) {
|
||||
(Ok(top), Ok(bottom), Ok(bottom_name)) => {
|
||||
|
@ -372,92 +487,53 @@ impl Context {
|
|||
_ => return Err(format!("Conversion of non-numbers is not defined"))
|
||||
};
|
||||
if top.1 != bottom.1 {
|
||||
use std::io::Write;
|
||||
|
||||
let mut buf = vec![];
|
||||
let width = 12;
|
||||
let mut topu = top.clone();
|
||||
topu.0 = Mpq::one();
|
||||
let mut bottomu = bottom.clone();
|
||||
bottomu.0 = Mpq::one();
|
||||
let left = topu.show(self);
|
||||
let right = bottomu.show(self);
|
||||
if self.short_output {
|
||||
writeln!(buf, "Conformance error [ {left} || {right} ]",
|
||||
left=left, right=right).unwrap();
|
||||
} else {
|
||||
writeln!(buf, concat!("Conformance error\n",
|
||||
"{:>width$}: {left}\n",
|
||||
"{:>width$}: {right}"),
|
||||
"Left side", "Right side", left=left, right=right, width=width).unwrap();
|
||||
}
|
||||
let diff = (&topu / &bottomu).unwrap();
|
||||
let (recip, desc) = self.describe_unit(&diff.invert());
|
||||
let word = match recip {
|
||||
false => "multiply",
|
||||
true => "divide"
|
||||
};
|
||||
writeln!(buf, "{:>width$}: {word} left side by {}", "Suggestion",
|
||||
desc.trim(), width=width, word=word).unwrap();
|
||||
let (recip, desc) = self.describe_unit(&diff);
|
||||
let word = match recip {
|
||||
false => "multiply",
|
||||
true => "divide"
|
||||
};
|
||||
writeln!(buf, "{:>width$} {word} right side by {}", "",
|
||||
desc.trim(), width=width, word=word).unwrap();
|
||||
|
||||
Err(String::from_utf8(buf).unwrap())
|
||||
Err(conformance_err(&top, &bottom))
|
||||
} else {
|
||||
let raw = match &top / &bottom {
|
||||
Some(raw) => raw,
|
||||
None => return Err(format!("Division by zero: {} / {}",
|
||||
top.show(self), bottom.show(self)))
|
||||
};
|
||||
let number = raw.show_number_part();
|
||||
let mut unit_top = vec![];
|
||||
let mut unit_frac = vec![];
|
||||
for (name, exp) in bottom_name.into_iter() {
|
||||
if exp < 0 {
|
||||
unit_frac.push((name, -exp));
|
||||
} else {
|
||||
unit_top.push((name, exp));
|
||||
}
|
||||
}
|
||||
let unit_top = unit_top.into_iter().fold(String::new(), |mut acc, (name, exp)| {
|
||||
acc.push(' ');
|
||||
acc.push_str(&*name);
|
||||
if exp != 1 {
|
||||
acc.push_str(&*format!("^{}", exp));
|
||||
}
|
||||
acc
|
||||
});
|
||||
let unit_frac = unit_frac.into_iter().fold(String::new(), |mut acc, (name, exp)| {
|
||||
if acc.len() > 0 {
|
||||
acc.push(' ');
|
||||
}
|
||||
acc.push_str(&*name);
|
||||
if exp != 1 {
|
||||
acc.push_str(&*format!("^{}", exp));
|
||||
}
|
||||
acc
|
||||
});
|
||||
let unit_frac = if unit_frac.len() > 0 {
|
||||
format!(" / {}", unit_frac)
|
||||
} else {
|
||||
unit_frac
|
||||
};
|
||||
let reduced = match self.describe_unit(&bottom) {
|
||||
(false, v) => v,
|
||||
(true, v) => format!("1 / {}", v)
|
||||
};
|
||||
Ok(format!("{number}{unit_top}{unit_frac} ({reduced})",
|
||||
number=number, unit_top=unit_top,
|
||||
unit_frac=unit_frac, reduced=reduced))
|
||||
Ok(show(&raw, &bottom, bottom_name))
|
||||
}
|
||||
},
|
||||
(Ok(ref top), Err(ref e), _) => match **bottom {
|
||||
Expr::DegC => {
|
||||
let top = match *top {
|
||||
Value::Number(ref num) => num,
|
||||
_ => return Err(format!("Expected number"))
|
||||
};
|
||||
let bottom = self.lookup("kelvin").expect("Unit kelvin missing");
|
||||
if top.1 != bottom.1 {
|
||||
Err(conformance_err(&top, &bottom))
|
||||
} else {
|
||||
let res = (top - &self.lookup("zerocelsius")
|
||||
.expect("Unit zerocelsius missing")).unwrap();
|
||||
let mut name = BTreeMap::new();
|
||||
name.insert("°C".to_owned(), 1);
|
||||
Ok(show(&res, &bottom, name))
|
||||
}
|
||||
},
|
||||
Expr::DegF => {
|
||||
let top = match *top {
|
||||
Value::Number(ref num) => num,
|
||||
_ => return Err(format!("Expected number"))
|
||||
};
|
||||
let bottom = self.lookup("kelvin").expect("Unit kelvin missing");
|
||||
if top.1 != bottom.1 {
|
||||
Err(conformance_err(&top, &bottom))
|
||||
} else {
|
||||
let res = (top - &self.lookup("zerofahrenheit")
|
||||
.expect("Unit zerofahrenheit missing")).unwrap();
|
||||
let res = (&res / &self.lookup("Rankine").expect("Unit Rankine missing")).unwrap();
|
||||
let mut name = BTreeMap::new();
|
||||
name.insert("°F".to_owned(), 1);
|
||||
Ok(show(&res, &bottom, name))
|
||||
}
|
||||
},
|
||||
_ => Err(e.clone())
|
||||
},
|
||||
(Err(e), _, _) => Err(e),
|
||||
(_, Err(e), _) => Err(e),
|
||||
(_, _, Err(e)) => Err(e),
|
||||
},
|
||||
_ => {
|
||||
|
|
|
@ -31,6 +31,8 @@ pub enum Token {
|
|||
DashArrow,
|
||||
Hash,
|
||||
ImaginaryUnit,
|
||||
DegC,
|
||||
DegF,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
|
@ -246,7 +248,11 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
break;
|
||||
}
|
||||
}
|
||||
Token::Ident(buf)
|
||||
match &*buf {
|
||||
"degC" | "°C" | "celsius" => Token::DegC,
|
||||
"degF" | "°F" | "fahrenheit" => Token::DegF,
|
||||
_ => Token::Ident(buf)
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(res)
|
||||
|
@ -255,6 +261,12 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
|
||||
pub type Iter<'a> = Peekable<TokenIterator<'a>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SuffixOp {
|
||||
Celsius,
|
||||
Fahrenheit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
Unit(String),
|
||||
|
@ -270,6 +282,9 @@ pub enum Expr {
|
|||
Plus(Box<Expr>),
|
||||
Convert(Box<Expr>, Box<Expr>),
|
||||
Equals(Box<Expr>, Box<Expr>),
|
||||
Suffix(SuffixOp, Box<Expr>),
|
||||
DegC,
|
||||
DegF,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
|
@ -345,9 +360,9 @@ fn parse_pow(mut iter: &mut Iter) -> Expr {
|
|||
|
||||
fn parse_mul(mut iter: &mut Iter) -> Expr {
|
||||
let mut terms = vec![parse_pow(iter)];
|
||||
loop { match *iter.peek().unwrap() {
|
||||
Token::Equals | Token::Plus | Token::Minus | Token::DashArrow | Token::TriplePipe |
|
||||
Token::RPar | Token::Newline | Token::Comment(_) | Token::Eof => break,
|
||||
loop { match iter.peek().cloned().unwrap() {
|
||||
Token::DegC | Token::DegF | Token::Equals | Token::Plus | Token::Minus | Token::DashArrow |
|
||||
Token::TriplePipe | Token::RPar | Token::Newline | Token::Comment(_) | Token::Eof => break,
|
||||
Token::Slash => {
|
||||
iter.next();
|
||||
let right = parse_pow(iter);
|
||||
|
@ -370,17 +385,32 @@ fn parse_mul(mut iter: &mut Iter) -> Expr {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_add(mut iter: &mut Iter) -> Expr {
|
||||
fn parse_suffix(mut iter: &mut Iter) -> Expr {
|
||||
let left = parse_mul(iter);
|
||||
match iter.peek().cloned().unwrap() {
|
||||
Token::DegC => {
|
||||
iter.next();
|
||||
Expr::Suffix(SuffixOp::Celsius, Box::new(left))
|
||||
},
|
||||
Token::DegF => {
|
||||
iter.next();
|
||||
Expr::Suffix(SuffixOp::Fahrenheit, Box::new(left))
|
||||
},
|
||||
_ => left
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_add(mut iter: &mut Iter) -> Expr {
|
||||
let left = parse_suffix(iter);
|
||||
match *iter.peek().unwrap() {
|
||||
Token::Plus => {
|
||||
iter.next();
|
||||
let right = parse_add(iter);
|
||||
let right = parse_suffix(iter);
|
||||
Expr::Add(Box::new(left), Box::new(right))
|
||||
},
|
||||
Token::Minus => {
|
||||
iter.next();
|
||||
let right = parse_add(iter);
|
||||
let right = parse_suffix(iter);
|
||||
Expr::Sub(Box::new(left), Box::new(right))
|
||||
},
|
||||
_ => left
|
||||
|
@ -404,7 +434,11 @@ pub fn parse_expr(mut iter: &mut Iter) -> Expr {
|
|||
match iter.peek().cloned().unwrap() {
|
||||
Token::DashArrow => {
|
||||
iter.next();
|
||||
let right = parse_eq(iter);
|
||||
let right = match iter.peek().cloned().unwrap() {
|
||||
Token::DegC => Expr::DegC,
|
||||
Token::DegF => Expr::DegF,
|
||||
_ => parse_eq(iter)
|
||||
};
|
||||
Expr::Convert(Box::new(left), Box::new(right))
|
||||
},
|
||||
_ => left
|
||||
|
|
22
units.txt
22
units.txt
|
@ -1189,13 +1189,13 @@ basispoint := 1/100 percent// Used in finance
|
|||
// Temperature difference
|
||||
// The units below are NOT an absolute temperature measurement in Fahrenheit,
|
||||
// but represents the size of a degree in the specified systems.
|
||||
degcelsius := K
|
||||
degreeCelsius := K // Per http://physics.nist.gov/Pubs/SP811/sec04.html#4.2.1.1
|
||||
//degcelsius := K
|
||||
//degreeCelsius := K // Per http://physics.nist.gov/Pubs/SP811/sec04.html#4.2.1.1
|
||||
|
||||
// This is allowable in the SI under resolutions 3 and 4 of the 13th CGPM:
|
||||
// http://www.bipm.org/en/CGPM/db/13/3/
|
||||
|
||||
degC := K // The *size* of a degree in the Celsius scale.
|
||||
//degC := K // The *size* of a degree in the Celsius scale.
|
||||
// This is identical to the size of a Kelvin.
|
||||
// WARNING: This should only be used when
|
||||
// you're indicating the *difference* between
|
||||
|
@ -1234,11 +1234,11 @@ zerocelsius := 273.15 K // Defined by the 10th CGPM, 1954, Resolution 3;
|
|||
// and a degree Celsius are the same, but
|
||||
// the zero point of the Celsius scale is actually
|
||||
// set to .01 Kelvin below the triple point.
|
||||
|
||||
|
||||
degfahrenheit := 5/9 degC // The *size* of a degree in the Fahrenheit scale.
|
||||
degreeFahrenheit := degfahrenheit // The *size* of a degree in the Fahrenheit scale.
|
||||
degF := degfahrenheit // WARNING: These should only be used when
|
||||
|
||||
//degfahrenheit := 5/9 kelvin // The *size* of a degree in the Fahrenheit scale.
|
||||
//degreeFahrenheit := degfahrenheit // The *size* of a degree in the Fahrenheit scale.
|
||||
//degF := degfahrenheit // WARNING: These should only be used when
|
||||
// you're indicating the *difference* between
|
||||
// two temperatures, (say, how much energy to
|
||||
// raise the temperature of a gram of water by 5
|
||||
|
@ -1254,7 +1254,7 @@ degF := degfahrenheit // WARNING: These should only be used when
|
|||
// to body heat (for reasons unknown).
|
||||
|
||||
|
||||
\u2109 := degfahrenheit // Single Unicode codepoint for
|
||||
//\u2109 := degfahrenheit // Single Unicode codepoint for
|
||||
// DEGREE FAHRENHEIT
|
||||
|
||||
degreesRankine := 5/9 K
|
||||
|
@ -1264,7 +1264,9 @@ degreerankine := degrankine // is at absolute zero.
|
|||
degR := degrankine
|
||||
Rankine := degreesrankine
|
||||
|
||||
degreaumur := 10/8 degC // The Reaumur scale was used in Europe and
|
||||
zerofahrenheit := zerocelsius - 32 degR
|
||||
|
||||
//degreaumur := 10/8 kelvin // The Reaumur scale was used in Europe and
|
||||
// particularly in France. It is defined
|
||||
// to be 0 at the freezing point of water
|
||||
// and 80 at the boiling point. Reaumur
|
||||
|
@ -2857,7 +2859,7 @@ Wh := watt hour
|
|||
|
||||
Rvalue := degrankine ft^2 hr / Btu // r-value, U.S. insulation figure
|
||||
Cvalue := 1/Rvalue // C-value U.S. insulation conductance rating
|
||||
kvalue := Btu in / (ft^2 hr degF) // k-value, insulation conductance/in thick
|
||||
kvalue := Btu in / (ft^2 hr degrankine) // k-value, insulation conductance/in thick
|
||||
Uvalue := 1/Rvalue
|
||||
europeanUvalue := watt / (m^2 K)
|
||||
RSI := K m^2 / W // SI insulation figure
|
||||
|
|
Loading…
Reference in a new issue