use crate::base::{set_low_numwords, set_mid_numwords, Cards, Lang, N2WError, Result};
use crate::currency::{parse_currency_parts, prefix_currency, CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const ORDINALS: [(&str, &str); 16] = [
("ένα", "πρώτος"),
("δύο", "δεύτερος"),
("τρία", "τρίτος"),
("τέσσερα", "τέταρτος"),
("πέντε", "πέμπτος"),
("έξι", "έκτος"),
("επτά", "έβδομος"),
("οκτώ", "όγδοος"),
("εννέα", "έννατος"),
("δέκα", "δέκατος"),
("έντεκα", "ενδέκατος"),
("δώδεκα", "δωδέκατος"),
("είκοσι", "εικοστός"),
("εκατό", "εκατοστός"),
("χίλια", "χιλιοστός"),
("εκατομμύριο", "εκατομμυριοστός"),
];
const LOW_NUMWORDS: [&str; 21] = [
"είκοσι",
"δεκαεννέα",
"δεκαοκτώ",
"δεκαεπτά",
"δεκαέξι",
"δεκαπέντε",
"δεκατέσσερα",
"δεκατρία",
"δώδεκα",
"έντεκα",
"δέκα",
"εννέα",
"οκτώ",
"επτά",
"έξι",
"πέντε",
"τέσσερα",
"τρία",
"δύο",
"ένα",
"μηδέν",
];
const MID_NUMWORDS: [(i64, &str); 12] = [
(1_000_000_000_000, "τρισεκατομμύριο"),
(1_000_000_000, "δισεκατομμύριο"),
(1_000_000, "εκατομμύριο"),
(1000, "χίλια"),
(100, "εκατό"),
(90, "ενενήντα"),
(80, "ογδόντα"),
(70, "εβδομήντα"),
(60, "εξήντα"),
(50, "πενήντα"),
(40, "σαράντα"),
(30, "τριάντα"),
];
const HUNDREDS: [(i64, &str); 8] = [
(2, "διακόσια"),
(3, "τριακόσια"),
(4, "τετρακόσια"),
(5, "πεντακόσια"),
(6, "εξακόσια"),
(7, "επτακόσια"),
(8, "οκτακόσια"),
(9, "εννιακόσια"),
];
const HUNDRED_THOUSANDS: [(i64, &str); 8] = [
(200, "διακόσιες χιλιάδες"),
(300, "τριακόσιες χιλιάδες"),
(400, "τετρακόσιες χιλιάδες"),
(500, "πεντακόσιες χιλιάδες"),
(600, "εξακόσιες χιλιάδες"),
(700, "επτακόσιες χιλιάδες"),
(800, "οκτακόσιες χιλιάδες"),
(900, "εννιακόσιες χιλιάδες"),
];
const FEMININE_REPLACEMENTS: [(&str, &str); 9] = [
("ένα", "μία"),
("διακόσια", "διακόσιες"),
("τριακόσια", "τριακόσιες"),
("τετρακόσια", "τετρακόσιες"),
("πεντακόσια", "πεντακόσιες"),
("εξακόσια", "εξακόσιες"),
("επτακόσια", "επτακόσιες"),
("οκτακόσια", "οκτακόσιες"),
("εννιακόσια", "εννιακόσιες"),
];
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"EUR",
CurrencyForms::new(&["ευρώ", "ευρώ"], &["λεπτό", "λεπτά"]),
);
m.insert(
"USD",
CurrencyForms::new(&["δολάριο", "δολάρια"], &["σεντ", "σεντς"]),
);
m.insert(
"GBP",
CurrencyForms::new(&["λίρα", "λίρες"], &["πέννα", "πένες"]),
);
m
}
fn build_currency_adjectives() -> HashMap<&'static str, &'static str> {
[
("AUD", "Australian"),
("BYN", "Belarusian"),
("CAD", "Canadian"),
("EEK", "Estonian"),
("USD", "US"),
("RUB", "Russian"),
("NOK", "Norwegian"),
("MXN", "Mexican"),
("RON", "Romanian"),
("INR", "Indian"),
("HUF", "Hungarian"),
("ISK", "íslenskar"),
("UZS", "Uzbekistan"),
("SAR", "Saudi"),
("JPY", "Japanese"),
("KRW", "Korean"),
]
.into_iter()
.collect()
}
fn strip_last_char(s: &str) -> String {
let mut chars: Vec<char> = s.chars().collect();
chars.pop();
chars.into_iter().collect()
}
fn is(n: &BigInt, k: i64) -> bool {
*n == BigInt::from(k)
}
pub struct LangEl {
cards: Cards,
maxval: BigInt,
ordinals: HashMap<&'static str, &'static str>,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl Default for LangEl {
fn default() -> Self {
Self::new()
}
}
impl LangEl {
pub fn new() -> Self {
let mut cards = Cards::new();
set_mid_numwords(&mut cards, &MID_NUMWORDS);
set_low_numwords(&mut cards, &LOW_NUMWORDS);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let ordinals: HashMap<&'static str, &'static str> = ORDINALS.into_iter().collect();
LangEl {
cards,
maxval,
ordinals,
exclude_title: vec!["και".into(), "κόμμα".into(), "μείον".into()],
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
}
}
fn pluralize_forms(&self, is_one: bool, forms: &[String]) -> Result<String> {
let form = if is_one { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
}
impl Lang for LangEl {
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
" και"
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"μείον "
}
fn pointword(&self) -> &str {
"κόμμα"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
let (ctext, cnum) = l;
let (ntext, nnum) = r;
let c100 = BigInt::from(100);
let c1000 = BigInt::from(1000);
let mega = BigInt::from(1_000_000i64);
let giga = BigInt::from(1_000_000_000i64);
let tera = BigInt::from(1_000_000_000_000i64);
if cnum.is_one() {
if *nnum == c100 {
return ("εκατό".to_string(), c100);
} else if *nnum == c1000 {
return ("χίλια".to_string(), c1000);
} else if *nnum >= mega {
return (format!("ένα {}", ntext), cnum * nnum);
} else {
return (ntext.to_string(), nnum.clone());
}
}
if nnum > cnum {
if *nnum == c100 {
for (k, w) in HUNDREDS {
if is(cnum, k) {
return (w.to_string(), BigInt::from(k * 100));
}
}
unreachable!("EL merge: nnum == 100 with cnum outside 2..=9");
} else if *nnum == c1000 {
if cnum.is_one() {
if ctext.contains("ένα") {
return (
format!("{} χιλιάδες", ctext.replace("ένα", "μία")),
cnum * nnum,
);
} else {
return ("χίλια".to_string(), c1000);
}
} else if is(cnum, 2) {
return ("δύο χιλιάδες".to_string(), BigInt::from(2000));
} else if is(cnum, 3) {
return ("τρεις χιλιάδες".to_string(), BigInt::from(3000));
} else if is(cnum, 4) {
return ("τέσσερις χιλιάδες".to_string(), BigInt::from(4000));
}
for (k, w) in HUNDRED_THOUSANDS {
if is(cnum, k) {
return (w.to_string(), BigInt::from(k * 1000));
}
}
let mut ctext_new = ctext.to_string();
for (from, to) in FEMININE_REPLACEMENTS {
ctext_new = ctext_new.replace(from, to);
}
(format!("{} χιλιάδες", ctext_new), cnum * nnum)
} else if *nnum == mega {
if cnum.is_one() {
return ("ένα εκατομμύριο".to_string(), mega);
} else if is(cnum, 2) {
return ("δύο εκατομμύρια".to_string(), BigInt::from(2_000_000i64));
} else if is(cnum, 3) {
return ("τρία εκατομμύρια".to_string(), BigInt::from(3_000_000i64));
} else if is(cnum, 4) {
return (
"τέσσερα εκατομμύρια".to_string(),
BigInt::from(4_000_000i64),
);
} else {
return (format!("{} εκατομμύρια", ctext), cnum * nnum);
}
} else if *nnum == giga {
if cnum.is_one() {
return ("ένα δισεκατομμύριο".to_string(), giga);
} else {
return (format!("{} δισεκατομμύρια", ctext), cnum * nnum);
}
} else if *nnum == tera {
if cnum.is_one() {
return ("ένα τρισεκατομμύριο".to_string(), tera);
} else {
return (format!("{} τρισεκατομμύρια", ctext), cnum * nnum);
}
} else if *nnum >= mega {
if cnum.is_one() {
return (format!("ένα {}", ntext), cnum * nnum);
} else {
return (format!("{} {}", ctext, ntext), cnum * nnum);
}
} else {
(format!("{} {}", ctext, ntext), cnum * nnum)
}
} else {
(format!("{} {}", ctext, ntext), cnum + nnum)
}
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let word = self.to_cardinal(value)?;
if let Some(o) = self.ordinals.get(word.as_str()) {
return Ok(o.to_string());
}
if word == "δεκατέσσερα" {
return Ok("δεκατέταρτος".to_string());
} else if word == "δεκατρία" {
return Ok("δεκατρίτος".to_string());
} else if word == "ένα εκατομμύριο" {
return Ok("εκατομμυριοστός".to_string());
}
let mut parts: Vec<String> = word.split_whitespace().map(String::from).collect();
if parts.len() > 1 {
if parts[0] == "είκοσι" && parts[1] == "ένα" {
return Ok("εικοστός πρώτος".to_string());
} else if parts[0] == "είκοσι" {
if let Some(o) = self.ordinals.get(parts[1].as_str()) {
return Ok(format!("εικοστός {}", o));
}
}
if parts[0] == "εκατό" && parts[1] == "ένα" {
return Ok("εκατοστός πρώτος".to_string());
} else if parts[0] == "εκατό" {
if let Some(o) = self.ordinals.get(parts[1].as_str()) {
return Ok(format!("εκατοστός {}", o));
}
}
let last_part = parts[parts.len() - 1].clone();
if let Some(o) = self.ordinals.get(last_part.as_str()) {
let o = o.to_string();
if parts[0] == "είκοσι" {
parts[0] = "εικοστός".to_string();
} else if parts[0] == "εκατό" {
parts[0] = "εκατοστός".to_string();
}
let n = parts.len();
parts[n - 1] = o;
return Ok(parts.join(" "));
} else if last_part == "τέσσερα" {
let n = parts.len();
parts[n - 1] = "τέταρτος".to_string();
return Ok(parts.join(" "));
}
}
if word.ends_with('α') {
Ok(format!("{}ος", strip_last_char(&word)))
} else if word.ends_with('ο') {
Ok(format!("{}ος", strip_last_char(&word)))
} else if word.ends_with('ι') {
Ok(format!("{}ος", word))
} else {
Ok(format!("{}ος", word))
}
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(format!("{}ος", value))
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let i = verify_ordinal_float(value, &ordinal_float_repr(value))?;
self.to_ordinal(&i)
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
verify_ordinal_float(value, repr_str)?;
Ok(format!("{}ος", repr_str))
}
fn lang_name(&self) -> &str {
"Num2Word_EL"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn currency_adjective(&self, code: &str) -> Option<&str> {
self.currency_adjectives.get(code).copied()
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
self.pluralize_forms(n.is_one(), forms)
}
fn money_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
if currency == "GBP" && number.is_one() {
return Ok("μία".to_string());
}
self.to_cardinal(number)
}
fn cents_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
if currency == "GBP" && number.is_one() {
return Ok("μία".to_string());
}
self.to_cardinal(number)
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
adjective: bool,
) -> Result<String> {
let separator = separator.unwrap_or(self.default_separator());
let decimal_val: BigDecimal = match val {
CurrencyValue::Int(i) => BigDecimal::from(i.clone()),
CurrencyValue::Decimal { value, .. } => value.clone(),
};
let has_fractional_cents = !(&decimal_val * BigDecimal::from(100)).is_integer();
let is_whole_float = match val {
CurrencyValue::Int(_) => false,
CurrencyValue::Decimal { value, .. } => value.is_integer(),
};
let is_int = matches!(val, CurrencyValue::Int(_));
let (left, right, is_negative) = match val {
CurrencyValue::Int(_) => parse_currency_parts(val, false, false, 100),
CurrencyValue::Decimal { value, .. } if is_whole_float => {
let truncated = CurrencyValue::Int(value.with_scale(0).as_bigint_and_exponent().0);
parse_currency_parts(&truncated, false, false, 100)
}
_ => parse_currency_parts(val, false, has_fractional_cents, 100),
};
let forms = self.currency_forms.get(currency).ok_or_else(|| {
N2WError::NotImplemented(format!(
"Currency code \"{}\" not implemented for \"{}\"",
currency,
self.lang_name()
))
})?;
let mut cr1 = forms.unit.clone();
let cr2 = forms.subunit.clone();
if adjective {
if let Some(adj) = self.currency_adjective(currency) {
cr1 = prefix_currency(adj, &cr1);
}
}
let minus_str = if is_negative {
format!("{} ", self.negword().trim())
} else {
String::new()
};
let money_str = self.money_verbose(&left, currency)?;
let has_decimal_and_not_whole = !is_int && !is_whole_float;
if has_decimal_and_not_whole || right > BigDecimal::zero() {
let cents_str = if has_fractional_cents {
if cents {
self.cardinal_from_decimal(&right)?
} else {
let f = right
.to_f64()
.ok_or_else(|| N2WError::Value(format!("cannot represent {}", right)))?;
format!("{}", f)
}
} else {
let right_int = right.with_scale(0).as_bigint_and_exponent().0;
if cents {
self.cents_verbose(&right_int, currency)?
} else {
self.cents_terse(&right_int, currency)?
}
};
return Ok(format!(
"{}{} {}{} {} {}",
minus_str,
money_str,
self.pluralize(&left, &cr1)?,
separator,
cents_str,
self.pluralize_forms(right == BigDecimal::one(), &cr2)?,
));
}
Ok(format!(
"{}{} {}",
minus_str,
money_str,
self.pluralize(&left, &cr1)?
))
}
}
fn verify_ordinal_float(value: &FloatValue, repr: &str) -> Result<BigInt> {
match value.as_whole_int() {
None => Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
repr
))),
Some(i) if i.is_negative() => Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
repr
))),
Some(i) => Ok(i),
}
}
fn ordinal_float_repr(value: &FloatValue) -> String {
match value {
FloatValue::Decimal { value, .. } => crate::strnum::python_decimal_str(value),
FloatValue::Float { value, .. } => {
if value.is_finite() && value.fract() == 0.0 && value.abs() < 1e16 {
format!("{:.1}", value)
} else {
value.to_string()
}
}
}
}