use std::cell::UnsafeCell;
use std::fmt::{self, Debug, Formatter};
use crate::TreePermutation;
mod clone;
mod compact;
mod inline;
mod sidecar;
pub use inline::InlineSuffixBag;
pub use sidecar::{SideCarUtils, SuffixSidecar};
const WIDTH: usize = 15;
const INITIAL_CAPACITY: usize = 128;
#[derive(Clone, Copy, Debug, Default)]
struct SlotMeta {
offset: u32,
len: u16,
_pad: u16,
}
impl SlotMeta {
const EMPTY: Self = Self {
offset: u32::MAX,
len: 0,
_pad: 0,
};
#[inline(always)]
const fn has_suffix(self) -> bool {
self.offset != u32::MAX
}
}
pub trait PermutationProvider {
fn size(&self) -> usize;
fn get(&self, i: usize) -> usize;
}
#[derive(Debug)]
pub struct SuffixBag {
slots: [SlotMeta; WIDTH],
data: Vec<u8>,
suffix_count: u8,
}
impl SuffixBag {
#[must_use]
#[inline(always)]
pub fn new() -> Self {
Self {
slots: [SlotMeta::EMPTY; WIDTH],
data: Vec::with_capacity(INITIAL_CAPACITY),
suffix_count: 0,
}
}
#[must_use]
#[inline(always)]
pub fn with_capacity(capacity: usize) -> Self {
Self {
slots: [SlotMeta::EMPTY; WIDTH],
data: Vec::with_capacity(capacity),
suffix_count: 0,
}
}
#[must_use]
#[inline(always)]
pub fn from_vec(data: Vec<u8>) -> Self {
debug_assert!(data.is_empty(), "from_vec expects empty Vec");
Self {
slots: [SlotMeta::EMPTY; WIDTH],
data,
suffix_count: 0,
}
}
#[must_use]
#[inline(always)]
pub const fn capacity(&self) -> usize {
self.data.capacity()
}
#[must_use]
#[inline(always)]
pub const fn used(&self) -> usize {
self.data.len()
}
#[must_use]
#[inline(always)]
pub const fn count(&self) -> usize {
self.suffix_count as usize
}
#[inline(always)]
pub fn reserve(&mut self, additional: usize) {
self.data.reserve(additional);
}
#[expect(clippy::indexing_slicing, reason = "Checked by caller")]
#[expect(clippy::cast_possible_truncation)]
fn append_and_record(&mut self, slot: usize, suffix: &[u8]) {
if !self.slots[slot].has_suffix() {
self.suffix_count += 1;
}
let offset: usize = self.data.len();
self.data.extend_from_slice(suffix);
self.slots[slot] = SlotMeta {
offset: offset as u32,
len: suffix.len() as u16,
_pad: 0,
};
}
#[expect(clippy::indexing_slicing, reason = "Checked access")]
pub fn try_assign(&mut self, slot: usize, suffix: &[u8]) -> bool {
debug_assert!(slot < WIDTH, "slot {slot} >= WIDTH {WIDTH}");
let suffix_len: usize = suffix.len();
debug_assert!(
u16::try_from(suffix_len).is_ok(),
"suffix too long: {suffix_len} > {}",
u16::MAX
);
let meta: SlotMeta = self.slots[slot];
if meta.has_suffix() && (suffix_len <= (meta.len as usize)) {
let start: usize = meta.offset as usize;
self.data[start..(start + suffix_len)].copy_from_slice(suffix);
#[expect(clippy::cast_possible_truncation)]
{
self.slots[slot] = SlotMeta {
offset: meta.offset,
len: suffix_len as u16,
_pad: 0,
};
}
return true;
}
if (self.data.len() + suffix_len) <= self.data.capacity() {
self.append_and_record(slot, suffix);
return true;
}
self.compact_in_place();
if (self.data.len() + suffix_len) <= self.data.capacity() {
self.append_and_record(slot, suffix);
return true;
}
self.data.reserve(suffix_len);
self.append_and_record(slot, suffix);
false }
#[must_use]
#[inline(always)]
#[expect(
clippy::indexing_slicing,
reason = "Slot bounds checked via caller contract"
)]
pub fn has_suffix(&self, slot: usize) -> bool {
debug_assert!(slot < WIDTH, "slot {slot} >= WIDTH {WIDTH}");
self.slots[slot].has_suffix()
}
#[must_use]
#[inline(always)]
pub fn get(&self, slot: usize) -> Option<&[u8]> {
debug_assert!(slot < WIDTH, "slot {slot} >= WIDTH {WIDTH}");
let meta: SlotMeta = self.slots[slot];
if !meta.has_suffix() {
return None;
}
let start: usize = meta.offset as usize;
let end: usize = start + meta.len as usize;
if end > self.data.len() {
return None;
}
Some(&self.data[start..end])
}
#[must_use]
#[inline(always)]
pub fn get_or_empty(&self, slot: usize) -> &[u8] {
self.get(slot).unwrap_or(&[])
}
#[inline]
pub fn try_assign_append_only(&mut self, slot: usize, suffix: &[u8]) -> bool {
debug_assert!(slot < WIDTH, "slot {slot} >= WIDTH {WIDTH}");
debug_assert!(
u16::try_from(suffix.len()).is_ok(),
"suffix too long: {} > {}",
suffix.len(),
u16::MAX
);
self.try_append_suffix(slot, suffix)
}
#[inline]
#[expect(
clippy::indexing_slicing,
reason = "Slot bounds checked via debug_assert"
)]
pub fn try_assign_in_place(&mut self, slot: usize, suffix: &[u8]) -> bool {
debug_assert!(slot < WIDTH, "slot {slot} >= WIDTH {WIDTH}");
debug_assert!(
u16::try_from(suffix.len()).is_ok(),
"suffix too long: {} > {}",
suffix.len(),
u16::MAX
);
let meta: SlotMeta = self.slots[slot];
if meta.has_suffix() && (suffix.len() <= (meta.len as usize)) {
let start: usize = meta.offset as usize;
self.data[start..(start + suffix.len())].copy_from_slice(suffix);
#[expect(clippy::cast_possible_truncation, reason = "len checked above")]
{
self.slots[slot] = SlotMeta {
offset: meta.offset,
len: suffix.len() as u16,
_pad: 0,
};
}
return true;
}
self.try_append_suffix(slot, suffix)
}
#[inline]
#[expect(clippy::indexing_slicing, reason = "Slot bounds checked by caller")]
fn try_append_suffix(&mut self, slot: usize, suffix: &[u8]) -> bool {
let new_offset: usize = self.data.len();
if (new_offset + suffix.len()) <= self.data.capacity() {
if !self.slots[slot].has_suffix() {
self.suffix_count += 1;
}
self.data.extend_from_slice(suffix);
#[expect(clippy::cast_possible_truncation, reason = "offset and len checked")]
{
self.slots[slot] = SlotMeta {
offset: new_offset as u32,
len: suffix.len() as u16,
_pad: 0,
};
}
return true;
}
false
}
#[inline]
#[expect(
clippy::indexing_slicing,
reason = "Slot bounds checked via debug_assert"
)]
pub fn assign(&mut self, slot: usize, suffix: &[u8]) {
debug_assert!(slot < WIDTH, "slot {slot} >= WIDTH {WIDTH}");
debug_assert!(
u16::try_from(suffix.len()).is_ok(),
"suffix too long: {} > {}",
suffix.len(),
u16::MAX
);
let meta: SlotMeta = self.slots[slot];
if !meta.has_suffix() {
self.suffix_count += 1;
}
let offset: usize = self.data.len();
self.data.extend_from_slice(suffix);
#[expect(
clippy::cast_possible_truncation,
reason = "offset bounded by Vec capacity, len checked above"
)]
{
self.slots[slot] = SlotMeta {
offset: offset as u32,
len: suffix.len() as u16,
_pad: 0,
};
}
}
#[inline(always)]
#[expect(
clippy::indexing_slicing,
reason = "Slot bounds checked via debug_assert"
)]
pub fn clear(&mut self, slot: usize) {
debug_assert!(slot < WIDTH, "slot {slot} >= WIDTH {WIDTH}");
if self.slots[slot].has_suffix() {
self.suffix_count -= 1;
}
self.slots[slot] = SlotMeta::EMPTY;
}
#[must_use]
#[inline(always)]
pub fn suffix_equals(&self, slot: usize, suffix: &[u8]) -> bool {
self.get(slot).is_some_and(|stored: &[u8]| stored == suffix)
}
#[must_use]
#[inline(always)]
pub fn suffix_compare(&self, slot: usize, suffix: &[u8]) -> Option<std::cmp::Ordering> {
self.get(slot).map(|stored: &[u8]| stored.cmp(suffix))
}
}
impl Default for SuffixBag {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
pub struct SuffixBagCell {
inner: UnsafeCell<SuffixBag>,
}
unsafe impl Send for SuffixBagCell {}
impl SuffixBagCell {
#[inline(always)]
pub(crate) fn new() -> Self {
Self {
inner: UnsafeCell::new(SuffixBag::new()),
}
}
#[inline(always)]
pub(crate) const fn from_bag(bag: SuffixBag) -> Self {
Self {
inner: UnsafeCell::new(bag),
}
}
#[inline(always)]
pub(crate) unsafe fn as_ref(&self) -> &SuffixBag {
unsafe { &*self.inner.get() }
}
#[inline(always)]
#[expect(clippy::mut_from_ref, reason = "Interior mutability via UnsafeCell")]
pub(crate) unsafe fn as_mut(&self) -> &mut SuffixBag {
unsafe { &mut *self.inner.get() }
}
}
impl Default for SuffixBagCell {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl Debug for SuffixBagCell {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let bag: &SuffixBag = unsafe { &*self.inner.get() };
f.debug_struct("SuffixBagCell").field("inner", bag).finish()
}
}
#[cfg(test)]
#[expect(clippy::indexing_slicing)]
mod unit_tests;