use core::fmt;
use core::hash::BuildHasher;
use core::str::Utf8Error;
use std::io::Cursor;
use hashbrown::hash_table::Entry;
use hashbrown::{DefaultHashBuilder, HashTable};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReadStringError {
#[error("error reading LEB128 encoded number")]
Leb128(#[from] leb128::read::Error),
#[error("error reading UTF-8 string data")]
Utf8(#[from] Utf8Error),
#[error("string offset or length is out of bounds")]
OutOfBounds,
}
#[derive(Clone, Default)]
pub struct StringTable {
hasher: DefaultHashBuilder,
offsets: HashTable<usize>,
buffer: Vec<u8>,
}
impl fmt::Debug for StringTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let iter = self
.offsets
.iter()
.map(|&offset| (offset, Self::read(&self.buffer, offset).unwrap()));
f.debug_map().entries(iter).finish()
}
}
impl StringTable {
pub fn new() -> Self {
Self::default()
}
fn read_bytes(buffer: &[u8], offset: usize) -> Result<(&[u8], usize), ReadStringError> {
let mut cursor = Cursor::new(buffer.get(offset..).ok_or(ReadStringError::OutOfBounds)?);
let len = leb128::read::unsigned(&mut cursor)? as usize;
let leb_len = cursor.position() as usize;
let start = offset + leb_len;
let end = start + len;
let string_bytes = buffer.get(start..end).ok_or(ReadStringError::OutOfBounds)?;
Ok((string_bytes, end))
}
fn raw_entry(&mut self, string_bytes: &[u8]) -> (Entry<'_, usize>, &mut Vec<u8>) {
let hasher = |val: &_| self.hasher.hash_one(val);
let hash = hasher(string_bytes);
let entry = self.offsets.entry(
hash,
|&offset| Self::read_bytes(&self.buffer, offset).unwrap().0 == string_bytes,
|&offset| hasher(Self::read_bytes(&self.buffer, offset).unwrap().0),
);
(entry, &mut self.buffer)
}
pub fn from_bytes(buffer: &[u8]) -> Result<Self, ReadStringError> {
let mut slf = Self {
buffer: buffer.into(),
..Default::default()
};
let mut offset = 0;
while offset < buffer.len() {
let (string_bytes, next_offset) = Self::read_bytes(buffer, offset)?;
std::str::from_utf8(string_bytes)?;
let (entry, _buffer) = slf.raw_entry(string_bytes);
entry.insert(offset);
offset = next_offset;
}
Ok(slf)
}
pub fn insert(&mut self, s: &str) -> usize {
let string_bytes = s.as_bytes();
let (entry, buffer) = self.raw_entry(string_bytes);
let entry = entry.or_insert_with(|| {
let offset = buffer.len();
let string_len = string_bytes.len() as u64;
leb128::write::unsigned(buffer, string_len).unwrap();
buffer.extend_from_slice(string_bytes);
offset
});
*entry.get()
}
pub fn as_bytes(&self) -> &[u8] {
&self.buffer
}
pub fn into_bytes(self) -> Vec<u8> {
self.buffer
}
pub fn read(buffer: &[u8], offset: usize) -> Result<&str, ReadStringError> {
let bytes = Self::read_bytes(buffer, offset)?.0;
Ok(std::str::from_utf8(bytes)?)
}
}