forked from asklyphe-public/asklyphe
		
	
		
			
	
	
		
			182 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
		
		
			
		
	
	
			182 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 = 1024;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |