This repository has been archived on 2025-03-12. You can view files and clone it, but cannot push or open issues or pull requests.
unit_converter/src/lib.rs
2025-03-10 20:12:11 -07:00

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
);
}
}