use super::{SubstitutionSet, SubstitutionSetChar};
use libdictenstein::CharUnit;
pub trait SubstitutionPolicy: Copy + Clone {
fn is_allowed(&self, dict_char: u8, query_char: u8) -> bool;
}
pub trait SubstitutionPolicyFor<U: CharUnit>: SubstitutionPolicy {
fn is_allowed_for(&self, dict_unit: U, query_unit: U) -> bool;
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Unrestricted;
impl SubstitutionPolicy for Unrestricted {
#[inline(always)]
fn is_allowed(&self, _dict_char: u8, _query_char: u8) -> bool {
false
}
}
impl<U: CharUnit> SubstitutionPolicyFor<U> for Unrestricted {
#[inline(always)]
fn is_allowed_for(&self, _dict_unit: U, _query_unit: U) -> bool {
false
}
}
#[derive(Copy, Clone, Debug)]
pub struct Restricted<'a> {
set: &'a SubstitutionSet,
}
impl<'a> Restricted<'a> {
#[inline]
pub fn new(set: &'a SubstitutionSet) -> Self {
Self { set }
}
#[inline]
pub fn set(&self) -> &'a SubstitutionSet {
self.set
}
}
impl<'a> SubstitutionPolicy for Restricted<'a> {
#[inline(always)]
fn is_allowed(&self, dict_char: u8, query_char: u8) -> bool {
dict_char == query_char || self.set.contains(dict_char, query_char)
}
}
impl<'a> SubstitutionPolicyFor<u8> for Restricted<'a> {
#[inline(always)]
fn is_allowed_for(&self, dict_unit: u8, query_unit: u8) -> bool {
dict_unit == query_unit || self.set.contains(dict_unit, query_unit)
}
}
pub trait SubstitutionPolicyChar: Copy + Clone {
fn is_allowed(&self, dict_char: char, query_char: char) -> bool;
}
impl SubstitutionPolicyChar for Unrestricted {
#[inline(always)]
fn is_allowed(&self, _dict_char: char, _query_char: char) -> bool {
false
}
}
#[derive(Copy, Clone, Debug)]
pub struct RestrictedChar<'a> {
set: &'a SubstitutionSetChar,
}
impl<'a> RestrictedChar<'a> {
#[inline]
pub fn new(set: &'a SubstitutionSetChar) -> Self {
Self { set }
}
#[inline]
pub fn set(&self) -> &'a SubstitutionSetChar {
self.set
}
}
impl<'a> SubstitutionPolicyChar for RestrictedChar<'a> {
#[inline(always)]
fn is_allowed(&self, dict_char: char, query_char: char) -> bool {
dict_char == query_char || self.set.contains(dict_char, query_char)
}
}
impl<'a> SubstitutionPolicy for RestrictedChar<'a> {
#[inline(always)]
fn is_allowed(&self, _dict_char: u8, _query_char: u8) -> bool {
unreachable!("RestrictedChar::is_allowed(u8) should never be called - use SubstitutionPolicyFor<char> instead")
}
}
impl<'a> SubstitutionPolicyFor<char> for RestrictedChar<'a> {
#[inline(always)]
fn is_allowed_for(&self, dict_unit: char, query_unit: char) -> bool {
dict_unit == query_unit || self.set.contains(dict_unit, query_unit)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unrestricted_size_is_zero() {
assert_eq!(
std::mem::size_of::<Unrestricted>(),
0,
"Unrestricted must be zero-sized for ZST optimization"
);
}
#[test]
fn test_unrestricted_no_zero_cost_substitutions() {
let policy = Unrestricted;
assert!(!SubstitutionPolicy::is_allowed(&policy, b'a', b'b'));
assert!(!SubstitutionPolicy::is_allowed(&policy, b'x', b'y'));
assert!(!SubstitutionPolicy::is_allowed(&policy, b'1', b'2'));
assert!(!SubstitutionPolicy::is_allowed(&policy, 0, 255));
}
#[test]
fn test_restricted_basic() {
let mut set = SubstitutionSet::new();
set.allow_byte(b'a', b'b');
set.allow_byte(b'x', b'y');
let policy = Restricted::new(&set);
assert!(policy.is_allowed(b'a', b'a'));
assert!(policy.is_allowed(b'z', b'z'));
assert!(policy.is_allowed(b'a', b'b'));
assert!(policy.is_allowed(b'x', b'y'));
assert!(!policy.is_allowed(b'a', b'c'));
assert!(!policy.is_allowed(b'b', b'a')); }
#[test]
fn test_policy_is_copy() {
let policy1 = Unrestricted;
let policy2 = policy1;
assert!(!SubstitutionPolicy::is_allowed(&policy1, b'a', b'b'));
assert!(!SubstitutionPolicy::is_allowed(&policy2, b'x', b'y'));
}
#[test]
fn test_restricted_zero_cost_substitutions() {
let mut set = SubstitutionSet::new();
set.allow('c', 'k');
set.allow('k', 'c');
let policy = Restricted::new(&set);
assert!(policy.is_allowed(b'c', b'k'), "c->k should be allowed");
assert!(policy.is_allowed(b'k', b'c'), "k->c should be allowed");
assert!(policy.is_allowed(b'c', b'c'), "c==c should be allowed");
assert!(policy.is_allowed(b'k', b'k'), "k==k should be allowed");
assert!(!policy.is_allowed(b'a', b'b'), "a->b should NOT be allowed");
assert!(!policy.is_allowed(b'c', b'z'), "c->z should NOT be allowed");
}
#[test]
fn test_unrestricted_char_policy() {
let policy = Unrestricted;
assert!(!SubstitutionPolicyChar::is_allowed(&policy, 'α', 'β'));
assert!(!SubstitutionPolicyChar::is_allowed(&policy, '你', '好'));
assert!(!SubstitutionPolicyChar::is_allowed(&policy, 'é', 'e'));
}
#[test]
fn test_restricted_char_basic() {
use crate::transducer::SubstitutionSetChar;
let mut set = SubstitutionSetChar::new();
set.allow('α', 'β');
set.allow('你', '好');
let policy = RestrictedChar::new(&set);
assert!(policy.is_allowed_for('α', 'α'));
assert!(policy.is_allowed_for('z', 'z'));
assert!(policy.is_allowed_for('α', 'β'));
assert!(policy.is_allowed_for('你', '好'));
assert!(!policy.is_allowed_for('α', 'γ'));
assert!(!policy.is_allowed_for('β', 'α')); }
#[test]
fn test_restricted_char_diacritics() {
use crate::transducer::SubstitutionSetChar;
let mut set = SubstitutionSetChar::new();
set.allow('é', 'e');
set.allow('e', 'é');
set.allow('ñ', 'n');
set.allow('n', 'ñ');
let policy = RestrictedChar::new(&set);
assert!(policy.is_allowed_for('é', 'e'), "é->e should be allowed");
assert!(policy.is_allowed_for('e', 'é'), "e->é should be allowed");
assert!(policy.is_allowed_for('ñ', 'n'), "ñ->n should be allowed");
assert!(policy.is_allowed_for('n', 'ñ'), "n->ñ should be allowed");
assert!(policy.is_allowed_for('é', 'é'), "é==é should be allowed");
assert!(policy.is_allowed_for('e', 'e'), "e==e should be allowed");
assert!(
!policy.is_allowed_for('a', 'b'),
"a->b should NOT be allowed"
);
assert!(
!policy.is_allowed_for('é', 'x'),
"é->x should NOT be allowed"
);
}
#[test]
fn test_policy_char_is_copy() {
let policy1: Unrestricted = Unrestricted;
let policy2 = policy1;
assert!(!SubstitutionPolicyChar::is_allowed(&policy1, 'α', 'β'));
assert!(!SubstitutionPolicyChar::is_allowed(&policy2, '你', '好'));
}
}