1use unicode_segmentation::UnicodeSegmentation;
2use Accent::*;
3use Breathing::*;
4
5#[inline]
8pub fn syllables<'a>(word: &'a str) -> Vec<&'a str> {
9 let mut syllables = Vec::<&'a str>::new();
10 let mut state = Syllable::Ending;
11 let mut end: usize = word.len();
12 let mut last: usize = end;
13 let mut prev: char = 0 as char;
14 let mut prev_prev: char = 0 as char;
15 for (i, element) in UnicodeSegmentation::grapheme_indices(word, true).rev() {
16 let (c, _, vowel, _, _, diaeresis) = categorise(element);
17 if c == 0 as char {
18 state = Syllable::Ending;
19 if last == end {
20 syllables.push(&word[i..end]);
21 } else {
22 syllables.push(&word[last..end]);
23 if last > i {
24 syllables.push(&word[i..last]);
25 }
26 }
27 end = i;
28 last = i;
29 prev = c;
30 continue;
31 }
32 match state {
33 Syllable::Ending => {
34 if vowel {
36 if diaeresis {
37 syllables.push(&word[i..end]);
38 end = i;
39 }
40 state = Syllable::Starting;
41 }
42 }
43 Syllable::Starting => {
44 if is_dipthong(c, prev) {
46 } else if vowel {
48 syllables.push(&word[last..end]);
49 end = last;
50 } else {
51 state = Syllable::Restarting;
52 }
53 }
54 Syllable::Restarting => {
55 if vowel {
58 syllables.push(&word[last..end]);
59 end = last;
60 state = Syllable::Starting;
61 } else if c == 'σ' && prev == 'τ' && prev_prev == 'ρ' {
62 syllables.push(&word[i..end]);
63 end = i;
64 state = Syllable::Ending;
65 } else if joinable_consonant(c, prev) {
66 prev_prev = prev;
67 } else {
68 syllables.push(&word[last..end]);
69 end = last;
70 state = Syllable::Ending;
71 }
72 }
73 }
74 prev = c;
75 last = i;
76 }
77 if end != 0 {
78 syllables.push(&word[0..end]);
79 }
80
81 syllables.reverse();
82 return syllables;
83}
84
85#[derive(Debug)]
86enum Syllable {
87 Ending,
88 Starting,
89 Restarting,
90}
91
92#[inline]
93fn is_dipthong(a: char, b: char) -> bool {
94 match (a, b) {
95 ('α', 'ι') => true,
96 ('ε', 'ι') => true,
97 ('ο', 'ι') => true,
98 ('υ', 'ι') => true,
99 ('α', 'υ') => true,
100 ('ε', 'υ') => true,
101 ('ο', 'υ') => true,
102 ('η', 'υ') => true,
103 _ => false,
104 }
105}
106
107#[inline]
108fn joinable_consonant(a: char, b: char) -> bool {
109 match (a, b) {
110 ('β', 'δ') => true,
111 ('β', 'λ') => true,
112 ('β', 'ρ') => true,
113 ('γ', 'λ') => true,
114 ('γ', 'ν') => true,
115 ('γ', 'ρ') => true,
116 ('δ', 'ρ') => true,
117 ('θ', 'λ') => true,
118 ('θ', 'ν') => true,
119 ('θ', 'ρ') => true,
120 ('κ', 'λ') => true,
121 ('κ', 'ν') => true,
122 ('κ', 'ρ') => true,
123 ('κ', 'τ') => true,
124 ('μ', 'ν') => true,
125 ('π', 'λ') => true,
126 ('π', 'ν') => true,
127 ('π', 'ρ') => true,
128 ('π', 'τ') => true,
129 ('σ', 'β') => true,
130 ('σ', 'θ') => true,
131 ('σ', 'κ') => true,
132 ('σ', 'μ') => true,
133 ('σ', 'π') => true,
134 ('σ', 'τ') => true,
135 ('σ', 'φ') => true,
136 ('σ', 'χ') => true,
137 ('τ', 'ρ') => true,
138 ('φ', 'θ') => true,
139 ('φ', 'λ') => true,
140 ('φ', 'ρ') => true,
141 ('χ', 'λ') => true,
142 ('χ', 'ρ') => true,
143 _ => false,
144 }
145}
146
147#[derive(Debug, PartialEq)]
148pub enum Accent {
149 Acute,
150 Circumflex,
151 Grave,
152 Unaccented,
153}
154
155#[derive(Debug, PartialEq)]
156pub enum Breathing {
157 Rough,
158 Smooth,
159 None,
160}
161
162#[inline]
163fn categorise(c: &str) -> (char, &'static str, bool, Breathing, Accent, bool) {
164 match c {
165 "Α" | "α" => {
166 return ('α', "a", true, None, Unaccented, false);
167 }
168 "Ε" | "ε" => {
169 return ('ε', "e", true, None, Unaccented, false);
170 }
171 "Η" | "η" => {
172 return ('η', "e", true, None, Unaccented, false);
173 }
174 "Ι" | "ι" => {
175 return ('ι', "i", true, None, Unaccented, false);
176 }
177 "Ο" | "ο" => {
178 return ('ο', "o", true, None, Unaccented, false);
179 }
180 "Υ" | "υ" => {
181 return ('υ', "u", true, None, Unaccented, false);
182 }
183 "Ω" | "ω" => {
184 return ('ω', "o", true, None, Unaccented, false);
185 }
186 "Ϋ" | "ϋ" => {
187 return ('υ', "υ", true, None, Unaccented, true);
188 }
189 "Ϊ" | "ϊ" => {
190 return ('ι', "i", true, None, Unaccented, true);
191 }
192 "ᾶ" => {
193 return ('ᾶ', "a", true, None, Circumflex, false);
194 }
195 "ῆ" => {
196 return ('ῆ', "e", true, None, Circumflex, false);
197 }
198 "ῖ" => {
199 return ('ῖ', "i", true, None, Circumflex, false);
200 }
201 "ῶ" => {
202 return ('ῶ', "o", true, None, Circumflex, false);
203 }
204 "Ά" | "Α\u{301}" | "ά" | "α\u{301}" => {
205 return ('α', "a", true, Smooth, Acute, false);
206 }
207 "Έ" | "Ε\u{301}" | "έ" | "ε\u{301}" => {
208 return ('ε', "e", true, Smooth, Acute, false);
209 }
210 "Ή" | "Η\u{301}" | "ή" | "η\u{301}" => {
211 return ('η', "e", true, Smooth, Acute, false);
212 }
213 "Ί" | "Ι\u{301}" | "ί" | "ι\u{301}" => {
214 return ('ι', "i", true, Smooth, Acute, false);
215 }
216 "Ό" | "Ο\u{301}" | "ό" | "ο\u{301}" => {
217 return ('ο', "o", true, Smooth, Acute, false);
218 }
219 "Ύ" | "Υ\u{301}" | "ύ" | "υ\u{301}" => {
220 return ('υ', "u", true, Smooth, Acute, false);
221 }
222 "Ώ" | "Ω\u{301}" | "ώ" | "ω\u{301}" => {
223 return ('ω', "o", true, Smooth, Acute, false);
224 }
225 "Ὰ" | "Α\u{300}" | "ὰ" | "α\u{300}" => {
226 return ('α', "a", true, Smooth, Grave, false);
227 }
228 "Ὲ" | "Ε\u{300}" | "ὲ" | "ε\u{300}" => {
229 return ('ε', "e", true, Smooth, Grave, false);
230 }
231 "Ὴ" | "Η\u{300}" | "ὴ" | "η\u{300}" => {
232 return ('η', "e", true, Smooth, Grave, false);
233 }
234 "Ὶ" | "Ι\u{300}" | "ὶ" | "ι\u{300}" => {
235 return ('ι', "i", true, Smooth, Grave, false);
236 }
237 "Ὸ" | "Ο\u{300}" | "ὸ" | "ο\u{300}" => {
238 return ('ο', "o", true, Smooth, Grave, false);
239 }
240 "Ὺ" | "Υ\u{300}" | "ὺ" | "υ\u{300}" => {
241 return ('υ', "u", true, Smooth, Grave, false);
242 }
243 "Ὼ" | "Ω\u{300}" | "ὼ" | "ω\u{300}" => {
244 return ('ω', "o", true, Smooth, Grave, false);
245 }
246 "Ἀ" | "Α\u{313}" | "ἀ" | "α\u{313}" => {
247 return ('α', "a", true, Smooth, Unaccented, false);
248 }
249 "Ἐ" | "Ε\u{313}" | "ἐ" | "ε\u{313}" => {
250 return ('ε', "e", true, Smooth, Unaccented, false);
251 }
252 "Ἠ" | "Η\u{313}" | "ἠ" | "η\u{313}" => {
253 return ('η', "e", true, Smooth, Unaccented, false);
254 }
255 "Ἰ" | "Ι\u{313}" | "ἰ" | "ι\u{313}" => {
256 return ('ι', "i", true, Smooth, Unaccented, false);
257 }
258 "Ὀ" | "Ο\u{313}" | "ὀ" | "ο\u{313}" => {
259 return ('ο', "o", true, Smooth, Unaccented, false);
260 }
261 "Υ\u{313}" | "ὐ" | "υ\u{313}" => {
262 return ('υ', "u", true, Smooth, Unaccented, false);
263 }
264 "Ὠ" | "Ω\u{313}" | "ὠ" | "ω\u{313}" => {
265 return ('ω', "o", true, Smooth, Unaccented, false);
266 }
267 "Ἁ" | "ἁ" => {
268 return ('α', "a", true, Rough, Unaccented, false);
269 }
270 "Ἑ" | "ἑ" => {
271 return ('ε', "e", true, Rough, Unaccented, false);
272 }
273 "Ἡ" | "ἡ" => {
274 return ('η', "e", true, Rough, Unaccented, false);
275 }
276 "Ἱ" | "ἱ" => {
277 return ('ι', "i", true, Rough, Unaccented, false);
278 }
279 "Ὁ" | "ὁ" => {
280 return ('ο', "i", true, Rough, Unaccented, false);
281 }
282 "Ὑ" | "ὑ" => {
283 return ('υ', "u", true, Rough, Unaccented, false);
284 }
285 "Ὡ" | "ὡ" => {
286 return ('ω', "o", true, Rough, Unaccented, false);
287 }
288 "Ἄ" | "ἄ" => {
289 return ('α', "a", true, Smooth, Acute, false);
290 }
291 "Ἔ" | "ἔ" | "ε\u{313}\u{301}" | "ε\u{301}\u{313}" | "Ε\u{313}\u{301}" | "Ε\u{301}\u{313}" => {
292 return ('ε', "e", true, Smooth, Acute, false);
293 }
294 "Ἤ" | "ἤ" | "η\u{313}\u{301}" | "η\u{301}\u{313}" | "Η\u{313}\u{301}" | "Η\u{301}\u{313}" => {
295 return ('η', "e", true, Smooth, Acute, false);
296 }
297 "Ἴ" | "ἴ" | "ι\u{313}\u{301}" | "ι\u{301}\u{313}" | "Ι\u{313}\u{301}" | "Ι\u{301}\u{313}" => {
298 return ('ι', "i", true, Smooth, Acute, false);
299 }
300 "Ὄ" | "ὄ" | "ο\u{313}\u{301}" | "ο\u{301}\u{313}" | "Ο\u{313}\u{301}" | "Ο\u{301}\u{313}" => {
301 return ('ο', "o", true, Smooth, Acute, false);
302 }
303 "ὔ" | "υ\u{313}\u{301}" | "υ\u{301}\u{313}" => {
304 return ('υ', "u", true, Smooth, Acute, false);
305 }
306 "Ὤ" | "ὤ" | "Ω\u{313}\u{301}" | "Ω\u{301}\u{313}" | "ω\u{313}\u{301}" | "ω\u{301}\u{313}" => {
307 return ('ω', "o", true, Smooth, Acute, false);
308 }
309 "ἅ" | "α\u{301}\u{314}" | "α\u{314}\u{301}" | "Ἅ" | "Α\u{301}\u{314}" | "Α\u{314}\u{301}" => {
310 return ('α', "a", true, Rough, Acute, false);
311 }
312 "ἕ" | "ε\u{301}\u{314}" | "ε\u{314}\u{301}" | "Ἕ" | "Ε\u{301}\u{314}" | "Ε\u{314}\u{301}" => {
313 return ('ε', "e", true, Rough, Acute, false);
314 }
315 "ἥ" | "η\u{301}\u{314}" | "η\u{314}\u{301}" | "Ἥ" | "Η\u{301}\u{314}" | "Η\u{314}\u{301}" => {
316 return ('η', "e", true, Rough, Acute, false);
317 }
318 "ἵ" | "ι\u{301}\u{314}" | "ι\u{314}\u{301}" | "Ἵ" | "Ι\u{301}\u{314}" | "Ι\u{314}\u{301}" => {
319 return ('ι', "i", true, Rough, Acute, false);
320 }
321 "ὅ" | "ο\u{301}\u{314}" | "ο\u{314}\u{301}" | "Ὅ" | "Ο\u{301}\u{314}" | "Ο\u{314}\u{301}" => {
322 return ('ο', "o", true, Rough, Acute, false);
323 }
324 "ὕ" | "υ\u{301}\u{314}" | "υ\u{314}\u{301}" | "Ὕ" | "Υ\u{301}\u{314}" | "Υ\u{314}\u{301}" => {
325 return ('υ', "u", true, Rough, Acute, false);
326 }
327 "ὥ" | "ω\u{301}\u{314}" | "ω\u{314}\u{301}" | "Ὥ" | "Ω\u{301}\u{314}" | "Ω\u{314}\u{301}" => {
328 return ('ω', "o", true, Rough, Acute, false);
329 }
330 "Ἂ" | "ἂ" => {
331 return ('α', "a", true, Smooth, Grave, false);
332 }
333 "Ἒ" | "ἒ" => {
334 return ('ε', "e", true, Smooth, Grave, false);
335 }
336 "Ἢ" | "ἢ" => {
337 return ('η', "e", true, Smooth, Grave, false);
338 }
339 "Ἲ" | "ἲ" => {
340 return ('ι', "i", true, Smooth, Grave, false);
341 }
342 "Ὂ" | "ὂ" => {
343 return ('ο', "o", true, Smooth, Grave, false);
344 }
345 "ὒ" => {
346 return ('υ', "u", true, Smooth, Grave, false);
347 }
348 "Ὢ" | "ὢ" => {
349 return ('ω', "o", true, Smooth, Grave, false);
350 }
351 "Ἃ" | "ἃ" => {
352 return ('α', "a", true, Rough, Grave, false);
353 }
354 "Ἓ" | "ἓ" => {
355 return ('ε', "e", true, Rough, Grave, false);
356 }
357 "Ἣ" | "ἣ" => {
358 return ('η', "e", true, Rough, Grave, false);
359 }
360 "Ἳ" | "ἳ" => {
361 return ('ι', "i", true, Rough, Grave, false);
362 }
363 "Ὃ" | "ὃ" => {
364 return ('ο', "o", true, Rough, Grave, false);
365 }
366 "Ὓ" | "ὓ" => {
367 return ('υ', "u", true, Rough, Grave, false);
368 }
369 "Ὣ" | "ὣ" => {
370 return ('ω', "o", true, Rough, Grave, false);
371 }
372 "Β" | "β" => {
373 return ('β', "b", false, None, Unaccented, false);
374 }
375 "Γ" | "γ" => {
376 return ('γ', "g", false, None, Unaccented, false);
377 }
378 "Δ" | "δ" => {
379 return ('δ', "d", false, None, Unaccented, false);
380 }
381 "Ζ" | "ζ" => {
382 return ('ζ', "z", false, None, Unaccented, false);
383 }
384 "Θ" | "θ" => {
385 return ('θ', "th", false, None, Unaccented, false);
386 }
387 "Κ" | "κ" => {
388 return ('κ', "k", false, None, Unaccented, false);
389 }
390 "Λ" | "λ" => {
391 return ('λ', "l", false, None, Unaccented, false);
392 }
393 "Μ" | "μ" => {
394 return ('μ', "m", false, None, Unaccented, false);
395 }
396 "Ν" | "ν" => {
397 return ('ν', "n", false, None, Unaccented, false);
398 }
399 "Ξ" | "ξ" => {
400 return ('ξ', "x", false, None, Unaccented, false);
401 }
402 "Π" | "π" => {
403 return ('π', "p", false, None, Unaccented, false);
404 }
405 "Ρ" | "ρ" => {
406 return ('ρ', "r", false, None, Unaccented, false);
407 }
408 "Σ" | "σ" | "ς" => {
409 return ('σ', "s", false, None, Unaccented, false);
410 }
411 "Τ" | "τ" => {
412 return ('τ', "t", false, None, Unaccented, false);
413 }
414 "Φ" | "φ" => {
415 return ('φ', "f", false, None, Unaccented, false);
416 }
417 "Χ" | "χ" => {
418 return ('χ', "ch", false, None, Unaccented, false);
419 }
420 "Ψ" | "ψ" => {
421 return ('ψ', "ps", false, None, Unaccented, false);
422 }
423 _ => (0 as char, "", false, None, Unaccented, false),
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn test_categorise() {
433 let (gc, c, vowel, breathing, accent, diaeresis) = categorise("α");
434 assert_eq!(c, "a");
435 assert_eq!(gc, 'α');
436 assert_eq!(vowel, true);
437 assert_eq!(breathing, None);
438 assert_eq!(accent, Unaccented);
439 assert_eq!(diaeresis, false);
440
441 let (gc, c, vowel, breathing, accent, _) = categorise("β");
442 assert_eq!(c, "b");
443 assert_eq!(gc, 'β');
444 assert_eq!(vowel, false);
445 assert_eq!(breathing, None);
446 assert_eq!(accent, Unaccented);
447
448 let (gc, c, vowel, breathing, accent, _) = categorise("ὔ");
449 assert_eq!(c, "u");
450 assert_eq!(gc, 'υ');
451 assert_eq!(vowel, true);
452 assert_eq!(breathing, Smooth);
453 assert_eq!(accent, Acute);
454
455 let (gc, c, vowel, breathing, accent, _) = categorise("Β");
456 assert_eq!(c, "b");
457 assert_eq!(gc, 'β');
458 assert_eq!(vowel, false);
459 assert_eq!(breathing, None);
460 assert_eq!(accent, Unaccented);
461 }
462
463 #[test]
464 fn test_invalid_characters() {
465 assert_eq!(syllables("σaσ"), ["σ", "a", "σ"]);
467 assert_eq!(syllables("eχει"), ["e", "χει"]);
468 assert_eq!(syllables("ἔχεi"), ["ἔ", "χε", "i"]);
469 assert_eq!(syllables("ἔχeι"), ["ἔχ", "e", "ι"]);
470 }
471
472 #[test]
473 fn test_basic_words() {
474 assert_eq!(syllables("αα"), ["α", "α"]);
476 assert_eq!(syllables("καα"), ["κα", "α"]);
477 assert_eq!(syllables("αλα"), ["α", "λα"]);
478 assert_eq!(syllables("ἄμα"), ["ἄ", "μα"]);
479 assert_eq!(syllables("ἀλά"), ["ἀ", "λά"]);
480 assert_eq!(syllables("ἀλὰ"), ["ἀ", "λὰ"]);
481 assert_eq!(syllables("ἈΛΆ"), ["Ἀ", "ΛΆ"]);
482 assert_eq!(syllables("χριστος"), ["χρι", "στος"]);
483 assert_eq!(syllables("χρίστος"), ["χρί", "στος"]);
484 assert_eq!(syllables("περιπα"), ["πε", "ρι", "πα"]);
485 assert_eq!(syllables("τραγος"), ["τρα", "γος"]);
486 assert_eq!(syllables("στρατιοτης"), ["στρα", "τι", "ο", "της"]);
487 assert_eq!(syllables("πιστευω"), ["πι", "στευ", "ω"]);
488 assert_eq!(syllables("γυναικός"), ["γυ", "ναι", "κός"]);
489 assert_eq!(syllables("φυω"), ["φυ", "ω"]);
490 assert_eq!(syllables("σσσ"), ["σσσ"]);
491 assert_eq!(syllables("μωϋσῆν"), ["μω", "ϋ", "σῆν"]);
492 assert_eq!(syllables("ὄσπριον"), ["ὄ", "σπρι", "ον"]);
493 assert_eq!(syllables("ὁσία"), ["ὁ", "σί", "α"]);
494 assert_eq!(syllables("ὅτου"), ["ὅ", "του"]);
495 assert_eq!(syllables("ἥτις"), ["ἥ", "τις"]);
496 assert_eq!(syllables("αἵτινες"), ["αἵ", "τι", "νες"]);
497 assert_eq!(syllables("οἵτινες"), ["οἵ", "τι", "νες"]);
498 assert_eq!(syllables("περιεπατήσαμεν"), ["πε", "ρι", "ε", "πα", "τή", "σα", "μεν"]);
499
500 assert_eq!(syllables("ἀετός"), ["ἀ", "ε", "τός"]);
502 assert_eq!(syllables("ἀετὸν"), ["ἀ", "ε", "τὸν"]);
503 assert_eq!(syllables("ὥσπερ"), ["ὥ", "σπερ"]); assert_eq!(syllables("ἔχει"), ["ἔ", "χει"]);
505 }
506}