1 | use std::cell::RefCell; |
2 | use std::collections::hash_map::Entry; |
3 | use std::collections::HashMap; |
4 | use std::hash::Hash; |
5 | use std::rc::{Rc, Weak}; |
6 | use unic_langid::LanguageIdentifier; |
7 | |
8 | pub mod concurrent; |
9 | |
10 | pub 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)] |
19 | pub struct IntlLangMemoizer { |
20 | lang: LanguageIdentifier, |
21 | map: RefCell<type_map::TypeMap>, |
22 | } |
23 | |
24 | impl 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)] |
58 | pub struct IntlMemoizer { |
59 | map: HashMap<LanguageIdentifier, Weak<IntlLangMemoizer>>, |
60 | } |
61 | |
62 | impl 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)] |
84 | mod 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 | |