use crate::base::{Lang, N2WError, Result};
use crate::floatpath::FloatValue;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{Signed, ToPrimitive};
const ZERO: &str = "nolla";
const MINUS_PREFIX_WORD: &str = "minus ";
const FLOAT_INFIX_WORD: &str = " comma ";
const CARDINAL_WORDS: [&str; 20] = [
ZERO,
"ün",
"duos",
"trais",
"quatter",
"tschinch",
"ses",
"set",
"och",
"nouv",
"desch",
"ündesch",
"dudesch",
"tredesch",
"quattordesch",
"quindesch",
"saidesch",
"dischset",
"dischdoch",
"dischnouv",
];
const ORDINAL_WORDS: [&str; 21] = [
ZERO,
"prüm",
"seguond",
"terz",
"quart",
"tschinchevel",
"sesevel",
"settevel",
"ochevel",
"nouvevel",
"deschevel",
"ündeschevel",
"dudeschevel",
"tredeschevel",
"quattordeschevel",
"quindeschevel",
"saideschevel",
"dischsettevel",
"dischdochevel",
"dischnouvevel",
"vainchevel",
];
const EXPONENT_PREFIXES: [&str; 11] = [
ZERO, "m", "b", "tr", "quadr", "quint", "sest", "sett", "och", "nov", "dec",
];
fn str_tens(tens: usize) -> Option<&'static str> {
match tens {
2 => Some("vaincha"), 3 => Some("trenta"),
4 => Some("quaraunta"),
5 => Some("tschinquaunta"),
6 => Some("sesaunta"),
7 => Some("settaunta"),
8 => Some("ochaunta"),
9 => Some("nonaunta"),
_ => None,
}
}
fn attribute_error(msg: &str) -> N2WError {
N2WError::Attribute(msg.to_string())
}
fn index_error(msg: &str) -> N2WError {
N2WError::Index(msg.to_string())
}
fn value_error(s: &str) -> N2WError {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s))
}
fn parse_int(s: &str) -> Result<BigInt> {
BigInt::parse_bytes(s.as_bytes(), 10).ok_or_else(|| value_error(s))
}
fn float_repr_has_point(f: f64) -> bool {
f.is_finite() && (f == 0.0 || (f.abs() >= 1e-4 && f.abs() < 1e16))
}
fn f64_trunc_to_bigint(f: f64) -> Result<BigInt> {
if f.is_nan() {
return Err(N2WError::Value(
"cannot convert float NaN to integer".to_string(),
));
}
num_traits::FromPrimitive::from_f64(f.trunc())
.ok_or_else(|| N2WError::Overflow("cannot convert float infinity to integer".to_string()))
}
fn drop_last_char(s: &str) -> String {
let chars: Vec<char> = s.chars().collect();
if chars.is_empty() {
return String::new();
}
chars[..chars.len() - 1].iter().collect()
}
fn cardinal_word(i: usize) -> Result<&'static str> {
CARDINAL_WORDS
.get(i)
.copied()
.ok_or_else(|| index_error("list index out of range"))
}
fn ordinal_word(i: usize) -> Result<&'static str> {
ORDINAL_WORDS
.get(i)
.copied()
.ok_or_else(|| index_error("list index out of range"))
}
fn as_index(n: &BigInt) -> Result<usize> {
n.to_usize()
.ok_or_else(|| index_error("list index out of range"))
}
fn phonetic_contraction(s: &str) -> String {
s.replace("aün", "ün")
.replace("aoch", "och")
.replace("vaincha_", "vainch")
.replace("_", "")
}
fn adapt_hundred(s: &str) -> String {
s.replace("duostschient", "duatschient")
.replace("traistschient", "trajatschient")
.replace("eün", "edün")
.replace("eoch", "edoch")
}
fn adapt_thousand(s: &str) -> String {
s.replace("duosmilli", "duamilli")
.replace("traismilli", "trajamilli")
.replace("eün", "edün")
.replace("eot", "edot")
}
fn adapt_milliard(s: &str) -> String {
let padded = format!(" {} ", s);
padded
.replace(" e ün", " ed ün")
.replace(" e och", " ed och")
}
fn exponent_length_to_string(exponent_length: usize) -> Result<String> {
let prefix = EXPONENT_PREFIXES
.get(exponent_length / 6)
.copied()
.ok_or_else(|| index_error("list index out of range"))?;
if exponent_length % 6 == 0 {
Ok(format!("{}illiun", prefix))
} else {
Ok(format!("{}illiard", prefix))
}
}
fn omitt_if_zero(number_to_string: &str) -> String {
if number_to_string == ZERO {
String::new()
} else {
number_to_string.to_string()
}
}
fn empty_if_zero(number_to_string: &str) -> String {
if number_to_string == ZERO {
"_".to_string()
} else {
number_to_string.to_string()
}
}
pub struct LangRmPuter;
impl Default for LangRmPuter {
fn default() -> Self {
Self::new()
}
}
impl LangRmPuter {
pub fn new() -> Self {
LangRmPuter
}
fn card(&self, number: &BigInt) -> Result<String> {
if number.is_negative() {
return Ok(format!("{}{}", MINUS_PREFIX_WORD, self.card(&-number)?));
}
if number < &BigInt::from(20) {
Ok(cardinal_word(as_index(number)?)?.to_string())
} else if number < &BigInt::from(100) {
self.tens_to_cardinal(number)
} else if number < &BigInt::from(1000) {
self.hundreds_to_cardinal(number)
} else if number < &BigInt::from(1_000_000) {
self.thousands_to_cardinal(number)
} else {
self.big_number_to_cardinal(number)
}
}
fn tens_to_cardinal(&self, number: &BigInt) -> Result<String> {
let tens = number.div_floor(&BigInt::from(10));
let units = number.mod_floor(&BigInt::from(10));
let tens_i = as_index(&tens)?;
let units_i = as_index(&units)?;
let prefix = match str_tens(tens_i) {
Some(p) => p.to_string(),
None => format!("{}aunta", drop_last_char(cardinal_word(tens_i)?)),
};
let postfix = empty_if_zero(cardinal_word(units_i)?);
Ok(phonetic_contraction(&format!("{}{}", prefix, postfix)))
}
fn hundreds_to_cardinal(&self, number: &BigInt) -> Result<String> {
let hundreds = number.div_floor(&BigInt::from(100));
let tens = number.mod_floor(&BigInt::from(100));
let mut prefix = "tschient".to_string();
if hundreds != BigInt::from(1) {
prefix = format!("{}tschient", cardinal_word(as_index(&hundreds)?)?);
}
let postfix = omitt_if_zero(&self.card(&tens)?);
let mut infix = "";
let t = as_index(&tens)?;
if (t > 0 && t <= 13) || matches!(t, 15 | 16 | 20 | 30) {
infix = "e";
}
Ok(adapt_hundred(&format!("{}{}{}", prefix, infix, postfix)))
}
fn thousands_to_cardinal(&self, number: &BigInt) -> Result<String> {
let thousands = number.div_floor(&BigInt::from(1000));
let hundreds = number.mod_floor(&BigInt::from(1000));
let mut prefix = "milli".to_string();
if thousands != BigInt::from(1) {
prefix = format!("{}milli", self.card(&thousands)?);
}
let postfix = omitt_if_zero(&self.card(&hundreds)?);
let mut infix = "";
if hundreds <= BigInt::from(100) && !postfix.is_empty() {
infix = "e";
}
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 { 3 } else { length % 3 };
let multiplier: Vec<char> = digits[..predigits].to_vec();
let exponent: Vec<char> = digits[predigits..].to_vec();
let mut infix = exponent_length_to_string(exponent.len())?;
let prefix = if multiplier == vec!['1'] {
"ün ".to_string()
} else {
let m: String = multiplier.iter().collect();
let p = self.card(&parse_int(&m)?)?;
infix = format!(" {}s", infix);
p
};
let exponent_is_zero = !exponent.is_empty() && exponent.iter().all(|&c| c == '0');
let postfix = if !exponent_is_zero {
let exponent_str: String = exponent.iter().collect();
let p = self.card(&parse_int(&exponent_str)?)?;
if exponent_str.starts_with("000") {
infix.push_str(" e ");
} else {
infix.push(' ');
}
p
} else {
String::new()
};
Ok(adapt_milliard(&format!("{}{}{}", prefix, infix, postfix))
.trim()
.to_string())
}
fn float_to_words(&self, value: f64, precision: u32) -> Result<String> {
let negative = value < 0.0;
let a = value.abs();
let pre = f64_trunc_to_bigint(a)?;
let prefix = self.card(&pre)?;
if !float_repr_has_point(a) {
return Err(index_error("list index out of range"));
}
let formatted = format!("{:.*}", precision as usize, a);
let float_part = formatted.split_once('.').map(|(_, f)| f).unwrap_or("");
let mut parts: Vec<String> = Vec::with_capacity(float_part.len());
for ch in float_part.chars() {
let d = ch
.to_digit(10)
.ok_or_else(|| value_error(&ch.to_string()))?;
parts.push(self.card(&BigInt::from(d))?);
}
let postfix = parts.join(" ");
let body = format!("{}{}{}", prefix, FLOAT_INFIX_WORD, postfix);
if negative {
Ok(format!("{}{}", MINUS_PREFIX_WORD, body))
} else {
Ok(body)
}
}
fn decimal_to_cardinal(&self, value: &BigDecimal, precision: u32) -> Result<String> {
let negative = value.is_negative();
let a = value.abs();
if a < BigDecimal::from(1_000_000i64) {
return Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
));
}
if precision == 0 {
let int_part = a.with_scale(0).as_bigint_and_exponent().0;
let body = self.card(&int_part)?;
return Ok(if negative {
format!("{}{}", MINUS_PREFIX_WORD, body)
} else {
body
});
}
let (coeff, _exp) = a.with_scale(precision as i64).as_bigint_and_exponent();
let modulus = BigInt::from(10).pow(precision);
let int_str = (&coeff / &modulus).to_string();
let frac_raw = (&coeff % &modulus).to_string();
let frac_str = format!(
"{}{}",
"0".repeat((precision as usize).saturating_sub(frac_raw.len())),
frac_raw
);
let s = format!("{}.{}", int_str, frac_str);
let length = s.chars().count();
if length >= 66 {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
let predigits = if length % 3 == 0 { 3 } else { length % 3 };
let exponent: String = s.chars().skip(predigits).collect();
Err(value_error(&exponent))
}
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.to_ordinal(&f64_trunc_to_bigint(f)?)?;
if !float_repr_has_point(f) {
return Err(index_error("list index out of range"));
}
let formatted = format!("{:.*}", precision as usize, f);
let float_part = formatted.split_once('.').map(|(_, x)| x).unwrap_or("");
let mut parts: Vec<String> = Vec::with_capacity(float_part.len());
for ch in float_part.chars() {
let d = ch
.to_digit(10)
.ok_or_else(|| value_error(&ch.to_string()))?;
parts.push(self.card(&BigInt::from(d))?);
}
return Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, 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_to_words(f, precision)?;
let last = cardinal
.chars()
.next_back()
.ok_or_else(|| index_error("string index out of range"))?;
if last == 'a' {
Ok(format!("{}evel", drop_last_char(&cardinal)))
} else if cardinal.ends_with("set") {
Ok(format!("{}tevel", cardinal))
} else {
Ok(format!("{}evel", cardinal))
}
}
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.to_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(index_error("list index out of range")),
};
let mut parts: Vec<String> = Vec::with_capacity(float_part.len());
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| value_error(&c.to_string()))?;
parts.push(self.card(&BigInt::from(d))?);
}
return Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, 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_to_cardinal(value, precision)?;
let last = cardinal
.chars()
.next_back()
.ok_or_else(|| index_error("string index out of range"))?;
if last == 'a' {
Ok(format!("{}evel", drop_last_char(&cardinal)))
} else if cardinal.ends_with("set") {
Ok(format!("{}tevel", cardinal))
} else {
Ok(format!("{}evel", cardinal))
}
}
}
impl Lang for LangRmPuter {
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_PUTER",
)))
}
fn to_cheque(&self, _val: &bigdecimal::BigDecimal, _currency: &str) -> Result<String> {
Err(N2WError::Attribute(format!(
"'{}' object has no attribute 'to_cheque'",
"Num2Word_RM_PUTER",
)))
}
fn negword(&self) -> &str {
MINUS_PREFIX_WORD
}
fn pointword(&self) -> &str {
FLOAT_INFIX_WORD
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => self.float_to_words(*value, *precision),
FloatValue::Decimal { value, precision } => self.decimal_to_cardinal(value, *precision),
}
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.card(value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.to_ordinal(&-value)?
));
}
if value <= &BigInt::from(20) {
return Ok(ordinal_word(as_index(value)?)?.to_string());
}
let cardinal = self.card(value)?;
let last = cardinal
.chars()
.next_back()
.ok_or_else(|| index_error("string index out of range"))?;
if last == 'a' {
Ok(format!("{}evel", drop_last_char(&cardinal)))
} else if cardinal.ends_with("set") {
Ok(format!("{}tevel", cardinal))
} else {
Ok(format!("{}evel", cardinal))
}
}
fn to_ordinal_num(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM_PUTER' object has no attribute 'to_ordinal_num'",
))
}
fn to_year(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM_PUTER' object has no attribute 'to_year'",
))
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => self.float_to_words(*value, *precision),
FloatValue::Decimal { value, precision } => self.decimal_to_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_PUTER' object has no attribute 'to_ordinal_num'",
))
}
fn year_float_entry(&self, _value: &FloatValue) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM_PUTER' object has no attribute 'to_year'",
))
}
fn neg_zero_decimal(&self, to: &str) -> Option<Result<String>> {
match to {
"cardinal" | "ordinal" => Some(Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
))),
_ => None,
}
}
fn str_to_number(&self, _s: &str) -> Result<crate::strnum::ParsedNumber> {
Err(attribute_error(
"'Num2Word_RM_PUTER' object has no attribute 'str_to_number'",
))
}
fn to_fraction(&self, _numerator: &BigInt, _denominator: &BigInt) -> Result<String> {
Err(attribute_error(
"'Num2Word_RM_PUTER' object has no attribute 'to_fraction'",
))
}
}