use yo_kv::Elements;
pub const KEYS_MAX: usize = 1 << 16;
#[derive(Debug, Clone, Default)]
pub struct Keys {
names: Elements<()>,
}
impl Keys {
#[must_use]
pub fn new() -> Keys {
Keys {
names: Elements::new(),
}
}
#[must_use]
pub fn with_capacity(n: usize) -> Keys {
Keys {
names: Elements::with_capacity(n.min(KEYS_MAX)),
}
}
pub fn intern(&mut self, name: &[u8]) -> Option<u16> {
if let Some(id) = self.id(name) {
return Some(id);
}
if self.names.len() >= KEYS_MAX {
return None;
}
let id = u16::try_from(self.names.len()).expect("the length is under KEYS_MAX");
self.names.insert(name, ()).ok()?;
Some(id)
}
#[must_use]
pub fn id(&self, name: &[u8]) -> Option<u16> {
let at = self.names.index_of(name)?;
u16::try_from(at).ok()
}
#[must_use]
pub fn name(&self, id: u16) -> Option<&[u8]> {
self.names.at(usize::from(id)).map(|(name, ())| name)
}
#[must_use]
pub fn len(&self) -> usize {
self.names.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.names.is_empty()
}
#[must_use]
pub fn is_full(&self) -> bool {
self.names.len() >= KEYS_MAX
}
pub fn iter(&self) -> impl Iterator<Item = (&[u8], u16)> {
self.names
.iter()
.enumerate()
.map(|(at, (name, ()))| (name, at as u16))
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.names.memory_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_name_keeps_the_id_it_was_given() {
let mut keys = Keys::new();
let a = keys.intern(b"alpha").expect("room");
let b = keys.intern(b"beta").expect("room");
assert_ne!(a, b);
assert_eq!(keys.intern(b"alpha"), Some(a));
assert_eq!(keys.intern(b"beta"), Some(b));
assert_eq!(keys.len(), 2);
}
#[test]
fn ids_come_out_in_the_order_they_were_handed_out() {
let mut keys = Keys::new();
for i in 0..64u16 {
let name = format!("field{i}");
assert_eq!(keys.intern(name.as_bytes()), Some(i));
}
for (i, (name, id)) in keys.iter().enumerate() {
assert_eq!(id, i as u16);
assert_eq!(name, format!("field{i}").as_bytes());
assert_eq!(keys.name(id), Some(name));
}
}
#[test]
fn a_name_nobody_interned_has_no_id() {
let mut keys = Keys::new();
keys.intern(b"here").expect("room");
assert_eq!(keys.id(b"not here"), None);
assert_eq!(keys.name(1), None);
assert_eq!(keys.len(), 1, "asking did not add it");
}
#[test]
#[cfg_attr(
miri,
ignore = "a full table is the claim and a full table is 65536 names"
)]
fn a_full_table_refuses_rather_than_failing_the_write() {
let mut keys = Keys::with_capacity(KEYS_MAX);
for i in 0..KEYS_MAX {
let name = format!("f{i}");
assert_eq!(keys.intern(name.as_bytes()), Some(i as u16));
}
assert!(keys.is_full());
assert_eq!(keys.intern(b"one too many"), None);
assert_eq!(
keys.id(b"f0"),
Some(0),
"a full table still answers for what is in it"
);
}
}