1use super::Registry;
6use crate::ast::{DatePattern, Expr, Query};
7use crate::output::{ConversionReply, Digits, NotFoundError, NumberParts, QueryError, QueryReply};
8use crate::types::{BaseUnit, BigInt, Dimensionality, Number, Numeric};
9use crate::{commands, Value};
10use chrono::{DateTime, Local, TimeZone};
11use std::collections::BTreeMap;
12
13#[derive(Debug)]
15pub struct Context {
16 pub registry: Registry,
18 pub(crate) temporaries: BTreeMap<String, Number>,
20 pub now: DateTime<Local>,
26 pub use_humanize: bool,
29 pub save_previous_result: bool,
32 pub previous_result: Option<Number>,
34}
35
36impl Default for Context {
37 fn default() -> Self {
39 Context::new()
40 }
41}
42
43impl Context {
44 pub fn new() -> Context {
46 Context {
47 registry: Registry::default(),
48 temporaries: BTreeMap::new(),
49 now: Local.timestamp_opt(0, 0).unwrap(),
50 use_humanize: true,
51 save_previous_result: false,
52 previous_result: None,
53 }
54 }
55
56 pub fn set_time(&mut self, time: DateTime<Local>) {
57 self.now = time;
58 }
59
60 pub fn update_time(&mut self) {
61 self.now = Local::now();
62 }
63
64 pub fn load_dates(&mut self, mut dates: Vec<Vec<DatePattern>>) {
65 self.registry.datepatterns.append(&mut dates)
66 }
67
68 pub fn lookup(&self, name: &str) -> Option<Number> {
71 if name == "ans" || name == "ANS" || name == "_" {
72 return self.previous_result.clone();
73 }
74 if let Some(v) = self.temporaries.get(name).cloned() {
75 return Some(v);
76 }
77
78 self.registry.lookup(name)
79 }
80
81 pub fn canonicalize(&self, name: &str) -> Option<String> {
83 self.registry.canonicalize(name)
84 }
85
86 pub fn describe_unit(&self, value: &Number) -> (bool, String) {
90 use std::io::Write;
91
92 let mut buf = vec![];
93 let mut recip = false;
94 let square = Number {
95 value: Numeric::one(),
96 unit: value.unit.clone(),
97 }
98 .root(2)
99 .ok();
100 let inverse = (&Number::one()
101 / &Number {
102 value: Numeric::one(),
103 unit: value.unit.clone(),
104 })
105 .unwrap();
106 if let Some(name) = self.registry.quantities.get(&value.unit) {
107 write!(buf, "{}", name).unwrap();
108 } else if let Some(name) =
109 square.and_then(|square| self.registry.quantities.get(&square.unit))
110 {
111 write!(buf, "{}^2", name).unwrap();
112 } else if let Some(name) = self.registry.quantities.get(&inverse.unit) {
113 recip = true;
114 write!(buf, "{}", name).unwrap();
115 } else {
116 let helper = |dim: &BaseUnit, pow: i64, buf: &mut Vec<u8>| {
117 let unit = Dimensionality::new_dim(dim.clone(), pow);
118 if let Some(name) = self.registry.quantities.get(&unit) {
119 write!(buf, " {}", name).unwrap();
120 } else {
121 let unit = Dimensionality::base_unit(dim.clone());
122 if let Some(name) = self.registry.quantities.get(&unit) {
123 write!(buf, " {}", name).unwrap();
124 } else {
125 write!(buf, " '{}'", dim).unwrap();
126 }
127 if pow != 1 {
128 write!(buf, "^{}", pow).unwrap();
129 }
130 }
131 };
132
133 let mut frac = vec![];
134 let mut found = false;
135 for (dim, &pow) in value.unit.iter() {
136 if pow < 0 {
137 frac.push((dim, -pow));
138 } else {
139 found = true;
140 helper(dim, pow, &mut buf);
141 }
142 }
143 if !frac.is_empty() {
144 if !found {
145 recip = true;
146 } else {
147 write!(buf, " /").unwrap();
148 }
149 for (dim, pow) in frac {
150 let unit = Dimensionality::new_dim(dim.clone(), pow);
151 if let Some(name) = self.registry.quantities.get(&unit) {
152 write!(buf, " {}", name).unwrap();
153 } else {
154 helper(dim, pow, &mut buf);
155 }
156 }
157 }
158 buf.remove(0);
159 }
160
161 (recip, String::from_utf8(buf).unwrap())
162 }
163
164 pub fn typo_dym<'a>(&'a self, what: &str) -> Option<&'a str> {
165 commands::search_internal(self, what, 1).into_iter().next()
166 }
167
168 pub fn unknown_unit_err(&self, name: &str) -> NotFoundError {
169 NotFoundError {
170 got: name.to_owned(),
171 suggestion: self.typo_dym(name).map(|x| x.to_owned()),
172 }
173 }
174
175 pub fn humanize<Tz: chrono::TimeZone>(&self, date: chrono::DateTime<Tz>) -> Option<String> {
176 if self.use_humanize {
177 crate::parsing::datetime::humanize(self.now, date)
178 } else {
179 None
180 }
181 }
182
183 pub fn load(&mut self, defs: crate::ast::Defs) -> Result<(), String> {
186 let errors = crate::loader::load_defs(self, defs);
187
188 if errors.is_empty() {
189 Ok(())
190 } else {
191 let mut lines = vec![format!("Multiple errors encountered while loading:")];
192 for error in errors {
193 lines.push(format!(" {error}"));
194 }
195 Err(lines.join("\n"))
196 }
197 }
198
199 pub fn eval(&self, expr: &Expr) -> Result<Value, QueryError> {
202 crate::runtime::eval_expr(self, expr)
203 }
204
205 #[deprecated(since = "0.7.0", note = "renamed to eval_query()")]
206 pub fn eval_outer(&self, query: &Query) -> Result<QueryReply, QueryError> {
207 self.eval_query(query)
208 }
209
210 pub fn eval_query(&self, query: &Query) -> Result<QueryReply, QueryError> {
212 crate::runtime::eval_query(self, query)
213 }
214
215 pub fn show(
216 &self,
217 raw: &Number,
218 bottom: &Number,
219 bottom_name: BTreeMap<String, isize>,
220 bottom_const: Numeric,
221 base: u8,
222 digits: Digits,
223 ) -> ConversionReply {
224 let (exact, approx) = raw.numeric_value(base, digits);
225 let bottom_name = bottom_name
226 .into_iter()
227 .map(|(a, b)| (BaseUnit::new(&*a), b as i64))
228 .collect();
229 let (num, den) = bottom_const.to_rational();
230 ConversionReply {
231 value: NumberParts {
232 raw_value: Some(raw.clone()),
233 exact_value: exact,
234 approx_value: approx,
235 factor: if num != BigInt::one() {
236 Some(num.to_string())
237 } else {
238 None
239 },
240 divfactor: if den != BigInt::one() {
241 Some(den.to_string())
242 } else {
243 None
244 },
245 unit: Some(Number::unit_to_string(&bottom_name)),
246 raw_unit: Some(bottom_name),
247 ..bottom.to_parts(self)
248 },
249 }
250 }
251}