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
76#[derive(Debug, PartialEq, Eq)]
78pub enum WordPushResult {
79 Continue,
81
82 InvalidHangul,
85
86 NonHangul,
88}
89
90impl HangulWordComposer {
91 pub fn new() -> Self {
93 HangulWordComposer {
94 prev_blocks: Vec::new(),
95 cur_block: BlockComposer::new(),
96 }
97 }
98
99 pub fn push_char(&mut self, c: char) -> Result<WordPushResult, WordError> {
115 match Character::from_char(c)? {
116 Character::Hangul(jamo) => self.push(&jamo),
117 Character::NonHangul(_) => Ok(WordPushResult::NonHangul),
118 }
119 }
120
121 pub fn push(&mut self, letter: &Jamo) -> Result<WordPushResult, WordError> {
127 match self.cur_block.push(letter) {
128 BlockPushResult::Success => Ok(WordPushResult::Continue),
129 BlockPushResult::InvalidHangul => Ok(WordPushResult::InvalidHangul),
130 BlockPushResult::NonHangul => Ok(WordPushResult::NonHangul),
131 BlockPushResult::StartNewBlockNoPop => match self.start_new_block(letter.clone()) {
132 Ok(_) => Ok(WordPushResult::Continue),
133 Err(e) => Err(e),
134 },
135 BlockPushResult::PopAndStartNewBlock => {
136 match self.pop_and_start_new_block(letter.clone()) {
137 Ok(_) => Ok(WordPushResult::Continue),
138 Err(e) => Err(e),
139 }
140 }
141 }
142 }
143
144 pub fn pop(&mut self) -> Result<Option<Jamo>, WordError> {
154 match self.cur_block.pop() {
155 BlockPopStatus::PoppedAndNonEmpty(l) => Ok(Some(l)),
156 BlockPopStatus::PoppedAndEmpty(l) => {
157 self.prev_block_to_cur()?;
158 Ok(Some(l))
159 }
160 BlockPopStatus::None => {
161 self.prev_block_to_cur()?;
162 Ok(None)
163 }
164 }
165 }
166
167 fn prev_block_to_cur(&mut self) -> Result<(), WordError> {
168 if let Some(last_block) = self.prev_blocks.pop() {
169 self.cur_block = BlockComposer::from_composed_block(&last_block)?;
170 Ok(())
171 } else {
172 Ok(())
173 }
174 }
175
176 fn pop_and_start_new_block(&mut self, letter: Jamo) -> Result<(), WordError> {
177 match self.cur_block.pop_end_consonant() {
178 Some(l) => {
179 self.complete_current_block()?;
180 self.cur_block.push(&l);
181 match self.cur_block.push(&letter) {
182 BlockPushResult::Success => Ok(()),
183 other => Err(WordError::CouldNotStartNewBlock(
184 letter.char_compatibility(),
185 other,
186 )),
187 }
188 }
189 None => Err(WordError::NothingToPop),
190 }
191 }
192
193 fn start_new_block(&mut self, letter: Jamo) -> Result<(), WordError> {
194 self.complete_current_block()?;
195 match self.cur_block.push(&letter) {
196 BlockPushResult::Success => Ok(()),
197 other => Err(WordError::CouldNotStartNewBlock(
198 letter.char_compatibility(),
199 other,
200 )),
201 }
202 }
203
204 pub fn as_string(&self) -> Result<String, WordError> {
208 let mut result = hangul_blocks_vec_to_string(&self.prev_blocks)?;
209 let cur_as_char = self.cur_block.block_as_string()?;
210 if let Some(c) = cur_as_char {
211 result.push(c);
212 }
213 Ok(result)
214 }
215
216 fn complete_current_block(&mut self) -> Result<(), WordError> {
217 match self.cur_block.try_as_complete_block()? {
218 BlockCompletionStatus::Complete(block) => {
219 self.prev_blocks.push(block);
220 self.cur_block = BlockComposer::new();
221 Ok(())
222 }
223 BlockCompletionStatus::Incomplete(c) => Err(WordError::CannotCompleteCurrentBlock(c)),
224 BlockCompletionStatus::Empty => {
225 Ok(())
227 }
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn start_new_block_valid() {
238 let mut composer = HangulWordComposer::new();
239
240 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
241 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
242 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue),);
243 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue),);
244 assert_eq!(
245 composer.prev_blocks,
246 vec![HangulBlock {
247 initial: Jamo::Consonant(JamoConsonantSingular::Giyeok),
248 vowel: Jamo::Vowel(JamoVowelSingular::A),
249 final_optional: Some(Jamo::Consonant(JamoConsonantSingular::Nieun)),
250 }]
251 );
252 assert_eq!(composer.push_char('ㅛ'), Ok(WordPushResult::Continue));
253 assert_eq!(composer.push_char('ㅉ'), Ok(WordPushResult::Continue),);
254 assert_eq!(
255 composer.prev_blocks,
256 vec![
257 HangulBlock {
258 initial: Jamo::Consonant(JamoConsonantSingular::Giyeok),
259 vowel: Jamo::Vowel(JamoVowelSingular::A),
260 final_optional: Some(Jamo::Consonant(JamoConsonantSingular::Nieun)),
261 },
262 HangulBlock {
263 initial: Jamo::Consonant(JamoConsonantSingular::Ieung),
264 vowel: Jamo::Vowel(JamoVowelSingular::Yo),
265 final_optional: None,
266 }
267 ]
268 );
269 }
270
271 #[test]
272 fn start_new_block_invalid() {
273 let mut composer = HangulWordComposer::new();
274
275 assert_eq!(
276 composer.start_new_block(Jamo::Vowel(JamoVowelSingular::A)),
277 Err(WordError::CouldNotStartNewBlock(
278 'ㅏ',
279 BlockPushResult::InvalidHangul
280 ))
281 );
282 let _ = composer.push_char('ㄱ');
283 assert_eq!(
284 composer.start_new_block(Jamo::CompositeVowel(JamoVowelComposite::Wae)),
285 Err(WordError::CannotCompleteCurrentBlock(Jamo::Consonant(
286 JamoConsonantSingular::Giyeok
287 )))
288 );
289 }
290
291 #[test]
292 fn push_char_valid() {
293 let mut composer = HangulWordComposer::new();
294
295 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
296 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
297 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue),);
298 }
299
300 #[test]
301 fn push_char_invalid_hangul() {
302 let mut composer = HangulWordComposer::new();
303
304 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
305 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
306 assert_eq!(composer.push_char('ㄹ'), Ok(WordPushResult::Continue));
307 assert_eq!(composer.push_char('ㄽ'), Ok(WordPushResult::InvalidHangul));
308 }
309
310 #[test]
311 fn push_char_next_block() {
312 let mut composer = HangulWordComposer::new();
313
314 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
315 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
316 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
317 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
318 }
319
320 #[test]
321 fn push_char_non_hangul() {
322 let mut composer = HangulWordComposer::new();
323
324 assert_eq!(composer.push_char('ㄱ'), Ok(WordPushResult::Continue));
325 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
326 assert_eq!(composer.push_char('A'), Ok(WordPushResult::NonHangul));
327 }
328
329 #[test]
330 fn test_single_word_안녕하세요_as_string() {
331 let mut composer = HangulWordComposer::new();
332
333 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
334 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
335 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
336 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
337 assert_eq!(composer.push_char('ㅕ'), Ok(WordPushResult::Continue));
338 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
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
346 let result_string = composer.as_string().unwrap();
347 assert_eq!(result_string, "안녕하세요".to_string());
348 }
349
350 #[test]
351 fn test_single_word_앖어요_as_string() {
352 let mut composer = HangulWordComposer::new();
353
354 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
355 assert_eq!(composer.push_char('ㅓ'), Ok(WordPushResult::Continue));
356 assert_eq!(composer.push_char('ㅂ'), Ok(WordPushResult::Continue));
357 assert_eq!(composer.push_char('ㅅ'), Ok(WordPushResult::Continue));
358 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
359 assert_eq!(composer.push_char('ㅓ'), Ok(WordPushResult::Continue));
360 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
361 assert_eq!(composer.push_char('ㅛ'), Ok(WordPushResult::Continue));
362
363 let result_string = composer.as_string().unwrap();
364 assert_eq!(result_string, "없어요".to_string());
365 }
366
367 #[test]
368 fn test_incomplete_block_as_string() {
369 let mut composer = HangulWordComposer::new();
370
371 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
372
373 let result_string = composer.as_string().unwrap();
374 assert_eq!(result_string, "ᄋ".to_string());
375 }
376
377 #[test]
378 fn test_deletions() {
379 let mut composer = HangulWordComposer::new();
380 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
381 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
382 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
383 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
384 assert_eq!(composer.push_char('ㅕ'), Ok(WordPushResult::Continue));
385 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅕ');
386 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
387 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
388 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅏ');
389 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅇ');
390 assert_eq!(composer.pop(), Ok(None));
391 }
392
393 #[test]
394 fn test_deletion_then_write_again() {
395 let mut composer = HangulWordComposer::new();
396 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
397 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
398 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
399
400 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
401 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅏ');
402 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㅇ');
403
404 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
405 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
406 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
407
408 let result_string = composer.as_string().unwrap();
409 assert_eq!(result_string, "안".to_string());
410 }
411
412 #[test]
413 fn deletion_removes_empty_block() {
414 let mut composer = HangulWordComposer::new();
415 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
416 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
417 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
418 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
419
420 assert_eq!(composer.pop().unwrap().unwrap().char_compatibility(), 'ㄴ');
421 assert_eq!(composer.as_string().unwrap(), "안".to_string());
423 }
424
425 #[test]
426 fn test_complete_current_block() {
427 let mut composer = HangulWordComposer::new();
428 assert_eq!(composer.push_char('ㅇ'), Ok(WordPushResult::Continue));
429 assert_eq!(composer.push_char('ㅏ'), Ok(WordPushResult::Continue));
430 assert_eq!(composer.push_char('ㄴ'), Ok(WordPushResult::Continue));
431
432 assert!(composer.complete_current_block().is_ok());
433
434 assert_eq!(composer.prev_blocks.len(), 1);
435 assert_eq!(composer.cur_block, BlockComposer::new());
436
437 let result_string = composer.as_string().unwrap();
438 assert_eq!(result_string, "안".to_string());
439 }
440}