mirror of
https://github.com/tiffany352/rink-rs
synced 2024-11-10 05:34:14 +00:00
After merging PR#40
This commit is contained in:
commit
7197d91e47
22 changed files with 731 additions and 949 deletions
|
@ -30,6 +30,7 @@ xml-rs = { version = "0.3.4", optional = true }
|
|||
json = { version = "0.10.2", optional = true }
|
||||
serde = { version = "0.8.16", optional = true }
|
||||
serde_derive = { version = "0.8.16", optional = true }
|
||||
dirs = "1.0.4"
|
||||
|
||||
[[bin]]
|
||||
name = "rink"
|
||||
|
|
|
@ -50,7 +50,7 @@ fn main() {
|
|||
let mut i = 0;
|
||||
let reply = eval(line);
|
||||
for line in reply.lines() {
|
||||
if line.trim().len() > 0 {
|
||||
if !line.trim().is_empty() {
|
||||
server.send(Command::NOTICE(reply_to.to_owned(), line.to_owned())).unwrap();
|
||||
i += 1;
|
||||
}
|
||||
|
|
142
src/ast.rs
142
src/ast.rs
|
@ -8,7 +8,7 @@ use num::Num;
|
|||
use chrono_tz::Tz;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SuffixOp {
|
||||
pub enum Degree {
|
||||
Celsius,
|
||||
Fahrenheit,
|
||||
Reaumur,
|
||||
|
@ -42,22 +42,95 @@ pub enum Expr {
|
|||
Neg(Box<Expr>),
|
||||
Plus(Box<Expr>),
|
||||
Equals(Box<Expr>, Box<Expr>),
|
||||
Suffix(SuffixOp, Box<Expr>),
|
||||
Suffix(Degree, Box<Expr>),
|
||||
Of(String, Box<Expr>),
|
||||
Call(String, Vec<Expr>),
|
||||
Call(Function, Vec<Expr>),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Function {
|
||||
Sqrt,
|
||||
Exp,
|
||||
Ln,
|
||||
Log2,
|
||||
Log10,
|
||||
Sin,
|
||||
Cos,
|
||||
Tan,
|
||||
Asin,
|
||||
Acos,
|
||||
Atan,
|
||||
Sinh,
|
||||
Cosh,
|
||||
Tanh,
|
||||
Asinh,
|
||||
Acosh,
|
||||
Atanh,
|
||||
Log,
|
||||
Hypot,
|
||||
Atan2,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn name(&self) -> &str {
|
||||
match *self {
|
||||
Function::Sqrt => "sqrt",
|
||||
Function::Exp => "exp",
|
||||
Function::Ln => "ln",
|
||||
Function::Log2 => "log2",
|
||||
Function::Log10 => "log10",
|
||||
Function::Sin => "sin",
|
||||
Function::Cos => "cos",
|
||||
Function::Tan => "tan",
|
||||
Function::Asin => "asin",
|
||||
Function::Acos => "acos",
|
||||
Function::Atan => "atan",
|
||||
Function::Sinh => "sinh",
|
||||
Function::Cosh => "cosh",
|
||||
Function::Tanh => "tanh",
|
||||
Function::Asinh => "asinh",
|
||||
Function::Acosh => "acosh",
|
||||
Function::Atanh => "atanh",
|
||||
Function::Log => "log",
|
||||
Function::Hypot => "hypot",
|
||||
Function::Atan2 => "atan2",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_name(s: &str) -> Option<Self> {
|
||||
let func = match s {
|
||||
"sqrt" => Function::Sqrt,
|
||||
"exp" => Function::Exp,
|
||||
"ln" => Function::Ln,
|
||||
"log2" => Function::Log2,
|
||||
"log10" => Function::Log10,
|
||||
"sin" => Function::Sin,
|
||||
"cos" => Function::Cos,
|
||||
"tan" => Function::Tan,
|
||||
"asin" => Function::Asin,
|
||||
"acos" => Function::Acos,
|
||||
"atan" => Function::Atan,
|
||||
"sinh" => Function::Sinh,
|
||||
"cosh" => Function::Cosh,
|
||||
"tanh" => Function::Tanh,
|
||||
"asinh" => Function::Asinh,
|
||||
"acosh" => Function::Acosh,
|
||||
"atanh" => Function::Atanh,
|
||||
"log" => Function::Log,
|
||||
"hypot" => Function::Hypot,
|
||||
"atan2" => Function::Atan2,
|
||||
_ => return None,
|
||||
};
|
||||
Some(func)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Conversion {
|
||||
None,
|
||||
Expr(Expr),
|
||||
DegC,
|
||||
DegF,
|
||||
DegRe,
|
||||
DegRo,
|
||||
DegDe,
|
||||
DegN,
|
||||
Degree(Degree),
|
||||
List(Vec<String>),
|
||||
Offset(i64),
|
||||
Timezone(Tz),
|
||||
|
@ -135,16 +208,11 @@ impl fmt::Display for Conversion {
|
|||
match *self {
|
||||
Conversion::None => write!(fmt, "nothing"),
|
||||
Conversion::Expr(ref expr) => write!(fmt, "{}", expr),
|
||||
Conversion::DegC => write!(fmt, "°C"),
|
||||
Conversion::DegF => write!(fmt, "°F"),
|
||||
Conversion::DegRe => write!(fmt, "°Ré"),
|
||||
Conversion::DegRo => write!(fmt, "°Rø"),
|
||||
Conversion::DegDe => write!(fmt, "°De"),
|
||||
Conversion::DegN => write!(fmt, "°N"),
|
||||
Conversion::Degree(ref deg) => write!(fmt, "{}", deg),
|
||||
Conversion::List(ref list) => {
|
||||
let list = list
|
||||
.iter()
|
||||
.map(|x| format!("{}", x))
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
write!(fmt, "{}", list)
|
||||
|
@ -157,15 +225,28 @@ impl fmt::Display for Conversion {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SuffixOp {
|
||||
impl fmt::Display for Degree {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
SuffixOp::Celsius => write!(fmt, "°C"),
|
||||
SuffixOp::Fahrenheit => write!(fmt, "°F"),
|
||||
SuffixOp::Newton => write!(fmt, "°N"),
|
||||
SuffixOp::Reaumur => write!(fmt, "°Ré"),
|
||||
SuffixOp::Romer => write!(fmt, "°Rø"),
|
||||
SuffixOp::Delisle => write!(fmt, "°De"),
|
||||
Degree::Celsius => write!(fmt, "°C"),
|
||||
Degree::Fahrenheit => write!(fmt, "°F"),
|
||||
Degree::Newton => write!(fmt, "°N"),
|
||||
Degree::Reaumur => write!(fmt, "°Ré"),
|
||||
Degree::Romer => write!(fmt, "°Rø"),
|
||||
Degree::Delisle => write!(fmt, "°De"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Degree {
|
||||
pub fn name_base_scale(&self) -> (&str, &str, &str) {
|
||||
match *self {
|
||||
Degree::Celsius => ("C", "zerocelsius", "kelvin"),
|
||||
Degree::Fahrenheit => ("F", "zerofahrenheit", "degrankine"),
|
||||
Degree::Reaumur => ("Ré", "zerocelsius", "reaumur_absolute"),
|
||||
Degree::Romer => ("Rø", "zeroromer", "romer_absolute"),
|
||||
Degree::Delisle => ("De", "zerodelisle", "delisle_absolute"),
|
||||
Degree::Newton => ("N", "zerocelsius", "newton_absolute"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,8 +297,8 @@ impl fmt::Display for Expr {
|
|||
}
|
||||
Ok(())
|
||||
},
|
||||
Expr::Call(ref name, ref args) => {
|
||||
try!(write!(fmt, "{}(", name));
|
||||
Expr::Call(ref func, ref args) => {
|
||||
try!(write!(fmt, "{}(", func.name()));
|
||||
if let Some(first) = args.first() {
|
||||
try!(recurse(first, fmt, Prec::Equals));
|
||||
}
|
||||
|
@ -317,9 +398,10 @@ impl fmt::Display for DateToken {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Expr::{self, *};
|
||||
use super::Function;
|
||||
|
||||
fn check<T: ::std::fmt::Display>(e: T, expected: &str) {
|
||||
assert_eq!(format!("{}", e), expected);
|
||||
assert_eq!(e.to_string(), expected);
|
||||
}
|
||||
|
||||
impl From<i64> for Expr {
|
||||
|
@ -330,9 +412,9 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_display_call() {
|
||||
check(Call("f".into(), vec![]), "f()");
|
||||
check(Call("f".into(), vec![1.into()]), "f(1)");
|
||||
check(Call("f".into(), vec![1.into(), 2.into()]), "f(1, 2)");
|
||||
check(Call("f".into(), vec![1.into(), 2.into(), 3.into()]), "f(1, 2, 3)");
|
||||
check(Call(Function::Sin, vec![]), "sin()");
|
||||
check(Call(Function::Sin, vec![1.into()]), "sin(1)");
|
||||
check(Call(Function::Sin, vec![1.into(), 2.into()]), "sin(1, 2)");
|
||||
check(Call(Function::Sin, vec![1.into(), 2.into(), 3.into()]), "sin(1, 2, 3)");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,16 +27,14 @@ fn main_noninteractive<T: BufRead>(mut f: T, show_prompt: bool) {
|
|||
print!("> ");
|
||||
}
|
||||
stdout().flush().unwrap();
|
||||
match f.read_line(&mut line) {
|
||||
Ok(_) => (),
|
||||
Err(_) => return
|
||||
};
|
||||
if f.read_line(&mut line).is_err() {
|
||||
return
|
||||
}
|
||||
// the underlying file object has hit an EOF if we try to read a
|
||||
// line but do not find the newline at the end, so let's break
|
||||
// out of the loop
|
||||
match line.find('\n') {
|
||||
Some(_) => (),
|
||||
None => return
|
||||
if line.find('\n').is_none() {
|
||||
return;
|
||||
}
|
||||
match one_line(&mut ctx, &*line) {
|
||||
Ok(v) => println!("{}", v),
|
||||
|
@ -80,13 +78,10 @@ fn main_interactive() {
|
|||
});
|
||||
}
|
||||
}
|
||||
for (ref k, _) in &ctx.units {
|
||||
for k in ctx.units.keys() {
|
||||
if k.starts_with(name) {
|
||||
let ref def = ctx.definitions.get(&**k);
|
||||
let def = match def {
|
||||
&Some(ref def) => format!("{} = ", def),
|
||||
&None => format!("")
|
||||
};
|
||||
let def = &ctx.definitions.get(&**k);
|
||||
let def = def.map(|def| format!("{} = ", def)).unwrap_or_default();
|
||||
let res = ctx.lookup(k).unwrap();
|
||||
let parts = res.to_parts(ctx);
|
||||
out.push(Completion {
|
||||
|
@ -236,7 +231,7 @@ fn main_interactive() {
|
|||
let readline = rl.read_line();
|
||||
match readline {
|
||||
Ok(ReadResult::Input(ref line)) if line == "quit" => {
|
||||
println!("");
|
||||
println!();
|
||||
break
|
||||
},
|
||||
Ok(ReadResult::Input(ref line)) if line == "help" => {
|
||||
|
@ -251,7 +246,7 @@ fn main_interactive() {
|
|||
};
|
||||
},
|
||||
Ok(ReadResult::Eof) => {
|
||||
println!("");
|
||||
println!();
|
||||
let hfile = hpath.and_then(|hpath| File::create(hpath).map_err(|x| x.to_string()));
|
||||
if let Ok(mut hfile) = hfile {
|
||||
for line in rl.history() {
|
||||
|
|
|
@ -13,13 +13,13 @@ static URL: &'static str = "https://blockchain.info/stats?format=json";
|
|||
|
||||
pub fn parse(mut f: File) -> Result<Defs, String> {
|
||||
let mut buf = String::new();
|
||||
try!(f.read_to_string(&mut buf).map_err(|x| format!("{}", x)));
|
||||
let parsed = try!(json::parse(&*buf).map_err(|x| format!("{}", x)));
|
||||
try!(f.read_to_string(&mut buf).map_err(|x| x.to_string()));
|
||||
let parsed = try!(json::parse(&*buf).map_err(|x| x.to_string()));
|
||||
let mut out = vec![];
|
||||
if let Some(price) = parsed["market_price_usd"].as_number() {
|
||||
let (sign, mantissa, exp) = price.as_parts();
|
||||
let integer = format!("{}{}", if sign { "" } else { "-" }, mantissa);
|
||||
if let Ok(price) = ::Number::from_parts(&*integer, None, Some(&*format!("{}", exp))) {
|
||||
if let Ok(price) = ::Number::from_parts(&*integer, None, Some(&*exp.to_string())) {
|
||||
out.push(DefEntry {
|
||||
name: "BTC".to_owned(),
|
||||
def: Rc::new(Def::Unit(
|
||||
|
@ -27,7 +27,7 @@ pub fn parse(mut f: File) -> Result<Defs, String> {
|
|||
Expr::Const(price),
|
||||
Expr::Unit("USD".to_owned())
|
||||
]))),
|
||||
doc: Some(format!("Sourced from blockchain.info.")),
|
||||
doc: Some("Sourced from blockchain.info.".to_string()),
|
||||
category: Some("currencies".to_owned()),
|
||||
});
|
||||
}
|
||||
|
|
132
src/context.rs
132
src/context.rs
|
@ -11,7 +11,7 @@ use substance::Substance;
|
|||
use reply::NotFoundError;
|
||||
|
||||
/// The evaluation context that contains unit definitions.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Context {
|
||||
pub dimensions: BTreeSet<Dim>,
|
||||
pub canonicalizations: BTreeMap<String, String>,
|
||||
|
@ -35,22 +35,9 @@ impl Context {
|
|||
/// Creates a new, empty context
|
||||
pub fn new() -> Context {
|
||||
Context {
|
||||
dimensions: BTreeSet::new(),
|
||||
canonicalizations: BTreeMap::new(),
|
||||
units: BTreeMap::new(),
|
||||
quantities: BTreeMap::new(),
|
||||
reverse: BTreeMap::new(),
|
||||
prefixes: Vec::new(),
|
||||
definitions: BTreeMap::new(),
|
||||
docs: BTreeMap::new(),
|
||||
categories: BTreeMap::new(),
|
||||
category_names: BTreeMap::new(),
|
||||
datepatterns: Vec::new(),
|
||||
substances: BTreeMap::new(),
|
||||
substance_symbols: BTreeMap::new(),
|
||||
temporaries: BTreeMap::new(),
|
||||
short_output: false,
|
||||
use_humanize: true,
|
||||
..Context::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,31 +68,33 @@ impl Context {
|
|||
}
|
||||
None
|
||||
}
|
||||
if let Some(v) = inner(self, name) {
|
||||
return Some(v)
|
||||
}
|
||||
for &(ref pre, ref value) in &self.prefixes {
|
||||
if name.starts_with(pre) {
|
||||
if let Some(v) = inner(self, &name[pre.len()..]) {
|
||||
return Some((&v * &value).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
// after so that "ks" is kiloseconds
|
||||
if name.ends_with("s") {
|
||||
let name = &name[0..name.len()-1];
|
||||
|
||||
let outer = |name: &str| -> Option<Number> {
|
||||
if let Some(v) = inner(self, name) {
|
||||
return Some(v)
|
||||
}
|
||||
for &(ref pre, ref value) in &self.prefixes {
|
||||
if name.starts_with(pre) {
|
||||
if let Some(v) = inner(self, &name[pre.len()..]) {
|
||||
return Some((&v * &value).unwrap())
|
||||
return Some((&v * value).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let res = outer(name);
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
|
||||
// after so that "ks" is kiloseconds
|
||||
if name.ends_with('s') {
|
||||
let name = &name[0..name.len()-1];
|
||||
outer(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Given a unit name, try to return a canonical name (expanding aliases and such)
|
||||
|
@ -131,24 +120,8 @@ impl Context {
|
|||
}
|
||||
None
|
||||
}
|
||||
if let Some(v) = inner(self, name) {
|
||||
return Some(v)
|
||||
}
|
||||
for &(ref pre, ref val) in &self.prefixes {
|
||||
if name.starts_with(pre) {
|
||||
if let Some(v) = inner(self, &name[pre.len()..]) {
|
||||
let mut pre = pre;
|
||||
for &(ref other, ref otherval) in &self.prefixes {
|
||||
if other.len() > pre.len() && val == otherval {
|
||||
pre = other;
|
||||
}
|
||||
}
|
||||
return Some(format!("{}{}", pre, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
if name.ends_with("s") {
|
||||
let name = &name[0..name.len()-1];
|
||||
|
||||
let outer = |name: &str| -> Option<String> {
|
||||
if let Some(v) = inner(self, name) {
|
||||
return Some(v)
|
||||
}
|
||||
|
@ -165,8 +138,20 @@ impl Context {
|
|||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let res = outer(name);
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
|
||||
if name.ends_with('s') {
|
||||
let name = &name[0..name.len()-1];
|
||||
outer(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Describes a value's unit, gives true if the unit is reciprocal
|
||||
|
@ -193,6 +178,25 @@ impl Context {
|
|||
recip = true;
|
||||
write!(buf, "{}", name).unwrap();
|
||||
} else {
|
||||
let helper = |dim: &Dim, pow: i64, buf: &mut Vec<u8>| {
|
||||
let mut map = Unit::new();
|
||||
map.insert(dim.clone(), pow);
|
||||
if let Some(name) = self.quantities.get(&map) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
let mut map = Unit::new();
|
||||
map.insert(dim.clone(), 1);
|
||||
if let Some(name) = self.quantities.get(&map) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
write!(buf, " '{}'", dim).unwrap();
|
||||
}
|
||||
if pow != 1 {
|
||||
write!(buf, "^{}", pow).unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut frac = vec![];
|
||||
let mut found = false;
|
||||
for (dim, &pow) in &value.unit {
|
||||
|
@ -200,25 +204,10 @@ impl Context {
|
|||
frac.push((dim, -pow));
|
||||
} else {
|
||||
found = true;
|
||||
let mut map = Unit::new();
|
||||
map.insert(dim.clone(), pow);
|
||||
if let Some(name) = self.quantities.get(&map) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
let mut map = Unit::new();
|
||||
map.insert(dim.clone(), 1);
|
||||
if let Some(name) = self.quantities.get(&map) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
write!(buf, " '{}'", dim).unwrap();
|
||||
}
|
||||
if pow != 1 {
|
||||
write!(buf, "^{}", pow).unwrap();
|
||||
}
|
||||
}
|
||||
helper(dim, pow, &mut buf);
|
||||
}
|
||||
}
|
||||
if frac.len() > 0 {
|
||||
if !frac.is_empty() {
|
||||
if !found {
|
||||
recip = true;
|
||||
} else {
|
||||
|
@ -230,16 +219,7 @@ impl Context {
|
|||
if let Some(name) = self.quantities.get(&map) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
let mut map = Unit::new();
|
||||
map.insert(dim.clone(), 1);
|
||||
if let Some(name) = self.quantities.get(&map) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
write!(buf, " '{}'", dim).unwrap();
|
||||
}
|
||||
if pow != 1 {
|
||||
write!(buf, "^{}", pow).unwrap();
|
||||
}
|
||||
helper(dim, pow, &mut buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ pub fn parse(f: File) -> Result<Defs, String> {
|
|||
}
|
||||
}
|
||||
if let (Some(currency), Some(rate)) = (currency, rate) {
|
||||
let mut iter = rate.split(".");
|
||||
let mut iter = rate.split('.');
|
||||
let integer = iter.next().unwrap();
|
||||
let frac = iter.next();
|
||||
if let Ok(num) = ::number::Number::from_parts(integer, frac, None) {
|
||||
|
@ -46,13 +46,13 @@ pub fn parse(f: File) -> Result<Defs, String> {
|
|||
Box::new(Expr::Const(num))),
|
||||
Expr::Unit("EUR".to_string())
|
||||
]))),
|
||||
doc: Some(format!("Sourced from European Central Bank.")),
|
||||
doc: Some("Sourced from European Central Bank.".to_string()),
|
||||
category: Some("currencies".to_owned()),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(format!("{}", e)),
|
||||
Err(e) => return Err(e.to_string()),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
45
src/date.rs
45
src/date.rs
|
@ -90,7 +90,7 @@ pub fn parse_date<I>(
|
|||
DateToken::Dash => (-1, None),
|
||||
DateToken::Plus => (1, None),
|
||||
DateToken::Number(i, None) => (1, Some(i)),
|
||||
_ => panic!()
|
||||
_ => unreachable!()
|
||||
};
|
||||
let num = match num {
|
||||
Some(x) => x,
|
||||
|
@ -169,7 +169,7 @@ pub fn parse_date<I>(
|
|||
}
|
||||
} else {
|
||||
let s = match take!(DateToken::Plus | DateToken::Dash) {
|
||||
DateToken::Plus => 1, DateToken::Dash => -1, _ => panic!()
|
||||
DateToken::Plus => 1, DateToken::Dash => -1, _ => unreachable!()
|
||||
};
|
||||
let h = take!(DateToken::Number(s, None), s);
|
||||
if h.len() == 4 {
|
||||
|
@ -253,10 +253,7 @@ pub fn parse_date<I>(
|
|||
if advance {
|
||||
date.next();
|
||||
}
|
||||
match res {
|
||||
Ok(()) => parse_date(out, out_tz, date, &pat[1..]),
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
res.and_then(|_| parse_date(out, out_tz, date, &pat[1..]))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -294,17 +291,19 @@ fn attempt(date: &[DateToken], pat: &[DatePattern]) -> Result<GenericDateTime, (
|
|||
if let Some(tz) = tz {
|
||||
match (time, date) {
|
||||
(Ok(time), Ok(date)) =>
|
||||
tz.from_local_datetime(&date.and_time(time)).earliest().ok_or_else(|| (format!(
|
||||
"Datetime does not represent a valid moment in time"
|
||||
), count)).map(GenericDateTime::Timezone),
|
||||
tz.from_local_datetime(&date.and_time(time)).earliest().ok_or_else(|| (
|
||||
"Datetime does not represent a valid moment in time".to_string(),
|
||||
count,
|
||||
)).map(GenericDateTime::Timezone),
|
||||
(Ok(time), Err(_)) =>
|
||||
Ok(UTC::now().with_timezone(&tz).date().and_time(time).unwrap()).map(
|
||||
GenericDateTime::Timezone),
|
||||
(Err(_), Ok(date)) =>
|
||||
tz.from_local_date(&date).earliest().map(|x| x.and_hms(0, 0, 0)).ok_or_else(|| (format!(
|
||||
"Datetime does not represent a valid moment in time"
|
||||
), count)).map(GenericDateTime::Timezone),
|
||||
_ => Err((format!("Failed to construct a useful datetime"), count))
|
||||
tz.from_local_date(&date).earliest().map(|x| x.and_hms(0, 0, 0)).ok_or_else(|| (
|
||||
"Datetime does not represent a valid moment in time".to_string(),
|
||||
count,
|
||||
)).map(GenericDateTime::Timezone),
|
||||
_ => Err(("Failed to construct a useful datetime".to_string(), count))
|
||||
}
|
||||
} else {
|
||||
let offset = parsed.to_fixed_offset().unwrap_or(FixedOffset::east(0));
|
||||
|
@ -348,13 +347,13 @@ pub fn try_decode(date: &[DateToken], context: &Context) -> Result<GenericDateTi
|
|||
if let Some((_, pat, err)) = best {
|
||||
Err(format!("Most likely pattern `{}` failed: {}", show_datepattern(pat), err))
|
||||
} else {
|
||||
Err(format!("Invalid date literal"))
|
||||
Err("Invalid date literal".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_duration(num: &Number) -> Result<Duration, String> {
|
||||
if num.unit.len() != 1 || num.unit.get("s") != Some(&1) {
|
||||
return Err(format!("Expected seconds"))
|
||||
return Err("Expected seconds".to_string())
|
||||
}
|
||||
let max = Num::from(i64::max_value() / 1000);
|
||||
if num.value.abs() > max {
|
||||
|
@ -395,7 +394,7 @@ pub fn parse_datepattern<I>(iter: &mut Peekable<I>)
|
|||
iter.next();
|
||||
let res = DatePattern::Optional(try!(parse_datepattern(iter)));
|
||||
if iter.peek().cloned() != Some(']') {
|
||||
return Err(format!("Expected ]"))
|
||||
return Err("Expected ]".to_string())
|
||||
} else {
|
||||
res
|
||||
}
|
||||
|
@ -447,7 +446,7 @@ pub fn parse_datefile(file: &str) -> Vec<Vec<DatePattern>> {
|
|||
for (num, line) in file.lines().enumerate() {
|
||||
let line = line.split('#').next().unwrap();
|
||||
let line = line.trim();
|
||||
if line.len() == 0 {
|
||||
if line.is_empty() {
|
||||
continue
|
||||
}
|
||||
let res = parse_datepattern(&mut line.chars().peekable());
|
||||
|
@ -464,7 +463,7 @@ impl Context {
|
|||
pub fn humanize<Tz: TimeZone>(&self, date: DateTime<Tz>) -> Option<String> {
|
||||
if self.use_humanize {
|
||||
use chrono_humanize::HumanTime;
|
||||
Some(format!("{}", HumanTime::from(date)))
|
||||
Some(HumanTime::from(date).to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -517,14 +516,14 @@ mod tests {
|
|||
|
||||
let date = vec![
|
||||
DateToken::Plus,
|
||||
DateToken::Number(format!("{}", expected.year.unwrap()), None),
|
||||
DateToken::Number(expected.year.unwrap().to_string(), None),
|
||||
];
|
||||
let (res, parsed) = parse(date.clone(), "year");
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(parsed, expected);
|
||||
|
||||
let date = vec![DateToken::Number(
|
||||
format!("{}", expected.year.unwrap()),
|
||||
expected.year.unwrap().to_string(),
|
||||
None,
|
||||
)];
|
||||
let (res, parsed2) = parse(date.clone(), "year");
|
||||
|
@ -543,13 +542,13 @@ mod tests {
|
|||
expected.set_minute(57).unwrap();
|
||||
|
||||
let date = vec![
|
||||
DateToken::Number(format!("{}", expected.day.unwrap()), None),
|
||||
DateToken::Number(expected.day.unwrap().to_string(), None),
|
||||
DateToken::Space,
|
||||
DateToken::Literal("Pm".into()),
|
||||
DateToken::Dash,
|
||||
DateToken::Number(format!("{:02}", expected.month.unwrap()), None),
|
||||
DateToken::Colon,
|
||||
DateToken::Number(format!("{}", expected.year.unwrap()), None),
|
||||
DateToken::Number(expected.year.unwrap().to_string(), None),
|
||||
DateToken::Space,
|
||||
DateToken::Number(format!("{:02}", expected.hour_mod_12.unwrap()), None),
|
||||
DateToken::Dash,
|
||||
|
@ -571,7 +570,7 @@ mod tests {
|
|||
expected.set_hour(7).unwrap();
|
||||
|
||||
let date = vec![
|
||||
DateToken::Number(format!("{}", year.abs()), None),
|
||||
DateToken::Number(year.abs().to_string(), None),
|
||||
DateToken::Space,
|
||||
DateToken::Literal("bce".into()),
|
||||
DateToken::Space,
|
||||
|
|
301
src/eval.rs
301
src/eval.rs
|
@ -6,7 +6,7 @@ use std::collections::BTreeMap;
|
|||
use number::{Number, Dim, NumberParts, pow};
|
||||
use num::{Num, Int};
|
||||
use date;
|
||||
use ast::{Expr, SuffixOp, Query, Conversion, Digits};
|
||||
use ast::{Expr, Query, Conversion, Digits, Function};
|
||||
use std::rc::Rc;
|
||||
use factorize::{factorize, Factors};
|
||||
use value::{Value, Show};
|
||||
|
@ -41,30 +41,6 @@ impl Context {
|
|||
}}
|
||||
}
|
||||
|
||||
macro_rules! temperature {
|
||||
($left:ident, $name:expr, $base:expr, $scale:expr) => {{
|
||||
let left = try!(self.eval(&**$left));
|
||||
let left = match left {
|
||||
Value::Number(left) => left,
|
||||
_ => return Err(QueryError::Generic(format!(
|
||||
"Expected number, got: <{}> °{}",
|
||||
left.show(self), stringify!($name)
|
||||
)))
|
||||
};
|
||||
if left.unit != BTreeMap::new() {
|
||||
Err(QueryError::Generic(format!(
|
||||
"Expected dimensionless, got: <{}>",
|
||||
left.show(self)
|
||||
)))
|
||||
} else {
|
||||
let left = (&left * &self.lookup($scale).expect(
|
||||
&*format!("Missing {} unit", $scale))).unwrap();
|
||||
Ok(Value::Number((&left + &self.lookup($base)
|
||||
.expect(&*format!("Missing {} constant", $base))).unwrap()))
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
match *expr {
|
||||
Expr::Unit(ref name) if name == "now" =>
|
||||
Ok(Value::DateTime(date::GenericDateTime::Fixed(date::now()))),
|
||||
|
@ -98,18 +74,29 @@ impl Context {
|
|||
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) =>
|
||||
temperature!(left, "C", "zerocelsius", "kelvin"),
|
||||
Expr::Suffix(SuffixOp::Fahrenheit, ref left) =>
|
||||
temperature!(left, "F", "zerofahrenheit", "degrankine"),
|
||||
Expr::Suffix(SuffixOp::Reaumur, ref left) =>
|
||||
temperature!(left, "Ré", "zerocelsius", "reaumur_absolute"),
|
||||
Expr::Suffix(SuffixOp::Romer, ref left) =>
|
||||
temperature!(left, "Rø", "zeroromer", "romer_absolute"),
|
||||
Expr::Suffix(SuffixOp::Delisle, ref left) =>
|
||||
temperature!(left, "De", "zerodelisle", "delisle_absolute"),
|
||||
Expr::Suffix(SuffixOp::Newton, ref left) =>
|
||||
temperature!(left, "N", "zerocelsius", "newton_absolute"),
|
||||
Expr::Suffix(ref deg, ref left) => {
|
||||
let (name, base, scale) = deg.name_base_scale();
|
||||
|
||||
let left = try!(self.eval(&**left));
|
||||
let left = match left {
|
||||
Value::Number(left) => left,
|
||||
_ => return Err(QueryError::Generic(format!(
|
||||
"Expected number, got: <{}> °{}",
|
||||
left.show(self), name
|
||||
)))
|
||||
};
|
||||
if left.unit != BTreeMap::new() {
|
||||
Err(QueryError::Generic(format!(
|
||||
"Expected dimensionless, got: <{}>",
|
||||
left.show(self)
|
||||
)))
|
||||
} else {
|
||||
let left = (&left * &self.lookup(scale).expect(
|
||||
&*format!("Missing {} unit", scale))).unwrap();
|
||||
Ok(Value::Number((&left + &self.lookup(base)
|
||||
.expect(&*format!("Missing {} constant", base))).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Mul(ref args) => args.iter().fold(Ok(Value::Number(Number::one())), |a, b| {
|
||||
a.and_then(|a| {
|
||||
|
@ -150,7 +137,7 @@ impl Context {
|
|||
}
|
||||
})
|
||||
},
|
||||
Expr::Call(ref name, ref args) => {
|
||||
Expr::Call(ref func, ref args) => {
|
||||
let args = try!(
|
||||
args.iter()
|
||||
.map(|x| self.eval(x))
|
||||
|
@ -200,27 +187,25 @@ impl Context {
|
|||
}
|
||||
}}
|
||||
|
||||
match &**name {
|
||||
"sqrt" => func!(fn sqrt(num: Number) {
|
||||
match func {
|
||||
Function::Sqrt => func!(fn sqrt(num: Number) {
|
||||
num.root(2).map(Value::Number)
|
||||
}),
|
||||
"exp" => func!(fn exp(num: Number) {
|
||||
Function::Exp => func!(fn exp(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().exp()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"ln" => func!(fn ln(num: Number) {
|
||||
Function::Ln => func!(fn ln(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().ln()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"log" => func!(fn log(num: Number, base: Number) {
|
||||
if base.unit.len() > 0 {
|
||||
Err(format!(
|
||||
"Base must be dimensionless"
|
||||
))
|
||||
Function::Log => func!(fn log(num: Number, base: Number) {
|
||||
if !base.unit.is_empty() {
|
||||
Err("Base must be dimensionless".to_string())
|
||||
} else {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64()
|
||||
|
@ -229,24 +214,21 @@ impl Context {
|
|||
}))
|
||||
}
|
||||
}),
|
||||
"log2" => func!(fn log2(num: Number) {
|
||||
Function::Log2 => func!(fn log2(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().log2()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"log10" => func!(fn ln(num: Number) {
|
||||
Function::Log10 => func!(fn ln(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().log10()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"hypot" => func!(fn hypot(x: Number, y: Number) {
|
||||
Function::Hypot => func!(fn hypot(x: Number, y: Number) {
|
||||
if x.unit != y.unit {
|
||||
Err(format!(
|
||||
"Arguments to hypot must have matching \
|
||||
dimensionality"
|
||||
))
|
||||
Err("Arguments to hypot must have matching dimensionality".to_string())
|
||||
} else {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(x.value.to_f64().hypot(y.value.to_f64())),
|
||||
|
@ -254,48 +236,45 @@ impl Context {
|
|||
}))
|
||||
}
|
||||
}),
|
||||
"sin" => func!(fn sin(num: Number) {
|
||||
Function::Sin => func!(fn sin(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().sin()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"cos" => func!(fn cos(num: Number) {
|
||||
Function::Cos => func!(fn cos(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().cos()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"tan" => func!(fn tan(num: Number) {
|
||||
Function::Tan => func!(fn tan(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().tan()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"asin" => func!(fn asin(num: Number) {
|
||||
Function::Asin => func!(fn asin(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().asin()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"acos" => func!(fn acos(num: Number) {
|
||||
Function::Acos => func!(fn acos(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().acos()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"atan" => func!(fn atan(num: Number) {
|
||||
Function::Atan => func!(fn atan(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().atan()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"atan2" => func!(fn atan2(x: Number, y: Number) {
|
||||
Function::Atan2 => func!(fn atan2(x: Number, y: Number) {
|
||||
if x.unit != y.unit {
|
||||
Err(format!(
|
||||
"Arguments to atan2 must have matching \
|
||||
dimensionality"
|
||||
))
|
||||
Err("Arguments to atan2 must have matching dimensionality".to_string())
|
||||
} else {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(x.value.to_f64()
|
||||
|
@ -304,45 +283,42 @@ impl Context {
|
|||
}))
|
||||
}
|
||||
}),
|
||||
"sinh" => func!(fn sinh(num: Number) {
|
||||
Function::Sinh => func!(fn sinh(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().sinh()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"cosh" => func!(fn cosh(num: Number) {
|
||||
Function::Cosh => func!(fn cosh(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().cosh()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"tanh" => func!(fn tanh(num: Number) {
|
||||
Function::Tanh => func!(fn tanh(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().tanh()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"asinh" => func!(fn asinh(num: Number) {
|
||||
Function::Asinh => func!(fn asinh(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().asinh()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"acosh" => func!(fn acosh(num: Number) {
|
||||
Function::Acosh => func!(fn acosh(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().acosh()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
"atanh" => func!(fn atanh(num: Number) {
|
||||
Function::Atanh => func!(fn atanh(num: Number) {
|
||||
Ok(Value::Number(Number {
|
||||
value: Num::Float(num.value.to_f64().atanh()),
|
||||
unit: num.unit.clone(),
|
||||
}))
|
||||
}),
|
||||
_ => Err(QueryError::Generic(format!(
|
||||
"Function not found: {}", name
|
||||
)))
|
||||
}
|
||||
},
|
||||
Expr::Error(ref e) => Err(QueryError::Generic(e.clone())),
|
||||
|
@ -361,9 +337,9 @@ impl Context {
|
|||
"Expected identifier, got {:?}", x
|
||||
)))
|
||||
},
|
||||
Expr::Call(_, _) => Err(QueryError::Generic(format!(
|
||||
"Calls are not allowed in the right hand side of conversions"
|
||||
))),
|
||||
Expr::Call(_, _) => Err(QueryError::Generic(
|
||||
"Calls are not allowed in the right hand side of conversions".to_string()
|
||||
)),
|
||||
Expr::Unit(ref name) | Expr::Quote(ref name) => {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(self.canonicalize(&**name)
|
||||
|
@ -398,14 +374,14 @@ impl Context {
|
|||
let res = try!(self.eval(exp));
|
||||
let res = match res {
|
||||
Value::Number(num) => num,
|
||||
_ => return Err(QueryError::Generic(format!(
|
||||
"Exponents must be numbers"
|
||||
)))
|
||||
_ => return Err(QueryError::Generic(
|
||||
"Exponents must be numbers".to_string()
|
||||
))
|
||||
};
|
||||
if !res.dimless() {
|
||||
return Err(QueryError::Generic(format!(
|
||||
"Exponents must be dimensionless"
|
||||
)))
|
||||
return Err(QueryError::Generic(
|
||||
"Exponents must be dimensionless".to_string()
|
||||
))
|
||||
}
|
||||
let res = res.value.to_f64();
|
||||
let (left, lv) = try!(self.eval_unit_name(left));
|
||||
|
@ -425,9 +401,9 @@ impl Context {
|
|||
let res = try!(self.eval(expr));
|
||||
let res = match res {
|
||||
Value::Substance(sub) => sub,
|
||||
_ => return Err(QueryError::Generic(format!(
|
||||
"Property access on non-substance"
|
||||
)))
|
||||
_ => return Err(QueryError::Generic(
|
||||
"Property access on non-substance".to_string()
|
||||
))
|
||||
};
|
||||
let name = if let Some(prop) = res.properties.properties.get(name) {
|
||||
if prop.input == Number::one() {
|
||||
|
@ -448,27 +424,35 @@ impl Context {
|
|||
let left = try!(self.eval_unit_name(left));
|
||||
let right = try!(self.eval_unit_name(right));
|
||||
if left != right {
|
||||
return Err(QueryError::Generic(format!(
|
||||
return Err(QueryError::Generic(
|
||||
"Add of values with differing \
|
||||
dimensions is not meaningful"
|
||||
)))
|
||||
dimensions is not meaningful".to_string()
|
||||
))
|
||||
}
|
||||
Ok(left)
|
||||
},
|
||||
Expr::Neg(ref v) => self.eval_unit_name(v).map(|(u, v)| (u, -&v)),
|
||||
Expr::Plus(ref v) => self.eval_unit_name(v),
|
||||
Expr::Suffix(_, _) =>
|
||||
Err(QueryError::Generic(format!(
|
||||
"Temperature conversions must not be compound units"
|
||||
))),
|
||||
Expr::Date(_) => Err(QueryError::Generic(format!(
|
||||
"Dates are not allowed in the right hand side of conversions"
|
||||
))),
|
||||
Err(QueryError::Generic(
|
||||
"Temperature conversions must not be compound units".to_string()
|
||||
)),
|
||||
Expr::Date(_) => Err(QueryError::Generic(
|
||||
"Dates are not allowed in the right hand side of conversions".to_string()
|
||||
)),
|
||||
Expr::Error(ref e) => Err(QueryError::Generic(e.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn conformance_err(&self, top: &Number, bottom: &Number) -> ConformanceError {
|
||||
fn multiply_or_divide(recip: bool) -> &'static str {
|
||||
if recip {
|
||||
"divide"
|
||||
} else {
|
||||
"multiply"
|
||||
}
|
||||
}
|
||||
|
||||
let mut topu = top.clone();
|
||||
topu.value = Num::one();
|
||||
let mut bottomu = bottom.clone();
|
||||
|
@ -476,27 +460,21 @@ impl Context {
|
|||
let mut suggestions = vec![];
|
||||
let diff = (&topu * &bottomu).unwrap();
|
||||
if diff.dimless() {
|
||||
suggestions.push(format!("Reciprocal conversion, invert one side"));
|
||||
suggestions.push("Reciprocal conversion, invert one side".to_string());
|
||||
} else {
|
||||
let diff = (&topu / &bottomu).unwrap();
|
||||
let (recip, desc) = self.describe_unit(&diff.invert());
|
||||
let word = match recip {
|
||||
false => "multiply",
|
||||
true => "divide"
|
||||
};
|
||||
let word = multiply_or_divide(recip);
|
||||
suggestions.push(format!("{word} left side by {}", desc.trim(), word=word));
|
||||
let (recip, desc) = self.describe_unit(&diff);
|
||||
let word = match recip {
|
||||
false => "multiply",
|
||||
true => "divide"
|
||||
};
|
||||
let word = multiply_or_divide(recip);
|
||||
suggestions.push(format!("{word} right side by {}", desc.trim(), word=word));
|
||||
}
|
||||
|
||||
ConformanceError {
|
||||
left: top.to_parts(self),
|
||||
right: bottom.to_parts(self),
|
||||
suggestions: suggestions,
|
||||
suggestions,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,12 +496,12 @@ impl Context {
|
|||
exact_value: exact,
|
||||
approx_value: approx,
|
||||
factor: if num != Int::one() {
|
||||
Some(format!("{}", num))
|
||||
Some(num.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
divfactor: if den != Int::one() {
|
||||
Some(format!("{}", den))
|
||||
Some(den.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
@ -542,7 +520,7 @@ impl Context {
|
|||
}).collect::<Result<Vec<Number>, _>>());
|
||||
{
|
||||
let first = try!(units.first().ok_or(
|
||||
format!("Expected non-empty unit list")));
|
||||
"Expected non-empty unit list".to_string()));
|
||||
try!(units.iter().skip(1).map(|x| {
|
||||
if first.unit != x.unit {
|
||||
Err(format!(
|
||||
|
@ -571,7 +549,7 @@ impl Context {
|
|||
}
|
||||
Ok(list.iter().zip(out.into_iter()).map(|(name, value)| {
|
||||
let pretty = Number {
|
||||
value: value,
|
||||
value,
|
||||
unit: Number::one_unit(Dim::new(name)).unit
|
||||
}.to_parts(self);
|
||||
let unit: String = pretty.unit.or(pretty.dimensions)
|
||||
|
@ -645,18 +623,18 @@ impl Context {
|
|||
let def = if let Some(ref q) = parts.quantity {
|
||||
format!("base unit of {}", q)
|
||||
} else {
|
||||
format!("base unit")
|
||||
"base unit".to_string()
|
||||
};
|
||||
(Some(def), None, None)
|
||||
} else {
|
||||
let def = self.definitions.get(&name);
|
||||
(def.as_ref().map(|x| format!("{}", x)),
|
||||
(def.as_ref().map(|x| x.to_string()),
|
||||
def,
|
||||
self.lookup(&name).map(|x| x.to_parts(self)))
|
||||
};
|
||||
Ok(QueryReply::Def(DefReply {
|
||||
canon_name: canon,
|
||||
def: def,
|
||||
def,
|
||||
def_expr: def_expr.as_ref().map(|x| ExprReply::from(*x)),
|
||||
value: res,
|
||||
doc: self.docs.get(&name).cloned(),
|
||||
|
@ -688,7 +666,7 @@ impl Context {
|
|||
"<{}> to {} is not defined",
|
||||
top.show(self),
|
||||
match digits {
|
||||
Digits::Default => panic!(),
|
||||
Digits::Default => unreachable!(),
|
||||
Digits::FullInt => "digits".to_owned(),
|
||||
Digits::Digits(n) => format!("{} digits", n)
|
||||
}
|
||||
|
@ -707,10 +685,10 @@ impl Context {
|
|||
}))
|
||||
},
|
||||
Query::Convert(ref top, Conversion::Expr(ref bottom), base, digits) => match
|
||||
(self.eval(top), self.eval(bottom), self.eval_unit_name(bottom))
|
||||
(try!(self.eval(top)), try!(self.eval(bottom)), try!(self.eval_unit_name(bottom)))
|
||||
{
|
||||
(Ok(Value::Number(top)), Ok(Value::Number(bottom)),
|
||||
Ok((bottom_name, bottom_const))) => {
|
||||
(Value::Number(top), Value::Number(bottom),
|
||||
(bottom_name, bottom_const)) => {
|
||||
if top.unit == bottom.unit {
|
||||
let raw = match &top / &bottom {
|
||||
Some(raw) => raw,
|
||||
|
@ -729,8 +707,8 @@ impl Context {
|
|||
&top, &bottom)))
|
||||
}
|
||||
},
|
||||
(Ok(Value::Substance(sub)), Ok(Value::Number(bottom)),
|
||||
Ok((bottom_name, bottom_const))) => {
|
||||
(Value::Substance(sub), Value::Number(bottom),
|
||||
(bottom_name, bottom_const)) => {
|
||||
sub.get_in_unit(
|
||||
bottom,
|
||||
self,
|
||||
|
@ -744,8 +722,8 @@ impl Context {
|
|||
QueryReply::Substance
|
||||
)
|
||||
},
|
||||
(Ok(Value::Number(top)), Ok(Value::Substance(mut sub)),
|
||||
Ok((bottom_name, bottom_const))) => {
|
||||
(Value::Number(top), Value::Substance(mut sub),
|
||||
(bottom_name, bottom_const)) => {
|
||||
let unit = sub.amount.clone();
|
||||
sub.amount = top;
|
||||
sub.get_in_unit(
|
||||
|
@ -761,14 +739,11 @@ impl Context {
|
|||
QueryReply::Substance
|
||||
)
|
||||
},
|
||||
(Ok(x), Ok(y), Ok(_)) => Err(QueryError::Generic(format!(
|
||||
(x, y, _) => Err(QueryError::Generic(format!(
|
||||
"Operation is not defined: <{}> -> <{}>",
|
||||
x.show(self),
|
||||
y.show(self)
|
||||
))),
|
||||
(Err(e), _, _) => Err(e),
|
||||
(_, Err(e), _) => Err(e),
|
||||
(_, _, Err(e)) => Err(e),
|
||||
},
|
||||
Query::Convert(ref top, Conversion::List(ref list), None, Digits::Default) => {
|
||||
let top = try!(self.eval(top));
|
||||
|
@ -788,7 +763,7 @@ impl Context {
|
|||
quantity: self.quantities.get(&top.unit).cloned(),
|
||||
..Default::default()
|
||||
},
|
||||
list: list,
|
||||
list,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -814,49 +789,32 @@ impl Context {
|
|||
let top = top.with_timezone(&tz);
|
||||
Ok(QueryReply::Date(DateReply::new(self, top)))
|
||||
},
|
||||
Query::Convert(ref top, ref which @ Conversion::DegC, None, digits) |
|
||||
Query::Convert(ref top, ref which @ Conversion::DegF, None, digits) |
|
||||
Query::Convert(ref top, ref which @ Conversion::DegN, None, digits) |
|
||||
Query::Convert(ref top, ref which @ Conversion::DegRe, None, digits) |
|
||||
Query::Convert(ref top, ref which @ Conversion::DegRo, None, digits) |
|
||||
Query::Convert(ref top, ref which @ Conversion::DegDe, None, digits) => {
|
||||
let top = try!(self.eval(top));
|
||||
macro_rules! temperature {
|
||||
($name:expr, $base:expr, $scale:expr) => {{
|
||||
let top = match top {
|
||||
Value::Number(ref num) => num,
|
||||
_ => return Err(QueryError::Generic(format!(
|
||||
"Cannot convert <{}> to °{}", top.show(self), $name)))
|
||||
};
|
||||
let bottom = self.lookup($scale)
|
||||
.expect(&*format!("Unit {} missing", $scale));
|
||||
if top.unit != bottom.unit {
|
||||
Err(QueryError::Conformance(
|
||||
self.conformance_err(&top, &bottom)))
|
||||
} else {
|
||||
let res = (top - &self.lookup($base)
|
||||
.expect(&*format!("Constant {} missing", $base))).unwrap();
|
||||
let res = (&res / &bottom).unwrap();
|
||||
let mut name = BTreeMap::new();
|
||||
name.insert(format!("°{}", $name), 1);
|
||||
Ok(QueryReply::Conversion(self.show(
|
||||
&res, &bottom,
|
||||
name, Num::one(),
|
||||
10,
|
||||
digits
|
||||
)))
|
||||
}
|
||||
}}
|
||||
}
|
||||
Query::Convert(ref top, Conversion::Degree(ref deg), None, digits) => {
|
||||
let (name, base, scale) = deg.name_base_scale();
|
||||
|
||||
match *which {
|
||||
Conversion::DegC => temperature!("C", "zerocelsius", "kelvin"),
|
||||
Conversion::DegF => temperature!("F", "zerofahrenheit", "degrankine"),
|
||||
Conversion::DegRe => temperature!("Ré", "zerocelsius", "reaumur_absolute"),
|
||||
Conversion::DegRo => temperature!("Rø", "zeroromer", "romer_absolute"),
|
||||
Conversion::DegDe => temperature!("De", "zerodelisle", "delisle_absolute"),
|
||||
Conversion::DegN => temperature!("N", "zerocelsius", "newton_absolute"),
|
||||
_ => panic!()
|
||||
let top = try!(self.eval(top));
|
||||
let top = match top {
|
||||
Value::Number(ref num) => num,
|
||||
_ => return Err(QueryError::Generic(format!(
|
||||
"Cannot convert <{}> to °{}", top.show(self), name)))
|
||||
};
|
||||
let bottom = self.lookup(scale)
|
||||
.expect(&*format!("Unit {} missing", scale));
|
||||
if top.unit != bottom.unit {
|
||||
Err(QueryError::Conformance(
|
||||
self.conformance_err(&top, &bottom)))
|
||||
} else {
|
||||
let res = (top - &self.lookup(base)
|
||||
.expect(&*format!("Constant {} missing", base))).unwrap();
|
||||
let res = (&res / &bottom).unwrap();
|
||||
let mut name = BTreeMap::new();
|
||||
name.insert(deg.to_string(), 1);
|
||||
Ok(QueryReply::Conversion(self.show(
|
||||
&res, &bottom,
|
||||
name, Num::one(),
|
||||
10,
|
||||
digits
|
||||
)))
|
||||
}
|
||||
},
|
||||
Query::Convert(ref _expr, ref which, Some(base), _digits) => {
|
||||
|
@ -892,12 +850,11 @@ impl Context {
|
|||
let val = match val {
|
||||
None => {
|
||||
let val = try!(self.eval(expr));
|
||||
let val = match val {
|
||||
match val {
|
||||
Value::Number(val) => val,
|
||||
_ => return Err(QueryError::Generic(format!(
|
||||
"Cannot find derivatives of <{}>", val.show(self))),)
|
||||
};
|
||||
val
|
||||
}
|
||||
},
|
||||
Some(val) => val
|
||||
};
|
||||
|
@ -974,7 +931,7 @@ impl Context {
|
|||
let mut cur_cat = None;
|
||||
for (category, name) in out {
|
||||
if category != cur_cat {
|
||||
if cur.len() > 0 {
|
||||
if !cur.is_empty() {
|
||||
let cat_name = cur_cat.and_then(|x| self.category_names.get(x));
|
||||
categories.push(UnitsInCategory {
|
||||
category: cat_name.map(ToOwned::to_owned),
|
||||
|
@ -985,7 +942,7 @@ impl Context {
|
|||
}
|
||||
cur.push(name.clone());
|
||||
}
|
||||
if cur.len() > 0 {
|
||||
if !cur.is_empty() {
|
||||
let cat_name = cur_cat.and_then(|x| self.category_names.get(x));
|
||||
categories.push(UnitsInCategory {
|
||||
category: cat_name.map(ToOwned::to_owned),
|
||||
|
|
|
@ -29,7 +29,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
type Item = Token;
|
||||
|
||||
fn next(&mut self) -> Option<Token> {
|
||||
if self.0.peek() == None {
|
||||
if self.0.peek().is_none() {
|
||||
return None
|
||||
}
|
||||
let res = match self.0.next().unwrap() {
|
||||
|
|
|
@ -53,7 +53,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
type Item = Token;
|
||||
|
||||
fn next(&mut self) -> Option<Token> {
|
||||
if self.0.peek() == None {
|
||||
if self.0.peek().is_none() {
|
||||
return Some(Token::Eof)
|
||||
}
|
||||
let res = match self.0.next().unwrap() {
|
||||
|
@ -90,17 +90,16 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
'\\' => match self.0.next() {
|
||||
Some('\r') => match self.0.next() {
|
||||
Some('\n') => self.next().unwrap(),
|
||||
_ => Token::Error(format!("Expected LF or CRLF line endings"))
|
||||
_ => Token::Error("Expected LF or CRLF line endings".to_string())
|
||||
},
|
||||
Some('\n') => self.next().unwrap(),
|
||||
Some(x) => Token::Error(format!("Invalid escape: \\{}", x)),
|
||||
None => Token::Error(format!("Unexpected EOF")),
|
||||
None => Token::Error("Unexpected EOF".to_string()),
|
||||
},
|
||||
'#' => {
|
||||
while let Some(c) = self.0.next() {
|
||||
match c {
|
||||
'\n' => break,
|
||||
_ => ()
|
||||
if c == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Token::Newline
|
||||
|
@ -134,7 +133,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
_ => break
|
||||
}
|
||||
}
|
||||
if buf.len() > 0 {
|
||||
if !buf.is_empty() {
|
||||
frac = Some(buf)
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +158,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
_ => break
|
||||
}
|
||||
}
|
||||
if buf.len() > 0 {
|
||||
if !buf.is_empty() {
|
||||
exp = Some(buf)
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +213,7 @@ fn parse_term(iter: &mut Iter) -> Expr {
|
|||
Token::Number(num, frac, exp) =>
|
||||
::number::Number::from_parts(&*num, frac.as_ref().map(|x| &**x), exp.as_ref().map(|x| &**x))
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|e| Expr::Error(format!("{}", e))),
|
||||
.unwrap_or_else(|e| Expr::Error(e.to_string())),
|
||||
Token::Plus => Expr::Plus(Box::new(parse_term(iter))),
|
||||
Token::Dash => Expr::Neg(Box::new(parse_term(iter))),
|
||||
Token::Slash => Expr::Frac(
|
||||
|
@ -364,14 +363,14 @@ pub fn parse(iter: &mut Iter) -> Defs {
|
|||
if name.ends_with("-") {
|
||||
name.pop();
|
||||
map.push(DefEntry {
|
||||
name: name,
|
||||
name,
|
||||
def: Rc::new(Def::Prefix(expr)),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
});
|
||||
} else {
|
||||
map.push(DefEntry {
|
||||
name: name,
|
||||
name,
|
||||
def: Rc::new(Def::SPrefix(expr)),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
|
@ -409,7 +408,7 @@ pub fn parse(iter: &mut Iter) -> Defs {
|
|||
iter.next();
|
||||
let expr = parse_expr(iter);
|
||||
map.push(DefEntry {
|
||||
name: name,
|
||||
name,
|
||||
def: Rc::new(Def::Quantity(expr)),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
|
@ -455,10 +454,10 @@ pub fn parse(iter: &mut Iter) -> Defs {
|
|||
let output = parse_div(iter);
|
||||
props.push(Property {
|
||||
output_name: name.clone(),
|
||||
name: name,
|
||||
name,
|
||||
input: Expr::Const(Num::one()),
|
||||
input_name: input_name,
|
||||
output: output,
|
||||
input_name,
|
||||
output,
|
||||
doc: prop_doc.take()
|
||||
});
|
||||
continue
|
||||
|
@ -486,16 +485,16 @@ pub fn parse(iter: &mut Iter) -> Defs {
|
|||
};
|
||||
let input = parse_mul(iter);
|
||||
props.push(Property {
|
||||
name: name,
|
||||
input: input,
|
||||
input_name: input_name,
|
||||
output: output,
|
||||
output_name: output_name,
|
||||
name,
|
||||
input,
|
||||
input_name,
|
||||
output,
|
||||
output_name,
|
||||
doc: prop_doc.take()
|
||||
});
|
||||
}
|
||||
map.push(DefEntry {
|
||||
name: name,
|
||||
name,
|
||||
def: Rc::new(Def::Substance {
|
||||
symbol: None,
|
||||
properties: props
|
||||
|
@ -507,7 +506,7 @@ pub fn parse(iter: &mut Iter) -> Defs {
|
|||
// derived
|
||||
let expr = parse_expr(iter);
|
||||
map.push(DefEntry {
|
||||
name: name,
|
||||
name,
|
||||
def: Rc::new(Def::Unit(expr)),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
|
|
67
src/lib.rs
67
src/lib.rs
|
@ -53,6 +53,7 @@ extern crate serde;
|
|||
#[cfg(feature = "nightly")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate dirs;
|
||||
|
||||
pub mod text_query;
|
||||
pub mod context;
|
||||
|
@ -78,8 +79,6 @@ pub use number::Number;
|
|||
pub use context::Context;
|
||||
pub use value::Value;
|
||||
|
||||
use std::env;
|
||||
use std::convert::From;
|
||||
use std::path::PathBuf;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
|
@ -87,36 +86,10 @@ use std::time::Duration;
|
|||
|
||||
const DATA_FILE_URL: &'static str = "https://raw.githubusercontent.com/tiffany352/rink-rs/master/definitions.units";
|
||||
|
||||
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
||||
pub fn config_dir() -> Result<PathBuf, String> {
|
||||
env::var("XDG_CONFIG_HOME")
|
||||
.map(From::from)
|
||||
.or_else(|_| {
|
||||
env::home_dir()
|
||||
.ok_or("Home dir not present".to_owned())
|
||||
.map(From::from)
|
||||
.map(|mut x: PathBuf| { x.push(".config/"); x })
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn config_dir() -> Result<PathBuf, String> {
|
||||
env::var("APPDATA")
|
||||
.map(From::from)
|
||||
.or_else(|_| {
|
||||
env::home_dir()
|
||||
.ok_or("Home dir not present".to_owned())
|
||||
.map(From::from)
|
||||
.map(|mut x: PathBuf| { x.push("AppData\\Roaming"); x })
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn config_dir() -> Result<PathBuf, String> {
|
||||
env::home_dir()
|
||||
.ok_or("Home dir not present".to_owned())
|
||||
.map(From::from)
|
||||
.map(|mut x: PathBuf| { x.push("Library/Application Support"); x})
|
||||
dirs::config_dir()
|
||||
.map(|mut x: PathBuf| { x.push("rink"); x })
|
||||
.ok_or_else(|| "Could not find config directory".into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "currency")]
|
||||
|
@ -152,8 +125,7 @@ pub fn load() -> Result<Context, String> {
|
|||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
let mut path = try!(config_dir());
|
||||
path.push("rink/");
|
||||
let path = try!(config_dir());
|
||||
let load = |name| {
|
||||
File::open(name)
|
||||
.and_then(|mut f| {
|
||||
|
@ -165,8 +137,8 @@ pub fn load() -> Result<Context, String> {
|
|||
let units =
|
||||
load(Path::new("definitions.units").to_path_buf())
|
||||
.or_else(|_| load(path.join("definitions.units")))
|
||||
.or_else(|_| DEFAULT_FILE.map(|x| x.to_owned()).ok_or(format!(
|
||||
"Did not exist in search path and binary is not compiled with `gpl` feature")))
|
||||
.or_else(|_| DEFAULT_FILE.map(|x| x.to_owned()).ok_or(
|
||||
"Did not exist in search path and binary is not compiled with `gpl` feature".to_string()))
|
||||
.map_err(|e| format!(
|
||||
"Failed to open definitions.units: {}\n\
|
||||
If you installed with `gpl` disabled, then you need to obtain definitions.units \
|
||||
|
@ -209,7 +181,7 @@ pub fn load() -> Result<Context, String> {
|
|||
let mut currency_defs = currency_defs;
|
||||
defs.append(&mut currency_defs.defs);
|
||||
ast::Defs {
|
||||
defs: defs
|
||||
defs,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -312,11 +284,11 @@ pub fn one_line_sandbox(line: &str) -> String {
|
|||
let res = match rx.try_recv() {
|
||||
Ok(res) => res,
|
||||
Err(_) if unsafe { libc::WIFSIGNALED(status) && libc::WTERMSIG(status) == libc::SIGXCPU } =>
|
||||
format!("Calculation timed out"),
|
||||
"Calculation timed out".to_string(),
|
||||
// :(
|
||||
Err(ref e) if format!("{}", e) == "IoError: Connection reset by peer (os error 104)" =>
|
||||
format!("Calculation ran out of memory"),
|
||||
Err(e) => format!("{}", e)
|
||||
Err(ref e) if e.to_string() == "IoError: Connection reset by peer (os error 104)" =>
|
||||
"Calculation ran out of memory".to_string(),
|
||||
Err(e) => e.to_string()
|
||||
};
|
||||
|
||||
println!("Calculation result: {:?}", res);
|
||||
|
@ -347,7 +319,7 @@ fn btree_merge<K: ::std::cmp::Ord+Clone, V:Clone, F:Fn(&V, &V) -> Option<V>>(
|
|||
res.insert(akey.clone(), aval.clone());
|
||||
a.next();
|
||||
},
|
||||
(Some(_), Some(_)) => panic!(),
|
||||
(Some(_), Some(_)) => unreachable!(),
|
||||
(None, Some((bkey, bval))) => {
|
||||
res.insert(bkey.clone(), bval.clone());
|
||||
b.next();
|
||||
|
@ -369,10 +341,9 @@ fn cached(file: &str, url: &str, expiration: Duration) -> Result<File, String> {
|
|||
use std::fs;
|
||||
|
||||
fn ts<T:Display>(x: T) -> String {
|
||||
format!("{}", x)
|
||||
x.to_string()
|
||||
}
|
||||
let mut path = try!(config_dir());
|
||||
path.push("rink/");
|
||||
let mut tmppath = path.clone();
|
||||
path.push(file);
|
||||
let tmpfile = format!("{}.part", file);
|
||||
|
@ -386,14 +357,14 @@ fn cached(file: &str, url: &str, expiration: Duration) -> Result<File, String> {
|
|||
let now = SystemTime::now();
|
||||
let elapsed = try!(now.duration_since(mtime).map_err(ts));
|
||||
if elapsed > expiration {
|
||||
Err(format!("File is out of date"))
|
||||
Err("File is out of date".to_string())
|
||||
} else {
|
||||
Ok(f)
|
||||
}
|
||||
})
|
||||
.or_else(|_| {
|
||||
try!(fs::create_dir_all(path.parent().unwrap()).map_err(|x| format!("{}", x)));
|
||||
let mut f = try!(File::create(tmppath.clone()).map_err(|x| format!("{}", x)));
|
||||
try!(fs::create_dir_all(path.parent().unwrap()).map_err(|x| x.to_string()));
|
||||
let mut f = try!(File::create(tmppath.clone()).map_err(|x| x.to_string()));
|
||||
|
||||
reqwest::get(url)
|
||||
.map_err(|err| format!("Request failed: {}", err))?
|
||||
|
@ -402,7 +373,7 @@ fn cached(file: &str, url: &str, expiration: Duration) -> Result<File, String> {
|
|||
try!(f.sync_all().map_err(|x| format!("{}", x)));
|
||||
drop(f);
|
||||
try!(fs::rename(tmppath.clone(), path.clone())
|
||||
.map_err(|x| format!("{}", x)));
|
||||
File::open(path).map_err(|x| format!("{}", x))
|
||||
.map_err(|x| x.to_string()));
|
||||
File::open(path).map_err(|x| x.to_string())
|
||||
})
|
||||
}
|
||||
|
|
306
src/load.rs
306
src/load.rs
|
@ -11,155 +11,142 @@ use std::rc::Rc;
|
|||
use value::Value;
|
||||
use Context;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
|
||||
enum Name {
|
||||
Unit(Rc<String>),
|
||||
Prefix(Rc<String>),
|
||||
Quantity(Rc<String>),
|
||||
Category(Rc<String>),
|
||||
}
|
||||
|
||||
impl Name {
|
||||
fn name(&self) -> String {
|
||||
match &self {
|
||||
Name::Unit(ref name) |
|
||||
Name::Prefix(ref name) |
|
||||
Name::Quantity(ref name) |
|
||||
Name::Category(ref name) => (**name).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Resolver {
|
||||
interned: BTreeSet<Rc<String>>,
|
||||
input: BTreeMap<Name, Rc<Def>>,
|
||||
sorted: Vec<Name>,
|
||||
unmarked: BTreeSet<Name>,
|
||||
temp_marks: BTreeSet<Name>,
|
||||
docs: BTreeMap<Name, String>,
|
||||
categories: BTreeMap<Name, String>,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
fn intern(&mut self, name: &String) -> Rc<String> {
|
||||
if let Some(v) = self.interned.get(name).cloned() {
|
||||
v
|
||||
} else {
|
||||
let v = Rc::new(name.to_owned());
|
||||
self.interned.insert(v.clone());
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(&mut self, name: &Rc<String>) -> bool {
|
||||
fn inner(ctx: &mut Resolver, name: &Rc<String>) -> bool {
|
||||
[Name::Unit, Name::Prefix, Name::Quantity].iter().any(|f| {
|
||||
let unit = f(name.clone());
|
||||
ctx.input.contains_key(&unit) && {
|
||||
ctx.visit(&unit);
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut outer = |name: &Rc<String>| -> bool {
|
||||
if inner(self, name) {
|
||||
return true;
|
||||
}
|
||||
let mut found = vec![];
|
||||
for pre in self.input.keys() {
|
||||
if let Name::Prefix(ref pre) = *pre {
|
||||
if (*name).starts_with(&**pre) {
|
||||
found.push(pre.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
found.into_iter().any(|pre| {
|
||||
inner(self, &Rc::new(name[pre.len()..].to_owned())) && {
|
||||
let unit = Name::Prefix(pre);
|
||||
self.visit(&unit);
|
||||
true
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
outer(name) || name.ends_with('s') && {
|
||||
let name = &Rc::new(name[0..name.len()-1].to_owned());
|
||||
outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
fn eval(&mut self, expr: &Expr) {
|
||||
match *expr {
|
||||
Expr::Unit(ref name) => {
|
||||
let name = self.intern(name);
|
||||
self.lookup(&name);
|
||||
},
|
||||
Expr::Frac(ref left, ref right) |
|
||||
Expr::Pow(ref left, ref right) |
|
||||
Expr::Add(ref left, ref right) |
|
||||
Expr::Sub(ref left, ref right) => {
|
||||
self.eval(left);
|
||||
self.eval(right);
|
||||
},
|
||||
Expr::Neg(ref expr) | Expr::Plus(ref expr) |
|
||||
Expr::Suffix(_, ref expr) | Expr::Of(_, ref expr) =>
|
||||
self.eval(expr),
|
||||
Expr::Mul(ref exprs) | Expr::Call(_, ref exprs) => for expr in exprs {
|
||||
self.eval(expr);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn visit(&mut self, name: &Name) {
|
||||
if self.temp_marks.get(name).is_some() {
|
||||
println!("Unit {:?} has a dependency cycle", name);
|
||||
return;
|
||||
}
|
||||
if self.unmarked.get(name).is_some() {
|
||||
self.temp_marks.insert(name.clone());
|
||||
if let Some(v) = self.input.get(name).cloned() {
|
||||
match *v {
|
||||
Def::Prefix(ref e) | Def::SPrefix(ref e) | Def::Unit(ref e) |
|
||||
Def::Quantity(ref e) =>
|
||||
self.eval(e),
|
||||
Def::Canonicalization(ref e) => {
|
||||
self.lookup(&Rc::new(e.clone()));
|
||||
},
|
||||
Def::Substance { ref properties, .. } => {
|
||||
for prop in properties {
|
||||
self.eval(&prop.input);
|
||||
self.eval(&prop.output);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
self.unmarked.remove(name);
|
||||
self.temp_marks.remove(name);
|
||||
self.sorted.push(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Takes a parsed definitions.units from
|
||||
/// `gnu_units::parse()`. Prints if there are errors in the file.
|
||||
pub fn load(&mut self, defs: Defs) {
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
|
||||
enum Name {
|
||||
Unit(Rc<String>),
|
||||
Prefix(Rc<String>),
|
||||
Quantity(Rc<String>),
|
||||
Category(Rc<String>),
|
||||
}
|
||||
|
||||
struct Resolver {
|
||||
interned: BTreeSet<Rc<String>>,
|
||||
input: BTreeMap<Name, Rc<Def>>,
|
||||
sorted: Vec<Name>,
|
||||
unmarked: BTreeSet<Name>,
|
||||
temp_marks: BTreeSet<Name>,
|
||||
docs: BTreeMap<Name, String>,
|
||||
categories: BTreeMap<Name, String>,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
fn intern(&mut self, name: &String) -> Rc<String> {
|
||||
if let Some(v) = self.interned.get(name).cloned() {
|
||||
v
|
||||
} else {
|
||||
let v = Rc::new(name.to_owned());
|
||||
self.interned.insert(v.clone());
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(&mut self, name: &Rc<String>) -> Option<()> {
|
||||
fn inner(ctx: &mut Resolver, name: &Rc<String>) -> Option<()> {
|
||||
let unit = Name::Unit(name.clone());
|
||||
if ctx.input.get(&unit).is_some() {
|
||||
ctx.visit(&unit);
|
||||
return Some(())
|
||||
}
|
||||
let unit = Name::Prefix(name.clone());
|
||||
if ctx.input.get(&unit).is_some() {
|
||||
ctx.visit(&unit);
|
||||
return Some(())
|
||||
}
|
||||
let unit = Name::Quantity(name.clone());
|
||||
if ctx.input.get(&unit).is_some() {
|
||||
ctx.visit(&unit);
|
||||
return Some(())
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
if let Some(()) = inner(self, name) {
|
||||
return Some(())
|
||||
}
|
||||
let mut found = vec![];
|
||||
for (pre, _) in &self.input {
|
||||
if let &Name::Prefix(ref pre) = pre {
|
||||
if (*name).starts_with(&**pre) {
|
||||
found.push(pre.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for pre in found {
|
||||
if let Some(()) = inner(self, &Rc::new(name[pre.len()..].to_owned())) {
|
||||
let unit = Name::Prefix(pre);
|
||||
self.visit(&unit);
|
||||
return Some(())
|
||||
}
|
||||
}
|
||||
if name.ends_with("s") {
|
||||
let name = &Rc::new(name[0..name.len()-1].to_owned());
|
||||
if let Some(()) = inner(self, name) {
|
||||
return Some(())
|
||||
}
|
||||
let mut found = vec![];
|
||||
for (pre, _) in &self.input {
|
||||
if let &Name::Prefix(ref pre) = pre {
|
||||
if (*name).starts_with(&**pre) {
|
||||
found.push(pre.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for pre in found {
|
||||
if let Some(()) = inner(self, &Rc::new(name[pre.len()..].to_owned())) {
|
||||
let unit = Name::Prefix(pre);
|
||||
self.visit(&unit);
|
||||
return Some(())
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn eval(&mut self, expr: &Expr) {
|
||||
match *expr {
|
||||
Expr::Unit(ref name) => {
|
||||
let name = self.intern(name);
|
||||
let _ = self.lookup(&name);
|
||||
},
|
||||
Expr::Frac(ref left, ref right) |
|
||||
Expr::Pow(ref left, ref right) |
|
||||
Expr::Add(ref left, ref right) |
|
||||
Expr::Sub(ref left, ref right) => {
|
||||
self.eval(left);
|
||||
self.eval(right);
|
||||
},
|
||||
Expr::Neg(ref expr) | Expr::Plus(ref expr) |
|
||||
Expr::Suffix(_, ref expr) | Expr::Of(_, ref expr) =>
|
||||
self.eval(expr),
|
||||
Expr::Mul(ref exprs) | Expr::Call(_, ref exprs) => for expr in exprs {
|
||||
self.eval(expr);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn visit(&mut self, name: &Name) {
|
||||
if self.temp_marks.get(name).is_some() {
|
||||
println!("Unit {:?} has a dependency cycle", name);
|
||||
return;
|
||||
}
|
||||
if self.unmarked.get(name).is_some() {
|
||||
self.temp_marks.insert(name.clone());
|
||||
if let Some(v) = self.input.get(name).cloned() {
|
||||
match *v {
|
||||
Def::Prefix(ref e) | Def::SPrefix(ref e) | Def::Unit(ref e) |
|
||||
Def::Quantity(ref e) =>
|
||||
self.eval(e),
|
||||
Def::Canonicalization(ref e) => {
|
||||
self.lookup(&Rc::new(e.clone()));
|
||||
},
|
||||
Def::Substance { ref properties, .. } => {
|
||||
for prop in properties {
|
||||
self.eval(&prop.input);
|
||||
self.eval(&prop.output);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
self.unmarked.remove(name);
|
||||
self.temp_marks.remove(name);
|
||||
self.sorted.push(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut resolver = Resolver {
|
||||
interned: BTreeSet::new(),
|
||||
input: BTreeMap::new(),
|
||||
|
@ -227,12 +214,7 @@ impl Context {
|
|||
reverse.insert("katal");
|
||||
|
||||
for (name, def) in udefs {
|
||||
let name = match name {
|
||||
Name::Unit(name) => (*name).clone(),
|
||||
Name::Prefix(name) => (*name).clone(),
|
||||
Name::Quantity(name) => (*name).clone(),
|
||||
Name::Category(name) => (*name).clone(),
|
||||
};
|
||||
let name = name.name();
|
||||
match *def {
|
||||
Def::Dimension => {
|
||||
self.dimensions.insert(Dim::new(&*name));
|
||||
|
@ -256,7 +238,7 @@ impl Context {
|
|||
self.units.insert(name.clone(), v);
|
||||
},
|
||||
Ok(Value::Substance(sub)) => {
|
||||
let sub = if sub.properties.name.contains("+") {
|
||||
let sub = if sub.properties.name.contains('+') {
|
||||
sub.rename(name.clone())
|
||||
} else {
|
||||
sub
|
||||
|
@ -351,9 +333,9 @@ impl Context {
|
|||
);
|
||||
}
|
||||
Ok((prop.name.clone(), Property {
|
||||
input: input,
|
||||
input,
|
||||
input_name: prop.input_name.clone(),
|
||||
output: output,
|
||||
output,
|
||||
output_name: prop.output_name.clone(),
|
||||
doc: prop.doc.clone(),
|
||||
}))
|
||||
|
@ -368,7 +350,7 @@ impl Context {
|
|||
properties: res,
|
||||
}),
|
||||
});
|
||||
if let &Some(ref symbol) = symbol {
|
||||
if let Some(ref symbol) = symbol {
|
||||
self.substance_symbols.insert(symbol.clone(), name.clone());
|
||||
}
|
||||
},
|
||||
|
@ -383,24 +365,14 @@ impl Context {
|
|||
}
|
||||
|
||||
for (name, val) in resolver.docs {
|
||||
let name = match name {
|
||||
Name::Unit(name) => (*name).clone(),
|
||||
Name::Prefix(name) => (*name).clone(),
|
||||
Name::Quantity(name) => (*name).clone(),
|
||||
Name::Category(name) => (*name).clone(),
|
||||
};
|
||||
let name = name.name();
|
||||
if self.docs.insert(name.clone(), val).is_some() {
|
||||
println!("Doc conflict for {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
for (name, val) in resolver.categories {
|
||||
let name = match name {
|
||||
Name::Unit(name) => (*name).clone(),
|
||||
Name::Prefix(name) => (*name).clone(),
|
||||
Name::Quantity(name) => (*name).clone(),
|
||||
Name::Category(name) => (*name).clone(),
|
||||
};
|
||||
let name = name.name();
|
||||
if self.categories.insert(name.clone(), val).is_some() {
|
||||
println!("Category conflict for {}", name);
|
||||
}
|
||||
|
|
|
@ -91,10 +91,11 @@ impl Num {
|
|||
t = m[1][0] * ai + m[1][1];
|
||||
m[1][1] = m[1][0];
|
||||
m[1][0] = t;
|
||||
if x == ai as f64 {
|
||||
let tmp = x - ai as f64;
|
||||
if tmp == 0.0 {
|
||||
break; // division by zero
|
||||
}
|
||||
x = 1.0/(x - ai as f64);
|
||||
x = tmp.recip();
|
||||
if x as i64 > i64::max_value() / 2 {
|
||||
break; // representation failure
|
||||
}
|
||||
|
|
107
src/number.rs
107
src/number.rs
|
@ -156,7 +156,7 @@ pub fn to_string(rational: &Num, base: u8, digits: Digits) -> (bool, String) {
|
|||
|
||||
/// Several stringified properties of a number which are useful for
|
||||
/// displaying it to a user.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(feature = "nightly", derive(Serialize, Deserialize))]
|
||||
pub struct NumberParts {
|
||||
/// Present if the number can be concisely represented exactly.
|
||||
|
@ -179,21 +179,6 @@ pub struct NumberParts {
|
|||
pub dimensions: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for NumberParts {
|
||||
fn default() -> Self {
|
||||
NumberParts {
|
||||
exact_value: None,
|
||||
approx_value: None,
|
||||
factor: None,
|
||||
divfactor: None,
|
||||
raw_unit: None,
|
||||
unit: None,
|
||||
quantity: None,
|
||||
dimensions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NumberParts {
|
||||
/// A DSL for formatting numbers.
|
||||
///
|
||||
|
@ -235,34 +220,32 @@ impl NumberParts {
|
|||
(None, None) => continue,
|
||||
},
|
||||
'u' => if let Some(unit) = self.raw_unit.as_ref() {
|
||||
if unit.len() == 0 { continue }
|
||||
if unit.is_empty() { continue }
|
||||
let mut frac = vec![];
|
||||
let mut toks = vec![];
|
||||
|
||||
if let Some(f) = self.factor.as_ref() {
|
||||
toks.push(format!("*"));
|
||||
toks.push(format!("{}", f));
|
||||
toks.push("*".to_string());
|
||||
toks.push(f.to_string());
|
||||
}
|
||||
for (dim, &exp) in unit {
|
||||
if exp < 0 {
|
||||
frac.push((dim, exp));
|
||||
} else if exp == 1 {
|
||||
toks.push(dim.to_string())
|
||||
} else {
|
||||
if exp == 1 {
|
||||
toks.push(format!("{}", dim))
|
||||
} else {
|
||||
toks.push(format!("{}^{}", dim, exp))
|
||||
}
|
||||
toks.push(format!("{}^{}", dim, exp))
|
||||
}
|
||||
}
|
||||
if frac.len() > 0 {
|
||||
toks.push(format!("/"));
|
||||
if !frac.is_empty() {
|
||||
toks.push("/".to_string());
|
||||
if let Some(d) = self.divfactor.as_ref() {
|
||||
toks.push(format!("{}", d));
|
||||
toks.push(d.to_string());
|
||||
}
|
||||
for (dim, exp) in frac {
|
||||
let exp = -exp;
|
||||
if exp == 1 {
|
||||
toks.push(format!("{}", dim))
|
||||
toks.push(dim.to_string())
|
||||
} else {
|
||||
toks.push(format!("{}^{}", dim, exp))
|
||||
}
|
||||
|
@ -270,7 +253,7 @@ impl NumberParts {
|
|||
}
|
||||
write!(out, "{}", toks.join(" ")).unwrap();
|
||||
} else if let Some(unit) = self.unit.as_ref() {
|
||||
if unit.len() == 0 { continue }
|
||||
if unit.is_empty() { continue }
|
||||
if let Some(f) = self.factor.as_ref() {
|
||||
write!(out, "* {} ", f).unwrap();
|
||||
}
|
||||
|
@ -279,7 +262,7 @@ impl NumberParts {
|
|||
}
|
||||
write!(out, "{}", unit).unwrap();
|
||||
} else if let Some(dim) = self.dimensions.as_ref() {
|
||||
if dim.len() == 0 { continue }
|
||||
if dim.is_empty() { continue }
|
||||
if let Some(f) = self.factor.as_ref() {
|
||||
write!(out, "* {} ", f).unwrap();
|
||||
}
|
||||
|
@ -301,19 +284,19 @@ impl NumberParts {
|
|||
continue
|
||||
},
|
||||
'd' => if let Some(dim) = self.dimensions.as_ref() {
|
||||
if self.unit.is_none() || dim.len() == 0 { continue }
|
||||
if self.unit.is_none() || dim.is_empty() { continue }
|
||||
write!(out, "{}", dim).unwrap();
|
||||
} else {
|
||||
continue
|
||||
},
|
||||
'D' => if let Some(dim) = self.dimensions.as_ref() {
|
||||
if dim.len() == 0 { continue }
|
||||
if dim.is_empty() { continue }
|
||||
write!(out, "{}", dim).unwrap();
|
||||
} else {
|
||||
continue
|
||||
},
|
||||
'p' => match (self.quantity.as_ref(), self.dimensions.as_ref().and_then(|x| {
|
||||
if self.unit.is_some() && x.len() > 0 {
|
||||
if self.unit.is_some() && !x.is_empty() {
|
||||
Some(x)
|
||||
} else {
|
||||
None
|
||||
|
@ -432,7 +415,7 @@ impl Number {
|
|||
.collect::<Unit>();
|
||||
Number {
|
||||
value: pow(&self.value, exp),
|
||||
unit: unit
|
||||
unit,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -440,13 +423,12 @@ impl Number {
|
|||
/// powers divisible by n.
|
||||
pub fn root(&self, exp: i32) -> Result<Number, String> {
|
||||
if self.value < Num::zero() {
|
||||
return Err(format!("Complex numbers are not implemented"))
|
||||
return Err("Complex numbers are not implemented".to_string())
|
||||
}
|
||||
let mut res = Unit::new();
|
||||
for (dim, &power) in &self.unit {
|
||||
if power % exp as i64 != 0 {
|
||||
return Err(format!(
|
||||
"Result must have integer dimensions"))
|
||||
return Err("Result must have integer dimensions".to_string())
|
||||
} else {
|
||||
res.insert(dim.clone(), power / exp as i64);
|
||||
}
|
||||
|
@ -459,10 +441,10 @@ impl Number {
|
|||
|
||||
pub fn pow(&self, exp: &Number) -> Result<Number, String> {
|
||||
if !exp.dimless() {
|
||||
return Err(format!("Exponent must be dimensionless"))
|
||||
return Err("Exponent must be dimensionless".to_string())
|
||||
}
|
||||
if exp.value.abs() >= Num::from(1 << 31) {
|
||||
return Err(format!("Exponent is too large"))
|
||||
return Err("Exponent is too large".to_string())
|
||||
}
|
||||
let (num, den) = exp.value.to_rational();
|
||||
let one = Int::one();
|
||||
|
@ -472,19 +454,16 @@ impl Number {
|
|||
} else if num == one {
|
||||
let exp: Option<i64> = (&den).into();
|
||||
self.root(exp.unwrap() as i32)
|
||||
} else if !self.dimless() {
|
||||
Err("Exponentiation must result in integer dimensions".to_string())
|
||||
} else {
|
||||
if !self.dimless() {
|
||||
Err(format!(
|
||||
"Exponentiation must result in integer dimensions"))
|
||||
} else {
|
||||
let exp = exp.value.to_f64();
|
||||
Ok(Number {
|
||||
value: Num::Float(
|
||||
self.value.to_f64().powf(exp)
|
||||
),
|
||||
unit: self.unit.clone()
|
||||
})
|
||||
}
|
||||
let exp = exp.value.to_f64();
|
||||
Ok(Number {
|
||||
value: Num::Float(
|
||||
self.value.to_f64().powf(exp)
|
||||
),
|
||||
unit: self.unit.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -496,8 +475,8 @@ impl Number {
|
|||
|
||||
match to_string(&self.value, base, digits) {
|
||||
(true, v) => (Some(v), None),
|
||||
(false, v) => if {den > Mpz::from(1_000) ||
|
||||
num > Mpz::from(1_000_000u64)} {
|
||||
(false, v) => if den > Mpz::from(1_000) ||
|
||||
num > Mpz::from(1_000_000u64) {
|
||||
(None, Some(v))
|
||||
} else {
|
||||
(Some(format!("{}/{}", num, den)), Some(v))
|
||||
|
@ -543,13 +522,13 @@ impl Number {
|
|||
continue;
|
||||
}
|
||||
let abs = val.abs();
|
||||
if { abs >= pow(&v.value, (*orig.1) as i32) &&
|
||||
abs < pow(&(&v.value * &Num::from(1000)),
|
||||
(*orig.1) as i32) } {
|
||||
if abs >= pow(&v.value, (*orig.1) as i32) &&
|
||||
abs < pow(&(&v.value * &Num::from(1000)),
|
||||
(*orig.1) as i32) {
|
||||
let res = &val / &pow(&v.value, (*orig.1) as i32);
|
||||
// tonne special case
|
||||
let unit = if &**(orig.0).0 == "gram" && p == "mega" {
|
||||
format!("tonne")
|
||||
"tonne".to_string()
|
||||
} else {
|
||||
format!("{}{}", p, orig.0)
|
||||
};
|
||||
|
@ -570,7 +549,7 @@ impl Number {
|
|||
} else {
|
||||
Number {
|
||||
value: self.value.clone(),
|
||||
unit: unit,
|
||||
unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -598,7 +577,7 @@ impl Number {
|
|||
approx_value: approx,
|
||||
unit: if value.unit != self.unit { Some(Number::unit_to_string(&value.unit)) } else { None },
|
||||
raw_unit: if value.unit != self.unit { Some(value.unit) } else { None },
|
||||
quantity: quantity,
|
||||
quantity,
|
||||
dimensions: Some(Number::unit_to_string(&self.unit)),
|
||||
..Default::default()
|
||||
}
|
||||
|
@ -620,7 +599,7 @@ impl Number {
|
|||
}
|
||||
}
|
||||
}
|
||||
if frac.len() > 0 {
|
||||
if !frac.is_empty() {
|
||||
write!(out, " /").unwrap();
|
||||
for (dim, exp) in frac {
|
||||
let exp = -exp;
|
||||
|
@ -631,7 +610,7 @@ impl Number {
|
|||
}
|
||||
}
|
||||
|
||||
if out.len() > 0 {
|
||||
if !out.is_empty() {
|
||||
out.remove(0);
|
||||
}
|
||||
String::from_utf8(out).unwrap()
|
||||
|
@ -646,11 +625,11 @@ impl Number {
|
|||
}
|
||||
|
||||
pub fn complexity_score(&self) -> i64 {
|
||||
self.unit.iter().map(|(_, p)| 1 + p.abs()).fold(0, |a,x| a+x)
|
||||
self.unit.iter().map(|(_, p)| 1 + p.abs()).sum()
|
||||
}
|
||||
|
||||
pub fn dimless(&self) -> bool {
|
||||
self.unit.len() == 0
|
||||
self.unit.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -664,7 +643,7 @@ impl fmt::Debug for Number {
|
|||
impl Show for Number {
|
||||
fn show(&self, context: &Context) -> String {
|
||||
let parts = self.to_parts(context);
|
||||
format!("{}", parts)
|
||||
parts.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
src/reply.rs
24
src/reply.rs
|
@ -189,7 +189,7 @@ impl ExprReply {
|
|||
Expr::Quote(ref name) => literal!(format!("'{}'", name)),
|
||||
Expr::Const(ref num) => {
|
||||
let (_exact, val) = ::number::to_string(num, 10, Digits::Default);
|
||||
literal!(format!("{}", val))
|
||||
literal!(val.to_string())
|
||||
},
|
||||
Expr::Date(ref _date) => literal!("NYI: date expr to expr parts"),
|
||||
Expr::Mul(ref exprs) => {
|
||||
|
@ -203,8 +203,8 @@ impl ExprReply {
|
|||
literal!(")");
|
||||
}
|
||||
},
|
||||
Expr::Call(ref name, ref args) => {
|
||||
literal!(format!("{}(", name));
|
||||
Expr::Call(ref func, ref args) => {
|
||||
literal!(format!("{}(", func.name()));
|
||||
if let Some(first) = args.first() {
|
||||
recurse(first, parts, Prec::Equals);
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ impl ExprReply {
|
|||
literal!("(");
|
||||
}
|
||||
recurse(expr, parts, Prec::Mul);
|
||||
literal!(format!("{}", op));
|
||||
literal!(op.to_string());
|
||||
if prec < Prec::Mul {
|
||||
literal!(")");
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ impl DateReply {
|
|||
where Tz: TimeZone, Tz::Offset: Display {
|
||||
use chrono::{Datelike, Timelike};
|
||||
DateReply {
|
||||
string: format!("{}", date),
|
||||
string: date.to_string(),
|
||||
year: date.year(),
|
||||
month: date.month() as i32,
|
||||
day: date.day() as i32,
|
||||
|
@ -392,7 +392,7 @@ impl Display for FactorizeReply {
|
|||
fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
|
||||
write!(fmt, "Factorizations: {}", self.factorizations.iter().map(|x| {
|
||||
x.iter().map(|(u, p)| {
|
||||
if *p == 1 { format!("{}", u) }
|
||||
if *p == 1 { u.to_string() }
|
||||
else { format!("{}^{}", u, p) }
|
||||
}).collect::<Vec<_>>().join(" ")
|
||||
}).collect::<Vec<_>>().join("; "))
|
||||
|
@ -416,16 +416,10 @@ impl Display for DurationReply {
|
|||
let res = [&self.years, &self.months, &self.weeks, &self.days,
|
||||
&self.hours, &self.minutes]
|
||||
.iter()
|
||||
.filter_map(|x| {
|
||||
if x.exact_value.as_ref().map(|x| &**x) == Some("0") {
|
||||
None
|
||||
} else {
|
||||
Some(x)
|
||||
}
|
||||
})
|
||||
.filter(|x| x.exact_value.as_ref().map(|x| &**x) != Some("0"))
|
||||
.chain(once(&&self.seconds))
|
||||
.map(|x| {
|
||||
format!("{}", x)
|
||||
x.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
@ -442,7 +436,7 @@ impl Display for UnitListReply {
|
|||
fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
|
||||
try!(write!(fmt, "{}",
|
||||
self.list.iter()
|
||||
.map(|x| format!("{}", x))
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")));
|
||||
if let Some(q) = self.rest.quantity.as_ref() {
|
||||
|
|
|
@ -59,13 +59,13 @@ pub fn search<'a>(ctx: &'a Context, query: &str, num_results: usize) -> Vec<&'a
|
|||
for k in &ctx.dimensions {
|
||||
try(&**k.0);
|
||||
}
|
||||
for (k, _v) in &ctx.units {
|
||||
for k in ctx.units.keys() {
|
||||
try(&**k);
|
||||
}
|
||||
for (_u, k) in &ctx.quantities {
|
||||
for k in ctx.quantities.values() {
|
||||
try(&**k);
|
||||
}
|
||||
for (k, _sub) in &ctx.substances {
|
||||
for k in ctx.substances.keys() {
|
||||
try(&**k);
|
||||
}
|
||||
}
|
||||
|
|
166
src/substance.rs
166
src/substance.rs
|
@ -13,6 +13,18 @@ use std::iter::once;
|
|||
use std::rc::Rc;
|
||||
use ast::Digits;
|
||||
|
||||
macro_rules! try_div {
|
||||
($x:expr, $y:expr, $context:expr) => {
|
||||
(&$x / &$y).ok_or_else(|| {
|
||||
format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
$x.show($context),
|
||||
$y.show($context)
|
||||
)
|
||||
})?
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Property {
|
||||
pub input: Number,
|
||||
|
@ -44,7 +56,7 @@ impl Substance {
|
|||
Substance {
|
||||
amount: self.amount,
|
||||
properties: Rc::new(Properties {
|
||||
name: name,
|
||||
name,
|
||||
properties: self.properties.properties.clone()
|
||||
})
|
||||
}
|
||||
|
@ -61,7 +73,7 @@ impl Substance {
|
|||
.expect("Non-zero property")
|
||||
})
|
||||
} else {
|
||||
for (_name, prop) in &self.properties.properties {
|
||||
for prop in self.properties.properties.values() {
|
||||
if name == prop.output_name {
|
||||
let input = try!(
|
||||
(&prop.input / &self.amount).ok_or_else(
|
||||
|
@ -117,12 +129,7 @@ impl Substance {
|
|||
properties: try!(self.properties.properties.iter().map(|(k, v)| {
|
||||
let (input, output) = if v.input.dimless() {
|
||||
let res = (&v.output * &self.amount).unwrap();
|
||||
(None, try!((&res / &v.input)
|
||||
.ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
res.show(context),
|
||||
v.input.show(context)
|
||||
))))
|
||||
(None, try_div!(res, v.input, context))
|
||||
} else {
|
||||
(Some(v.input.clone()), v.output.clone())
|
||||
};
|
||||
|
@ -140,26 +147,14 @@ impl Substance {
|
|||
(input, output)
|
||||
};
|
||||
let output_show = context.show(
|
||||
&try!((
|
||||
&output / &unit
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
unit.show(context)
|
||||
))),
|
||||
&try_div!(output, unit, context),
|
||||
&unit,
|
||||
bottom_name.clone(),
|
||||
bottom_const.clone(),
|
||||
base,
|
||||
digits
|
||||
).value;
|
||||
let output = try!((
|
||||
&output / &unit
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
unit.show(context)
|
||||
)));
|
||||
let output = try_div!(output, unit, context);
|
||||
let input: Option<Number> = input;
|
||||
Ok(Some(PropertyReply {
|
||||
name: k.clone(),
|
||||
|
@ -168,14 +163,8 @@ impl Substance {
|
|||
let mut output_pretty = output.clone();
|
||||
output_pretty.unit = bottom_name.iter()
|
||||
.map(|(k,v)| (Dim::new(&k), *v as i64)).collect();
|
||||
let mut res = try!((
|
||||
&output_pretty / &input_pretty
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
input.show(context)
|
||||
))).to_parts(context);
|
||||
let value = (&unit / &input)
|
||||
let mut res = try_div!(output_pretty, input_pretty, context).to_parts(context);
|
||||
let value = (&unit / input)
|
||||
.expect("Already known safe").to_parts(context);
|
||||
res.quantity = value.quantity;
|
||||
res
|
||||
|
@ -190,80 +179,42 @@ impl Substance {
|
|||
})
|
||||
} else {
|
||||
let func = |(_k, v): (&String, &Property)| {
|
||||
let input = try!((&v.input / &self.amount).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.input.show(context),
|
||||
self.amount.show(context)
|
||||
)));
|
||||
let output = try!((&v.output / &self.amount).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.output.show(context),
|
||||
self.amount.show(context)
|
||||
)));
|
||||
let input = try_div!(v.input, self.amount, context);
|
||||
let output = try_div!(v.output, self.amount, context);
|
||||
let (name, input, output) = if input.dimless() {
|
||||
if v.output.unit != unit.unit {
|
||||
return Ok(None)
|
||||
}
|
||||
let div = try!(
|
||||
(&v.output / &input).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.output.show(context),
|
||||
input.show(context)
|
||||
))
|
||||
);
|
||||
let div = try_div!(v.output, input, context);
|
||||
(v.output_name.clone(), None, div)
|
||||
} else if output.dimless() {
|
||||
if v.input.unit != unit.unit {
|
||||
return Ok(None)
|
||||
}
|
||||
let div = try!(
|
||||
(&v.input / &output).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.input.show(context),
|
||||
output.show(context)
|
||||
))
|
||||
);
|
||||
let div = try_div!(v.input, output, context);
|
||||
(v.input_name.clone(), None, div)
|
||||
} else {
|
||||
return Ok(None)
|
||||
};
|
||||
let output_show = context.show(
|
||||
&try!((
|
||||
&output / &unit
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
unit.show(context)
|
||||
))),
|
||||
&try_div!(output, unit, context),
|
||||
&unit,
|
||||
bottom_name.clone(),
|
||||
bottom_const.clone(),
|
||||
base,
|
||||
digits
|
||||
).value;
|
||||
let output = try!((
|
||||
&output / &unit
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
unit.show(context)
|
||||
)));
|
||||
let output = try_div!(output, unit, context);
|
||||
let input: Option<Number> = input;
|
||||
Ok(Some(PropertyReply {
|
||||
name: name,
|
||||
name,
|
||||
value: if let Some(input) = input.as_ref() {
|
||||
let input_pretty = input.prettify(context);
|
||||
let mut output_pretty = output.clone();
|
||||
output_pretty.unit = bottom_name.iter()
|
||||
.map(|(k,v)| (Dim::new(&k), *v as i64)).collect();
|
||||
let mut res = try!((
|
||||
&output_pretty / &input_pretty
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
input.show(context)
|
||||
))).to_parts(context);
|
||||
let value = (&unit / &input)
|
||||
let mut res = try_div!(output_pretty, input_pretty, context).to_parts(context);
|
||||
let value = (&unit / input)
|
||||
.expect("Already known safe").to_parts(context);
|
||||
res.quantity = value.quantity;
|
||||
res
|
||||
|
@ -303,12 +254,7 @@ impl Substance {
|
|||
properties: try!(self.properties.properties.iter().map(|(k, v)| {
|
||||
let (input, output) = if v.input.dimless() {
|
||||
let res = (&v.output * &self.amount).unwrap();
|
||||
(None, try!((&res / &v.input)
|
||||
.ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
res.show(context),
|
||||
v.input.show(context)
|
||||
))))
|
||||
(None, try_div!(res, v.input, context))
|
||||
} else {
|
||||
(Some(v.input.clone()), v.output.clone())
|
||||
};
|
||||
|
@ -317,14 +263,8 @@ impl Substance {
|
|||
value: if let Some(input) = input.as_ref() {
|
||||
let input_pretty = input.prettify(context);
|
||||
let output_pretty = output.prettify(context);
|
||||
let mut res = try!((
|
||||
&output_pretty / &input_pretty
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
input.show(context)
|
||||
))).to_parts(context);
|
||||
let value = (&output / &input)
|
||||
let mut res = try_div!(output_pretty, input_pretty, context).to_parts(context);
|
||||
let value = (&output / input)
|
||||
.expect("Already known safe").to_parts(context);
|
||||
res.quantity = value.quantity;
|
||||
res
|
||||
|
@ -337,51 +277,25 @@ impl Substance {
|
|||
})
|
||||
} else {
|
||||
let func = |(_k, v): (&String, &Property)| {
|
||||
let input = try!((&v.input / &self.amount).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.input.show(context),
|
||||
self.amount.show(context)
|
||||
)));
|
||||
let output = try!((&v.output / &self.amount).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.output.show(context),
|
||||
self.amount.show(context)
|
||||
)));
|
||||
let input = try_div!(v.input, self.amount, context);
|
||||
let output = try_div!(v.output, self.amount, context);
|
||||
let (name, input, output) = if input.dimless() {
|
||||
let div = try!(
|
||||
(&v.output / &input).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.output.show(context),
|
||||
input.show(context)
|
||||
))
|
||||
);
|
||||
let div = try_div!(v.output, input, context);
|
||||
(v.output_name.clone(), None, div)
|
||||
} else if output.dimless() {
|
||||
let div = try!(
|
||||
(&v.input / &output).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
v.input.show(context),
|
||||
output.show(context)
|
||||
))
|
||||
);
|
||||
let div = try_div!(v.input, output, context);
|
||||
(v.input_name.clone(), None, div)
|
||||
} else {
|
||||
return Ok(None)
|
||||
};
|
||||
let input: Option<Number> = input;
|
||||
Ok(Some(PropertyReply {
|
||||
name: name,
|
||||
name,
|
||||
value: if let Some(input) = input.as_ref() {
|
||||
let input_pretty = input.prettify(context);
|
||||
let output_pretty = output.prettify(context);
|
||||
let mut res = try!((
|
||||
&output_pretty / &input_pretty
|
||||
).ok_or_else(|| format!(
|
||||
"Division by zero: <{}> / <{}>",
|
||||
output.show(context),
|
||||
input.show(context)
|
||||
))).to_parts(context);
|
||||
let value = (&output / &input)
|
||||
let mut res = try_div!(output_pretty, input_pretty, context).to_parts(context);
|
||||
let value = (&output / input)
|
||||
.expect("Already known safe").to_parts(context);
|
||||
res.quantity = value.quantity;
|
||||
res
|
||||
|
@ -486,8 +400,8 @@ impl<'a, 'b> Add<&'b Substance> for &'a Substance {
|
|||
}).collect()
|
||||
})
|
||||
};
|
||||
if res.properties.properties.len() == 0 {
|
||||
Err(format!("No shared properties"))
|
||||
if res.properties.properties.is_empty() {
|
||||
Err("No shared properties".to_string())
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
|
|
|
@ -35,12 +35,7 @@ pub enum Token {
|
|||
Colon,
|
||||
Date(Vec<DateToken>),
|
||||
Comma,
|
||||
DegC,
|
||||
DegF,
|
||||
DegRe,
|
||||
DegRo,
|
||||
DegDe,
|
||||
DegN,
|
||||
Degree(Degree),
|
||||
Percent,
|
||||
Error(String),
|
||||
}
|
||||
|
@ -69,13 +64,8 @@ fn describe(token: &Token) -> String {
|
|||
Token::Colon => "`:`".to_owned(),
|
||||
Token::Date(_) => "date literal".to_owned(),
|
||||
Token::Comma => "`,`".to_owned(),
|
||||
Token::DegC => "`°C`".to_owned(),
|
||||
Token::DegF => "`°F`".to_owned(),
|
||||
Token::DegRe => "`°Ré`".to_owned(),
|
||||
Token::DegRo => "`°Rø`".to_owned(),
|
||||
Token::DegDe => "`°De`".to_owned(),
|
||||
Token::DegN => "`°N`".to_owned(),
|
||||
Token::Percent => "%".to_owned(),
|
||||
Token::Degree(ref deg) => format!("`{}`", deg),
|
||||
Token::Error(ref e) => format!("<{}>", e)
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +83,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
type Item = Token;
|
||||
|
||||
fn next(&mut self) -> Option<Token> {
|
||||
if self.0.peek() == None {
|
||||
if self.0.peek().is_none() {
|
||||
return Some(Token::Eof)
|
||||
}
|
||||
let res = match self.0.next().unwrap() {
|
||||
|
@ -144,7 +134,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
}
|
||||
}
|
||||
if self.0.peek() == None {
|
||||
return Some(Token::Error(format!("Expected `*/`, got EOF")))
|
||||
return Some(Token::Error("Expected `*/`, got EOF".to_string()))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -165,7 +155,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
_ => break
|
||||
}
|
||||
}
|
||||
if hex.len() == 0 {
|
||||
if hex.is_empty() {
|
||||
return Some(Token::Error(
|
||||
"Malformed hexadecimal literal: No digits after 0x".to_owned()))
|
||||
}
|
||||
|
@ -186,7 +176,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
_ => break
|
||||
}
|
||||
}
|
||||
if oct.len() == 0 {
|
||||
if oct.is_empty() {
|
||||
return Some(Token::Error(
|
||||
"Malformed octal literal: No digits after 0o".to_owned()))
|
||||
}
|
||||
|
@ -207,7 +197,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
_ => break
|
||||
}
|
||||
}
|
||||
if bin.len() == 0 {
|
||||
if bin.is_empty() {
|
||||
return Some(Token::Error(
|
||||
"Malformed binary literal: No digits after 0b".to_owned()))
|
||||
}
|
||||
|
@ -248,7 +238,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
_ => break
|
||||
}
|
||||
}
|
||||
if buf.len() == 0 {
|
||||
if buf.is_empty() {
|
||||
return Some(Token::Error(
|
||||
"Malformed number literal: No digits after decimal point".to_owned()))
|
||||
}
|
||||
|
@ -281,7 +271,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
_ => break
|
||||
}
|
||||
}
|
||||
if buf.len() == 0 {
|
||||
if buf.is_empty() {
|
||||
return Some(Token::Error(
|
||||
"Malformed number literal: No digits after exponent".to_owned()))
|
||||
}
|
||||
|
@ -308,19 +298,19 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
Token::Error(format!("Invalid unicode scalar: {:x}", v))
|
||||
}
|
||||
},
|
||||
_ => Token::Error(format!("Unexpected \\"))
|
||||
_ => Token::Error("Unexpected \\".to_string())
|
||||
},
|
||||
'\'' => {
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match self.0.next() {
|
||||
None | Some('\n') => return Some(Token::Error(format!("Unexpected newline or EOF"))),
|
||||
None | Some('\n') => return Some(Token::Error("Unexpected newline or EOF".to_string())),
|
||||
Some('\\') => match self.0.next() {
|
||||
Some('\'') => buf.push('\''),
|
||||
Some('n') => buf.push('\n'),
|
||||
Some('t') => buf.push('\t'),
|
||||
Some(c) => return Some(Token::Error(format!("Invalid escape sequence \\{}", c))),
|
||||
None => return Some(Token::Error(format!("Unexpected EOF"))),
|
||||
None => return Some(Token::Error("Unexpected EOF".to_string())),
|
||||
},
|
||||
Some('\'') => break,
|
||||
Some(c) => buf.push(c),
|
||||
|
@ -421,12 +411,12 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
}
|
||||
}
|
||||
match &*buf {
|
||||
"degC" | "°C" | "celsius" | "℃" => Token::DegC,
|
||||
"degF" | "°F" | "fahrenheit" | "℉" => Token::DegF,
|
||||
"degRé" | "°Ré" | "degRe" | "°Re" | "réaumur" | "reaumur" => Token::DegRe,
|
||||
"degRø" | "°Rø" | "degRo" | "°Ro" | "rømer" | "romer" => Token::DegRo,
|
||||
"degDe" | "°De" | "delisle" => Token::DegDe,
|
||||
"degN" | "°N" | "degnewton" => Token::DegN,
|
||||
"degC" | "°C" | "celsius" | "℃" => Token::Degree(Degree::Celsius),
|
||||
"degF" | "°F" | "fahrenheit" | "℉" => Token::Degree(Degree::Fahrenheit),
|
||||
"degRé" | "°Ré" | "degRe" | "°Re" | "réaumur" | "reaumur" => Token::Degree(Degree::Reaumur),
|
||||
"degRø" | "°Rø" | "degRo" | "°Ro" | "rømer" | "romer" => Token::Degree(Degree::Romer),
|
||||
"degDe" | "°De" | "delisle" => Token::Degree(Degree::Delisle),
|
||||
"degN" | "°N" | "degnewton" => Token::Degree(Degree::Newton),
|
||||
"per" => Token::Slash,
|
||||
"to" | "in" => Token::DashArrow,
|
||||
_ => Token::Ident(buf)
|
||||
|
@ -439,33 +429,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||
|
||||
pub type Iter<'a> = Peekable<TokenIterator<'a>>;
|
||||
|
||||
fn is_func(name: &str) -> bool {
|
||||
match name {
|
||||
"sqrt" => true,
|
||||
"exp" => true,
|
||||
"ln" => true,
|
||||
"log" => true,
|
||||
"log2" => true,
|
||||
"log10" => true,
|
||||
"hypot" => true,
|
||||
"sin" => true,
|
||||
"cos" => true,
|
||||
"tan" => true,
|
||||
"asin" => true,
|
||||
"acos" => true,
|
||||
"atan" => true,
|
||||
"atan2" => true,
|
||||
"sinh" => true,
|
||||
"cosh" => true,
|
||||
"tanh" => true,
|
||||
"asinh" => true,
|
||||
"acosh" => true,
|
||||
"atanh" => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_attr(name: &str) -> Option<&'static str> {
|
||||
fn attr_from_name(name: &str) -> Option<&'static str> {
|
||||
match name {
|
||||
"int" | "international" => Some("int"),
|
||||
"UKSJJ" => Some("UKSJJ"),
|
||||
|
@ -484,74 +448,73 @@ fn is_attr(name: &str) -> Option<&'static str> {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_function(iter: &mut Iter, func: Function) -> Expr {
|
||||
let args = match iter.peek().cloned().unwrap() {
|
||||
Token::LPar => {
|
||||
iter.next();
|
||||
let mut args = vec![];
|
||||
loop {
|
||||
if let Some(&Token::RPar) = iter.peek() {
|
||||
iter.next();
|
||||
break;
|
||||
}
|
||||
args.push(parse_expr(iter));
|
||||
match iter.peek().cloned().unwrap() {
|
||||
Token::Comma => {
|
||||
iter.next();
|
||||
},
|
||||
Token::RPar => (),
|
||||
x => return Expr::Error(format!("Expected `,` or `)`, got {}",
|
||||
describe(&x)))
|
||||
}
|
||||
}
|
||||
args
|
||||
},
|
||||
_ => vec![parse_pow(iter)],
|
||||
};
|
||||
Expr::Call(func, args)
|
||||
}
|
||||
|
||||
fn parse_radix(num: &str, base: u8, description: &str) -> Expr {
|
||||
Mpz::from_str_radix(&*num, base)
|
||||
.map(|x| Mpq::ratio(&x, &Mpz::one()))
|
||||
.map(Num::Mpq)
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|_| Expr::Error(format!("Failed to parse {}", description)))
|
||||
}
|
||||
|
||||
fn parse_term(iter: &mut Iter) -> Expr {
|
||||
match iter.next().unwrap() {
|
||||
Token::Ident(ref name) if is_func(name) => {
|
||||
match iter.peek().cloned().unwrap() {
|
||||
Token::LPar => {
|
||||
iter.next();
|
||||
let mut args = vec![];
|
||||
loop {
|
||||
if let Some(&Token::RPar) = iter.peek() {
|
||||
iter.next();
|
||||
break;
|
||||
}
|
||||
args.push(parse_expr(iter));
|
||||
match iter.peek().cloned().unwrap() {
|
||||
Token::Comma => {
|
||||
iter.next();
|
||||
},
|
||||
Token::RPar => (),
|
||||
x => return Expr::Error(format!("Expected `,` or `)`, got {}",
|
||||
describe(&x)))
|
||||
}
|
||||
}
|
||||
Expr::Call(name.clone(), args)
|
||||
},
|
||||
_ => Expr::Call(name.clone(), vec![parse_pow(iter)]),
|
||||
Token::Ident(ref id) => {
|
||||
if let Some(func) = Function::from_name(id) {
|
||||
parse_function(iter, func)
|
||||
} else if let Some(attr) = attr_from_name(id) {
|
||||
match iter.peek().cloned().unwrap() {
|
||||
Token::Ident(ref name) => {
|
||||
iter.next();
|
||||
Expr::Unit(format!("{}{}", attr, name))
|
||||
},
|
||||
x => Expr::Error(format!("Attribute must be followed by ident, got {}",
|
||||
describe(&x)))
|
||||
}
|
||||
} else {
|
||||
match iter.peek().cloned().unwrap() {
|
||||
Token::Ident(ref s) if s == "of" => {
|
||||
iter.next();
|
||||
Expr::Of(id.clone(), Box::new(parse_juxt(iter)))
|
||||
},
|
||||
_ => Expr::Unit(id.to_string())
|
||||
}
|
||||
}
|
||||
},
|
||||
Token::Ident(ref attr) if is_attr(attr).is_some() => {
|
||||
match iter.peek().cloned().unwrap() {
|
||||
Token::Ident(ref name) => {
|
||||
let attr = is_attr(attr).unwrap();
|
||||
iter.next();
|
||||
Expr::Unit(format!("{}{}", attr, name))
|
||||
},
|
||||
x => Expr::Error(format!("Attribute must be followed by ident, got {}",
|
||||
describe(&x)))
|
||||
}
|
||||
},
|
||||
Token::Ident(name) => match iter.peek().cloned().unwrap() {
|
||||
Token::Ident(ref s) if s == "of" => {
|
||||
iter.next();
|
||||
Expr::Of(name.clone(), Box::new(parse_juxt(iter)))
|
||||
},
|
||||
_ => Expr::Unit(name)
|
||||
},
|
||||
Token::Quote(name) => Expr::Quote(name),
|
||||
Token::Decimal(num, frac, exp) =>
|
||||
::number::Number::from_parts(&*num, frac.as_ref().map(|x| &**x), exp.as_ref().map(|x| &**x))
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|e| Expr::Error(format!("{}", e))),
|
||||
Token::Hex(num) =>
|
||||
Mpz::from_str_radix(&*num, 16)
|
||||
.map(|x| Mpq::ratio(&x, &Mpz::one()))
|
||||
.map(Num::Mpq)
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|_| Expr::Error(format!("Failed to parse hex"))),
|
||||
Token::Oct(num) =>
|
||||
Mpz::from_str_radix(&*num, 8)
|
||||
.map(|x| Mpq::ratio(&x, &Mpz::one()))
|
||||
.map(Num::Mpq)
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|_| Expr::Error(format!("Failed to parse octal"))),
|
||||
Token::Bin(num) =>
|
||||
Mpz::from_str_radix(&*num, 2)
|
||||
.map(|x| Mpq::ratio(&x, &Mpz::one()))
|
||||
.map(Num::Mpq)
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|_| Expr::Error(format!("Failed to parse binary"))),
|
||||
.unwrap_or_else(|e| Expr::Error(e.to_string())),
|
||||
Token::Hex(num) => parse_radix(&*num, 16, "hex"),
|
||||
Token::Oct(num) => parse_radix(&*num, 8, "octal"),
|
||||
Token::Bin(num) => parse_radix(&*num, 2, "binary"),
|
||||
Token::Plus => Expr::Plus(Box::new(parse_term(iter))),
|
||||
Token::Minus => Expr::Neg(Box::new(parse_term(iter))),
|
||||
Token::LPar => {
|
||||
|
@ -614,29 +577,9 @@ fn parse_juxt(iter: &mut Iter) -> Expr {
|
|||
Token::Plus | Token::Minus | Token::DashArrow |
|
||||
Token::RPar | Token::Newline |
|
||||
Token::Comment(_) | Token::Eof => break,
|
||||
Token::DegC => {
|
||||
Token::Degree(deg) => {
|
||||
iter.next();
|
||||
terms = vec![Expr::Suffix(SuffixOp::Celsius, Box::new(Expr::Mul(terms)))]
|
||||
},
|
||||
Token::DegF => {
|
||||
iter.next();
|
||||
terms = vec![Expr::Suffix(SuffixOp::Fahrenheit, Box::new(Expr::Mul(terms)))]
|
||||
},
|
||||
Token::DegRe => {
|
||||
iter.next();
|
||||
terms = vec![Expr::Suffix(SuffixOp::Reaumur, Box::new(Expr::Mul(terms)))]
|
||||
},
|
||||
Token::DegRo => {
|
||||
iter.next();
|
||||
terms = vec![Expr::Suffix(SuffixOp::Romer, Box::new(Expr::Mul(terms)))]
|
||||
},
|
||||
Token::DegDe => {
|
||||
iter.next();
|
||||
terms = vec![Expr::Suffix(SuffixOp::Delisle, Box::new(Expr::Mul(terms)))]
|
||||
},
|
||||
Token::DegN => {
|
||||
iter.next();
|
||||
terms = vec![Expr::Suffix(SuffixOp::Newton, Box::new(Expr::Mul(terms)))]
|
||||
terms = vec![Expr::Suffix(deg, Box::new(Expr::Mul(terms)))]
|
||||
},
|
||||
_ => terms.push(parse_frac(iter))
|
||||
}}
|
||||
|
@ -738,10 +681,10 @@ pub fn parse_offset(iter: &mut Iter) -> Option<i64> {
|
|||
Token::Decimal(ref i, None, None) if i.len() == 2 => i.clone(),
|
||||
_ => return None
|
||||
};
|
||||
let _col = match iter.next().unwrap() {
|
||||
match iter.next().unwrap() {
|
||||
Token::Colon => (),
|
||||
_ => return None
|
||||
};
|
||||
}
|
||||
let min = match iter.next().unwrap() {
|
||||
Token::Decimal(ref i, None, None) if i.len() == 2 => i.clone(),
|
||||
_ => return None
|
||||
|
@ -817,8 +760,9 @@ pub fn parse_query(iter: &mut Iter) -> Query {
|
|||
},
|
||||
Some(x) => return Query::Error(format!(
|
||||
"Expected decimal base, got {}", describe(&x))),
|
||||
None => return Query::Error(format!(
|
||||
"Expected decimal base, got eof"))
|
||||
None => return Query::Error(
|
||||
"Expected decimal base, got eof".to_string()
|
||||
)
|
||||
}
|
||||
},
|
||||
Token::Ident(ref s) if s == "hex" || s == "hexadecimal" || s == "base16" => {
|
||||
|
@ -837,12 +781,7 @@ pub fn parse_query(iter: &mut Iter) -> Query {
|
|||
};
|
||||
let right = match iter.peek().cloned().unwrap() {
|
||||
Token::Eof => Conversion::None,
|
||||
Token::DegC => Conversion::DegC,
|
||||
Token::DegF => Conversion::DegF,
|
||||
Token::DegRe => Conversion::DegRe,
|
||||
Token::DegRo => Conversion::DegRo,
|
||||
Token::DegDe => Conversion::DegDe,
|
||||
Token::DegN => Conversion::DegN,
|
||||
Token::Degree(deg) => Conversion::Degree(deg),
|
||||
Token::Plus | Token::Minus => {
|
||||
let mut old = iter.clone();
|
||||
if let Some(off) = parse_offset(iter) {
|
||||
|
|
30
src/value.rs
30
src/value.rs
|
@ -28,7 +28,7 @@ impl Show for DateTime<FixedOffset> {
|
|||
if let Some(h) = context.humanize(*self) {
|
||||
format!("{} ({})", self, h)
|
||||
} else {
|
||||
format!("{}", self)
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ impl Show for DateTime<Tz> {
|
|||
if let Some(h) = context.humanize(*self) {
|
||||
format!("{} ({})", self, h)
|
||||
} else {
|
||||
format!("{}", self)
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ impl Value {
|
|||
match (self, exp) {
|
||||
(&Value::Number(ref left), &Value::Number(ref right)) =>
|
||||
left.pow(right).map(Value::Number),
|
||||
(_, _) => Err(format!("Operation is not defined"))
|
||||
(_, _) => Err("Operation is not defined".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ impl<'a,'b> Add<&'b Value> for &'a Value {
|
|||
match (self, other) {
|
||||
(&Value::Number(ref left), &Value::Number(ref right)) =>
|
||||
(left + right)
|
||||
.ok_or(format!("Addition of units with mismatched units is not meaningful"))
|
||||
.ok_or("Addition of units with mismatched units is not meaningful".to_string())
|
||||
.map(Value::Number),
|
||||
(&Value::DateTime(ref left), &Value::Number(ref right)) |
|
||||
(&Value::Number(ref right), &Value::DateTime(ref left)) =>
|
||||
|
@ -91,12 +91,12 @@ impl<'a,'b> Add<&'b Value> for &'a Value {
|
|||
right
|
||||
))).map(GenericDateTime::Timezone),
|
||||
}
|
||||
.ok_or(format!("Implementation error: value is out of range representable by datetime"))
|
||||
.ok_or("Implementation error: value is out of range representable by datetime".to_string())
|
||||
.map(Value::DateTime),
|
||||
(&Value::Substance(ref left), &Value::Substance(ref right)) =>
|
||||
left.add(right)
|
||||
.map(Value::Substance),
|
||||
(_, _) => Err(format!("Operation is not defined"))
|
||||
(_, _) => Err("Operation is not defined".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ impl<'a,'b> Sub<&'b Value> for &'a Value {
|
|||
match (self, other) {
|
||||
(&Value::Number(ref left), &Value::Number(ref right)) =>
|
||||
(left - right)
|
||||
.ok_or(format!("Subtraction of units with mismatched units is not meaningful"))
|
||||
.ok_or("Subtraction of units with mismatched units is not meaningful".to_string())
|
||||
.map(Value::Number),
|
||||
(&Value::DateTime(ref left), &Value::Number(ref right)) |
|
||||
(&Value::Number(ref right), &Value::DateTime(ref left)) =>
|
||||
|
@ -120,7 +120,7 @@ impl<'a,'b> Sub<&'b Value> for &'a Value {
|
|||
right
|
||||
))).map(GenericDateTime::Timezone),
|
||||
}
|
||||
.ok_or(format!("Implementation error: value is out of range representable by datetime"))
|
||||
.ok_or("Implementation error: value is out of range representable by datetime".to_string())
|
||||
.map(Value::DateTime),
|
||||
(&Value::DateTime(ref left), &Value::DateTime(ref right)) =>
|
||||
date::from_duration(&match (left, right) {
|
||||
|
@ -134,7 +134,7 @@ impl<'a,'b> Sub<&'b Value> for &'a Value {
|
|||
*left - *right,
|
||||
})
|
||||
.map(Value::Number),
|
||||
(_, _) => Err(format!("Operation is not defined"))
|
||||
(_, _) => Err("Operation is not defined".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,8 +145,8 @@ impl<'a> Neg for &'a Value {
|
|||
fn neg(self) -> Self::Output {
|
||||
match *self {
|
||||
Value::Number(ref num) =>
|
||||
(-num).ok_or(format!("Bug: Negation should not fail")).map(Value::Number),
|
||||
_ => Err(format!("Operation is not defined"))
|
||||
(-num).ok_or("Bug: Negation should not fail".to_string()).map(Value::Number),
|
||||
_ => Err("Operation is not defined".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,12 +158,12 @@ impl<'a,'b> Mul<&'b Value> for &'a Value {
|
|||
match (self, other) {
|
||||
(&Value::Number(ref left), &Value::Number(ref right)) =>
|
||||
(left * right)
|
||||
.ok_or(format!("Bug: Mul should not fail"))
|
||||
.ok_or("Bug: Mul should not fail".to_string())
|
||||
.map(Value::Number),
|
||||
(&Value::Number(ref co), &Value::Substance(ref sub)) |
|
||||
(&Value::Substance(ref sub), &Value::Number(ref co)) =>
|
||||
(sub * co).map(Value::Substance),
|
||||
(_, _) => Err(format!("Operation is not defined"))
|
||||
(_, _) => Err("Operation is not defined".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,11 +175,11 @@ impl<'a,'b> Div<&'b Value> for &'a Value {
|
|||
match (self, other) {
|
||||
(&Value::Number(ref left), &Value::Number(ref right)) =>
|
||||
(left / right)
|
||||
.ok_or(format!("Division by zero"))
|
||||
.ok_or("Division by zero".to_string())
|
||||
.map(Value::Number),
|
||||
(&Value::Substance(ref sub), &Value::Number(ref co)) =>
|
||||
(sub / co).map(Value::Substance),
|
||||
(_, _) => Err(format!("Operation is not defined"))
|
||||
(_, _) => Err("Operation is not defined".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,8 +55,7 @@ fn root(rink: &Rink, req: &mut Request) -> IronResult<Response> {
|
|||
|
||||
let map = req.get_ref::<Params>().unwrap();
|
||||
match map.find(&["q"]) {
|
||||
Some(&Value::String(ref query)) if query == "" => (),
|
||||
Some(&Value::String(ref query)) => {
|
||||
Some(&Value::String(ref query)) if !query.is_empty() => {
|
||||
let mut reply = eval_json(query);
|
||||
reply.as_object_mut().unwrap().insert("input".to_owned(), query.to_json());
|
||||
println!("{}", reply.pretty());
|
||||
|
@ -66,7 +65,7 @@ fn root(rink: &Rink, req: &mut Request) -> IronResult<Response> {
|
|||
_ => (),
|
||||
};
|
||||
|
||||
if data.len() == 0 {
|
||||
if data.is_empty() {
|
||||
data.insert("main-page".to_owned(), true.to_json());
|
||||
}
|
||||
|
||||
|
@ -82,10 +81,10 @@ impl AfterMiddleware for ErrorMiddleware {
|
|||
let mut data = BTreeMap::new();
|
||||
let mut error = BTreeMap::new();
|
||||
if let Some(status) = err.response.status {
|
||||
error.insert("status".to_owned(), format!("{}", status));
|
||||
data.insert("title".to_owned(), format!("{}", status).to_json());
|
||||
error.insert("status".to_owned(), status.to_string());
|
||||
data.insert("title".to_owned(), status.to_string().to_json());
|
||||
}
|
||||
error.insert("message".to_owned(), format!("{}", err.error));
|
||||
error.insert("message".to_owned(), err.error.to_string());
|
||||
data.insert("error".to_owned(), error.to_json());
|
||||
data.insert("config".to_owned(), self.0.config.to_json());
|
||||
println!("{:#?}", data);
|
||||
|
@ -162,7 +161,7 @@ fn urlescapehelper(
|
|||
let res = utf8_percent_encode(&res, QUERY_ENCODE_SET).collect::<String>();
|
||||
let res = res.split("%20").collect::<Vec<_>>().join("+");
|
||||
try!(rc.writer.write_all(res.as_bytes()).map_err(
|
||||
|e| RenderError::new(&format!("{}", e))));
|
||||
|e| RenderError::new(&e.to_string())));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -201,7 +200,7 @@ fn main() {
|
|||
).unwrap()
|
||||
};
|
||||
let rink = Arc::new(Rink {
|
||||
config: config,
|
||||
config,
|
||||
});
|
||||
let (logger_before, logger_after) = Logger::new(None);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ impl Serialize for Error {
|
|||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::Generic(format!("{}", err))
|
||||
Error::Generic(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ pub fn eval(query: &str) -> Result<QueryReply, Error> {
|
|||
Sandbox stdout: {}\n\
|
||||
Sandbox stderr: {}",
|
||||
e,
|
||||
output.status.signal().map(|x| format!("{}", x)).unwrap_or("None".to_owned()),
|
||||
output.status.signal().map(|x| x.to_string()).unwrap_or("None".to_owned()),
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
)))
|
||||
|
@ -122,11 +122,11 @@ pub fn eval(query: &str) -> Result<QueryReply, Error> {
|
|||
|
||||
pub fn eval_text(query: &str) -> String {
|
||||
match eval(query) {
|
||||
Ok(v) => format!("{}", v),
|
||||
Err(Error::Generic(e)) => format!("{}", e),
|
||||
Err(Error::Memory) => format!("Calculation ran out of memory"),
|
||||
Err(Error::Time) => format!("Calculation timed out"),
|
||||
Err(Error::Rink(e)) => format!("{}", e),
|
||||
Ok(v) => v.to_string(),
|
||||
Err(Error::Generic(e)) => e.to_string(),
|
||||
Err(Error::Memory) => "Calculation ran out of memory".to_string(),
|
||||
Err(Error::Time) => "Calculation timed out".to_string(),
|
||||
Err(Error::Rink(e)) => e.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue