/* * 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 . */ 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 ); } }