use super::fast_log::log2_floor_non_zero;
const LITERAL_BYTE_SCORE: usize = 135;
const DISTANCE_BIT_PENALTY: usize = 30;
pub(crate) const SCORE_BASE: usize = DISTANCE_BIT_PENALTY * 8 * size_of::<usize>();
pub(crate) const MIN_SCORE: usize = SCORE_BASE + 100;
pub(crate) const fn backward_reference_score(copy_length: usize, offset: usize) -> usize {
SCORE_BASE + LITERAL_BYTE_SCORE * copy_length
- DISTANCE_BIT_PENALTY * log2_floor_non_zero(offset) as usize
}
pub(crate) const fn backward_reference_score_using_last_distance(copy_length: usize) -> usize {
LITERAL_BYTE_SCORE * copy_length + SCORE_BASE + 15
}
pub(crate) const fn backward_reference_penalty_using_last_distance(index: usize) -> usize {
39 + ((0x1CA10usize >> (index & 0xE)) & 0xE)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct SearchResult {
pub(crate) len: usize,
pub(crate) distance: usize,
pub(crate) score: usize,
pub(crate) len_code_delta: i32,
}
impl SearchResult {
pub(crate) const fn empty() -> Self {
Self {
len: 0,
distance: 0,
score: MIN_SCORE,
len_code_delta: 0,
}
}
pub(crate) const fn is_match(&self) -> bool {
self.score > MIN_SCORE
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn score_constants_match_the_reference() {
assert_eq!(SCORE_BASE, 1920);
assert_eq!(MIN_SCORE, 2020);
}
#[test]
fn a_longer_match_scores_higher_at_the_same_distance() {
assert!(backward_reference_score(5, 100) < backward_reference_score(6, 100));
}
#[test]
fn a_nearer_match_scores_higher_at_the_same_length() {
assert!(backward_reference_score(5, 1 << 20) < backward_reference_score(5, 4));
assert_eq!(
backward_reference_score(5, 4) - backward_reference_score(5, 8),
DISTANCE_BIT_PENALTY
);
}
#[test]
fn a_cached_distance_beats_the_same_match_spelled_out() {
assert!(
backward_reference_score(4, 1024) < backward_reference_score_using_last_distance(4)
);
}
#[test]
fn cache_penalties_match_the_reference_table() {
let expected = [
39usize, 39, 43, 43, 39, 39, 47, 47, 49, 49, 41, 41, 51, 51, 45, 45,
];
for (index, &value) in expected.iter().enumerate() {
assert_eq!(
backward_reference_penalty_using_last_distance(index),
value,
"cache slot {index}"
);
}
}
#[test]
fn a_penalised_cached_score_never_underflows() {
for index in 0usize..16 {
let score = backward_reference_score_using_last_distance(2);
assert!(score > backward_reference_penalty_using_last_distance(index));
}
}
#[test]
fn an_empty_result_is_not_a_match() {
let empty = SearchResult::empty();
assert!(!empty.is_match());
assert_eq!(empty.score, MIN_SCORE);
let found = SearchResult {
score: MIN_SCORE + 1,
..empty
};
assert!(found.is_match());
}
}