1 use std::borrow::Cow;
2
3 use crate::error::{FendError, Interrupt};
4 use crate::eval::evaluate_to_value;
5 use crate::num::Number;
6 use crate::result::FResult;
7 use crate::value::Value;
8 use crate::Attrs;
9
10 mod builtin;
11
12 pub(crate) use builtin::lookup_default_unit;
13 pub(crate) use builtin::IMPLICIT_UNIT_MAP;
14
15 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
16 pub(crate) enum PrefixRule {
17 NoPrefixesAllowed,
18 LongPrefixAllowed,
19 LongPrefix,
20 ShortPrefixAllowed,
21 ShortPrefix,
22 }
23
24 #[derive(Debug)]
25 pub(crate) struct UnitDef {
26 singular: Cow<'static, str>,
27 plural: Cow<'static, str>,
28 prefix_rule: PrefixRule,
29 alias: bool,
30 value: Value,
31 }
32
expr_unit<I: Interrupt>( unit_def: (Cow<'static, str>, Cow<'static, str>, Cow<'static, str>), attrs: Attrs, context: &mut crate::Context, int: &I, ) -> FResult<UnitDef>33 fn expr_unit<I: Interrupt>(
34 unit_def: (Cow<'static, str>, Cow<'static, str>, Cow<'static, str>),
35 attrs: Attrs,
36 context: &mut crate::Context,
37 int: &I,
38 ) -> FResult<UnitDef> {
39 let (singular, plural, definition) = unit_def;
40 let mut definition = definition.trim();
41 if definition == "$CURRENCY" {
42 let Some(exchange_rate_fn) = &context.get_exchange_rate else {
43 return Err(FendError::NoExchangeRatesAvailable);
44 };
45 let one_base_in_currency = exchange_rate_fn.relative_to_base_currency(&singular)?;
46 let value = evaluate_to_value(
47 format!("(1/{one_base_in_currency}) BASE_CURRENCY").as_str(),
48 None,
49 attrs,
50 context,
51 int,
52 )?
53 .expect_num()?;
54 let value = Number::create_unit_value_from_value(
55 &value,
56 Cow::Borrowed(""),
57 false,
58 singular.clone(),
59 plural.clone(),
60 int,
61 )?;
62 return Ok(UnitDef {
63 singular,
64 plural,
65 prefix_rule: PrefixRule::LongPrefixAllowed,
66 alias: false,
67 value: Value::Num(Box::new(value)),
68 });
69 }
70 let mut rule = PrefixRule::NoPrefixesAllowed;
71 if let Some(remaining) = definition.strip_prefix("l@") {
72 definition = remaining;
73 rule = PrefixRule::LongPrefixAllowed;
74 }
75 if let Some(remaining) = definition.strip_prefix("lp@") {
76 definition = remaining;
77 rule = PrefixRule::LongPrefix;
78 }
79 if let Some(remaining) = definition.strip_prefix("s@") {
80 definition = remaining;
81 rule = PrefixRule::ShortPrefixAllowed;
82 }
83 if let Some(remaining) = definition.strip_prefix("sp@") {
84 definition = remaining;
85 rule = PrefixRule::ShortPrefix;
86 }
87 if definition == "!" {
88 return Ok(UnitDef {
89 value: Value::Num(Box::new(Number::new_base_unit(
90 singular.clone(),
91 plural.clone(),
92 ))),
93 prefix_rule: rule,
94 singular,
95 plural,
96 alias: false,
97 });
98 }
99 let (alias, definition) = definition
100 .strip_prefix('=')
101 .map_or((false, definition), |remaining| (true, remaining));
102 // long prefixes like `hecto` are always treated as aliases
103 let alias = alias || rule == PrefixRule::LongPrefix;
104 let mut num = evaluate_to_value(definition, None, attrs, context, int)?.expect_num()?;
105
106 // There are three cases to consider:
107 // 1. Unitless aliases (e.g. `million` or `mega`) should be treated as an
108 // actual unit, but with the `alias` flag set so it can be simplified
109 // when possible.
110 // 2. Aliases with units (e.g. `sqft`) should be a pure alias (not a unit)
111 // so it can always be replaced. We can't convert this like unitless
112 // aliases since we would be simplifying it to base units (scaled m^2),
113 // so the precise unit we are aliasing to would be lost.
114 // 3. Units that aren't aliased (e.g. `meter`) should be converted to a
115 // normal unit.
116 //
117 // One exception to these cases is `unitless`, which should always be
118 // replaced with `1` even when we aren't simplifying, so it is defined
119 // manually instead of as a normal unit definition.
120
121 #[allow(clippy::nonminimal_bool)]
122 if !alias || (alias && num.is_unitless(int)?) {
123 // convert to an actual unit (cases 1 and 3)
124 num = Number::create_unit_value_from_value(
125 &num,
126 Cow::Borrowed(""),
127 alias,
128 singular.clone(),
129 plural.clone(),
130 int,
131 )?;
132 }
133 Ok(UnitDef {
134 value: Value::Num(Box::new(num)),
135 prefix_rule: rule,
136 singular,
137 plural,
138 alias,
139 })
140 }
141
construct_prefixed_unit<I: Interrupt>(a: UnitDef, b: UnitDef, int: &I) -> FResult<Value>142 fn construct_prefixed_unit<I: Interrupt>(a: UnitDef, b: UnitDef, int: &I) -> FResult<Value> {
143 let product = a.value.expect_num()?.mul(b.value.expect_num()?, int)?;
144 assert_eq!(a.singular, a.plural);
145 let unit = Number::create_unit_value_from_value(
146 &product, a.singular, b.alias, b.singular, b.plural, int,
147 )?;
148 Ok(Value::Num(Box::new(unit)))
149 }
150
query_unit<I: Interrupt>( ident: &str, attrs: Attrs, context: &mut crate::Context, int: &I, ) -> FResult<Value>151 pub(crate) fn query_unit<I: Interrupt>(
152 ident: &str,
153 attrs: Attrs,
154 context: &mut crate::Context,
155 int: &I,
156 ) -> FResult<Value> {
157 if ident.starts_with('\'') && ident.ends_with('\'') && ident.len() >= 3 {
158 let ident = ident.split_at(1).1;
159 let ident = ident.split_at(ident.len() - 1).0;
160 return Ok(Value::Num(Box::new(Number::new_base_unit(
161 ident.to_string().into(),
162 ident.to_string().into(),
163 ))));
164 }
165 query_unit_static(ident, attrs, context, int)
166 }
167
query_unit_static<I: Interrupt>( ident: &str, attrs: Attrs, context: &mut crate::Context, int: &I, ) -> FResult<Value>168 pub(crate) fn query_unit_static<I: Interrupt>(
169 ident: &str,
170 attrs: Attrs,
171 context: &mut crate::Context,
172 int: &I,
173 ) -> FResult<Value> {
174 match query_unit_case_sensitive(ident, true, attrs, context, int) {
175 Err(FendError::IdentifierNotFound(_)) => (),
176 Err(e) => return Err(e),
177 Ok(value) => {
178 return Ok(value);
179 }
180 }
181 query_unit_case_sensitive(ident, false, attrs, context, int)
182 }
183
query_unit_case_sensitive<I: Interrupt>( ident: &str, case_sensitive: bool, attrs: Attrs, context: &mut crate::Context, int: &I, ) -> FResult<Value>184 fn query_unit_case_sensitive<I: Interrupt>(
185 ident: &str,
186 case_sensitive: bool,
187 attrs: Attrs,
188 context: &mut crate::Context,
189 int: &I,
190 ) -> FResult<Value> {
191 match query_unit_internal(ident, false, case_sensitive, true, context) {
192 Err(FendError::IdentifierNotFound(_)) => (),
193 Err(e) => return Err(e),
194 Ok(unit_def) => {
195 // Return value without prefix. Note that lone short prefixes
196 // won't be returned here.
197 return Ok(expr_unit(unit_def, attrs, context, int)?.value);
198 }
199 }
200 let mut split_idx = ident.chars().next().unwrap().len_utf8();
201 while split_idx < ident.len() {
202 let (prefix, remaining_ident) = ident.split_at(split_idx);
203 split_idx += remaining_ident.chars().next().unwrap().len_utf8();
204 let a = match query_unit_internal(prefix, true, case_sensitive, false, context) {
205 Err(FendError::IdentifierNotFound(_)) => continue,
206 Err(e) => {
207 return Err(e);
208 }
209 Ok(a) => a,
210 };
211 match query_unit_internal(remaining_ident, false, case_sensitive, false, context) {
212 Err(FendError::IdentifierNotFound(_)) => continue,
213 Err(e) => return Err(e),
214 Ok(b) => {
215 let (a, b) = (
216 expr_unit(a, attrs, context, int)?,
217 expr_unit(b, attrs, context, int)?,
218 );
219 if (a.prefix_rule == PrefixRule::LongPrefix
220 && b.prefix_rule == PrefixRule::LongPrefixAllowed)
221 || (a.prefix_rule == PrefixRule::ShortPrefix
222 && b.prefix_rule == PrefixRule::ShortPrefixAllowed)
223 {
224 // now construct a new unit!
225 return construct_prefixed_unit(a, b, int);
226 }
227 return Err(FendError::IdentifierNotFound(ident.to_string().into()));
228 }
229 };
230 }
231 Err(FendError::IdentifierNotFound(ident.to_string().into()))
232 }
233
234 #[allow(clippy::type_complexity)]
query_unit_internal( ident: &str, short_prefixes: bool, case_sensitive: bool, whole_unit: bool, context: &crate::Context, ) -> FResult<(Cow<'static, str>, Cow<'static, str>, Cow<'static, str>)>235 fn query_unit_internal(
236 ident: &str,
237 short_prefixes: bool,
238 case_sensitive: bool,
239 whole_unit: bool,
240 context: &crate::Context,
241 ) -> FResult<(Cow<'static, str>, Cow<'static, str>, Cow<'static, str>)> {
242 if !short_prefixes {
243 for (s, p, d) in &context.custom_units {
244 let p = if p.is_empty() { s } else { p };
245 if (ident == s || ident == p)
246 || (!case_sensitive
247 && (s.eq_ignore_ascii_case(ident) || p.eq_ignore_ascii_case(ident)))
248 {
249 return Ok((
250 s.to_string().into(),
251 p.to_string().into(),
252 d.to_string().into(),
253 ));
254 }
255 }
256 }
257 if whole_unit && context.fc_mode == crate::FCMode::CelsiusFahrenheit {
258 if ident == "C" {
259 return Ok((
260 Cow::Borrowed("C"),
261 Cow::Borrowed("C"),
262 Cow::Borrowed("=\u{b0}C"),
263 ));
264 } else if ident == "F" {
265 return Ok((
266 Cow::Borrowed("F"),
267 Cow::Borrowed("F"),
268 Cow::Borrowed("=\u{b0}F"),
269 ));
270 }
271 }
272 if let Some(unit_def) = builtin::query_unit(ident, short_prefixes, case_sensitive) {
273 Ok(unit_def)
274 } else {
275 Err(FendError::IdentifierNotFound(ident.to_string().into()))
276 }
277 }
278
get_completions_for_prefix(prefix: &str) -> Vec<crate::Completion>279 pub(crate) fn get_completions_for_prefix(prefix: &str) -> Vec<crate::Completion> {
280 use crate::Completion;
281
282 let mut result = vec![];
283
284 let mut add = |name: &str| {
285 if name.starts_with(prefix) && name != prefix {
286 result.push(Completion {
287 display: name.to_string(),
288 insert: name.split_at(prefix.len()).1.to_string(),
289 });
290 }
291 };
292
293 for group in builtin::ALL_UNIT_DEFS {
294 for (s, _, _, _) in *group {
295 // only add singular name, since plurals
296 // unnecessarily clutter autocompletions
297 add(s);
298 }
299 }
300
301 result.sort_by(|a, b| a.display().cmp(b.display()));
302
303 result
304 }
305