tossicat/
hangeul.rs

1//! # 한글을 처리하는 모듈
2//!
3//! ## is_hangeul
4//! 해당 글자가 한글인지를 체크하는 함수.
5//! ```text
6//! ex) 글 -> True  
7//! ```
8//!
9//! ## join_phonemes
10//! 초,중,종성을 하나의 글자로 합쳐주는 함수.
11//! ```text
12//! ex) ['ㄱ','ㅡ','ㄹ'] -> '글'  
13//! ex) ['ㅈ','ㅏ',' '] -> '자'  
14//! ```
15//!
16//! ## split_phonemes
17//! 한글자를 초,중,종성으로 구분하는 함수.
18//! 종성이 없는 경우에는 ' '으로 치환된다.  
19//! ```text
20//! ex) '글' -> ['ㄱ','ㅡ','ㄹ']  
21//! ex) '자' -> ['ㅈ','ㅏ',' ']  
22//! ```
23
24// 초성, 중성, 종성 배열 정의
25const INITIAL: [char; 19] = [
26    'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ',
27    'ㅌ', 'ㅍ', 'ㅎ',
28];
29const MEDIAL: [char; 21] = [
30    'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ',
31    'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ',
32];
33const FINAL: [char; 28] = [
34    ' ', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ',
35    'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ',
36];
37
38/// ### 한글인지 체크하는 함수
39/// 사용법은 아래 `_is_hangeul()` 참고
40pub fn is_hangeul(word: char) -> bool {
41    ('가'..='힣').contains(&word)
42}
43
44/// 자음인지 체크하는 함수
45fn is_consonant(word: char) -> bool {
46    ('ㄱ'..='ㅎ').contains(&word)
47}
48
49/// 모음인지 체크하는 함수
50fn is_medial(word: char) -> bool {
51    ('ㅏ'..='ㅣ').contains(&word)
52}
53
54/// ## 한글 음절인지 아닌지 체크하는 함수
55/// 초,중,종성으로 들어온 것이 합치면 적절하게 한글 음절이 될 수 없는지 있는지를 판단하는 함수
56/// 사용법: 이 모둘 아래 tests 모듈, _hangeul.rs 참고
57
58fn is_hangul_syllable(word: [char; 3]) -> bool {
59    if is_consonant(word[0]) && is_medial(word[1]) {
60        let res = FINAL.iter().position(|&s| s == word[2]);
61        res.is_some()
62    } else {
63        false
64    }
65}
66
67/// ## 초,중,종성을 하나의 글자로 합쳐주는 함수
68/// 이 함수는 기본적으로 입력된 것이 종성까지 가지고 있는다고 가정하고 작성하였다.
69/// 사용하기 위해서는 종성이 없는 경우에도 다음과 같이 종성 자리에 ` `를 넣어야 한다.
70/// ```rust
71///    let temp = ['ㄱ', 'ㅏ', 'ㄴ'];
72///    assert_eq!('간', tossicat::join_phonemes(temp));
73///    let temp = ['ㄱ', 'ㅏ', ' '];
74///    assert_eq!('가', tossicat::join_phonemes(temp));
75/// ```
76/// 사용법 tests 모듈, /tests/_is_hangul_syllable.rs 참고
77pub fn join_phonemes(word: [char; 3]) -> char {
78    //한글이 아닌 경우에는 입력된 첫 번째 글자 반환합니다.
79    if !is_hangul_syllable(word) {
80        return word[0];
81    }
82    // 파라미터로 받은 초,중,종성 인덱스 추출
83    let idx_begin = INITIAL.iter().position(|&x| x == word[0]).unwrap();
84    let idx_middle = MEDIAL.iter().position(|&x| x == word[1]).unwrap();
85    let idx_end = FINAL.iter().position(|&x| x == word[2]).unwrap();
86    // 추가될 값 계산
87    let initial = '가' as u32;
88    let offset = ((idx_begin * MEDIAL.len() + idx_middle) * FINAL.len() + idx_end) as u32;
89    char::from_u32(initial + offset).unwrap()
90}
91
92/// ## 입력된 한 글자에서 그 글자의 종성을 바꿔주는 함수
93/// 이 함수는 입력된 한글 한 글자에서 입력된 값으로 종성을 바꿔 반환한다.
94/// 이때 입력된 한 글자가 한글이 아닌 경우와
95/// 바꾸기 위해 입력한 자모가 한글 종성 자모에 쓰일 수 없는 것이면
96/// 입력된 글자 그대로를 반환한다.
97/// ```rust
98///    let temp = '정';
99///    assert_eq!('점', tossicat::modify_finall_jamo(temp, 'ㅁ'));
100///    let temp = '감';
101///    assert_eq!('강', tossicat::modify_finall_jamo(temp, 'ㅇ'));
102/// ```
103/// 사용법 tests 모듈, /tests/_is_modify_finall_jamo.rs 참고
104
105pub fn modify_finall_jamo(letter: char, jamo: char) -> char {
106    if !is_hangeul(letter) {
107        return letter;
108    }
109    if FINAL.contains(&jamo) {
110        let mut splited_letter = split_phonemes(letter);
111        splited_letter[2] = jamo;
112        join_phonemes(splited_letter)
113    } else {
114        letter
115    }
116}
117
118/// ## 입력된 한 글자를 초, 중, 종성으로 구분해 반환하는 함수
119/// 이 함수는 기본적으로 입력된 것이 종성이 없는 경우에도 종성을 스페이스, 즉 `' '`으로 반환한다.
120/// 사용법은 tests 모듈, /tests/hangeul.rs 참고
121pub fn split_phonemes(word: char) -> [char; 3] {
122    // 조,중,종성을 담을 배열 정의
123    let mut phonemes: [char; 3] = [' '; 3];
124    // 받은 문자가 한글인지 확인, 한글이 아닐 경우 배열 첫번째 요소에 그대로 출력
125    if !is_hangeul(word) {
126        phonemes[0] = word;
127        return phonemes;
128    }
129    //'가'와의 차이값 계산
130    let unicode = word as u32;
131    let initial = '가' as u32;
132    let offset = unicode - initial;
133    //초,중,종성 값 계산
134    //초성
135    let idx_begin: usize = (offset / (21 * 28)) as usize;
136    phonemes[0] = INITIAL[idx_begin];
137    //중성
138    let idx_middle: usize = ((offset / 28) % 21) as usize;
139    phonemes[1] = MEDIAL[idx_middle];
140    //종성은 있는 경우에만 계산
141    if (((unicode - 0xAC00) % (21 * 28)) % 28) != 0 {
142        let idx_end: usize = (offset % 28) as usize;
143        phonemes[2] = FINAL[idx_end];
144    }
145    //초,중,종성이 배열로 묶여서 전달
146    phonemes
147}
148
149/// 비 공개 함수를 테스트합니다.
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn _is_hangeul() {
156        let temp = '똠';
157        assert_eq!(true, is_hangeul(temp));
158
159        let temp = 'a';
160        assert_eq!(false, is_hangeul(temp));
161
162        let temp = '😀';
163        assert_eq!(false, is_hangeul(temp));
164    }
165
166    #[test]
167    fn _is_hangul_syllable() {
168        let temp = ['ㄱ', 'ㅏ', 'ㄴ'];
169        assert_eq!(true, is_hangul_syllable(temp));
170
171        let temp = ['ㄱ', 'ㄴ', 'ㄷ'];
172        assert_eq!(false, is_hangul_syllable(temp));
173
174        let temp = ['ㅊ', 'ㄴ', 'ㅓ'];
175        assert_eq!(false, is_hangul_syllable(temp));
176
177        let temp = ['😀', 'ㄴ', 'ㄷ'];
178        assert_eq!(false, is_hangul_syllable(temp));
179    }
180}