#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluralCategory {
Zero,
One,
Two,
Few,
Many,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PluralOperands {
pub n: f64,
pub i: u64,
pub v: u32,
pub w: u32,
pub f: u64,
pub t: u64,
pub c: u32,
}
impl PluralOperands {
#[must_use]
pub const fn from_int(value: i64) -> Self {
let i = value.unsigned_abs();
PluralOperands {
n: i as f64,
i,
v: 0,
w: 0,
f: 0,
t: 0,
c: 0,
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
let s = s.trim();
let s = s.strip_prefix(['-', '+']).unwrap_or(s);
if let Some((mantissa, exp_str)) = s.split_once(['c', 'e', 'C', 'E']) {
let exp: u32 = exp_str.parse().ok()?;
if exp > 30 {
return None;
}
let mut buf = [0u8; 48];
let expanded = expand_compact(&mut buf, mantissa, exp as usize)?;
let mut ops = Self::parse_plain(expanded)?;
ops.c = exp;
return Some(ops);
}
Self::parse_plain(s)
}
fn parse_plain(s: &str) -> Option<Self> {
let (int_str, frac_str) = match s.split_once('.') {
Some((a, b)) => (a, b),
None => (s, ""),
};
if int_str.is_empty() || !int_str.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
if !frac_str.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let i: u64 = int_str.parse().ok()?;
let v = frac_str.len() as u32;
let f: u64 = if frac_str.is_empty() {
0
} else {
frac_str.parse().ok()?
};
let trimmed = frac_str.trim_end_matches('0');
let w = trimmed.len() as u32;
let t: u64 = if trimmed.is_empty() {
0
} else {
trimmed.parse().ok()?
};
let n: f64 = s.parse().ok()?;
Some(PluralOperands {
n,
i,
v,
w,
f,
t,
c: 0,
})
}
}
fn expand_compact<'a>(buf: &'a mut [u8], mantissa: &str, exp: usize) -> Option<&'a str> {
let (int_str, frac_str) = match mantissa.split_once('.') {
Some((a, b)) => (a, b),
None => (mantissa, ""),
};
if int_str.is_empty() || !int_str.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
if !frac_str.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let point = int_str.len() + exp; let mut pos = 0usize;
let mut idx = 0usize;
for &b in int_str.as_bytes().iter().chain(frac_str.as_bytes()) {
if idx == point {
*buf.get_mut(pos)? = b'.';
pos += 1;
}
*buf.get_mut(pos)? = b;
pos += 1;
idx += 1;
}
while idx < point {
*buf.get_mut(pos)? = b'0'; pos += 1;
idx += 1;
}
core::str::from_utf8(&buf[..pos]).ok()
}
#[doc(hidden)]
#[must_use]
pub fn in_set(x: f64, ranges: &[(f64, f64)]) -> bool {
x % 1.0 == 0.0 && ranges.iter().any(|&(a, b)| x >= a && x <= b)
}
#[must_use]
pub fn plural_category(lang: &str, operands: &PluralOperands) -> PluralCategory {
select(
operands,
lang,
crate::unicode::generated::plurals::plural_category,
)
}
#[must_use]
pub fn ordinal_category(lang: &str, operands: &PluralOperands) -> PluralCategory {
select(
operands,
lang,
crate::unicode::generated::plurals::ordinal_category,
)
}
fn select(
operands: &PluralOperands,
lang: &str,
lookup: fn(&str, &PluralOperands) -> Option<PluralCategory>,
) -> PluralCategory {
let mut buf = [0u8; 40];
let bytes = lang.as_bytes();
let len = bytes.len().min(buf.len());
for k in 0..len {
let b = bytes[k].to_ascii_lowercase();
buf[k] = if b == b'_' { b'-' } else { b };
}
let norm = core::str::from_utf8(&buf[..len]).unwrap_or("");
let mut end = norm.len();
loop {
if let Some(cat) = lookup(&norm[..end], operands) {
return cat;
}
match norm[..end].rfind('-') {
Some(i) => end = i,
None => return PluralCategory::Other,
}
}
}