use std::collections::{BTreeMap, BTreeSet};
use number::{Dim, Number, Unit};
use num::Num;
use ast::{Expr, DatePattern};
use search;
use substance::Substance;
use reply::NotFoundError;
#[derive(Debug)]
pub struct Context {
pub dimensions: BTreeSet<Dim>,
pub canonicalizations: BTreeMap<String, String>,
pub units: BTreeMap<String, Number>,
pub quantities: BTreeMap<Unit, String>,
pub reverse: BTreeMap<Unit, String>,
pub prefixes: Vec<(String, Number)>,
pub definitions: BTreeMap<String, Expr>,
pub docs: BTreeMap<String, String>,
pub categories: BTreeMap<String, String>,
pub category_names: BTreeMap<String, String>,
pub datepatterns: Vec<Vec<DatePattern>>,
pub substances: BTreeMap<String, Substance>,
pub substance_symbols: BTreeMap<String, String>,
pub temporaries: BTreeMap<String, Number>,
pub short_output: bool,
pub use_humanize: bool,
}
impl 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,
}
}
pub fn load_dates(&mut self, mut dates: Vec<Vec<DatePattern>>) {
self.datepatterns.append(&mut dates)
}
pub fn lookup(&self, name: &str) -> Option<Number> {
fn inner(ctx: &Context, name: &str) -> Option<Number> {
if let Some(v) = ctx.temporaries.get(name).cloned() {
return Some(v)
}
if let Some(k) = ctx.dimensions.get(name) {
return Some(Number::one_unit(k.to_owned()))
}
if let Some(v) = ctx.units.get(name).cloned() {
return Some(v)
}
for (unit, quantity) in &ctx.quantities {
if name == quantity {
return Some(Number {
value: Num::one(),
unit: unit.clone()
})
}
}
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())
}
}
}
if name.ends_with("s") {
let name = &name[0..name.len()-1];
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())
}
}
}
}
None
}
pub fn canonicalize(&self, name: &str) -> Option<String> {
fn inner(ctx: &Context, name: &str) -> Option<String> {
if let Some(v) = ctx.canonicalizations.get(name) {
return Some(v.clone())
}
if let Some(k) = ctx.dimensions.get(name) {
return Some((*k.0).clone())
}
if let Some(v) = ctx.definitions.get(name) {
if let Expr::Unit(ref name) = *v {
if let Some(r) = ctx.canonicalize(&*name) {
return Some(r)
} else {
return Some(name.clone())
}
} else {
return Some(name.to_owned())
}
}
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];
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))
}
}
}
}
None
}
pub fn describe_unit(&self, value: &Number) -> (bool, String) {
use std::io::Write;
let mut buf = vec![];
let mut recip = false;
let square = Number {
value: Num::one(),
unit: value.unit.clone()
}.root(2).ok();
let inverse = (&Number::one() / &Number {
value: Num::one(),
unit: value.unit.clone()
}).unwrap();
if let Some(name) = self.quantities.get(&value.unit) {
write!(buf, "{}", name).unwrap();
} else if let Some(name) = square.and_then(|square| self.quantities.get(&square.unit)) {
write!(buf, "{}^2", name).unwrap();
} else if let Some(name) = self.quantities.get(&inverse.unit) {
recip = true;
write!(buf, "{}", name).unwrap();
} else {
let mut frac = vec![];
let mut found = false;
for (dim, &pow) in &value.unit {
if pow < 0 {
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();
}
}
}
}
if frac.len() > 0 {
if !found {
recip = true;
} else {
write!(buf, " /").unwrap();
}
for (dim, pow) in frac {
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();
}
}
}
}
buf.remove(0);
}
(recip, String::from_utf8(buf).unwrap())
}
pub fn typo_dym<'a>(&'a self, what: &str) -> Option<&'a str> {
search::search(self, what, 1).into_iter().next()
}
pub fn unknown_unit_err(&self, name: &str) -> NotFoundError {
NotFoundError {
got: name.to_owned(),
suggestion: self.typo_dym(name).map(|x| x.to_owned()),
}
}
}