Skip to main content

ganit_core/eval/functions/parser/convert/
mod.rs

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        // Length (base: m)
24        "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        // Mass (base: kg)
42        "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        // Time (base: s)
56        "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        // Temperature (special handling, factor unused)
63        "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        // Pressure (base: Pa)
69        "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        // Energy (base: J)
76        "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        // Power (base: W)
88        "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        // Force (base: N)
94        "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        // Speed (base: m/s)
100        "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        // Area (base: m²)
107        "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        // Volume (base: L)
118        "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        // Magnetism (base: T)
138        "T"  => Some((UnitCategory::Magnetism, 1.0)),
139        "ga" => Some((UnitCategory::Magnetism, 0.0001)),
140
141        // Information (base: bit)
142        "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;