1#![feature(test)]
3
4extern crate test;
7
8use char_util::can_form_hiatus;
9use std::fmt;
10use std::fmt::Display;
11use std::usize;
12use str_util::is_both_b_or_v;
13use str_util::is_both_s_or_z;
14use str_util::loose_match;
15
16pub mod char_util;
17pub mod str_util;
18pub mod syllable;
19
20use crate::char_util::can_form_triphthong;
21use crate::char_util::combo_type;
22use crate::char_util::ComboType;
23use crate::char_util::IsVowel;
24use crate::str_util::is_consonant_group;
25use crate::syllable::Syllable;
26
27type Result<T> = std::result::Result<T, InvalidWord>;
28
29#[derive(Debug, Clone)]
30struct InvalidWord;
31
32impl fmt::Display for InvalidWord {
33 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34 write!(f, "invalid word")
35 }
36}
37
38#[derive(PartialEq, Debug)]
40pub enum StressType {
41 Oxytone,
43 Paroxytone,
45 Proparoxytone,
47 Superproparoxytone,
49}
50
51#[derive(PartialEq)]
58enum Position {
59 None,
60 Onset, Nucleus, Coda, }
64
65#[derive(PartialEq, Debug)]
66pub enum HiatusType {
67 Simple,
68 Accentual,
69}
70
71#[derive(PartialEq, Debug)]
72pub enum DiphthongType {
73 Rising, Falling, Homogenous, }
77
78pub struct Hiatus {
79 pub syllable_index: usize,
80 pub composite: String,
81 pub kind: HiatusType,
82}
83
84pub struct Diphthong {
85 pub syllable_index: usize,
86 pub composite: String,
87 pub kind: DiphthongType,
88}
89
90pub struct Triphthong {
91 pub syllable_index: usize,
92 pub composite: String,
93}
94
95pub struct VowelCombos {
96 pub hiatuses: Vec<Hiatus>,
97 pub diphthongs: Vec<Diphthong>,
98 pub triphthongs: Vec<Triphthong>,
99}
100
101#[derive(Clone, Copy)]
102pub struct RhymeOptions {
103 pub yeismo: bool,
104 pub seseo: bool,
105 pub b_equals_v: bool,
106}
107
108pub fn equal_onset(a: &Syllable, b: &Syllable, opt: &RhymeOptions) -> bool {
109 if a.onset == b.onset {
110 return true;
111 }
112 if opt.seseo {
113 if a.onset == "c" && b.onset == "s" {
114 let a_first_nucleus = a.nucleus.chars().next().unwrap();
115 if a_first_nucleus.is_soft_c_trigger()
116 && loose_match(a.nucleus.as_str(), b.nucleus.as_str())
117 {
118 return true;
119 }
120 } else if a.onset == "s" && b.onset == "c" {
121 let b_first_nucleus = b.nucleus.chars().next().unwrap();
122 if b_first_nucleus.is_soft_c_trigger()
123 && loose_match(a.nucleus.as_str(), b.nucleus.as_str())
124 {
125 return true;
126 }
127 } else if is_both_s_or_z(a.onset.as_str(), b.onset.as_str()) {
128 return true;
129 }
130 }
131 if opt.yeismo {
132 if a.onset == "y" && b.onset == "ll" || a.onset == "ll" && b.onset == "y" {
133 return true;
134 }
135 }
136 if opt.b_equals_v {
137 if is_both_b_or_v(a.onset.as_str(), b.onset.as_str()) {
138 return true;
139 }
140 }
141
142 false
143}
144
145#[derive(Clone, Debug)]
147pub struct Word {
148 pub syllables: Vec<Syllable>,
149 pub stress_index: usize,
150}
151
152impl Word {
153 pub fn rhyme(&self) -> String {
154 if self.syllables.len() == 0 {
155 return String::new();
156 }
157 let stress_syllable = &self.syllables[self.stress_index];
158 let mut rhyme = stress_syllable.vowels_since_stress();
159 rhyme.push_str(stress_syllable.coda.as_str());
160
161 for i in self.stress_index + 1..self.syllables.len() {
162 rhyme.push_str(self.syllables[i].to_string().as_str())
163 }
164 rhyme
165 }
166
167 pub fn assonant_rhymes_with(&self, other: &Word) -> bool {
168 let this_syllables = &self.syllables[self.stress_index..self.syllables.len()];
169 let that_syllables = &other.syllables[other.stress_index..other.syllables.len()];
170 if this_syllables.len() != that_syllables.len() {
171 return false;
172 }
173 for (i, j) in this_syllables.iter().enumerate() {
174 if i == 0 {
175 let k1 = j.vowels_since_stress();
176 let k2 = that_syllables[i].vowels_since_stress();
177 if k1 != k2 {
178 if k1.chars().count() == k2.chars().count() {
179 if k1.chars().count() == 1 {
180 let m1 = k1.chars().collect::<String>();
181 let m2 = k2.chars().collect::<String>();
182 if !loose_match(m1.as_str(), m2.as_str()) {
183 return false;
184 }
185 } else if k1.chars().collect::<String>() != k2.chars().collect::<String>() {
186 return false;
187 }
188 } else {
189 return false;
190 }
191 }
192 } else if j.nucleus != that_syllables[i].nucleus {
193 return false;
194 }
195 }
196 true
197 }
198
199 pub fn rhymes_with(&self, other: &Word, opt: Option<RhymeOptions>) -> bool {
200 let opt = opt.unwrap_or(RhymeOptions {
201 seseo: false,
202 yeismo: true,
203 b_equals_v: true,
204 });
205 let this_syllables = &self.syllables[self.stress_index..self.syllables.len()];
206 let that_syllables = &other.syllables[other.stress_index..other.syllables.len()];
207 if this_syllables.len() != that_syllables.len() {
208 return false;
209 }
210 for (i, j) in this_syllables.iter().enumerate() {
211 if i == 0 {
212 let k1 = j.vowels_since_stress();
213 let k2 = that_syllables[i].vowels_since_stress();
214 if this_syllables.len() == 1 {
215 if !loose_match(&k1, &k2) {
216 return false;
217 }
218 if !opt.seseo && j.coda != that_syllables[i].coda {
219 return false;
220 }
221 if opt.seseo && is_both_s_or_z(j.coda.as_str(), that_syllables[i].coda.as_str())
222 {
223 return true;
224 }
225 return true;
226 } else if k1 != k2 || j.coda != that_syllables[i].coda {
227 return false;
228 }
229 } else if !equal_onset(j, &that_syllables[i], &opt) || j.nucleus != that_syllables[i].nucleus
231 || !(j.coda == that_syllables[i].coda || (opt.seseo && is_both_s_or_z(j.coda.as_str(), that_syllables[i].coda.as_str())))
232 {
233 return false;
234 }
235 }
236 true
237 }
238
239 pub fn vowel_combos(&self) -> VowelCombos {
240 let syllables = &self.syllables;
241 let mut index = 0;
242 let mut hiatuses = vec![];
243 let mut diphthongs = vec![];
244 let mut triphthongs = vec![];
245 while index < syllables.len() {
246 let nucleus_chars: Vec<char> = syllables[index].nucleus.chars().collect();
247 if syllables[index].coda.is_empty()
248 && nucleus_chars.len() == 1
249 && index + 1 < syllables.len()
250 && (syllables[index + 1].onset.is_empty() || syllables[index + 1].onset == "h")
251 && syllables[index + 1].nucleus.chars().count() == 1
252 {
253 let mut composite = syllables[index].nucleus.clone();
254 composite.push_str(syllables[index + 1].nucleus.as_str());
255 hiatuses.push(Hiatus {
256 syllable_index: index,
257 composite,
258 kind: if syllables[index].has_accented_vowel()
259 || syllables[index + 1].has_accented_vowel()
260 {
261 HiatusType::Accentual
262 } else {
263 HiatusType::Simple
264 },
265 });
266 } else if nucleus_chars.len() == 2 {
267 let dp_type: DiphthongType = match combo_type(nucleus_chars[0], nucleus_chars[1]) {
268 ComboType::Diphthong(t) => t,
269 _ => panic!("Not a diphthong"),
270 };
271 diphthongs.push(Diphthong {
272 syllable_index: index,
273 kind: dp_type,
274 composite: syllables[index].nucleus.clone(),
275 });
276 } else if syllables[index].nucleus.chars().count() == 3 {
277 triphthongs.push(Triphthong {
278 syllable_index: index,
279 composite: syllables[index].nucleus.clone(),
280 });
281 } else if syllables[index].coda.is_empty()
282 && syllables[index].nucleus.chars().count() == 2
283 && index + 1 < syllables.len()
284 && (syllables[index + 1].onset.is_empty() || syllables[index + 1].onset == "h")
285 && syllables[index + 1].nucleus.chars().count() == 1
286 {
287 }
289 index += 1;
290 }
291 VowelCombos {
292 hiatuses,
293 diphthongs,
294 triphthongs,
295 }
296 }
297
298 pub fn stress(&self) -> StressType {
299 let d = self.syllables.len() - 1 - self.stress_index;
300 match d {
301 0 => StressType::Oxytone,
302 1 => StressType::Paroxytone,
303 2 => StressType::Proparoxytone,
304 3.. => StressType::Superproparoxytone,
305 _ => panic!("Invalid stress count {}", d),
306 }
307 }
308
309 pub fn syllabize(&self, delimiter: &str) -> String {
310 return self
311 .syllables
312 .iter()
313 .map(|s| s.to_string())
314 .collect::<Vec<String>>()
315 .join(delimiter);
316 }
317}
318
319impl From<&str> for Word {
320 fn from(item: &str) -> Self {
321 let syllables = to_syllables(item);
322 match syllables {
323 Ok(s) => {
324 let stress_index = identify_stress(&s);
325 Word {
326 syllables: s,
327 stress_index,
328 }
329 }
330 Err(_e) => Word {
331 syllables: vec![],
332 stress_index: 0,
333 }
334 }
335 }
336}
337
338impl Display for Word {
339 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
340 let res = self
341 .syllables
342 .iter()
343 .map(|s| s.to_string())
344 .collect::<Vec<String>>()
345 .join("");
346 write!(f, "{}", res)
347 }
348}
349
350
351
352fn to_syllables(word: &str) -> Result<Vec<Syllable>> {
353 if word.is_empty() {
354 return Ok(vec![]);
355 }
356
357 let chars: Vec<char> = word.chars().collect();
358 let word_len = chars.len();
359
360 if word_len == 1 {
361 return Ok(vec![Syllable {
362 onset: "".to_string(),
363 nucleus: chars[0].to_string(),
364 coda: "".to_string(),
365 }]);
366 }
367
368 let mut syllables: Vec<Syllable> = Vec::with_capacity(32);
371
372 let mut index = 0;
373 let mut position = Position::None;
374 let mut syllable = Syllable {
375 onset: "".to_string(),
376 nucleus: "".to_string(),
377 coda: "".to_string(),
378 };
379
380 loop {
381 let curr_char = chars[index];
382 if !curr_char.is_vowel() {
383 if position == Position::None || position == Position::Onset {
384 if curr_char == 'y' {
385 if syllable.onset.is_empty() {
386 syllable.onset.push(curr_char);
387 position = Position::Onset;
388 } else {
389 syllable.nucleus.push(curr_char);
390 position = Position::Nucleus
391 }
392 } else if curr_char == 'q' || curr_char == 'g' {
393 syllable.onset.push(curr_char);
394 position = Position::Onset;
395 index += 1;
396 if word_len <= index {
397 return Err(InvalidWord)
398 }
399 let next_char = chars[index];
400 if next_char == 'u' {
401 index += 1;
402 if word_len <= index {
403 return Err(InvalidWord)
404 }
405 let after_next_char = chars[index];
406 if after_next_char == 'i' || after_next_char == 'e' {
407 syllable.onset.push(next_char);
408 syllable.nucleus.push(after_next_char);
409 position = Position::Nucleus;
410 }
411 } else {
412 index -= 1;
413 }
414 } else {
415 syllable.onset.push(curr_char);
416 position = Position::Onset;
417 }
418 } else if position == Position::Nucleus {
419 if curr_char == 'y'
420 && (index == word_len - 1
421 || (index + 1 < word_len && !chars[index + 1].is_vowel()))
422 {
423 syllable.nucleus.push(curr_char);
424 } else if curr_char == 'h' {
425 index += 1;
426 if word_len <= index {
427 return Err(InvalidWord)
428 }
429 if index == chars.len() {
430 syllable.coda.push(curr_char);
431 position = Position::Coda;
432 } else {
433 let next_char = chars[index];
434 if next_char.is_vowel() {
435 if syllable.nucleus.chars().count() == 1
436 && can_form_hiatus(
437 syllable.nucleus.chars().next().unwrap(),
438 next_char,
439 )
440 {
441 syllables.push(syllable);
442 syllable = Syllable {
443 onset: curr_char.to_string(),
444 nucleus: next_char.to_string(),
445 coda: "".to_string(),
446 };
447 position = Position::Nucleus;
448 } else {
449 index += 1;
450 if index == chars.len() {
451 syllable.nucleus.push(curr_char);
452 syllable.nucleus.push(next_char);
453 position = Position::Coda;
454 } else {
455 let after_next_char = chars[index];
456 if after_next_char.is_vowel() {
457 if can_form_triphthong(
458 syllable.nucleus.chars().next().unwrap(),
459 next_char,
460 after_next_char,
461 ) {
462 } else {
464 syllables.push(syllable);
465 let mut nucleus = next_char.to_string();
466 nucleus.push(after_next_char);
467 syllable = Syllable {
468 onset: curr_char.to_string(),
469 nucleus,
470 coda: "".to_string(),
471 };
472 position = Position::Nucleus;
473 }
474 } else {
475 syllable.nucleus.push(curr_char);
476 syllable.nucleus.push(next_char);
477 syllable.coda.push(after_next_char);
478 position = Position::Coda;
479 }
480 }
481 }
482 } else {
483 syllable.coda.push(curr_char);
484 syllables.push(syllable);
485 syllable = Syllable {
486 onset: next_char.to_string(),
487 nucleus: "".to_string(),
488 coda: "".to_string(),
489 };
490 position = Position::Onset;
491 }
492 }
493 } else {
494 syllable.coda.push(curr_char);
495 position = Position::Coda;
496 }
497 } else if position == Position::Coda {
498 if curr_char == 'y' {
499 if syllable.coda.chars().count() == 1 {
500 if index + 1 < word_len {
501 if chars[index + 1].is_vowel() {
502 syllables.push(syllable);
503 syllable = Syllable {
504 onset: curr_char.to_string(),
505 nucleus: "".to_string(),
506 coda: "".to_string(),
507 };
508 position = Position::Onset;
509 } else {
510 let onset = syllable.coda.clone();
511 syllable.coda.clear();
512 syllables.push(syllable);
513 syllable = Syllable {
514 onset,
515 nucleus: curr_char.to_string(),
516 coda: "".to_string(),
517 };
518 position = Position::Nucleus;
519 }
520 }
521 } else if syllable.coda.chars().count() == 2 {
522 if is_consonant_group(syllable.coda.as_str()) {
523 let onset = syllable.coda.clone();
524 syllable.coda = "".to_string();
525 syllables.push(syllable);
526 syllable = Syllable {
527 onset,
528 nucleus: curr_char.to_string(),
529 coda: "".to_string(),
530 };
531 position = Position::Nucleus;
532 } else {
533 let chars: Vec<char> = syllable.coda.chars().collect();
534 let onset = chars[1].to_string();
535 syllable.coda = chars[0].to_string();
536 syllables.push(syllable);
537 syllable = Syllable {
538 onset,
539 nucleus: curr_char.to_string(),
540 coda: "".to_string(),
541 };
542 position = Position::Nucleus;
543 }
544 } else {
545 syllable.coda.push(curr_char);
546 }
547 } else {
548 syllable.coda.push(curr_char);
549 }
550 }
551 } else if position == Position::None || position == Position::Onset {
552 position = Position::Nucleus;
553 syllable.nucleus.push(curr_char);
554 } else if position == Position::Nucleus {
555 if syllable.nucleus.chars().count() == 1 {
556 if can_form_hiatus(syllable.nucleus.chars().next().unwrap(), curr_char) {
557 syllables.push(syllable);
558 syllable = Syllable {
559 onset: "".to_string(),
560 nucleus: curr_char.to_string(),
561 coda: "".to_string(),
562 };
563 } else {
564 syllable.nucleus.push(curr_char);
565 }
566 } else if syllable.nucleus.chars().count() == 2 {
567 if can_form_triphthong(
568 syllable.nucleus.chars().next().unwrap(),
569 syllable.nucleus.chars().nth(1).unwrap(),
570 curr_char,
571 ) {
572 syllable.nucleus.push(curr_char);
573 } else {
574 let last_nucleus = syllable.nucleus.chars().nth(1).unwrap();
575 if last_nucleus.is_weak_vowel() {
576 syllable.nucleus = syllable.nucleus.chars().next().unwrap().to_string();
577 syllables.push(syllable);
578 let mut last_nucleus = last_nucleus.to_string();
579 last_nucleus.push(curr_char);
580 syllable = Syllable {
581 onset: "".to_string(),
582 nucleus: last_nucleus,
583 coda: "".to_string(),
584 }
585 } else {
586 syllables.push(syllable);
587 syllable = Syllable {
588 onset: "".to_string(),
589 nucleus: curr_char.to_string(),
590 coda: "".to_string(),
591 }
592 }
593 }
594 position = Position::Nucleus;
595 }
596 } else if position == Position::Coda {
597 if syllable.coda.chars().count() == 1 {
598 let temp = syllable.coda.clone();
599 syllable.coda = "".to_string();
600 syllables.push(syllable);
601 syllable = Syllable {
602 onset: temp,
603 nucleus: curr_char.to_string(),
604 coda: "".to_string(),
605 }
606 } else if syllable.coda.chars().count() == 2 {
607 let temp: String;
608 if is_consonant_group(syllable.coda.as_str()) {
609 temp = syllable.coda.clone();
610 syllable.coda = "".to_string();
611 } else {
612 temp = syllable.coda.chars().nth(1).unwrap().to_string();
613 syllable.coda = syllable.coda.chars().next().unwrap().to_string();
614 }
615 syllables.push(syllable);
616 syllable = Syllable {
617 onset: temp,
618 nucleus: curr_char.to_string(),
619 coda: "".to_string(),
620 };
621 } else if syllable.coda.chars().count() == 3 {
622 let temp = syllable.coda.chars().skip(1).collect::<String>();
623 syllable.coda = syllable.coda.chars().next().unwrap().to_string();
624 syllables.push(syllable);
625 syllable = Syllable {
626 onset: temp,
627 nucleus: curr_char.to_string(),
628 coda: "".to_string(),
629 }
630 } else if syllable.coda.chars().count() == 4 {
631 let temp = syllable.coda.as_str()[2..4].to_string();
634 syllable.coda = syllable.coda.as_str()[0..2].to_string();
635 syllables.push(syllable);
636 syllable = Syllable {
637 onset: temp,
638 nucleus: curr_char.to_string(),
639 coda: "".to_string(),
640 }
641 }
642 position = Position::Nucleus;
643 }
644
645 index += 1;
646 if index > word_len - 1 {
647 syllables.push(syllable);
648 break;
649 }
650 }
651 Ok(syllables)
652}
653
654fn identify_stress(syllables: &[Syllable]) -> usize {
655 let syllable_count = syllables.len();
656 if syllable_count == 0 || syllable_count == 1 {
657 return 0;
658 }
659 if syllable_count > 1 && syllables[syllable_count - 1].has_accented_vowel() {
660 return syllable_count - 1;
661 }
662 if syllable_count >= 2 && syllables[syllable_count - 2].has_accented_vowel() {
663 return syllable_count - 2;
664 }
665 if syllable_count >= 3 && syllables[syllable_count - 3].has_accented_vowel() {
666 return syllable_count - 3;
667 }
668 if syllable_count >= 4 {
669 let mut index = syllable_count as i8 - 4;
670 while index >= 0 {
671 if syllables[index as usize].has_accented_vowel() {
672 return index as usize;
673 }
674 index -= 1;
675 }
676 }
677
678 let last_syllable = &syllables[syllable_count - 1];
679 if last_syllable.coda.is_empty() {
680 if last_syllable.nucleus.chars().count() == 3 {
681 return syllable_count - 1;
682 }
683 } else {
684 if last_syllable.coda != "n" && last_syllable.coda != "s" {
685 return syllable_count - 1;
686 }
687 }
688
689 syllable_count - 2
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695 use test::Bencher;
696
697 #[test]
698 fn test_hiato() {
699 let word: Word = "lee".into();
700 let vowel_combos = word.vowel_combos();
701 assert_eq!(vowel_combos.hiatuses.len(), 1);
702 assert_eq!(vowel_combos.diphthongs.len(), 0);
703 assert_eq!(vowel_combos.triphthongs.len(), 0);
704 }
705
706 #[test]
707 fn test_rhymes_with() {
708 let word: Word = "vida".into();
709 assert!(word.rhymes_with(&Word::from("frida"), None));
710 assert!(!word.rhymes_with(&Word::from("vía"), None));
711 assert!(word.assonant_rhymes_with(&Word::from("villa")));
712 assert!(word.assonant_rhymes_with(&Word::from("vía")));
713 }
714
715 #[test]
716 fn test_rhymes_with_yeismo() {
717 let word: Word = "callo".into();
718 assert!(word.rhymes_with(
719 &Word::from("cayo"),
720 Some(RhymeOptions {
721 yeismo: true,
722 seseo: false,
723 b_equals_v: true
724 })
725 ));
726 assert!(!word.rhymes_with(
727 &Word::from("cayo"),
728 Some(RhymeOptions {
729 yeismo: false,
730 seseo: false,
731 b_equals_v: true
732 })
733 ));
734 }
735
736 #[test]
737 fn test_rhymes_with_seseo() {
738 let word: Word = "cabeza".into();
739 assert!(word.rhymes_with(
740 &Word::from("besa"),
741 Some(RhymeOptions {
742 yeismo: true,
743 seseo: true,
744 b_equals_v: true
745 })
746 ));
747 assert!(!word.rhymes_with(
748 &Word::from("besa"),
749 Some(RhymeOptions {
750 yeismo: true,
751 seseo: false,
752 b_equals_v: true
753 })
754 ));
755 }
756
757 #[test]
758 fn test_rhymes_with_seseo_end() {
759 let word: Word = "estrés".into();
760 assert!(word.rhymes_with(
761 &Word::from("vez"),
762 Some(RhymeOptions {
763 yeismo: true,
764 seseo: true,
765 b_equals_v: true
766 })
767 ));
768 assert!(!word.rhymes_with(
769 &Word::from("vez"),
770 Some(RhymeOptions {
771 yeismo: true,
772 seseo: false,
773 b_equals_v: true
774 })
775 ));
776 }
777
778 #[test]
779 fn test_rhymes_b_v() {
780 let word: Word = "escribe".into();
781 assert!(word.rhymes_with(
782 &Word::from("declive"),
783 Some(RhymeOptions {
784 yeismo: true,
785 seseo: true,
786 b_equals_v: true
787 })
788 ));
789 assert!(!word.rhymes_with(
790 &Word::from("declive"),
791 Some(RhymeOptions {
792 yeismo: true,
793 seseo: true,
794 b_equals_v: false
795 })
796 ));
797 }
798
799 #[test]
800 fn test_rhymes_with_single_syllable() {
801 let word: Word = "vi".into();
802 assert!(word.rhymes_with(&Word::from("tí"), None));
803 }
804
805 #[test]
806 fn test_rhymes_with_y() {
807 let word: Word = "ley".into();
808 assert!(!word.rhymes_with(&Word::from("é"), None));
809 }
810
811 #[test]
812 fn test_rhymes_no_match() {
813 let word: Word = "vi".into();
814 assert!(!word.rhymes_with(&Word::from("sid"), None));
815 }
816
817 #[test]
818 fn test_diptongo() {
819 let word: Word = "DIALOGO".into();
820 let vowel_combos = word.vowel_combos();
821 assert_eq!(vowel_combos.hiatuses.len(), 0);
822 assert_eq!(vowel_combos.diphthongs.len(), 1);
823 assert_eq!(vowel_combos.triphthongs.len(), 0);
824 }
825
826 #[bench]
827 fn bench_wordify(b: &mut Bencher) {
828 b.iter(|| {
829 let _word: Word = "envergadura".into();
830 });
831 }
832
833 #[bench]
834 fn bench_vowel_combos(b: &mut Bencher) {
835 let word: Word = "envergadura".into();
836 b.iter(|| word.vowel_combos());
837 }
838
839 #[bench]
840 fn bench_rhyme(b: &mut Bencher) {
841 let word: Word = "envergadura".into();
842 b.iter(|| word.rhyme());
843 }
844
845 #[bench]
846 fn bench_syllabize(b: &mut Bencher) {
847 let word: Word = "envergadura".into();
848 b.iter(|| word.syllabize("-"));
849 }
850}
851
852#[cfg(doctest)]
853mod test_readme {
854 macro_rules! external_doc_test {
855 ($x:expr) => {
856 #[doc = $x]
857 unsafe extern "C" {}
858 };
859 }
860
861 external_doc_test!(include_str!("../README.md"));
862}