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" => Some((UnitCategory::Area, 1.0)),
108 "km2" => Some((UnitCategory::Area, 1e6)),
109 "ft2" => Some((UnitCategory::Area, 0.09290304)),
110 "in2" => Some((UnitCategory::Area, 0.00064516)),
111 "yd2" => Some((UnitCategory::Area, 0.83612736)),
112 "mi2" => Some((UnitCategory::Area, 2589988.110336)),
113 "ha" => Some((UnitCategory::Area, 10000.0)),
114 "ar" => Some((UnitCategory::Area, 100.0)),
115 "acre" => Some((UnitCategory::Area, 4046.8564224)),
116
117 "l" | "L" | "lt" => Some((UnitCategory::Volume, 1.0)),
119 "ml" | "mL" => Some((UnitCategory::Volume, 0.001)),
120 "m3" => Some((UnitCategory::Volume, 1000.0)),
121 "km3" => Some((UnitCategory::Volume, 1e12)),
122 "ft3" => Some((UnitCategory::Volume, 28.316846592)),
123 "in3" => Some((UnitCategory::Volume, 0.016387064)),
124 "yd3" => Some((UnitCategory::Volume, 764.554857984)),
125 "mi3" => Some((UnitCategory::Volume, 4_168_181_825.440_579_4)),
126 "gal" => Some((UnitCategory::Volume, 3.785411784)),
127 "qt" => Some((UnitCategory::Volume, 0.946352946)),
128 "cup" => Some((UnitCategory::Volume, 0.2365882365)),
129 "fl_oz" => Some((UnitCategory::Volume, 0.0295735295625)),
130 "tsp" => Some((UnitCategory::Volume, 0.00492892159375)),
131 "tbs" => Some((UnitCategory::Volume, 0.01478676478125)),
132 "uk_pt" => Some((UnitCategory::Volume, 0.56826125)),
133 "uk_qt" => Some((UnitCategory::Volume, 1.1365225)),
134 "uk_gal" => Some((UnitCategory::Volume, 4.54609)),
135 "ang3" => Some((UnitCategory::Volume, 1e-30)),
136
137 "T" => Some((UnitCategory::Magnetism, 1.0)),
139 "ga" => Some((UnitCategory::Magnetism, 0.0001)),
140
141 "bit" => Some((UnitCategory::Information, 1.0)),
143 "byte" => Some((UnitCategory::Information, 8.0)),
144 "kbit" => Some((UnitCategory::Information, 1000.0)),
145 "kbyte" => Some((UnitCategory::Information, 8000.0)),
146 "Mbit" => Some((UnitCategory::Information, 1e6)),
147 "Mbyte" => Some((UnitCategory::Information, 8e6)),
148 "Gbit" => Some((UnitCategory::Information, 1e9)),
149 "Gbyte" => Some((UnitCategory::Information, 8e9)),
150 "Tbit" => Some((UnitCategory::Information, 1e12)),
151 "Tbyte" => Some((UnitCategory::Information, 8e12)),
152
153 _ => None,
154 }
155}
156
157fn is_temperature(name: &str) -> bool {
158 matches!(name, "C" | "cel" | "F" | "fah" | "K" | "kel" | "Rank")
159}
160
161fn convert_temperature(value: f64, from: &str, to: &str) -> f64 {
162 let celsius = match from {
163 "C" | "cel" => value,
164 "F" | "fah" => (value - 32.0) * 5.0 / 9.0,
165 "K" | "kel" => value - 273.15,
166 "Rank" => (value - 491.67) * 5.0 / 9.0,
167 _ => unreachable!(),
168 };
169 match to {
170 "C" | "cel" => celsius,
171 "F" | "fah" => celsius * 9.0 / 5.0 + 32.0,
172 "K" | "kel" => celsius + 273.15,
173 "Rank" => (celsius + 273.15) * 9.0 / 5.0,
174 _ => unreachable!(),
175 }
176}
177
178fn coerce_to_number(v: &Value) -> Option<f64> {
179 match v {
180 Value::Number(n) => Some(*n),
181 Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
182 Value::Date(n) => Some(*n),
183 _ => None,
184 }
185}
186
187pub fn convert_fn(args: &[Value]) -> Value {
188 if let Some(err) = check_arity(args, 3, 3) {
189 return err;
190 }
191
192 let value = match coerce_to_number(&args[0]) {
193 Some(n) => n,
194 None => return Value::Error(ErrorKind::Value),
195 };
196
197 let from = match &args[1] {
198 Value::Text(s) => s.clone(),
199 Value::Error(_) => return args[1].clone(),
200 _ => return Value::Error(ErrorKind::Value),
201 };
202
203 let to = match &args[2] {
204 Value::Text(s) => s.clone(),
205 Value::Error(_) => return args[2].clone(),
206 _ => return Value::Error(ErrorKind::Value),
207 };
208
209 if is_temperature(&from) || is_temperature(&to) {
210 if !is_temperature(&from) || !is_temperature(&to) {
211 return Value::Error(ErrorKind::NA);
212 }
213 let result = convert_temperature(value, &from, &to);
214 if result.is_finite() {
215 Value::Number(result)
216 } else {
217 Value::Error(ErrorKind::Num)
218 }
219 } else {
220 let (from_cat, from_factor) = match get_unit(&from) {
221 Some(u) => u,
222 None => return Value::Error(ErrorKind::NA),
223 };
224 let (to_cat, to_factor) = match get_unit(&to) {
225 Some(u) => u,
226 None => return Value::Error(ErrorKind::NA),
227 };
228 if from_cat != to_cat {
229 return Value::Error(ErrorKind::NA);
230 }
231 let result = value * from_factor / to_factor;
232 if result.is_finite() {
233 Value::Number(result)
234 } else {
235 Value::Error(ErrorKind::Num)
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests;