1use std::fmt::Debug;
2
3use thiserror::Error;
4
5use crate::{block::*, jamo::*};
6
7#[derive(Error, Debug, PartialEq, Eq)]
9pub enum WordError {
10 #[error("Block error: {0}")]
12 BlockError(#[from] BlockError),
13
14 #[error("Jamo error: {0}")]
16 JamoError(#[from] JamoError),
17
18 #[error("Could not start new block with character '{0}'; reason: {1:?}")]
21 CouldNotStartNewBlock(char, BlockPushResult),
22
23 #[error("Tried popping from empty word")]
25 NothingToPop,
26
27 #[error("Cannot complete current block; currently contains only one Jamo: {0:?}")]
29 CannotCompleteCurrentBlock(Jamo),
30}
31
32#[derive(Debug)]
71pub struct HangulWordComposer {
72 prev_blocks: Vec<HangulBlock>,
73 cur_block: BlockComposer,
74}
75
76impl Default for HangulWordComposer {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[derive(Debug, PartialEq, Eq)]
84pub enum WordPushResult {
85 Continue,
87
88 InvalidHangul,
91
92 NonHangul,
94}
95
96impl HangulWordComposer {
97 pub fn new() -> Self {
99 HangulWordComposer {
100 prev_blocks: Vec::new(),
101 cur_block: BlockComposer::new(),
102 }
103 }
104
105 pub fn push_char(&mut self, c: char) -> Result<WordPushResult, WordError> {
121 match Character::from_char(c)? {
122 Character::Hangul(jamo) => self.push(&jamo),
123 Character::NonHangul(_) => Ok(WordPushResult::NonHangul),
124 }
125 }
126
127 pub fn push(&mut self, letter: &Jamo) -> Result<WordPushResult, WordError> {
133 match self.cur_block.push(letter) {
134 BlockPushResult::Success => Ok(WordPushResult::Continue),
135 BlockPushResult::InvalidHangul => Ok(WordPushResult::InvalidHangul),
136 BlockPushResult::NonHangul => Ok(WordPushResult::NonHangul),
137 BlockPushResult::StartNewBlockNoPop => match self.start_new_block(letter.clone()) {
138 Ok(_) => Ok(WordPushResult::Continue),
139 Err(e) => Err(e),
140 },
141 BlockPushResult::PopAndStartNewBlock => {
142 match self.pop_and_start_new_block(letter.clone()) {
143 Ok(_) => Ok(WordPushResult::Continue),
144 Err(e) => Err(e),
145 }
146 }
147 }
148 }
149
150 pub fn pop(&mut self) -> Result<Option<Jamo>, WordError> {
160 match self.cur_block.pop() {
161 BlockPopStatus::PoppedAndNonEmpty(l) => Ok(Some(l)),
162 BlockPopStatus::PoppedAndEmpty(l) => {
163 self.prev_block_to_cur()?;
164 Ok(Some(l))
165 }
166 BlockPopStatus::None => {
167 self.prev_block_to_cur()?;
168 Ok(None)
169 }
170 }
171 }
172
173 fn prev_block_to_cur(&mut self) -> Result<(), WordError> {
174 if let Some(last_block) = self.prev_blocks.pop() {
175 self.cur_block = BlockComposer::from_composed_block(&last_block)?;
176 Ok(())
177 } else {
178 Ok(())
179 }
180 }
181
182 fn pop_and_start_new_block(&mut self, letter: Jamo) -> Result<(), WordError> {
183 match self.cur_block.pop_end_consonant() {
184 Some(l) => {
185 self.complete_current_block()?;
186 self.cur_block.push(&l);
187 match self.cur_block.push(&letter) {
188 BlockPushResult::Success => Ok(()),
189 other => Err(WordError::CouldNotStartNewBlock(
190 letter.char_compatibility(),
191 other,
192 )),
193 }
194 }
195 None => Err(WordError::NothingToPop),
196 }
197 }
198
199 fn start_new_block(&mut self, letter: Jamo) -> Result<(), WordError> {
200 self.complete_current_block()?;
201 match self.cur_block.push(&letter) {
202 BlockPushResult::Success => Ok(()),
203 other => Err(WordError::CouldNotStartNewBlock(
204 letter.char_compatibility(),
205 other,
206 )),
207 }
208 }
209
210 pub fn as_string(&self) -> Result<String, WordError> {
214 let mut result = hangul_blocks_vec_to_string(&self.prev_blocks)?;
215 let cur_as_char = self.cur_block.block_as_string()?;
216 if let Some(c) = cur_as_char {
217 result.push(c);
218 }
219 Ok(result)
220 }
221
222 fn complete_current_block(&mut self) -> Result<(), WordError> {
223 match self.cur_block.try_as_complete_block()? {
224 BlockCompletionStatus::Complete(block) => {
225 self.prev_blocks.push(block);
226 self.cur_block = BlockComposer::new();
227 Ok(())
228 }
229 BlockCompletionStatus::Incomplete(c) => Err(WordError::CannotCompleteCurrentBlock(c)),
230 BlockCompletionStatus::Empty => {
231 Ok(())
233 }
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn start_new_block_valid() {
244 let mut composer = HangulWordComposer::new();
245
246 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
247 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
248 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue),);
249 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue),);
250 assert_eq!(
251 composer.prev_blocks,
252 vec![HangulBlock {
253 initial: Jamo::Consonant(JamoConsonantSingular::Giyeok),
254 vowel: Jamo::Vowel(JamoVowelSingular::A),
255 final_optional: Some(Jamo::Consonant(JamoConsonantSingular::Nieun)),
256 }]
257 );
258 assert_eq!(composer.push_char('ㅛ'), Ok(WordPushResult::Continue));
259 assert_eq!(composer.push_char('ㅉ'), Ok(WordPushResult::Continue),);
260 assert_eq!(
261 composer.prev_blocks,
262 vec![
263 HangulBlock {
264 initial: Jamo::Consonant(JamoConsonantSingular::Giyeok),
265 vowel: Jamo::Vowel(JamoVowelSingular::A),
266 final_optional: Some(Jamo::Consonant(JamoConsonantSingular::Nieun)),
267 },
268 HangulBlock {
269 initial: Jamo::Consonant(JamoConsonantSingular::Ieung),
270 vowel: Jamo::Vowel(JamoVowelSingular::Yo),
271 final_optional: None,
272 }
273 ]
274 );
275 }
276
277 #[test]
278 fn start_new_block_invalid() {
279 let mut composer = HangulWordComposer::new();
280
281 assert_eq!(
282 composer.start_new_block(Jamo::Vowel(JamoVowelSingular::A)),
283 Err(WordError::CouldNotStartNewBlock(
284 'ㅏ',
285 BlockPushResult::InvalidHangul
286 ))
287 );
288 let _ = composer.push_char('ㄱ');
289 assert_eq!(
290 composer.start_new_block(Jamo::CompositeVowel(JamoVowelComposite::Wae)),
291 Err(WordError::CannotCompleteCurrentBlock(Jamo::Consonant(
292 JamoConsonantSingular::Giyeok
293 )))
294 );
295 }
296
297 #[test]
298 fn push_char_valid() {
299 let mut composer = HangulWordComposer::new();
300
301 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
302 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
303 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue),);
304 }
305
306 #[test]
307 fn push_char_invalid_hangul() {
308 let mut composer = HangulWordComposer::new();
309
310 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
311 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
312 assert_eq!(composer.push_char('ㄹ'), Ok(WordPushResult::Continue));
313 assert_eq!(composer.push_char('ㄽ'), Ok(WordPushResult::InvalidHangul));
314 }
315
316 #[test]
317 fn push_char_next_block() {
318 let mut composer = HangulWordComposer::new();
319
320 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
321 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
322 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
323 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
324 }
325
326 #[test]
327 fn push_char_non_hangul() {
328 let mut composer = HangulWordComposer::new();
329
330 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
331 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
332 assert_eq!(composer.push_char('A'), Ok(WordPushResult::NonHangul));
333 }
334
335 #[test]
336 fn test_single_word_안녕하세요_as_string() {
337 let mut composer = HangulWordComposer::new();
338
339 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
340 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
341 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
342 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
343 assert_eq!(composer.push_char('ㅕ'), Ok(WordPushResult::Continue));
344 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
345 assert_eq!(composer.push_char('ㅎ'), Ok(WordPushResult::Continue));
346 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
347 assert_eq!(composer.push_char('ㅅ'), Ok(WordPushResult::Continue));
348 assert_eq!(composer.push_char('ㅔ'), Ok(WordPushResult::Continue));
349 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
350 assert_eq!(composer.push_char('ㅛ'), Ok(WordPushResult::Continue));
351
352 let result_string = composer.as_string().unwrap();
353 assert_eq!(result_string, "안녕하세요".to_string());
354 }
355
356 #[test]
357 fn test_single_word_앖어요_as_string() {
358 let mut composer = HangulWordComposer::new();
359
360 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
361 assert_eq!(composer.push_char('ㅓ'), Ok(WordPushResult::Continue));
362 assert_eq!(composer.push_char('ㅂ'), Ok(WordPushResult::Continue));
363 assert_eq!(composer.push_char('ㅅ'), Ok(WordPushResult::Continue));
364 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
365 assert_eq!(composer.push_char('ㅓ'), Ok(WordPushResult::Continue));
366 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
367 assert_eq!(composer.push_char('ㅛ'), Ok(WordPushResult::Continue));
368
369 let result_string = composer.as_string().unwrap();
370 assert_eq!(result_string, "없어요".to_string());
371 }
372
373 #[test]
374 fn test_incomplete_block_as_string() {
375 let mut composer = HangulWordComposer::new();
376
377 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
378
379 let result_string = composer.as_string().unwrap();
380 assert_eq!(result_string, "ᄋ".to_string());
381 }
382
383 #[test]
384 fn test_deletions() {
385 let mut composer = HangulWordComposer::new();
386 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
387 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
388 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
389 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
390 assert_eq!(composer.push_char('ㅕ'), Ok(WordPushResult::Continue));
391 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅕ');
392 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
393 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
394 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅏ');
395 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅇ');
396 assert_eq!(composer.pop(), Ok(None));
397 }
398
399 #[test]
400 fn test_deletion_then_write_again() {
401 let mut composer = HangulWordComposer::new();
402 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
403 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
404 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
405
406 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
407 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅏ');
408 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅇ');
409
410 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
411 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
412 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
413
414 let result_string = composer.as_string().unwrap();
415 assert_eq!(result_string, "안".to_string());
416 }
417
418 #[test]
419 fn deletion_removes_empty_block() {
420 let mut composer = HangulWordComposer::new();
421 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
422 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
423 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
424 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
425
426 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
427 assert_eq!(composer.as_string().unwrap(), "안".to_string());
429 }
430
431 #[test]
432 fn test_complete_current_block() {
433 let mut composer = HangulWordComposer::new();
434 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
435 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
436 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
437
438 assert!(composer.complete_current_block().is_ok());
439
440 assert_eq!(composer.prev_blocks.len(), 1);
441 assert_eq!(composer.cur_block, BlockComposer::new());
442
443 let result_string = composer.as_string().unwrap();
444 assert_eq!(result_string, "안".to_string());
445 }
446}