use super::SmallCString; use std::cell::RefCell; use std::collections::BTreeMap; /// Maps parameter names to parameter indices. #[derive(Default, Clone, Debug)] // BTreeMap seems to do better here unless we want to pull in a custom hash // function. pub(crate) struct ParamIndexCache(RefCell>); impl ParamIndexCache { pub fn get_or_insert_with(&self, s: &str, func: F) -> Option where F: FnOnce(&std::ffi::CStr) -> Option, { let mut cache = self.0.borrow_mut(); // Avoid entry API, needs allocation to test membership. if let Some(v) = cache.get(s) { return Some(*v); } // If there's an internal nul in the name it couldn't have been a // parameter, so early return here is ok. let name = SmallCString::new(s).ok()?; let val = func(&name)?; cache.insert(name, val); Some(val) } } #[cfg(test)] mod test { use super::*; #[test] fn test_cache() { let p = ParamIndexCache::default(); let v = p.get_or_insert_with("foo", |cstr| { assert_eq!(cstr.to_str().unwrap(), "foo"); Some(3) }); assert_eq!(v, Some(3)); let v = p.get_or_insert_with("foo", |_| { panic!("shouldn't be called this time"); }); assert_eq!(v, Some(3)); let v = p.get_or_insert_with("gar\0bage", |_| { panic!("shouldn't be called here either"); }); assert_eq!(v, None); let v = p.get_or_insert_with("bar", |cstr| { assert_eq!(cstr.to_str().unwrap(), "bar"); None }); assert_eq!(v, None); let v = p.get_or_insert_with("bar", |cstr| { assert_eq!(cstr.to_str().unwrap(), "bar"); Some(30) }); assert_eq!(v, Some(30)); } }