use super::Registry;
use crate::ast::{Conversion, DatePattern, Expr, Query};
use crate::output::{ConversionReply, Digits, NotFoundError, NumberParts, QueryError, QueryReply};
use crate::types::{BaseUnit, BigInt, DateTime, Dimensionality, Number, Numeric};
use crate::{commands, Value};
use std::collections::BTreeMap;
#[derive(Debug)]
pub struct Context {
pub registry: Registry,
pub(crate) temporaries: BTreeMap<String, Number>,
pub now: DateTime,
pub use_humanize: bool,
pub save_previous_result: bool,
pub previous_result: Option<Value>,
}
impl Default for Context {
fn default() -> Self {
Context::new()
}
}
impl Context {
pub fn new() -> Context {
Context {
registry: Registry::default(),
temporaries: BTreeMap::new(),
now: DateTime::default(),
use_humanize: true,
save_previous_result: false,
previous_result: None,
}
}
pub fn set_time(&mut self, time: DateTime) {
self.now = time;
}
pub fn load_dates(&mut self, mut dates: Vec<Vec<DatePattern>>) {
self.registry.datepatterns.append(&mut dates)
}
pub fn load_date_file(&mut self, file: &str) {
let dates = crate::parsing::datetime::parse_datefile(file);
self.load_dates(dates)
}
pub fn lookup(&self, name: &str) -> Option<Value> {
if name == "ans" || name == "ANS" || name == "_" {
return self.previous_result.clone();
}
if let Some(v) = self.temporaries.get(name).cloned() {
return Some(Value::Number(v));
}
self.registry.lookup(name)
}
pub fn canonicalize(&self, name: &str) -> Option<String> {
self.registry.canonicalize(name)
}
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: Numeric::one(),
unit: value.unit.clone(),
}
.root(2)
.ok();
let inverse = (&Number::one()
/ &Number {
value: Numeric::one(),
unit: value.unit.clone(),
})
.unwrap();
if let Some(name) = self.registry.quantities.get(&value.unit) {
write!(buf, "{}", name).unwrap();
} else if let Some(name) =
square.and_then(|square| self.registry.quantities.get(&square.unit))
{
write!(buf, "{}^2", name).unwrap();
} else if let Some(name) = self.registry.quantities.get(&inverse.unit) {
recip = true;
write!(buf, "{}", name).unwrap();
} else {
let helper = |dim: &BaseUnit, pow: i64, buf: &mut Vec<u8>| {
let unit = Dimensionality::new_dim(dim.clone(), pow);
if let Some(name) = self.registry.quantities.get(&unit) {
write!(buf, " {}", name).unwrap();
} else {
let unit = Dimensionality::base_unit(dim.clone());
if let Some(name) = self.registry.quantities.get(&unit) {
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.iter() {
if pow < 0 {
frac.push((dim, -pow));
} else {
found = true;
helper(dim, pow, &mut buf);
}
}
if !frac.is_empty() {
if !found {
recip = true;
} else {
write!(buf, " /").unwrap();
}
for (dim, pow) in frac {
let unit = Dimensionality::new_dim(dim.clone(), pow);
if let Some(name) = self.registry.quantities.get(&unit) {
write!(buf, " {}", name).unwrap();
} else {
helper(dim, pow, &mut buf);
}
}
}
buf.remove(0);
}
(recip, String::from_utf8(buf).unwrap())
}
pub fn typo_dym<'a>(&'a self, what: &str) -> Option<&'a str> {
commands::search_internal(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()),
}
}
pub fn humanize(&self, date: &DateTime) -> Option<String> {
if self.use_humanize {
Some(date.humanize(&self.now))
} else {
None
}
}
pub fn load(&mut self, defs: crate::ast::Defs) -> Result<(), String> {
let errors = crate::loader::load_defs(self, defs);
if errors.is_empty() {
Ok(())
} else {
let mut lines = vec![format!("Multiple errors encountered while loading:")];
for error in errors {
lines.push(format!(" {error}"));
}
Err(lines.join("\n"))
}
}
pub fn load_definitions(&mut self, content: &str) -> Result<(), String> {
let defs = crate::loader::gnu_units::parse_str(&content);
self.load(defs)
}
#[cfg(feature = "serde_json")]
pub fn load_currency(
&mut self,
live_data: Option<&str>,
currency_units: &str,
) -> Result<(), String> {
if let Some(live_data) = live_data {
let live_defs = serde_json::from_str(&live_data).map_err(|err| format!("{}", err))?;
self.load(live_defs)?;
};
let base_defs = crate::loader::gnu_units::parse_str(currency_units);
self.load(base_defs)?;
Ok(())
}
fn canonicalize_expr(&self, expr: Expr) -> Expr {
match expr {
Expr::Unit { name } => Expr::Unit {
name: self.canonicalize(&name).unwrap_or(name),
},
Expr::BinOp(expr) => Expr::BinOp(crate::ast::BinOpExpr {
op: expr.op,
left: Box::new(self.canonicalize_expr(*expr.left)),
right: Box::new(self.canonicalize_expr(*expr.right)),
}),
Expr::UnaryOp(expr) => Expr::UnaryOp(crate::ast::UnaryOpExpr {
op: expr.op,
expr: Box::new(self.canonicalize_expr(*expr.expr)),
}),
Expr::Mul { exprs } => Expr::Mul {
exprs: exprs
.into_iter()
.map(|expr| self.canonicalize_expr(expr))
.collect(),
},
Expr::Of { property, expr } => Expr::Of {
property,
expr: Box::new(self.canonicalize_expr(*expr)),
},
Expr::Call { func, args } => Expr::Call {
func,
args: args
.into_iter()
.map(|expr| self.canonicalize_expr(expr))
.collect(),
},
x => x,
}
}
pub fn canonicalize_query(&self, query: Query) -> Query {
match query {
Query::Expr(expr) => Query::Expr(self.canonicalize_expr(expr)),
Query::Convert(expr, conversion, base, digits) => {
let conversion = match conversion {
Conversion::Expr(expr) => Conversion::Expr(self.canonicalize_expr(expr)),
Conversion::List(items) => Conversion::List(
items
.into_iter()
.map(|unit| self.canonicalize(&unit).unwrap_or(unit))
.collect(),
),
x => x,
};
Query::Convert(self.canonicalize_expr(expr), conversion, base, digits)
}
Query::Factorize(expr) => Query::Factorize(self.canonicalize_expr(expr)),
Query::UnitsFor(expr) => Query::UnitsFor(self.canonicalize_expr(expr)),
x => x,
}
}
pub fn eval(&self, expr: &Expr) -> Result<Value, QueryError> {
crate::runtime::eval_expr(self, expr)
}
#[deprecated(since = "0.7.0", note = "renamed to eval_query()")]
pub fn eval_outer(&self, query: &Query) -> Result<QueryReply, QueryError> {
self.eval_query(query)
}
pub fn eval_query(&self, query: &Query) -> Result<QueryReply, QueryError> {
crate::runtime::eval_query(self, query)
}
pub fn show(
&self,
raw: &Number,
bottom: &Number,
bottom_name: BTreeMap<String, isize>,
bottom_const: Numeric,
base: u8,
digits: Digits,
) -> ConversionReply {
let (exact, approx) = raw.numeric_value(base, digits);
let bottom_name = bottom_name
.into_iter()
.map(|(a, b)| (BaseUnit::new(&*a), b as i64))
.collect();
let (num, den) = bottom_const.to_rational();
ConversionReply {
value: NumberParts {
raw_value: Some(raw.clone()),
exact_value: exact,
approx_value: approx,
factor: if num != BigInt::one() {
Some(num.to_string())
} else {
None
},
divfactor: if den != BigInt::one() {
Some(den.to_string())
} else {
None
},
unit: Some(Number::unit_to_string(&bottom_name)),
raw_unit: Some(bottom_name),
..bottom.to_parts(self)
},
}
}
}