aboutsummaryrefslogtreecommitdiff
path: root/src/util/param_cache.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/param_cache.rs')
-rw-r--r--src/util/param_cache.rs60
1 files changed, 60 insertions, 0 deletions
diff --git a/src/util/param_cache.rs b/src/util/param_cache.rs
new file mode 100644
index 0000000..6faced9
--- /dev/null
+++ b/src/util/param_cache.rs
@@ -0,0 +1,60 @@
+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<BTreeMap<SmallCString, usize>>);
+
+impl ParamIndexCache {
+ pub fn get_or_insert_with<F>(&self, s: &str, func: F) -> Option<usize>
+ where
+ F: FnOnce(&std::ffi::CStr) -> Option<usize>,
+ {
+ 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));
+ }
+}