1use std::{error::Error, fmt::{self, Display, Formatter}, str::FromStr};
2use super::convert::Convert;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
6#[non_exhaustive]
7pub struct CompoundUnit {
8 units: Vec<Unit>,
10}
11
12impl Display for CompoundUnit {
13 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
14 let Some((first, rest)) = self.units.split_first() else {
15 return Ok(());
16 };
17
18 write!(f, "{}", first)?;
20
21 for unit in rest {
22 if unit.power > 0 {
23 write!(f, "*{}", unit.base)?;
24 } else {
25 write!(f, "/{}", unit.base)?;
26 }
27 if unit.power.abs() > 1 {
28 write!(f, "^{}", unit.power.abs())?;
29 }
30 }
31
32 Ok(())
33 }
34}
35
36impl FromStr for CompoundUnit {
37 type Err = InvalidUnit;
38
39 fn from_str(value: &str) -> Result<Self, Self::Err> {
40 let mut units = Vec::new();
41
42 fn look_back(slice: &str) -> Option<(usize, char)> {
45 let next_star = slice.rfind('*');
46 let next_div = slice.rfind('/');
47 match (next_star, next_div) {
48 (Some(s), Some(d)) => Some((s.max(d), if s > d { '*' } else { '/' })),
50 (Some(s), None) => Some((s, '*')),
51 (None, Some(d)) => Some((d, '/')),
52 _ => None,
53 }
54 }
55
56 let mut start = value.len();
64 while let Some((next, c)) = look_back(&value[..start]) {
65 let mut unit: Unit = value[next + 1..start].parse()?;
66 unit.power *= if c == '*' { 1 } else { -1 };
67 units.push(unit);
68
69 start = next;
70 }
71
72 units.push(value[..start].parse()?);
74
75 units.reverse();
77
78 Ok(Self { units })
79 }
80}
81
82impl TryFrom<&str> for CompoundUnit {
83 type Error = InvalidUnit;
84
85 fn try_from(value: &str) -> Result<Self, Self::Error> {
86 value.parse()
87 }
88}
89
90impl CompoundUnit {
91 pub fn conversion_factor(&self, target: &CompoundUnit) -> Result<f64, ConversionError> {
94 let mut factor = 1.0;
95 for unit in &self.units {
96 let target_unit_factor = target.units.iter()
101 .flat_map(|u| unit.conversion_factor(*u))
102 .next()
103 .ok_or(ConversionError { unit: *unit, target: target.units[0] })?;
104 factor *= target_unit_factor;
105 }
106 Ok(factor)
107 }
108}
109
110macro_rules! unit_impl {
112 (
113 $doc:literal,
114 $enum_name:ident, $base_variant:ident: $base_abbr:literal $(=> $to_base_factor:literal $base_quantity:ident^$base_power:literal)?,
115 $(
116 $($variant_doc:literal,)? $variant:ident: $main_abbr:literal $(, $alt_abbr:literal)* => $factor:literal
117 ),*
118 $(,)?
119 ) => {
120 #[doc = $doc]
121 #[doc = concat!("[`", stringify!($enum_name), "`] is [`", stringify!($enum_name), "::", stringify!($base_variant), "`].")]
128 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
131 #[non_exhaustive]
132 pub enum $enum_name {
133 $(
134 $(
135 #[doc = $variant_doc]
136 )?
138 #[doc = concat!("- Abbreviation: `", $main_abbr, "`", $(", `", $alt_abbr, "`")*)]
139 #[doc = concat!("- `1 ", $main_abbr, " = ", $factor, " ", $base_abbr, "`")]
141 $variant,
142 )*
143 }
144
145 impl FromStr for $enum_name {
146 type Err = InvalidUnit;
147
148 fn from_str(value: &str) -> Result<Self, Self::Err> {
149 match value {
150 $(
151 $main_abbr $(| $alt_abbr)* => Ok($enum_name::$variant),
152 )*
153 _ => Err(InvalidUnit { unit: value.to_owned() }),
154 }
155 }
156 }
157
158 impl Display for $enum_name {
159 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
160 match self {
161 $(
162 $enum_name::$variant => write!(f, $main_abbr),
163 )*
164 }
165 }
166 }
167
168 impl Convert for $enum_name {
169 const BASE: Self = $enum_name::$base_variant;
170
171 fn conversion_factor(&self) -> f64 {
172 match self {
173 $(
174 $enum_name::$variant => $factor,
175 )*
176 }
177 }
178
179 $(
180 fn conversion_factor_to(&self, target: impl Into<Unit>) -> Option<f64> {
181 let target = target.into();
182 if matches!(target.base, Base::$base_quantity(_)) && target.power == $base_power {
183 Some(
184 self.conversion_factor() * $to_base_factor
186
187 * Unit::with_power($base_quantity::BASE, $base_power).conversion_factor(target).unwrap()
189 )
190 } else {
191 None
192 }
193 }
194 )?
195 }
196
197 impl From<$enum_name> for CompoundUnit {
198 fn from(u: $enum_name) -> Self {
199 Self { units: vec![Unit::from(u)] }
200 }
201 }
202
203 impl From<$enum_name> for Unit {
204 fn from(u: $enum_name) -> Self {
205 Self::new(Base::$enum_name(u))
206 }
207 }
208
209 impl From<$enum_name> for Base {
210 fn from(u: $enum_name) -> Self {
211 Self::$enum_name(u)
212 }
213 }
214
215 impl TryFrom<&str> for $enum_name {
216 type Error = InvalidUnit;
217
218 fn try_from(value: &str) -> Result<Self, Self::Error> {
219 value.parse()
220 }
221 }
222
223 impl $enum_name {
224 pub fn pow(&self, power: i8) -> Unit {
226 Unit::with_power(Base::$enum_name(*self), power)
227 }
228
229 pub fn squared(&self) -> Unit {
231 self.pow(2)
232 }
233
234 pub fn cubed(&self) -> Unit {
236 self.pow(3)
237 }
238 }
239 }
240}
241
242#[derive(Clone, Copy, Debug, PartialEq, Eq)]
244#[non_exhaustive]
245pub struct Unit {
246 base: Base,
248
249 power: i8,
251}
252
253impl Display for Unit {
254 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
255 write!(f, "{}", self.base)?;
256 if self.power != 1 {
257 write!(f, "^{}", self.power)?;
258 }
259 Ok(())
260 }
261}
262
263impl FromStr for Unit {
269 type Err = InvalidUnit;
270
271 fn from_str(value: &str) -> Result<Self, Self::Err> {
272 let mut iter = value.split('^');
273 let (a, b) = (iter.next(), iter.next());
274 if iter.next().is_some() {
275 return Err(InvalidUnit { unit: value.to_owned() });
276 }
277
278 match (a, b) {
279 (Some(a), Some(b)) => {
280 let quantity = Base::try_from(a)?;
281 let power = b.parse().map_err(|_| InvalidUnit { unit: value.to_owned() })?;
282 Ok(Unit::with_power(quantity, power))
283 },
284 (Some(a), None) => Ok(Base::try_from(a)?.into()),
285 _ => Err(InvalidUnit { unit: value.to_owned() }),
286 }
287 }
288}
289
290impl TryFrom<&str> for Unit {
296 type Error = InvalidUnit;
297
298 fn try_from(value: &str) -> Result<Self, Self::Error> {
299 value.parse()
300 }
301}
302
303impl From<Unit> for CompoundUnit {
304 fn from(u: Unit) -> Self {
305 Self { units: vec![u] }
306 }
307}
308
309impl Unit {
310 pub fn new(quantity: impl Into<Base>) -> Self {
312 Self { base: quantity.into(), power: 1 }
313 }
314
315 pub fn with_power(quantity: impl Into<Base>, power: i8) -> Self {
317 Self { base: quantity.into(), power }
318 }
319
320 pub fn conversion_factor(&self, target: Unit) -> Result<f64, ConversionError> {
323 if self.power != target.power {
324 return self.base.conversion_factor_to(target)
325 .or_else(|| target.base.conversion_factor_to(*self).map(|f| 1.0 / f))
326 .ok_or(ConversionError { unit: *self, target });
327 }
328
329 let power = self.power as i32;
330 match (self.base, target.base) {
331 (Base::Length(l1), Base::Length(l2)) => {
332 Ok(l1.conversion_factor().powi(power)
333 / l2.conversion_factor().powi(power))
334 },
335 (Base::Mass(m1), Base::Mass(m2)) => {
336 Ok(m1.conversion_factor().powi(power)
337 / m2.conversion_factor().powi(power))
338 },
339 (Base::Area(a1), Base::Area(a2)) => {
340 Ok(a1.conversion_factor().powi(power)
341 / a2.conversion_factor().powi(power))
342 },
343 (Base::Volume(v1), Base::Volume(v2)) => {
344 Ok(v1.conversion_factor().powi(power)
345 / v2.conversion_factor().powi(power))
346 },
347 (Base::Time(t1), Base::Time(t2)) => {
348 Ok(t1.conversion_factor().powi(power)
349 / t2.conversion_factor().powi(power))
350 },
351 _ => Err(ConversionError { unit: *self, target }),
352 }
353 }
354}
355
356#[derive(Debug)]
358pub struct ConversionError {
359 unit: Unit,
361
362 target: Unit,
364}
365
366impl Display for ConversionError {
367 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
368 write!(f,
369 "cannot convert from `{}` to `{}`",
370 self.unit, self.target
371 )
372 }
373}
374
375impl Error for ConversionError {}
376
377#[derive(Debug)]
379pub struct InvalidUnit {
380 unit: String,
382}
383
384impl Display for InvalidUnit {
385 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
386 write!(f, "not a valid unit: `{}`", self.unit)
387 }
388}
389
390impl Error for InvalidUnit {}
391
392#[derive(Clone, Copy, Debug, PartialEq, Eq)]
396#[non_exhaustive]
397pub enum Base {
398 Length(Length),
399 Mass(Mass),
400 Area(Area),
401 Volume(Volume),
402 Time(Time),
403}
404
405impl Display for Base {
406 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
407 match self {
408 Base::Length(l) => write!(f, "{}", l),
409 Base::Mass(m) => write!(f, "{}", m),
410 Base::Area(a) => write!(f, "{}", a),
411 Base::Volume(v) => write!(f, "{}", v),
412 Base::Time(t) => write!(f, "{}", t),
413 }
414 }
415}
416
417impl FromStr for Base {
423 type Err = InvalidUnit;
424
425 fn from_str(value: &str) -> Result<Self, Self::Err> {
426 Length::try_from(value).map(Base::Length)
427 .or_else(|_| Mass::try_from(value).map(Base::Mass))
428 .or_else(|_| Area::try_from(value).map(Base::Area))
429 .or_else(|_| Volume::try_from(value).map(Base::Volume))
430 .or_else(|_| Time::try_from(value).map(Base::Time))
431 }
432}
433
434impl TryFrom<&str> for Base {
439 type Error = InvalidUnit;
440
441 fn try_from(value: &str) -> Result<Self, Self::Error> {
442 value.parse()
443 }
444}
445
446impl From<Base> for Unit {
447 fn from(q: Base) -> Self {
448 Self::new(q)
449 }
450}
451
452impl Base {
453 fn conversion_factor_to(&self, target: impl Into<Unit>) -> Option<f64> {
454 let target = target.into();
455 match self {
456 Base::Length(l) => l.conversion_factor_to(target),
457 Base::Mass(m) => m.conversion_factor_to(target),
458 Base::Area(a) => a.conversion_factor_to(target),
459 Base::Volume(v) => v.conversion_factor_to(target),
460 Base::Time(t) => t.conversion_factor_to(target),
461 }
462 }
463
464 pub fn pow(&self, power: i8) -> Unit {
466 Unit::with_power(*self, power)
467 }
468
469 pub fn squared(&self) -> Unit {
471 self.pow(2)
472 }
473
474 pub fn cubed(&self) -> Unit {
476 self.pow(3)
477 }
478}
479
480unit_impl!("A unit of length.",
481 Length, Meter: "m",
482 Parsec: "pc" => 3.085677581e16,
483 LightYear: "ly" => 9.4607304725808e15,
484 AstronomicalUnit: "au" => 1.495978707e11,
485 NauticalMile: "nmi" => 1852.0,
486 Kilometer: "km" => 1000.0,
487 Meter: "m" => 1.0,
488 Decimeter: "dm" => 0.1,
489 Centimeter: "cm" => 0.01,
490 Millimeter: "mm" => 0.001,
491 Micrometer: "µm", "um" => 1e-6,
492 Nanometer: "nm" => 1e-9,
493 Angstrom: "Å", "A" => 1e-10,
494 Picometer: "pm" => 1e-12,
495 Mile: "mi" => 1609.344,
496 Yard: "yd" => 0.9144,
497 Foot: "ft" => 0.3048,
498 Inch: "in" => 0.0254,
499);
500
501unit_impl!("A unit of mass.",
502 Mass, Kilogram: "kg",
503 "A US (short) ton (2000 pounds).", ShortTon: "tn" => 907.18474,
504 "An imperial (long) ton (2240 pounds).", LongTon: "lt" => 1016.0469088,
505 Pound: "lb" => 0.45359237,
506 Ounce: "oz" => 0.028349523125,
507 "A metric tonne (1000 kilograms).", Tonne: "t" => 1000.0,
508 Kilogram: "kg" => 1.0,
509 Gram: "g" => 0.001,
510 Centigram: "cg" => 1e-5,
511 Milligram: "mg" => 1e-6,
512 Microgram: "µg", "ug" => 1e-9,
513 "An atomic mass unit (1/12 of the mass of a carbon 12 atom).", AtomicMassUnit: "amu" => 1.66053906892e-27,
514);
515
516unit_impl!("A unit of area.\n\nMeasurements of area are of the same kind as measurements of length with power 2. Thus, any measurement created with an area unit can be converted to a a length unit squared, and vice versa.",
517 Area, Are: "a" => 100.0 Length^2, Hectare: "ha" => 100.0,
519 Decare: "daa" => 10.0,
520 Are: "a" => 1.0,
521 Deciare: "da" => 0.1,
522 Centiare: "ca" => 0.01,
523 Barn: "b" => 1e-30,
524 Acre: "ac" => 40.468564224,
525);
526
527unit_impl!("A unit of volume.\n\nMeasurements of volume are of the same kind as measurements of length with power 3. Thus, any measurement created with a volume unit can be converted to a a length unit cubed, and vice versa.",
528 Volume, Liter: "L" => 0.001 Length^3, "A US bushel (2150.42 cubic inches).", Bushel: "bu" => 35.2390704,
530 "A US gallon (231 cubic inches).", Gallon: "gal" => 3.785411784,
531 "A US quart (57.75 cubic inches).", Quart: "qt" => 0.946352946,
532 "A US pint (28.875 cubic inches).", Pint: "pt" => 0.473176473,
533 "A US cup (8 fluid ounces).", Cup: "c" => 0.2365882365,
534 "A US fluid ounce (1.8046875 cubic inches).", FluidOunce: "floz" => 0.0295735295625,
535 "A US tablespoon (0.90234375 cubic inches).", Tablespoon: "tbsp" => 0.01478676478125,
536 "A US teaspoon (0.30078125 cubic inches).", Teaspoon: "tsp" => 0.00492892159375,
537 Kiloliter: "kL" => 1000.0,
538 Liter: "L" => 1.0,
539 Centiliter: "cL" => 0.01,
540 Milliliter: "mL" => 0.001,
541 Microliter: "µL", "uL" => 1e-6,
542);
543
544unit_impl!("A unit of time.",
545 Time, Second: "s",
546 "100 years, each of 365.25 days.", Century: "cen" => 3.15576e9,
547 "10 years, each of 365.25 days.", Decade: "dec" => 3.15576e8,
548 "A year of 365.25 days. Decades and centuries are defined in terms of this unit.", Year: "yr" => 3.15576e7,
549 Week: "wk" => 604800.0,
550 Day: "day" => 86400.0,
551 Hour: "hr" => 3600.0,
552 Minute: "min" => 60.0,
553 Second: "s" => 1.0,
554 Decisecond: "ds" => 0.1,
555 Centisecond: "cs" => 0.01,
556 Millisecond: "ms" => 0.001,
557 Microsecond: "µs", "us" => 1e-6,
558 Nanosecond: "ns" => 1e-9,
559 Picosecond: "ps" => 1e-12,
560);