1use crate::eval::functions::check_arity;
2use crate::types::{ErrorKind, Value};
3
4#[derive(PartialEq)]
5enum UnitCategory {
6 Length,
7 Mass,
8 Time,
9 Temperature,
10 Pressure,
11 Energy,
12 Power,
13 Force,
14 Speed,
15 Area,
16 Volume,
17 Magnetism,
18 Information,
19}
20
21fn get_unit(name: &str) -> Option<(UnitCategory, f64)> {
22 match name {
23 "m" => Some((UnitCategory::Length, 1.0)),
25 "km" => Some((UnitCategory::Length, 1000.0)),
26 "mi" => Some((UnitCategory::Length, 1609.344)),
27 "nmi" | "Nmi" => Some((UnitCategory::Length, 1852.0)),
28 "ft" => Some((UnitCategory::Length, 0.3048)),
29 "in" => Some((UnitCategory::Length, 0.0254)),
30 "yd" => Some((UnitCategory::Length, 0.9144)),
31 "cm" => Some((UnitCategory::Length, 0.01)),
32 "mm" => Some((UnitCategory::Length, 0.001)),
33 "Å" | "Ang" => Some((UnitCategory::Length, 1e-10)),
34 "ly" => Some((UnitCategory::Length, 9.4607304725808e15)),
35 "pica" => Some((UnitCategory::Length, 0.00423333333)),
36 "pt" => Some((UnitCategory::Length, 0.000352778)),
37 "ell" => Some((UnitCategory::Length, 1.143)),
38 "parsec" => Some((UnitCategory::Length, 3.08568e16)),
39 "survey_mi" => Some((UnitCategory::Length, 1609.3472)),
40
41 "kg" => Some((UnitCategory::Mass, 1.0)),
43 "g" => Some((UnitCategory::Mass, 0.001)),
44 "lbm" => Some((UnitCategory::Mass, 0.45359237)),
45 "oz" => Some((UnitCategory::Mass, 0.028349523125)),
46 "mg" => Some((UnitCategory::Mass, 1e-6)),
47 "grain" => Some((UnitCategory::Mass, 6.479891e-5)),
48 "cwt" => Some((UnitCategory::Mass, 45.359237)),
49 "uk_cwt" => Some((UnitCategory::Mass, 50.80234544)),
50 "ton" => Some((UnitCategory::Mass, 907.18474)),
51 "uk_ton" => Some((UnitCategory::Mass, 1016.0469088)),
52 "stone" => Some((UnitCategory::Mass, 6.35029318)),
53 "slug" => Some((UnitCategory::Mass, 14.59390294)),
54
55 "s" | "sec" => Some((UnitCategory::Time, 1.0)),
57 "mn" | "min" => Some((UnitCategory::Time, 60.0)),
58 "hr" => Some((UnitCategory::Time, 3600.0)),
59 "day" => Some((UnitCategory::Time, 86400.0)),
60 "yr" => Some((UnitCategory::Time, 31557600.0)),
61
62 "C" | "cel" => Some((UnitCategory::Temperature, 0.0)),
64 "F" | "fah" => Some((UnitCategory::Temperature, 0.0)),
65 "K" | "kel" => Some((UnitCategory::Temperature, 0.0)),
66 "Rank" => Some((UnitCategory::Temperature, 0.0)),
67
68 "Pa" => Some((UnitCategory::Pressure, 1.0)),
70 "atm" => Some((UnitCategory::Pressure, 101325.0)),
71 "mmHg" => Some((UnitCategory::Pressure, 133.322387415)),
72 "psi" => Some((UnitCategory::Pressure, 6894.757293168)),
73 "Torr" => Some((UnitCategory::Pressure, 133.322387415)),
74
75 "J" => Some((UnitCategory::Energy, 1.0)),
77 "kJ" => Some((UnitCategory::Energy, 1000.0)),
78 "cal" => Some((UnitCategory::Energy, 4.184)),
79 "kcal" => Some((UnitCategory::Energy, 4184.0)),
80 "eV" => Some((UnitCategory::Energy, 1.602176634e-19)),
81 "BTU" => Some((UnitCategory::Energy, 1055.05585262)),
82 "Wh" => Some((UnitCategory::Energy, 3600.0)),
83 "kWh" => Some((UnitCategory::Energy, 3600000.0)),
84 "HPh" => Some((UnitCategory::Energy, 2684519.5368856)),
85 "ft-lb" => Some((UnitCategory::Energy, 1.3558179483314)),
86
87 "W" => Some((UnitCategory::Power, 1.0)),
89 "kW" => Some((UnitCategory::Power, 1000.0)),
90 "HP" => Some((UnitCategory::Power, 745.69987158227)),
91 "PS" => Some((UnitCategory::Power, 735.49875)),
92
93 "N" => Some((UnitCategory::Force, 1.0)),
95 "lbf" => Some((UnitCategory::Force, 4.4482216152605)),
96 "dyn" => Some((UnitCategory::Force, 1e-5)),
97 "pond" => Some((UnitCategory::Force, 0.00980665)),
98
99 "m/s" => Some((UnitCategory::Speed, 1.0)),
101 "m/h" => Some((UnitCategory::Speed, 0.00027778)),
102 "kn" => Some((UnitCategory::Speed, 0.51444)),
103 "mph" => Some((UnitCategory::Speed, 0.44704)),
104 "admkn" => Some((UnitCategory::Speed, 0.514773)),
105
106 "m2" | "m^2" => Some((UnitCategory::Area, 1.0)),
109 "km2" | "km^2" => Some((UnitCategory::Area, 1e6)),
110 "ft2" | "ft^2" => Some((UnitCategory::Area, 0.09290304)),
111 "in2" | "in^2" => Some((UnitCategory::Area, 0.00064516)),
112 "yd2" | "yd^2" => Some((UnitCategory::Area, 0.83612736)),
113 "mi2" | "mi^2" => Some((UnitCategory::Area, 2589988.110336)),
114 "ha" => Some((UnitCategory::Area, 10000.0)),
115 "ar" => Some((UnitCategory::Area, 100.0)),
116 "acre" => Some((UnitCategory::Area, 4046.8564224)),
117
118 "l" | "L" | "lt" => Some((UnitCategory::Volume, 1.0)),
121 "ml" | "mL" => Some((UnitCategory::Volume, 0.001)),
122 "m3" | "m^3" => Some((UnitCategory::Volume, 1000.0)),
123 "km3" | "km^3" => Some((UnitCategory::Volume, 1e12)),
124 "ft3" | "ft^3" => Some((UnitCategory::Volume, 28.316846592)),
125 "in3" | "in^3" => Some((UnitCategory::Volume, 0.016387064)),
126 "yd3" | "yd^3" => Some((UnitCategory::Volume, 764.554857984)),
127 "mi3" | "mi^3" => Some((UnitCategory::Volume, 4_168_181_825.440_579_4)),
128 "gal" => Some((UnitCategory::Volume, 3.785411784)),
129 "qt" => Some((UnitCategory::Volume, 0.946352946)),
130 "cup" => Some((UnitCategory::Volume, 0.2365882365)),
131 "fl_oz" => Some((UnitCategory::Volume, 0.0295735295625)),
132 "tsp" => Some((UnitCategory::Volume, 0.00492892159375)),
133 "tbs" => Some((UnitCategory::Volume, 0.01478676478125)),
134 "uk_pt" => Some((UnitCategory::Volume, 0.56826125)),
135 "uk_qt" => Some((UnitCategory::Volume, 1.1365225)),
136 "uk_gal" => Some((UnitCategory::Volume, 4.54609)),
137 "ang3" | "ang^3" => Some((UnitCategory::Volume, 1e-30)),
138
139 "T" => Some((UnitCategory::Magnetism, 1.0)),
141 "ga" => Some((UnitCategory::Magnetism, 0.0001)),
142
143 "bit" => Some((UnitCategory::Information, 1.0)),
145 "byte" => Some((UnitCategory::Information, 8.0)),
146 "kbit" => Some((UnitCategory::Information, 1000.0)),
147 "kbyte" => Some((UnitCategory::Information, 8000.0)),
148 "Mbit" => Some((UnitCategory::Information, 1e6)),
149 "Mbyte" => Some((UnitCategory::Information, 8e6)),
150 "Gbit" => Some((UnitCategory::Information, 1e9)),
151 "Gbyte" => Some((UnitCategory::Information, 8e9)),
152 "Tbit" => Some((UnitCategory::Information, 1e12)),
153 "Tbyte" => Some((UnitCategory::Information, 8e12)),
154
155 _ => None,
156 }
157}
158
159fn is_temperature(name: &str) -> bool {
160 matches!(name, "C" | "cel" | "F" | "fah" | "K" | "kel" | "Rank")
161}
162
163fn convert_temperature(value: f64, from: &str, to: &str) -> f64 {
164 let celsius = match from {
165 "C" | "cel" => value,
166 "F" | "fah" => (value - 32.0) * 5.0 / 9.0,
167 "K" | "kel" => value - 273.15,
168 "Rank" => (value - 491.67) * 5.0 / 9.0,
169 _ => unreachable!(),
170 };
171 match to {
172 "C" | "cel" => celsius,
173 "F" | "fah" => celsius * 9.0 / 5.0 + 32.0,
174 "K" | "kel" => celsius + 273.15,
175 "Rank" => (celsius + 273.15) * 9.0 / 5.0,
176 _ => unreachable!(),
177 }
178}
179
180fn coerce_to_number(v: &Value) -> Option<f64> {
181 match v {
182 Value::Number(n) => Some(*n),
183 Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
184 Value::Date(n) => Some(*n),
185 _ => None,
186 }
187}
188
189pub fn convert_fn(args: &[Value]) -> Value {
190 if let Some(err) = check_arity(args, 3, 3) {
191 return err;
192 }
193
194 let value = match coerce_to_number(&args[0]) {
195 Some(n) => n,
196 None => return Value::Error(ErrorKind::Value),
197 };
198
199 let from = match &args[1] {
200 Value::Text(s) => s.clone(),
201 Value::Error(_) => return args[1].clone(),
202 _ => return Value::Error(ErrorKind::Value),
203 };
204
205 let to = match &args[2] {
206 Value::Text(s) => s.clone(),
207 Value::Error(_) => return args[2].clone(),
208 _ => return Value::Error(ErrorKind::Value),
209 };
210
211 if is_temperature(&from) || is_temperature(&to) {
212 if !is_temperature(&from) || !is_temperature(&to) {
213 return Value::Error(ErrorKind::NA);
214 }
215 let result = convert_temperature(value, &from, &to);
216 if result.is_finite() {
217 Value::Number(result)
218 } else {
219 Value::Error(ErrorKind::Num)
220 }
221 } else {
222 let (from_cat, from_factor) = match get_unit(&from) {
223 Some(u) => u,
224 None => return Value::Error(ErrorKind::NA),
225 };
226 let (to_cat, to_factor) = match get_unit(&to) {
227 Some(u) => u,
228 None => return Value::Error(ErrorKind::NA),
229 };
230 if from_cat != to_cat {
231 return Value::Error(ErrorKind::NA);
232 }
233 let result = value * from_factor / to_factor;
234 if result.is_finite() {
235 Value::Number(result)
236 } else {
237 Value::Error(ErrorKind::Num)
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests;