hangeul 0.1.0

Korean alphabet manipulation library
Documentation
//! Korean alphabet manipulation library for Rust.
//!
//! It is lightweight, without any unicode libraries.
//!
//! [View on GitHub](https://github.com/bekker/hangeul-rs)
//!
//! ```toml
//! [dependencies]
//! hangeul = { git = "https://github.com/bekker/hangeul-rs.git" }
//! ```
//!
//! ## Usage
//!
//! ```rust
//! extern crate hangeul;
//!
//! fn main() {
//!     let subjective = "피카츄";
//!     let sub_josa = match hangeul::ends_with_jongseong(subjective).unwrap() {
//!         true => "이",
//!         false => "가"
//!     };
//!     let sentence = format!("야생의 {}{} 나타났다!", subjective, sub_josa);
//!     println!("{}", sentence); // 야생의 피카츄가 나타났다!
//!     print_choseong(&sentence); // ㅇㅅㅇ ㅍㅋㅊㄱ ㄴㅌㄴㄷ!
//! }
//!
//! fn print_choseong(s:&str) {
//!     for c in s.chars() {
//!         print!("{}", hangeul::get_choseong(c).unwrap_or(c));
//!     }
//!     print!("\n");
//! }
//! ```
//!
//! ## Why not 'Hangul'?
//! 'Hangul' is from old romanization system - McCune–Reischauer system.
//!
//! 'Hangeul' is official in South Korea, since 2000
//!
//! ## License
//! Distributed under MIT License
//!

use std::fmt;
use std::error;

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn decomposition() {
        let han = '';
        let ha = '';
        assert_eq!(get_choseong(han).unwrap(), '');
        assert_eq!(get_jungseong(han).unwrap(), '');
        assert_eq!(get_jongseong(han).unwrap(), '');
        assert_eq!(has_jongseong(han).unwrap(), true);
        assert_eq!(has_jongseong(ha).unwrap(), false);
        get_jongseong(ha).unwrap_err();
    }

    #[test]
    fn check_jamo() {
        assert_eq!(is_jamo(''), true);
        assert_eq!(is_jamo(''), true);
        assert_eq!(is_jamo('a'), false);
        assert_eq!(is_jaeum(''), true);
        assert_eq!(is_jaeum(''), true);
        assert_eq!(is_jaeum(''), false);
        assert_eq!(is_choseong(''), true);
        assert_eq!(is_choseong(''), true);
        assert_eq!(is_choseong(''), true);
        assert_eq!(is_choseong(''), false);
        assert_eq!(is_jongseong(''), true);
        assert_eq!(is_jongseong(''), true);
        assert_eq!(is_jongseong(''), false);
        assert_eq!(is_jongseong(''), true);
    }
}

#[derive(Debug)]
pub enum HangeulError {
    NotSyllable,
    NoJongSeong,
}

impl fmt::Display for HangeulError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            HangeulError::NotSyllable => write!(f, "HangeulError: Not correct Hangeul syllable"),
            HangeulError::NoJongSeong => write!(f, "HangeulError: The syllable has no jongseong")
        }
    }
}

impl error::Error for HangeulError {
    fn description(&self) -> &str {
        match *self {
            HangeulError::NotSyllable => "HangeulError: Not correct Hangeul syllable",
            HangeulError::NoJongSeong => "HangeulError: The syllable has no jongseong",
        }
    }
    fn cause(&self) -> Option<&error::Error> {
        match *self {
            _ => None
        }
    }
}

// These tables are for converting to compatible jamo
const CHOSEONG_TABLE: [u32; 19] = [
    0x01, //    0x02, //    0x04, //    0x07, //    0x08, //    0x09, //    0x11, //    0x12, //    0x13, //    0x15, //    0x16, //    0x17, //    0x18, //    0x19, //    0x1A, //    0x1B, //    0x1C, //    0x1D, //    0x1E, //];

const JONGSEONG_TABLE: [u32; 27] = [
    0x01, //    0x02, //    0x03, //    0x04, //    0x05, //    0x06, //    0x07, //    0x09, //    0x0A, //    0x0B, //    0x0C, //    0x0D, //    0x0E, //    0x0F, //    0x10, //    0x11, //    0x12, //    0x14, //    0x15, //    0x16, //    0x17, //    0x18, //    0x1A, //    0x1B, //    0x1C, //    0x1D, //    0x1E, //];

fn _is_syllable(code:u32) -> bool {
    (code >= 0xAC00 && code <= 0xD7AF)
}

/// Check if the syllable is correct Hangeul syllable
pub fn is_syllable(c:char) -> bool {
    let code = c as u32;
    _is_syllable(code)
}

fn syllable_to_u32(c:char) -> Result<u32, HangeulError> {
    let code = c as u32;
    if _is_syllable(code) {
        Ok(code)
    } else {
        Err(HangeulError::NotSyllable)
    }
}

/// Get choseong (top) of the syllable as compatible jamo
pub fn get_choseong(c:char) -> Result<char, HangeulError> {
    let code = try!(syllable_to_u32(c));
    let x = (code - 0xAC00) / 21 / 28;
    // These unwrap()s are fail-safe
    Ok(std::char::from_u32(CHOSEONG_TABLE[x as usize] + 0x3130).unwrap())
}

/// Get jungseong (middle) of the syllable as compatible jamo
pub fn get_jungseong(c:char) -> Result<char, HangeulError> {
    let code = try!(syllable_to_u32(c));
    Ok(std::char::from_u32(((code - 0xAc00) % (21 * 28)) / 28 + 0x314F).unwrap())
}

/// Get jongseong (bottom) of the syllable as compatible jamo
pub fn get_jongseong(c:char) -> Result<char, HangeulError> {
    let code = try!(syllable_to_u32(c));
    // x should be i32, can be negative
    let x:i32 = (code - 0xAC00) as i32 % 28 - 1;
    if x >= 0 {
        Ok(std::char::from_u32(JONGSEONG_TABLE[x as usize] + 0x3130).unwrap())
    } else {
        Err(HangeulError::NoJongSeong)
    }
}

/// Check if the syllable has jongseong (bottom)
pub fn has_jongseong(c:char) -> Result<bool, HangeulError> {
    let code = try!(syllable_to_u32(c));
    Ok((code - 0xAC00) % 28 != 0)
}

/// Check if the end syllable of the string has jongseong (bottom)
pub fn ends_with_jongseong(s:&str) -> Result<bool, HangeulError> {
    let c = match s.chars().last() {
        Some(x) => x,
        None => return Err(HangeulError::NotSyllable),
    };
    has_jongseong(c)
}

/// Check if the char is compatible jamo
pub fn is_jamo(c:char) -> bool {
    let code = c as u32;
    (code >= 0x3131 && code <= 0x3163)
}

/// Check if the char is compatible jaeum
pub fn is_jaeum(c:char) -> bool {
    let code = c as u32;
    (code >= 0x3131 && code <= 0x314E)
}

/// Check if the char is compatible jamo which can be a choseong (top)
pub fn is_choseong(c:char) -> bool {
    let code_jamo = c as u32 - 0x3130;
    CHOSEONG_TABLE.into_iter().any(|&x| x == code_jamo)
}

/// Check if the char is compatible jamo which can be a jongseong (bottom)
pub fn is_jongseong(c:char) -> bool {
    let code_jamo = c as u32 - 0x3130;
    JONGSEONG_TABLE.into_iter().any(|&x| x == code_jamo)
}