use std::borrow::Cow;
use thiserror::Error;
use crate::compressor::core::shared::dictionary::{
BUILTIN_OFFSETS_BY_LENGTH, BUILTIN_SIZE_BITS_BY_LENGTH, BUILTIN_WORDS,
};
use super::transform::MAX_WORD_LENGTH;
pub(crate) const MIN_WORD_LENGTH: usize = 4;
pub(crate) const NUM_ENCODED_LENGTHS: usize = MAX_WORD_LENGTH - MIN_WORD_LENGTH + 1;
pub(crate) const MAX_SIZE_BITS: u8 = 15;
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum WordListError {
#[error("length {length} claims 2^{bits} words, past the limit of 2^{MAX_SIZE_BITS}")]
TooManySizeBits {
length: usize,
bits: u8,
},
#[error("the size bits describe {expected} bytes of words, but {found} were given")]
DataLength {
expected: usize,
found: usize,
},
}
#[derive(Debug, Clone)]
pub(crate) struct WordList {
size_bits_by_length: [u8; MAX_WORD_LENGTH + 1],
offsets_by_length: [u32; MAX_WORD_LENGTH + 1],
data: Cow<'static, [u8]>,
}
impl WordList {
pub(crate) fn builtin() -> Self {
let mut size_bits_by_length = [0u8; MAX_WORD_LENGTH + 1];
let mut offsets_by_length = [0u32; MAX_WORD_LENGTH + 1];
size_bits_by_length.copy_from_slice(&BUILTIN_SIZE_BITS_BY_LENGTH[..=MAX_WORD_LENGTH]);
offsets_by_length.copy_from_slice(&BUILTIN_OFFSETS_BY_LENGTH[..=MAX_WORD_LENGTH]);
Self {
size_bits_by_length,
offsets_by_length,
data: Cow::Borrowed(&BUILTIN_WORDS[..]),
}
}
pub(crate) fn from_parts(
size_bits: &[u8; NUM_ENCODED_LENGTHS],
data: Cow<'static, [u8]>,
) -> Result<Self, WordListError> {
let mut size_bits_by_length = [0u8; MAX_WORD_LENGTH + 1];
for (index, &bits) in size_bits.iter().enumerate() {
let length = MIN_WORD_LENGTH + index;
if bits > MAX_SIZE_BITS {
return Err(WordListError::TooManySizeBits { length, bits });
}
size_bits_by_length[length] = bits;
}
let (offsets_by_length, expected) = offsets(&size_bits_by_length);
if data.len() != expected {
return Err(WordListError::DataLength {
expected,
found: data.len(),
});
}
Ok(Self {
size_bits_by_length,
offsets_by_length,
data,
})
}
pub(crate) fn size_bits(&self, length: usize) -> u8 {
self.size_bits_by_length
.get(length)
.copied()
.unwrap_or_default()
}
pub(crate) fn word_count(&self, length: usize) -> usize {
match self.size_bits(length) {
0 => 0,
bits => 1usize << bits,
}
}
pub(crate) fn offset(&self, length: usize) -> usize {
self.offsets_by_length
.get(length)
.copied()
.unwrap_or_default() as usize
}
pub(crate) fn word(&self, length: usize, index: usize) -> &[u8] {
if index >= self.word_count(length) {
return &[];
}
let start = self.offset(length) + index * length;
self.data.get(start..start + length).unwrap_or_default()
}
pub(crate) fn data(&self) -> &[u8] {
&self.data
}
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub(crate) fn wire_len(&self) -> usize {
NUM_ENCODED_LENGTHS + self.data.len()
}
pub(crate) fn serialize(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.size_bits_by_length[MIN_WORD_LENGTH..=MAX_WORD_LENGTH]);
out.extend_from_slice(&self.data);
}
#[cfg(test)]
pub(crate) fn encoded_size_bits(&self) -> [u8; NUM_ENCODED_LENGTHS] {
let mut encoded = [0u8; NUM_ENCODED_LENGTHS];
encoded.copy_from_slice(&self.size_bits_by_length[MIN_WORD_LENGTH..=MAX_WORD_LENGTH]);
encoded
}
}
fn offsets(size_bits_by_length: &[u8; MAX_WORD_LENGTH + 1]) -> ([u32; MAX_WORD_LENGTH + 1], usize) {
let mut offsets = [0u32; MAX_WORD_LENGTH + 1];
let mut position = 0u32;
for (length, &bits) in size_bits_by_length.iter().enumerate() {
offsets[length] = position;
if bits != 0 {
position += (length as u32) << bits;
}
}
(offsets, position as usize)
}
#[cfg(test)]
mod tests {
use super::*;
fn list(length: usize, bits: u8, fill: u8) -> WordList {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[length - MIN_WORD_LENGTH] = bits;
let data = vec![fill; length << bits];
WordList::from_parts(&size_bits, Cow::Owned(data)).expect("well formed")
}
#[test]
fn the_builtin_list_is_the_reference_one() {
let words = WordList::builtin();
assert_eq!(words.data().len(), 122_784);
assert_eq!(words.word(4, 0), b"time");
assert_eq!(words.word_count(4), 1024);
assert_eq!(words.size_bits(4), 10);
}
#[test]
fn the_builtin_offsets_tile_every_length() {
let words = WordList::builtin();
let mut expected = 0usize;
for length in MIN_WORD_LENGTH..=MAX_WORD_LENGTH {
assert_eq!(words.offset(length), expected, "length {length}");
expected += length * words.word_count(length);
}
assert_eq!(expected, words.data().len());
}
#[test]
fn a_zero_exponent_means_no_words_rather_than_one() {
let words = WordList::from_parts(&[0; NUM_ENCODED_LENGTHS], Cow::Owned(Vec::new()))
.expect("well formed");
assert_eq!(words.word_count(4), 0);
assert_eq!(words.word(4, 0), b"");
assert!(words.is_empty());
}
#[test]
fn words_are_addressed_by_length_and_index() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let data = b"abcdefgh".to_vec();
let words = WordList::from_parts(&size_bits, Cow::Owned(data)).expect("well formed");
assert_eq!(words.word(4, 0), b"abcd");
assert_eq!(words.word(4, 1), b"efgh");
assert_eq!(words.word(4, 2), b"");
assert_eq!(words.word(5, 0), b"");
}
#[test]
fn an_exponent_past_the_limit_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = MAX_SIZE_BITS + 1;
assert_eq!(
WordList::from_parts(&size_bits, Cow::Owned(Vec::new())).err(),
Some(WordListError::TooManySizeBits {
length: MIN_WORD_LENGTH,
bits: MAX_SIZE_BITS + 1,
})
);
}
#[test]
fn data_of_the_wrong_length_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
assert_eq!(
WordList::from_parts(&size_bits, Cow::Owned(vec![0; 7])).err(),
Some(WordListError::DataLength {
expected: 8,
found: 7,
})
);
}
#[test]
fn the_largest_list_a_length_may_hold_is_accepted() {
let words = list(MAX_WORD_LENGTH, MAX_SIZE_BITS, b'z');
assert_eq!(words.word_count(MAX_WORD_LENGTH), 1 << MAX_SIZE_BITS);
assert_eq!(words.data().len(), MAX_WORD_LENGTH << MAX_SIZE_BITS);
}
#[test]
fn serializing_round_trips_through_the_wire_layout() {
let words = list(6, 2, b'q');
let mut bytes = Vec::new();
words.serialize(&mut bytes);
assert_eq!(bytes.len(), words.wire_len());
let (encoded, data) = bytes.split_at(NUM_ENCODED_LENGTHS);
let size_bits: [u8; NUM_ENCODED_LENGTHS] = encoded.try_into().expect("28 bytes");
let parsed = WordList::from_parts(&size_bits, Cow::Owned(data.to_vec()))
.expect("what was written parses");
assert_eq!(parsed.encoded_size_bits(), words.encoded_size_bits());
assert_eq!(parsed.data(), words.data());
}
#[test]
fn the_encoded_size_bits_cover_lengths_four_to_thirty_one() {
let words = list(MAX_WORD_LENGTH, 1, b'a');
let encoded = words.encoded_size_bits();
assert_eq!(encoded.len(), NUM_ENCODED_LENGTHS);
assert_eq!(encoded[NUM_ENCODED_LENGTHS - 1], 1);
assert_eq!(encoded[0], 0);
}
#[test]
fn a_length_outside_the_covered_range_holds_nothing() {
let words = WordList::builtin();
assert_eq!(words.word_count(3), 0);
assert_eq!(words.word_count(32), 0);
assert_eq!(words.size_bits(99), 0);
assert_eq!(words.offset(99), 0);
}
}