181 lines
6.2 KiB
Rust
181 lines
6.2 KiB
Rust
/*
|
|
* unit_converter lib.rs
|
|
* - main library file for the asklyphe unit converter
|
|
*
|
|
* Copyright (C) 2025 Real Microsoft, LLC
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use crate::length_units::{LengthUnit, LENGTH_NAMES, LENGTH_STORE, PRECISION};
|
|
use crate::unit_defs::{ConvertTo, MetricPrefix};
|
|
use astro_float::{BigFloat, Consts, Radix, RoundingMode};
|
|
|
|
pub const MAX_PRECISION: usize = 2048;
|
|
|
|
pub mod length_units;
|
|
pub mod unit_defs;
|
|
|
|
/// removes a leading conversion word
|
|
pub fn remove_leading_conversion_word(input_str: &str) -> String {
|
|
const CONVERSION_WORDS: &[&str] = &["to", "into", "as", "in", "using"];
|
|
|
|
let input_str = input_str.trim_start();
|
|
|
|
for word in CONVERSION_WORDS {
|
|
if input_str.starts_with(word) {
|
|
return input_str.strip_prefix(word).unwrap().to_string();
|
|
}
|
|
}
|
|
input_str.to_string()
|
|
}
|
|
|
|
/// takes an input starting with a number value, and returns it as a bigfloat, plus whatevers left
|
|
pub fn parse_number(input_str: &str) -> (BigFloat, String) {
|
|
let mut cc = Consts::new().expect("constants cache not initialized!");
|
|
// parse digits, scientific notation, inf, and nan
|
|
let mut buf = "".to_string();
|
|
let mut input = input_str.chars();
|
|
let mut first = true;
|
|
let mut exp = false;
|
|
let mut nan = 0;
|
|
let mut inf = 0;
|
|
let mut cursor = 0;
|
|
while let Some(c) = input.next() {
|
|
cursor += 1;
|
|
if c == ',' || c.is_whitespace() {
|
|
continue;
|
|
}
|
|
if exp && c == '+' && first {
|
|
buf.push('+');
|
|
first = false;
|
|
continue;
|
|
}
|
|
if c.is_numeric() || (c == '-' && first) || c == '.' {
|
|
buf.push(c);
|
|
first = false;
|
|
} else {
|
|
let lower = c.to_ascii_lowercase();
|
|
if inf > 0 {
|
|
if inf == 1 && lower == 'n' {
|
|
buf.push('n');
|
|
inf += 1;
|
|
} else if inf == 2 && lower == 'f' {
|
|
buf.push('f');
|
|
break;
|
|
} else if inf == 1 {
|
|
buf.pop(); // pop i
|
|
cursor -= 1;
|
|
cursor -= 1;
|
|
break;
|
|
} else if inf == 2 {
|
|
buf.pop(); // pop n
|
|
buf.pop(); // pop i
|
|
cursor -= 1;
|
|
cursor -= 2;
|
|
break;
|
|
}
|
|
} else if nan > 0 {
|
|
if nan == 1 && lower == 'a' {
|
|
buf.push('a');
|
|
nan += 1;
|
|
} else if nan == 2 && lower == 'n' {
|
|
buf.push('n');
|
|
break;
|
|
} else if nan == 1 {
|
|
buf.pop(); // pop n
|
|
cursor -= 1;
|
|
cursor -= 1;
|
|
break;
|
|
} else if nan == 2 {
|
|
buf.pop(); // pop a
|
|
buf.pop(); // pop n
|
|
cursor -= 1;
|
|
cursor -= 2;
|
|
break;
|
|
}
|
|
} else if !exp {
|
|
if lower == 'e' {
|
|
exp = true;
|
|
first = true;
|
|
buf.push('e');
|
|
} else if lower == 'i' {
|
|
inf = 1;
|
|
buf.push('i');
|
|
} else if lower == 'n' {
|
|
nan = 1;
|
|
buf.push('n');
|
|
} else {
|
|
cursor -= 1;
|
|
break;
|
|
}
|
|
} else {
|
|
cursor -= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
let mut output = "".to_string();
|
|
let input = input_str.chars();
|
|
for c in input {
|
|
if cursor > 0 {
|
|
cursor -= 1;
|
|
} else {
|
|
output.push(c);
|
|
}
|
|
}
|
|
|
|
let value = BigFloat::parse(&buf, Radix::Dec, MAX_PRECISION, RoundingMode::None, &mut cc);
|
|
|
|
(value, output)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::unit_defs::{BestUnit, ConvertTo, FromUnitName};
|
|
use std::str::FromStr;
|
|
|
|
#[test]
|
|
fn it_works() {
|
|
let (number, remainder) = parse_number(" 2 meter to foot");
|
|
println!("number:{} remainder:{}", number, remainder);
|
|
let (unit_a, remainder) = LengthUnit::parse(remainder).unwrap();
|
|
println!("unit_a: {:?}, remainer: {}", unit_a, remainder);
|
|
let remainder = remove_leading_conversion_word(&remainder);
|
|
println!("stripped: {}", remainder);
|
|
let (unit_b, _) = LengthUnit::parse(remainder).unwrap();
|
|
println!("unit_b: {:?}", unit_b);
|
|
let conversion = unit_a.convert_to(number, unit_b);
|
|
let approx = f64::from_str(&format!("{}", conversion)).unwrap();
|
|
//conversion.set_exponent(Exponent::from(1));
|
|
println!("conversion: {}", conversion);
|
|
println!(
|
|
"approx: {} {}",
|
|
approx,
|
|
LENGTH_NAMES.get(&unit_b).unwrap().first().unwrap().1
|
|
);
|
|
}
|
|
#[test]
|
|
fn best_unit() {
|
|
let (number, remainder) = parse_number(" 25 ft");
|
|
println!("number:{} remainder:{}", number, remainder);
|
|
let (unit_a, remainder) = LengthUnit::parse(remainder).unwrap();
|
|
println!("unit_a: {:?}, remainer: {}", unit_a, remainder);
|
|
let best_unit = unit_a.best_unit(number.clone());
|
|
println!("best unit: {:?}", best_unit);
|
|
let conversion = unit_a.convert_to(number, best_unit);
|
|
let approx = f64::from_str(&format!("{}", conversion)).unwrap();
|
|
//conversion.set_exponent(Exponent::from(1));
|
|
println!("conversion: {}", conversion);
|
|
println!(
|
|
"approx: {} {}",
|
|
approx,
|
|
LENGTH_NAMES.get(&best_unit).unwrap().first().unwrap().1
|
|
);
|
|
}
|
|
}
|