use jiff::tz::{Offset, TimeZone};
use super::{Show, SubstanceGetError, Value};
use crate::ast::{BinOpExpr, BinOpType, Conversion, Expr, Function, Query, UnaryOpType};
use crate::commands;
use crate::loader::Context;
use crate::output::{
ConformanceError, ConversionReply, DateReply, DefReply, Digits, DurationReply, ExprReply,
Factorization, FactorizeReply, NumberParts, QueryError, QueryReply, UnitListReply,
UnitsForReply, UnitsInCategory,
};
use crate::parsing::{datetime, formula};
use crate::types::{BaseUnit, BigInt, Dimensionality, Number, Numeric};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::rc::Rc;
fn lookup_number(ctx: &Context, name: &str) -> Result<Number, QueryError> {
match ctx.lookup(name) {
Some(Value::Number(number)) => Ok(number),
Some(Value::MissingDeps(deps)) => Err(QueryError::MissingDeps(deps)),
Some(x) => Err(QueryError::generic(format!(
"Expected `{name}` to be a number, got: <{}>",
x.show(ctx)
))),
None => Err(QueryError::NotFound(ctx.unknown_unit_err(name))),
}
}
fn expect_number(ctx: &Context, value: Value) -> Result<Number, QueryError> {
match value {
Value::Number(value) => Ok(value),
_ => Err(QueryError::generic(format!(
"Expected number, got: <{}>",
value.show(ctx)
))),
}
}
fn lookup_unit(ctx: &Context, name: &str) -> Option<Value> {
ctx.lookup(name)
.or_else(|| {
ctx.registry
.substances
.get(name)
.cloned()
.map(Value::Substance)
})
.or_else(|| {
ctx.registry
.substance_symbols
.get(name)
.and_then(|full_name| {
ctx.registry
.substances
.get(full_name)
.cloned()
.map(Value::Substance)
})
})
}
pub(crate) fn eval_expr(ctx: &Context, expr: &Expr) -> Result<Value, QueryError> {
use std::ops::*;
match *expr {
Expr::Unit { ref name } if name == "now" => Ok(Value::DateTime(ctx.now.clone())),
Expr::Unit { ref name } if name == "__super_secret_plz_crash" => {
panic!("Thank you for playing Wing Commander")
}
Expr::Unit { ref name } => lookup_unit(ctx, name)
.or_else(|| {
formula::substance_from_formula(
name,
&ctx.registry.substance_symbols,
&ctx.registry.substances,
)
.map(Value::Substance)
})
.ok_or_else(|| QueryError::NotFound(ctx.unknown_unit_err(name))),
Expr::Quote { ref string } => Ok(Value::Number(Number::one_unit(BaseUnit::new(string)))),
Expr::Const { ref value } => Ok(Value::Number(Number::new(value.clone()))),
Expr::Date { ref tokens } => match datetime::try_decode(tokens, ctx) {
Ok(date) => Ok(Value::DateTime(date)),
Err(e) => Err(QueryError::generic(e)),
},
Expr::BinOp(BinOpExpr {
op: BinOpType::Equals,
ref left,
ref right,
}) => {
match **left {
Expr::Unit { .. } => (),
ref x => {
return Err(QueryError::generic(format!(
"= is currently only used for inline unit definitions: \
expected unit, got {}",
x
)))
}
};
eval_expr(ctx, right)
}
Expr::BinOp(ref binop) => {
let left = eval_expr(ctx, &binop.left)?;
let right = eval_expr(ctx, &binop.right)?;
let result = match binop.op {
BinOpType::Add => left.add(&right),
BinOpType::Sub => left.sub(&right),
BinOpType::Frac => left.div(&right),
BinOpType::Pow => left.pow(&right),
BinOpType::Equals => panic!("Should be unreachable"),
BinOpType::ShiftL => left.shl(&right),
BinOpType::ShiftR => left.shr(&right),
BinOpType::Mod => left.rem(&right),
BinOpType::And => left.and(&right),
BinOpType::Or => left.or(&right),
BinOpType::Xor => left.xor(&right),
};
result.map_err(|e| {
QueryError::generic(format!(
"{}: <{}> {} <{}>",
e,
left.show(ctx),
binop.op.symbol().trim(),
right.show(ctx)
))
})
}
Expr::UnaryOp(ref unaryop) => match unaryop.op {
UnaryOpType::Positive => eval_expr(ctx, &unaryop.expr),
UnaryOpType::Negative => eval_expr(ctx, &unaryop.expr).and_then(|v| {
(-&v).map_err(|e| QueryError::generic(format!("{}: - <{}>", e, v.show(ctx))))
}),
UnaryOpType::Degree(ref suffix) => {
let (_name, base, scale) = suffix.name_base_scale();
let result = eval_expr(ctx, &unaryop.expr)?;
let result = expect_number(ctx, result)?;
if result.unit != Dimensionality::new() {
return Err(QueryError::generic(format!(
"Expected dimensionless, got: <{}>",
result.show(ctx)
)));
}
let scale = lookup_number(ctx, scale)?;
let base = lookup_number(ctx, base)?;
let result: Number = (&result * &scale).unwrap();
let result = (&result + &base).unwrap();
Ok(Value::Number(result))
}
},
Expr::Mul { ref exprs } => exprs.iter().fold(Ok(Value::Number(Number::one())), |a, b| {
a.and_then(|a| {
let b = eval_expr(ctx, b)?;
(&a * &b).map_err(|e| {
QueryError::generic(format!("{}: <{}> * <{}>", e, a.show(ctx), b.show(ctx)))
})
})
}),
Expr::Of {
ref property,
ref expr,
} => {
let expr = eval_expr(ctx, expr)?;
let expr = match expr {
Value::Substance(sub) => sub,
Value::MissingDeps(deps) => return Ok(Value::MissingDeps(deps)),
x => {
return Err(QueryError::generic(format!(
"Not defined: {} of <{}>",
property,
x.show(ctx)
)))
}
};
expr.get(property).map(Value::Number).map_err(|e| match e {
SubstanceGetError::Generic(s) => QueryError::generic(s),
SubstanceGetError::Conformance(l, r) => {
QueryError::Conformance(Box::new(conformance_err(ctx, &l, &r)))
}
})
}
Expr::Call { ref func, ref args } => {
let args = args
.iter()
.map(|x| eval_expr(ctx, x))
.collect::<Result<Vec<_>, _>>()?;
macro_rules! func {
(fn $fname:ident($($name:ident : $ty:ident),*) $block:block) => {{
let mut iter = args.iter();
let mut count = 0;
$( count += 1; let _ = stringify!($name); )*
$(
let $name = match iter.next() {
Some(&Value::$ty(ref v)) => v,
Some(x) => return Err(QueryError::generic(
format!(
"Expected {}, got <{}>",
stringify!($ty), x.show(ctx)
)
)),
None => return Err(QueryError::generic(format!(
"Argument number mismatch for {}: \
Expected {}, got {}",
stringify!($fname), count, args.len()
)))
};
)*
if iter.next().is_some() {
return Err(QueryError::generic(format!(
"Argument number mismatch for {}: \
Expected {}, got {}",
stringify!($fname), count, args.len()
)));
}
let res: Result<Value, String> = {
$block
};
res.map_err(|e| {
QueryError::generic(format!(
"{}: {}({})",
e, stringify!($fname),
args.iter()
.map(|x| x.show(ctx))
.collect::<Vec<_>>()
.join(", ")
))
})
}
}}
match func {
Function::Sqrt => func!(
fn sqrt(num: Number) {
num.root(2).map(Value::Number)
}
),
Function::Exp => func!(
fn exp(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().exp()),
unit: num.unit.clone(),
}))
}
),
Function::Ln => func!(
fn ln(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().ln()),
unit: num.unit.clone(),
}))
}
),
Function::Log => func!(
fn log(num: Number, base: Number) {
if !base.unit.is_dimensionless() {
Err("Base must be dimensionless".to_string())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().log(base.value.to_f64())),
unit: num.unit.clone(),
}))
}
}
),
Function::Log2 => func!(
fn log2(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().log2()),
unit: num.unit.clone(),
}))
}
),
Function::Log10 => func!(
fn ln(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().log10()),
unit: num.unit.clone(),
}))
}
),
Function::Hypot => func!(
fn hypot(x: Number, y: Number) {
if x.unit != y.unit {
Err("Arguments to hypot must have matching dimensionality".to_string())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(x.value.to_f64().hypot(y.value.to_f64())),
unit: x.unit.clone(),
}))
}
}
),
Function::Sin => func!(
fn sin(num: Number) {
let radian = Dimensionality::base_unit(BaseUnit::new("radian"));
if !num.unit.is_dimensionless() && num.unit != radian {
Err("sin() accepts only angles or dimensionless inputs".to_owned())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().sin()),
unit: Dimensionality::new(),
}))
}
}
),
Function::Cos => func!(
fn cos(num: Number) {
let radian = Dimensionality::base_unit(BaseUnit::new("radian"));
if !num.unit.is_dimensionless() && num.unit != radian {
Err("cos() accepts only angles or dimensionless inputs".to_owned())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().cos()),
unit: Dimensionality::new(),
}))
}
}
),
Function::Tan => func!(
fn tan(num: Number) {
let radian = Dimensionality::base_unit(BaseUnit::new("radian"));
if !num.unit.is_dimensionless() && num.unit != radian {
Err("tan() accepts only angles or dimensionless inputs".to_owned())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().tan()),
unit: Dimensionality::new(),
}))
}
}
),
Function::Asin => func!(
fn asin(num: Number) {
if !num.unit.is_dimensionless() {
Err("asin() accepts only dimensionless inputs".to_owned())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().asin()),
unit: Dimensionality::base_unit(BaseUnit::new("radian")),
}))
}
}
),
Function::Acos => func!(
fn acos(num: Number) {
if !num.unit.is_dimensionless() {
Err("acos() accepts only dimensionless inputs".to_owned())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().acos()),
unit: Dimensionality::base_unit(BaseUnit::new("radian")),
}))
}
}
),
Function::Atan => func!(
fn atan(num: Number) {
if !num.unit.is_dimensionless() {
Err("atan() accepts only dimensionless inputs".to_owned())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().atan()),
unit: Dimensionality::base_unit(BaseUnit::new("radian")),
}))
}
}
),
Function::Atan2 => func!(
fn atan2(x: Number, y: Number) {
if x.unit != y.unit {
Err("Arguments to atan2 must have matching dimensionality".to_string())
} else {
Ok(Value::Number(Number {
value: Numeric::Float(x.value.to_f64().atan2(y.value.to_f64())),
unit: Dimensionality::base_unit(BaseUnit::new("radian")),
}))
}
}
),
Function::Sinh => func!(
fn sinh(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().sinh()),
unit: num.unit.clone(),
}))
}
),
Function::Cosh => func!(
fn cosh(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().cosh()),
unit: num.unit.clone(),
}))
}
),
Function::Tanh => func!(
fn tanh(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().tanh()),
unit: num.unit.clone(),
}))
}
),
Function::Asinh => func!(
fn asinh(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().asinh()),
unit: num.unit.clone(),
}))
}
),
Function::Acosh => func!(
fn acosh(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().acosh()),
unit: num.unit.clone(),
}))
}
),
Function::Atanh => func!(
fn atanh(num: Number) {
Ok(Value::Number(Number {
value: Numeric::Float(num.value.to_f64().atanh()),
unit: num.unit.clone(),
}))
}
),
Function::Fac => func!(
fn fac(num: Number) {
if !num.unit.is_dimensionless() {
Err("fac() accepts only dimensionless integers".to_owned())
} else {
let n = num.value.as_bigint();
let n = n.and_then(|n| n.as_int());
let n = n.and_then(|x| u32::try_from(x).ok());
if let Some(n) = n {
let value = BigInt::factorial(n).into();
Ok(Value::Number(Number {
value,
unit: num.unit.clone(),
}))
} else {
Err("fac() passed a value that is out of range".to_owned())
}
}
}
),
}
}
Expr::Error { ref message } => Err(QueryError::generic(message.clone())),
}
}
pub fn eval_unit_name(
ctx: &Context,
expr: &Expr,
) -> Result<(BTreeMap<String, isize>, Numeric), QueryError> {
match *expr {
Expr::Call { .. } => Err(QueryError::generic(
"Calls are not allowed in the right hand side of conversions".to_string(),
)),
Expr::Unit { ref name } | Expr::Quote { string: ref name } => {
let mut map = BTreeMap::new();
map.insert(ctx.canonicalize(&**name).unwrap_or_else(|| name.clone()), 1);
Ok((map, Numeric::one()))
}
Expr::Const { ref value } => Ok((BTreeMap::new(), value.clone())),
Expr::BinOp(ref binop) => match binop.op {
BinOpType::Equals => match *binop.left {
Expr::Unit { ref name } => {
let mut map = BTreeMap::new();
map.insert(name.clone(), 1);
Ok((map, Numeric::one()))
}
ref x => Err(QueryError::generic(format!(
"Expected identifier, got {:?}",
x
))),
},
BinOpType::Add | BinOpType::Sub => {
let (left_unit, left) = eval_unit_name(ctx, &binop.left)?;
let (right_unit, _right) = eval_unit_name(ctx, &binop.right)?;
if left_unit != right_unit {
return Err(QueryError::generic(
"Add of values with differing \
dimensions is not meaningful"
.to_string(),
));
}
Ok((left_unit, left))
}
BinOpType::Frac => {
let (left_unit, left) = eval_unit_name(ctx, &binop.left)?;
let (right_unit, right) = eval_unit_name(ctx, &binop.right)?;
let right_unit = right_unit
.into_iter()
.map(|(k, v)| (k, -v))
.collect::<BTreeMap<_, _>>();
Ok((
crate::algorithms::btree_merge(&left_unit, &right_unit, |a, b| {
if a + b != 0 {
Some(a + b)
} else {
None
}
}),
&left / &right,
))
}
BinOpType::Pow => {
let right = eval_expr(ctx, &binop.right)?;
let right = match right {
Value::Number(ref num) => num,
_ => return Err(QueryError::generic("Exponents must be numbers".to_string())),
};
if !right.dimless() {
return Err(QueryError::generic(
"Exponents must be dimensionless".to_string(),
));
}
let right = right.value.to_f64();
let (left_unit, left_value) = eval_unit_name(ctx, &binop.left)?;
Ok((
left_unit
.into_iter()
.filter_map(|(k, v)| {
let v = v * right as isize;
if v != 0 {
Some((k, v))
} else {
None
}
})
.collect::<BTreeMap<_, _>>(),
left_value.pow(right as i32),
))
}
BinOpType::ShiftL => todo!(),
BinOpType::ShiftR => todo!(),
BinOpType::Mod => {
let (left_unit, left) = eval_unit_name(ctx, &binop.left)?;
let (right_unit, _right) = eval_unit_name(ctx, &binop.right)?;
if left_unit != right_unit {
return Err(QueryError::generic(
"Modulo of values with differing dimensions is not meaningful".to_string(),
));
}
Ok((left_unit, left))
}
BinOpType::And | BinOpType::Or | BinOpType::Xor => {
let (left_unit, left) = eval_unit_name(ctx, &binop.left)?;
let (right_unit, _right) = eval_unit_name(ctx, &binop.right)?;
if !left_unit.is_empty() || !right_unit.is_empty() {
return Err(QueryError::generic(format!(
"Arguments to {:?} must be dimensionless",
binop.op
)));
}
Ok((left_unit, left))
}
},
Expr::Mul { ref exprs } => {
exprs[1..]
.iter()
.fold(eval_unit_name(ctx, &exprs[0]), |acc, b| {
let (acc, av) = acc?;
let (b, bv) = eval_unit_name(ctx, b)?;
Ok((
crate::algorithms::btree_merge(&acc, &b, |a, b| {
if a + b != 0 {
Some(a + b)
} else {
None
}
}),
&av * &bv,
))
})
}
Expr::Of {
ref property,
ref expr,
} => {
let res = eval_expr(ctx, expr)?;
let res = match res {
Value::Substance(sub) => sub,
_ => {
return Err(QueryError::generic(
"Property access on non-substance".to_string(),
))
}
};
let property = if let Some(prop) = res.properties.properties.get(property) {
if prop.input == Number::one() {
&prop.input_name
} else {
property
}
} else {
property
};
let mut map = BTreeMap::new();
map.insert(
ctx.canonicalize(property)
.unwrap_or_else(|| property.clone()),
1,
);
Ok((map, Numeric::one()))
}
Expr::UnaryOp(ref unaryop) => match unaryop.op {
UnaryOpType::Positive => eval_unit_name(ctx, &unaryop.expr),
UnaryOpType::Negative => eval_unit_name(ctx, &unaryop.expr).map(|(u, v)| (u, -&v)),
UnaryOpType::Degree(_) => 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 message } => Err(QueryError::generic(message.clone())),
}
}
fn conformance_err(ctx: &Context, 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 = Numeric::one();
let mut bottomu = bottom.clone();
bottomu.value = Numeric::one();
let mut suggestions = vec![];
let diff = (&topu * &bottomu).unwrap();
if diff.dimless() {
suggestions.push("Reciprocal conversion, invert one side".to_string());
} else {
let diff = (&topu / &bottomu).unwrap();
let (recip, desc) = ctx.describe_unit(&diff.invert());
let word = multiply_or_divide(recip);
suggestions.push(format!("{word} left side by {}", desc.trim(), word = word));
let (recip, desc) = ctx.describe_unit(&diff);
let word = multiply_or_divide(recip);
suggestions.push(format!("{word} right side by {}", desc.trim(), word = word));
}
ConformanceError {
left: top.to_parts(ctx),
right: bottom.to_parts(ctx),
suggestions,
}
}
fn to_list(ctx: &Context, top: &Number, list: &[&str]) -> Result<Vec<NumberParts>, QueryError> {
let units = list
.iter()
.map(|name| lookup_number(ctx, name))
.collect::<Result<Vec<Number>, _>>()?;
{
let first = units
.first()
.ok_or_else(|| "Expected non-empty unit list".to_string())?;
units
.iter()
.skip(1)
.map(|x| {
if first.unit != x.unit {
Err(format!(
"Units in unit list must conform: <{}> ; <{}>",
first.show(ctx),
x.show(ctx)
))
} else {
Ok(())
}
})
.collect::<Result<Vec<()>, _>>()?;
if top.unit != first.unit {
return Err(QueryError::Conformance(Box::new(conformance_err(
ctx, top, first,
))));
}
}
let mut value = top.value.clone();
let mut out = vec![];
let len = units.len();
for (i, unit) in units.into_iter().enumerate() {
if i == len - 1 {
out.push(&value / &unit.value);
} else {
let (div, rem) = value.div_rem(&unit.value);
out.push(div);
value = rem;
}
}
Ok(list
.iter()
.zip(out.into_iter())
.map(|(name, value)| {
let raw_number = Number {
value,
unit: Number::one_unit(BaseUnit::new(name)).unit,
};
let pretty = raw_number.to_parts(ctx);
let unit: String = pretty
.unit
.or(pretty.dimensions)
.map(|x| ctx.canonicalize(&*x).unwrap_or(x))
.expect("to_parts returned no dimensions");
let raw = Dimensionality::base_unit(BaseUnit::new(&unit));
NumberParts {
raw_value: Some(raw_number),
unit: Some(unit),
raw_unit: Some(raw),
exact_value: Some(
pretty
.approx_value
.or(pretty.exact_value)
.expect("to_parts returned neither exact nor approx value"),
),
..Default::default()
}
})
.collect())
}
fn can_show_definition(ctx: &Context, name: &str) -> bool {
if ctx.registry.definitions.contains_key(name) {
return true;
}
if ctx.registry.base_units.contains(name) {
return true;
}
if let Some(name) = ctx.canonicalize(name) {
if ctx.registry.definitions.contains_key(&name) {
return true;
}
if ctx.registry.base_units.contains(&name[..]) {
return true;
}
}
false
}
fn expand_aliases(ctx: &Context, name: &str) -> (String, String) {
let mut name = name.to_owned();
let mut canon = ctx.canonicalize(&name).unwrap_or_else(|| name.clone());
while let Some(&Expr::Unit { name: ref unit }) = {
ctx.registry
.definitions
.get(&name)
.or_else(|| ctx.registry.definitions.get(&*canon))
} {
if ctx.registry.base_units.contains(&*name) {
break;
}
let unit_canon = ctx.canonicalize(unit).unwrap_or_else(|| unit.clone());
if ctx.registry.base_units.contains(&**unit) {
name = unit.clone();
canon = unit_canon;
break;
}
if ctx.registry.definitions.get(unit).is_none() {
if ctx.registry.definitions.get(&unit_canon).is_none() {
if !ctx.registry.base_units.contains(&**unit) {
break;
} else {
assert!(name != *unit || canon != unit_canon);
name = unit.clone();
canon = unit_canon;
break;
}
} else {
assert!(name != unit_canon || canon != unit_canon);
name = unit_canon.clone();
canon = unit_canon;
}
} else {
assert!(name != *unit || canon != unit_canon);
name = unit.clone();
canon = unit_canon.clone();
}
}
(name, canon)
}
fn find_quantity(ctx: &Context, name: &str) -> Option<Dimensionality> {
for (dims, quantity_name) in &ctx.registry.quantities {
if quantity_name == name {
return Some(dims.clone());
}
}
None
}
pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, QueryError> {
match *expr {
Query::Expr(Expr::Unit { ref name }) if can_show_definition(ctx, name) => {
let (name, canon_name) = expand_aliases(ctx, name);
let (def, def_expr, value) =
if let Some(base_unit) = ctx.registry.base_units.get(&*name) {
let parts = Number::one_unit(base_unit.clone()).to_parts(ctx);
let def = if let Some(ref q) = parts.quantity {
format!("base unit of {}", q)
} else {
"base unit".to_string()
};
(Some(def), None, None)
} else if let Some(dims) = find_quantity(ctx, &name) {
let def = ctx
.registry
.definitions
.get(&name)
.expect("quantities should always have definitions");
let description = format!("physical quantity for {def} ({dims})");
(Some(description), None, None)
} else {
let def = ctx.registry.definitions.get(&name);
(
def.as_ref().map(|x| x.to_string()),
def,
ctx.lookup(&name)
.and_then(|v| v.to_number())
.map(|x| x.to_parts(ctx)),
)
};
Ok(QueryReply::Def(Box::new(DefReply {
def_expr: def_expr.as_ref().map(|x| ExprReply::from(*x)),
doc: ctx.registry.docs.get(&name).cloned(),
canon_name,
def,
value,
})))
}
Query::Convert(ref top, Conversion::None, Some(base), digits) => {
let top = eval_expr(ctx, top)?;
let top = match top {
Value::Number(top) => top,
Value::MissingDeps(d) => return Err(QueryError::MissingDeps(d)),
_ => {
return Err(QueryError::generic(format!(
"<{}> in base {} is not defined",
top.show(ctx),
base
)))
}
};
let (exact, approx) = top.numeric_value(base, digits);
let parts = NumberParts {
raw_value: Some(top.clone()),
exact_value: exact,
approx_value: approx,
..top.to_parts(ctx)
};
Ok(QueryReply::Conversion(Box::new(ConversionReply {
value: parts,
})))
}
Query::Convert(
ref top,
Conversion::None,
base,
digits @ Digits::Digits(_)
| digits @ Digits::FullInt
| digits @ Digits::Fraction
| digits @ Digits::Scientific
| digits @ Digits::Engineering,
) => {
let top = eval_expr(ctx, top)?;
let top = match top {
Value::Number(top) => top,
Value::MissingDeps(d) => return Err(QueryError::MissingDeps(d)),
_ => {
return Err(QueryError::generic(format!(
"<{}> to {} is not defined",
top.show(ctx),
match digits {
Digits::Default => unreachable!(),
Digits::FullInt => "digits".to_owned(),
Digits::Digits(n) => format!("{} digits", n),
Digits::Fraction => "fraction".to_owned(),
Digits::Scientific => "scientific".to_owned(),
Digits::Engineering => "engineering".to_owned(),
}
)))
}
};
let parts = top.to_parts_digits(ctx, base.unwrap_or(10), digits);
Ok(QueryReply::Conversion(Box::new(ConversionReply {
value: parts,
})))
}
Query::Convert(ref top, Conversion::Expr(ref bottom), base, digits) => match (
eval_expr(ctx, top)?,
eval_expr(ctx, bottom)?,
eval_unit_name(ctx, bottom)?,
) {
(Value::Number(top), Value::Number(bottom), (bottom_name, bottom_const)) => {
if top.unit == bottom.unit {
let raw = match &top / &bottom {
Some(raw) => raw,
None => {
return Err(QueryError::generic(format!(
"Division by zero: {} / {}",
top.show(ctx),
bottom.show(ctx)
)))
}
};
Ok(QueryReply::Conversion(Box::new(ctx.show(
&raw,
&bottom,
bottom_name,
bottom_const,
base.unwrap_or(10),
digits,
))))
} else {
Err(QueryError::Conformance(Box::new(conformance_err(
ctx, &top, &bottom,
))))
}
}
(Value::Substance(sub), Value::Number(bottom), (bottom_name, bottom_const)) => sub
.get_in_unit(
bottom,
ctx,
bottom_name,
bottom_const,
base.unwrap_or(10),
digits,
)
.map_err(QueryError::generic)
.map(QueryReply::Substance),
(Value::Number(top), Value::Substance(mut sub), (bottom_name, bottom_const)) => {
let unit = sub.amount.clone();
sub.amount = top;
sub.get_in_unit(
unit,
ctx,
bottom_name,
bottom_const,
base.unwrap_or(10),
digits,
)
.map_err(QueryError::generic)
.map(QueryReply::Substance)
}
(Value::MissingDeps(d), _, _) | (_, Value::MissingDeps(d), _) => {
Err(QueryError::MissingDeps(d))
}
(x, y, _) => Err(QueryError::generic(format!(
"Operation is not defined: <{}> -> <{}>",
x.show(ctx),
y.show(ctx)
))),
},
Query::Convert(ref top, Conversion::List(ref list), None, Digits::Default) => {
let top = eval_expr(ctx, top)?;
let top = match top {
Value::Number(num) => num,
Value::MissingDeps(d) => return Err(QueryError::MissingDeps(d)),
_ => {
return Err(QueryError::generic(format!(
"Cannot convert <{}> to {:?}",
top.show(ctx),
list
)))
}
};
let list = to_list(
ctx,
&top,
&list.iter().map(|x| &**x).collect::<Vec<_>>()[..],
)?;
Ok(QueryReply::UnitList(UnitListReply {
rest: NumberParts {
quantity: ctx.registry.quantities.get(&top.unit).cloned(),
..Default::default()
},
list,
}))
}
Query::Convert(ref top, Conversion::Offset(off), None, Digits::Default) => {
let top = eval_expr(ctx, top)?;
let top = match top {
Value::DateTime(date) => date,
_ => {
return Err(QueryError::generic(format!(
"Cannot convert <{}> to timezone offset {:+}",
top.show(ctx),
off
)))
}
};
let offset = TimeZone::fixed(Offset::from_seconds(off as i32).unwrap());
let top = top.convert_to_timezone(offset);
Ok(QueryReply::Date(DateReply::new(ctx, top)))
}
Query::Convert(ref top, Conversion::Timezone(ref tz), None, Digits::Default) => {
let top = eval_expr(ctx, top)?;
let top = match top {
Value::DateTime(date) => date,
_ => {
return Err(QueryError::generic(format!(
"Cannot convert <{}> to timezone {:?}",
top.show(ctx),
tz
)))
}
};
let top = top.convert_to_timezone(tz.clone());
Ok(QueryReply::Date(DateReply::new(ctx, top)))
}
Query::Convert(ref top, Conversion::Degree(ref deg), None, digits) => {
let (name, base, scale) = deg.name_base_scale();
let top = eval_expr(ctx, top)?;
let top = match top {
Value::Number(ref num) => num,
_ => {
return Err(QueryError::generic(format!(
"Cannot convert <{}> to °{}",
top.show(ctx),
name
)))
}
};
let scale = lookup_number(ctx, scale)?;
let base = lookup_number(ctx, base)?;
if top.unit != scale.unit {
Err(QueryError::Conformance(Box::new(conformance_err(
ctx, top, &scale,
))))
} else {
let res = (top - &base).unwrap();
let res = (&res / &scale).unwrap();
let mut name = BTreeMap::new();
name.insert(deg.to_string(), 1);
Ok(QueryReply::Conversion(Box::new(ctx.show(
&res,
&scale,
name,
Numeric::one(),
10,
digits,
))))
}
}
Query::Convert(ref _expr, ref which, Some(base), _digits) => Err(QueryError::generic(
format!("Conversion to {} is not defined in base {}", which, base),
)),
Query::Convert(ref _expr, ref which, _base, Digits::Digits(digits)) => {
Err(QueryError::generic(format!(
"Conversion to {} is not defined to {} digits",
which, digits
)))
}
Query::Convert(
ref _expr,
ref which,
_base,
Digits::FullInt | Digits::Fraction | Digits::Scientific | Digits::Engineering,
) => Err(QueryError::generic(format!(
"Conversion to digits of {} is not defined",
which
))),
Query::Factorize(ref expr) => {
let mut val = None;
if let Expr::Unit { ref name } = *expr {
for (u, k) in &ctx.registry.quantities {
if name == k {
val = Some(Number {
value: Numeric::one(),
unit: u.clone(),
});
break;
}
}
}
let val = match val {
None => {
let val = eval_expr(ctx, expr)?;
match val {
Value::Number(val) => val,
_ => {
return Err(QueryError::generic(format!(
"Cannot find derivatives of <{}>",
val.show(ctx)
)))
}
}
}
Some(val) => val,
};
let quantities = ctx
.registry
.quantities
.iter()
.map(|(a, b)| (a.clone(), Rc::new(b.clone())))
.collect::<BTreeMap<_, _>>();
let results = commands::factorize(&val, &quantities);
let mut results = results.into_sorted_vec();
results.dedup();
let results = results
.into_iter()
.map(|commands::Factors(_score, names)| {
let mut next = BTreeMap::<Rc<String>, usize>::new();
for name in names.into_iter() {
*next.entry(name).or_insert(0) += 1;
}
Factorization { units: next }
})
.collect::<Vec<_>>();
Ok(QueryReply::Factorize(FactorizeReply {
factorizations: results,
}))
}
Query::UnitsFor(ref expr) => {
let mut val = None;
if let Expr::Unit { ref name } = *expr {
for (u, k) in &ctx.registry.quantities {
if name == k {
val = Some(Number {
value: Numeric::one(),
unit: u.clone(),
});
break;
}
}
}
let val = match val {
None => {
let val = eval_expr(ctx, expr)?;
match val {
Value::Number(val) => val,
_ => {
return Err(QueryError::generic(format!(
"Cannot find units for <{}>",
val.show(ctx)
)))
}
}
}
Some(val) => val,
};
let dim_name;
let mut out = vec![];
for (name, unit) in ctx.registry.units.iter() {
if let Some(&Expr::Unit { .. }) = ctx.registry.definitions.get(name) {
continue;
}
let category = ctx.registry.categories.get(name);
if val.unit == unit.unit {
out.push((category, name));
}
}
if let Some((dim, _power)) = val.unit.as_single() {
dim_name = ctx
.canonicalize(dim.as_str())
.unwrap_or_else(|| dim.to_string());
let category = ctx.registry.categories.get(&dim_name);
out.push((category, &dim_name));
}
out.sort_by(|&(ref c1, ref n1), &(ref c2, ref n2)| {
use std::cmp::Ordering;
match (c1, c2) {
(&None, &None) => Ordering::Equal,
(&Some(_), &None) => Ordering::Less,
(&None, &Some(_)) => Ordering::Greater,
(&Some(ref a), &Some(ref b)) => (a, n1).cmp(&(b, n2)),
}
});
let mut categories = vec![];
let mut cur = vec![];
let mut cur_cat = None;
for (category, name) in out {
if category != cur_cat {
if !cur.is_empty() {
let cat_name = cur_cat.and_then(|x| ctx.registry.category_names.get(x));
categories.push(UnitsInCategory {
category: cat_name.map(ToOwned::to_owned),
units: cur.drain(..).collect(),
});
}
cur_cat = category;
}
cur.push(name.clone());
}
if !cur.is_empty() {
let cat_name = cur_cat.and_then(|x| ctx.registry.category_names.get(x));
categories.push(UnitsInCategory {
category: cat_name.map(ToOwned::to_owned),
units: cur,
});
}
let parts = val.to_parts(ctx);
Ok(QueryReply::UnitsFor(UnitsForReply {
units: categories,
of: parts,
}))
}
Query::Search(ref string) => Ok(QueryReply::Search(commands::search(ctx, &**string, 5))),
Query::Expr(ref expr)
| Query::Convert(ref expr, Conversion::None, None, Digits::Default) => {
let val = eval_expr(ctx, expr)?;
match val {
Value::Number(ref n) if n.unit == Number::one_unit(BaseUnit::new("s")).unit => {
let units = &["year", "week", "day", "hour", "minute", "second"];
let list = to_list(ctx, n, units)?;
let mut list = list.into_iter();
Ok(QueryReply::Duration(Box::new(DurationReply {
raw: n.to_parts(ctx),
years: list.next().expect("Unexpected end of iterator"),
months: NumberParts {
exact_value: Some("0".to_owned()),
unit: Some("month".to_owned()),
raw_unit: Some(Dimensionality::base_unit(BaseUnit::new("month"))),
..Default::default()
},
weeks: list.next().expect("Unexpected end of iterator"),
days: list.next().expect("Unexpected end of iterator"),
hours: list.next().expect("Unexpected end of iterator"),
minutes: list.next().expect("Unexpected end of iterator"),
seconds: list.next().expect("Unexpected end of iterator"),
})))
}
Value::Number(n) => Ok(QueryReply::Number(n.to_parts(ctx))),
Value::DateTime(d) => Ok(QueryReply::Date(DateReply::new(ctx, d))),
Value::Substance(s) => Ok(QueryReply::Substance(
s.to_reply(ctx).map_err(QueryError::generic)?,
)),
Value::MissingDeps(d) => Err(QueryError::MissingDeps(d)),
}
}
Query::Error(ref e) => Err(QueryError::generic(e.clone())),
}
}