sejong 0.1.5

Sejong Buffer is a buffer that can receive ASCII bytes different from keyboard and send out UTF-32 Hangul string. This buffer allows deletion by Jamo.
Documentation
use super::*;
use std::convert::{TryFrom, TryInto};

#[derive(Clone, Copy, Debug)]
pub(crate) enum Syllable {
    Initial(InitialConsonant),
    Medial(InitialConsonant, MedialVowel),
    Final(InitialConsonant, MedialVowel, FinalConsonant),
    VowelOnly(MedialVowel),
}

impl Syllable {
    pub fn put(&mut self, byte: Byte) -> Option<Byte> {
        if let Some(new) = self.update(byte) {
            *self = new;
            None
        } else {
            Some(byte)
        }
    }
    pub fn try_split_with_vowel(&mut self, byte: Byte) -> Result<Self, Byte> {
        if !byte.is_vowel() {
            return Err(byte);
        }
        if let Self::Final(ic, mv, fc) = self {
            if let Ok(new_ic) = InitialConsonant::try_from(*fc) {
                let mut splitted = Self::Initial(new_ic);
                if let Some(byte) = splitted.put(byte) {
                    return Err(byte);
                }
                *self = Self::Medial(*ic, *mv);
                Ok(splitted)
            } else {
                let split_result: Result<(FinalConsonant, InitialConsonant), FinalConsonant> =
                    (*fc).try_into();
                if let Ok(consonants) = split_result {
                    let mut splitted = Self::Initial(consonants.1);
                    if let Some(byte) = splitted.put(byte) {
                        return Err(byte);
                    }
                    *self = Self::Final(*ic, *mv, consonants.0);
                    Ok(splitted)
                } else {
                    Err(byte)
                }
            }
        } else {
            Err(byte)
        }
    }

    pub fn remove_last(&mut self) -> Option<()> {
        match match self {
            Self::Medial(ic, mv) => match mv.try_remove_second_half() {
                Some(new_mv) => Some(Self::Medial(*ic, new_mv)),
                None => Some(Self::Initial(*ic)),
            },
            Self::Final(ic, mv, fc) => match fc.try_remove_second_half() {
                Some(new_fc) => Some(Self::Final(*ic, *mv, new_fc)),
                None => Some(Self::Medial(*ic, *mv)),
            },
            _ => None,
        } {
            Some(new) => {
                *self = new;
                Some(())
            }
            _ => None,
        }
    }

    fn update(&self, byte: Byte) -> Option<Self> {
        match self {
            Self::Initial(ic) => Self::handle_initial(ic, byte),
            Self::Medial(ic, mv) => Self::handle_medial(ic, mv, byte),
            Self::Final(ic, mv, fc) => Self::handle_final(ic, mv, fc, byte),
            Self::VowelOnly(_) => None,
        }
    }

    fn handle_initial(ic: &InitialConsonant, byte: Byte) -> Option<Self> {
        match MedialVowel::try_from(byte) {
            Ok(mv) => Some(Self::Medial(*ic, mv)),
            Err(_) => None,
        }
    }

    fn handle_medial(ic: &InitialConsonant, mv: &MedialVowel, byte: Byte) -> Option<Self> {
        if let Ok(added) = MedialVowel::try_from((*mv, byte)) {
            return Some(Self::Medial(*ic, added));
        }
        match FinalConsonant::try_from(byte) {
            Ok(fc) => Some(Self::Final(*ic, *mv, fc)),
            Err(_) => None,
        }
    }

    fn handle_final(
        ic: &InitialConsonant,
        mv: &MedialVowel,
        fc: &FinalConsonant,
        byte: Byte,
    ) -> Option<Self> {
        match FinalConsonant::try_from((*fc, byte)) {
            Ok(new) => Some(Self::Final(*ic, *mv, new)),
            Err(_) => None,
        }
    }
}

impl Into<char> for Syllable {
    fn into(self) -> char {
        match self {
            Self::Initial(ic) => ic.into(),
            Self::Medial(ic, mv) => calculate_syllable_u32(ic as u32, mv as u32, 0),
            Self::Final(ic, mv, fc) => calculate_syllable_u32(ic as u32, mv as u32, fc as u32),
            Self::VowelOnly(v) => v.into(),
        }
    }
}

// the formula comes from this Wikipedia page:
// https://en.wikipedia.org/wiki/Korean_language_and_computers#Hangul_Syllables_block
fn calculate_syllable_u32(initial_consonant: u32, medial_vowel: u32, final_consonant: u32) -> char {
    unsafe {
        std::char::from_u32_unchecked(
            initial_consonant * 588 + medial_vowel * 28 + final_consonant + 44032,
        )
    }
}

impl From<Byte> for Syllable {
    fn from(b: Byte) -> Self {
        if b.is_consonant() {
            Self::Initial(InitialConsonant::try_from(b).unwrap())
        } else {
            Self::VowelOnly(MedialVowel::try_from(b).unwrap())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_into_char() {
        let mut syllable1 = Syllable::from(Byte::M);
        syllable1.put(Byte::A);
        syllable1.put(Byte::N);
        let char1: char = syllable1.into();
        assert_eq!(char1, '');
    }

    #[test]
    fn size_of_syllable() {
        let size = std::mem::size_of::<Syllable>();
        assert_eq!(4, size);
    }

    #[test]
    fn guard_from_byte() {
        let pairs = vec![
            (Byte::TT, ''),
            (Byte::YAE, ''),
            (Byte::YE, ''),
            (Byte::PP, ''),
            (Byte::KK, ''),
            (Byte::SS, ''),
            (Byte::JJ, ''),
            (Byte::M, ''),
            (Byte::YU, ''),
            (Byte::CH, ''),
            (Byte::NG, ''),
            (Byte::D, ''),
            (Byte::R, ''),
            (Byte::H, ''),
            (Byte::O, ''),
            (Byte::YA, ''),
            (Byte::EO, ''),
            (Byte::A, ''),
            (Byte::I, ''),
            (Byte::EU, ''),
            (Byte::U, ''),
            (Byte::AE, ''),
            (Byte::E, ''),
            (Byte::B, ''),
            (Byte::G, ''),
            (Byte::N, ''),
            (Byte::S, ''),
            (Byte::YEO, ''),
            (Byte::P, ''),
            (Byte::J, ''),
            (Byte::T, ''),
            (Byte::YO, ''),
            (Byte::K, ''),
        ];
        for pair in pairs {
            let c: char = Syllable::from(pair.0).into();
            assert_eq!(c, pair.1)
        }
    }
}