diff --git a/Cargo.toml b/Cargo.toml index 1d9f854..2984d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ gpl = [] [dependencies] rust-gmp = "0.3.2" chrono = "0.2.25" +strsim = "0.5.1" chrono-humanize = { version = "0.0.6", optional = true } linefeed = { version = "0.1.4", optional = true } irc = { version = "0.11.3", optional = true } diff --git a/src/eval.rs b/src/eval.rs index 842e64c..a6ec226 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -337,6 +337,39 @@ impl Context { (recip, String::from_utf8(buf).unwrap()) } + fn typo_dym<'a>(&'a self, what: &str) -> Option<&'a str> { + use strsim::jaro_winkler; + + let mut best = None; + { + let mut try = |x: &'a str| { + let score = jaro_winkler(x, what); + let better = best.as_ref().map(|&(s, _v)| score > s).unwrap_or(true); + if better { + best = Some((score, x)); + } + }; + + for k in &self.dimensions { + try(&**k.0); + } + for (k, _v) in &self.units { + try(&**k); + } + for (_u, k) in &self.quantities { + try(&**k); + } + } + best.and_then(|(s, v)| if s > 0.8 { Some(v) } else { None }) + } + + fn unknown_unit_err(&self, name: &str) -> String { + match self.typo_dym(name) { + Some(x) => format!("Unknown unit {}, did you mean {}?", name, x), + None => format!("Unknown unit {}", name) + } + } + /// Evaluates an expression to compute its value, *excluding* `->` /// conversions. pub fn eval(&self, expr: &Expr) -> Result { @@ -371,7 +404,8 @@ impl Context { match *expr { Expr::Unit(ref name) if name == "now" => Ok(Value::DateTime(date::now())), - Expr::Unit(ref name) => self.lookup(name).ok_or(format!("Unknown unit {}", name)).map(Value::Number), + Expr::Unit(ref name) => + self.lookup(name).ok_or_else(|| self.unknown_unit_err(name)).map(Value::Number), Expr::Quote(ref name) => Ok(Value::Number(Number::one_unit(Dim::new(&**name)))), Expr::Const(ref num, ref frac, ref exp) => Number::from_parts( @@ -661,10 +695,7 @@ impl Context { top.show(self), list)) }; let units = try!(list.iter().map(|x| { - match self.lookup(x) { - Some(x) => Ok(x), - None => Err(format!("Unit {} does not exist", x)) - } + self.lookup(x).ok_or_else(|| self.unknown_unit_err(x)) }).collect::, _>>()); { let first = try!(units.first().ok_or(format!("Expected non-empty unit list"))); diff --git a/src/lib.rs b/src/lib.rs index 1f7a685..ef5b92a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ println!("{}", one_line(&mut ctx, "kWh / year -> W").unwrap()); extern crate gmp; extern crate chrono; +extern crate strsim; #[cfg(feature = "chrono-humanize")] extern crate chrono_humanize; #[cfg(feature = "sandbox")]