1use super::Show;
6use crate::loader::Context;
7use crate::output::{Digits, PropertyReply, SubstanceReply};
8use crate::types::{BaseUnit, Number, Numeric};
9use std::collections::BTreeMap;
10use std::iter::once;
11use std::ops::{Add, Div, Mul};
12use std::sync::Arc;
13
14macro_rules! try_div {
15 ($x:expr, $y:expr, $context:expr) => {
16 (&$x / &$y).ok_or_else(|| {
17 format!(
18 "Division by zero: <{}> / <{}>",
19 $x.show($context),
20 $y.show($context)
21 )
22 })?
23 };
24}
25
26#[derive(Debug, Clone)]
27pub struct Property {
28 pub input: Number,
29 pub input_name: String,
30 pub output: Number,
31 pub output_name: String,
32 pub doc: Option<String>,
33}
34
35#[derive(Debug, Clone)]
36pub struct Properties {
37 pub name: String,
38 pub properties: BTreeMap<String, Property>,
39}
40
41#[derive(Debug, Clone)]
42pub struct Substance {
43 pub amount: Number,
44 pub properties: Arc<Properties>,
45}
46
47pub enum SubstanceGetError {
48 Generic(String),
49 Conformance(Number, Number),
50}
51
52impl Substance {
53 pub fn rename(self, name: String) -> Substance {
54 Substance {
55 amount: self.amount,
56 properties: Arc::new(Properties {
57 name,
58 properties: self.properties.properties.clone(),
59 }),
60 }
61 }
62
63 pub fn get(&self, name: &str) -> Result<Number, SubstanceGetError> {
64 if self.amount.dimless() {
65 self.properties
66 .properties
67 .get(name)
68 .ok_or_else(|| {
69 SubstanceGetError::Generic(format!(
70 "No such property {} of {}",
71 name, self.properties.name
72 ))
73 })
74 .map(|prop| {
75 (&(&self.amount * &prop.output).unwrap() / &prop.input)
76 .expect("Non-zero property")
77 })
78 } else {
79 for prop in self.properties.properties.values() {
80 if name == prop.output_name {
81 let input = (&prop.input / &self.amount)
82 .ok_or_else(|| SubstanceGetError::Generic("Division by zero".to_owned()))?;
83 if input.dimless() {
84 let res = (&prop.output / &input).ok_or_else(|| {
85 SubstanceGetError::Generic("Division by zero".to_owned())
86 })?;
87 return Ok(res);
88 } else {
89 return Err(SubstanceGetError::Conformance(
90 self.amount.clone(),
91 prop.input.clone(),
92 ));
93 }
94 } else if name == prop.input_name {
95 let output = (&prop.output / &self.amount)
96 .ok_or_else(|| SubstanceGetError::Generic("Division by zero".to_owned()))?;
97 if output.dimless() {
98 let res = (&prop.input / &output).ok_or_else(|| {
99 SubstanceGetError::Generic("Division by zero".to_owned())
100 })?;
101 return Ok(res);
102 } else {
103 return Err(SubstanceGetError::Conformance(
104 self.amount.clone(),
105 prop.output.clone(),
106 ));
107 }
108 }
109 }
110 Err(SubstanceGetError::Generic(format!(
111 "No such property {} of {}",
112 name, self.properties.name
113 )))
114 }
115 }
116
117 pub fn get_in_unit(
119 &self,
120 unit: Number,
121 context: &Context,
122 bottom_name: BTreeMap<String, isize>,
123 bottom_const: Numeric,
124 base: u8,
125 digits: Digits,
126 ) -> Result<SubstanceReply, String> {
127 if self.amount.dimless() {
128 Ok(SubstanceReply {
129 name: self.properties.name.clone(),
130 doc: context.registry.docs.get(&self.properties.name).cloned(),
131 amount: self.amount.to_parts(context),
132 properties: self
133 .properties
134 .properties
135 .iter()
136 .map(|(k, v)| {
137 let (input, output) = if v.input.dimless() {
138 let res = (&v.output * &self.amount).unwrap();
139 (None, try_div!(res, v.input, context))
140 } else {
141 (Some(v.input.clone()), v.output.clone())
142 };
143 let (input, output) = if output.unit != unit.unit {
144 if let Some(input) = input {
145 if input.unit == unit.unit {
146 (Some(output), input)
147 } else {
148 return Ok(None);
149 }
150 } else {
151 return Ok(None);
152 }
153 } else {
154 (input, output)
155 };
156 let output_show = context
157 .show(
158 &try_div!(output, unit, context),
159 &unit,
160 bottom_name.clone(),
161 bottom_const.clone(),
162 base,
163 digits,
164 )
165 .value;
166 let output = try_div!(output, unit, context);
167 let input: Option<Number> = input;
168 Ok(Some(PropertyReply {
169 name: k.clone(),
170 value: if let Some(input) = input.as_ref() {
171 let input_pretty = input.prettify(context);
172 let mut output_pretty = output;
173 output_pretty.unit = bottom_name
174 .iter()
175 .map(|(k, v)| (BaseUnit::new(k), *v as i64))
176 .collect();
177 let mut res = try_div!(output_pretty, input_pretty, context)
178 .to_parts(context);
179 let value = (&unit / input)
180 .expect("Already known safe")
181 .to_parts(context);
182 res.quantity = value.quantity;
183 res
184 } else {
185 output_show
186 },
187 doc: v.doc.clone(),
188 }))
189 })
190 .filter_map(|x| x.map(|x| x.map(Ok)).unwrap_or_else(|e| Some(Err(e))))
191 .collect::<Result<Vec<PropertyReply>, String>>()?,
192 })
193 } else {
194 let func = |(_k, v): (&String, &Property)| {
195 let input = try_div!(v.input, self.amount, context);
196 let output = try_div!(v.output, self.amount, context);
197 let (name, input, output) = if input.dimless() {
198 if v.output.unit != unit.unit {
199 return Ok(None);
200 }
201 let div = try_div!(v.output, input, context);
202 (v.output_name.clone(), None, div)
203 } else if output.dimless() {
204 if v.input.unit != unit.unit {
205 return Ok(None);
206 }
207 let div = try_div!(v.input, output, context);
208 (v.input_name.clone(), None, div)
209 } else {
210 return Ok(None);
211 };
212 let output_show = context
213 .show(
214 &try_div!(output, unit, context),
215 &unit,
216 bottom_name.clone(),
217 bottom_const.clone(),
218 base,
219 digits,
220 )
221 .value;
222 let output = try_div!(output, unit, context);
223 let input: Option<Number> = input;
224 Ok(Some(PropertyReply {
225 name,
226 value: if let Some(input) = input.as_ref() {
227 let input_pretty = input.prettify(context);
228 let mut output_pretty = output;
229 output_pretty.unit = bottom_name
230 .iter()
231 .map(|(k, v)| (BaseUnit::new(k), *v as i64))
232 .collect();
233 let mut res =
234 try_div!(output_pretty, input_pretty, context).to_parts(context);
235 let value = (&unit / input)
236 .expect("Already known safe")
237 .to_parts(context);
238 res.quantity = value.quantity;
239 res
240 } else {
241 output_show
242 },
243 doc: v.doc.clone(),
244 }))
245 };
246 let amount = PropertyReply {
247 name: self
248 .amount
249 .to_parts(context)
250 .quantity
251 .unwrap_or_else(|| "amount".to_owned()),
252 value: self.amount.to_parts(context),
253 doc: None,
254 };
255 Ok(SubstanceReply {
256 name: self.properties.name.clone(),
257 doc: context.registry.docs.get(&self.properties.name).cloned(),
258 amount: self.amount.to_parts(context),
259 properties: once(Ok(Some(amount)))
260 .chain(self.properties.properties.iter().map(func))
261 .collect::<Result<Vec<Option<PropertyReply>>, String>>()?
262 .into_iter()
263 .flatten()
264 .collect(),
265 })
266 }
267 }
268
269 pub fn to_reply(&self, context: &Context) -> Result<SubstanceReply, String> {
270 if self.amount.dimless() {
271 Ok(SubstanceReply {
272 name: self.properties.name.clone(),
273 doc: context.registry.docs.get(&self.properties.name).cloned(),
274 amount: self.amount.to_parts(context),
275 properties: self
276 .properties
277 .properties
278 .iter()
279 .map(|(k, v)| {
280 let (input, output) = if v.input.dimless() {
281 let res = (&v.output * &self.amount).unwrap();
282 (None, try_div!(res, v.input, context))
283 } else {
284 (Some(v.input.clone()), v.output.clone())
285 };
286 Ok(PropertyReply {
287 name: k.clone(),
288 value: if let Some(input) = input.as_ref() {
289 let input_pretty = input.prettify(context);
290 let output_pretty = output.prettify(context);
291 let mut res = try_div!(output_pretty, input_pretty, context)
292 .to_parts(context);
293 let value = (&output / input)
294 .expect("Already known safe")
295 .to_parts(context);
296 res.quantity = value.quantity;
297 res
298 } else {
299 output.to_parts(context)
300 },
301 doc: v.doc.clone(),
302 })
303 })
304 .collect::<Result<Vec<PropertyReply>, String>>()?,
305 })
306 } else {
307 let func = |(_k, v): (&String, &Property)| {
308 let input = try_div!(v.input, self.amount, context);
309 let output = try_div!(v.output, self.amount, context);
310 let (name, input, output) = if input.dimless() {
311 let div = try_div!(v.output, input, context);
312 (v.output_name.clone(), None, div)
313 } else if output.dimless() {
314 let div = try_div!(v.input, output, context);
315 (v.input_name.clone(), None, div)
316 } else {
317 return Ok(None);
318 };
319 let input: Option<Number> = input;
320 Ok(Some(PropertyReply {
321 name,
322 value: if let Some(input) = input.as_ref() {
323 let input_pretty = input.prettify(context);
324 let output_pretty = output.prettify(context);
325 let mut res =
326 try_div!(output_pretty, input_pretty, context).to_parts(context);
327 let value = (&output / input)
328 .expect("Already known safe")
329 .to_parts(context);
330 res.quantity = value.quantity;
331 res
332 } else {
333 output.to_parts(context)
334 },
335 doc: v.doc.clone(),
336 }))
337 };
338 let amount = PropertyReply {
339 name: self
340 .amount
341 .to_parts(context)
342 .quantity
343 .unwrap_or_else(|| "amount".to_owned()),
344 value: self.amount.to_parts(context),
345 doc: None,
346 };
347 Ok(SubstanceReply {
348 name: self.properties.name.clone(),
349 doc: context.registry.docs.get(&self.properties.name).cloned(),
350 amount: self.amount.to_parts(context),
351 properties: once(Ok(Some(amount)))
352 .chain(self.properties.properties.iter().map(func))
353 .collect::<Result<Vec<Option<PropertyReply>>, String>>()?
354 .into_iter()
355 .flatten()
356 .collect(),
357 })
358 }
359 }
360}
361
362impl Show for Substance {
363 fn show(&self, context: &Context) -> String {
364 format!(
365 "{} {}",
366 self.amount.to_parts(context).format("n u p"),
367 self.properties.name
368 )
369 }
370}
371
372impl<'a, 'b> Mul<&'b Number> for &'a Substance {
373 type Output = Result<Substance, String>;
374
375 fn mul(self, other: &'b Number) -> Self::Output {
376 Ok(Substance {
377 amount: (&self.amount * other)
378 .ok_or_else(|| "Multiplication of numbers should not fail".to_owned())?,
379 properties: self.properties.clone(),
380 })
381 }
382}
383
384impl<'a, 'b> Div<&'b Number> for &'a Substance {
385 type Output = Result<Substance, String>;
386
387 fn div(self, other: &'b Number) -> Self::Output {
388 Ok(Substance {
389 amount: (&self.amount / other).ok_or_else(|| "Division by zero".to_owned())?,
390 properties: self.properties.clone(),
391 })
392 }
393}
394
395impl<'a, 'b> Add<&'b Substance> for &'a Substance {
396 type Output = Result<Substance, String>;
397
398 #[allow(clippy::suspicious_arithmetic_impl)]
399 fn add(self, other: &'b Substance) -> Self::Output {
400 let res = Substance {
401 amount: Number::one(),
402 properties: Arc::new(Properties {
403 name: format!(
404 "{} {} + {} {}",
405 self.amount.to_parts_simple().format("n u"),
406 self.properties.name,
407 other.amount.to_parts_simple().format("n u"),
408 other.properties.name,
409 ),
410 properties: self
411 .properties
412 .properties
413 .iter()
414 .filter_map(|(k, prop1)| {
415 let prop2 = match other.properties.properties.get(k) {
416 Some(v) => v,
417 None => return None,
418 };
419 let mol = Number::one_unit(BaseUnit::new("mol"));
420 if prop1.input_name != prop2.input_name
421 || prop1.output_name != prop2.output_name
422 || prop1.input.unit != prop2.input.unit
423 || prop1.output.unit != prop2.output.unit
424 || prop1.input != mol
425 || prop2.input != mol
426 {
427 return None;
428 }
429 Some((
430 k.clone(),
431 Property {
432 output: (&(&self.amount * &prop1.output).unwrap()
433 + &(&other.amount * &prop2.output).unwrap())
434 .expect("Add"),
435 input_name: prop1.input_name.clone(),
436 input: mol,
437 output_name: prop1.output_name.clone(),
438 doc: None,
439 },
440 ))
441 })
442 .collect(),
443 }),
444 };
445 if res.properties.properties.is_empty() {
446 Err("No shared properties".to_string())
447 } else {
448 Ok(res)
449 }
450 }
451}