use crate::base::{Kwargs, KwVal, Lang, N2WError, Result};
use crate::currency::{default_to_currency, CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, Zero};
use std::collections::HashMap;
const NOM: u8 = 10; const GEN: u8 = 11; const ACC: u8 = 12;
const PTV: u8 = 13; const INE: u8 = 14; const ELA: u8 = 15; const ILL: u8 = 16; const ADE: u8 = 17; const ABL: u8 = 18; const ALL: u8 = 19; const ESS: u8 = 20; const TRANSL: u8 = 21; const INSTRUC: u8 = 22; const ABE: u8 = 23; const COM: u8 = 24;
fn name_to_case(name: &str) -> Result<u8> {
Ok(match name {
"nominative" => NOM,
"genitive" => GEN,
"accusative" => ACC,
"partitive" => PTV,
"inessive" => INE,
"elative" => ELA,
"illative" => ILL,
"adessive" => ADE,
"ablative" => ABL,
"allative" => ALL,
"essive" => ESS,
"translative" => TRANSL,
"instructive" => INSTRUC,
"abessive" => ABE,
"comitative" => COM,
other => return Err(N2WError::Key(format!("'{}'", other))),
})
}
const BACK_TO_FRONT: [(char, &str); 3] = [('a', "ä"), ('o', "ö"), ('u', "y")];
type Row = (u8, &'static [&'static str], &'static [&'static str]);
const K5: &[Row] = &[
(NOM, &["i"], &["it"]),
(GEN, &["in"], &["ien"]),
(PTV, &["ia"], &["eja"]),
(INE, &["issa"], &["eissa"]),
(ELA, &["ista"], &["eista"]),
(ILL, &["iin"], &["eihin"]),
(ADE, &["illa"], &["eilla"]),
(ABL, &["ilta"], &["eilta"]),
(ALL, &["ille"], &["eille"]),
(ESS, &["ina"], &["eina"]),
(TRANSL, &["iksi"], &["eiksi"]),
(INSTRUC, &["ein"], &["ein"]),
(ABE, &["itta"], &["eitta"]),
(COM, &["eine"], &["eine"]),
];
const K7: &[Row] = &[
(NOM, &["i"], &["et"]),
(GEN, &["en"], &["ien"]),
(PTV, &["ea"], &["ia"]),
(INE, &["essa"], &["issa"]),
(ELA, &["esta"], &["ista"]),
(ILL, &["een"], &["iin"]),
(ADE, &["ella"], &["illa"]),
(ABL, &["elta"], &["ilta"]),
(ALL, &["elle"], &["ille"]),
(ESS, &["ena"], &["ina"]),
(TRANSL, &["eksi"], &["iksi"]),
(INSTRUC, &["in"], &["in"]),
(ABE, &["etta"], &["itta"]),
(COM, &["ine"], &["ine"]),
];
const K8: &[Row] = &[
(NOM, &["e"], &["et"]),
(GEN, &["en"], &["ejen", "ein"]),
(PTV, &["ea"], &["eja"]),
(INE, &["essa"], &["eissa"]),
(ELA, &["esta"], &["eista"]),
(ILL, &["een"], &["eihin"]),
(ADE, &["ella"], &["eilla"]),
(ABL, &["elta"], &["eilta"]),
(ALL, &["elle"], &["eille"]),
(ESS, &["ena"], &["eina"]),
(TRANSL, &["eksi"], &["eiksi"]),
(INSTRUC, &["ein"], &["ein"]),
(ABE, &["etta"], &["eitta"]),
(COM, &["eine"], &["eine"]),
];
const K109: &[Row] = &[
(NOM, &["ta"], &["dat"]),
(GEN, &["dan"], &["tojen", "tain"]),
(PTV, &["taa"], &["toja"]),
(INE, &["dassa"], &["doissa"]),
(ELA, &["dasta"], &["doista"]),
(ILL, &["taan"], &["toihin"]),
(ADE, &["dalla"], &["doilla"]),
(ABL, &["dalta"], &["doilta"]),
(ALL, &["dalle"], &["doille"]),
(ESS, &["tana"], &["toina"]),
(TRANSL, &["daksi"], &["doiksi"]),
(INSTRUC, &["doin"], &["doin"]),
(ABE, &["datta"], &["doitta"]),
(COM, &["toine"], &["toine"]),
];
const K10: &[Row] = &[
(NOM, &["a"], &["at"]),
(GEN, &["an"], &["ien", "ain"]),
(PTV, &["aa"], &["ia"]),
(INE, &["assa"], &["issa"]),
(ELA, &["asta"], &["ista"]),
(ILL, &["aan"], &["iin"]),
(ADE, &["alla"], &["illa"]),
(ABL, &["alta"], &["ilta"]),
(ALL, &["alle"], &["ille"]),
(ESS, &["ana"], &["ina"]),
(TRANSL, &["aksi"], &["iksi"]),
(INSTRUC, &["in"], &["in"]),
(ABE, &["atta"], &["itta"]),
(COM, &["ine"], &["ine"]),
];
const K27: &[Row] = &[
(NOM, &["si"], &["det"]),
(GEN, &["den"], &["sien", "tten"]),
(PTV, &["tta"], &["sia"]),
(INE, &["dessa"], &["sissa"]),
(ELA, &["desta"], &["sista"]),
(ILL, &["teen"], &["siin"]),
(ADE, &["della"], &["silla"]),
(ABL, &["delta"], &["silta"]),
(ALL, &["delle"], &["sille"]),
(ESS, &["tena"], &["sina"]),
(TRANSL, &["deksi"], &["siksi"]),
(INSTRUC, &["sin"], &["sin"]),
(ABE, &["detta"], &["sitta"]),
(COM, &["sine"], &["sine"]),
];
const K31: &[Row] = &[
(NOM, &["ksi"], &["hdet"]),
(GEN, &["hden"], &["ksien"]),
(PTV, &["hta"], &["ksia"]),
(INE, &["hdessa"], &["ksissa"]),
(ELA, &["hdesta"], &["ksista"]),
(ILL, &["hteen"], &["ksiin"]),
(ADE, &["hdella"], &["ksilla"]),
(ABL, &["hdelta"], &["ksilta"]),
(ALL, &["hdelle"], &["ksille"]),
(ESS, &["htena"], &["ksina"]),
(TRANSL, &["hdeksi"], &["ksiksi"]),
(INSTRUC, &["ksin"], &["ksin"]),
(ABE, &["hdetta"], &["ksitta"]),
(COM, &["ksine"], &["ksine"]),
];
const K32: &[Row] = &[
(NOM, &[""], &["et"]),
(GEN, &["en"], &["ien", "ten"]),
(PTV, &["ta"], &["ia"]),
(INE, &["essa"], &["issa"]),
(ELA, &["esta"], &["ista"]),
(ILL, &["een"], &["iin"]),
(ADE, &["ella"], &["illa"]),
(ABL, &["elta"], &["ilta"]),
(ALL, &["elle"], &["ille"]),
(ESS, &["ena"], &["ina"]),
(TRANSL, &["eksi"], &["iksi"]),
(INSTRUC, &["in"], &["in"]),
(ABE, &["etta"], &["itta"]),
(COM, &["ine"], &["ine"]),
];
const K38: &[Row] = &[
(NOM, &["nen"], &["set"]),
(GEN, &["sen"], &["sten", "sien"]),
(PTV, &["sta"], &["sia"]),
(INE, &["sessa"], &["sissa"]),
(ELA, &["sesta"], &["sista"]),
(ILL, &["seen"], &["siin"]),
(ADE, &["sella"], &["silla"]),
(ABL, &["selta"], &["silta"]),
(ALL, &["selle"], &["sille"]),
(ESS, &["sena"], &["sina"]),
(TRANSL, &["seksi"], &["siksi"]),
(INSTRUC, &["sin"], &["sin"]),
(ABE, &["setta"], &["sitta"]),
(COM, &["sine"], &["sine"]),
];
const K45: &[Row] = &[
(NOM, &["s"], &["nnet"]),
(GEN, &["nnen"], &["nsien"]),
(PTV, &["tta"], &["nsia"]),
(INE, &["nnessa"], &["nsissa"]),
(ELA, &["nnesta"], &["nsista"]),
(ILL, &["nteen"], &["nsiin"]),
(ADE, &["nnella"], &["nsilla"]),
(ABL, &["nnelta"], &["nsilta"]),
(ALL, &["nnelle"], &["nsille"]),
(ESS, &["ntena"], &["nsina"]),
(TRANSL, &["nneksi"], &["nsiksi"]),
(INSTRUC, &["nsin"], &["nsin"]),
(ABE, &["nnetta"], &["nsitta"]),
(COM, &["nsine"], &["nsine"]),
];
const K46: &[Row] = &[
(NOM, &["t"], &["nnet"]),
(GEN, &["nnen"], &["nsien", "nten"]),
(PTV, &["tta"], &["nsia"]),
(INE, &["nnessa"], &["nsissa"]),
(ELA, &["nnesta"], &["nsista"]),
(ILL, &["nteen"], &["nsiin"]),
(ADE, &["nnella"], &["nsilla"]),
(ABL, &["nnelta"], &["nsilta"]),
(ALL, &["nnelle"], &["nsille"]),
(ESS, &["ntena"], &["nsina"]),
(TRANSL, &["nneksi"], &["nsiksi"]),
(INSTRUC, &["nsin"], &["nsin"]),
(ABE, &["nnetta"], &["nsitta"]),
(COM, &["nsine"], &["nsine"]),
];
const K108: &[Row] = &[
(NOM, &["e"], &["et"]),
(GEN, &["en"], &["ien"]),
(PTV, &["ea"], &["ia"]),
(INE, &["essa"], &["issa"]),
(ELA, &["esta"], &["ista"]),
(ILL, &["een"], &["iin"]),
(ADE, &["ella"], &["illa"]),
(ABL, &["elta"], &["ilta"]),
(ALL, &["elle"], &["ille"]),
(ESS, &["ena"], &["ina"]),
(TRANSL, &["eksi"], &["iksi"]),
(INSTRUC, &["en"], &["in"]),
(ABE, &["etta"], &["itta"]),
(COM, &["ine"], &["ine"]),
];
const K110: &[Row] = &[
(NOM, &["an"], &["at"]),
(GEN, &["an"], &["ien", "ain"]),
(PTV, &["aa"], &["ia"]),
(INE, &["assa"], &["issa"]),
(ELA, &["asta"], &["ista"]),
(ILL, &["aan"], &["iin"]),
(ADE, &["alla"], &["illa"]),
(ABL, &["alta"], &["ilta"]),
(ALL, &["alle"], &["ille"]),
(ESS, &["ana"], &["ina"]),
(TRANSL, &["aksi"], &["iksi"]),
(INSTRUC, &["in"], &["in"]),
(ABE, &["atta"], &["itta"]),
(COM, &["ine"], &["ine"]),
];
const K132: &[Row] = &[
(NOM, &["en"], &["et"]),
(GEN, &["en"], &["ien", "ten"]),
(PTV, &["ta"], &["ia"]),
(INE, &["essa"], &["issa"]),
(ELA, &["esta"], &["ista"]),
(ILL, &["een"], &["iin"]),
(ADE, &["ella"], &["illa"]),
(ABL, &["elta"], &["ilta"]),
(ALL, &["elle"], &["ille"]),
(ESS, &["ena"], &["ina"]),
(TRANSL, &["eksi"], &["iksi"]),
(INSTRUC, &["in"], &["in"]),
(ABE, &["etta"], &["itta"]),
(COM, &["ine"], &["ine"]),
];
fn kotus_suffixes(kotus: u16, case: u8) -> Result<(&'static [&'static str], &'static [&'static str])> {
let rows: &'static [Row] = match kotus {
5 => K5,
7 => K7,
8 => K8,
10 => K10,
27 => K27,
31 => K31,
32 => K32,
38 => K38,
45 => K45,
46 => K46,
108 => K108,
109 => K109,
110 => K110,
132 => K132,
_ => return Err(N2WError::Key(format!("{}", kotus))),
};
for &(c, sg, pl) in rows {
if c == case {
return Ok((sg, pl));
}
}
Err(N2WError::Key(format!("{}", case)))
}
#[derive(Debug, Clone)]
enum Part {
Text(String),
Stem {
stem: String,
kotus: u16,
case_override: Option<u8>,
},
}
type Value = Vec<Part>;
fn text(s: impl Into<String>) -> Value {
vec![Part::Text(s.into())]
}
fn stem(s: &str, kotus: u16) -> Part {
Part::Stem { stem: s.to_string(), kotus, case_override: None }
}
fn stem_case(s: &str, kotus: u16, case: u8) -> Part {
Part::Stem { stem: s.to_string(), kotus, case_override: Some(case) }
}
#[derive(Debug, Clone)]
struct Options {
ordinal: bool,
case: u8,
plural: bool,
prefer: Vec<String>,
}
impl Options {
fn with_case(&self, case: u8) -> Options {
Options { case, ..self.clone() }
}
}
fn inflect(parts: &Value, options: &Options) -> Result<String> {
let mut out = String::new();
for part in parts {
let (stem, kotus, case_override) = match part {
Part::Text(t) => {
out.push_str(t);
continue;
}
Part::Stem { stem, kotus, case_override } => (stem, *kotus, *case_override),
};
let mut tmp_case = options.case;
if let Some(c) = case_override {
if options.case == NOM && !options.plural {
tmp_case = c;
}
}
let (sg, pl) = kotus_suffixes(kotus, tmp_case)?;
let choices = if options.plural { pl } else { sg };
let mut suffix = choices[0].to_string();
if choices.len() > 1 {
let common: Vec<&str> = choices
.iter()
.copied()
.filter(|c| options.prefer.iter().any(|p| p.as_str() == *c))
.collect();
if common.len() == 1 {
suffix = common[0].to_string();
}
}
if !stem.chars().any(|ch| BACK_TO_FRONT.iter().any(|(b, _)| *b == ch)) {
for (back, front) in BACK_TO_FRONT {
suffix = suffix.replace(back, front);
}
}
out.push_str(stem);
out.push_str(&suffix);
}
Ok(out)
}
#[derive(Debug, Default)]
struct Table {
entries: Vec<(BigInt, Value)>,
}
impl Table {
fn new() -> Self {
Table { entries: Vec::new() }
}
fn push(&mut self, key: BigInt, value: Value) {
self.entries.push((key, value));
}
fn get(&self, key: &BigInt) -> Option<&Value> {
self.entries
.binary_search_by(|(k, _)| key.cmp(k))
.ok()
.map(|i| &self.entries[i].1)
}
fn iter_from(&self, value: &BigInt) -> impl Iterator<Item = &(BigInt, Value)> {
let start = self.entries.partition_point(|(k, _)| k > value);
self.entries[start..].iter()
}
fn highest(&self) -> Option<&BigInt> {
self.entries.first().map(|(k, _)| k)
}
}
#[derive(Debug, Clone)]
enum Node {
Leaf(Value, BigInt),
List(Vec<Node>),
}
impl Node {
fn is_leaf(&self) -> bool {
matches!(self, Node::Leaf(..))
}
}
fn gen_high_numwords(units: &[&str], tens: &[&str], lows: &[&str]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for t in tens {
for u in units {
out.push(format!("{}{}", u, t));
}
}
out.reverse();
const REPLACEMENTS: &[(&str, &str)] = &[
("novemn", "noven"),
("novemo", "novo"),
("octoo", "octo"),
("quintd", "quind"),
("quintn", "quin"),
("quintq", "quinq"),
("quints", "quins"),
("quintt", "quint"),
("quintv", "quinv"),
("septenn", "septen"),
("septent", "sept"),
("sexn", "sen"),
("sexs", "ses"),
("tresd", "tred"),
("tresn", "tren"),
("tress", "tres"),
("tresv", "trev"),
("unno", "uno"),
];
for (k, v) in REPLACEMENTS {
out = out.iter().map(|o| o.replace(k, v)).collect();
}
out.extend(lows.iter().map(|s| s.to_string()));
out
}
fn char_slice_from(s: &str, n: usize) -> String {
s.chars().skip(n).collect()
}
fn wrap_index(i: usize, len: usize) -> usize {
let idx = i as isize - 10;
(if idx < 0 { idx + len as isize } else { idx }) as usize
}
fn translate_high(high: &[String]) -> Vec<String> {
const REPLACEMENTS: [(&str, &str); 4] = [
("qu", "kv"),
("x", "ks"),
("c", "k"),
("kent", "sent"), ];
let mut translated: Vec<String> = Vec::with_capacity(high.len());
for (i, numword) in high.iter().enumerate() {
let mut numword: String = numword.clone();
if numword.starts_with("sex") && numword != "sext" {
numword = numword.replace("sex", "se");
}
else if numword.starts_with("sept") && numword != "sept" {
numword = format!("septen{}", char_slice_from(&numword, "sept".len()));
}
else if numword.starts_with("octo") {
let w = &high[wrap_index(i, high.len())];
numword = format!("duode{}", char_slice_from(w, "octo".len()));
}
else if numword.starts_with("nove") {
let w = &high[wrap_index(i, high.len())];
numword = format!("unde{}", char_slice_from(w, "nove".len() + 1));
}
for (a, b) in REPLACEMENTS {
numword = numword.replace(a, b);
}
translated.push(numword);
}
translated
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
const GENERIC_CENTS: [&str; 2] = ["sentti", "senttiä"];
const GENERIC_CENTAVOS: [&str; 2] = ["centavo", "centavoa"];
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert("BRL", CurrencyForms::new(&["real", "realia"], &GENERIC_CENTAVOS));
m.insert("CHF", CurrencyForms::new(&["frangi", "frangia"], &["rappen", "rappenia"]));
m.insert("CNY", CurrencyForms::new(&["juan", "juania"], &["fen", "feniä"]));
m.insert("EUR", CurrencyForms::new(&["euro", "euroa"], &GENERIC_CENTS));
m.insert("FIM", CurrencyForms::new(&["markka", "markkaa"], &["penni", "penniä"]));
m.insert("INR", CurrencyForms::new(&["rupia", "rupiaa"], &["paisa", "paisaa"]));
m.insert("JPY", CurrencyForms::new(&["jeni", "jeniä"], &["sen", "seniä"]));
m.insert("KRW", CurrencyForms::new(&["won", "wonia"], &["jeon", "jeonia"]));
m.insert("KPW", CurrencyForms::new(&["won", "wonia"], &["chon", "chonia"]));
m.insert("MXN", CurrencyForms::new(&["peso", "pesoa"], &GENERIC_CENTAVOS));
m.insert("RUB", CurrencyForms::new(&["rupla", "ruplaa"], &["kopeekka", "kopeekkaa"]));
m.insert("TRY", CurrencyForms::new(&["liira", "liiraa"], &["kuruş", "kuruşia"]));
m.insert("ZAR", CurrencyForms::new(&["randi", "randia"], &GENERIC_CENTS));
for code in ["DKK", "ISK", "NOK", "SEK"] {
m.insert(code, CurrencyForms::new(&["kruunu", "kruunua"], &["äyri", "äyriä"]));
}
for code in ["AUD", "CAD", "HKD", "NZD", "SGD", "USD"] {
m.insert(code, CurrencyForms::new(&["dollari", "dollaria"], &GENERIC_CENTS));
}
m.insert("GBP", CurrencyForms::new(&["punta", "puntaa"], &["penny", "pennyä"]));
m
}
fn build_currency_adjectives() -> HashMap<&'static str, &'static str> {
[
("AUD", "Australian"),
("BRL", "Brasilian"),
("CAD", "Kanadan"),
("CHF", "Sveitsin"),
("DKK", "Tanskan"),
("FIM", "Suomen"), ("GBP", "Englannin"),
("HKD", "Hongkongin"),
("INR", "Intian"),
("ISK", "Islannin"),
("KRW", "Etelä-Korean"),
("KPW", "Pohjois-Korean"),
("MXN", "Meksikon"),
("NOK", "Norjan"),
("NZD", "Uuden-Seelannin"),
("RUB", "Venäjän"),
("SEK", "Ruotsin"),
("SGD", "Singaporen"),
("TRY", "Turkin"),
("USD", "Yhdysvaltain"),
("ZAR", "Etelä-Afrikan"),
]
.into_iter()
.collect()
}
pub struct LangFi {
cards: Table,
ords: Table,
maxval: BigInt,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl Default for LangFi {
fn default() -> Self {
Self::new()
}
}
impl LangFi {
pub fn new() -> Self {
let lows = ["non", "oct", "sept", "sext", "quint", "quadr", "tr", "b", "m"];
let units = [
"", "un", "duo", "tres", "quattuor", "quint", "sex", "septen", "octo", "novem",
];
let tens = [
"dec",
"vigint",
"trigint",
"quadragint",
"quinquagint",
"sexagint",
"septuagint",
"octogint",
"nonagint",
];
let mut high = vec!["cent".to_string()];
high.extend(gen_high_numwords(&units, &tens, &lows));
let mut cards = Table::new();
let mut ords = Table::new();
let translated = translate_high(&high);
let ten = BigInt::from(10u8);
let mut n: i64 = 6 * translated.len() as i64;
for word in translated.iter() {
if n <= 0 {
break; }
if n == 6 {
cards.push(ten.pow(9), vec![stem("miljard", 5)]);
ords.push(ten.pow(9), vec![stem("miljardi", 45)]);
}
cards.push(ten.pow(n as u32), vec![stem(&format!("{}iljoon", word), 10)]);
ords.push(ten.pow(n as u32), vec![stem(&format!("{}iljoona", word), 45)]);
n -= 6;
}
cards.push(BigInt::from(1000), vec![stem("tuha", 46)]);
cards.push(BigInt::from(100), vec![stem("sa", 109)]);
cards.push(BigInt::from(90), vec![stem("yhdeks", 110), stem_case("kymmen", 132, PTV)]);
cards.push(BigInt::from(80), vec![stem("kahdeks", 110), stem_case("kymmen", 132, PTV)]);
cards.push(BigInt::from(70), vec![stem("seitsem", 110), stem_case("kymmen", 132, PTV)]);
cards.push(BigInt::from(60), vec![stem("kuu", 27), stem_case("kymmen", 132, PTV)]);
cards.push(BigInt::from(50), vec![stem("vii", 27), stem_case("kymmen", 132, PTV)]);
cards.push(BigInt::from(40), vec![stem("nelj", 10), stem_case("kymmen", 132, PTV)]);
cards.push(BigInt::from(30), vec![stem("kolm", 108), stem_case("kymmen", 132, PTV)]);
ords.push(BigInt::from(1000), vec![stem("tuhanne", 45)]);
ords.push(BigInt::from(100), vec![stem("sada", 45)]);
ords.push(BigInt::from(90), vec![stem("yhdeksä", 45), stem("kymmene", 45)]);
ords.push(BigInt::from(80), vec![stem("kahdeksa", 45), stem("kymmene", 45)]);
ords.push(BigInt::from(70), vec![stem("seitsemä", 45), stem("kymmene", 45)]);
ords.push(BigInt::from(60), vec![stem("kuude", 45), stem("kymmene", 45)]);
ords.push(BigInt::from(50), vec![stem("viide", 45), stem("kymmene", 45)]);
ords.push(BigInt::from(40), vec![stem("neljä", 45), stem("kymmene", 45)]);
ords.push(BigInt::from(30), vec![stem("kolma", 45), stem("kymmene", 45)]);
cards.push(BigInt::from(20), vec![stem("ka", 31), stem_case("kymmen", 132, PTV)]);
cards.push(BigInt::from(19), vec![stem("yhdeks", 110), Part::Text("toista".into())]);
cards.push(BigInt::from(18), vec![stem("kahdeks", 110), Part::Text("toista".into())]);
cards.push(BigInt::from(17), vec![stem("seitsem", 110), Part::Text("toista".into())]);
cards.push(BigInt::from(16), vec![stem("kuu", 27), Part::Text("toista".into())]);
cards.push(BigInt::from(15), vec![stem("vii", 27), Part::Text("toista".into())]);
cards.push(BigInt::from(14), vec![stem("nelj", 10), Part::Text("toista".into())]);
cards.push(BigInt::from(13), vec![stem("kolm", 108), Part::Text("toista".into())]);
cards.push(BigInt::from(12), vec![stem("ka", 31), Part::Text("toista".into())]);
cards.push(BigInt::from(11), vec![stem("y", 31), Part::Text("toista".into())]);
cards.push(BigInt::from(10), vec![stem("kymmen", 132)]);
cards.push(BigInt::from(9), vec![stem("yhdeks", 110)]);
cards.push(BigInt::from(8), vec![stem("kahdeks", 110)]);
cards.push(BigInt::from(7), vec![stem("seitsem", 110)]);
cards.push(BigInt::from(6), vec![stem("kuu", 27)]);
cards.push(BigInt::from(5), vec![stem("vii", 27)]);
cards.push(BigInt::from(4), vec![stem("nelj", 10)]);
cards.push(BigInt::from(3), vec![stem("kolm", 108)]);
cards.push(BigInt::from(2), vec![stem("ka", 31)]);
cards.push(BigInt::from(1), vec![stem("y", 31)]);
cards.push(BigInt::zero(), vec![stem("noll", 10)]);
ords.push(BigInt::from(20), vec![stem("kahde", 45), stem("kymmene", 45)]);
ords.push(BigInt::from(19), vec![stem("yhdeksä", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(18), vec![stem("kahdeksa", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(17), vec![stem("seitsemä", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(16), vec![stem("kuude", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(15), vec![stem("viide", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(14), vec![stem("neljä", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(13), vec![stem("kolma", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(12), vec![stem("kahde", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(11), vec![stem("yhde", 45), Part::Text("toista".into())]);
ords.push(BigInt::from(10), vec![stem("kymmene", 45)]);
ords.push(BigInt::from(9), vec![stem("yhdeksä", 45)]);
ords.push(BigInt::from(8), vec![stem("kahdeksa", 45)]);
ords.push(BigInt::from(7), vec![stem("seitsemä", 45)]);
ords.push(BigInt::from(6), vec![stem("kuude", 45)]);
ords.push(BigInt::from(5), vec![stem("viide", 45)]);
ords.push(BigInt::from(4), vec![stem("neljä", 45)]);
ords.push(BigInt::from(3), vec![stem("kolma", 45)]);
ords.push(BigInt::from(2), vec![stem("toi", 38)]);
ords.push(BigInt::from(1), vec![stem("ensimmäi", 38)]);
ords.push(BigInt::zero(), vec![stem("nolla", 45)]);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
LangFi {
cards,
ords,
maxval,
exclude_title: vec!["pilkku".into(), "miinus".into()],
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
}
}
fn splitnum(&self, value: &BigInt, options: &Options) -> Result<Option<Vec<Node>>> {
let elems = if options.ordinal { &self.ords } else { &self.cards };
for (elem, word) in elems.iter_from(value) {
let mut out: Vec<Node> = Vec::new();
let (div, mod_) = if value.is_zero() {
(BigInt::one(), BigInt::zero())
} else {
value.div_mod_floor(elem)
};
if div.is_one() {
let one = BigInt::one();
let v = elems.get(&one).cloned().ok_or_else(|| N2WError::Key("1".into()))?;
out.push(Node::Leaf(v, one));
} else {
if &div == value {
return Err(N2WError::Type(
"lang_FI splitnum tally branch is unreachable".into(),
));
}
match self.splitnum(&div, options)? {
Some(sub) => out.push(Node::List(sub)),
None => return Ok(None),
}
}
out.push(Node::Leaf(word.clone(), elem.clone()));
if !mod_.is_zero() {
match self.splitnum(&mod_, options)? {
Some(sub) => out.push(Node::List(sub)),
None => return Ok(None),
}
}
return Ok(Some(out));
}
Ok(None)
}
fn merge_parts(
&self,
l: (&Value, &BigInt),
r: (&Value, &BigInt),
options: &Options,
) -> Result<Option<(Value, BigInt)>> {
let (ltext, lnum) = l;
let (rtext, rnum) = r;
let one = BigInt::one();
let two = BigInt::from(2);
let thousand = BigInt::from(1000);
if *lnum == one {
let rt = inflect(rtext, options)?;
return Ok(Some((text(rt), rnum.clone())));
}
if lnum > rnum {
let lt = inflect(ltext, options)?;
let rt = inflect(rtext, options)?;
let fmt = if *lnum >= thousand { " " } else { "" };
return Ok(Some((text(format!("{}{}{}", lt, fmt, rt)), lnum + rnum)));
}
if lnum < rnum {
let (lval, rt): (Value, String) = if options.ordinal {
let lval = if *lnum == two { vec![stem("kahde", 45)] } else { ltext.clone() };
(lval, inflect(rtext, options)?)
} else {
let mut rcase = options.case;
if options.case == NOM && !options.plural {
rcase = PTV;
}
(ltext.clone(), inflect(rtext, &options.with_case(rcase))?)
};
let lt = inflect(&lval, options)?;
return Ok(Some((text(format!("{}{}", lt, rt)), lnum * rnum)));
}
Ok(None)
}
fn clean(&self, val: Vec<Node>, options: &Options) -> Result<Node> {
let mut val = val;
while val.len() != 1 {
let mut out: Vec<Node> = Vec::new();
if val.len() >= 2 && val[0].is_leaf() && val[1].is_leaf() {
let (lt, ln) = match &val[0] {
Node::Leaf(t, n) => (t.clone(), n.clone()),
_ => unreachable!(),
};
let (rt, rn) = match &val[1] {
Node::Leaf(t, n) => (t.clone(), n.clone()),
_ => unreachable!(),
};
match self.merge_parts((<, &ln), (&rt, &rn), options)? {
Some((mt, mn)) => out.push(Node::Leaf(mt, mn)),
None => {
return Err(N2WError::Type(
"cannot unpack non-sequence NoneType object".into(),
))
}
}
if val.len() > 2 {
out.push(Node::List(val[2..].to_vec()));
}
} else {
for elem in val.into_iter() {
match elem {
Node::List(inner) => {
if inner.len() == 1 {
out.push(inner.into_iter().next().unwrap());
} else {
out.push(self.clean(inner, options)?);
}
}
leaf => out.push(leaf),
}
}
}
val = out;
}
Ok(val.into_iter().next().unwrap())
}
fn convert(&self, value: &BigInt, options: &Options) -> Result<String> {
let tree = self.splitnum(value, options)?.ok_or_else(|| {
N2WError::Overflow(format!("abs({}) must be less than {}.", value, self.maxval))
})?;
match self.clean(tree, options)? {
Node::Leaf(v, _) => inflect(&v, options),
Node::List(_) => Err(N2WError::Type("clean did not reduce".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(())
}
fn to_cardinal_opts(&self, value: &BigInt, options: &Options) -> Result<String> {
let mut out = String::new();
let mut v = value.clone();
if v.is_negative() {
v = v.abs();
out.push_str(self.negword());
}
if v >= self.maxval {
return Err(N2WError::Overflow(format!(
"abs({}) must be less than {}.",
v, self.maxval
)));
}
let words = self.convert(&v, options)?;
Ok(self.title(&format!("{}{}", out, words)))
}
fn to_ordinal_opts(&self, value: &BigInt, options: &Options) -> Result<String> {
self.verify_ordinal(value)?;
if value >= &self.maxval {
return Err(N2WError::Overflow(format!(
"abs({}) must be less than {}.",
value, self.maxval
)));
}
let words = self.convert(value, options)?;
Ok(self.title(&words))
}
fn options_from_kwargs(&self, kw: &Kwargs, ordinal: bool) -> Result<Options> {
let case = match kw.get("case") {
None => NOM,
Some(KwVal::Str(s)) => name_to_case(s)?,
Some(other) => return Err(N2WError::Key(format!("{:?}", other))),
};
let plural = match kw.get("plural") {
None => false,
Some(KwVal::Bool(b)) => *b,
Some(KwVal::Int(0)) => false,
Some(KwVal::Int(1)) => true,
Some(_) => return Err(N2WError::Fallback("kwargs".into())),
};
let prefer = match kw.get("prefer") {
None | Some(KwVal::None) => Vec::new(),
Some(KwVal::List(l)) => l.clone(),
Some(_) => return Err(N2WError::Fallback("kwargs".into())),
};
Ok(Options { ordinal, case, plural, prefer })
}
}
fn float_msg_repr(value: &FloatValue) -> String {
match value {
FloatValue::Float { value: f, .. } => {
if f.is_finite() && f.fract() == 0.0 && f.abs() < 1e16 {
format!("{:.1}", f)
} else {
format!("{}", f)
}
}
FloatValue::Decimal { value: d, .. } => crate::strnum::python_decimal_str(d),
}
}
impl Lang for LangFi {
fn python_maxval(&self) -> Option<num_bigint::BigInt> {
Some(num_bigint::BigInt::from(10u32).pow(603))
}
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
" ja"
}
fn negword(&self) -> &str {
"miinus "
}
fn pointword(&self) -> &str {
"pilkku"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let options = Options { ordinal: false, case: NOM, plural: false, prefer: Vec::new() };
self.to_cardinal_opts(value, &options)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
let options = Options { ordinal: true, case: NOM, plural: false, prefer: Vec::new() };
self.to_ordinal_opts(value, &options)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}.", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
let mut val = value.clone();
let mut suffix = "";
if val.is_negative() {
val = val.abs();
suffix = " ennen ajanlaskun alkua";
}
Ok(format!("{}{}", self.to_cardinal(&val)?.replace(' ', ""), suffix))
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
match value.as_whole_int() {
None => Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
float_msg_repr(value)
))),
Some(i) => {
if i.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
float_msg_repr(value)
)));
}
self.to_ordinal(&i)
}
}
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}.", repr_str))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
let (below_zero, abs) = match value {
FloatValue::Float { value: f, precision } => (
*f < 0.0,
FloatValue::Float { value: f.abs(), precision: *precision },
),
FloatValue::Decimal { value: d, precision } => (
d.is_negative(),
FloatValue::Decimal { value: d.abs(), precision: *precision },
),
};
let (val, suffix) = if below_zero {
(&abs, " ennen ajanlaskun alkua")
} else {
(value, "")
};
let cardinal = self.cardinal_float_entry(val, None)?;
Ok(format!("{}{}", cardinal.replace(' ', ""), suffix))
}
fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["case", "plural", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let options = self.options_from_kwargs(kw, false)?;
self.to_cardinal_opts(value, &options)
}
fn to_ordinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["case", "plural", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let options = self.options_from_kwargs(kw, true)?;
self.to_ordinal_opts(value, &options)
}
fn to_ordinal_num_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["case", "plural"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
Ok(format!("{}.", value))
}
fn to_year_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["suffix", "longval"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let mut suffix: String = match kw.get("suffix") {
None | Some(KwVal::None) => String::new(),
Some(KwVal::Str(s)) => s.clone(),
Some(_) => return Err(N2WError::Fallback("kwargs".into())),
};
let mut val = value.clone();
if val.is_negative() {
val = val.abs();
if suffix.is_empty() {
suffix = " ennen ajanlaskun alkua".to_string();
}
}
Ok(format!("{}{}", self.to_cardinal(&val)?.replace(' ', ""), suffix))
}
fn to_cardinal_float_kw(
&self,
value: &FloatValue,
precision_override: Option<u32>,
kw: &Kwargs,
) -> Result<String> {
if !kw.only(&["case", "plural", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let options = self.options_from_kwargs(kw, false)?;
if let Some(i) = value.as_whole_int() {
return self.to_cardinal_opts(&i, &options);
}
if options.case != NOM {
return Err(N2WError::NotImplemented(
"Cases other than nominative are not implemented for \
cardinal floating point numbers."
.into(),
));
}
self.to_cardinal_float(value, precision_override)
}
fn lang_name(&self) -> &str {
"Num2Word_FI"
}
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> {
let form = if n.is_one() { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
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());
if let CurrencyValue::Int(v) = val {
let cr1 = match self.currency_forms.get(currency) {
Some(forms) => &forms.unit,
None => {
return default_to_currency(self, val, currency, cents, separator, adjective)
}
};
let minus_str = if v.is_negative() { self.negword() } else { "" };
let abs_val = v.abs();
let money_str = self.to_cardinal(&abs_val)?;
let currency_str = if abs_val.is_one() {
cr1.first()
} else if cr1.len() > 1 {
cr1.get(1)
} else {
cr1.first()
}
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))?;
return Ok(format!("{} {} {}", minus_str, money_str, currency_str)
.trim()
.to_string());
}
default_to_currency(self, val, currency, cents, separator, adjective)
}
}