rink_core/loader/
registry.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{
4    ast::{DatePattern, Expr},
5    runtime::Substance,
6    types::{BaseUnit, Dimensionality, Number, Numeric},
7};
8
9#[derive(Default, Debug)]
10pub struct Registry {
11    /// Contains the base units, e.g. `kg`, `bit`.
12    pub base_units: BTreeSet<BaseUnit>,
13    /// Mappings from short forms of base units to long forms, e.g. `kg` → `kilogram`.
14    pub base_unit_long_names: BTreeMap<String, String>,
15    /// Contains numerical values of units.
16    pub units: BTreeMap<String, Number>,
17    /// Maps dimensionality to named physical quantities like `energy`.
18    pub quantities: BTreeMap<Dimensionality, String>,
19    /// Maps dimensionality to names of SI derived units (newton,
20    /// pascal, etc.) for showing simplified forms of units.
21    pub decomposition_units: BTreeMap<Dimensionality, String>,
22    /// A list of prefixes that can be applied to units, like `kilo`.
23    pub prefixes: Vec<(String, Numeric)>,
24    /// Contains the original expressions defining a unit.
25    pub definitions: BTreeMap<String, Expr>,
26    /// Contains documentation strings.
27    pub docs: BTreeMap<String, String>,
28    /// Maps unit names to category IDs.
29    pub categories: BTreeMap<String, String>,
30    /// Maps category IDs to display names.
31    pub category_names: BTreeMap<String, String>,
32    /// Used for matching date formats.
33    pub datepatterns: Vec<Vec<DatePattern>>,
34    /// Objects or materials that have certain properties.
35    pub substances: BTreeMap<String, Substance>,
36    /// Maps elemental names (like `He`) to substance names (`helium`),
37    /// used for parsing molecular formulas, e.g. `H2O`.
38    pub substance_symbols: BTreeMap<String, String>,
39}
40
41impl Registry {
42    fn lookup_exact(&self, name: &str) -> Option<Number> {
43        if let Some(k) = self.base_units.get(name) {
44            return Some(Number::one_unit(k.to_owned()));
45        }
46        if let Some(v) = self.units.get(name).cloned() {
47            return Some(v);
48        }
49        None
50    }
51
52    fn lookup_with_prefix(&self, name: &str) -> Option<Number> {
53        if let Some(v) = self.lookup_exact(name) {
54            return Some(v);
55        }
56        for &(ref pre, ref value) in &self.prefixes {
57            if name.starts_with(pre) {
58                if let Some(v) = self.lookup_exact(&name[pre.len()..]) {
59                    return Some((&v * &Number::new(value.clone())).unwrap());
60                }
61            }
62        }
63        None
64    }
65
66    pub(crate) fn lookup(&self, name: &str) -> Option<Number> {
67        let res = self.lookup_with_prefix(name);
68        if res.is_some() {
69            return res;
70        }
71
72        // Check for plurals, but only do this after exhausting every
73        // other possibility, so that `ks` is kiloseconds instead of
74        // kelvin.
75        if let Some(name) = name.strip_suffix('s') {
76            self.lookup_with_prefix(name)
77        } else {
78            None
79        }
80    }
81
82    fn canonicalize_exact(&self, name: &str) -> Option<String> {
83        if let Some(v) = self.base_unit_long_names.get(name) {
84            return Some(v.clone());
85        }
86        if let Some(base_unit) = self.base_units.get(name) {
87            return Some(base_unit.to_string());
88        }
89        if let Some(expr) = self.definitions.get(name) {
90            if let Expr::Unit { ref name } = *expr {
91                if let Some(canonicalized) = self.canonicalize(&*name) {
92                    return Some(canonicalized);
93                } else {
94                    return Some(name.clone());
95                }
96            } else {
97                // we cannot canonicalize it further
98                return Some(name.to_owned());
99            }
100        }
101        None
102    }
103
104    fn canonicalize_with_prefix(&self, name: &str) -> Option<String> {
105        if let Some(v) = self.canonicalize_exact(name) {
106            return Some(v);
107        }
108        for &(ref prefix, ref value) in &self.prefixes {
109            if let Some(name) = name.strip_prefix(prefix) {
110                if let Some(canonicalized) = self.canonicalize_exact(name) {
111                    let mut prefix = prefix;
112                    for &(ref other, ref otherval) in &self.prefixes {
113                        if other.len() > prefix.len() && value == otherval {
114                            prefix = other;
115                        }
116                    }
117                    return Some(format!("{}{}", prefix, canonicalized));
118                }
119            }
120        }
121        None
122    }
123
124    /// Given a unit name, tries to find a canonical name for it.
125    ///
126    /// # Examples
127    ///
128    /// * `kg` -> `kilogram` (base units are converted to long name)
129    /// * `mm` -> `millimeter` (prefixes are converted to long form)
130    /// * `micron` -> `micrometer` (aliases are expanded)
131    pub fn canonicalize(&self, name: &str) -> Option<String> {
132        let res = self.canonicalize_with_prefix(name);
133        if res.is_some() {
134            return res;
135        }
136
137        if let Some(name) = name.strip_suffix('s') {
138            self.canonicalize_with_prefix(name)
139        } else {
140            None
141        }
142    }
143}