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 = "nolla";
const CARDINAL_WORDS: [&str; 20] = [
ZERO,
"en",
"dus",
"treis",
"quatter",
"tschintg",
"seis",
"set",
"otg",
"nov",
"diesch",
"endesch",
"dodesch",
"tredesch",
"quittordesch",
"quindesch",
"sedesch",
"dischset",
"dischdotg",
"dischnov",
];
const ORDINAL_WORDS: [&str; 21] = [
ZERO,
"amprem",
"sagond",
"terz",
"quart",
"tschintgavel",
"seisavel",
"settavel",
"otgavel",
"novavel",
"dieschavel",
"endeschavel",
"dodeschavel",
"tredeschavel",
"quittordeschavel",
"quindeschavel",
"sedeschavel",
"dischsettavel",
"dischdotgavel",
"dischnovavel",
"vantgavel",
];
fn str_tens(tens: usize) -> Option<&'static str> {
match tens {
2 => Some("vantga"),
3 => Some("trenta"),
4 => Some("curanta"),
5 => Some("tschuncanta"),
6 => Some("sessanta"),
7 => Some("settanta"),
8 => Some("otganta"),
9 => Some("novanta"),
_ => None,
}
}
const EXPONENT_PREFIXES: [&str; 11] = [
ZERO, "m", "b", "tr", "quadr", "quint", "sest", "sett", "ott", "nov", "dec",
];
const MINUS_PREFIX_WORD: &str = "minus ";
const FLOAT_INFIX_WORD: &str = " comma ";
fn phonetic_contraction(string: &str) -> String {
string
.replace("aen", "egn") .replace("aotg", "otg") .replace("tga_", "tg") .replace('_', "")
}
fn adapt_hundred(string: &str) -> String {
string
.replace("dustschent", "dutschent")
.replace("treistschent", "tretschent")
.replace("aendesch", "adendesch")
.replace("aen", "adegn")
.replace("aotg", "adotg")
}
fn adapt_thousand(string: &str) -> String {
string
.replace("dusmella", "dumella")
.replace("treismella", "tremella")
.replace("aendesch", "adendesch")
.replace("aaen", "adegn")
.replace("aaotg", "adotg")
.replace("aa", "a")
}
fn adapt_milliarda(string: &str) -> String {
let string = format!(" {} ", string);
string
.replace(" en milliarda ", " ena milliarda ")
.replace(" a endesch", " ad endesch")
.replace(" a en", " ad egn")
.replace(" a otg", " ad otg")
}
fn exponent_length_to_string(exponent_length: usize) -> Result<String> {
let prefix = EXPONENT_PREFIXES
.get(exponent_length / 6)
.ok_or_else(|| N2WError::Index(format!("list index out of range: {}", exponent_length / 6)))?;
if exponent_length % 6 == 0 {
Ok(format!("{}illiun", prefix))
} else {
Ok(format!("{}illiarda", 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()
}
}
fn drop_last_char(s: &str) -> String {
let mut chars: Vec<char> = s.chars().collect();
chars.pop();
chars.into_iter().collect()
}
fn attribute_error(attr: &str) -> N2WError {
N2WError::Attribute(format!(
"'Num2Word_RM_SURMIRAN' object has no attribute '{}'",
attr
))
}
pub struct LangRmSurmiran;
impl LangRmSurmiran {
pub fn new() -> Self {
LangRmSurmiran
}
fn tens_to_cardinal(&self, number: u64) -> String {
let tens = (number / 10) as usize;
let units = (number % 10) as usize;
let prefix = match str_tens(tens) {
Some(p) => p.to_string(),
None => format!("{}anta", drop_last_char(CARDINAL_WORDS[tens])),
};
let postfix = empty_if_zero(CARDINAL_WORDS[units]);
phonetic_contraction(&format!("{}{}", prefix, postfix))
}
fn hundreds_to_cardinal(&self, number: u64) -> Result<String> {
let hundreds = (number / 100) as usize;
let tens = number % 100;
let mut prefix = "tschent".to_string();
if hundreds != 1 {
prefix = format!("{}tschent", CARDINAL_WORDS[hundreds]);
}
let postfix = omitt_if_zero(&self.cardinal(&BigInt::from(tens))?);
let infix = if (tens > 0 && tens <= 13) || matches!(tens, 15 | 16 | 20 | 30) {
"a"
} else {
""
};
Ok(adapt_hundred(&format!("{}{}{}", prefix, infix, postfix)))
}
fn thousands_to_cardinal(&self, number: u64) -> Result<String> {
let thousands = number / 1000;
let hundreds = number % 1000;
let mut prefix = "mella".to_string();
if thousands != 1 {
prefix = format!("{}mella", self.cardinal(&BigInt::from(thousands))?);
}
let postfix = omitt_if_zero(&self.cardinal(&BigInt::from(hundreds))?);
let infix = if hundreds <= 100 && !postfix.is_empty() {
"a"
} 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 { 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'] {
prefix = "en ".to_string();
} else {
let m: String = multiplier.iter().collect();
prefix = self.cardinal(&parse_int(&m)?)?;
infix = format!(" {}s", infix);
}
let postfix;
if exponent.is_empty() || exponent.iter().any(|c| *c != '0') {
let exponent_str: String = exponent.iter().collect();
postfix = self.cardinal(&parse_int(&exponent_str)?)?;
if exponent_str.starts_with("000") {
infix.push_str(" a ");
} else {
infix.push(' ');
}
} else {
postfix = 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) {
let n = number.to_usize().expect("bounded by 20");
Ok(CARDINAL_WORDS[n].to_string())
} else if number < &BigInt::from(100) {
Ok(self.tens_to_cardinal(number.to_u64().expect("bounded by 100")))
} else if number < &BigInt::from(1000) {
self.hundreds_to_cardinal(number.to_u64().expect("bounded by 1000"))
} else if number < &BigInt::from(1_000_000) {
self.thousands_to_cardinal(number.to_u64().expect("bounded by 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) {
let n = number.to_usize().expect("bounded by 20");
return Ok(ORDINAL_WORDS[n].to_string());
}
let cardinal = self.cardinal(number)?;
let suffix = if cardinal.chars().next_back() == Some('a') {
"vel"
} else if cardinal.ends_with("set") {
"tavel"
} else {
"avel"
};
Ok(format!("{}{}", cardinal, suffix))
}
fn float_to_words(&self, value: f64, precision: u32) -> Result<String> {
let s = format!("{:.*}", precision as usize, value);
let mut parts = s.splitn(2, '.');
let int_str = parts.next().unwrap_or("");
let prefix = self.cardinal(&parse_int(int_str)?)?;
if !float_repr_has_point(value) {
return Err(N2WError::Index("list index out of range".to_string()));
}
let float_part = parts
.next()
.ok_or_else(|| N2WError::Index("list index out of range".to_string()))?;
let mut words: Vec<String> = Vec::new();
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", c))
})?;
words.push(self.cardinal(&BigInt::from(d))?);
}
Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, words.join(" ")))
}
fn cardinal_decimal(&self, value: &BigDecimal) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.cardinal_decimal(&value.abs())?
));
}
if value < &BigDecimal::from(1_000_000i64) {
return Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
));
}
let s = crate::strnum::python_decimal_str(value);
self.big_number_to_cardinal_str(&s)
}
fn big_number_to_cardinal_str(&self, s: &str) -> Result<String> {
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 { 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'] {
prefix = "en ".to_string();
} else {
let m: String = multiplier.iter().collect();
prefix = self.cardinal(&parse_int(&m)?)?;
infix = format!(" {}s", infix);
}
let postfix;
if exponent.is_empty() || exponent.iter().any(|c| *c != '0') {
let exponent_str: String = exponent.iter().collect();
postfix = self.cardinal(&parse_int(&exponent_str)?)?;
if exponent_str.starts_with("000") {
infix.push_str(" a ");
} else {
infix.push(' ');
}
} else {
postfix = 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 s = format!("{:.*}", precision as usize, f);
let mut split = s.splitn(2, '.');
let int_str = split.next().unwrap_or("");
let prefix = self.ordinal(&parse_int(int_str)?)?;
if !float_repr_has_point(f) {
return Err(N2WError::Index("list index out of range".to_string()));
}
let float_part = split
.next()
.ok_or_else(|| N2WError::Index("list index out of range".to_string()))?;
let mut words: Vec<String> = Vec::new();
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", c))
})?;
words.push(self.cardinal(&BigInt::from(d))?);
}
return Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, words.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 suffix = if cardinal.chars().next_back() == Some('a') {
"vel"
} else if cardinal.ends_with("set") {
"tavel"
} else {
"avel"
};
Ok(format!("{}{}", cardinal, suffix))
}
fn decimal_ordinal(&self, value: &BigDecimal) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.decimal_ordinal(&value.abs())?
));
}
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 words: Vec<String> = Vec::new();
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", c))
})?;
words.push(self.cardinal(&BigInt::from(d))?);
}
return Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, words.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.cardinal_decimal(value)?;
let suffix = if cardinal.chars().next_back() == Some('a') {
"vel"
} else if cardinal.ends_with("set") {
"tavel"
} else {
"avel"
};
Ok(format!("{}{}", cardinal, suffix))
}
}
fn float_repr_has_point(f: f64) -> bool {
f.is_finite() && (f == 0.0 || (f.abs() >= 1e-4 && f.abs() < 1e16))
}
fn parse_int(s: &str) -> Result<BigInt> {
s.parse::<BigInt>()
.map_err(|_| N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s)))
}
impl Default for LangRmSurmiran {
fn default() -> Self {
Self::new()
}
}
impl Lang for LangRmSurmiran {
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_SURMIRAN",
)))
}
fn to_cheque(&self, _val: &bigdecimal::BigDecimal, _currency: &str) -> Result<String> {
Err(N2WError::Attribute(format!(
"'{}' object has no attribute 'to_cheque'",
"Num2Word_RM_SURMIRAN",
)))
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.cardinal(value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.ordinal(value)
}
fn to_ordinal_num(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error("to_ordinal_num"))
}
fn to_year(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error("to_year"))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => {
if *value < 0.0 {
Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.float_to_words(-*value, *precision)?
))
} else {
self.float_to_words(*value, *precision)
}
}
FloatValue::Decimal { value, .. } => self.cardinal_decimal(value),
}
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => {
if *value < 0.0 {
Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.float_to_words(-*value, *precision)?
))
} else {
self.float_to_words(*value, *precision)
}
}
FloatValue::Decimal { value, .. } => self.cardinal_decimal(value),
}
}
fn neg_zero_decimal(&self, to: &str) -> Option<Result<String>> {
Some(match to {
"cardinal" | "ordinal" => Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
)),
"year" => Err(attribute_error("to_year")),
"ordinal_num" => Err(attribute_error("to_ordinal_num")),
_ => Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".to_string(),
)),
})
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value, precision } => self.float_ordinal(*value, *precision),
FloatValue::Decimal { value, .. } => self.decimal_ordinal(value),
}
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, _repr_str: &str) -> Result<String> {
Err(attribute_error("to_ordinal_num"))
}
fn year_float_entry(&self, _value: &FloatValue) -> Result<String> {
Err(attribute_error("to_year"))
}
fn str_to_number(&self, _s: &str) -> Result<crate::strnum::ParsedNumber> {
Err(attribute_error("str_to_number"))
}
fn to_fraction(&self, _numerator: &BigInt, _denominator: &BigInt) -> Result<String> {
Err(attribute_error("to_fraction"))
}
}