icu_provider_source/units/
helpers.rsuse core::str::FromStr;
use std::collections::{BTreeMap, VecDeque};
use icu::experimental::measure::parser::MeasureUnitParser;
use icu::experimental::units::provider::{ConversionInfo, Exactness, Sign};
use icu::experimental::units::ratio::IcuRatio;
use icu_provider::DataError;
use num_traits::One;
use num_traits::Signed;
use zerovec::ZeroVec;
use crate::cldr_serde::units::info::Constant;
pub(crate) struct ScientificNumber {
pub(crate) clean_num: Vec<String>,
pub(crate) clean_den: Vec<String>,
pub(crate) exactness: Exactness,
}
#[derive(Debug)]
struct GeneralNonScientificNumber {
clean_num: Vec<String>,
clean_den: Vec<String>,
non_scientific_num: VecDeque<String>,
non_scientific_den: VecDeque<String>,
exactness: Exactness,
}
impl GeneralNonScientificNumber {
fn new(num: &[String], den: &[String], exactness: Exactness) -> Self {
let mut constant = GeneralNonScientificNumber {
clean_num: Vec::new(),
clean_den: Vec::new(),
non_scientific_num: VecDeque::new(),
non_scientific_den: VecDeque::new(),
exactness,
};
for n in num {
if is_scientific_number(n) {
constant.clean_num.push(n.clone());
} else {
constant.non_scientific_num.push_back(n.clone());
}
}
for d in den {
if is_scientific_number(d) {
constant.clean_den.push(d.clone());
} else {
constant.non_scientific_den.push_back(d.clone());
}
}
constant
}
fn is_free_of_non_scientific(&self) -> bool {
self.non_scientific_num.is_empty() && self.non_scientific_den.is_empty()
}
}
pub(crate) fn process_factor_part(
factor_part: &str,
cons_map: &BTreeMap<&str, ScientificNumber>,
) -> Result<ScientificNumber, DataError> {
if factor_part.contains('/') {
return Err(DataError::custom("the factor part is fractional number"));
}
let mut result = ScientificNumber {
clean_num: Vec::new(),
clean_den: Vec::new(),
exactness: Exactness::Exact,
};
let factor_parts = factor_part.split('*');
for factor in factor_parts {
if let Some(cons) = cons_map.get(factor.trim()) {
result.clean_num.extend(cons.clean_num.clone());
result.clean_den.extend(cons.clean_den.clone());
if cons.exactness == Exactness::Approximate {
result.exactness = Exactness::Approximate;
}
} else {
result.clean_num.push(factor.trim().to_string());
}
}
Ok(result)
}
pub(crate) fn process_factor(
factor: &str,
cons_map: &BTreeMap<&str, ScientificNumber>,
) -> Result<ScientificNumber, DataError> {
let mut factor_parts = factor.split('/');
let factor_num_str = factor_parts.next().unwrap_or("0").trim();
let factor_den_str = factor_parts.next().unwrap_or("1").trim();
if factor_parts.next().is_some() {
return Err(DataError::custom(
"the factor is not a valid scientific notation number",
));
}
let mut result = process_factor_part(factor_num_str, cons_map)?;
let factor_den_scientific = process_factor_part(factor_den_str, cons_map)?;
result.clean_num.extend(factor_den_scientific.clean_den);
result.clean_den.extend(factor_den_scientific.clean_num);
if factor_den_scientific.exactness == Exactness::Approximate {
result.exactness = Exactness::Approximate;
}
Ok(result)
}
pub(crate) fn extract_conversion_info<'data>(
base_unit: &str,
factor: &ScientificNumber,
offset: &ScientificNumber,
parser: &MeasureUnitParser,
) -> Result<ConversionInfo<'data>, DataError> {
let factor_fraction = convert_slices_to_fraction(&factor.clean_num, &factor.clean_den)?;
let offset_fraction = convert_slices_to_fraction(&offset.clean_num, &offset.clean_den)?;
let (factor_num, factor_den, factor_sign) = flatten_fraction(factor_fraction);
let (offset_num, offset_den, offset_sign) = flatten_fraction(offset_fraction);
let exactness = if factor.exactness == Exactness::Exact && offset.exactness == Exactness::Exact
{
Exactness::Exact
} else {
Exactness::Approximate
};
let base_unit = parser
.try_from_str(base_unit)
.map_err(|_| DataError::custom("the base unit is not valid"))?;
Ok(ConversionInfo {
basic_units: ZeroVec::from_iter(base_unit.contained_units),
factor_num: factor_num.into(),
factor_den: factor_den.into(),
factor_sign,
offset_num: offset_num.into(),
offset_den: offset_den.into(),
offset_sign,
exactness,
})
}
pub(crate) fn process_constants<'a>(
constants: &'a BTreeMap<String, Constant>,
) -> Result<BTreeMap<&'a str, ScientificNumber>, DataError> {
let mut constants_with_non_scientific =
VecDeque::<(&'a str, GeneralNonScientificNumber)>::new();
let mut clean_constants_map = BTreeMap::<&str, GeneralNonScientificNumber>::new();
for (cons_name, cons_value) in constants {
let (num, den) = split_unit_term(&cons_value.value)?;
let exactness = match cons_value.status.as_deref() {
Some("approximate") => Exactness::Approximate,
_ => Exactness::Exact,
};
let constant = GeneralNonScientificNumber::new(&num, &den, exactness);
if constant.is_free_of_non_scientific() {
clean_constants_map.insert(cons_name, constant);
} else {
constants_with_non_scientific.push_back((cons_name, constant));
}
}
let mut no_update_count = 0;
while !constants_with_non_scientific.is_empty() {
let mut updated = false;
let (constant_key, mut non_scientific_constant) = constants_with_non_scientific
.pop_front()
.ok_or(DataError::custom(
"non scientific queue error: an element must exist",
))?;
for _ in 0..non_scientific_constant.non_scientific_num.len() {
if let Some(num) = non_scientific_constant.non_scientific_num.pop_front() {
if let Some(clean_constant) = clean_constants_map.get(num.as_str()) {
non_scientific_constant
.clean_num
.extend(clean_constant.clean_num.clone());
non_scientific_constant
.clean_den
.extend(clean_constant.clean_den.clone());
updated = true;
} else {
non_scientific_constant.non_scientific_num.push_back(num);
}
}
}
for _ in 0..non_scientific_constant.non_scientific_den.len() {
if let Some(den) = non_scientific_constant.non_scientific_den.pop_front() {
if let Some(clean_constant) = clean_constants_map.get(den.as_str()) {
non_scientific_constant
.clean_num
.extend(clean_constant.clean_den.clone());
non_scientific_constant
.clean_den
.extend(clean_constant.clean_num.clone());
updated = true;
} else {
non_scientific_constant.non_scientific_den.push_back(den);
}
}
}
if non_scientific_constant.is_free_of_non_scientific() {
clean_constants_map.insert(constant_key, non_scientific_constant);
} else {
constants_with_non_scientific.push_back((constant_key, non_scientific_constant));
}
no_update_count = if !updated { no_update_count + 1 } else { 0 };
if no_update_count > constants_with_non_scientific.len() {
return Err(DataError::custom(
"A loop was detected in the CLDR constants data!",
));
}
}
Ok(clean_constants_map
.into_iter()
.map(|(k, v)| {
(k, {
ScientificNumber {
clean_num: v.clean_num,
clean_den: v.clean_den,
exactness: v.exactness,
}
})
})
.collect())
}
pub(crate) fn contains_alphabetic_chars(s: &str) -> bool {
s.chars().any(char::is_alphabetic)
}
#[test]
fn test_contains_alphabetic_chars() {
let input = "1";
let expected = false;
let actual = contains_alphabetic_chars(input);
assert_eq!(expected, actual);
let input = "ft_to_m";
let expected = true;
let actual = contains_alphabetic_chars(input);
assert_eq!(expected, actual);
let input = "1E2";
let expected = true;
let actual = contains_alphabetic_chars(input);
assert_eq!(expected, actual);
let input = "1.5E-2";
let expected = true;
let actual = contains_alphabetic_chars(input);
assert_eq!(expected, actual);
}
pub(crate) fn is_scientific_number(s: &str) -> bool {
let mut parts = s.split('E');
let base = parts.next().unwrap_or("0");
let exponent = parts.next().unwrap_or("0");
if parts.next().is_some() {
return false;
}
!contains_alphabetic_chars(base) && !contains_alphabetic_chars(exponent)
}
pub(crate) fn flatten_fraction(fraction: IcuRatio) -> (Vec<u8>, Vec<u8>, Sign) {
let fraction = fraction.get_ratio();
let numer_bytes = fraction.numer().to_bytes_le().1;
let denom_bytes = fraction.denom().to_bytes_le().1;
let sign = match fraction.is_negative() {
true => Sign::Negative,
false => Sign::Positive,
};
(numer_bytes, denom_bytes, sign)
}
pub(crate) fn convert_slices_to_fraction(
numerator_strings: &[String],
denominator_strings: &[String],
) -> Result<IcuRatio, DataError> {
numerator_strings
.iter()
.try_fold(IcuRatio::one(), |result, num| {
IcuRatio::from_str(num.as_str())
.map_err(|_| {
DataError::custom("The numerator is not a valid scientific notation number")
})
.map(|num_fraction| result * num_fraction)
})
.and_then(|num_product| {
denominator_strings
.iter()
.try_fold(num_product, |result, den| {
IcuRatio::from_str(den.as_str())
.map_err(|_| {
DataError::custom(
"The denominator is not a valid scientific notation number",
)
})
.map(|den_fraction| result / den_fraction)
})
})
}
#[test]
fn test_convert_array_of_strings_to_fraction() {
use num_bigint::BigInt;
let numerator = vec!["1".to_string()];
let denominator = vec!["2".to_string()];
let expected = IcuRatio::from_big_ints(BigInt::from(1i32), BigInt::from(2i32));
let actual = convert_slices_to_fraction(&numerator, &denominator).unwrap();
assert_eq!(expected, actual);
let numerator = vec!["1".to_string(), "2".to_string()];
let denominator = vec!["3".to_string(), "1E2".to_string()];
let expected = IcuRatio::from_big_ints(BigInt::from(2i32), BigInt::from(300i32));
let actual = convert_slices_to_fraction(&numerator, &denominator).unwrap();
assert_eq!(expected, actual);
let numerator = vec!["1".to_string(), "2".to_string()];
let denominator = vec!["3".to_string(), "1E-2".to_string()];
let expected = IcuRatio::from_big_ints(BigInt::from(200i32), BigInt::from(3i32));
let actual = convert_slices_to_fraction(&numerator, &denominator).unwrap();
assert_eq!(expected, actual);
let numerator = vec!["1".to_string(), "2".to_string()];
let denominator = vec!["3".to_string(), "1E-2.5".to_string()];
let actual = convert_slices_to_fraction(&numerator, &denominator);
assert!(actual.is_err());
let numerator = vec!["1E2".to_string()];
let denominator = vec!["2".to_string()];
let expected = IcuRatio::from_big_ints(BigInt::from(50i32), BigInt::from(1i32));
let actual = convert_slices_to_fraction(&numerator, &denominator).unwrap();
assert_eq!(expected, actual);
let numerator = vec!["1E2".to_string(), "2".to_string()];
let denominator = vec!["3".to_string(), "1E2".to_string()];
let expected = IcuRatio::from_big_ints(BigInt::from(2i32), BigInt::from(3i32));
let actual = convert_slices_to_fraction(&numerator, &denominator).unwrap();
assert_eq!(expected, actual);
}
pub(crate) fn split_unit_term(
constant_string: &str,
) -> Result<(Vec<String>, Vec<String>), DataError> {
let split: Vec<&str> = constant_string.split('/').collect();
if split.len() > 2 {
return Err(DataError::custom("Invalid constant string"));
}
let process_string = |s: &str| -> Vec<String> {
if s.is_empty() {
vec!["1".to_string()]
} else {
s.split('*').map(|s| s.trim().to_string()).collect()
}
};
let numerator_values = process_string(split.first().unwrap_or(&"1"));
let denominator_values = process_string(split.get(1).unwrap_or(&"1"));
if numerator_values
.iter()
.any(|s| s.chars().any(char::is_whitespace))
|| denominator_values
.iter()
.any(|s| s.chars().any(char::is_whitespace))
{
return Err(DataError::custom(
"The constant string contains internal white spaces",
));
}
Ok((numerator_values, denominator_values))
}
#[test]
fn test_convert_constant_to_num_denom_strings() {
let input = "1/2";
let expected = (vec!["1".to_string()], vec!["2".to_string()]);
let actual = split_unit_term(input).unwrap();
assert_eq!(expected, actual);
let input = "1 * 2 / 3 * ft_to_m";
let expected = (
vec!["1".to_string(), "2".to_string()],
vec!["3".to_string(), "ft_to_m".to_string()],
);
let actual = split_unit_term(input).unwrap();
assert_eq!(expected, actual);
let input = "/2";
let expected = (vec!["1".to_string()], vec!["2".to_string()]);
let actual = split_unit_term(input).unwrap();
assert_eq!(expected, actual);
let input = "2";
let expected = (vec!["2".to_string()], vec!["1".to_string()]);
let actual = split_unit_term(input).unwrap();
assert_eq!(expected, actual);
let input = "2/";
let expected = (vec!["2".to_string()], vec!["1".to_string()]);
let actual = split_unit_term(input).unwrap();
assert_eq!(expected, actual);
let input = "1E2";
let expected = (vec!["1E2".to_string()], vec!["1".to_string()]);
let actual = split_unit_term(input).unwrap();
assert_eq!(expected, actual);
let input = "1 2 * 3";
let actual = split_unit_term(input);
assert!(actual.is_err());
}