1use std::cell::RefCell;
2use std::collections::hash_map::Entry;
3use std::collections::HashMap;
4use std::hash::Hash;
5use std::rc::{Rc, Weak};
6use unic_langid::LanguageIdentifier;
7
8pub mod concurrent;
9
10pub trait Memoizable {
11 type Args: 'static + Eq + Hash + Clone;
12 type Error;
13 fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error>
14 where
15 Self: std::marker::Sized;
16}
17
18#[derive(Debug)]
19pub struct IntlLangMemoizer {
20 lang: LanguageIdentifier,
21 map: RefCell<type_map::TypeMap>,
22}
23
24impl IntlLangMemoizer {
25 pub fn new(lang: LanguageIdentifier) -> Self {
26 Self {
27 lang,
28 map: RefCell::new(type_map::TypeMap::new()),
29 }
30 }
31
32 pub fn with_try_get<I, R, U>(&self, args: I::Args, cb: U) -> Result<R, I::Error>
33 where
34 Self: Sized,
35 I: Memoizable + 'static,
36 U: FnOnce(&I) -> R,
37 {
38 let mut map = self
39 .map
40 .try_borrow_mut()
41 .expect("Cannot use memoizer reentrantly");
42 let cache = map
43 .entry::<HashMap<I::Args, I>>()
44 .or_insert_with(HashMap::new);
45
46 let e = match cache.entry(args.clone()) {
47 Entry::Occupied(entry) => entry.into_mut(),
48 Entry::Vacant(entry) => {
49 let val = I::construct(self.lang.clone(), args)?;
50 entry.insert(val)
51 }
52 };
53 Ok(cb(&e))
54 }
55}
56
57#[derive(Default)]
58pub struct IntlMemoizer {
59 map: HashMap<LanguageIdentifier, Weak<IntlLangMemoizer>>,
60}
61
62impl IntlMemoizer {
63 pub fn get_for_lang(&mut self, lang: LanguageIdentifier) -> Rc<IntlLangMemoizer> {
64 match self.map.entry(key:lang.clone()) {
65 Entry::Vacant(empty: VacantEntry<'_, LanguageIdentifier, …>) => {
66 let entry: Rc = Rc::new(IntlLangMemoizer::new(lang));
67 empty.insert(Rc::downgrade(&entry));
68 entry
69 }
70 Entry::Occupied(mut entry: OccupiedEntry<'_, LanguageIdentifier, …>) => {
71 if let Some(entry: Rc) = entry.get().upgrade() {
72 entry
73 } else {
74 let e: Rc = Rc::new(IntlLangMemoizer::new(lang));
75 entry.insert(Rc::downgrade(&e));
76 e
77 }
78 }
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use fluent_langneg::{negotiate_languages, NegotiationStrategy};
87 use intl_pluralrules::{PluralCategory, PluralRuleType, PluralRules as IntlPluralRules};
88
89 struct PluralRules(pub IntlPluralRules);
90
91 impl PluralRules {
92 pub fn new(
93 lang: LanguageIdentifier,
94 pr_type: PluralRuleType,
95 ) -> Result<Self, &'static str> {
96 let default_lang: LanguageIdentifier = "en".parse().unwrap();
97 let pr_lang = negotiate_languages(
98 &[lang],
99 &IntlPluralRules::get_locales(pr_type),
100 Some(&default_lang),
101 NegotiationStrategy::Lookup,
102 )[0]
103 .clone();
104
105 Ok(Self(IntlPluralRules::create(pr_lang, pr_type)?))
106 }
107 }
108
109 impl Memoizable for PluralRules {
110 type Args = (PluralRuleType,);
111 type Error = &'static str;
112 fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> {
113 Self::new(lang, args.0)
114 }
115 }
116
117 #[test]
118 fn it_works() {
119 let lang: LanguageIdentifier = "en".parse().unwrap();
120
121 let mut memoizer = IntlMemoizer::default();
122 {
123 let en_memoizer = memoizer.get_for_lang(lang.clone());
124
125 let result = en_memoizer
126 .with_try_get::<PluralRules, _, _>((PluralRuleType::CARDINAL,), |cb| cb.0.select(5))
127 .unwrap();
128 assert_eq!(result, Ok(PluralCategory::OTHER));
129 }
130
131 {
132 let en_memoizer = memoizer.get_for_lang(lang.clone());
133
134 let result = en_memoizer
135 .with_try_get::<PluralRules, _, _>((PluralRuleType::CARDINAL,), |cb| cb.0.select(5))
136 .unwrap();
137 assert_eq!(result, Ok(PluralCategory::OTHER));
138 }
139 }
140}
141