forked from asklyphe-public/asklyphe
		
	
		
			
				
	
	
		
			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 = 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
 | 
						|
        );
 | 
						|
    }
 | 
						|
}
 |