1use crate::tables;
6
7#[derive(Clone, Copy, Default)]
8pub(crate) struct Letter {
9 pub(crate) c: char,
10 pub(crate) is_vowel: bool,
11 pub(crate) variant: u8,
12 pub(crate) tone: u8,
13 pub(crate) is_dstroke: bool,
14 pub(crate) upper: bool,
15 pub(crate) from_w: bool,
16 pub(crate) horn_propagated: bool, pub(crate) circ_toggled: bool, pub(crate) d_toggled: bool, pub(crate) horn_toggled: bool, }
21
22impl Letter {
23 pub(crate) fn new(c: char, upper: bool) -> Self {
24 let lc = c.to_ascii_lowercase();
25 Letter {
26 c: lc,
27 is_vowel: tables::is_vowel(lc),
28 variant: 0,
29 tone: 0,
30 is_dstroke: false,
31 upper,
32 from_w: false,
33 horn_propagated: false,
34 circ_toggled: false,
35 d_toggled: false,
36 horn_toggled: false,
37 }
38 }
39}
40
41fn emit(ls: &[Letter]) -> String {
42 let mut out = String::with_capacity(ls.len() * 3);
43 for lt in ls {
44 if lt.is_dstroke {
45 out.push_str(if lt.upper {
46 tables::D_STROKE_UP
47 } else {
48 tables::D_STROKE_LO
49 });
50 } else if lt.is_vowel {
51 out.push_str(tables::vowel_utf8(lt.c, lt.variant, lt.tone, lt.upper));
52 } else {
53 out.push(if lt.upper {
54 lt.c.to_ascii_uppercase()
55 } else {
56 lt.c
57 });
58 }
59 }
60 out
61}
62
63fn last_vowel_index(ls: &[Letter]) -> Option<usize> {
64 ls.iter().rposition(|lt| lt.is_vowel)
65}
66fn last_d_index(ls: &[Letter]) -> Option<usize> {
67 ls.iter()
68 .rposition(|lt| lt.c == 'd' && !lt.is_vowel && !lt.is_dstroke)
69}
70fn last_dstroke_index(ls: &[Letter]) -> Option<usize> {
71 ls.iter().rposition(|lt| lt.is_dstroke)
72}
73
74fn tone_vowel_index(ls: &[Letter], modern: bool) -> Option<usize> {
80 let end = last_vowel_index(ls)?;
81 let mut start = end;
82 while start > 0 && ls[start - 1].is_vowel {
83 start -= 1;
84 }
85 if start < end {
86 if ls[start].c == 'u' && start > 0 && ls[start - 1].c == 'q' {
87 start += 1;
88 } else if ls[start].c == 'i' && start > 0 && ls[start - 1].c == 'g' {
89 start += 1;
90 }
91 }
92 if start > end {
93 start = end;
94 }
95 let count = end - start + 1;
96 if count < 3 {
100 for i in (start..=end).rev() {
101 if ls[i].variant != 0 {
102 return Some(i);
103 }
104 }
105 }
106 if count == 1 {
107 return Some(start);
108 }
109 if count >= 3 {
110 if ls[start].c == 'u'
111 && start + 2 <= end
112 && ls[start + 1].c == 'y'
113 && ls[start + 2].c == 'e'
114 {
115 return Some(start + 2);
116 }
117 return Some(start + 1);
118 }
119 if end + 1 < ls.len() {
120 return Some(end);
121 }
122 let (c1, c2) = (ls[start].c, ls[end].c);
123 if (c1 == 'o' && c2 == 'a') || (c1 == 'o' && c2 == 'e') || (c1 == 'u' && c2 == 'y') {
124 return if modern { Some(end) } else { Some(start) };
127 }
128 Some(start) }
130
131fn reposition_tone_after_hook(ls: &mut [Letter], modern: bool) {
135 let end = match last_vowel_index(ls) {
136 Some(e) => e,
137 None => return,
138 };
139 let mut start = end;
140 while start > 0 && ls[start - 1].is_vowel {
141 start -= 1;
142 }
143 if start < end {
145 if ls[start].c == 'u' && start > 0 && ls[start - 1].c == 'q' {
146 start += 1;
147 } else if ls[start].c == 'i' && start > 0 && ls[start - 1].c == 'g' {
148 start += 1;
149 }
150 }
151 if start > end {
152 return;
153 }
154
155 let cur_tone_idx = (start..=end).find(|&i| ls[i].tone != 0);
157 let tone_val = match cur_tone_idx {
158 Some(i) => ls[i].tone,
159 None => return, };
161
162 let new_tone_idx = match tone_vowel_index(ls, modern) {
164 Some(i) => i,
165 None => return,
166 };
167
168 if Some(new_tone_idx) != cur_tone_idx {
170 if let Some(old) = cur_tone_idx {
171 ls[old].tone = 0;
172 }
173 ls[new_tone_idx].tone = tone_val;
174 }
175}
176
177fn telex_tone(c: char) -> Option<u8> {
178 match c.to_ascii_lowercase() {
179 's' => Some(2),
180 'f' => Some(1),
181 'r' => Some(3),
182 'x' => Some(4),
183 'j' => Some(5),
184 _ => None,
185 }
186}
187fn vni_tone(c: char) -> Option<u8> {
188 match c {
189 '1' => Some(2),
190 '2' => Some(1),
191 '3' => Some(3),
192 '4' => Some(4),
193 '5' => Some(5),
194 _ => None,
195 }
196}
197
198pub fn convert_telex(input: &str, modern: bool, short_w: bool) -> String {
199 convert_telex_impl(input, modern, short_w)
200}
201
202pub fn convert_telex_auto_restore(input: &str, modern: bool, short_w: bool) -> String {
207 let composed = convert_telex_impl(input, modern, short_w);
208 let all_ascii = composed.chars().all(|c| c.is_ascii());
209 if composed != input
210 && !all_ascii
211 && !crate::spelling::is_valid_cvc(&composed)
212 && !crate::has_vn_markers(input)
213 {
214 input.to_string()
215 } else {
216 composed
217 }
218}
219
220fn convert_telex_impl(input: &str, modern: bool, short_w: bool) -> String {
221 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
222 for ch in input.chars() {
223 let lc = ch.to_ascii_lowercase();
224 let upper = ch.is_ascii_uppercase();
225 if lc == 'd' {
228 let any_d_toggled = ls.iter().any(|lt| lt.c == 'd' && lt.d_toggled);
229 if !any_d_toggled && last_dstroke_index(&ls).is_none() {
230 if let Some(di) = last_d_index(&ls) {
232 ls[di].is_dstroke = true;
233 if upper {
234 ls[di].upper = true;
235 }
236 continue;
237 }
238 } else if last_dstroke_index(&ls).is_some() {
239 if let Some(di) = last_dstroke_index(&ls) {
241 ls[di].is_dstroke = false;
242 ls[di].d_toggled = true;
243 }
245 }
246 }
247 if matches!(lc, 'a' | 'e' | 'o') {
251 let mut consumed = false;
254 if let Some(vi) = last_vowel_index(&ls) {
255 let any_circ_toggled = ls[..=vi]
257 .iter()
258 .any(|lt| lt.is_vowel && lt.c == lc && lt.circ_toggled);
259 if !any_circ_toggled {
260 for i in (0..=vi).rev() {
261 if ls[i].is_vowel {
262 if ls[i].c == lc {
263 if i < vi {
264 let blocked = ls[i + 1..=vi]
270 .iter()
271 .any(|lt| lt.is_vowel
272 && !matches!(
273 (lc, lt.c),
274 ('a', 'y' | 'u')
275 | ('e', 'u')
276 | ('o', 'i')
277 ));
278 if blocked {
279 break;
280 }
281 }
282 if ls[i].variant == 0 {
283 ls[i].variant = 1;
284 if upper {
285 ls[i].upper = true;
286 }
287 consumed = true;
288 break;
289 }
290 if ls[i].variant == 1 {
291 ls[i].variant = 0;
292 ls[i].circ_toggled = true;
293 break;
294 } if ls[i].variant == 2 {
296 ls[i].variant = 1;
302 if upper {
303 ls[i].upper = true;
304 }
305 if ls[i].c == 'o'
306 && i > 0
307 && ls[i - 1].is_vowel
308 && ls[i - 1].c == 'u'
309 && ls[i - 1].variant == 2
310 {
311 ls[i - 1].variant = 0;
312 }
313 consumed = true;
314 break;
315 }
316 }
317 } else {
318 break;
319 }
320 }
321 }
322 }
323 if consumed {
324 continue;
325 }
326 }
327 if lc == 'w' {
328 let hook_result = crate::vseq::try_apply_hook(&mut ls);
332 match hook_result {
333 crate::vseq::HookResult::Applied => {
334 reposition_tone_after_hook(&mut ls, modern);
336 continue;
337 }
338 crate::vseq::HookResult::ToggledOff => {
339 reposition_tone_after_hook(&mut ls, modern);
341 }
343 crate::vseq::HookResult::NotApplicable => {
344 if crate::vseq::try_insert_horn(&mut ls, short_w, upper) {
345 continue;
346 }
347 }
349 }
350 }
351 if let Some(t) = telex_tone(lc) {
352 if let Some(vi) = tone_vowel_index(&ls, modern) {
353 ls[vi].tone = t;
354 continue; }
356 }
357 if lc == 'z' {
358 if let Some(vi) = tone_vowel_index(&ls, modern) {
359 if ls[vi].tone != 0 {
360 ls[vi].tone = 0;
361 continue;
362 }
363 }
364 }
365 ls.push(Letter::new(ch, upper));
366 if ls.len() >= 2 {
369 let last = ls.len() - 1;
370 if ls[last].is_vowel && ls[last].variant == 0 {
371 let prev = last - 1;
372 if ls[prev].is_vowel && ls[prev].variant == 2 {
373 if ls[prev].c == 'u' && ls[last].c == 'o' {
374 ls[last].variant = 2;
375 ls[last].horn_propagated = true;
376 }
377 if ls[prev].c == 'o' && ls[last].c == 'u' {
378 let has_u_horn_before = prev >= 1
379 && ls[prev - 1].is_vowel
380 && ls[prev - 1].c == 'u'
381 && ls[prev - 1].variant == 2;
382 if !has_u_horn_before {
383 ls[last].variant = 2;
384 ls[last].horn_propagated = true;
385 }
386 }
387 }
388 }
389 }
390 if ls.len() >= 2 {
395 let last = ls.len() - 1;
396 if ls[last].is_vowel {
397 let first_vowel = (0..last).find(|&j| ls[j].is_vowel);
399 if let Some(fv) = first_vowel {
400 let vcount = last - fv + 1;
401 if vcount >= 2 {
403 for j in (fv..last).rev() {
404 if ls[j].tone != 0 {
405 let move_tone = (ls[j].c == 'i' && j > 0 && ls[j - 1].c == 'g')
406 || (ls[j].c == 'u' && j > 0 && ls[j - 1].c == 'q');
407 if move_tone {
408 ls[last].tone = ls[j].tone;
409 ls[j].tone = 0;
410 }
411 break;
412 }
413 }
414 }
415 if vcount >= 3 && (fv..=last).all(|j| ls[j].is_vowel) {
421 for j in (fv..last).rev() {
422 if ls[j].tone != 0 {
423 let t = ls[j].tone;
424 ls[j].tone = 0;
425 let target = if ls[fv].c == 'u'
426 && fv + 2 <= last
427 && ls[fv + 1].c == 'y'
428 && ls[fv + 2].c == 'e'
429 {
430 fv + 2 } else {
432 fv + 1 };
434 if target <= last && target != j {
437 ls[target].tone = t;
438 } else {
439 ls[j].tone = t; }
441 break;
442 }
443 }
444 }
445 }
446 } else if lc != 'w' {
447 if let Some(end) = last_vowel_index(&ls) {
451 if end + 1 < ls.len() {
452 let fv = (0..end).find(|&j| ls[j].is_vowel);
453 if let Some(fv) = fv {
454 if fv < end {
455 for j in (fv..end).rev() {
456 if ls[j].tone != 0 {
457 ls[end].tone = ls[j].tone;
458 ls[j].tone = 0;
459 break;
460 }
461 }
462 }
463 }
464 }
465 }
466 }
467 }
468 }
469 emit(&ls)
470}
471
472pub fn convert_teip_vni(input: &str, modern: bool, short_w: bool) -> String {
478 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
479 for ch in input.chars() {
480 let lc = ch.to_ascii_lowercase();
481 let upper = ch.is_ascii_uppercase();
482
483 if let Some(t) = vni_tone(lc) {
485 if let Some(vi) = tone_vowel_index(&ls, modern) {
486 ls[vi].tone = t;
487 continue;
488 }
489 }
490 if lc >= '0' && lc <= '9' {
491 match lc {
492 '6' => {
493 if let Some(vi) = last_vowel_index(&ls) {
494 if matches!(ls[vi].c, 'a' | 'e' | 'o') {
495 ls[vi].variant = 1;
496 continue;
497 }
498 }
499 }
500 '7' => {
501 if let Some(vi) = last_vowel_index(&ls) {
502 if matches!(ls[vi].c, 'o' | 'u') {
503 ls[vi].variant = 2;
504 if ls[vi].c == 'o' && vi > 0 && ls[vi - 1].c == 'u' {
505 ls[vi - 1].variant = 2;
506 }
507 continue;
508 }
509 }
510 }
511 '8' => {
512 if let Some(vi) = last_vowel_index(&ls) {
513 if ls[vi].c == 'a' {
514 ls[vi].variant = 2;
515 continue;
516 }
517 }
518 }
519 '9' => {
520 if let Some(di) = last_d_index(&ls) {
521 ls[di].is_dstroke = true;
522 if upper {
523 ls[di].upper = true;
524 }
525 }
526 continue;
527 }
528 '0' => {
529 continue;
530 }
531 _ => {} }
533 }
534
535 if lc == 'd' {
537 let any_d_toggled = ls.iter().any(|lt| lt.c == 'd' && lt.d_toggled);
538 if !any_d_toggled && last_dstroke_index(&ls).is_none() {
539 if let Some(di) = last_d_index(&ls) {
540 ls[di].is_dstroke = true;
541 if upper {
542 ls[di].upper = true;
543 }
544 continue;
545 }
546 } else if last_dstroke_index(&ls).is_some() {
547 if let Some(di) = last_dstroke_index(&ls) {
548 ls[di].is_dstroke = false;
549 ls[di].d_toggled = true;
550 }
552 }
553 }
554
555 if matches!(lc, 'a' | 'e' | 'o') {
557 let mut consumed = false;
560 if let Some(vi) = last_vowel_index(&ls) {
561 let any_circ_toggled = ls[..=vi]
563 .iter()
564 .any(|lt| lt.is_vowel && lt.c == lc && lt.circ_toggled);
565 if !any_circ_toggled {
566 for i in (0..=vi).rev() {
567 if ls[i].is_vowel {
568 if ls[i].c == lc {
569 if i < vi {
570 let blocked = ls[i + 1..=vi]
576 .iter()
577 .any(|lt| lt.is_vowel
578 && !matches!(
579 (lc, lt.c),
580 ('a', 'y' | 'u')
581 | ('e', 'u')
582 | ('o', 'i')
583 ));
584 if blocked {
585 break;
586 }
587 }
588 if ls[i].variant == 0 {
589 ls[i].variant = 1;
590 if upper {
591 ls[i].upper = true;
592 }
593 consumed = true;
594 break;
595 }
596 if ls[i].variant == 1 {
597 ls[i].variant = 0;
598 ls[i].circ_toggled = true;
599 break;
600 } if ls[i].variant == 2 {
602 ls[i].variant = 1;
608 if upper {
609 ls[i].upper = true;
610 }
611 if ls[i].c == 'o'
612 && i > 0
613 && ls[i - 1].is_vowel
614 && ls[i - 1].c == 'u'
615 && ls[i - 1].variant == 2
616 {
617 ls[i - 1].variant = 0;
618 }
619 consumed = true;
620 break;
621 }
622 }
623 } else {
624 break;
625 }
626 }
627 }
628 }
629 if consumed {
630 continue;
631 }
632 }
633
634 if lc == 'w' {
636 let hook_result = crate::vseq::try_apply_hook(&mut ls);
637 match hook_result {
638 crate::vseq::HookResult::Applied => {
639 reposition_tone_after_hook(&mut ls, modern);
640 continue;
641 }
642 crate::vseq::HookResult::ToggledOff => {
643 reposition_tone_after_hook(&mut ls, modern);
644 }
646 crate::vseq::HookResult::NotApplicable => {
647 if crate::vseq::try_insert_horn(&mut ls, short_w, upper) {
648 continue;
649 }
650 }
651 }
652 }
653
654 if let Some(t) = telex_tone(lc) {
656 if let Some(vi) = tone_vowel_index(&ls, modern) {
657 ls[vi].tone = t;
658 continue; }
660 }
661 if lc == 'z' {
662 if let Some(vi) = tone_vowel_index(&ls, modern) {
663 if ls[vi].tone != 0 {
664 ls[vi].tone = 0;
665 continue;
666 }
667 }
668 }
669
670 ls.push(Letter::new(ch, upper));
671 if ls.len() >= 2 {
674 let last = ls.len() - 1;
675 if ls[last].is_vowel {
676 let first_vowel = (0..last).find(|&j| ls[j].is_vowel);
677 if let Some(fv) = first_vowel {
678 let vcount = last - fv + 1;
679 if vcount >= 2 {
681 for j in (fv..last).rev() {
682 if ls[j].tone != 0 {
683 let move_tone = (ls[j].c == 'i' && j > 0 && ls[j - 1].c == 'g')
684 || (ls[j].c == 'u' && j > 0 && ls[j - 1].c == 'q');
685 if move_tone {
686 ls[last].tone = ls[j].tone;
687 ls[j].tone = 0;
688 }
689 break;
690 }
691 }
692 }
693 if vcount >= 3 {
696 for j in (fv..last).rev() {
697 if ls[j].tone != 0 {
698 let t = ls[j].tone;
699 ls[j].tone = 0;
700 let target = if ls[fv].c == 'u'
701 && fv + 2 <= last
702 && ls[fv + 1].c == 'y'
703 && ls[fv + 2].c == 'e'
704 {
705 fv + 2
706 } else {
707 fv + 1
708 };
709 if target <= last && target != j {
710 ls[target].tone = t;
711 } else {
712 ls[j].tone = t;
713 }
714 break;
715 }
716 }
717 }
718 }
719 } else {
720 if let Some(end) = last_vowel_index(&ls) {
723 if end + 1 < ls.len() {
724 let fv = (0..end).find(|&j| ls[j].is_vowel);
725 if let Some(fv) = fv {
726 if fv < end {
727 for j in (fv..end).rev() {
728 if ls[j].tone != 0 {
729 ls[end].tone = ls[j].tone;
730 ls[j].tone = 0;
731 break;
732 }
733 }
734 }
735 }
736 }
737 }
738 }
739 }
740 }
741 emit(&ls)
742}
743
744pub fn convert_viqr(input: &str) -> String {
751 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
752 for ch in input.chars() {
753 let lc = ch.to_ascii_lowercase();
754 let n = ls.len();
755
756 match ch {
758 '^' | '(' | '+' => {
759 if let Some(vi) = last_vowel_index(&ls) {
760 match (ch, ls[vi].c) {
761 ('^', 'a' | 'e' | 'o') => {
762 ls[vi].variant = 1;
763 continue;
764 }
765 ('(', 'a') => {
766 ls[vi].variant = 2;
767 continue;
768 }
769 ('+', 'o' | 'u') => {
770 ls[vi].variant = 2;
771 continue;
772 }
773 _ => {} }
775 }
776 ls.push(Letter {
778 c: ch,
779 is_vowel: false,
780 variant: 0,
781 tone: 0,
782 is_dstroke: false,
783 upper: false,
784 from_w: false,
785 horn_propagated: false,
786 circ_toggled: false,
787 d_toggled: false,
788 horn_toggled: false,
789 });
790 continue;
791 }
792 _ => {}
793 }
794
795 match ch {
797 '\'' => {
798 if let Some(vi) = last_vowel_index(&ls) {
799 ls[vi].tone = 2;
800 continue;
801 }
802 }
803 '`' => {
804 if let Some(vi) = last_vowel_index(&ls) {
805 ls[vi].tone = 1;
806 continue;
807 }
808 }
809 '?' => {
810 if let Some(vi) = last_vowel_index(&ls) {
811 ls[vi].tone = 3;
812 continue;
813 }
814 }
815 '~' => {
816 if let Some(vi) = last_vowel_index(&ls) {
817 ls[vi].tone = 4;
818 continue;
819 }
820 }
821 '.' => {
822 if let Some(vi) = last_vowel_index(&ls) {
823 ls[vi].tone = 5;
824 continue;
825 }
826 }
827 _ => {}
828 }
829
830 if lc == 'd' && n > 0 && ls[n - 1].c == 'd' && !ls[n - 1].is_vowel && !ls[n - 1].is_dstroke
832 {
833 ls[n - 1].is_dstroke = true;
834 if ch.is_ascii_uppercase() {
835 ls[n - 1].upper = true;
836 }
837 continue;
838 }
839
840 ls.push(Letter::new(ch, ch.is_ascii_uppercase()));
841 }
842 emit(&ls)
843}
844
845pub fn convert_vni(input: &str, modern: bool, _short_w: bool) -> String {
846 let mut ls: Vec<Letter> = Vec::with_capacity(input.len());
847 for ch in input.chars() {
848 let lc = ch.to_ascii_lowercase();
849 let upper = ch.is_ascii_uppercase();
850 if let Some(t) = vni_tone(lc) {
851 if let Some(vi) = tone_vowel_index(&ls, modern) {
852 ls[vi].tone = t;
853 continue;
854 }
855 }
856 match lc {
857 '6' => {
858 if let Some(vi) = last_vowel_index(&ls) {
859 if matches!(ls[vi].c, 'a' | 'e' | 'o') {
860 ls[vi].variant = 1;
861 continue;
862 }
863 }
864 }
865 '7' => {
866 if let Some(vi) = last_vowel_index(&ls) {
867 if matches!(ls[vi].c, 'o' | 'u') {
868 ls[vi].variant = 2;
869 if ls[vi].c == 'o' && vi > 0 && ls[vi - 1].c == 'u' {
870 ls[vi - 1].variant = 2;
871 }
872 continue;
873 }
874 }
875 }
876 '8' => {
877 if let Some(vi) = last_vowel_index(&ls) {
878 if ls[vi].c == 'a' {
879 ls[vi].variant = 2;
880 continue;
881 }
882 }
883 }
884 '9' => {
885 if let Some(di) = last_d_index(&ls) {
886 ls[di].is_dstroke = true;
887 if upper {
888 ls[di].upper = true;
889 }
890 }
891 continue;
892 }
893 '0' => {
894 continue;
895 }
896 _ => {}
897 }
898 if lc.is_ascii_alphabetic() || (lc.is_ascii_digit() && !ls.is_empty()) {
899 ls.push(Letter::new(ch, upper));
900 }
901 }
902 emit(&ls)
903}
904
905#[cfg(test)]
906mod tests {
907 use super::*;
908 fn t(s: &str) -> String {
910 convert_telex(s, true, false)
911 }
912 fn tw(s: &str) -> String {
913 convert_telex(s, true, true)
914 }
915 fn tt(s: &str) -> String {
917 convert_telex(s, false, true)
918 }
919 fn vt(s: &str) -> String {
920 convert_vni(s, false, true)
921 }
922
923 #[test]
924 fn telex_basic() {
925 assert_eq!(t("as"), "á");
926 assert_eq!(t("af"), "à");
927 assert_eq!(t("ar"), "ả");
928 assert_eq!(t("ax"), "ã");
929 assert_eq!(t("aj"), "ạ");
930 assert_eq!(t("aw"), "ă");
931 assert_eq!(t("aa"), "â");
932 assert_eq!(t("ee"), "ê");
933 assert_eq!(t("oo"), "ô");
934 assert_eq!(t("ow"), "ơ");
935 assert_eq!(t("uw"), "ư");
936 assert_eq!(t("dd"), "đ");
937 }
938 #[test]
939 fn telex_compound() {
940 assert_eq!(t("aws"), "ắ");
941 assert_eq!(t("aas"), "ấ");
942 assert_eq!(t("ees"), "ế");
943 assert_eq!(t("oos"), "ố");
944 assert_eq!(t("ows"), "ớ");
945 assert_eq!(t("uws"), "ứ");
946 }
947 #[test]
948 fn telex_tone_not_lost_on_consonant_gap() {
949 assert_eq!(t("for"), "fỏ");
952 assert_eq!(t("force"), "fỏce");
953 assert_eq!(t("vai"), "vai");
954 assert_eq!(t("vaif"), "vài");
955 }
956 #[test]
957 fn telex_words() {
958 assert_eq!(t("xin"), "xin");
959 assert_eq!(t("tooi"), "tôi");
960 assert_eq!(t("laf"), "là");
961 assert_eq!(t("cos"), "có");
962 assert_eq!(t("khoong"), "không");
963 assert_eq!(t("tieengs"), "tiếng");
964 assert_eq!(t("Vieetj"), "Việt");
965 }
966 #[test]
967 fn telex_uow() {
968 assert_eq!(t("uow"), "ươ");
969 assert_eq!(t("cuowcs"), "cước");
970 assert_eq!(t("dduwowcj"), "được");
971 assert_eq!(t("nguwowif"), "người");
972 assert_eq!(t("uwocs"), "ước");
974 assert_eq!(t("truwocs"), "trước");
975 assert_eq!(t("luwowcs"), "lước"); assert_eq!(t("truwowcs"), "trước"); assert_eq!(t("uowus"), "ướu"); assert_eq!(t("ruowus"), "rướu");
980 assert_eq!(t("luoojcw"), "lược"); assert_eq!(t("oow"), "ơ");
983 assert_eq!(t("coow"), "cơ");
984 assert_eq!(t("aaw"), "ă");
985 assert_eq!(t("ooiw"), "ơi"); assert_eq!(t("uooiw"), "ươi");
987 }
988 #[test]
989 fn telex_gi_qu() {
990 assert_eq!(t("gif"), "gì");
991 assert_eq!(t("giowf"), "giờ");
992 assert_eq!(t("gioir"), "giỏi");
993 assert_eq!(t("gias"), "giá");
994 assert_eq!(t("quas"), "quá");
995 }
996 #[test]
997 fn telex_tone_placement() {
998 assert_eq!(t("chafo"), "chào");
999 assert_eq!(t("toans"), "toán");
1000 assert_eq!(t("hoaf"), "hoà");
1001 assert_eq!(t("thowif"), "thời");
1002 assert_eq!(t("cuar"), "của");
1003 assert_eq!(t("thoix"), "thõi");
1004 assert_eq!(t("ofa"), "òa"); assert_eq!(t("oaf"), "oà"); }
1008 #[test]
1009 fn telex_english() {
1010 assert_eq!(t("hello"), "hello");
1011 assert_eq!(t("wood"), "wôd");
1012 assert_eq!(t("reboot"), "rebôt");
1013 }
1014 #[test]
1015 fn telex_z() {
1016 assert_eq!(t("asz"), "a");
1017 }
1018 #[test]
1019 fn telex_w_standalone() {
1020 assert_eq!(t("w"), "w"); assert_eq!(tw("w"), "ư"); assert_eq!(tw("ws"), "ứ");
1023 assert_eq!(tw("wf"), "ừ");
1024 assert_eq!(tw("wn"), "ưn");
1025 }
1026
1027 #[test]
1030 fn telex_dd_traditional() {
1031 assert_eq!(tt("dd"), "đ");
1032 assert_eq!(tt("DD"), "Đ");
1033 assert_eq!(tt("Dd"), "Đ");
1034 assert_eq!(tt("dD"), "Đ");
1035 }
1036
1037 #[test]
1038 fn telex_dd_across_chars() {
1039 assert_eq!(tt("dad"), "đa");
1041 assert_eq!(tt("dda"), "đa");
1042 assert_eq!(tt("dadf"), "đà");
1043 assert_eq!(tt("dads"), "đá");
1044 assert_eq!(tt("dadr"), "đả");
1045 assert_eq!(tt("dadx"), "đã");
1046 assert_eq!(tt("dadj"), "đạ");
1047 assert_eq!(tt("dangd"), "đang"); assert_eq!(tt("dangdf"), "đàng"); assert_eq!(tt("DAD"), "ĐA");
1050 assert_eq!(tt("Dad"), "Đa");
1051 assert_eq!(tt("dadd"), "dad");
1053 assert_eq!(tt("ddd"), "dd"); assert_eq!(tt("dddd"), "ddd"); assert_eq!(tt("ddddd"), "dddd"); assert_eq!(tt("ddda"), "dda"); assert_eq!(tt("ddddf"), "dddf"); }
1059 #[test]
1060 fn telex_marks_traditional() {
1061 assert_eq!(tt("aa"), "â");
1062 assert_eq!(tt("aw"), "ă");
1063 assert_eq!(tt("ee"), "ê");
1064 assert_eq!(tt("ew"), "ew"); assert_eq!(tt("oo"), "ô");
1066 assert_eq!(tt("ow"), "ơ");
1067 assert_eq!(tt("uw"), "ư");
1068 assert_eq!(tt("uow"), "ươ");
1069 }
1070 #[test]
1071 fn telex_w_traditional() {
1072 assert_eq!(tt("w"), "ư");
1073 assert_eq!(tt("W"), "Ư");
1074 assert_eq!(tt("wn"), "ưn");
1075 assert_eq!(tt("ws"), "ứ");
1076 assert_eq!(tt("wf"), "ừ");
1077 }
1078 #[test]
1079 fn telex_compound_traditional() {
1080 assert_eq!(tt("aws"), "ắ");
1081 assert_eq!(tt("aas"), "ấ");
1082 assert_eq!(tt("ees"), "ế");
1083 assert_eq!(tt("oos"), "ố");
1084 assert_eq!(tt("ows"), "ớ");
1085 assert_eq!(tt("uws"), "ứ");
1086 }
1087 #[test]
1088 fn telex_words_traditional() {
1089 assert_eq!(tt("chaof"), "chào");
1090 assert_eq!(tt("vieejt"), "việt");
1091 assert_eq!(tt("tieengs"), "tiếng");
1092 assert_eq!(tt("quas"), "quá");
1093 assert_eq!(tt("gias"), "giá");
1094 assert_eq!(tt("xin"), "xin");
1095 assert_eq!(tt("tooi"), "tôi");
1096 assert_eq!(tt("laf"), "là");
1097 assert_eq!(tt("cos"), "có");
1098 assert_eq!(tt("khoong"), "không");
1099 assert_eq!(tt("Vieetj"), "Việt");
1100 }
1101 #[test]
1102 fn telex_tone_traditional() {
1103 assert_eq!(tt("toans"), "toán");
1105 assert_eq!(tt("hoaf"), "hòa");
1106 assert_eq!(tt("thowif"), "thời");
1107 assert_eq!(tt("cuar"), "của");
1108 }
1109 #[test]
1110 fn telex_gi_qu_traditional() {
1111 assert_eq!(tt("gif"), "gì");
1112 assert_eq!(tt("giowf"), "giờ");
1113 assert_eq!(tt("gioir"), "giỏi");
1114 }
1115
1116 #[test]
1117 fn telex_gi_qu_tone_reassign() {
1118 assert_eq!(tt("gisa"), "giá"); assert_eq!(tt("gifa"), "già"); assert_eq!(tt("quisa"), "quía"); assert_eq!(tt("qufa"), "quà"); assert_eq!(tt("gias"), "giá"); assert_eq!(tt("giois"), "giói"); }
1127
1128 #[test]
1129 fn telex_trailing_consonant_reassign() {
1130 assert_eq!(tt("tofan"), "toàn"); assert_eq!(tt("toafn"), "toàn"); assert_eq!(tt("hoafn"), "hoàn"); assert_eq!(tt("toanf"), "toàn");
1137 assert_eq!(tt("hoanf"), "hoàn");
1138 }
1139 #[test]
1140 fn telex_uow_traditional() {
1141 assert_eq!(tt("cuowcs"), "cước");
1142 assert_eq!(tt("dduwowcj"), "được");
1143 assert_eq!(tt("nguwowif"), "người");
1144 }
1145 #[test]
1146 fn telex_z_traditional() {
1147 assert_eq!(tt("asz"), "a");
1148 }
1149 #[test]
1150 fn telex_english_traditional() {
1151 assert_eq!(tt("hello"), "hello");
1152 }
1153 #[test]
1154 fn telex_more_traditional() {
1155 assert_eq!(tt("hoanf"), "hoàn");
1156 assert_eq!(tt("huyeenf"), "huyền");
1157 assert_eq!(tt("nguyeexn"), "nguyễn");
1158 assert_eq!(tt("ngoaif"), "ngoài");
1159 assert_eq!(tt("thuowngf"), "thường");
1160 }
1161 #[test]
1162 fn vni_marks_traditional() {
1163 assert_eq!(vt("d9"), "đ");
1164 assert_eq!(vt("a6"), "â");
1165 assert_eq!(vt("a8"), "ă");
1166 assert_eq!(vt("e6"), "ê");
1167 assert_eq!(vt("o6"), "ô");
1168 assert_eq!(vt("o7"), "ơ");
1169 assert_eq!(vt("u7"), "ư");
1170 assert_eq!(vt("uo7"), "ươ");
1171 }
1172 #[test]
1173 fn vni_words_traditional() {
1174 assert_eq!(vt("vie65t"), "việt");
1175 assert_eq!(vt("tie61ng"), "tiếng");
1176 assert_eq!(vt("qua1"), "quá");
1177 assert_eq!(vt("gia1"), "giá");
1178 }
1179
1180 #[test]
1182 fn telex_tone_replace() {
1183 assert_eq!(tt("saosf"), "sào"); assert_eq!(tt("saojs"), "sáo"); assert_eq!(tt("saojr"), "sảo"); assert_eq!(tt("hoasf"), "hòa"); assert_eq!(tt("toansr"), "toản"); }
1189 #[test]
1190 fn teipvni_tone_replace() {
1191 assert_eq!(tv("saosf"), "sào"); assert_eq!(tv("sao1f"), "sào"); }
1194
1195 #[test]
1197 fn telex_double_vowel_toggle() {
1198 assert_eq!(tt("aa"), "â");
1199 assert_eq!(tt("aaa"), "aa"); assert_eq!(tt("aaaa"), "aaa"); assert_eq!(tt("ee"), "ê");
1202 assert_eq!(tt("eee"), "ee");
1203 assert_eq!(tt("eeee"), "eee"); assert_eq!(tt("oo"), "ô");
1205 assert_eq!(tt("ooo"), "oo");
1206 assert_eq!(tt("oooo"), "ooo");
1207 assert_eq!(tt("roo"), "rô");
1208 assert_eq!(tt("rooot"), "root"); }
1210
1211 #[test]
1213 fn telex_double_vowel_across_y() {
1214 assert_eq!(tt("vaya"), "vây");
1215 assert_eq!(tt("vayaj"), "vậy");
1216 assert_eq!(tt("vayas"), "vấy");
1217 assert_eq!(tt("vayaf"), "vầy");
1218 assert_eq!(tt("mayas"), "mấy");
1219 assert_eq!(tt("dayaj"), "dậy");
1220 assert_eq!(tt("quaya"), "quây");
1221 assert_eq!(tt("quayar"), "quẩy");
1222 assert_eq!(tt("saua"), "sâu");
1223 assert_eq!(tt("sauas"), "sấu");
1224 assert_eq!(tt("dauaf"), "dầu");
1225 assert_eq!(tt("aay"), "ây");
1226 }
1227
1228 #[test]
1230 fn telex_double_vowel_swap() {
1231 assert_eq!(tt("lawsma"), "lấm"); assert_eq!(t("lawsma"), "lấm"); assert_eq!(tt("awsa"), "ấ"); assert_eq!(tt("luowjco"), "luộc"); assert_eq!(tv("lawsma"), "lấm"); assert_eq!(tt("lawsmaa"), "láma"); }
1240
1241 #[test]
1244 fn telex_roof_crossing() {
1245 assert_eq!(tt("oio"), "ôi");
1247 assert_eq!(t("oio"), "ôi");
1248 assert_eq!(tt("ddoio"), "đôi");
1249 assert_eq!(tt("đoio"), "đôi"); assert_eq!(tt("doio"), "dôi");
1251 assert_eq!(tt("oiof"), "ồi"); assert_eq!(tt("uoio"), "uôi");
1254 assert_eq!(tt("uoiof"), "uồi");
1255 assert_eq!(tt("owio"), "ôi");
1257 assert_eq!(tt("uowio"), "uôi");
1258 assert_eq!(tv("oio"), "ôi"); assert_eq!(tt("aia"), "aia");
1261 assert_eq!(tt("eie"), "eie"); assert_eq!(tt("vaya"), "vây");
1264 assert_eq!(tt("saua"), "sâu");
1265 assert_eq!(tt("eue"), "êu");
1266 assert_eq!(tt("uouo"), "uouo");
1268 assert_eq!(tt("oyo"), "oyo");
1269 }
1270
1271 #[test]
1273 fn telex_w_toggle() {
1274 assert_eq!(tt("w"), "ư");
1277 assert_eq!(tt("ww"), "w"); assert_eq!(tt("www"), "ww"); assert_eq!(tt("wws"), "ws"); }
1281 #[test]
1282 fn telex_w_toggle_regular() {
1283 assert_eq!(tt("aw"), "ă"); assert_eq!(tt("aww"), "aw"); assert_eq!(tt("ow"), "ơ"); assert_eq!(tt("oww"), "ow"); assert_eq!(tt("uw"), "ư"); assert_eq!(tt("uww"), "uw"); assert_eq!(tt("ew"), "ew");
1291 assert_eq!(tt("uow"), "ươ"); assert_eq!(tt("uoww"), "uow"); assert_eq!(tt("owww"), "oww"); assert_eq!(tt("uowww"), "uoww"); assert_eq!(tt("uuw"), "ưu");
1298 assert_eq!(tt("uuww"), "uuw"); assert_eq!(tt("uuws"), "ứu"); }
1301
1302 #[test]
1304 fn telex_w_target() {
1305 assert_eq!(tt("toiw"), "tơi");
1307 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");
1313 assert_eq!(tt("nguoiws"), "ngưới");
1314 assert_eq!(tt("muoiwf"), "mười");
1316 assert_eq!(tt("tuoiws"), "tưới");
1318 assert_eq!(tt("tuooir"), "tuổi");
1320 assert_eq!(tt("tuooif"), "tuồi");
1321 }
1322 #[test]
1323 fn telex_toan() {
1324 assert_eq!(tt("toanf"), "toàn");
1326 assert_eq!(t("toanf"), "toàn");
1327 assert_eq!(tt("hoanf"), "hoàn");
1328 assert_eq!(t("hoanf"), "hoàn");
1329 assert_eq!(t("oaf"), "oà"); assert_eq!(t("ofa"), "òa"); assert_eq!(tt("oaf"), "òa"); assert_eq!(tt("ofa"), "òa"); }
1336
1337 fn tv(s: &str) -> String {
1339 convert_teip_vni(s, false, true)
1340 }
1341
1342 #[test]
1343 fn teipvni_telex_words() {
1344 assert_eq!(tv("vieejt"), "việt");
1345 assert_eq!(tv("dd"), "đ");
1346 assert_eq!(tv("aa"), "â");
1347 assert_eq!(tv("aw"), "ă");
1348 assert_eq!(tv("ee"), "ê");
1349 assert_eq!(tv("oo"), "ô");
1350 assert_eq!(tv("ow"), "ơ");
1351 assert_eq!(tv("uw"), "ư");
1352 assert_eq!(tv("uow"), "ươ");
1353 assert_eq!(tv("w"), "ư");
1354 }
1355 #[test]
1356 fn teipvni_vni_marks() {
1357 assert_eq!(tv("a6"), "â");
1358 assert_eq!(tv("a8"), "ă");
1359 assert_eq!(tv("e6"), "ê");
1360 assert_eq!(tv("o6"), "ô");
1361 assert_eq!(tv("o7"), "ơ");
1362 assert_eq!(tv("u7"), "ư");
1363 assert_eq!(tv("uo7"), "ươ");
1364 assert_eq!(tv("d9"), "đ");
1365 }
1366 #[test]
1367 fn teipvni_numbers() {
1368 assert_eq!(tv("qua1"), "quá");
1370 assert_eq!(tv("gia1"), "giá");
1371 assert_eq!(tv("vie65t"), "việt");
1372 assert_eq!(tv("tie61ng"), "tiếng");
1373 }
1374 #[test]
1375 fn teipvni_mixed() {
1376 assert_eq!(tv("vie6t"), "viêt"); assert_eq!(tv("vie6ts"), "viết"); assert_eq!(tv("tie6ngs"), "tiếng");
1380 }
1381
1382 fn viqr(s: &str) -> String {
1384 convert_viqr(s)
1385 }
1386
1387 #[test]
1388 fn viqr_marks() {
1389 assert_eq!(viqr("a^"), "â");
1390 assert_eq!(viqr("a("), "ă");
1391 assert_eq!(viqr("e^"), "ê");
1392 assert_eq!(viqr("o^"), "ô");
1393 assert_eq!(viqr("o+"), "ơ");
1394 assert_eq!(viqr("u+"), "ư");
1395 assert_eq!(viqr("dd"), "đ");
1396 }
1397 #[test]
1398 fn viqr_tones() {
1399 assert_eq!(viqr("a'"), "á");
1400 assert_eq!(viqr("a`"), "à");
1401 assert_eq!(viqr("a?"), "ả");
1402 assert_eq!(viqr("a~"), "ã");
1403 assert_eq!(viqr("a."), "ạ");
1404 }
1405 #[test]
1406 fn viqr_words() {
1407 assert_eq!(viqr("vie^'t"), "viết");
1408 assert_eq!(viqr("nu+o+'c"), "nước");
1409 assert_eq!(viqr("ngu+o+`i"), "người");
1410 }
1411 #[test]
1412 fn viqr_dd() {
1413 assert_eq!(viqr("dd"), "đ");
1414 assert_eq!(viqr("DD"), "Đ");
1415 }
1416 #[test]
1417 fn viqr_english() {
1418 assert_eq!(viqr("hello"), "hello");
1419 }
1420}
1421
1422#[test]
1423fn full_parity() {
1424 struct Case {
1428 input: &'static str,
1429 expected: &'static str,
1430 method: u8,
1431 }
1432 let cases = [
1434 Case {
1436 input: "dd",
1437 expected: "đ",
1438 method: 1,
1439 },
1440 Case {
1441 input: "DD",
1442 expected: "Đ",
1443 method: 1,
1444 },
1445 Case {
1446 input: "Dd",
1447 expected: "Đ",
1448 method: 1,
1449 },
1450 Case {
1451 input: "dD",
1452 expected: "Đ",
1453 method: 1,
1454 },
1455 Case {
1456 input: "aa",
1457 expected: "â",
1458 method: 1,
1459 },
1460 Case {
1461 input: "aw",
1462 expected: "ă",
1463 method: 1,
1464 },
1465 Case {
1466 input: "ee",
1467 expected: "ê",
1468 method: 1,
1469 },
1470 Case {
1471 input: "ew",
1472 expected: "ew", method: 1,
1474 },
1475 Case {
1476 input: "oo",
1477 expected: "ô",
1478 method: 1,
1479 },
1480 Case {
1481 input: "ow",
1482 expected: "ơ",
1483 method: 1,
1484 },
1485 Case {
1486 input: "uw",
1487 expected: "ư",
1488 method: 1,
1489 },
1490 Case {
1491 input: "uow",
1492 expected: "ươ",
1493 method: 1,
1494 },
1495 Case {
1496 input: "w",
1497 expected: "ư",
1498 method: 1,
1499 },
1500 Case {
1501 input: "W",
1502 expected: "Ư",
1503 method: 1,
1504 },
1505 Case {
1506 input: "wn",
1507 expected: "ưn",
1508 method: 1,
1509 },
1510 Case {
1511 input: "ws",
1512 expected: "ứ",
1513 method: 1,
1514 },
1515 Case {
1516 input: "wf",
1517 expected: "ừ",
1518 method: 1,
1519 },
1520 Case {
1521 input: "aws",
1522 expected: "ắ",
1523 method: 1,
1524 },
1525 Case {
1526 input: "aas",
1527 expected: "ấ",
1528 method: 1,
1529 },
1530 Case {
1531 input: "ees",
1532 expected: "ế",
1533 method: 1,
1534 },
1535 Case {
1536 input: "oos",
1537 expected: "ố",
1538 method: 1,
1539 },
1540 Case {
1541 input: "ows",
1542 expected: "ớ",
1543 method: 1,
1544 },
1545 Case {
1546 input: "uws",
1547 expected: "ứ",
1548 method: 1,
1549 },
1550 Case {
1551 input: "chaof",
1552 expected: "chào",
1553 method: 1,
1554 },
1555 Case {
1556 input: "vieejt",
1557 expected: "việt",
1558 method: 1,
1559 },
1560 Case {
1561 input: "tieengs",
1562 expected: "tiếng",
1563 method: 1,
1564 },
1565 Case {
1566 input: "quas",
1567 expected: "quá",
1568 method: 1,
1569 },
1570 Case {
1571 input: "gias",
1572 expected: "giá",
1573 method: 1,
1574 },
1575 Case {
1576 input: "xin",
1577 expected: "xin",
1578 method: 1,
1579 },
1580 Case {
1581 input: "tooi",
1582 expected: "tôi",
1583 method: 1,
1584 },
1585 Case {
1586 input: "laf",
1587 expected: "là",
1588 method: 1,
1589 },
1590 Case {
1591 input: "cos",
1592 expected: "có",
1593 method: 1,
1594 },
1595 Case {
1596 input: "khoong",
1597 expected: "không",
1598 method: 1,
1599 },
1600 Case {
1601 input: "Vieetj",
1602 expected: "Việt",
1603 method: 1,
1604 },
1605 Case {
1606 input: "toans",
1607 expected: "toán",
1608 method: 1,
1609 },
1610 Case {
1611 input: "hoaf",
1612 expected: "hòa",
1613 method: 1,
1614 },
1615 Case {
1616 input: "thowif",
1617 expected: "thời",
1618 method: 1,
1619 },
1620 Case {
1621 input: "cuar",
1622 expected: "của",
1623 method: 1,
1624 },
1625 Case {
1626 input: "gif",
1627 expected: "gì",
1628 method: 1,
1629 },
1630 Case {
1631 input: "giowf",
1632 expected: "giờ",
1633 method: 1,
1634 },
1635 Case {
1636 input: "gioir",
1637 expected: "giỏi",
1638 method: 1,
1639 },
1640 Case {
1641 input: "cuowcs",
1642 expected: "cước",
1643 method: 1,
1644 },
1645 Case {
1646 input: "dduwowcj",
1647 expected: "được",
1648 method: 1,
1649 },
1650 Case {
1651 input: "nguwowif",
1652 expected: "người",
1653 method: 1,
1654 },
1655 Case {
1656 input: "asz",
1657 expected: "a",
1658 method: 1,
1659 },
1660 Case {
1661 input: "hello",
1662 expected: "hello",
1663 method: 1,
1664 },
1665 Case {
1666 input: "hoanf",
1667 expected: "hoàn",
1668 method: 1,
1669 },
1670 Case {
1671 input: "huyeenf",
1672 expected: "huyền",
1673 method: 1,
1674 },
1675 Case {
1676 input: "nguyeexn",
1677 expected: "nguyễn",
1678 method: 1,
1679 },
1680 Case {
1681 input: "ngoaif",
1682 expected: "ngoài",
1683 method: 1,
1684 },
1685 Case {
1686 input: "thuowngf",
1687 expected: "thường",
1688 method: 1,
1689 },
1690 Case {
1692 input: "d9",
1693 expected: "đ",
1694 method: 2,
1695 },
1696 Case {
1697 input: "a6",
1698 expected: "â",
1699 method: 2,
1700 },
1701 Case {
1702 input: "a8",
1703 expected: "ă",
1704 method: 2,
1705 },
1706 Case {
1707 input: "e6",
1708 expected: "ê",
1709 method: 2,
1710 },
1711 Case {
1712 input: "o6",
1713 expected: "ô",
1714 method: 2,
1715 },
1716 Case {
1717 input: "o7",
1718 expected: "ơ",
1719 method: 2,
1720 },
1721 Case {
1722 input: "u7",
1723 expected: "ư",
1724 method: 2,
1725 },
1726 Case {
1727 input: "uo7",
1728 expected: "ươ",
1729 method: 2,
1730 },
1731 Case {
1732 input: "vie65t",
1733 expected: "việt",
1734 method: 2,
1735 },
1736 Case {
1737 input: "tie61ng",
1738 expected: "tiếng",
1739 method: 2,
1740 },
1741 Case {
1742 input: "qua1",
1743 expected: "quá",
1744 method: 2,
1745 },
1746 Case {
1747 input: "gia1",
1748 expected: "giá",
1749 method: 2,
1750 },
1751 Case {
1753 input: "vie61t",
1754 expected: "viết",
1755 method: 4,
1756 },
1757 Case {
1758 input: "vieejt",
1759 expected: "việt",
1760 method: 4,
1761 },
1762 Case {
1763 input: "tie61ng",
1764 expected: "tiếng",
1765 method: 4,
1766 },
1767 Case {
1768 input: "a6",
1769 expected: "â",
1770 method: 4,
1771 },
1772 Case {
1773 input: "dd",
1774 expected: "đ",
1775 method: 4,
1776 },
1777 Case {
1779 input: "dd",
1780 expected: "đ",
1781 method: 3,
1782 },
1783 Case {
1784 input: "DD",
1785 expected: "Đ",
1786 method: 3,
1787 },
1788 Case {
1789 input: "vie^'t",
1790 expected: "viết",
1791 method: 3,
1792 },
1793 Case {
1794 input: "nu+o+'c",
1795 expected: "nước",
1796 method: 3,
1797 },
1798 Case {
1799 input: "ngu+o+`i",
1800 expected: "người",
1801 method: 3,
1802 },
1803 ];
1804
1805 let mut failures = Vec::new();
1806 for c in &cases {
1807 let result = match c.method {
1808 1 => convert_telex(c.input, false, true),
1809 2 => convert_vni(c.input, false, true),
1810 3 => convert_viqr(c.input),
1811 4 => convert_teip_vni(c.input, false, true),
1812 _ => unreachable!(),
1813 };
1814 if result != c.expected {
1815 failures.push(format!(
1816 "[m{}] '{}' -> '{}' (expected '{}')",
1817 c.method, c.input, result, c.expected
1818 ));
1819 }
1820 }
1821 assert!(
1822 failures.is_empty(),
1823 "{} full parity failures:\n{}",
1824 failures.len(),
1825 failures.join("\n")
1826 );
1827}
1828
1829#[test]
1830fn comprehensive_vietnamese() {
1831 let cases: &[(&str, &str, u8)] = &[
1837 ("as", "á", 1),
1839 ("af", "à", 1),
1840 ("ar", "ả", 1),
1841 ("ax", "ã", 1),
1842 ("aj", "ạ", 1),
1843 ("es", "é", 1),
1844 ("ef", "è", 1),
1845 ("er", "ẻ", 1),
1846 ("ex", "ẽ", 1),
1847 ("ej", "ẹ", 1),
1848 ("is", "í", 1),
1849 ("if", "ì", 1),
1850 ("ir", "ỉ", 1),
1851 ("ix", "ĩ", 1),
1852 ("ij", "ị", 1),
1853 ("os", "ó", 1),
1854 ("of", "ò", 1),
1855 ("or", "ỏ", 1),
1856 ("ox", "õ", 1),
1857 ("oj", "ọ", 1),
1858 ("us", "ú", 1),
1859 ("uf", "ù", 1),
1860 ("ur", "ủ", 1),
1861 ("ux", "ũ", 1),
1862 ("uj", "ụ", 1),
1863 ("ys", "ý", 1),
1864 ("yf", "ỳ", 1),
1865 ("yr", "ỷ", 1),
1866 ("yx", "ỹ", 1),
1867 ("yj", "ỵ", 1),
1868 ("aw", "ă", 1),
1871 ("aws", "ắ", 1),
1872 ("awf", "ằ", 1),
1873 ("awr", "ẳ", 1),
1874 ("awx", "ẵ", 1),
1875 ("awj", "ặ", 1),
1876 ("aa", "â", 1),
1878 ("aas", "ấ", 1),
1879 ("aaf", "ầ", 1),
1880 ("aar", "ẩ", 1),
1881 ("aax", "ẫ", 1),
1882 ("aaj", "ậ", 1),
1883 ("ee", "ê", 1),
1885 ("ees", "ế", 1),
1886 ("eef", "ề", 1),
1887 ("eer", "ể", 1),
1888 ("eex", "ễ", 1),
1889 ("eej", "ệ", 1),
1890 ("ew", "ew", 1),
1893 ("ews", "éw", 1),
1894 ("ewf", "èw", 1),
1895 ("ewr", "ẻw", 1),
1896 ("oo", "ô", 1),
1898 ("oos", "ố", 1),
1899 ("oof", "ồ", 1),
1900 ("oor", "ổ", 1),
1901 ("oox", "ỗ", 1),
1902 ("ooj", "ộ", 1),
1903 ("ow", "ơ", 1),
1905 ("ows", "ớ", 1),
1906 ("owf", "ờ", 1),
1907 ("owr", "ở", 1),
1908 ("owx", "ỡ", 1),
1909 ("owj", "ợ", 1),
1910 ("uw", "ư", 1),
1912 ("uws", "ứ", 1),
1913 ("uwf", "ừ", 1),
1914 ("uwr", "ử", 1),
1915 ("uwx", "ữ", 1),
1916 ("uwj", "ự", 1),
1917 ("dd", "đ", 1),
1919 ("w", "ư", 1),
1921 ("ws", "ứ", 1),
1922 ("wf", "ừ", 1),
1923 ("wr", "ử", 1),
1924 ("wx", "ữ", 1),
1925 ("wj", "ự", 1),
1926 ("wn", "ưn", 1),
1927 ("wt", "ưt", 1),
1928 ("ais", "ái", 1),
1931 ("aif", "ài", 1),
1932 ("air", "ải", 1),
1933 ("aix", "ãi", 1),
1934 ("aij", "ại", 1),
1935 ("aos", "áo", 1),
1936 ("aof", "ào", 1),
1937 ("aor", "ảo", 1),
1938 ("aox", "ão", 1),
1939 ("aoj", "ạo", 1),
1940 ("aus", "áu", 1),
1941 ("auf", "àu", 1),
1942 ("aur", "ảu", 1),
1943 ("aux", "ãu", 1),
1944 ("auj", "ạu", 1),
1945 ("aays", "ấy", 1),
1947 ("aayf", "ầy", 1),
1948 ("aayr", "ẩy", 1),
1949 ("aayx", "ẫy", 1),
1950 ("aayj", "ậy", 1),
1951 ("ays", "áy", 1),
1952 ("ayf", "ày", 1),
1953 ("ayr", "ảy", 1),
1954 ("ayx", "ãy", 1),
1955 ("ayj", "ạy", 1),
1956 ("eos", "éo", 1),
1958 ("eof", "èo", 1),
1959 ("eor", "ẻo", 1),
1960 ("eox", "ẽo", 1),
1961 ("eoj", "ẹo", 1),
1962 ("eeus", "ếu", 1),
1963 ("eeuf", "ều", 1),
1964 ("eeur", "ểu", 1),
1965 ("eeux", "ễu", 1),
1966 ("eeuj", "ệu", 1),
1967 ("ois", "ói", 1),
1969 ("oif", "òi", 1),
1970 ("oir", "ỏi", 1),
1971 ("oix", "õi", 1),
1972 ("oij", "ọi", 1),
1973 ("oois", "ối", 1),
1974 ("ooif", "ồi", 1),
1975 ("ooir", "ổi", 1),
1976 ("ooix", "ỗi", 1),
1977 ("ooij", "ội", 1),
1978 ("owis", "ới", 1),
1979 ("owif", "ời", 1),
1980 ("owir", "ởi", 1),
1981 ("owix", "ỡi", 1),
1982 ("owij", "ợi", 1),
1983 ("uis", "úi", 1),
1984 ("uif", "ùi", 1),
1985 ("uir", "ủi", 1),
1986 ("uix", "ũi", 1),
1987 ("uij", "ụi", 1),
1988 ("uwis", "ứi", 1),
1989 ("uwif", "ừi", 1),
1990 ("uwir", "ửi", 1),
1991 ("uwix", "ữi", 1),
1992 ("uwij", "ựi", 1),
1993 ("uwus", "ứu", 1),
1994 ("uwuf", "ừu", 1),
1995 ("uwur", "ửu", 1),
1996 ("uwux", "ữu", 1),
1997 ("uwuj", "ựu", 1),
1998 ("oans", "oán", 1),
2000 ("oanf", "oàn", 1),
2001 ("oanr", "oản", 1),
2002 ("oanx", "oãn", 1),
2003 ("oanj", "oạn", 1),
2004 ("oats", "oát", 1),
2005 ("oatf", "oàt", 1),
2006 ("oatr", "oảt", 1),
2007 ("oens", "oén", 1),
2008 ("oenf", "oèn", 1),
2009 ("uyns", "uýn", 1),
2010 ("uynf", "uỳn", 1),
2011 ("uynr", "uỷn", 1),
2012 ("oas", "óa", 1),
2014 ("oaf", "òa", 1),
2015 ("oar", "ỏa", 1),
2016 ("oax", "õa", 1),
2017 ("oaj", "ọa", 1),
2018 ("oes", "óe", 1),
2019 ("oef", "òe", 1),
2020 ("uys", "úy", 1),
2021 ("uyf", "ùy", 1),
2022 ("uyr", "ủy", 1),
2023 ("uyx", "ũy", 1),
2024 ("uyj", "ụy", 1),
2025 ("oais", "oái", 1),
2028 ("oaif", "oài", 1),
2029 ("oair", "oải", 1),
2030 ("oaix", "oãi", 1),
2031 ("oaij", "oại", 1),
2032 ("oays", "oáy", 1),
2033 ("oayf", "oày", 1),
2034 ("oayr", "oảy", 1),
2035 ("oayx", "oãy", 1),
2036 ("oayj", "oạy", 1),
2037 ("uaays", "uấy", 1),
2039 ("uaayf", "uầy", 1),
2040 ("uaayr", "uẩy", 1),
2041 ("uaayx", "uẫy", 1),
2042 ("uaayj", "uậy", 1),
2043 ("uyeef", "uyề", 1),
2045 ("uyees", "uyế", 1),
2046 ("uyeer", "uyể", 1),
2047 ("uyeex", "uyễ", 1),
2048 ("uyeej", "uyệ", 1),
2049 ("ieef", "iề", 1),
2051 ("iees", "iế", 1),
2052 ("ieer", "iể", 1),
2053 ("ieex", "iễ", 1),
2054 ("ieej", "iệ", 1),
2055 ("yeef", "yề", 1),
2056 ("yees", "yế", 1),
2057 ("yeer", "yể", 1),
2058 ("yeex", "yễ", 1),
2059 ("yeej", "yệ", 1),
2060 ("uoois", "uối", 1),
2062 ("uooif", "uồi", 1),
2063 ("uooir", "uổi", 1),
2064 ("uooix", "uỗi", 1),
2065 ("uooij", "uội", 1),
2066 ("uoiws", "ưới", 1),
2068 ("uoiwf", "ười", 1),
2069 ("uoiwr", "ưởi", 1),
2070 ("uoiwx", "ưỡi", 1),
2071 ("uoiwj", "ượi", 1),
2072 ("gif", "gì", 1),
2074 ("gis", "gí", 1),
2075 ("gir", "gỉ", 1),
2076 ("gix", "gĩ", 1),
2077 ("gij", "gị", 1),
2078 ("gias", "giá", 1),
2079 ("giaf", "già", 1),
2080 ("giar", "giả", 1),
2081 ("giax", "giã", 1),
2082 ("giaj", "giạ", 1),
2083 ("gioir", "giỏi", 1),
2084 ("gioif", "giòi", 1),
2085 ("giois", "giói", 1),
2086 ("quas", "quá", 1),
2087 ("quaf", "quà", 1),
2088 ("quar", "quả", 1),
2089 ("quax", "quã", 1),
2090 ("quaj", "quạ", 1),
2091 ("quys", "quý", 1),
2092 ("quyf", "quỳ", 1),
2093 ("quyr", "quỷ", 1),
2094 ("quyx", "quỹ", 1),
2095 ("quyj", "quỵ", 1),
2096 ("chaof", "chào", 1),
2098 ("vieejt", "việt", 1),
2099 ("tieengs", "tiếng", 1),
2100 ("khoong", "không", 1),
2101 ("cuar", "của", 1),
2102 ("cuowcs", "cước", 1),
2103 ("dduwowcj", "được", 1),
2104 ("nguwowif", "người", 1),
2105 ("thuowngf", "thường", 1),
2106 ("hoanf", "hoàn", 1),
2107 ("huyeenf", "huyền", 1),
2108 ("nguyeexn", "nguyễn", 1),
2109 ("ngoaif", "ngoài", 1),
2110 ("ngoafi", "ngoài", 1),
2111 ("vaya", "vây", 1),
2112 ("vayaj", "vậy", 1),
2113 ("vayas", "vấy", 1),
2114 ("mayas", "mấy", 1),
2115 ("nhana", "nhân", 1),
2117 ("khanas", "khấn", 1),
2118 ("nhanaa", "nhana", 1),
2119 ("toans", "toán", 1),
2120 ("thowif", "thời", 1),
2121 ("xin", "xin", 1),
2122 ("tooi", "tôi", 1),
2123 ("laf", "là", 1),
2124 ("cos", "có", 1),
2125 ("banj", "bạn", 1),
2126 ("minhf", "mình", 1),
2127 ("nawm", "năm", 1),
2128 ("thangs", "tháng", 1),
2129 ("truwowngf", "trường", 1),
2130 ("luaatj", "luật", 1),
2131 ("dad", "đa", 1),
2133 ("DAD", "ĐA", 1),
2134 ("Dad", "Đa", 1),
2135 ("dda", "đa", 1),
2136 ("dadf", "đà", 1),
2137 ("dads", "đá", 1),
2138 ("dadr", "đả", 1),
2139 ("dadx", "đã", 1),
2140 ("dadj", "đạ", 1),
2141 ("dadd", "dad", 1),
2143 ("ddd", "dd", 1),
2145 ("dddd", "ddd", 1),
2146 ("nhas", "nhá", 1),
2148 ("nghir", "nghỉ", 1),
2149 ("gheps", "ghép", 1),
2150 ("truws", "trứ", 1),
2151 ("thas", "thá", 1),
2152 ("phos", "phó", 1),
2153 ("khas", "khá", 1),
2154 ("chays", "cháy", 1),
2155 ("ngays", "ngáy", 1),
2156 ("ddejp", "đẹp", 1),
2158 ("maats", "mất", 1),
2159 ("bacs", "bác", 1),
2160 ("ngachs", "ngách", 1),
2161 ("banhf", "bành", 1),
2162 ("tins", "tín", 1),
2163 ("metj", "mẹt", 1),
2164 ("asz", "a", 1),
2166 ("awsjz", "ă", 1),
2167 ("aasz", "â", 1),
2168 ("hello", "hello", 1),
2170 ("bacon", "bacon", 1),
2171 ("thing", "thing", 1),
2172 ("climb", "climb", 1),
2173 ("As", "Á", 1),
2175 ("Af", "À", 1),
2176 ("Aas", "Ấ", 1),
2177 ("Aws", "Ắ", 1),
2178 ("Dd", "Đ", 1),
2179 ("DD", "Đ", 1),
2180 ("W", "Ư", 1),
2181 ("Ws", "Ứ", 1),
2182 ("Vieetj", "Việt", 1),
2183 ("Tooi", "Tôi", 1),
2184 ("d9", "đ", 2),
2186 ("a6", "â", 2),
2187 ("a8", "ă", 2),
2188 ("e6", "ê", 2),
2189 ("o6", "ô", 2),
2190 ("o7", "ơ", 2),
2191 ("u7", "ư", 2),
2192 ("uo7", "ươ", 2),
2193 ("vie65t", "việt", 2),
2194 ("tie61ng", "tiếng", 2),
2195 ("qua1", "quá", 2),
2196 ("gia1", "giá", 2),
2197 ("vie61t", "viết", 4),
2199 ("vieejt", "việt", 4),
2200 ("tie61ng", "tiếng", 4),
2201 ("a6", "â", 4),
2202 ("dd", "đ", 4),
2203 ("dd", "đ", 3),
2205 ("DD", "Đ", 3),
2206 ("vie^'t", "viết", 3),
2207 ("nu+o+'c", "nước", 3),
2208 ("ngu+o+`i", "người", 3),
2209 ];
2210
2211 let mut failures = Vec::new();
2212 for &(input, expected, method) in cases {
2213 let result = match method {
2214 1 => convert_telex(input, false, true),
2215 2 => convert_vni(input, false, true),
2216 3 => convert_viqr(input),
2217 4 => convert_teip_vni(input, false, true),
2218 _ => unreachable!(),
2219 };
2220 if result != expected {
2221 failures.push(format!(
2222 "[m{}] '{}' -> '{}' (expected '{}')",
2223 method, input, result, expected
2224 ));
2225 }
2226 }
2227 assert!(
2228 failures.is_empty(),
2229 "{} comprehensive failures (traditional mode):\n{}",
2230 failures.len(),
2231 failures.join("\n")
2232 );
2233}