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; construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> where Self: std::marker::Sized13 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 { new(lang: LanguageIdentifier) -> Self25 pub fn new(lang: LanguageIdentifier) -> Self { 26 Self { 27 lang, 28 map: RefCell::new(type_map::TypeMap::new()), 29 } 30 } 31 with_try_get<I, R, U>(&self, args: I::Args, cb: U) -> Result<R, I::Error> where Self: Sized, I: Memoizable + 'static, U: FnOnce(&I) -> R,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 { get_for_lang(&mut self, lang: LanguageIdentifier) -> Rc<IntlLangMemoizer>63 pub fn get_for_lang(&mut self, lang: LanguageIdentifier) -> Rc<IntlLangMemoizer> { 64 match self.map.entry(lang.clone()) { 65 Entry::Vacant(empty) => { 66 let entry = Rc::new(IntlLangMemoizer::new(lang)); 67 empty.insert(Rc::downgrade(&entry)); 68 entry 69 } 70 Entry::Occupied(mut entry) => { 71 if let Some(entry) = entry.get().upgrade() { 72 entry 73 } else { 74 let e = 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 { new( lang: LanguageIdentifier, pr_type: PluralRuleType, ) -> Result<Self, &'static str>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; construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error>112 fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> { 113 Self::new(lang, args.0) 114 } 115 } 116 117 #[test] it_works()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