kakasi 0.1.0

Romanize hiragana, katakana and kanji (Japanese text)
Documentation
use byteorder::{ByteOrder, LittleEndian};
use phf::PhfHash;

const LEN_ENTRY: usize = 5;
const START_DISPS: usize = 16;

pub struct PhfMap {
    data: &'static [u8],
    key: u64,
    n_disps: u32,
    n_entries: u32,
    start_entries: usize,
    start_entry_data: usize,
}

pub trait Decodable {
    fn decode(data: &'static [u8]) -> Self;
}

impl PhfMap {
    pub fn new(data: &'static [u8]) -> Self {
        let n_disps = LittleEndian::read_u32(&data[8..12]);
        let n_entries = LittleEndian::read_u32(&data[12..16]);
        let start_entries = START_DISPS + n_disps as usize * 8;
        let start_entry_data = start_entries + n_entries as usize * LEN_ENTRY;

        Self {
            data,
            key: LittleEndian::read_u64(&data[0..8]),
            n_disps,
            n_entries,
            start_entries,
            start_entry_data,
        }
    }

    fn displacement(&self, i: usize) -> (u32, u32) {
        let start = START_DISPS + i * 8;
        (
            LittleEndian::read_u32(&self.data[start..start + 4]),
            LittleEndian::read_u32(&self.data[start + 4..start + 8]),
        )
    }

    fn entry(&self, i: usize) -> (u32, u8, u8) {
        let start = self.start_entries + i * LEN_ENTRY;
        (
            LittleEndian::read_u24(&self.data[start..start + 3]),
            self.data[start + 3],
            self.data[start + 4],
        )
    }

    fn entry_data<K: Decodable, V: Decodable>(&self, i: usize) -> (K, V) {
        let (i, k_len, v_len) = self.entry(i);

        let k_start = self.start_entry_data + i as usize;
        let v_start = k_start + k_len as usize;
        let k_bytes = &self.data[k_start..v_start];
        let v_bytes = &self.data[v_start..v_start + v_len as usize];

        (K::decode(k_bytes), V::decode(v_bytes))
    }

    pub fn get<K: PhfHash + Eq + Decodable, V: Decodable>(&self, key: K) -> Option<V> {
        let hashes = phf_shared::hash(&key, &self.key);
        let (d1, d2) = self.displacement((hashes.g % self.n_disps) as usize);
        let i = phf_shared::displace(hashes.f1, hashes.f2, d1, d2) % self.n_entries;
        let (k, v) = self.entry_data::<K, V>(i as usize);
        if k == key {
            Some(v)
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::types::{KanjiString, Readings};

    use super::*;

    #[test]
    fn lookup() {
        let phfmap = PhfMap::new(crate::util::KANJI_DICT);

        dbg!(phfmap.get::<KanjiString, Readings>(KanjiString::new("")));
    }

    #[test]
    fn utf16() {
        let t = "Abc 描 😅";

        let bytes = t
            .encode_utf16()
            .flat_map(|c16| [((c16 & 0xff00) >> 8) as u8, (c16 & 0xff) as u8])
            .collect::<Vec<_>>();

        let decoded = String::from_utf16(
            &bytes
                .chunks_exact(2)
                .map(|c| ((c[0] as u16) << 8) | c[1] as u16)
                .collect::<Vec<_>>(),
        )
        .unwrap();

        assert_eq!(decoded, t);
    }
}