use crate::base::{Lang, N2WError, Result};
use crate::floatpath::FloatValue;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{Signed, ToPrimitive};
const ZERO: &str = "nulla";
const MINUS_PREFIX_WORD: &str = "minus ";
const CARDINAL_WORDS: [&str; 20] = [
ZERO,
"in",
"dus",
"trais",
"quatter",
"tschintg",
"sis",
"set",
"otg",
"nov",
"diesch",
"indesch",
"dudesch",
"tredesch",
"quattordesch",
"quindesch",
"sedesch",
"deschset",
"deschdotg",
"deschnov",
];
const ORDINAL_WORDS: [&str; 21] = [
ZERO,
"emprim",
"segund",
"terz",
"quart",
"tschintgavel",
"sisavel",
"settavel",
"otgavel",
"novavel",
"dieschavel",
"indeschavel",
"dudeschavel",
"tredeschavel",
"quattordeschavel",
"quindeschavel",
"sedeschavel",
"deschsettavel",
"deschdotgavel",
"deschnovavel",
"ventgavel",
];
const STR_TENS: [&str; 10] = [
"",
"",
"ventga",
"trenta",
"quaranta",
"tschuncanta",
"sessanta",
"settanta",
"otganta",
"novanta",
];
const EXPONENT_PREFIXES: [&str; 11] = [
ZERO, "m", "b", "tr", "quadr", "quint", "sest", "sett", "ott", "nov", "dec",
];
fn phonetic_contraction(string: &str) -> String {
string
.replace("ain", "in") .replace("aotg", "otg") .replace("tga_", "tg") .replace('_', "")
}
fn adapt_hundred(string: &str) -> String {
string
.replace("dustschient", "duatschient")
.replace("traistschient", "traitschient")
.replace("ein", "edin")
.replace("eotg", "edotg")
}
fn adapt_thousand(string: &str) -> String {
string
.replace("dusmilli", "duamilli")
.replace("traismilli", "traimilli")
.replace("ein", "edin")
.replace("eotg", "edotg")
}
fn adapt_milliarda(string: &str) -> String {
let padded = format!(" {} ", string);
padded
.replace(" in milliarda ", " ina milliarda ")
.replace("dus milliardas", "duas milliardas")
.replace(" e in", " ed in")
.replace(" e otg", " ed otg")
}
fn exponent_length_to_string(exponent_length: usize) -> Result<String> {
let idx = exponent_length / 6;
let prefix = EXPONENT_PREFIXES
.get(idx)
.ok_or_else(|| N2WError::Index("list index out of range".to_string()))?;
if exponent_length % 6 == 0 {
Ok(format!("{}illiun", prefix))
} else {
Ok(format!("{}illiarda", prefix))
}
}
fn omitt_if_zero(number_to_string: &str) -> &str {
if number_to_string == ZERO {
""
} else {
number_to_string
}
}
fn empty_if_zero(number_to_string: &str) -> &str {
if number_to_string == ZERO {
"_"
} else {
number_to_string
}
}
fn attribute_error(msg: &str) -> N2WError {
N2WError::Attribute(msg.to_string())
}
fn value_error(msg: &str) -> N2WError {
N2WError::Value(msg.to_string())
}
fn float_repr_has_point(f: f64) -> bool {
f.is_finite() && (f == 0.0 || (f.abs() >= 1e-4 && f.abs() < 1e16))
}
fn float_trunc_int(f: f64) -> Result<BigInt> {
if f.is_nan() {
return Err(value_error("cannot convert float NaN to integer"));
}
num_traits::FromPrimitive::from_f64(f.trunc()).ok_or_else(|| {
N2WError::Overflow("cannot convert float infinity to integer".to_string())
})
}
pub struct LangRm;
impl LangRm {
pub fn new() -> Self {
LangRm
}
fn tens_to_cardinal(&self, number: u32) -> String {
let tens = (number / 10) as usize;
let units = (number % 10) as usize;
let prefix = if (2..=9).contains(&tens) {
STR_TENS[tens].to_string()
} else {
let base: String = {
let w = CARDINAL_WORDS[tens];
let n = w.chars().count();
w.chars().take(n.saturating_sub(1)).collect()
};
format!("{}anta", base)
};
let postfix = empty_if_zero(CARDINAL_WORDS[units]);
phonetic_contraction(&format!("{}{}", prefix, postfix))
}
fn hundreds_to_cardinal(&self, number: u32) -> Result<String> {
let hundreds = (number / 100) as usize;
let tens = number % 100;
let prefix = if hundreds != 1 {
format!("{}tschient", CARDINAL_WORDS[hundreds])
} else {
"tschient".to_string()
};
let inner = self.cardinal(&BigInt::from(tens))?;
let postfix = omitt_if_zero(&inner);
let infix = if (tens > 0 && tens <= 13) || matches!(tens, 15 | 16 | 20 | 30) {
"e"
} else {
""
};
Ok(adapt_hundred(&format!("{}{}{}", prefix, infix, postfix)))
}
fn thousands_to_cardinal(&self, number: u32) -> Result<String> {
let thousands = number / 1000;
let hundreds = number % 1000;
let prefix = if thousands != 1 {
format!("{}milli", self.cardinal(&BigInt::from(thousands))?)
} else {
"milli".to_string()
};
let inner = self.cardinal(&BigInt::from(hundreds))?;
let postfix = omitt_if_zero(&inner);
let infix = if hundreds <= 100 && !postfix.is_empty() {
"e"
} else {
""
};
Ok(adapt_thousand(&format!("{}{}{}", prefix, infix, postfix)))
}
fn big_number_to_cardinal(&self, number: &BigInt) -> Result<String> {
let digits: Vec<char> = number.to_string().chars().collect();
let length = digits.len();
if length >= 66 {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
let predigits = if length % 3 != 0 { length % 3 } else { 3 };
let multiplier: String = digits[..predigits].iter().collect();
let exponent: String = digits[predigits..].iter().collect();
let mut infix = exponent_length_to_string(exponent.len())?;
let prefix = if multiplier == "1" {
"in ".to_string()
} else {
let p = self.cardinal(
&multiplier
.parse::<BigInt>()
.map_err(|_| value_error("invalid literal for int() with base 10"))?,
)?;
infix = format!(" {}s", infix);
p
};
let exponent_is_zero = !exponent.is_empty() && exponent.chars().all(|c| c == '0');
let postfix = if !exponent_is_zero {
let p = self.cardinal(
&exponent
.parse::<BigInt>()
.map_err(|_| value_error("invalid literal for int() with base 10: ''"))?,
)?;
if exponent.starts_with("000") {
infix.push_str(" e ");
} else {
infix.push(' ');
}
p
} else {
String::new()
};
Ok(adapt_milliarda(&format!("{}{}{}", prefix, infix, postfix))
.trim()
.to_string())
}
fn cardinal(&self, number: &BigInt) -> Result<String> {
if number.is_negative() {
return Ok(format!("{}{}", MINUS_PREFIX_WORD, self.cardinal(&-number)?));
}
if number < &BigInt::from(20) {
Ok(CARDINAL_WORDS[number.to_usize().expect("0 <= number < 20")].to_string())
} else if number < &BigInt::from(100) {
Ok(self.tens_to_cardinal(number.to_u32().expect("20 <= number < 100")))
} else if number < &BigInt::from(1000) {
self.hundreds_to_cardinal(number.to_u32().expect("100 <= number < 1000"))
} else if number < &BigInt::from(1_000_000) {
self.thousands_to_cardinal(number.to_u32().expect("1000 <= number < 10**6"))
} else {
self.big_number_to_cardinal(number)
}
}
fn ordinal(&self, number: &BigInt) -> Result<String> {
if number.is_negative() {
return Ok(format!("{}{}", MINUS_PREFIX_WORD, self.ordinal(&-number)?));
}
if number <= &BigInt::from(20) {
return Ok(ORDINAL_WORDS[number.to_usize().expect("0 <= number <= 20")].to_string());
}
let cardinal = self.cardinal(number)?;
let suffix = if cardinal.chars().last() == Some('a') {
"vel"
} else if cardinal.ends_with("set") {
"tavel"
} else {
"avel"
};
Ok(format!("{}{}", cardinal, suffix))
}
fn float_cardinal(&self, f: f64, precision: u32) -> Result<String> {
if f < 0.0 {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.float_cardinal(-f, precision)?
));
}
let prefix = self.cardinal(&float_trunc_int(f)?)?;
if !float_repr_has_point(f) {
return Err(N2WError::Index("list index out of range".to_string()));
}
let formatted = format!("{:.*}", precision as usize, f);
let float_part = formatted
.split_once('.')
.map(|(_, frac)| frac)
.unwrap_or("");
let mut parts = Vec::with_capacity(float_part.len());
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| {
value_error(&format!("invalid literal for int() with base 10: '{}'", c))
})?;
parts.push(self.cardinal(&BigInt::from(d))?);
}
Ok(format!("{} comma {}", prefix, parts.join(" ")))
}
fn decimal_cardinal(&self, value: &BigDecimal, precision: u32) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.decimal_cardinal(&value.abs(), precision)?
));
}
if value < &BigDecimal::from(1_000_000i64) {
return Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
));
}
self.big_number_decimal(value, precision)
}
fn decimal_str(&self, value: &BigDecimal, precision: u32) -> String {
let (mantissa, _scale) = value.with_scale(precision as i64).as_bigint_and_exponent();
let digits = mantissa.abs().to_string();
if precision == 0 {
return digits;
}
let p = precision as usize;
let padded = if digits.len() <= p {
format!("{}{}", "0".repeat(p + 1 - digits.len()), digits)
} else {
digits
};
let split = padded.len() - p;
format!("{}.{}", &padded[..split], &padded[split..])
}
fn big_number_decimal(&self, value: &BigDecimal, precision: u32) -> Result<String> {
let s = self.decimal_str(value, precision);
let digits: Vec<char> = s.chars().collect();
let length = digits.len();
if length >= 66 {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
let predigits = if length % 3 != 0 { length % 3 } else { 3 };
let multiplier: String = digits[..predigits].iter().collect();
let exponent: String = digits[predigits..].iter().collect();
let mut infix = exponent_length_to_string(exponent.len())?;
let prefix = if multiplier == "1" {
"in ".to_string()
} else {
let m = multiplier.parse::<BigInt>().map_err(|_| {
value_error(&format!(
"invalid literal for int() with base 10: '{}'",
multiplier
))
})?;
let p = self.cardinal(&m)?;
infix = format!(" {}s", infix);
p
};
let exponent_is_zero = !exponent.is_empty() && exponent.chars().all(|c| c == '0');
let postfix = if !exponent_is_zero {
let e = exponent.parse::<BigInt>().map_err(|_| {
value_error(&format!(
"invalid literal for int() with base 10: '{}'",
exponent
))
})?;
let p = self.cardinal(&e)?;
if exponent.starts_with("000") {
infix.push_str(" e ");
} else {
infix.push(' ');
}
p
} else {
String::new()
};
Ok(adapt_milliarda(&format!("{}{}{}", prefix, infix, postfix))
.trim()
.to_string())
}
fn float_ordinal(&self, f: f64, precision: u32) -> Result<String> {
if f < 0.0 {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.float_ordinal(-f, precision)?
));
}
if f.fract() != 0.0 {
let prefix = self.ordinal(&float_trunc_int(f)?)?;
if !float_repr_has_point(f) {
return Err(N2WError::Index("list index out of range".to_string()));
}
let formatted = format!("{:.*}", precision as usize, f);
let float_part = formatted
.split_once('.')
.map(|(_, frac)| frac)
.unwrap_or("");
let mut parts = Vec::with_capacity(float_part.len());
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| {
value_error(&format!("invalid literal for int() with base 10: '{}'", c))
})?;
parts.push(self.cardinal(&BigInt::from(d))?);
}
return Ok(format!("{} comma {}", prefix, parts.join(" ")));
}
if f <= 20.0 {
return Err(N2WError::Type(
"list indices must be integers or slices, not float".to_string(),
));
}
let cardinal = self.float_cardinal(f, precision)?;
let suffix = if cardinal.chars().last() == Some('a') {
"vel"
} else if cardinal.ends_with("set") {
"tavel"
} else {
"avel"
};
Ok(format!("{}{}", cardinal, suffix))
}
fn decimal_ordinal(&self, value: &BigDecimal, precision: u32) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.decimal_ordinal(&value.abs(), precision)?
));
}
if !value.is_integer() {
let pre = value.with_scale(0).as_bigint_and_exponent().0;
let prefix = self.ordinal(&pre)?;
let s = crate::strnum::python_decimal_str(value);
let float_part = match s.split_once('.') {
Some((_, frac)) => frac.to_string(),
None => return Err(N2WError::Index("list index out of range".to_string())),
};
let mut parts = Vec::with_capacity(float_part.len());
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| {
value_error(&format!("invalid literal for int() with base 10: '{}'", c))
})?;
parts.push(self.cardinal(&BigInt::from(d))?);
}
return Ok(format!("{} comma {}", prefix, parts.join(" ")));
}
if value <= &BigDecimal::from(20) {
return Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
));
}
let cardinal = self.decimal_cardinal(value, precision)?;
let suffix = if cardinal.chars().last() == Some('a') {
"vel"
} else if cardinal.ends_with("set") {
"tavel"
} else {
"avel"
};
Ok(format!("{}{}", cardinal, suffix))
}
}
impl Default for LangRm {
fn default() -> Self {
LangRm::new()
}
}
impl Lang for LangRm {
fn to_currency(
&self,
_val: &crate::currency::CurrencyValue,
_currency: &str,
_cents: bool,
_separator: Option<&str>,
_adjective: bool,
) -> Result<String> {
Err(N2WError::Attribute(format!(
"'{}' object has no attribute 'to_currency'",
"Num2Word_RM",
)))
}
fn to_cheque(&self, _val: &bigdecimal::BigDecimal, _currency: &str) -> Result<String> {
Err(N2WError::Attribute(format!(
"'{}' object has no attribute 'to_cheque'",
"Num2Word_RM",
)))
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.cardinal(value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.ordinal(value)
}
fn neg_zero_decimal(&self, to: &str) -> Option<Result<String>> {
match to {
"cardinal" => Some(Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
))),
_ => None,
}
}
fn to_ordinal_num(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM' object has no attribute 'to_ordinal_num'",
))
}
fn to_year(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM' object has no attribute 'to_year'",
))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => self.float_cardinal(*value, *precision),
FloatValue::Decimal { value, precision } => self.decimal_cardinal(value, *precision),
}
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => self.float_cardinal(*value, *precision),
FloatValue::Decimal { value, precision } => self.decimal_cardinal(value, *precision),
}
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value, precision } => self.float_ordinal(*value, *precision),
FloatValue::Decimal { value, precision } => self.decimal_ordinal(value, *precision),
}
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, _repr_str: &str) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM' object has no attribute 'to_ordinal_num'",
))
}
fn year_float_entry(&self, _value: &FloatValue) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM' object has no attribute 'to_year'",
))
}
fn str_to_number(&self, _s: &str) -> Result<crate::strnum::ParsedNumber> {
Err(attribute_error(
"'Num2Word_RM' object has no attribute 'str_to_number'",
))
}
fn to_fraction(&self, _numerator: &BigInt, _denominator: &BigInt) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM' object has no attribute 'to_fraction'",
))
}
}