use std::collections::HashSet;
use std::sync::OnceLock;
use crate::error::Result;
use crate::normalized_string::NormalizedString;
use crate::pre_tokenizers::impl_unit_serde;
use crate::traits::Normalizer;
pub(crate) fn bytes_to_chars() -> &'static [char; 256] {
static TABLE: OnceLock<[char; 256]> = OnceLock::new();
TABLE.get_or_init(|| {
let mut table = ['\0'; 256];
let mut n = 0u32;
for b in 0..=255u8 {
let printable = matches!(b, b'!'..=b'~' | 0xA1..=0xAC | 0xAE..=0xFF);
table[b as usize] = if printable {
char::from(b)
} else {
let c = char::from_u32(256 + n).expect("valid codepoint");
n += 1;
c
};
}
table
})
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ByteLevel;
impl_unit_serde!(ByteLevel);
impl ByteLevel {
pub fn new() -> Self {
Self
}
pub fn alphabet() -> HashSet<char> {
bytes_to_chars().iter().copied().collect()
}
}
impl Normalizer for ByteLevel {
fn normalize(&self, normalized: &mut NormalizedString) -> Result<()> {
if normalized.is_empty() {
return Ok(());
}
let table = bytes_to_chars();
let mut dest = Vec::with_capacity(normalized.len());
let mut buf = [0u8; 4];
for c in normalized.get().chars() {
for (i, b) in c.encode_utf8(&mut buf).bytes().enumerate() {
dest.push((table[b as usize], isize::from(i > 0)));
}
}
normalized.transform(dest, 0);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::normalized_string::OffsetRange;
#[test]
fn matches_hf() {
let mut ns = NormalizedString::from("Hello world");
ByteLevel.normalize(&mut ns).unwrap();
assert_eq!(ns.get(), "HelloĠworld");
let mut ns = NormalizedString::from("é😀");
ByteLevel.normalize(&mut ns).unwrap();
assert_eq!(ns.get(), "éðŁĺĢ");
assert_eq!(
ns.convert_offsets(OffsetRange::Normalized(0..4)),
Some(0..2)
);
}
#[test]
fn alphabet_has_256_distinct_chars() {
assert_eq!(ByteLevel::alphabet().len(), 256);
assert_eq!(bytes_to_chars()[b' ' as usize], 'Ġ');
assert_eq!(bytes_to_chars()[b'\n' as usize], 'Ċ');
}
}