use std::collections::HashMap;
use thiserror::Error;
use crate::layout::{STRING_HEADER_ALIGN, STRING_HEADER_SIZE};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum StringPoolError {
#[error("string pool size exceeds u32::MAX after interning a {len}-byte literal")]
SizeOverflow {
len: usize,
},
}
#[derive(Debug, Default, Clone)]
pub(crate) struct StringPool {
data: Vec<u8>,
by_text: HashMap<String, u32>,
}
impl StringPool {
#[must_use]
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn intern(&mut self, text: &str) -> Result<u32, StringPoolError> {
if let Some(&offset) = self.by_text.get(text) {
return Ok(offset);
}
let bytes_offset = u32::try_from(self.data.len())
.map_err(|_| StringPoolError::SizeOverflow { len: text.len() })?;
self.data.extend_from_slice(text.as_bytes());
let header_align = STRING_HEADER_ALIGN as usize;
while !self.data.len().is_multiple_of(header_align) {
self.data.push(0);
}
let header_offset = u32::try_from(self.data.len())
.map_err(|_| StringPoolError::SizeOverflow { len: text.len() })?;
self.data.extend_from_slice(&bytes_offset.to_le_bytes());
let len_bytes = u32::try_from(text.len())
.map_err(|_| StringPoolError::SizeOverflow { len: text.len() })?;
self.data.extend_from_slice(&len_bytes.to_le_bytes());
let _ = STRING_HEADER_SIZE; self.by_text.insert(text.to_owned(), header_offset);
Ok(header_offset)
}
#[must_use]
pub(crate) fn data(&self) -> &[u8] {
&self.data
}
#[must_use]
pub(crate) const fn lookup_map(&self) -> &HashMap<String, u32> {
&self.by_text
}
}