1use crate::tables;
6
7#[derive(Clone, Copy, Default)]
8struct Letter {
9 c: char,
10 is_vowel: bool,
11 variant: u8,
12 tone: u8,
13 is_dstroke: bool,
14 upper: bool,
15 from_w: bool,
16}
17
18impl Letter {
19 fn new(c: char, upper: bool) -> Self {
20 let lc = c.to_ascii_lowercase();
21 Letter {
22 c: lc,
23 is_vowel: tables::is_vowel(lc),
24 variant: 0,
25 tone: 0,
26 is_dstroke: false,
27 upper,
28 from_w: false,
29 }
30 }
31}
32
33fn emit(ls: &[Letter]) -> String {
34 let mut out = String::with_capacity(ls.len() * 3);
35 for lt in ls {
36 if lt.is_dstroke {
37 out.push_str(if lt.upper {
38 tables::D_STROKE_UP
39 } else {
40 tables::D_STROKE_LO
41 });
42 } else if lt.is_vowel {
43 out.push_str(tables::vowel_utf8(lt.c, lt.variant, lt.tone, lt.upper));
44 } else {
45 out.push(if lt.upper {
46 lt.c.to_ascii_uppercase()
47 } else {
48 lt.c
49 });
50 }
51 }
52 out
53}
54
55fn last_vowel_index(ls: &[Letter]) -> Option<usize> {
56 ls.iter().rposition(|lt| lt.is_vowel)
57}
58fn last_d_index(ls: &[Letter]) -> Option<usize> {
59 ls.iter()
60 .rposition(|lt| lt.c == 'd' && !lt.is_vowel && !lt.is_dstroke)
61}
62
63fn last_w_target_index(ls: &[Letter]) -> Option<usize> {
66 ls.iter()
67 .rposition(|lt| lt.is_vowel && matches!(lt.c, 'a' | 'o' | 'u' | 'e'))
68}
69
70fn tone_vowel_index(ls: &[Letter], _modern: bool) -> Option<usize> {
71 let end = last_vowel_index(ls)?;
72 let mut start = end;
73 while start > 0 && ls[start - 1].is_vowel {
74 start -= 1;
75 }
76 if start < end {
77 if ls[start].c == 'u' && start > 0 && ls[start - 1].c == 'q' {
78 start += 1;
79 } else if ls[start].c == 'i' && start > 0 && ls[start - 1].c == 'g' {
80 start += 1;
81 }
82 }
83 if start > end {
84 start = end;
85 }
86 for i in (start..=end).rev() {
87 if ls[i].variant != 0 {
88 return Some(i);
89 }
90 }
91 let count = end - start + 1;
92 if count == 1 {
93 return Some(start);
94 }
95 if count >= 3 {
96 if ls[start].c == 'u'
97 && start + 2 <= end
98 && ls[start + 1].c == 'y'
99 && ls[start + 2].c == 'e'
100 {
101 return Some(start + 2);
102 }
103 return Some(start + 1);
104 }
105 if end + 1 < ls.len() {
106 return Some(end);
107 }
108 let (c1, c2) = (ls[start].c, ls[end].c);
109 if (c1 == 'o' && c2 == 'a') || (c1 == 'o' && c2 == 'e') || (c1 == 'u' && c2 == 'y') {
110 return Some(end); }
112 Some(start) }
114
115fn telex_tone(c: char) -> Option<u8> {
116 match c.to_ascii_lowercase() {
117 's' => Some(2),
118 'f' => Some(1),
119 'r' => Some(3),
120 'x' => Some(4),
121 'j' => Some(5),
122 _ => None,
123 }
124}
125fn vni_tone(c: char) -> Option<u8> {
126 match c {
127 '1' => Some(2),
128 '2' => Some(1),
129 '3' => Some(3),
130 '4' => Some(4),
131 '5' => Some(5),
132 _ => None,
133 }
134}
135
136pub fn convert_telex(input: &str, modern: bool, short_w: bool) -> String {
137 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
138 for ch in input.chars() {
139 let lc = ch.to_ascii_lowercase();
140 let upper = ch.is_ascii_uppercase();
141 let n = ls.len();
142 if lc == 'd' && n > 0 && ls[n - 1].c == 'd' && !ls[n - 1].is_vowel && !ls[n - 1].is_dstroke
143 {
144 ls[n - 1].is_dstroke = true;
145 if upper {
146 ls[n - 1].upper = true;
147 }
148 continue;
149 }
150 if tables::is_vowel(lc) && matches!(lc, 'a' | 'e' | 'o') && n > 0 {
155 let mut consumed = false;
156 if let Some(vi) = last_vowel_index(&ls) {
157 for i in (0..=vi).rev() {
158 if ls[i].is_vowel {
159 if ls[i].c == lc {
160 if ls[i].variant == 0 {
161 ls[i].variant = 1;
162 if upper {
163 ls[i].upper = true;
164 }
165 consumed = true;
166 break;
167 }
168 if ls[i].variant == 1 {
169 ls[i].variant = 0;
170 break;
171 } }
173 } else {
174 break;
175 }
176 }
177 }
178 if consumed {
179 continue;
180 }
181 }
182 if lc == 'w' {
183 if let Some(vi) = last_w_target_index(&ls) {
184 match ls[vi].c {
185 'a' => {
186 ls[vi].variant = if ls[vi].variant == 2 { 0 } else { 2 };
187 continue;
188 }
189 'o' => {
190 if ls[vi].variant == 2 {
191 ls[vi].variant = 0;
192 if vi > 0 && ls[vi - 1].c == 'u' && ls[vi - 1].variant == 2 {
193 ls[vi - 1].variant = 0;
194 }
195 } else {
196 ls[vi].variant = 2;
197 if vi > 0 && ls[vi - 1].c == 'u' {
198 ls[vi - 1].variant = 2;
199 }
200 }
201 continue;
202 }
203 'u' => {
204 if ls[vi].variant == 2 {
205 if ls[vi].from_w {
206 ls[vi].c = 'w';
208 ls[vi].is_vowel = false;
209 ls[vi].from_w = false;
210 }
211 ls[vi].variant = 0;
212 } else {
213 ls[vi].variant = 2;
214 }
215 continue;
216 }
217 'e' => {
218 ls[vi].variant = if ls[vi].variant == 1 { 0 } else { 1 };
219 continue;
220 }
221 _ => {}
222 }
223 } else if short_w && !ls.last().map_or(false, |lt| lt.c == 'w' && !lt.is_vowel) {
224 ls.push(Letter {
226 c: 'u',
227 is_vowel: true,
228 variant: 2,
229 tone: 0,
230 is_dstroke: false,
231 upper,
232 from_w: true,
233 });
234 continue;
235 }
236 }
237 if let Some(t) = telex_tone(lc) {
238 if let Some(vi) = tone_vowel_index(&ls, modern) {
239 ls[vi].tone = t;
240 continue; }
242 }
243 if lc == 'z' {
244 if let Some(vi) = tone_vowel_index(&ls, modern) {
245 if ls[vi].tone != 0 {
246 ls[vi].tone = 0;
247 continue;
248 }
249 }
250 }
251 ls.push(Letter::new(ch, upper));
252 }
253 emit(&ls)
254}
255
256pub fn convert_teip_vni(input: &str, modern: bool, short_w: bool) -> String {
262 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
263 for ch in input.chars() {
264 let lc = ch.to_ascii_lowercase();
265 let upper = ch.is_ascii_uppercase();
266 let n = ls.len();
267
268 if let Some(t) = vni_tone(lc) {
270 if let Some(vi) = tone_vowel_index(&ls, modern) {
271 ls[vi].tone = t;
272 continue;
273 }
274 }
275 if lc >= '0' && lc <= '9' {
276 match lc {
277 '6' => {
278 if let Some(vi) = last_vowel_index(&ls) {
279 if matches!(ls[vi].c, 'a' | 'e' | 'o') {
280 ls[vi].variant = 1;
281 continue;
282 }
283 }
284 }
285 '7' => {
286 if let Some(vi) = last_vowel_index(&ls) {
287 if matches!(ls[vi].c, 'o' | 'u') {
288 ls[vi].variant = 2;
289 if ls[vi].c == 'o' && vi > 0 && ls[vi - 1].c == 'u' {
290 ls[vi - 1].variant = 2;
291 }
292 continue;
293 }
294 }
295 }
296 '8' => {
297 if let Some(vi) = last_vowel_index(&ls) {
298 if ls[vi].c == 'a' {
299 ls[vi].variant = 2;
300 continue;
301 }
302 }
303 }
304 '9' => {
305 if let Some(di) = last_d_index(&ls) {
306 ls[di].is_dstroke = true;
307 if upper {
308 ls[di].upper = true;
309 }
310 }
311 continue;
312 }
313 '0' => {
314 continue;
315 }
316 _ => {} }
318 }
319
320 if lc == 'd' && n > 0 && ls[n - 1].c == 'd' && !ls[n - 1].is_vowel && !ls[n - 1].is_dstroke
322 {
323 ls[n - 1].is_dstroke = true;
324 if upper {
325 ls[n - 1].upper = true;
326 }
327 continue;
328 }
329
330 if tables::is_vowel(lc) && matches!(lc, 'a' | 'e' | 'o') && n > 0 {
334 let mut consumed = false;
335 if let Some(vi) = last_vowel_index(&ls) {
336 for i in (0..=vi).rev() {
337 if ls[i].is_vowel {
338 if ls[i].c == lc {
339 if ls[i].variant == 0 {
340 ls[i].variant = 1;
341 if upper {
342 ls[i].upper = true;
343 }
344 consumed = true;
345 break;
346 }
347 if ls[i].variant == 1 {
348 ls[i].variant = 0;
349 break;
350 } }
352 } else {
353 break;
354 }
355 }
356 }
357 if consumed {
358 continue;
359 }
360 }
361
362 if lc == 'w' {
364 if let Some(vi) = last_w_target_index(&ls) {
365 match ls[vi].c {
366 'a' => {
367 ls[vi].variant = if ls[vi].variant == 2 { 0 } else { 2 };
368 continue;
369 }
370 'o' => {
371 if ls[vi].variant == 2 {
372 ls[vi].variant = 0;
373 if vi > 0 && ls[vi - 1].c == 'u' && ls[vi - 1].variant == 2 {
374 ls[vi - 1].variant = 0;
375 }
376 } else {
377 ls[vi].variant = 2;
378 if vi > 0 && ls[vi - 1].c == 'u' {
379 ls[vi - 1].variant = 2;
380 }
381 }
382 continue;
383 }
384 'u' => {
385 if ls[vi].variant == 2 {
386 if ls[vi].from_w {
387 ls[vi].c = 'w';
388 ls[vi].is_vowel = false;
389 ls[vi].from_w = false;
390 }
391 ls[vi].variant = 0;
392 } else {
393 ls[vi].variant = 2;
394 }
395 continue;
396 }
397 'e' => {
398 ls[vi].variant = if ls[vi].variant == 1 { 0 } else { 1 };
399 continue;
400 }
401 _ => {}
402 }
403 } else if short_w && !ls.last().map_or(false, |lt| lt.c == 'w' && !lt.is_vowel) {
404 ls.push(Letter {
405 c: 'u',
406 is_vowel: true,
407 variant: 2,
408 tone: 0,
409 is_dstroke: false,
410 upper,
411 from_w: true,
412 });
413 continue;
414 }
415 }
416
417 if let Some(t) = telex_tone(lc) {
419 if let Some(vi) = tone_vowel_index(&ls, modern) {
420 ls[vi].tone = t;
421 continue; }
423 }
424 if lc == 'z' {
425 if let Some(vi) = tone_vowel_index(&ls, modern) {
426 if ls[vi].tone != 0 {
427 ls[vi].tone = 0;
428 continue;
429 }
430 }
431 }
432
433 ls.push(Letter::new(ch, upper));
434 }
435 emit(&ls)
436}
437
438pub fn convert_viqr(input: &str) -> String {
445 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
446 for ch in input.chars() {
447 let lc = ch.to_ascii_lowercase();
448 let n = ls.len();
449
450 match ch {
452 '^' | '(' | '+' => {
453 if let Some(vi) = last_vowel_index(&ls) {
454 match (ch, ls[vi].c) {
455 ('^', 'a' | 'e' | 'o') => {
456 ls[vi].variant = 1;
457 continue;
458 }
459 ('(', 'a') => {
460 ls[vi].variant = 2;
461 continue;
462 }
463 ('+', 'o' | 'u') => {
464 ls[vi].variant = 2;
465 continue;
466 }
467 _ => {} }
469 }
470 ls.push(Letter {
472 c: ch,
473 is_vowel: false,
474 variant: 0,
475 tone: 0,
476 is_dstroke: false,
477 upper: false,
478 from_w: false,
479 });
480 continue;
481 }
482 _ => {}
483 }
484
485 match ch {
487 '\'' => {
488 if let Some(vi) = last_vowel_index(&ls) {
489 ls[vi].tone = 2;
490 continue;
491 }
492 }
493 '`' => {
494 if let Some(vi) = last_vowel_index(&ls) {
495 ls[vi].tone = 1;
496 continue;
497 }
498 }
499 '?' => {
500 if let Some(vi) = last_vowel_index(&ls) {
501 ls[vi].tone = 3;
502 continue;
503 }
504 }
505 '~' => {
506 if let Some(vi) = last_vowel_index(&ls) {
507 ls[vi].tone = 4;
508 continue;
509 }
510 }
511 '.' => {
512 if let Some(vi) = last_vowel_index(&ls) {
513 ls[vi].tone = 5;
514 continue;
515 }
516 }
517 _ => {}
518 }
519
520 if lc == 'd' && n > 0 && ls[n - 1].c == 'd' && !ls[n - 1].is_vowel && !ls[n - 1].is_dstroke
522 {
523 ls[n - 1].is_dstroke = true;
524 if ch.is_ascii_uppercase() {
525 ls[n - 1].upper = true;
526 }
527 continue;
528 }
529
530 ls.push(Letter::new(ch, ch.is_ascii_uppercase()));
531 }
532 emit(&ls)
533}
534
535pub fn convert_vni(input: &str, modern: bool, _short_w: bool) -> String {
536 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
537 for ch in input.chars() {
538 let lc = ch.to_ascii_lowercase();
539 let upper = ch.is_ascii_uppercase();
540 if let Some(t) = vni_tone(lc) {
541 if let Some(vi) = tone_vowel_index(&ls, modern) {
542 ls[vi].tone = t;
543 continue;
544 }
545 }
546 match lc {
547 '6' => {
548 if let Some(vi) = last_vowel_index(&ls) {
549 if matches!(ls[vi].c, 'a' | 'e' | 'o') {
550 ls[vi].variant = 1;
551 continue;
552 }
553 }
554 }
555 '7' => {
556 if let Some(vi) = last_vowel_index(&ls) {
557 if matches!(ls[vi].c, 'o' | 'u') {
558 ls[vi].variant = 2;
559 if ls[vi].c == 'o' && vi > 0 && ls[vi - 1].c == 'u' {
560 ls[vi - 1].variant = 2;
561 }
562 continue;
563 }
564 }
565 }
566 '8' => {
567 if let Some(vi) = last_vowel_index(&ls) {
568 if ls[vi].c == 'a' {
569 ls[vi].variant = 2;
570 continue;
571 }
572 }
573 }
574 '9' => {
575 if let Some(di) = last_d_index(&ls) {
576 ls[di].is_dstroke = true;
577 if upper {
578 ls[di].upper = true;
579 }
580 }
581 continue;
582 }
583 '0' => {
584 continue;
585 }
586 _ => {}
587 }
588 if lc.is_ascii_alphabetic() || (lc.is_ascii_digit() && !ls.is_empty()) {
589 ls.push(Letter::new(ch, upper));
590 }
591 }
592 emit(&ls)
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 fn t(s: &str) -> String {
600 convert_telex(s, true, false)
601 }
602 fn tw(s: &str) -> String {
603 convert_telex(s, true, true)
604 }
605 fn tt(s: &str) -> String {
607 convert_telex(s, false, true)
608 }
609 fn vt(s: &str) -> String {
610 convert_vni(s, false, true)
611 }
612
613 #[test]
614 fn telex_basic() {
615 assert_eq!(t("as"), "á");
616 assert_eq!(t("af"), "à");
617 assert_eq!(t("ar"), "ả");
618 assert_eq!(t("ax"), "ã");
619 assert_eq!(t("aj"), "ạ");
620 assert_eq!(t("aw"), "ă");
621 assert_eq!(t("aa"), "â");
622 assert_eq!(t("ee"), "ê");
623 assert_eq!(t("oo"), "ô");
624 assert_eq!(t("ow"), "ơ");
625 assert_eq!(t("uw"), "ư");
626 assert_eq!(t("dd"), "đ");
627 }
628 #[test]
629 fn telex_compound() {
630 assert_eq!(t("aws"), "ắ");
631 assert_eq!(t("aas"), "ấ");
632 assert_eq!(t("ees"), "ế");
633 assert_eq!(t("oos"), "ố");
634 assert_eq!(t("ows"), "ớ");
635 assert_eq!(t("uws"), "ứ");
636 }
637 #[test]
638 fn telex_words() {
639 assert_eq!(t("xin"), "xin");
640 assert_eq!(t("tooi"), "tôi");
641 assert_eq!(t("laf"), "là");
642 assert_eq!(t("cos"), "có");
643 assert_eq!(t("khoong"), "không");
644 assert_eq!(t("tieengs"), "tiếng");
645 assert_eq!(t("Vieetj"), "Việt");
646 }
647 #[test]
648 fn telex_uow() {
649 assert_eq!(t("uow"), "ươ");
650 assert_eq!(t("cuowcs"), "cước");
651 assert_eq!(t("dduwowcj"), "được");
652 assert_eq!(t("nguwowif"), "người");
653 }
654 #[test]
655 fn telex_gi_qu() {
656 assert_eq!(t("gif"), "gì");
657 assert_eq!(t("giowf"), "giờ");
658 assert_eq!(t("gioir"), "giỏi");
659 assert_eq!(t("gias"), "giá");
660 assert_eq!(t("quas"), "quá");
661 }
662 #[test]
663 fn telex_tone_placement() {
664 assert_eq!(t("chafo"), "chào");
665 assert_eq!(t("toans"), "toán");
666 assert_eq!(t("hoaf"), "hoà");
667 assert_eq!(t("thowif"), "thời");
668 assert_eq!(t("cuar"), "của");
669 assert_eq!(t("thoix"), "thõi");
670 assert_eq!(t("ofa"), "òa"); assert_eq!(t("oaf"), "oà"); }
674 #[test]
675 fn telex_english() {
676 assert_eq!(t("hello"), "hello");
677 assert_eq!(t("wood"), "wôd");
678 assert_eq!(t("reboot"), "rebôt");
679 }
680 #[test]
681 fn telex_z() {
682 assert_eq!(t("asz"), "a");
683 }
684 #[test]
685 fn telex_w_standalone() {
686 assert_eq!(t("w"), "w"); assert_eq!(tw("w"), "ư"); assert_eq!(tw("ws"), "ứ");
689 assert_eq!(tw("wf"), "ừ");
690 assert_eq!(tw("wn"), "ưn");
691 }
692
693 #[test]
696 fn telex_dd_traditional() {
697 assert_eq!(tt("dd"), "đ");
698 assert_eq!(tt("DD"), "Đ");
699 assert_eq!(tt("Dd"), "Đ");
700 assert_eq!(tt("dD"), "Đ");
701 }
702 #[test]
703 fn telex_marks_traditional() {
704 assert_eq!(tt("aa"), "â");
705 assert_eq!(tt("aw"), "ă");
706 assert_eq!(tt("ee"), "ê");
707 assert_eq!(tt("ew"), "ê");
708 assert_eq!(tt("oo"), "ô");
709 assert_eq!(tt("ow"), "ơ");
710 assert_eq!(tt("uw"), "ư");
711 assert_eq!(tt("uow"), "ươ");
712 }
713 #[test]
714 fn telex_w_traditional() {
715 assert_eq!(tt("w"), "ư");
716 assert_eq!(tt("W"), "Ư");
717 assert_eq!(tt("wn"), "ưn");
718 assert_eq!(tt("ws"), "ứ");
719 assert_eq!(tt("wf"), "ừ");
720 }
721 #[test]
722 fn telex_compound_traditional() {
723 assert_eq!(tt("aws"), "ắ");
724 assert_eq!(tt("aas"), "ấ");
725 assert_eq!(tt("ees"), "ế");
726 assert_eq!(tt("oos"), "ố");
727 assert_eq!(tt("ows"), "ớ");
728 assert_eq!(tt("uws"), "ứ");
729 }
730 #[test]
731 fn telex_words_traditional() {
732 assert_eq!(tt("chaof"), "chào");
733 assert_eq!(tt("vieejt"), "việt");
734 assert_eq!(tt("tieengs"), "tiếng");
735 assert_eq!(tt("quas"), "quá");
736 assert_eq!(tt("gias"), "giá");
737 assert_eq!(tt("xin"), "xin");
738 assert_eq!(tt("tooi"), "tôi");
739 assert_eq!(tt("laf"), "là");
740 assert_eq!(tt("cos"), "có");
741 assert_eq!(tt("khoong"), "không");
742 assert_eq!(tt("Vieetj"), "Việt");
743 }
744 #[test]
745 fn telex_tone_traditional() {
746 assert_eq!(tt("toans"), "toán");
748 assert_eq!(tt("hoaf"), "hoà");
749 assert_eq!(tt("thowif"), "thời");
750 assert_eq!(tt("cuar"), "của");
751 }
752 #[test]
753 fn telex_gi_qu_traditional() {
754 assert_eq!(tt("gif"), "gì");
755 assert_eq!(tt("giowf"), "giờ");
756 assert_eq!(tt("gioir"), "giỏi");
757 }
758 #[test]
759 fn telex_uow_traditional() {
760 assert_eq!(tt("cuowcs"), "cước");
761 assert_eq!(tt("dduwowcj"), "được");
762 assert_eq!(tt("nguwowif"), "người");
763 }
764 #[test]
765 fn telex_z_traditional() {
766 assert_eq!(tt("asz"), "a");
767 }
768 #[test]
769 fn telex_english_traditional() {
770 assert_eq!(tt("hello"), "hello");
771 }
772 #[test]
773 fn telex_more_traditional() {
774 assert_eq!(tt("hoanf"), "hoàn");
775 assert_eq!(tt("huyeenf"), "huyền");
776 assert_eq!(tt("nguyeexn"), "nguyễn");
777 assert_eq!(tt("ngoaif"), "ngoài");
778 assert_eq!(tt("thuowngf"), "thường");
779 }
780 #[test]
781 fn vni_marks_traditional() {
782 assert_eq!(vt("d9"), "đ");
783 assert_eq!(vt("a6"), "â");
784 assert_eq!(vt("a8"), "ă");
785 assert_eq!(vt("e6"), "ê");
786 assert_eq!(vt("o6"), "ô");
787 assert_eq!(vt("o7"), "ơ");
788 assert_eq!(vt("u7"), "ư");
789 assert_eq!(vt("uo7"), "ươ");
790 }
791 #[test]
792 fn vni_words_traditional() {
793 assert_eq!(vt("vie65t"), "việt");
794 assert_eq!(vt("tie61ng"), "tiếng");
795 assert_eq!(vt("qua1"), "quá");
796 assert_eq!(vt("gia1"), "giá");
797 }
798
799 #[test]
801 fn telex_tone_replace() {
802 assert_eq!(tt("saosf"), "sào"); assert_eq!(tt("saojs"), "sáo"); assert_eq!(tt("saojr"), "sảo"); assert_eq!(tt("hoasf"), "hoà"); assert_eq!(tt("toansr"), "toản"); }
808 #[test]
809 fn teipvni_tone_replace() {
810 assert_eq!(tv("saosf"), "sào"); assert_eq!(tv("sao1f"), "sào"); }
813
814 #[test]
816 fn telex_triple_toggle() {
817 assert_eq!(tt("rooot"), "root"); assert_eq!(tt("roo"), "rô");
819 assert_eq!(tt("aa"), "â"); assert_eq!(tt("aaa"), "aa"); assert_eq!(tt("aaaa"), "aâ"); assert_eq!(tt("ee"), "ê");
823 assert_eq!(tt("eee"), "ee");
824 assert_eq!(tt("oooo"), "oô"); }
826
827 #[test]
829 fn telex_double_vowel_across_y() {
830 assert_eq!(tt("vaya"), "vây"); assert_eq!(tt("vayaj"), "vậy"); assert_eq!(tt("vayas"), "vấy"); assert_eq!(tt("vayaf"), "vầy"); assert_eq!(tt("mayas"), "mấy"); assert_eq!(tt("dayaj"), "dậy"); assert_eq!(tt("quaya"), "quây"); assert_eq!(tt("quayar"), "quẩy"); assert_eq!(tt("saua"), "sâu");
840 assert_eq!(tt("sauas"), "sấu");
841 assert_eq!(tt("dauaf"), "dầu");
842 assert_eq!(tt("ee"), "ê");
844 assert_eq!(tt("aay"), "ây");
846 }
847
848 #[test]
850 fn telex_w_toggle() {
851 assert_eq!(tt("w"), "ư");
853 assert_eq!(tt("ww"), "w");
854 assert_eq!(tt("www"), "ww");
855 assert_eq!(tt("wwww"), "www");
856 assert_eq!(tt("wws"), "ws");
858 }
859 #[test]
860 fn telex_w_toggle_regular() {
861 assert_eq!(tt("aw"), "ă"); assert_eq!(tt("aww"), "a"); assert_eq!(tt("ow"), "ơ"); assert_eq!(tt("oww"), "o"); assert_eq!(tt("uw"), "ư"); assert_eq!(tt("uww"), "u"); assert_eq!(tt("ew"), "ê"); assert_eq!(tt("eww"), "e"); assert_eq!(tt("uow"), "ươ"); assert_eq!(tt("uoww"), "uo"); }
872
873 #[test]
875 fn telex_w_target() {
876 assert_eq!(tt("toiw"), "tơi");
878 assert_eq!(tt("toiws"), "tới"); assert_eq!(tt("toiwf"), "tời"); assert_eq!(tt("toiwr"), "tởi"); assert_eq!(tt("toiwj"), "tợi"); assert_eq!(tt("nguoiwf"), "người");
884 assert_eq!(tt("nguoiws"), "ngưới");
885 assert_eq!(tt("muoiwf"), "mười");
887 assert_eq!(tt("tuoiws"), "tưới");
889 assert_eq!(tt("tuooir"), "tuổi");
891 assert_eq!(tt("tuooif"), "tuồi");
892 }
893 #[test]
894 fn telex_toan() {
895 assert_eq!(tt("toanf"), "toàn");
897 assert_eq!(t("toanf"), "toàn");
898 assert_eq!(tt("hoanf"), "hoàn");
899 assert_eq!(t("hoanf"), "hoàn");
900 assert_eq!(t("oaf"), "oà"); assert_eq!(t("ofa"), "òa"); assert_eq!(tt("oaf"), "oà");
905 assert_eq!(tt("ofa"), "òa"); }
907
908 fn tv(s: &str) -> String {
910 convert_teip_vni(s, false, true)
911 }
912
913 #[test]
914 fn teipvni_telex_words() {
915 assert_eq!(tv("vieejt"), "việt");
916 assert_eq!(tv("dd"), "đ");
917 assert_eq!(tv("aa"), "â");
918 assert_eq!(tv("aw"), "ă");
919 assert_eq!(tv("ee"), "ê");
920 assert_eq!(tv("oo"), "ô");
921 assert_eq!(tv("ow"), "ơ");
922 assert_eq!(tv("uw"), "ư");
923 assert_eq!(tv("uow"), "ươ");
924 assert_eq!(tv("w"), "ư");
925 }
926 #[test]
927 fn teipvni_vni_marks() {
928 assert_eq!(tv("a6"), "â");
929 assert_eq!(tv("a8"), "ă");
930 assert_eq!(tv("e6"), "ê");
931 assert_eq!(tv("o6"), "ô");
932 assert_eq!(tv("o7"), "ơ");
933 assert_eq!(tv("u7"), "ư");
934 assert_eq!(tv("uo7"), "ươ");
935 assert_eq!(tv("d9"), "đ");
936 }
937 #[test]
938 fn teipvni_numbers() {
939 assert_eq!(tv("qua1"), "quá");
941 assert_eq!(tv("gia1"), "giá");
942 assert_eq!(tv("vie65t"), "việt");
943 assert_eq!(tv("tie61ng"), "tiếng");
944 }
945 #[test]
946 fn teipvni_mixed() {
947 assert_eq!(tv("vie6t"), "viêt"); assert_eq!(tv("vie6ts"), "viết"); assert_eq!(tv("tie6ngs"), "tiếng");
951 }
952
953 fn viqr(s: &str) -> String {
955 convert_viqr(s)
956 }
957
958 #[test]
959 fn viqr_marks() {
960 assert_eq!(viqr("a^"), "â");
961 assert_eq!(viqr("a("), "ă");
962 assert_eq!(viqr("e^"), "ê");
963 assert_eq!(viqr("o^"), "ô");
964 assert_eq!(viqr("o+"), "ơ");
965 assert_eq!(viqr("u+"), "ư");
966 assert_eq!(viqr("dd"), "đ");
967 }
968 #[test]
969 fn viqr_tones() {
970 assert_eq!(viqr("a'"), "á");
971 assert_eq!(viqr("a`"), "à");
972 assert_eq!(viqr("a?"), "ả");
973 assert_eq!(viqr("a~"), "ã");
974 assert_eq!(viqr("a."), "ạ");
975 }
976 #[test]
977 fn viqr_words() {
978 assert_eq!(viqr("vie^'t"), "viết");
979 assert_eq!(viqr("nu+o+'c"), "nước");
980 assert_eq!(viqr("ngu+o+`i"), "người");
981 }
982 #[test]
983 fn viqr_dd() {
984 assert_eq!(viqr("dd"), "đ");
985 assert_eq!(viqr("DD"), "Đ");
986 }
987 #[test]
988 fn viqr_english() {
989 assert_eq!(viqr("hello"), "hello");
990 }
991}
992
993#[test]
994fn full_parity() {
995 struct Case {
999 input: &'static str,
1000 expected: &'static str,
1001 method: u8,
1002 }
1003 let cases = [
1005 Case {
1007 input: "dd",
1008 expected: "đ",
1009 method: 1,
1010 },
1011 Case {
1012 input: "DD",
1013 expected: "Đ",
1014 method: 1,
1015 },
1016 Case {
1017 input: "Dd",
1018 expected: "Đ",
1019 method: 1,
1020 },
1021 Case {
1022 input: "dD",
1023 expected: "Đ",
1024 method: 1,
1025 },
1026 Case {
1027 input: "aa",
1028 expected: "â",
1029 method: 1,
1030 },
1031 Case {
1032 input: "aw",
1033 expected: "ă",
1034 method: 1,
1035 },
1036 Case {
1037 input: "ee",
1038 expected: "ê",
1039 method: 1,
1040 },
1041 Case {
1042 input: "ew",
1043 expected: "ê",
1044 method: 1,
1045 },
1046 Case {
1047 input: "oo",
1048 expected: "ô",
1049 method: 1,
1050 },
1051 Case {
1052 input: "ow",
1053 expected: "ơ",
1054 method: 1,
1055 },
1056 Case {
1057 input: "uw",
1058 expected: "ư",
1059 method: 1,
1060 },
1061 Case {
1062 input: "uow",
1063 expected: "ươ",
1064 method: 1,
1065 },
1066 Case {
1067 input: "w",
1068 expected: "ư",
1069 method: 1,
1070 },
1071 Case {
1072 input: "W",
1073 expected: "Ư",
1074 method: 1,
1075 },
1076 Case {
1077 input: "wn",
1078 expected: "ưn",
1079 method: 1,
1080 },
1081 Case {
1082 input: "ws",
1083 expected: "ứ",
1084 method: 1,
1085 },
1086 Case {
1087 input: "wf",
1088 expected: "ừ",
1089 method: 1,
1090 },
1091 Case {
1092 input: "aws",
1093 expected: "ắ",
1094 method: 1,
1095 },
1096 Case {
1097 input: "aas",
1098 expected: "ấ",
1099 method: 1,
1100 },
1101 Case {
1102 input: "ees",
1103 expected: "ế",
1104 method: 1,
1105 },
1106 Case {
1107 input: "oos",
1108 expected: "ố",
1109 method: 1,
1110 },
1111 Case {
1112 input: "ows",
1113 expected: "ớ",
1114 method: 1,
1115 },
1116 Case {
1117 input: "uws",
1118 expected: "ứ",
1119 method: 1,
1120 },
1121 Case {
1122 input: "chaof",
1123 expected: "chào",
1124 method: 1,
1125 },
1126 Case {
1127 input: "vieejt",
1128 expected: "việt",
1129 method: 1,
1130 },
1131 Case {
1132 input: "tieengs",
1133 expected: "tiếng",
1134 method: 1,
1135 },
1136 Case {
1137 input: "quas",
1138 expected: "quá",
1139 method: 1,
1140 },
1141 Case {
1142 input: "gias",
1143 expected: "giá",
1144 method: 1,
1145 },
1146 Case {
1147 input: "xin",
1148 expected: "xin",
1149 method: 1,
1150 },
1151 Case {
1152 input: "tooi",
1153 expected: "tôi",
1154 method: 1,
1155 },
1156 Case {
1157 input: "laf",
1158 expected: "là",
1159 method: 1,
1160 },
1161 Case {
1162 input: "cos",
1163 expected: "có",
1164 method: 1,
1165 },
1166 Case {
1167 input: "khoong",
1168 expected: "không",
1169 method: 1,
1170 },
1171 Case {
1172 input: "Vieetj",
1173 expected: "Việt",
1174 method: 1,
1175 },
1176 Case {
1177 input: "toans",
1178 expected: "toán",
1179 method: 1,
1180 },
1181 Case {
1182 input: "hoaf",
1183 expected: "hoà",
1184 method: 1,
1185 },
1186 Case {
1187 input: "thowif",
1188 expected: "thời",
1189 method: 1,
1190 },
1191 Case {
1192 input: "cuar",
1193 expected: "của",
1194 method: 1,
1195 },
1196 Case {
1197 input: "gif",
1198 expected: "gì",
1199 method: 1,
1200 },
1201 Case {
1202 input: "giowf",
1203 expected: "giờ",
1204 method: 1,
1205 },
1206 Case {
1207 input: "gioir",
1208 expected: "giỏi",
1209 method: 1,
1210 },
1211 Case {
1212 input: "cuowcs",
1213 expected: "cước",
1214 method: 1,
1215 },
1216 Case {
1217 input: "dduwowcj",
1218 expected: "được",
1219 method: 1,
1220 },
1221 Case {
1222 input: "nguwowif",
1223 expected: "người",
1224 method: 1,
1225 },
1226 Case {
1227 input: "asz",
1228 expected: "a",
1229 method: 1,
1230 },
1231 Case {
1232 input: "hello",
1233 expected: "hello",
1234 method: 1,
1235 },
1236 Case {
1237 input: "hoanf",
1238 expected: "hoàn",
1239 method: 1,
1240 },
1241 Case {
1242 input: "huyeenf",
1243 expected: "huyền",
1244 method: 1,
1245 },
1246 Case {
1247 input: "nguyeexn",
1248 expected: "nguyễn",
1249 method: 1,
1250 },
1251 Case {
1252 input: "ngoaif",
1253 expected: "ngoài",
1254 method: 1,
1255 },
1256 Case {
1257 input: "thuowngf",
1258 expected: "thường",
1259 method: 1,
1260 },
1261 Case {
1263 input: "d9",
1264 expected: "đ",
1265 method: 2,
1266 },
1267 Case {
1268 input: "a6",
1269 expected: "â",
1270 method: 2,
1271 },
1272 Case {
1273 input: "a8",
1274 expected: "ă",
1275 method: 2,
1276 },
1277 Case {
1278 input: "e6",
1279 expected: "ê",
1280 method: 2,
1281 },
1282 Case {
1283 input: "o6",
1284 expected: "ô",
1285 method: 2,
1286 },
1287 Case {
1288 input: "o7",
1289 expected: "ơ",
1290 method: 2,
1291 },
1292 Case {
1293 input: "u7",
1294 expected: "ư",
1295 method: 2,
1296 },
1297 Case {
1298 input: "uo7",
1299 expected: "ươ",
1300 method: 2,
1301 },
1302 Case {
1303 input: "vie65t",
1304 expected: "việt",
1305 method: 2,
1306 },
1307 Case {
1308 input: "tie61ng",
1309 expected: "tiếng",
1310 method: 2,
1311 },
1312 Case {
1313 input: "qua1",
1314 expected: "quá",
1315 method: 2,
1316 },
1317 Case {
1318 input: "gia1",
1319 expected: "giá",
1320 method: 2,
1321 },
1322 Case {
1324 input: "vie61t",
1325 expected: "viết",
1326 method: 4,
1327 },
1328 Case {
1329 input: "vieejt",
1330 expected: "việt",
1331 method: 4,
1332 },
1333 Case {
1334 input: "tie61ng",
1335 expected: "tiếng",
1336 method: 4,
1337 },
1338 Case {
1339 input: "a6",
1340 expected: "â",
1341 method: 4,
1342 },
1343 Case {
1344 input: "dd",
1345 expected: "đ",
1346 method: 4,
1347 },
1348 Case {
1350 input: "dd",
1351 expected: "đ",
1352 method: 3,
1353 },
1354 Case {
1355 input: "DD",
1356 expected: "Đ",
1357 method: 3,
1358 },
1359 Case {
1360 input: "vie^'t",
1361 expected: "viết",
1362 method: 3,
1363 },
1364 Case {
1365 input: "nu+o+'c",
1366 expected: "nước",
1367 method: 3,
1368 },
1369 Case {
1370 input: "ngu+o+`i",
1371 expected: "người",
1372 method: 3,
1373 },
1374 ];
1375
1376 let mut failures = Vec::new();
1377 for c in &cases {
1378 let result = match c.method {
1379 1 => convert_telex(c.input, false, true),
1380 2 => convert_vni(c.input, false, true),
1381 3 => convert_viqr(c.input),
1382 4 => convert_teip_vni(c.input, false, true),
1383 _ => unreachable!(),
1384 };
1385 if result != c.expected {
1386 failures.push(format!(
1387 "[m{}] '{}' -> '{}' (expected '{}')",
1388 c.method, c.input, result, c.expected
1389 ));
1390 }
1391 }
1392 assert!(
1393 failures.is_empty(),
1394 "{} full parity failures:\n{}",
1395 failures.len(),
1396 failures.join("\n")
1397 );
1398}
1399
1400#[test]
1401fn comprehensive_vietnamese() {
1402 let cases: &[(&str, &str, u8)] = &[
1408 ("as", "á", 1),
1410 ("af", "à", 1),
1411 ("ar", "ả", 1),
1412 ("ax", "ã", 1),
1413 ("aj", "ạ", 1),
1414 ("es", "é", 1),
1415 ("ef", "è", 1),
1416 ("er", "ẻ", 1),
1417 ("ex", "ẽ", 1),
1418 ("ej", "ẹ", 1),
1419 ("is", "í", 1),
1420 ("if", "ì", 1),
1421 ("ir", "ỉ", 1),
1422 ("ix", "ĩ", 1),
1423 ("ij", "ị", 1),
1424 ("os", "ó", 1),
1425 ("of", "ò", 1),
1426 ("or", "ỏ", 1),
1427 ("ox", "õ", 1),
1428 ("oj", "ọ", 1),
1429 ("us", "ú", 1),
1430 ("uf", "ù", 1),
1431 ("ur", "ủ", 1),
1432 ("ux", "ũ", 1),
1433 ("uj", "ụ", 1),
1434 ("ys", "ý", 1),
1435 ("yf", "ỳ", 1),
1436 ("yr", "ỷ", 1),
1437 ("yx", "ỹ", 1),
1438 ("yj", "ỵ", 1),
1439 ("aw", "ă", 1),
1442 ("aws", "ắ", 1),
1443 ("awf", "ằ", 1),
1444 ("awr", "ẳ", 1),
1445 ("awx", "ẵ", 1),
1446 ("awj", "ặ", 1),
1447 ("aa", "â", 1),
1449 ("aas", "ấ", 1),
1450 ("aaf", "ầ", 1),
1451 ("aar", "ẩ", 1),
1452 ("aax", "ẫ", 1),
1453 ("aaj", "ậ", 1),
1454 ("ee", "ê", 1),
1456 ("ees", "ế", 1),
1457 ("eef", "ề", 1),
1458 ("eer", "ể", 1),
1459 ("eex", "ễ", 1),
1460 ("eej", "ệ", 1),
1461 ("ew", "ê", 1),
1462 ("ews", "ế", 1),
1463 ("ewf", "ề", 1),
1464 ("ewr", "ể", 1),
1465 ("oo", "ô", 1),
1467 ("oos", "ố", 1),
1468 ("oof", "ồ", 1),
1469 ("oor", "ổ", 1),
1470 ("oox", "ỗ", 1),
1471 ("ooj", "ộ", 1),
1472 ("ow", "ơ", 1),
1474 ("ows", "ớ", 1),
1475 ("owf", "ờ", 1),
1476 ("owr", "ở", 1),
1477 ("owx", "ỡ", 1),
1478 ("owj", "ợ", 1),
1479 ("uw", "ư", 1),
1481 ("uws", "ứ", 1),
1482 ("uwf", "ừ", 1),
1483 ("uwr", "ử", 1),
1484 ("uwx", "ữ", 1),
1485 ("uwj", "ự", 1),
1486 ("dd", "đ", 1),
1488 ("w", "ư", 1),
1490 ("ws", "ứ", 1),
1491 ("wf", "ừ", 1),
1492 ("wr", "ử", 1),
1493 ("wx", "ữ", 1),
1494 ("wj", "ự", 1),
1495 ("wn", "ưn", 1),
1496 ("wt", "ưt", 1),
1497 ("ais", "ái", 1),
1500 ("aif", "ài", 1),
1501 ("air", "ải", 1),
1502 ("aix", "ãi", 1),
1503 ("aij", "ại", 1),
1504 ("aos", "áo", 1),
1505 ("aof", "ào", 1),
1506 ("aor", "ảo", 1),
1507 ("aox", "ão", 1),
1508 ("aoj", "ạo", 1),
1509 ("aus", "áu", 1),
1510 ("auf", "àu", 1),
1511 ("aur", "ảu", 1),
1512 ("aux", "ãu", 1),
1513 ("auj", "ạu", 1),
1514 ("aays", "ấy", 1),
1516 ("aayf", "ầy", 1),
1517 ("aayr", "ẩy", 1),
1518 ("aayx", "ẫy", 1),
1519 ("aayj", "ậy", 1),
1520 ("ays", "áy", 1),
1521 ("ayf", "ày", 1),
1522 ("ayr", "ảy", 1),
1523 ("ayx", "ãy", 1),
1524 ("ayj", "ạy", 1),
1525 ("eos", "éo", 1),
1527 ("eof", "èo", 1),
1528 ("eor", "ẻo", 1),
1529 ("eox", "ẽo", 1),
1530 ("eoj", "ẹo", 1),
1531 ("eeus", "ếu", 1),
1532 ("eeuf", "ều", 1),
1533 ("eeur", "ểu", 1),
1534 ("eeux", "ễu", 1),
1535 ("eeuj", "ệu", 1),
1536 ("ois", "ói", 1),
1538 ("oif", "òi", 1),
1539 ("oir", "ỏi", 1),
1540 ("oix", "õi", 1),
1541 ("oij", "ọi", 1),
1542 ("oois", "ối", 1),
1543 ("ooif", "ồi", 1),
1544 ("ooir", "ổi", 1),
1545 ("ooix", "ỗi", 1),
1546 ("ooij", "ội", 1),
1547 ("owis", "ới", 1),
1548 ("owif", "ời", 1),
1549 ("owir", "ởi", 1),
1550 ("owix", "ỡi", 1),
1551 ("owij", "ợi", 1),
1552 ("uis", "úi", 1),
1553 ("uif", "ùi", 1),
1554 ("uir", "ủi", 1),
1555 ("uix", "ũi", 1),
1556 ("uij", "ụi", 1),
1557 ("uwis", "ứi", 1),
1558 ("uwif", "ừi", 1),
1559 ("uwir", "ửi", 1),
1560 ("uwix", "ữi", 1),
1561 ("uwij", "ựi", 1),
1562 ("uwus", "ứu", 1),
1563 ("uwuf", "ừu", 1),
1564 ("uwur", "ửu", 1),
1565 ("uwux", "ữu", 1),
1566 ("uwuj", "ựu", 1),
1567 ("oans", "oán", 1),
1569 ("oanf", "oàn", 1),
1570 ("oanr", "oản", 1),
1571 ("oanx", "oãn", 1),
1572 ("oanj", "oạn", 1),
1573 ("oats", "oát", 1),
1574 ("oatf", "oàt", 1),
1575 ("oatr", "oảt", 1),
1576 ("oens", "oén", 1),
1577 ("oenf", "oèn", 1),
1578 ("uyns", "uýn", 1),
1579 ("uynf", "uỳn", 1),
1580 ("uynr", "uỷn", 1),
1581 ("oas", "oá", 1),
1583 ("oaf", "oà", 1),
1584 ("oar", "oả", 1),
1585 ("oax", "oã", 1),
1586 ("oaj", "oạ", 1),
1587 ("oes", "oé", 1),
1588 ("oef", "oè", 1),
1589 ("uys", "uý", 1),
1590 ("uyf", "uỳ", 1),
1591 ("uyr", "uỷ", 1),
1592 ("uyx", "uỹ", 1),
1593 ("uyj", "uỵ", 1),
1594 ("oais", "oái", 1),
1597 ("oaif", "oài", 1),
1598 ("oair", "oải", 1),
1599 ("oaix", "oãi", 1),
1600 ("oaij", "oại", 1),
1601 ("oays", "oáy", 1),
1602 ("oayf", "oày", 1),
1603 ("oayr", "oảy", 1),
1604 ("oayx", "oãy", 1),
1605 ("oayj", "oạy", 1),
1606 ("uaays", "uấy", 1),
1608 ("uaayf", "uầy", 1),
1609 ("uaayr", "uẩy", 1),
1610 ("uaayx", "uẫy", 1),
1611 ("uaayj", "uậy", 1),
1612 ("uyeef", "uyề", 1),
1614 ("uyees", "uyế", 1),
1615 ("uyeer", "uyể", 1),
1616 ("uyeex", "uyễ", 1),
1617 ("uyeej", "uyệ", 1),
1618 ("ieef", "iề", 1),
1620 ("iees", "iế", 1),
1621 ("ieer", "iể", 1),
1622 ("ieex", "iễ", 1),
1623 ("ieej", "iệ", 1),
1624 ("yeef", "yề", 1),
1625 ("yees", "yế", 1),
1626 ("yeer", "yể", 1),
1627 ("yeex", "yễ", 1),
1628 ("yeej", "yệ", 1),
1629 ("uoois", "uối", 1),
1631 ("uooif", "uồi", 1),
1632 ("uooir", "uổi", 1),
1633 ("uooix", "uỗi", 1),
1634 ("uooij", "uội", 1),
1635 ("uoiws", "ưới", 1),
1637 ("uoiwf", "ười", 1),
1638 ("uoiwr", "ưởi", 1),
1639 ("uoiwx", "ưỡi", 1),
1640 ("uoiwj", "ượi", 1),
1641 ("gif", "gì", 1),
1643 ("gis", "gí", 1),
1644 ("gir", "gỉ", 1),
1645 ("gix", "gĩ", 1),
1646 ("gij", "gị", 1),
1647 ("gias", "giá", 1),
1648 ("giaf", "già", 1),
1649 ("giar", "giả", 1),
1650 ("giax", "giã", 1),
1651 ("giaj", "giạ", 1),
1652 ("gioir", "giỏi", 1),
1653 ("gioif", "giòi", 1),
1654 ("giois", "giói", 1),
1655 ("quas", "quá", 1),
1656 ("quaf", "quà", 1),
1657 ("quar", "quả", 1),
1658 ("quax", "quã", 1),
1659 ("quaj", "quạ", 1),
1660 ("quys", "quý", 1),
1661 ("quyf", "quỳ", 1),
1662 ("quyr", "quỷ", 1),
1663 ("quyx", "quỹ", 1),
1664 ("quyj", "quỵ", 1),
1665 ("chaof", "chào", 1),
1667 ("vieejt", "việt", 1),
1668 ("tieengs", "tiếng", 1),
1669 ("khoong", "không", 1),
1670 ("cuar", "của", 1),
1671 ("cuowcs", "cước", 1),
1672 ("dduwowcj", "được", 1),
1673 ("nguwowif", "người", 1),
1674 ("thuowngf", "thường", 1),
1675 ("hoanf", "hoàn", 1),
1676 ("huyeenf", "huyền", 1),
1677 ("nguyeexn", "nguyễn", 1),
1678 ("ngoaif", "ngoài", 1),
1679 ("ngoafi", "ngoài", 1),
1680 ("vaya", "vây", 1),
1681 ("vayaj", "vậy", 1),
1682 ("vayas", "vấy", 1),
1683 ("mayas", "mấy", 1),
1684 ("nhana", "nhân", 1),
1686 ("khanas", "khấn", 1),
1687 ("nhanaa", "nhana", 1),
1688 ("toans", "toán", 1),
1689 ("thowif", "thời", 1),
1690 ("xin", "xin", 1),
1691 ("tooi", "tôi", 1),
1692 ("laf", "là", 1),
1693 ("cos", "có", 1),
1694 ("banj", "bạn", 1),
1695 ("minhf", "mình", 1),
1696 ("nawm", "năm", 1),
1697 ("thangs", "tháng", 1),
1698 ("truwowngf", "trường", 1),
1699 ("luaatj", "luật", 1),
1700 ("nhas", "nhá", 1),
1702 ("nghir", "nghỉ", 1),
1703 ("gheps", "ghép", 1),
1704 ("truws", "trứ", 1),
1705 ("thas", "thá", 1),
1706 ("phos", "phó", 1),
1707 ("khas", "khá", 1),
1708 ("chays", "cháy", 1),
1709 ("ngays", "ngáy", 1),
1710 ("ddejp", "đẹp", 1),
1712 ("maats", "mất", 1),
1713 ("bacs", "bác", 1),
1714 ("ngachs", "ngách", 1),
1715 ("banhf", "bành", 1),
1716 ("tins", "tín", 1),
1717 ("metj", "mẹt", 1),
1718 ("asz", "a", 1),
1720 ("awsjz", "ă", 1),
1721 ("aasz", "â", 1),
1722 ("hello", "hello", 1),
1724 ("bacon", "bacon", 1),
1725 ("thing", "thing", 1),
1726 ("climb", "climb", 1),
1727 ("As", "Á", 1),
1729 ("Af", "À", 1),
1730 ("Aas", "Ấ", 1),
1731 ("Aws", "Ắ", 1),
1732 ("Dd", "Đ", 1),
1733 ("DD", "Đ", 1),
1734 ("W", "Ư", 1),
1735 ("Ws", "Ứ", 1),
1736 ("Vieetj", "Việt", 1),
1737 ("Tooi", "Tôi", 1),
1738 ("d9", "đ", 2),
1740 ("a6", "â", 2),
1741 ("a8", "ă", 2),
1742 ("e6", "ê", 2),
1743 ("o6", "ô", 2),
1744 ("o7", "ơ", 2),
1745 ("u7", "ư", 2),
1746 ("uo7", "ươ", 2),
1747 ("vie65t", "việt", 2),
1748 ("tie61ng", "tiếng", 2),
1749 ("qua1", "quá", 2),
1750 ("gia1", "giá", 2),
1751 ("vie61t", "viết", 4),
1753 ("vieejt", "việt", 4),
1754 ("tie61ng", "tiếng", 4),
1755 ("a6", "â", 4),
1756 ("dd", "đ", 4),
1757 ("dd", "đ", 3),
1759 ("DD", "Đ", 3),
1760 ("vie^'t", "viết", 3),
1761 ("nu+o+'c", "nước", 3),
1762 ("ngu+o+`i", "người", 3),
1763 ];
1764
1765 let mut failures = Vec::new();
1766 for &(input, expected, method) in cases {
1767 let result = match method {
1768 1 => convert_telex(input, false, true),
1769 2 => convert_vni(input, false, true),
1770 3 => convert_viqr(input),
1771 4 => convert_teip_vni(input, false, true),
1772 _ => unreachable!(),
1773 };
1774 if result != expected {
1775 failures.push(format!(
1776 "[m{}] '{}' -> '{}' (expected '{}')",
1777 method, input, result, expected
1778 ));
1779 }
1780 }
1781 assert!(
1782 failures.is_empty(),
1783 "{} comprehensive failures (traditional mode):\n{}",
1784 failures.len(),
1785 failures.join("\n")
1786 );
1787}