/src/espeak-ng/src/libespeak-ng/dictionary.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2005 to 2014 by Jonathan Duddington |
3 | | * email: jonsd@users.sourceforge.net |
4 | | * Copyright (C) 2013-2017 Reece H. Dunn |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation; either version 3 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, see: <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include "config.h" |
21 | | |
22 | | #include <ctype.h> |
23 | | #include <stdint.h> |
24 | | #include <stdio.h> |
25 | | #include <stdlib.h> |
26 | | #include <string.h> |
27 | | #include <wctype.h> |
28 | | #include <wchar.h> |
29 | | #include <assert.h> |
30 | | |
31 | | #include <espeak-ng/espeak_ng.h> |
32 | | #include <espeak-ng/speak_lib.h> |
33 | | #include <espeak-ng/encoding.h> |
34 | | |
35 | | #include "common.h" // for GetFileLength, strncpy0 |
36 | | #include "dictionary.h" |
37 | | #include "numbers.h" // for LookupAccentedLetter, Look... |
38 | | #include "phoneme.h" // for PHONEME_TAB, phVOWEL, phon... |
39 | | #include "readclause.h" // for WordToString2 |
40 | | #include "speech.h" // for path_home |
41 | | #include "compiledict.h" // for DecodeRule |
42 | | #include "synthdata.h" // for PhonemeCode, InterpretPhoneme |
43 | | #include "synthesize.h" // for STRESS_IS_PRIMARY, phoneme... |
44 | | #include "translate.h" // for Translator, utf8_in, LANGU... |
45 | | |
46 | | static int LookupFlags(Translator *tr, const char *word, unsigned int flags_out[2]); |
47 | | static void DollarRule(char *word[], char *word_start, int consumed, int group_length, char word_buf[N_WORD_BYTES], Translator *tr, int command, int *failed, int *add_points); |
48 | | |
49 | | typedef struct { |
50 | | int points; |
51 | | const char *phonemes; |
52 | | int end_type; |
53 | | char *del_fwd; |
54 | | } MatchRecord; |
55 | | |
56 | | |
57 | | int dictionary_skipwords; |
58 | | char dictionary_name[40]; |
59 | | |
60 | | // accented characters which indicate (in some languages) the start of a separate syllable |
61 | | static const unsigned short diereses_list[7] = { 0xe4, 0xeb, 0xef, 0xf6, 0xfc, 0xff, 0 }; |
62 | | |
63 | | // convert characters to an approximate 7 bit ascii equivalent |
64 | | // used for checking for vowels (up to 0x259=schwa) |
65 | 469k | #define N_REMOVE_ACCENT 0x25e |
66 | | static const unsigned char remove_accent[N_REMOVE_ACCENT] = { |
67 | | 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', // 0c0 |
68 | | 'd', 'n', 'o', 'o', 'o', 'o', 'o', 0, 'o', 'u', 'u', 'u', 'u', 'y', 't', 's', // 0d0 |
69 | | 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', // 0e0 |
70 | | 'd', 'n', 'o', 'o', 'o', 'o', 'o', 0, 'o', 'u', 'u', 'u', 'u', 'y', 't', 'y', // 0f0 |
71 | | |
72 | | 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'd', 'd', // 100 |
73 | | 'd', 'd', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'g', 'g', 'g', 'g', // 110 |
74 | | 'g', 'g', 'g', 'g', 'h', 'h', 'h', 'h', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', // 120 |
75 | | 'i', 'i', 'i', 'i', 'j', 'j', 'k', 'k', 'k', 'l', 'l', 'l', 'l', 'l', 'l', 'l', // 130 |
76 | | 'l', 'l', 'l', 'n', 'n', 'n', 'n', 'n', 'n', 'n', 'n', 'n', 'o', 'o', 'o', 'o', // 140 |
77 | | 'o', 'o', 'o', 'o', 'r', 'r', 'r', 'r', 'r', 'r', 's', 's', 's', 's', 's', 's', // 150 |
78 | | 's', 's', 't', 't', 't', 't', 't', 't', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 160 |
79 | | 'u', 'u', 'u', 'u', 'w', 'w', 'y', 'y', 'y', 'z', 'z', 'z', 'z', 'z', 'z', 's', // 170 |
80 | | 'b', 'b', 'b', 'b', 0, 0, 'o', 'c', 'c', 'd', 'd', 'd', 'd', 'd', 'e', 'e', // 180 |
81 | | 'e', 'f', 'f', 'g', 'g', 'h', 'i', 'i', 'k', 'k', 'l', 'l', 'm', 'n', 'n', 'o', // 190 |
82 | | 'o', 'o', 'o', 'o', 'p', 'p', 'y', 0, 0, 's', 's', 't', 't', 't', 't', 'u', // 1a0 |
83 | | 'u', 'u', 'v', 'y', 'y', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 0, 0, 0, 'w', // 1b0 |
84 | | 't', 't', 't', 'k', 'd', 'd', 'd', 'l', 'l', 'l', 'n', 'n', 'n', 'a', 'a', 'i', // 1c0 |
85 | | 'i', 'o', 'o', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'e', 'a', 'a', // 1d0 |
86 | | 'a', 'a', 'a', 'a', 'g', 'g', 'g', 'g', 'k', 'k', 'o', 'o', 'o', 'o', 'z', 'z', // 1e0 |
87 | | 'j', 'd', 'd', 'd', 'g', 'g', 'w', 'w', 'n', 'n', 'a', 'a', 'a', 'a', 'o', 'o', // 1f0 |
88 | | |
89 | | 'a', 'a', 'a', 'a', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'o', 'o', 'o', 'o', // 200 |
90 | | 'r', 'r', 'r', 'r', 'u', 'u', 'u', 'u', 's', 's', 't', 't', 'y', 'y', 'h', 'h', // 210 |
91 | | 'n', 'd', 'o', 'o', 'z', 'z', 'a', 'a', 'e', 'e', 'o', 'o', 'o', 'o', 'o', 'o', // 220 |
92 | | 'o', 'o', 'y', 'y', 'l', 'n', 't', 'j', 'd', 'q', 'a', 'c', 'c', 'l', 't', 's', // 230 |
93 | | 'z', 0, 0, 'b', 'u', 'v', 'e', 'e', 'j', 'j', 'q', 'q', 'r', 'r', 'y', 'y', // 240 |
94 | | 'a', 'a', 'a', 'b', 'o', 'c', 'd', 'd', 'e', 'e', 'e', 'e', 'e', 'e' |
95 | | }; |
96 | | |
97 | | static int Reverse4Bytes(int word) |
98 | 72.0k | { |
99 | | // reverse the order of bytes from little-endian to big-endian |
100 | | #ifdef ARCH_BIG |
101 | | int ix; |
102 | | int word2 = 0; |
103 | | |
104 | | for (ix = 0; ix <= 24; ix += 8) { |
105 | | word2 = word2 << 8; |
106 | | word2 |= (word >> ix) & 0xff; |
107 | | } |
108 | | return word2; |
109 | | #else |
110 | 72.0k | return word; |
111 | 72.0k | #endif |
112 | 72.0k | } |
113 | | |
114 | | static void InitGroups(Translator *tr) |
115 | 36.0k | { |
116 | | // Called after dictionary 1 is loaded, to set up table of entry points for translation rule chains |
117 | | // for single-letters and two-letter combinations |
118 | | |
119 | 36.0k | int ix; |
120 | 36.0k | char *p; |
121 | 36.0k | char *p_name; |
122 | 36.0k | unsigned char c, c2; |
123 | 36.0k | int len; |
124 | | |
125 | 36.0k | tr->n_groups2 = 0; |
126 | 9.26M | for (ix = 0; ix < 256; ix++) { |
127 | 9.22M | tr->groups1[ix] = NULL; |
128 | 9.22M | tr->groups2_count[ix] = 0; |
129 | 9.22M | tr->groups2_start[ix] = 255; // indicates "not set" |
130 | 9.22M | } |
131 | 36.0k | memset(tr->letterGroups, 0, sizeof(tr->letterGroups)); |
132 | 36.0k | memset(tr->groups3, 0, sizeof(tr->groups3)); |
133 | | |
134 | 36.0k | p = tr->data_dictrules; |
135 | | // If there are no rules in the dictionary, compile_dictrules will not |
136 | | // write a RULE_GROUP_START (written in the for loop), but will write |
137 | | // a RULE_GROUP_END. |
138 | 2.78M | if (*p != RULE_GROUP_END) while (*p != 0) { |
139 | 2.74M | if (*p != RULE_GROUP_START) { |
140 | 0 | fprintf(stderr, "Bad rules data in '%s_dict' at 0x%x (%c)\n", dictionary_name, (unsigned int)(p - tr->data_dictrules), *p); |
141 | 0 | break; |
142 | 0 | } |
143 | 2.74M | p++; |
144 | | |
145 | 2.74M | if (p[0] == RULE_REPLACEMENTS) { |
146 | 31.4k | p = (char *)(((intptr_t)p+4) & ~3); // advance to next word boundary |
147 | 31.4k | tr->langopts.replace_chars = (unsigned char *)p; |
148 | | |
149 | 3.82M | while ( !is_str_totally_null(p, 4) ) { |
150 | 3.79M | p++; |
151 | 3.79M | } |
152 | | |
153 | 184k | while (*p != RULE_GROUP_END) p++; |
154 | 31.4k | p++; |
155 | 31.4k | continue; |
156 | 31.4k | } |
157 | | |
158 | 2.71M | if (p[0] == RULE_LETTERGP2) { |
159 | 280k | ix = p[1] - 'A'; |
160 | 280k | if (ix < 0) |
161 | 35.8k | ix += 256; |
162 | 280k | p += 2; |
163 | 280k | if ((ix >= 0) && (ix < N_LETTER_GROUPS)) |
164 | 280k | tr->letterGroups[ix] = p; |
165 | 2.43M | } else { |
166 | 2.43M | len = strlen(p); |
167 | 2.43M | p_name = p; |
168 | 2.43M | c = p_name[0]; |
169 | 2.43M | c2 = p_name[1]; |
170 | | |
171 | 2.43M | p += (len+1); |
172 | 2.43M | if (len == 1) |
173 | 564k | tr->groups1[c] = p; |
174 | 1.86M | else if (len == 0) |
175 | 32.4k | tr->groups1[0] = p; |
176 | 1.83M | else if (c == 1) { |
177 | | // index by offset from letter base |
178 | 769k | tr->groups3[c2 - 1] = p; |
179 | 1.06M | } else { |
180 | 1.06M | if (tr->groups2_start[c] == 255) |
181 | 232k | tr->groups2_start[c] = tr->n_groups2; |
182 | | |
183 | 1.06M | tr->groups2_count[c]++; |
184 | 1.06M | tr->groups2[tr->n_groups2] = p; |
185 | 1.06M | tr->groups2_name[tr->n_groups2++] = (c + (c2 << 8)); |
186 | 1.06M | } |
187 | 2.43M | } |
188 | | |
189 | | // skip over all the rules in this group |
190 | 101M | while (*p != RULE_GROUP_END) |
191 | 98.6M | p += (strlen(p) + 1); |
192 | 2.71M | p++; |
193 | 2.71M | } |
194 | 36.0k | } |
195 | | |
196 | | int LoadDictionary(Translator *tr, const char *name, int no_error) |
197 | 36.0k | { |
198 | 36.0k | int hash; |
199 | 36.0k | char *p; |
200 | 36.0k | int *pw; |
201 | 36.0k | int length; |
202 | 36.0k | FILE *f; |
203 | 36.0k | int size; |
204 | 36.0k | char fname[N_PATH_BUF]; |
205 | | |
206 | 36.0k | if (dictionary_name != name) |
207 | 36.0k | snprintf(dictionary_name, sizeof(dictionary_name), "%s", name); // currently loaded dictionary name |
208 | 36.0k | if (tr->dictionary_name != name) |
209 | 9.21k | snprintf(tr->dictionary_name, sizeof(tr->dictionary_name), "%s", name); |
210 | | |
211 | | // Load a pronunciation data file into memory |
212 | | // bytes 0-3: offset to rules data |
213 | | // bytes 4-7: number of hash table entries |
214 | 36.0k | snprintf(fname, sizeof(fname), "%s%c%s_dict", path_home, PATHSEP, name); |
215 | 36.0k | size = GetFileLength(fname); |
216 | | |
217 | 36.0k | if (tr->data_dictlist != NULL) { |
218 | 0 | free(tr->data_dictlist); |
219 | 0 | tr->data_dictlist = NULL; |
220 | 0 | } |
221 | | |
222 | 36.0k | f = fopen(fname, "rb"); |
223 | 36.0k | if ((f == NULL) || (size <= 0)) { |
224 | 0 | if (no_error == 0) |
225 | 0 | fprintf(stderr, "Can't read dictionary file: '%s'\n", fname); |
226 | 0 | if (f != NULL) |
227 | 0 | fclose(f); |
228 | 0 | return 1; |
229 | 0 | } |
230 | | |
231 | 36.0k | if ((tr->data_dictlist = malloc(size)) == NULL) { |
232 | 0 | fclose(f); |
233 | 0 | return 3; |
234 | 0 | } |
235 | 36.0k | size = fread(tr->data_dictlist, 1, size, f); |
236 | 36.0k | fclose(f); |
237 | | |
238 | 36.0k | pw = (int *)(tr->data_dictlist); |
239 | 36.0k | length = Reverse4Bytes(pw[1]); |
240 | | |
241 | 36.0k | if (size <= (N_HASH_DICT + sizeof(int)*2)) { |
242 | 0 | fprintf(stderr, "Empty _dict file: '%s\n", fname); |
243 | 0 | return 2; |
244 | 0 | } |
245 | | |
246 | 36.0k | if ((Reverse4Bytes(pw[0]) != N_HASH_DICT) || |
247 | 36.0k | (length <= 0) || (length > 0x8000000)) { |
248 | 0 | fprintf(stderr, "Bad data: '%s' (%x length=%x)\n", fname, Reverse4Bytes(pw[0]), length); |
249 | 0 | return 2; |
250 | 0 | } |
251 | 36.0k | tr->data_dictrules = &(tr->data_dictlist[length]); |
252 | | |
253 | | // set up indices into data_dictrules |
254 | 36.0k | InitGroups(tr); |
255 | | |
256 | | // set up hash table for data_dictlist |
257 | 36.0k | p = &(tr->data_dictlist[8]); |
258 | | |
259 | 36.9M | for (hash = 0; hash < N_HASH_DICT; hash++) { |
260 | 36.9M | tr->dict_hashtab[hash] = p; |
261 | 565M | while ((length = *(uint8_t *)p) != 0) |
262 | 528M | p += length; |
263 | 36.9M | p++; // skip over the zero which terminates the list for this hash value |
264 | 36.9M | } |
265 | | |
266 | 36.0k | if ((tr->dict_min_size > 0) && (size < (unsigned int)tr->dict_min_size)) |
267 | 0 | fprintf(stderr, "Full dictionary is not installed for '%s'\n", name); |
268 | | |
269 | 36.0k | return 0; |
270 | 36.0k | } |
271 | | |
272 | | /* Generate a hash code from the specified string |
273 | | This is used to access the dictionary_2 word-lookup dictionary |
274 | | */ |
275 | | int HashDictionary(const char *string) |
276 | 6.29M | { |
277 | 6.29M | int c; |
278 | 6.29M | int chars = 0; |
279 | 6.29M | int hash = 0; |
280 | | |
281 | 21.4M | while ((c = (*string++ & 0xff)) != 0) { |
282 | 15.1M | hash = hash * 8 + c; |
283 | 15.1M | hash = (hash & 0x3ff) ^ (hash >> 8); // exclusive or |
284 | 15.1M | chars++; |
285 | 15.1M | } |
286 | | |
287 | 6.29M | return (hash+chars) & 0x3ff; // a 10 bit hash code |
288 | 6.29M | } |
289 | | |
290 | | /* Translate a phoneme string from ascii mnemonics to internal phoneme numbers, |
291 | | from 'p' up to next blank . |
292 | | Returns advanced 'p' |
293 | | outptr contains encoded phonemes, unrecognized phoneme stops the encoding |
294 | | bad_phoneme must point to char array of length 2 of more |
295 | | */ |
296 | | const char *EncodePhonemes(const char *p, char *outptr, int *bad_phoneme) |
297 | 134k | { |
298 | 134k | int ix; |
299 | 134k | unsigned char c; |
300 | 134k | int count; // num. of matching characters |
301 | 134k | int max; // highest num. of matching found so far |
302 | 134k | int max_ph; // corresponding phoneme with highest matching |
303 | 134k | int consumed; |
304 | 134k | unsigned int mnemonic_word; |
305 | | |
306 | 134k | if (bad_phoneme != NULL) |
307 | 98.0k | *bad_phoneme = 0; |
308 | | |
309 | | // skip initial blanks |
310 | 134k | while ((uint8_t)*p < 0x80 && isspace(*p)) |
311 | 6 | p++; |
312 | | |
313 | 408k | while (((c = *p) != 0) && !isspace(c)) { |
314 | 358k | consumed = 0; |
315 | | |
316 | 358k | switch (c) |
317 | 358k | { |
318 | 1.15k | case '|': |
319 | | // used to separate phoneme mnemonics if needed, to prevent characters being treated |
320 | | // as a multi-letter mnemonic |
321 | | |
322 | 1.15k | if ((c = p[1]) == '|') { |
323 | | // treat double || as a word-break symbol, drop through |
324 | | // to the default case with c = '|' |
325 | 994 | } else { |
326 | 160 | p++; |
327 | 160 | break; |
328 | 160 | } |
329 | 358k | default: |
330 | | // lookup the phoneme mnemonic, find the phoneme with the highest number of |
331 | | // matching characters |
332 | 358k | max = -1; |
333 | 358k | max_ph = 0; |
334 | | |
335 | 48.6M | for (ix = 1; ix < n_phoneme_tab; ix++) { |
336 | 48.3M | if (phoneme_tab[ix] == NULL) |
337 | 46 | continue; |
338 | 48.3M | if (phoneme_tab[ix]->type == phINVALID) |
339 | 0 | continue; // this phoneme is not defined for this language |
340 | | |
341 | 48.3M | count = 0; |
342 | 48.3M | mnemonic_word = phoneme_tab[ix]->mnemonic; |
343 | | |
344 | 49.4M | while (((c = p[count]) > ' ') && (count < 4) && |
345 | 49.1M | (c == ((mnemonic_word >> (count*8)) & 0xff))) |
346 | 1.07M | count++; |
347 | | |
348 | 48.3M | if ((count > max) && |
349 | 21.6M | ((count == 4) || (((mnemonic_word >> (count*8)) & 0xff) == 0))) { |
350 | 286k | max = count; |
351 | 286k | max_ph = phoneme_tab[ix]->code; |
352 | 286k | } |
353 | 48.3M | } |
354 | | |
355 | 358k | if (max_ph == 0) { |
356 | | // not recognised, report and ignore |
357 | 84.9k | if (bad_phoneme != NULL) |
358 | 84.9k | utf8_in(bad_phoneme, p); |
359 | 84.9k | *outptr++ = 0; |
360 | 84.9k | return p+1; |
361 | 84.9k | } |
362 | | |
363 | 273k | if (max <= 0) |
364 | 0 | max = 1; |
365 | 273k | p += (consumed + max); |
366 | 273k | *outptr++ = (char)(max_ph); |
367 | | |
368 | 273k | if (max_ph == phonSWITCH) { |
369 | | // Switch Language: this phoneme is followed by a text string |
370 | 403 | char *p_lang = outptr; |
371 | 2.49k | while (!isspace(c = *p) && (c != 0)) { |
372 | 2.09k | p++; |
373 | 2.09k | *outptr++ = tolower(c); |
374 | 2.09k | } |
375 | 403 | *outptr = 0; |
376 | 403 | if (c == 0) { |
377 | 0 | if (strcmp(p_lang, ESPEAKNG_DEFAULT_VOICE) == 0) { |
378 | 0 | *p_lang = 0; // don't need ESPEAKNG_DEFAULT_VOICE, it's assumed by default |
379 | 0 | return p; |
380 | 0 | } |
381 | 0 | } else |
382 | 403 | *outptr++ = '|'; // more phonemes follow, terminate language string with separator |
383 | 403 | } |
384 | 273k | break; |
385 | 358k | } |
386 | 358k | } |
387 | | // terminate the encoded string |
388 | 49.9k | *outptr = 0; |
389 | 49.9k | return p; |
390 | 134k | } |
391 | | |
392 | | void DecodePhonemes(const char *inptr, char *outptr) |
393 | 17.2k | { |
394 | | // Translate from internal phoneme codes into phoneme mnemonics |
395 | 17.2k | unsigned char phcode; |
396 | 17.2k | unsigned char c; |
397 | 17.2k | unsigned int mnem; |
398 | 17.2k | PHONEME_TAB *ph; |
399 | 17.2k | static const char stress_chars[] = "==,,'* "; |
400 | | |
401 | 17.2k | sprintf(outptr, "* "); |
402 | 182k | while ((phcode = *inptr++) > 0) { |
403 | 165k | if (phcode == 255) |
404 | 0 | continue; // indicates unrecognised phoneme |
405 | 165k | if ((ph = phoneme_tab[phcode]) == NULL) |
406 | 0 | continue; |
407 | | |
408 | 165k | if ((ph->type == phSTRESS) && (ph->std_length <= 4) && (ph->program == 0)) { |
409 | 31.4k | if (ph->std_length > 1) |
410 | 23.7k | *outptr++ = stress_chars[ph->std_length]; |
411 | 133k | } else { |
412 | 133k | mnem = ph->mnemonic; |
413 | | |
414 | 286k | while ((c = (mnem & 0xff)) != 0) { |
415 | 152k | *outptr++ = c; |
416 | 152k | mnem = mnem >> 8; |
417 | 152k | } |
418 | 133k | if (phcode == phonSWITCH) { |
419 | 178 | while (isalpha(*inptr)) |
420 | 178 | *outptr++ = *inptr++; |
421 | 178 | } |
422 | 133k | } |
423 | 165k | } |
424 | 17.2k | *outptr = 0; // string terminator |
425 | 17.2k | } |
426 | | |
427 | | // using Kirschenbaum to IPA translation, ascii 0x20 to 0x7f |
428 | | static const unsigned short ipa1[96] = { |
429 | | 0x20, 0x21, 0x22, 0x2b0, 0x24, 0x25, 0x0e6, 0x2c8, 0x28, 0x29, 0x27e, 0x2b, 0x2cc, 0x2d, 0x2e, 0x2f, |
430 | | 0x252, 0x31, 0x32, 0x25c, 0x34, 0x35, 0x36, 0x37, 0x275, 0x39, 0x2d0, 0x2b2, 0x3c, 0x3d, 0x3e, 0x294, |
431 | | 0x259, 0x251, 0x3b2, 0xe7, 0xf0, 0x25b, 0x46, 0x262, 0x127, 0x26a, 0x25f, 0x4b, 0x26b, 0x271, 0x14b, 0x254, |
432 | | 0x3a6, 0x263, 0x280, 0x283, 0x3b8, 0x28a, 0x28c, 0x153, 0x3c7, 0xf8, 0x292, 0x32a, 0x5c, 0x5d, 0x5e, 0x5f, |
433 | | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x261, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, |
434 | | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x303, 0x7f |
435 | | }; |
436 | | |
437 | 0 | #define N_PHON_OUT 500 // realloc increment |
438 | | static char *phon_out_buf = NULL; // passes the result of GetTranslatedPhonemeString() |
439 | | static unsigned int phon_out_size = 0; |
440 | | |
441 | | char *WritePhMnemonic(char *phon_out, PHONEME_TAB *ph, PHONEME_LIST *plist, int use_ipa, int *flags) |
442 | 0 | { |
443 | 0 | int c; |
444 | 0 | int mnem; |
445 | 0 | int len; |
446 | 0 | bool first; |
447 | 0 | int ix = 0; |
448 | 0 | char *p; |
449 | 0 | PHONEME_DATA phdata; |
450 | |
|
451 | 0 | if (ph->code == phonEND_WORD) { |
452 | | // ignore |
453 | 0 | phon_out[0] = 0; |
454 | 0 | return phon_out; |
455 | 0 | } |
456 | | |
457 | 0 | if (ph->code == phonSWITCH) { |
458 | | // the tone_ph field contains a phoneme table number |
459 | 0 | p = phoneme_tab_list[plist->tone_ph].name; |
460 | 0 | sprintf(phon_out, "(%s)", p); |
461 | 0 | return phon_out + strlen(phon_out); |
462 | 0 | } |
463 | | |
464 | 0 | if (use_ipa) { |
465 | | // has an ipa name been defined for this phoneme ? |
466 | 0 | phdata.ipa_string[0] = 0; |
467 | |
|
468 | 0 | if (plist == NULL) |
469 | 0 | InterpretPhoneme2(ph->code, &phdata); |
470 | 0 | else |
471 | 0 | InterpretPhoneme(NULL, 0, plist, phoneme_list, &phdata, NULL); |
472 | |
|
473 | 0 | p = phdata.ipa_string; |
474 | 0 | if (*p == 0x20) { |
475 | | // indicates no name for this phoneme |
476 | 0 | *phon_out = 0; |
477 | 0 | return phon_out; |
478 | 0 | } |
479 | 0 | if ((*p != 0) && ((*p & 0xff) < 0x20)) { |
480 | | // name starts with a flags byte |
481 | 0 | if (flags != NULL) |
482 | 0 | *flags = *p; |
483 | 0 | p++; |
484 | 0 | } |
485 | |
|
486 | 0 | len = strlen(p); |
487 | 0 | if (len > 0) { |
488 | 0 | strcpy(phon_out, p); |
489 | 0 | phon_out += len; |
490 | 0 | *phon_out = 0; |
491 | 0 | return phon_out; |
492 | 0 | } |
493 | 0 | } |
494 | | |
495 | 0 | first = true; |
496 | 0 | for (mnem = ph->mnemonic; (c = mnem & 0xff) != 0; mnem = mnem >> 8) { |
497 | 0 | if (c == '/') |
498 | 0 | break; // discard phoneme variant indicator |
499 | | |
500 | 0 | if (use_ipa) { |
501 | | // convert from ascii to ipa |
502 | 0 | if (first && (c == '_')) |
503 | 0 | break; // don't show pause phonemes |
504 | | |
505 | 0 | if ((c == '#') && (ph->type == phVOWEL)) |
506 | 0 | break; // # is subscript-h, but only for consonants |
507 | | |
508 | | // ignore digits after the first character |
509 | 0 | if (!first && IsDigit09(c)) |
510 | 0 | continue; |
511 | | |
512 | 0 | if ((c >= 0x20) && (c < 128)) |
513 | 0 | c = ipa1[c-0x20]; |
514 | |
|
515 | 0 | ix += utf8_out(c, &phon_out[ix]); |
516 | 0 | } else |
517 | 0 | phon_out[ix++] = c; |
518 | 0 | first = false; |
519 | 0 | } |
520 | |
|
521 | 0 | phon_out = &phon_out[ix]; |
522 | 0 | *phon_out = 0; |
523 | 0 | return phon_out; |
524 | 0 | } |
525 | | |
526 | | //// Extension: write phone mnemonic with stress |
527 | 0 | char *WritePhMnemonicWithStress(char *phon_out, PHONEME_TAB *ph, PHONEME_LIST *plist, int use_ipa, int *flags) { |
528 | 0 | if (plist->synthflags & SFLAG_SYLLABLE) { |
529 | 0 | unsigned char stress = plist->stresslevel; |
530 | |
|
531 | 0 | if (stress > 1) { |
532 | 0 | int c = 0; |
533 | |
|
534 | 0 | if (stress > STRESS_IS_PRIORITY) { |
535 | 0 | stress = STRESS_IS_PRIORITY; |
536 | 0 | } |
537 | |
|
538 | 0 | if (use_ipa) { |
539 | 0 | c = 0x2cc; // ipa, secondary stress |
540 | |
|
541 | 0 | if (stress > STRESS_IS_SECONDARY) { |
542 | 0 | c = 0x02c8; // ipa, primary stress |
543 | 0 | } |
544 | 0 | } else { |
545 | 0 | const char stress_chars[] = "==,,''"; |
546 | |
|
547 | 0 | c = stress_chars[stress]; |
548 | 0 | } |
549 | |
|
550 | 0 | if (c != 0) { |
551 | 0 | phon_out += utf8_out(c, phon_out); |
552 | 0 | } |
553 | 0 | } |
554 | 0 | } |
555 | |
|
556 | 0 | return WritePhMnemonic(phon_out, ph, plist, use_ipa, flags); |
557 | 0 | } |
558 | | //// |
559 | | |
560 | | const char *GetTranslatedPhonemeString(int phoneme_mode) |
561 | 0 | { |
562 | | /* Called after a clause has been translated into phonemes, in order |
563 | | to display the clause in phoneme mnemonic form. |
564 | | |
565 | | phoneme_mode |
566 | | bit 1: use IPA phoneme names |
567 | | bit 7: use tie between letters in multi-character phoneme names |
568 | | bits 8-23 tie or separator character |
569 | | |
570 | | */ |
571 | |
|
572 | 0 | int ix; |
573 | 0 | unsigned int len; |
574 | 0 | int phon_out_ix = 0; |
575 | 0 | int stress; |
576 | 0 | int c; |
577 | 0 | char *p; |
578 | 0 | char *buf; |
579 | 0 | int count; |
580 | 0 | int flags; |
581 | 0 | int use_ipa; |
582 | 0 | int use_tie; |
583 | 0 | int separate_phonemes; |
584 | 0 | char phon_buf[30]; |
585 | 0 | char phon_buf2[30]; |
586 | 0 | PHONEME_LIST *plist; |
587 | |
|
588 | 0 | static const char stress_chars[] = "==,,''"; |
589 | |
|
590 | 0 | if (phon_out_buf == NULL) { |
591 | 0 | phon_out_size = N_PHON_OUT; |
592 | 0 | if ((phon_out_buf = (char *)malloc(phon_out_size)) == NULL) { |
593 | 0 | phon_out_size = 0; |
594 | 0 | return ""; |
595 | 0 | } |
596 | 0 | } |
597 | | |
598 | 0 | use_ipa = phoneme_mode & espeakPHONEMES_IPA; |
599 | 0 | if (phoneme_mode & espeakPHONEMES_TIE) { |
600 | 0 | use_tie = phoneme_mode >> 8; |
601 | 0 | separate_phonemes = 0; |
602 | 0 | } else { |
603 | 0 | separate_phonemes = phoneme_mode >> 8; |
604 | 0 | use_tie = 0; |
605 | 0 | } |
606 | |
|
607 | 0 | for (ix = 1; ix < (n_phoneme_list-2); ix++) { |
608 | 0 | buf = phon_buf; |
609 | |
|
610 | 0 | plist = &phoneme_list[ix]; |
611 | |
|
612 | 0 | WritePhMnemonic(phon_buf2, plist->ph, plist, use_ipa, &flags); |
613 | 0 | if (plist->newword & PHLIST_START_OF_WORD && !(plist->newword & (PHLIST_START_OF_SENTENCE | PHLIST_START_OF_CLAUSE))) |
614 | 0 | *buf++ = ' '; |
615 | |
|
616 | 0 | if ((!plist->newword) || (separate_phonemes == ' ')) { |
617 | 0 | if ((separate_phonemes != 0) && (ix > 1)) { |
618 | 0 | utf8_in(&c, phon_buf2); |
619 | 0 | if ((c < 0x2b0) || (c > 0x36f)) // not if the phoneme starts with a superscript letter |
620 | 0 | buf += utf8_out(separate_phonemes, buf); |
621 | 0 | } |
622 | 0 | } |
623 | |
|
624 | 0 | if (plist->synthflags & SFLAG_SYLLABLE) { |
625 | 0 | if ((stress = plist->stresslevel) > 1) { |
626 | 0 | c = 0; |
627 | 0 | if (stress > STRESS_IS_PRIORITY) stress = STRESS_IS_PRIORITY; |
628 | |
|
629 | 0 | if (use_ipa) { |
630 | 0 | c = 0x2cc; // ipa, secondary stress |
631 | 0 | if (stress > STRESS_IS_SECONDARY) |
632 | 0 | c = 0x02c8; // ipa, primary stress |
633 | 0 | } else |
634 | 0 | c = stress_chars[stress]; |
635 | |
|
636 | 0 | if (c != 0) |
637 | 0 | buf += utf8_out(c, buf); |
638 | 0 | } |
639 | 0 | } |
640 | |
|
641 | 0 | flags = 0; |
642 | 0 | count = 0; |
643 | 0 | for (p = phon_buf2; *p != 0;) { |
644 | 0 | p += utf8_in(&c, p); |
645 | 0 | if (use_tie != 0) { |
646 | | // look for non-initial alphabetic character, but not diacritic, superscript etc. |
647 | 0 | if ((count > 0) && !(flags & (1 << (count-1))) && ((c < 0x2b0) || (c > 0x36f)) && iswalpha(c)) |
648 | 0 | buf += utf8_out(use_tie, buf); |
649 | 0 | } |
650 | 0 | buf += utf8_out(c, buf); |
651 | 0 | count++; |
652 | 0 | } |
653 | |
|
654 | 0 | if (plist->ph->code != phonSWITCH) { |
655 | 0 | if (plist->synthflags & SFLAG_LENGTHEN) |
656 | 0 | buf = WritePhMnemonic(buf, phoneme_tab[phonLENGTHEN], plist, use_ipa, NULL); |
657 | 0 | if ((plist->synthflags & SFLAG_SYLLABLE) && (plist->type != phVOWEL)) { |
658 | | // syllablic consonant |
659 | 0 | buf = WritePhMnemonic(buf, phoneme_tab[phonSYLLABIC], plist, use_ipa, NULL); |
660 | 0 | } |
661 | 0 | if (plist->tone_ph > 0) { |
662 | 0 | PHONEME_TAB *tone_ph = TonePhoneme(plist); |
663 | 0 | if (tone_ph != NULL) |
664 | 0 | buf = WritePhMnemonic(buf, tone_ph, plist, use_ipa, NULL); |
665 | 0 | } |
666 | 0 | } |
667 | |
|
668 | 0 | len = buf - phon_buf; |
669 | 0 | if ((phon_out_ix + len) >= phon_out_size) { |
670 | | // enlarge the phoneme buffer |
671 | 0 | phon_out_size = phon_out_ix + len + N_PHON_OUT; |
672 | 0 | char *new_phon_out_buf = (char *)realloc(phon_out_buf, phon_out_size); |
673 | 0 | if (new_phon_out_buf == NULL) { |
674 | 0 | phon_out_size = 0; |
675 | 0 | return ""; |
676 | 0 | } else |
677 | 0 | phon_out_buf = new_phon_out_buf; |
678 | 0 | } |
679 | | |
680 | 0 | phon_buf[len] = 0; |
681 | 0 | strcpy(&phon_out_buf[phon_out_ix], phon_buf); |
682 | 0 | phon_out_ix += len; |
683 | 0 | } |
684 | | |
685 | 0 | if (!phon_out_buf) |
686 | 0 | return ""; |
687 | | |
688 | 0 | phon_out_buf[phon_out_ix] = 0; |
689 | |
|
690 | 0 | return phon_out_buf; |
691 | 0 | } |
692 | | |
693 | | static int LetterGroupNo(char *rule) |
694 | 8.03M | { |
695 | | /* |
696 | | * Returns number of letter group |
697 | | */ |
698 | 8.03M | int groupNo = *rule; |
699 | 8.03M | groupNo = groupNo - 'A'; // subtracting 'A' makes letter_group equal to number in .Lxx definition |
700 | 8.03M | if (groupNo < 0) // fix sign if necessary |
701 | 2.49k | groupNo += 256; |
702 | 8.03M | return groupNo; |
703 | 8.03M | } |
704 | | |
705 | | static int IsLetterGroup(Translator *tr, char *word, int group, int pre) |
706 | 454k | { |
707 | | /* Match the word against a list of utf-8 strings. |
708 | | * returns length of matching letter group or -1 |
709 | | * |
710 | | * How this works: |
711 | | * |
712 | | * +-+ |
713 | | * |c|<-(tr->letterGroups[group]) |
714 | | * |0| |
715 | | * *p->|c|<-len+ +-+ |
716 | | * |s|<----+ |a|<-(Actual word to be tested) |
717 | | * |0| *word-> |t|<-*w=word-len+1 (for pre-rule) |
718 | | * |~| |a|<-*w=word (for post-rule) |
719 | | * |7| |s| |
720 | | * +-+ +-+ |
721 | | * |
722 | | * 7=RULE_GROUP_END |
723 | | * 0=null terminator |
724 | | * pre==1 — pre-rule |
725 | | * pre==0 — post-rule |
726 | | */ |
727 | 454k | char *p; // group counter |
728 | 454k | char *w; // word counter |
729 | 454k | int len = 0, i; |
730 | | |
731 | 454k | p = tr->letterGroups[group]; |
732 | 454k | if (p == NULL) |
733 | 0 | return -1; |
734 | | |
735 | 4.13M | while (*p != RULE_GROUP_END) { |
736 | | // If '~' (no character) is allowed in group, return 0. |
737 | 3.76M | if (*p == '~') |
738 | 16.7k | return 0; |
739 | | |
740 | 3.75M | if (pre) { |
741 | 1.33M | len = strlen(p); |
742 | 1.33M | w = word; |
743 | 1.33M | if (*w == 0) |
744 | 2.79k | goto skip; |
745 | 2.16M | for (i = 0; i < len-1; i++) |
746 | 864k | { |
747 | 864k | w--; |
748 | 864k | if (*w == 0) |
749 | | // Not found, skip the rest of this group. |
750 | 28.3k | goto skip; |
751 | 864k | } |
752 | 1.33M | } else |
753 | 2.41M | w = word; |
754 | | |
755 | | // Check current group |
756 | 3.95M | while ((*p == *w) && (*w != 0)) { |
757 | 238k | w++; |
758 | 238k | p++; |
759 | 238k | } |
760 | 3.71M | if (*p == 0) { // Matched the current group. |
761 | 67.1k | if (pre) |
762 | 19.7k | return len; |
763 | 47.3k | return w - word; |
764 | 67.1k | } |
765 | | |
766 | | // No match, so skip the rest of this group. |
767 | 3.68M | skip: |
768 | 9.68M | while (*p++ != 0) |
769 | 6.00M | ; |
770 | 3.68M | } |
771 | | // Not found |
772 | 370k | return -1; |
773 | 454k | } |
774 | | |
775 | | static int IsLetter(Translator *tr, int letter, int group) |
776 | 19.3M | { |
777 | 19.3M | int letter2; |
778 | | |
779 | 19.3M | if (tr->letter_groups[group] != NULL) { |
780 | 396k | if (wcschr(tr->letter_groups[group], letter)) |
781 | 142k | return 1; |
782 | 253k | return 0; |
783 | 396k | } |
784 | | |
785 | 18.9M | if (group > 7) |
786 | 0 | return 0; |
787 | | |
788 | 18.9M | if (tr->letter_bits_offset > 0) { |
789 | 45.5k | if (((letter2 = (letter - tr->letter_bits_offset)) > 0) && (letter2 < 0x100)) |
790 | 32.2k | letter = letter2; |
791 | 13.2k | else |
792 | 13.2k | return 0; |
793 | 18.9M | } else if ((letter >= 0xc0) && (letter < N_REMOVE_ACCENT)) |
794 | 29.9k | return tr->letter_bits[remove_accent[letter-0xc0]] & (1L << group); |
795 | | |
796 | 18.9M | if ((letter >= 0) && (letter < 0x100)) |
797 | 18.8M | return tr->letter_bits[letter] & (1L << group); |
798 | | |
799 | 67.9k | return 0; |
800 | 18.9M | } |
801 | | |
802 | | int IsVowel(Translator *tr, int letter) |
803 | 9.95M | { |
804 | 9.95M | return IsLetter(tr, letter, LETTERGP_VOWEL2); |
805 | 9.95M | } |
806 | | |
807 | | int GetVowelStress(Translator *tr, unsigned char *phonemes, signed char *vowel_stress, int *vowel_count, int *stressed_syllable, int control) |
808 | 960k | { |
809 | | // control = 1, set stress to 1 for forced unstressed vowels |
810 | 960k | unsigned char phcode; |
811 | 960k | PHONEME_TAB *ph; |
812 | 960k | unsigned char *ph_out = phonemes; |
813 | 960k | int count = 1; |
814 | 960k | int max_stress = -1; |
815 | 960k | int ix; |
816 | 960k | int j; |
817 | 960k | int stress = -1; |
818 | 960k | int primary_posn = 0; |
819 | | |
820 | 960k | vowel_stress[0] = STRESS_IS_UNSTRESSED; |
821 | 6.68M | while (((phcode = *phonemes++) != 0) && (count < (N_WORD_PHONEMES/2)-1)) { |
822 | 5.72M | if ((ph = phoneme_tab[phcode]) == NULL) |
823 | 18 | continue; |
824 | | |
825 | 5.72M | if ((ph->type == phSTRESS) && (ph->program == 0)) { |
826 | | // stress marker, use this for the following vowel |
827 | | |
828 | 552k | if (phcode == phonSTRESS_PREV) { |
829 | | // primary stress on preceding vowel |
830 | 6.56k | j = count - 1; |
831 | 6.57k | while ((j > 0) && (*stressed_syllable == 0) && (vowel_stress[j] < STRESS_IS_PRIMARY)) { |
832 | 2.73k | if ((vowel_stress[j] != STRESS_IS_DIMINISHED) && (vowel_stress[j] != STRESS_IS_UNSTRESSED)) { |
833 | | // don't promote a phoneme which must be unstressed |
834 | 2.72k | vowel_stress[j] = STRESS_IS_PRIMARY; |
835 | | |
836 | 2.72k | if (max_stress < STRESS_IS_PRIMARY) { |
837 | 2.37k | max_stress = STRESS_IS_PRIMARY; |
838 | 2.37k | primary_posn = j; |
839 | 2.37k | } |
840 | | |
841 | | /* reduce any preceding primary stress markers */ |
842 | 4.46k | for (ix = 1; ix < j; ix++) { |
843 | 1.74k | if (vowel_stress[ix] == STRESS_IS_PRIMARY) |
844 | 293 | vowel_stress[ix] = STRESS_IS_SECONDARY; |
845 | 1.74k | } |
846 | 2.72k | break; |
847 | 2.72k | } |
848 | 6 | j--; |
849 | 6 | } |
850 | 545k | } else { |
851 | 545k | if ((ph->std_length < 4) || (*stressed_syllable == 0)) { |
852 | 545k | stress = ph->std_length; |
853 | | |
854 | 545k | if (stress > max_stress) |
855 | 345k | max_stress = stress; |
856 | 545k | } |
857 | 545k | } |
858 | 552k | continue; |
859 | 552k | } |
860 | | |
861 | 5.17M | if ((ph->type == phVOWEL) && !(ph->phflags & phNONSYLLABIC)) { |
862 | 1.99M | vowel_stress[count] = (char)stress; |
863 | 1.99M | if ((stress >= STRESS_IS_PRIMARY) && (stress >= max_stress)) { |
864 | 476k | primary_posn = count; |
865 | 476k | max_stress = stress; |
866 | 476k | } |
867 | | |
868 | 1.99M | if ((stress < 0) && (control & 1) && (ph->phflags & phUNSTRESSED)) |
869 | 130k | vowel_stress[count] = STRESS_IS_UNSTRESSED; // weak vowel, must be unstressed |
870 | | |
871 | 1.99M | count++; |
872 | 1.99M | stress = -1; |
873 | 3.18M | } else if (phcode == phonSYLLABIC) { |
874 | | // previous consonant phoneme is syllablic |
875 | 1.16k | vowel_stress[count] = (char)stress; |
876 | 1.16k | if ((stress < 0) && (control & 1)) |
877 | 956 | vowel_stress[count] = STRESS_IS_UNSTRESSED; // syllabic consonant, usually unstressed |
878 | 1.16k | count++; |
879 | 1.16k | } |
880 | | |
881 | 5.17M | *ph_out++ = phcode; |
882 | 5.17M | } |
883 | 960k | vowel_stress[count] = STRESS_IS_UNSTRESSED; |
884 | 960k | *ph_out = 0; |
885 | | |
886 | | // has the position of the primary stress been specified by $1, $2, etc? |
887 | 960k | if (*stressed_syllable > 0) { |
888 | 1.08k | if (*stressed_syllable >= count) |
889 | 0 | *stressed_syllable = count-1; // the final syllable |
890 | | |
891 | 1.08k | vowel_stress[*stressed_syllable] = STRESS_IS_PRIMARY; |
892 | 1.08k | max_stress = STRESS_IS_PRIMARY; |
893 | 1.08k | primary_posn = *stressed_syllable; |
894 | 1.08k | } |
895 | | |
896 | 960k | if (max_stress == STRESS_IS_PRIORITY) { |
897 | | // priority stress, replaces any other primary stress marker |
898 | 1.77k | for (ix = 1; ix < count; ix++) { |
899 | 1.35k | if (vowel_stress[ix] == STRESS_IS_PRIMARY) { |
900 | 105 | if (tr->langopts.stress_flags & S_PRIORITY_STRESS) |
901 | 58 | vowel_stress[ix] = STRESS_IS_UNSTRESSED; |
902 | 47 | else |
903 | 47 | vowel_stress[ix] = STRESS_IS_SECONDARY; |
904 | 105 | } |
905 | | |
906 | 1.35k | if (vowel_stress[ix] == STRESS_IS_PRIORITY) { |
907 | 421 | vowel_stress[ix] = STRESS_IS_PRIMARY; |
908 | 421 | primary_posn = ix; |
909 | 421 | } |
910 | 1.35k | } |
911 | 421 | max_stress = STRESS_IS_PRIMARY; |
912 | 421 | } |
913 | | |
914 | 960k | *stressed_syllable = primary_posn; |
915 | 960k | *vowel_count = count; |
916 | 960k | return max_stress; |
917 | 960k | } |
918 | | |
919 | | const char stress_phonemes[] = { |
920 | | phonSTRESS_D, phonSTRESS_U, phonSTRESS_2, phonSTRESS_3, |
921 | | phonSTRESS_P, phonSTRESS_P2, phonSTRESS_TONIC |
922 | | }; |
923 | | |
924 | | void SetWordStress(Translator *tr, char *output, unsigned int *dictionary_flags, int tonic, int control) |
925 | 1.25M | { |
926 | | /* Guess stress pattern of word. This is language specific |
927 | | |
928 | | 'output' is used for input and output |
929 | | |
930 | | 'dictionary_flags' has bits 0-3 position of stressed vowel (if > 0) |
931 | | or unstressed (if == 7) or syllables 1 and 2 (if == 6) |
932 | | bits 8... dictionary flags |
933 | | |
934 | | If 'tonic' is set (>= 0), replace highest stress by this value. |
935 | | |
936 | | control: bit 0 This is an individual symbol, not a word |
937 | | bit 1 Suffix phonemes are still to be added |
938 | | */ |
939 | | |
940 | 1.25M | unsigned char phcode; |
941 | 1.25M | unsigned char *p; |
942 | 1.25M | PHONEME_TAB *ph; |
943 | 1.25M | int stress; |
944 | 1.25M | int max_stress; |
945 | 1.25M | int max_stress_input; // any stress specified in the input? |
946 | 1.25M | int vowel_count; // num of vowels + 1 |
947 | 1.25M | int ix; |
948 | 1.25M | int v; |
949 | 1.25M | int v_stress; |
950 | 1.25M | int stressed_syllable; // position of stressed syllable |
951 | 1.25M | int max_stress_posn; |
952 | 1.25M | char *max_output; |
953 | 1.25M | int final_ph; |
954 | 1.25M | int final_ph2; |
955 | 1.25M | int mnem; |
956 | 1.25M | int opt_length; |
957 | 1.25M | int stressflags; |
958 | 1.25M | int dflags = 0; |
959 | 1.25M | int first_primary; |
960 | 1.25M | int long_vowel; |
961 | | |
962 | 1.25M | signed char vowel_stress[N_WORD_PHONEMES/2]; |
963 | 1.25M | char syllable_weight[N_WORD_PHONEMES/2]; |
964 | 1.25M | char vowel_length[N_WORD_PHONEMES/2]; |
965 | 1.25M | unsigned char phonetic[N_WORD_PHONEMES]; |
966 | | |
967 | 1.25M | static const char consonant_types[16] = { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 }; |
968 | | |
969 | 1.25M | memset(syllable_weight, 0, sizeof(syllable_weight)); |
970 | 1.25M | memset(vowel_length, 0, sizeof(vowel_length)); |
971 | | |
972 | 1.25M | stressflags = tr->langopts.stress_flags; |
973 | | |
974 | 1.25M | if (dictionary_flags != NULL) |
975 | 1.25M | dflags = dictionary_flags[0]; |
976 | | |
977 | | // copy input string into internal buffer |
978 | 6.74M | for (ix = 0; ix < N_WORD_PHONEMES; ix++) { |
979 | 6.74M | phonetic[ix] = output[ix]; |
980 | 6.74M | if (phonetic[ix] == 0) |
981 | 1.25M | break; |
982 | | // check for unknown phoneme codes. A code may be out of range, or it may |
983 | | // fall in a gap of the current table, in which case phoneme_tab holds NULL |
984 | | // for it (see SelectPhonemeTable). The code below dereferences these |
985 | | // entries unconditionally, so both cases must be substituted here. |
986 | 5.48M | if ((phonetic[ix] >= n_phoneme_tab) || (phoneme_tab[phonetic[ix]] == NULL)) |
987 | 18.2k | phonetic[ix] = phonSCHWA; |
988 | 5.48M | } |
989 | 1.25M | if (ix == 0) return; |
990 | 918k | final_ph = phonetic[ix-1]; |
991 | 918k | final_ph2 = phonetic[(ix > 1) ? ix-2 : ix-1]; |
992 | | |
993 | 918k | max_output = output + (N_WORD_PHONEMES-3); // check for overrun |
994 | | |
995 | | |
996 | | // any stress position marked in the xx_list dictionary ? |
997 | 918k | bool unstressed_word = false; |
998 | 918k | stressed_syllable = dflags & 0x7; |
999 | 918k | if (dflags & 0x8) { |
1000 | | // this indicates a word without a primary stress |
1001 | 15.4k | stressed_syllable = dflags & 0x3; |
1002 | 15.4k | unstressed_word = true; |
1003 | 15.4k | } |
1004 | | |
1005 | 918k | max_stress = max_stress_input = GetVowelStress(tr, phonetic, vowel_stress, &vowel_count, &stressed_syllable, 1); |
1006 | 918k | if ((max_stress < 0) && dictionary_flags) |
1007 | 610k | max_stress = STRESS_IS_DIMINISHED; |
1008 | | |
1009 | | // heavy or light syllables |
1010 | 918k | ix = 1; |
1011 | 5.81M | for (p = phonetic; *p != 0; p++) { |
1012 | 4.89M | if ((phoneme_tab[p[0]]->type == phVOWEL) && !(phoneme_tab[p[0]]->phflags & phNONSYLLABIC)) { |
1013 | 1.92M | int weight = 0; |
1014 | 1.92M | bool lengthened = false; |
1015 | | |
1016 | 1.92M | if (phoneme_tab[p[1]]->code == phonLENGTHEN) |
1017 | 85.3k | lengthened = true; |
1018 | | |
1019 | 1.92M | if (lengthened || (phoneme_tab[p[0]]->phflags & phLONG)) { |
1020 | | // long vowel, increase syllable weight |
1021 | 109k | weight++; |
1022 | 109k | } |
1023 | 1.92M | vowel_length[ix] = weight; |
1024 | | |
1025 | 1.92M | if (lengthened) p++; // advance over phonLENGTHEN |
1026 | | |
1027 | 1.92M | if (consonant_types[phoneme_tab[p[1]]->type] && ((phoneme_tab[p[2]]->type != phVOWEL) || (phoneme_tab[p[1]]->phflags & phLONG))) { |
1028 | | // followed by two consonants, a long consonant, or consonant and end-of-word |
1029 | 754k | weight++; |
1030 | 754k | } |
1031 | 1.92M | syllable_weight[ix] = weight; |
1032 | 1.92M | ix++; |
1033 | 1.92M | } |
1034 | 4.89M | } |
1035 | | |
1036 | 918k | switch (tr->langopts.stress_rule) |
1037 | 918k | { |
1038 | 3.05k | case STRESSPOSN_2LLH: |
1039 | | // stress on first syllable, unless it is a light syllable followed by a heavy syllable |
1040 | 3.05k | if ((syllable_weight[1] > 0) || (syllable_weight[2] == 0)) |
1041 | 2.65k | break; |
1042 | | // fallthrough: |
1043 | 398 | case STRESSPOSN_2L: |
1044 | | // stress on second syllable |
1045 | 398 | if ((stressed_syllable == 0) && (vowel_count > 2)) { |
1046 | 17 | stressed_syllable = 2; |
1047 | 17 | if (max_stress == STRESS_IS_DIMINISHED) |
1048 | 17 | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1049 | 17 | max_stress = STRESS_IS_PRIMARY; |
1050 | 17 | } |
1051 | 398 | break; |
1052 | | |
1053 | 114k | case STRESSPOSN_2R: |
1054 | | // a language with stress on penultimate vowel |
1055 | | |
1056 | 114k | if (stressed_syllable == 0) { |
1057 | | // no explicit stress - stress the penultimate vowel |
1058 | 65.4k | max_stress = STRESS_IS_PRIMARY; |
1059 | | |
1060 | 65.4k | if (vowel_count > 2) { |
1061 | 21.8k | stressed_syllable = vowel_count - 2; |
1062 | | |
1063 | 21.8k | if (stressflags & S_FINAL_SPANISH) { |
1064 | | // LANG=Spanish, stress on last vowel if the word ends in a consonant other than 'n' or 's' |
1065 | 6.16k | if (phoneme_tab[final_ph]->type != phVOWEL) { |
1066 | 1.58k | mnem = phoneme_tab[final_ph]->mnemonic; |
1067 | | |
1068 | 1.58k | if ((tr->translator_name == L('a', 'n')) || (tr->translator_name == L('c', 'a'))) { |
1069 | 99 | if (((mnem != 's') && (mnem != 'n')) || phoneme_tab[final_ph2]->type != phVOWEL) |
1070 | 79 | stressed_syllable = vowel_count - 1; // stress on last syllable |
1071 | 1.48k | } else if (tr->translator_name == L('i', 'a')) { |
1072 | 0 | if ((mnem != 's') || phoneme_tab[final_ph2]->type != phVOWEL) |
1073 | 0 | stressed_syllable = vowel_count - 1; // stress on last syllable |
1074 | 1.48k | } else { |
1075 | 1.48k | if ((mnem == 's') && (phoneme_tab[final_ph2]->type == phNASAL)) { |
1076 | | // -ns stress remains on penultimate syllable |
1077 | 1.47k | } else if (((phoneme_tab[final_ph]->type != phNASAL) && (mnem != 's')) || (phoneme_tab[final_ph2]->type != phVOWEL)) |
1078 | 505 | stressed_syllable = vowel_count - 1; |
1079 | 1.48k | } |
1080 | 1.58k | } |
1081 | 6.16k | } |
1082 | | |
1083 | 21.8k | if (stressflags & S_FINAL_LONG) { |
1084 | | // stress on last syllable if it has a long vowel, but previous syllable has a short vowel |
1085 | 3.91k | if (vowel_length[vowel_count - 1] > vowel_length[vowel_count - 2]) |
1086 | 788 | stressed_syllable = vowel_count - 1; |
1087 | 3.91k | } |
1088 | | |
1089 | 21.8k | if ((vowel_stress[stressed_syllable] == STRESS_IS_DIMINISHED) || (vowel_stress[stressed_syllable] == STRESS_IS_UNSTRESSED)) { |
1090 | | // but this vowel is explicitly marked as unstressed |
1091 | 511 | if (stressed_syllable > 1) |
1092 | 297 | stressed_syllable--; |
1093 | 214 | else |
1094 | 214 | stressed_syllable++; |
1095 | 511 | } |
1096 | 21.8k | } else |
1097 | 43.5k | stressed_syllable = 1; |
1098 | | |
1099 | | // only set the stress if it's not already marked explicitly |
1100 | 65.4k | if (vowel_stress[stressed_syllable] < 0) { |
1101 | | // don't stress if next and prev syllables are stressed |
1102 | 62.8k | if ((vowel_stress[stressed_syllable-1] < STRESS_IS_PRIMARY) || (vowel_stress[stressed_syllable+1] < STRESS_IS_PRIMARY)) |
1103 | 62.8k | vowel_stress[stressed_syllable] = max_stress; |
1104 | 62.8k | } |
1105 | 65.4k | } |
1106 | 114k | break; |
1107 | 82.2k | case STRESSPOSN_1R: |
1108 | | // stress on last vowel |
1109 | 82.2k | if (stressed_syllable == 0) { |
1110 | | // no explicit stress - stress the final vowel |
1111 | 36.7k | stressed_syllable = vowel_count - 1; |
1112 | | |
1113 | 37.3k | while (stressed_syllable > 0) { |
1114 | | // find the last vowel which is not unstressed |
1115 | 36.9k | if (vowel_stress[stressed_syllable] < STRESS_IS_DIMINISHED) { |
1116 | 36.3k | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1117 | 36.3k | break; |
1118 | 36.3k | } else |
1119 | 629 | stressed_syllable--; |
1120 | 36.9k | } |
1121 | 36.7k | max_stress = STRESS_IS_PRIMARY; |
1122 | 36.7k | } |
1123 | 82.2k | break; |
1124 | 28.8k | case STRESSPOSN_3R: // stress on antipenultimate vowel |
1125 | 28.8k | if (stressed_syllable == 0) { |
1126 | 16.3k | stressed_syllable = vowel_count - 3; |
1127 | 16.3k | if (stressed_syllable < 1) |
1128 | 14.2k | stressed_syllable = 1; |
1129 | | |
1130 | 16.3k | if (max_stress == STRESS_IS_DIMINISHED) |
1131 | 16.3k | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1132 | 16.3k | max_stress = STRESS_IS_PRIMARY; |
1133 | 16.3k | } |
1134 | 28.8k | break; |
1135 | 16.3k | case STRESSPOSN_SYLCOUNT: |
1136 | | // LANG=Russian |
1137 | 16.3k | if (stressed_syllable == 0) { |
1138 | | // no explicit stress - guess the stress from the number of syllables |
1139 | 813 | static const char guess_ru[16] = { 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, 10, 11 }; |
1140 | 813 | static const char guess_ru_v[16] = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, 10 }; // for final phoneme is a vowel |
1141 | 813 | static const char guess_ru_t[16] = { 0, 0, 1, 2, 3, 3, 3, 4, 5, 6, 7, 7, 7, 8, 9, 10 }; // for final phoneme is an unvoiced stop |
1142 | | |
1143 | 813 | stressed_syllable = vowel_count - 3; |
1144 | 813 | if (vowel_count < 16) { |
1145 | 813 | if (phoneme_tab[final_ph]->type == phVOWEL) |
1146 | 261 | stressed_syllable = guess_ru_v[vowel_count]; |
1147 | 552 | else if (phoneme_tab[final_ph]->type == phSTOP) |
1148 | 158 | stressed_syllable = guess_ru_t[vowel_count]; |
1149 | 394 | else |
1150 | 394 | stressed_syllable = guess_ru[vowel_count]; |
1151 | 813 | } |
1152 | 813 | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1153 | 813 | max_stress = STRESS_IS_PRIMARY; |
1154 | 813 | } |
1155 | 16.3k | break; |
1156 | 6.66k | case STRESSPOSN_1RH: // LANG=hi stress on the last heaviest syllable |
1157 | 6.66k | if (stressed_syllable == 0) { |
1158 | 2.65k | int wt; |
1159 | 2.65k | int max_weight = -1; |
1160 | | |
1161 | | // find the heaviest syllable, excluding the final syllable |
1162 | 4.31k | for (ix = 1; ix < (vowel_count-1); ix++) { |
1163 | 1.66k | if (vowel_stress[ix] < STRESS_IS_DIMINISHED) { |
1164 | 1.50k | if ((wt = syllable_weight[ix]) >= max_weight) { |
1165 | 1.41k | max_weight = wt; |
1166 | 1.41k | stressed_syllable = ix; |
1167 | 1.41k | } |
1168 | 1.50k | } |
1169 | 1.66k | } |
1170 | | |
1171 | 2.65k | if ((syllable_weight[vowel_count-1] == 2) && (max_weight < 2)) { |
1172 | | // the only double=heavy syllable is the final syllable, so stress this |
1173 | 255 | stressed_syllable = vowel_count-1; |
1174 | 2.39k | } else if (max_weight <= 0) { |
1175 | | // all syllables, exclusing the last, are light. Stress the first syllable |
1176 | 1.90k | stressed_syllable = 1; |
1177 | 1.90k | } |
1178 | | |
1179 | 2.65k | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1180 | 2.65k | max_stress = STRESS_IS_PRIMARY; |
1181 | 2.65k | } |
1182 | 6.66k | break; |
1183 | 2.36k | case STRESSPOSN_1RU : // LANG=tr, the last syllable for any vowel marked explicitly as unstressed |
1184 | 2.36k | if (stressed_syllable == 0) { |
1185 | 1.08k | stressed_syllable = vowel_count - 1; |
1186 | 2.10k | for (ix = 1; ix < vowel_count; ix++) { |
1187 | 1.04k | if (vowel_stress[ix] == STRESS_IS_UNSTRESSED) { |
1188 | 26 | stressed_syllable = ix-1; |
1189 | 26 | break; |
1190 | 26 | } |
1191 | 1.04k | } |
1192 | 1.08k | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1193 | 1.08k | max_stress = STRESS_IS_PRIMARY; |
1194 | 1.08k | } |
1195 | 2.36k | break; |
1196 | 0 | case STRESSPOSN_ALL: // mark all as stressed |
1197 | 0 | for (ix = 1; ix < vowel_count; ix++) { |
1198 | 0 | if (vowel_stress[ix] < STRESS_IS_DIMINISHED) |
1199 | 0 | vowel_stress[ix] = STRESS_IS_PRIMARY; |
1200 | 0 | } |
1201 | 0 | break; |
1202 | 13.7k | case STRESSPOSN_GREENLANDIC: // LANG=kl (Greenlandic) |
1203 | 13.7k | long_vowel = 0; |
1204 | 29.7k | for (ix = 1; ix < vowel_count; ix++) { |
1205 | 16.0k | if (vowel_stress[ix] == STRESS_IS_PRIMARY) |
1206 | 4.82k | vowel_stress[ix] = STRESS_IS_SECONDARY; // change marked stress (consonant clusters) to secondary (except the last) |
1207 | | |
1208 | 16.0k | if (vowel_length[ix] > 0) { |
1209 | 709 | long_vowel = ix; |
1210 | 709 | vowel_stress[ix] = STRESS_IS_SECONDARY; // give secondary stress to all long vowels |
1211 | 709 | } |
1212 | 16.0k | } |
1213 | | |
1214 | | // 'stressed_syllable' gives the last marked stress |
1215 | 13.7k | if (stressed_syllable == 0) { |
1216 | | // no marked stress, choose the last long vowel |
1217 | 9.68k | if (long_vowel > 0) |
1218 | 261 | stressed_syllable = long_vowel; |
1219 | 9.41k | else { |
1220 | | // no long vowels or consonant clusters |
1221 | 9.41k | if (vowel_count > 5) |
1222 | 0 | stressed_syllable = vowel_count - 3; // more than 4 syllables |
1223 | 9.41k | else |
1224 | 9.41k | stressed_syllable = vowel_count - 1; |
1225 | 9.41k | } |
1226 | 9.68k | } |
1227 | 13.7k | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1228 | 13.7k | max_stress = STRESS_IS_PRIMARY; |
1229 | 13.7k | break; |
1230 | 11.0k | case STRESSPOSN_1SL: // LANG=ml, 1st unless 1st vowel is short and 2nd is long |
1231 | 11.0k | if (stressed_syllable == 0) { |
1232 | 6.31k | stressed_syllable = 1; |
1233 | 6.31k | if ((vowel_length[1] == 0) && (vowel_count > 2) && (vowel_length[2] > 0)) |
1234 | 493 | stressed_syllable = 2; |
1235 | 6.31k | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1236 | 6.31k | max_stress = STRESS_IS_PRIMARY; |
1237 | 6.31k | } |
1238 | 11.0k | break; |
1239 | | |
1240 | 19.7k | case STRESSPOSN_EU: // LANG=eu. If more than 2 syllables: primary stress in second syllable and secondary on last. |
1241 | 19.7k | if ((stressed_syllable == 0) && (vowel_count > 2)) { |
1242 | 2.24k | for (ix = 1; ix < vowel_count; ix++) { |
1243 | 1.73k | vowel_stress[ix] = STRESS_IS_DIMINISHED; |
1244 | 1.73k | } |
1245 | 508 | stressed_syllable = 2; |
1246 | 508 | if (max_stress == STRESS_IS_DIMINISHED) |
1247 | 496 | vowel_stress[stressed_syllable] = STRESS_IS_PRIMARY; |
1248 | 508 | max_stress = STRESS_IS_PRIMARY; |
1249 | 508 | if (vowel_count > 3) { |
1250 | 358 | vowel_stress[vowel_count - 1] = STRESS_IS_SECONDARY; |
1251 | 358 | } |
1252 | 508 | } |
1253 | 19.7k | break; |
1254 | 918k | } |
1255 | | |
1256 | 918k | if ((stressflags & S_FINAL_VOWEL_UNSTRESSED) && ((control & 2) == 0) && (vowel_count > 2) && (max_stress_input < STRESS_IS_SECONDARY) && (vowel_stress[vowel_count - 1] == STRESS_IS_PRIMARY)) { |
1257 | | // Don't allow stress on a word-final vowel |
1258 | | // Only do this if there is no suffix phonemes to be added, and if a stress position was not given explicitly |
1259 | 1.58k | if (phoneme_tab[final_ph]->type == phVOWEL) { |
1260 | 1.07k | vowel_stress[vowel_count - 1] = STRESS_IS_UNSTRESSED; |
1261 | 1.07k | vowel_stress[vowel_count - 2] = STRESS_IS_PRIMARY; |
1262 | 1.07k | } |
1263 | 1.58k | } |
1264 | | |
1265 | | // now guess the complete stress pattern |
1266 | 918k | if (max_stress < STRESS_IS_PRIMARY) |
1267 | 485k | stress = STRESS_IS_PRIMARY; // no primary stress marked, use for 1st syllable |
1268 | 432k | else |
1269 | 432k | stress = STRESS_IS_SECONDARY; |
1270 | | |
1271 | 918k | if (unstressed_word == false) { |
1272 | 903k | if ((stressflags & S_2_SYL_2) && (vowel_count == 3)) { |
1273 | | // Two syllable word, if one syllable has primary stress, then give the other secondary stress |
1274 | 0 | if (vowel_stress[1] == STRESS_IS_PRIMARY) |
1275 | 0 | vowel_stress[2] = STRESS_IS_SECONDARY; |
1276 | 0 | if (vowel_stress[2] == STRESS_IS_PRIMARY) |
1277 | 0 | vowel_stress[1] = STRESS_IS_SECONDARY; |
1278 | 0 | } |
1279 | | |
1280 | 903k | if ((stressflags & S_INITIAL_2) && (vowel_stress[1] < STRESS_IS_DIMINISHED)) { |
1281 | | // If there is only one syllable before the primary stress, give it a secondary stress |
1282 | 1.53k | if ((vowel_count > 3) && (vowel_stress[2] >= STRESS_IS_PRIMARY)) |
1283 | 569 | vowel_stress[1] = STRESS_IS_SECONDARY; |
1284 | 1.53k | } |
1285 | 903k | } |
1286 | | |
1287 | 918k | bool done = false; |
1288 | 918k | first_primary = 0; |
1289 | 2.84M | for (v = 1; v < vowel_count; v++) { |
1290 | 1.92M | if (vowel_stress[v] < STRESS_IS_DIMINISHED) { |
1291 | 1.16M | if ((stressflags & S_FINAL_NO_2) && (stress < STRESS_IS_PRIMARY) && (v == vowel_count-1)) { |
1292 | | // flag: don't give secondary stress to final vowel |
1293 | 1.08M | } else if ((stressflags & 0x8000) && (done == false)) { |
1294 | 57.4k | vowel_stress[v] = (char)stress; |
1295 | 57.4k | done = true; |
1296 | 57.4k | stress = STRESS_IS_SECONDARY; // use secondary stress for remaining syllables |
1297 | 1.02M | } else if ((vowel_stress[v-1] <= STRESS_IS_UNSTRESSED) && ((vowel_stress[v+1] <= STRESS_IS_UNSTRESSED) || ((stress == STRESS_IS_PRIMARY) && (vowel_stress[v+1] <= STRESS_IS_NOT_STRESSED)))) { |
1298 | | // trochaic: give stress to vowel surrounded by unstressed vowels |
1299 | | |
1300 | 606k | if ((stress == STRESS_IS_SECONDARY) && (stressflags & S_NO_AUTO_2)) |
1301 | 48.0k | continue; // don't use secondary stress |
1302 | | |
1303 | | // don't put secondary stress on a light syllable if the rest of the word (excluding last syllable) contains a heavy syllable |
1304 | 558k | if ((v > 1) && (stressflags & S_2_TO_HEAVY) && (syllable_weight[v] == 0)) { |
1305 | 226 | bool skip = false; |
1306 | 530 | for (int i = v; i < vowel_count - 1; i++) { |
1307 | 322 | if (syllable_weight[i] > 0) { |
1308 | 18 | skip = true; |
1309 | 18 | break; |
1310 | 18 | } |
1311 | 322 | } |
1312 | 226 | if (skip == true) |
1313 | 18 | continue; |
1314 | 226 | } |
1315 | | |
1316 | 558k | if ((v > 1) && (stressflags & S_2_TO_HEAVY) && (syllable_weight[v] == 0) && (syllable_weight[v+1] > 0)) { |
1317 | | // don't put secondary stress on a light syllable which is followed by a heavy syllable |
1318 | 2 | continue; |
1319 | 2 | } |
1320 | | |
1321 | | // should start with secondary stress on the first syllable, or should it count back from |
1322 | | // the primary stress and put secondary stress on alternate syllables? |
1323 | 558k | vowel_stress[v] = (char)stress; |
1324 | 558k | done = true; |
1325 | 558k | stress = STRESS_IS_SECONDARY; // use secondary stress for remaining syllables |
1326 | 558k | } |
1327 | 1.16M | } |
1328 | | |
1329 | 1.88M | if (vowel_stress[v] >= STRESS_IS_PRIMARY) { |
1330 | 1.04M | if (first_primary == 0) |
1331 | 893k | first_primary = v; |
1332 | 155k | else if (stressflags & S_FIRST_PRIMARY) { |
1333 | | // reduce primary stresses after the first to secondary |
1334 | 3.75k | vowel_stress[v] = STRESS_IS_SECONDARY; |
1335 | 3.75k | } |
1336 | 1.04M | } |
1337 | 1.88M | } |
1338 | | |
1339 | 918k | if ((unstressed_word) && (tonic < 0)) { |
1340 | 15.4k | if (vowel_count <= 2) |
1341 | 14.7k | tonic = tr->langopts.unstressed_wd1; // monosyllable - unstressed |
1342 | 617 | else |
1343 | 617 | tonic = tr->langopts.unstressed_wd2; // more than one syllable, used secondary stress as the main stress |
1344 | 15.4k | } |
1345 | | |
1346 | 918k | max_stress = STRESS_IS_DIMINISHED; |
1347 | 918k | max_stress_posn = 0; |
1348 | 2.84M | for (v = 1; v < vowel_count; v++) { |
1349 | 1.92M | if (vowel_stress[v] >= max_stress) { |
1350 | 1.11M | max_stress = vowel_stress[v]; |
1351 | 1.11M | max_stress_posn = v; |
1352 | 1.11M | } |
1353 | 1.92M | } |
1354 | | |
1355 | 918k | if (tonic >= 0) { |
1356 | | // find position of highest stress, and replace it by 'tonic' |
1357 | | |
1358 | | // don't disturb an explicitly set stress by 'unstress-at-end' flag |
1359 | 15.6k | if ((tonic > max_stress) || (max_stress <= STRESS_IS_PRIMARY)) |
1360 | 15.6k | vowel_stress[max_stress_posn] = (char)tonic; |
1361 | 15.6k | max_stress = tonic; |
1362 | 15.6k | } |
1363 | | |
1364 | | // produce output phoneme string |
1365 | 918k | p = phonetic; |
1366 | 918k | v = 1; |
1367 | | |
1368 | 918k | if (!(control & 1) && ((ph = phoneme_tab[*p]) != NULL)) { |
1369 | 682k | while ((ph->type == phSTRESS) || (*p == phonEND_WORD)) { |
1370 | 82.9k | p++; |
1371 | 82.9k | ph = phoneme_tab[p[0]]; |
1372 | 82.9k | } |
1373 | | |
1374 | 600k | if ((tr->langopts.vowel_pause & 0x30) && (ph->type == phVOWEL)) { |
1375 | | // word starts with a vowel |
1376 | | |
1377 | 22.5k | if ((tr->langopts.vowel_pause & 0x20) && (vowel_stress[1] >= STRESS_IS_PRIMARY)) |
1378 | 20.1k | *output++ = phonPAUSE_NOLINK; // not to be replaced by link |
1379 | 2.37k | else |
1380 | 2.37k | *output++ = phonPAUSE_VSHORT; // break, but no pause |
1381 | 22.5k | } |
1382 | 600k | } |
1383 | | |
1384 | 918k | p = phonetic; |
1385 | | /* Note: v progression has to strictly follow the vowel_stress production in GetVowelStress */ |
1386 | 5.86M | while (((phcode = *p++) != 0) && (output < max_output)) { |
1387 | 4.94M | if ((ph = phoneme_tab[phcode]) == NULL) |
1388 | 0 | continue; |
1389 | | |
1390 | 4.94M | if (ph->type == phPAUSE) |
1391 | 395k | tr->prev_last_stress = 0; |
1392 | 4.54M | else if (((ph->type == phVOWEL) && !(ph->phflags & phNONSYLLABIC)) || (*p == phonSYLLABIC)) { |
1393 | | // a vowel, or a consonant followed by a syllabic consonant marker |
1394 | | |
1395 | 1.91M | assert(v <= vowel_count); |
1396 | | |
1397 | 1.91M | v_stress = vowel_stress[v]; |
1398 | 1.91M | tr->prev_last_stress = v_stress; |
1399 | | |
1400 | 1.91M | if (v_stress <= STRESS_IS_UNSTRESSED) { |
1401 | 699k | if ((v > 1) && (max_stress >= 2) && (stressflags & S_FINAL_DIM) && (v == (vowel_count-1))) { |
1402 | | // option: mark unstressed final syllable as diminished |
1403 | 105k | v_stress = STRESS_IS_DIMINISHED; |
1404 | 593k | } else if ((stressflags & S_NO_DIM) || (v == 1) || (v == (vowel_count-1))) { |
1405 | | // first or last syllable, or option 'don't set diminished stress' |
1406 | 333k | v_stress = STRESS_IS_UNSTRESSED; |
1407 | 333k | } else if ((v == (vowel_count-2)) && (vowel_stress[vowel_count-1] <= STRESS_IS_UNSTRESSED)) { |
1408 | | // penultimate syllable, followed by an unstressed final syllable |
1409 | 23.2k | v_stress = STRESS_IS_UNSTRESSED; |
1410 | 237k | } else { |
1411 | | // unstressed syllable within a word |
1412 | 237k | if ((vowel_stress[v-1] < STRESS_IS_DIMINISHED) || ((stressflags & S_MID_DIM) == 0)) { |
1413 | 234k | v_stress = STRESS_IS_DIMINISHED; |
1414 | 234k | vowel_stress[v] = v_stress; |
1415 | 234k | } |
1416 | 237k | } |
1417 | 699k | } |
1418 | | |
1419 | 1.91M | if ((v_stress == STRESS_IS_DIMINISHED) || (v_stress > STRESS_IS_UNSTRESSED)) |
1420 | 1.55M | *output++ = stress_phonemes[v_stress]; // mark stress of all vowels except 1 (unstressed) |
1421 | | |
1422 | 1.91M | if (vowel_stress[v] > max_stress) |
1423 | 0 | max_stress = vowel_stress[v]; |
1424 | | |
1425 | 1.91M | if ((*p == phonLENGTHEN) && ((opt_length = tr->langopts.param[LOPT_IT_LENGTHEN]) & 1)) { |
1426 | | // remove lengthen indicator from non-stressed syllables |
1427 | 17.4k | bool shorten = false; |
1428 | | |
1429 | 17.4k | if (opt_length & 0x10) { |
1430 | | // only allow lengthen indicator on the highest stress syllable in the word |
1431 | 0 | if (v != max_stress_posn) |
1432 | 0 | shorten = true; |
1433 | 17.4k | } else if (v_stress < STRESS_IS_PRIMARY) { |
1434 | | // only allow lengthen indicator if stress >= STRESS_IS_PRIMARY. |
1435 | 2.09k | shorten = true; |
1436 | 2.09k | } |
1437 | | |
1438 | 17.4k | if (shorten) |
1439 | 2.09k | p++; |
1440 | 17.4k | } |
1441 | 1.91M | v++; |
1442 | 1.91M | } |
1443 | | |
1444 | 4.94M | if (phcode != 1) |
1445 | 4.94M | *output++ = phcode; |
1446 | 4.94M | } |
1447 | 918k | *output++ = 0; |
1448 | | |
1449 | 918k | return; |
1450 | 918k | } |
1451 | | |
1452 | | void AppendPhonemes(Translator *tr, char *string, int size, const char *ph) |
1453 | 1.24M | { |
1454 | | /* Add new phoneme string "ph" to "string" |
1455 | | Keeps count of the number of vowel phonemes in the word, and whether these |
1456 | | can be stressed syllables. These values can be used in translation rules |
1457 | | */ |
1458 | | |
1459 | 1.24M | const char *p; |
1460 | 1.24M | unsigned char c; |
1461 | 1.24M | int length; |
1462 | | |
1463 | 1.24M | length = strlen(ph) + strlen(string); |
1464 | 1.24M | if (length >= size) |
1465 | 277k | return; |
1466 | | |
1467 | | // any stressable vowel ? |
1468 | 1.24M | bool unstress_mark = false; |
1469 | 965k | p = ph; |
1470 | 2.74M | while ((c = *p++) != 0) { |
1471 | 1.78M | if (c >= n_phoneme_tab) continue; |
1472 | | |
1473 | 1.77M | if (!phoneme_tab[c]) continue; |
1474 | | |
1475 | 1.77M | if (phoneme_tab[c]->type == phSTRESS) { |
1476 | 145k | if (phoneme_tab[c]->std_length < 4) |
1477 | 52.6k | unstress_mark = true; |
1478 | 1.63M | } else { |
1479 | 1.63M | if (phoneme_tab[c]->type == phVOWEL) { |
1480 | 703k | if (((phoneme_tab[c]->phflags & phUNSTRESSED) == 0) && |
1481 | 643k | (unstress_mark == false)) { |
1482 | 617k | tr->word_stressed_count++; |
1483 | 617k | } |
1484 | 703k | unstress_mark = false; |
1485 | 703k | tr->word_vowel_count++; |
1486 | 703k | } |
1487 | 1.63M | } |
1488 | 1.77M | } |
1489 | | |
1490 | 965k | if (string != NULL) |
1491 | 965k | strcat(string, ph); |
1492 | 965k | } |
1493 | | |
1494 | | static void MatchRule(Translator *tr, char *word[], char *word_start, int group_length, char *rule, MatchRecord *match_out, int word_flags, int dict_flags) |
1495 | 2.59M | { |
1496 | | /* Checks a specified word against dictionary rules. |
1497 | | Returns with phoneme code string, or NULL if no match found. |
1498 | | |
1499 | | word (indirect) points to current character group within the input word |
1500 | | This is advanced by this procedure as characters are consumed |
1501 | | |
1502 | | group: the initial characters used to choose the rules group |
1503 | | |
1504 | | rule: address of dictionary rule data for this character group |
1505 | | |
1506 | | match_out: returns best points score |
1507 | | |
1508 | | word_flags: indicates whether this is a retranslation after a suffix has been removed |
1509 | | */ |
1510 | | |
1511 | 2.59M | unsigned char rb; // current instuction from rule |
1512 | 2.59M | unsigned char letter; // current letter from input word, single byte |
1513 | 2.59M | int letter_w; // current letter, wide character |
1514 | 2.59M | int last_letter_w; // last letter, wide character |
1515 | 2.59M | int letter_xbytes; // number of extra bytes of multibyte character (num bytes - 1) |
1516 | | |
1517 | 2.59M | char *pre_ptr; |
1518 | 2.59M | char *post_ptr; // pointer to first character after group |
1519 | | |
1520 | 2.59M | char *rule_start; // start of current match template |
1521 | 2.59M | char *p; |
1522 | 2.59M | int match_type; // left, right, or consume |
1523 | 2.59M | int syllable_count; |
1524 | 2.59M | int vowel; |
1525 | 2.59M | int letter_group; |
1526 | 2.59M | int lg_pts; |
1527 | 2.59M | int n_bytes; |
1528 | 2.59M | int add_points; |
1529 | 2.59M | int command; |
1530 | | |
1531 | 2.59M | MatchRecord match; |
1532 | 2.59M | MatchRecord best; |
1533 | | |
1534 | 2.59M | int total_consumed; // letters consumed for best match |
1535 | | |
1536 | 2.59M | unsigned char condition_num; |
1537 | 2.59M | char *common_phonemes; // common to a group of entries |
1538 | 2.59M | char *group_chars; |
1539 | 2.59M | char word_buf[N_WORD_BYTES]; |
1540 | | |
1541 | 2.59M | group_chars = *word; |
1542 | | |
1543 | 2.59M | if (rule == NULL) { |
1544 | 113k | match_out->points = 0; |
1545 | 113k | (*word)++; |
1546 | 113k | return; |
1547 | 113k | } |
1548 | | |
1549 | 2.48M | total_consumed = 0; |
1550 | 2.48M | common_phonemes = NULL; |
1551 | | |
1552 | 2.48M | best.points = 0; |
1553 | 2.48M | best.phonemes = ""; |
1554 | 2.48M | best.end_type = 0; |
1555 | 2.48M | best.del_fwd = NULL; |
1556 | | |
1557 | | // search through dictionary rules |
1558 | 118M | while (rule[0] != RULE_GROUP_END) { |
1559 | 115M | bool check_atstart = false; |
1560 | 115M | int consumed = 0; // number of letters consumed from input |
1561 | 115M | int distance_left = -2; |
1562 | 115M | int distance_right = -6; // used to reduce points for matches further away the current letter |
1563 | 115M | int failed = 0; |
1564 | 115M | int unpron_ignore = word_flags & FLAG_UNPRON_TEST; |
1565 | | |
1566 | 115M | match_type = 0; |
1567 | 115M | letter_w = 0; |
1568 | | |
1569 | 115M | match.points = 1; |
1570 | 115M | match.end_type = 0; |
1571 | 115M | match.del_fwd = NULL; |
1572 | | |
1573 | 115M | pre_ptr = *word; |
1574 | 115M | post_ptr = *word + group_length; |
1575 | | |
1576 | | // work through next rule until end, or until no-match proved |
1577 | 115M | rule_start = rule; |
1578 | | |
1579 | 320M | while (!failed) { |
1580 | 204M | rb = *rule++; |
1581 | 204M | add_points = 0; |
1582 | | |
1583 | 204M | if (rb <= RULE_LINENUM) { |
1584 | 79.5M | switch (rb) |
1585 | 79.5M | { |
1586 | 473k | case 0: // no phoneme string for this rule, use previous common rule |
1587 | 473k | if (common_phonemes != NULL) { |
1588 | 473k | match.phonemes = common_phonemes; |
1589 | 2.38M | while (((rb = *match.phonemes++) != 0) && (rb != RULE_PHONEMES)) { |
1590 | 1.91M | if (rb == RULE_CONDITION) |
1591 | 446 | match.phonemes++; // skip over condition number |
1592 | 1.91M | if (rb == RULE_LINENUM) |
1593 | 0 | match.phonemes += 2; // skip over line number |
1594 | 1.91M | } |
1595 | 473k | } else |
1596 | 0 | match.phonemes = ""; |
1597 | 473k | rule--; // so we are still pointing at the 0 |
1598 | 473k | failed = 2; // matched OK |
1599 | 473k | break; |
1600 | 10.1M | case RULE_PRE_ATSTART: // pre rule with implied 'start of word' |
1601 | 10.1M | check_atstart = true; |
1602 | 10.1M | unpron_ignore = 0; |
1603 | 10.1M | match_type = RULE_PRE; |
1604 | 10.1M | break; |
1605 | 29.5M | case RULE_PRE: |
1606 | 29.5M | match_type = RULE_PRE; |
1607 | 29.5M | if (word_flags & FLAG_UNPRON_TEST) { |
1608 | | // checking the start of the word for unpronouncable character sequences, only |
1609 | | // consider rules which explicitly match the start of a word |
1610 | | // Note: Those rules now use RULE_PRE_ATSTART |
1611 | 426k | failed = 1; |
1612 | 426k | } |
1613 | 29.5M | break; |
1614 | 22.9M | case RULE_POST: |
1615 | 22.9M | match_type = RULE_POST; |
1616 | 22.9M | break; |
1617 | 1.36M | case RULE_PHONEMES: |
1618 | 1.36M | match.phonemes = rule; |
1619 | 1.36M | failed = 2; // matched OK |
1620 | 1.36M | break; |
1621 | 14.5M | case RULE_PH_COMMON: |
1622 | 14.5M | common_phonemes = rule; |
1623 | 14.5M | break; |
1624 | 600k | case RULE_CONDITION: |
1625 | | // conditional rule, next byte gives condition number |
1626 | 600k | condition_num = *rule++; |
1627 | | |
1628 | 600k | if (condition_num >= 32) { |
1629 | | // allow the rule only if the condition number is NOT set |
1630 | 77.0k | if ((tr->dict_condition & (1L << (condition_num-32))) != 0) |
1631 | 75 | failed = 1; |
1632 | 523k | } else { |
1633 | | // allow the rule only if the condition number is set |
1634 | 523k | if ((tr->dict_condition & (1L << condition_num)) == 0) |
1635 | 437k | failed = 1; |
1636 | 523k | } |
1637 | | |
1638 | 600k | if (!failed) |
1639 | 163k | match.points++; // add one point for a matched conditional rule |
1640 | 600k | break; |
1641 | 0 | case RULE_LINENUM: |
1642 | 0 | rule += 2; |
1643 | 0 | break; |
1644 | 79.5M | } |
1645 | 79.5M | continue; |
1646 | 79.5M | } |
1647 | | |
1648 | 124M | switch (match_type) |
1649 | 124M | { |
1650 | 65.4M | case 0: |
1651 | | // match and consume this letter |
1652 | 65.4M | letter = *post_ptr++; |
1653 | | |
1654 | 65.4M | if ((letter == rb) || ((letter == (unsigned char)REPLACED_E) && (rb == 'e'))) { |
1655 | 3.01M | if ((letter & 0xc0) != 0x80) |
1656 | 2.72M | add_points = 21; // don't add point for non-initial UTF-8 bytes |
1657 | 3.01M | consumed++; |
1658 | 3.01M | } else |
1659 | 62.4M | failed = 1; |
1660 | 65.4M | break; |
1661 | 25.0M | case RULE_POST: |
1662 | | // continue moving forwards |
1663 | 25.0M | distance_right += 6; |
1664 | 25.0M | if (distance_right > 18) |
1665 | 32.0k | distance_right = 19; |
1666 | 25.0M | last_letter_w = letter_w; |
1667 | 25.0M | if (!post_ptr[-1]) { |
1668 | | // we had already reached the end of text! |
1669 | | // reading after that does not make sense, that cannot match |
1670 | 42 | failed = 1; |
1671 | 42 | break; |
1672 | 42 | } |
1673 | 25.0M | letter_xbytes = utf8_in(&letter_w, post_ptr)-1; |
1674 | 25.0M | letter = *post_ptr++; |
1675 | | |
1676 | 25.0M | switch (rb) |
1677 | 25.0M | { |
1678 | 5.19M | case RULE_LETTERGP: |
1679 | 5.19M | letter_group = LetterGroupNo(rule++); |
1680 | 5.19M | if (IsLetter(tr, letter_w, letter_group)) { |
1681 | 1.02M | lg_pts = 20; |
1682 | 1.02M | if (letter_group == 2) |
1683 | 533k | lg_pts = 19; // fewer points for C, general consonant |
1684 | 1.02M | add_points = (lg_pts-distance_right); |
1685 | 1.02M | post_ptr += letter_xbytes; |
1686 | 1.02M | } else |
1687 | 4.16M | failed = 1; |
1688 | 5.19M | break; |
1689 | 230k | case RULE_LETTERGP2: // match against a list of utf-8 strings |
1690 | 230k | letter_group = LetterGroupNo(rule++); |
1691 | 230k | if ((n_bytes = IsLetterGroup(tr, post_ptr-1, letter_group, 0)) >= 0) { |
1692 | 43.5k | add_points = (20-distance_right); |
1693 | | // move pointer, if group was found |
1694 | 43.5k | post_ptr += (n_bytes-1); |
1695 | 43.5k | } else |
1696 | 186k | failed = 1; |
1697 | 230k | break; |
1698 | 11.0k | case RULE_NOTVOWEL: |
1699 | 11.0k | if (IsLetter(tr, letter_w, 0) || ((letter_w == ' ') && (word_flags & FLAG_SUFFIX_VOWEL))) |
1700 | 4.97k | failed = 1; |
1701 | 6.12k | else { |
1702 | 6.12k | add_points = (20-distance_right); |
1703 | 6.12k | post_ptr += letter_xbytes; |
1704 | 6.12k | } |
1705 | 11.0k | break; |
1706 | 11.6k | case RULE_DIGIT: |
1707 | 11.6k | if (IsDigit(letter_w)) { |
1708 | 3.17k | add_points = (20-distance_right); |
1709 | 3.17k | post_ptr += letter_xbytes; |
1710 | 8.51k | } else if (tr->langopts.tone_numbers) { |
1711 | | // also match if there is no digit |
1712 | 10 | add_points = (20-distance_right); |
1713 | 10 | post_ptr--; |
1714 | 10 | } else |
1715 | 8.50k | failed = 1; |
1716 | 11.6k | break; |
1717 | 0 | case RULE_NONALPHA: |
1718 | 0 | if (!iswalpha(letter_w)) { |
1719 | 0 | add_points = (21-distance_right); |
1720 | 0 | post_ptr += letter_xbytes; |
1721 | 0 | } else |
1722 | 0 | failed = 1; |
1723 | 0 | break; |
1724 | 13.0k | case RULE_DOUBLE: |
1725 | 13.0k | if (letter_w == last_letter_w) { |
1726 | 1.41k | add_points = (21-distance_right); |
1727 | 1.41k | post_ptr += letter_xbytes; |
1728 | 1.41k | } else |
1729 | 11.6k | failed = 1; |
1730 | 13.0k | break; |
1731 | 114k | case RULE_DOLLAR: |
1732 | 114k | post_ptr--; |
1733 | 114k | command = *rule++; |
1734 | 114k | if (command == DOLLAR_UNPR) |
1735 | 4.58k | match.end_type = SUFX_UNPRON; // $unpron |
1736 | 110k | else if (command == DOLLAR_NOPREFIX) { // $noprefix |
1737 | 370 | if (word_flags & FLAG_PREFIX_REMOVED) |
1738 | 0 | failed = 1; // a prefix has been removed |
1739 | 370 | else |
1740 | 370 | add_points = 1; |
1741 | 109k | } else if ((command & 0xf0) == 0x10) { |
1742 | | // $w_alt |
1743 | 109k | if (dict_flags & (1 << (BITNUM_FLAG_ALT + (command & 0xf)))) |
1744 | 241 | add_points = 23; |
1745 | 109k | else |
1746 | 109k | failed = 1; |
1747 | 109k | } else if (((command & 0xf0) == 0x20) || (command == DOLLAR_LIST)) { |
1748 | 665 | DollarRule(word, word_start, consumed, group_length, word_buf, tr, command, &failed, &add_points); |
1749 | 665 | } |
1750 | | |
1751 | 114k | break; |
1752 | 290k | case '-': |
1753 | 290k | if ((letter == '-') || ((letter == ' ') && (word_flags & FLAG_HYPHEN_AFTER))) |
1754 | 443 | add_points = (22-distance_right); // one point more than match against space |
1755 | 290k | else |
1756 | 290k | failed = 1; |
1757 | 290k | break; |
1758 | 49.3k | case RULE_SYLLABLE: |
1759 | 49.3k | { |
1760 | | // more than specified number of vowel letters to the right |
1761 | 49.3k | char *p = post_ptr + letter_xbytes; |
1762 | 49.3k | int vowel_count = 0; |
1763 | | |
1764 | 49.3k | syllable_count = 1; |
1765 | 54.2k | while (*rule == RULE_SYLLABLE) { |
1766 | 4.91k | rule++; |
1767 | 4.91k | syllable_count += 1; // number of syllables to match |
1768 | 4.91k | } |
1769 | 49.3k | vowel = 0; |
1770 | 306k | while (letter_w != RULE_SPACE && letter_w != 0) { |
1771 | 257k | if ((vowel == 0) && IsLetter(tr, letter_w, LETTERGP_VOWEL2)) { |
1772 | | // this is counting vowels which are separated by non-vowel letters |
1773 | 59.5k | vowel_count++; |
1774 | 59.5k | } |
1775 | 257k | vowel = IsLetter(tr, letter_w, LETTERGP_VOWEL2); |
1776 | 257k | p += utf8_in(&letter_w, p); |
1777 | 257k | } |
1778 | 49.3k | if (syllable_count <= vowel_count) |
1779 | 29.8k | add_points = (18+syllable_count-distance_right); |
1780 | 19.5k | else |
1781 | 19.5k | failed = 1; |
1782 | 49.3k | } |
1783 | 49.3k | break; |
1784 | 122k | case RULE_NOVOWELS: |
1785 | 122k | { |
1786 | 122k | char *p = post_ptr + letter_xbytes; |
1787 | 273k | while (letter_w != RULE_SPACE && letter_w != 0) { |
1788 | 267k | if (IsLetter(tr, letter_w, LETTERGP_VOWEL2)) { |
1789 | 115k | failed = 1; |
1790 | 115k | break; |
1791 | 115k | } |
1792 | 151k | p += utf8_in(&letter_w, p); |
1793 | 151k | } |
1794 | 122k | if (!failed) |
1795 | 6.33k | add_points = (19-distance_right); |
1796 | 122k | } |
1797 | 122k | break; |
1798 | 11.5k | case RULE_SKIPCHARS: |
1799 | 11.5k | { |
1800 | | // '(Jxy' means 'skip characters until xy' |
1801 | 11.5k | char *p = post_ptr - 1; // to allow empty jump (without letter between), go one back |
1802 | 11.5k | char *p2 = p; // pointer to the previous character in the word |
1803 | 11.5k | int rule_w; // first wide character of skip rule |
1804 | 11.5k | utf8_in(&rule_w, rule); |
1805 | 11.5k | int g_bytes = -1; // bytes of successfully found character group |
1806 | 63.9k | while ((letter_w != rule_w) && (letter_w != RULE_SPACE) && (letter_w != 0) && (g_bytes == -1)) { |
1807 | 52.4k | if (rule_w == RULE_LETTERGP2) |
1808 | 39.1k | g_bytes = IsLetterGroup(tr, p, LetterGroupNo(rule + 1), 0); |
1809 | 52.4k | p2 = p; |
1810 | 52.4k | p += utf8_in(&letter_w, p); |
1811 | 52.4k | } |
1812 | 11.5k | if ((letter_w == rule_w) || (g_bytes >= 0)) |
1813 | 5.45k | post_ptr = p2; |
1814 | 11.5k | } |
1815 | 11.5k | break; |
1816 | 10.8k | case RULE_INC_SCORE: |
1817 | 10.8k | post_ptr--; |
1818 | 10.8k | add_points = 20; // force an increase in points |
1819 | 10.8k | break; |
1820 | 1.02k | case RULE_DEC_SCORE: |
1821 | 1.02k | post_ptr--; |
1822 | 1.02k | add_points = -20; // force an decrease in points |
1823 | 1.02k | break; |
1824 | 5.79k | case RULE_DEL_FWD: |
1825 | | // find the next 'e' in the word and replace by 'E' |
1826 | 11.5k | for (p = *word + group_length; p < post_ptr; p++) { |
1827 | 11.4k | if (*p == 'e') { |
1828 | 5.78k | match.del_fwd = p; |
1829 | 5.78k | break; |
1830 | 5.78k | } |
1831 | 11.4k | } |
1832 | 5.79k | break; |
1833 | 33.2k | case RULE_ENDING: |
1834 | 33.2k | { |
1835 | 33.2k | int end_type; |
1836 | | // next 3 bytes are a (non-zero) ending type. 2 bytes of flags + suffix length |
1837 | 33.2k | end_type = (rule[0] << 16) + ((rule[1] & 0x7f) << 8) + (rule[2] & 0x7f); |
1838 | | |
1839 | 33.2k | if ((tr->word_vowel_count == 0) && !(end_type & SUFX_P) && (tr->langopts.param[LOPT_SUFFIX] & 1)) |
1840 | 633 | failed = 1; // don't match a suffix rule if there are no previous syllables (needed for lang=tr). |
1841 | 32.6k | else { |
1842 | 32.6k | match.end_type = end_type; |
1843 | 32.6k | rule += 3; |
1844 | 32.6k | } |
1845 | 33.2k | } |
1846 | 33.2k | break; |
1847 | 10.4k | case RULE_NO_SUFFIX: |
1848 | 10.4k | if (word_flags & FLAG_SUFFIX_REMOVED) |
1849 | 109 | failed = 1; // a suffix has been removed |
1850 | 10.3k | else { |
1851 | 10.3k | post_ptr--; |
1852 | 10.3k | add_points = 1; |
1853 | 10.3k | } |
1854 | 10.4k | break; |
1855 | 18.9M | default: |
1856 | 18.9M | if (letter == rb) { |
1857 | 1.36M | if ((letter & 0xc0) != 0x80) { |
1858 | | // not for non-initial UTF-8 bytes |
1859 | 1.18M | add_points = (21-distance_right); |
1860 | 1.18M | } |
1861 | 1.36M | } else |
1862 | 17.5M | failed = 1; |
1863 | 18.9M | break; |
1864 | 25.0M | } |
1865 | 25.0M | break; |
1866 | 34.2M | case RULE_PRE: |
1867 | | // match backwards from start of current group |
1868 | 34.2M | distance_left += 2; |
1869 | 34.2M | if (distance_left > 18) |
1870 | 15 | distance_left = 19; |
1871 | | |
1872 | 34.2M | if (!*pre_ptr) { |
1873 | | // we had already reached the beginning of text! |
1874 | | // reading before this does not make sense, that cannot match |
1875 | 0 | failed = 1; |
1876 | 0 | break; |
1877 | 0 | } |
1878 | 34.2M | utf8_in(&last_letter_w, pre_ptr); |
1879 | 34.2M | pre_ptr--; |
1880 | 34.2M | letter_xbytes = utf8_in2(&letter_w, pre_ptr, 1)-1; |
1881 | 34.2M | letter = *pre_ptr; |
1882 | | |
1883 | 34.2M | switch (rb) |
1884 | 34.2M | { |
1885 | 2.38M | case RULE_LETTERGP: |
1886 | 2.38M | letter_group = LetterGroupNo(rule++); |
1887 | 2.38M | if (IsLetter(tr, letter_w, letter_group)) { |
1888 | 638k | lg_pts = 20; |
1889 | 638k | if (letter_group == 2) |
1890 | 523k | lg_pts = 19; // fewer points for C, general consonant |
1891 | 638k | add_points = (lg_pts-distance_left); |
1892 | 638k | pre_ptr -= letter_xbytes; |
1893 | 638k | } else |
1894 | 1.74M | failed = 1; |
1895 | 2.38M | break; |
1896 | 140k | case RULE_LETTERGP2: // match against a list of utf-8 strings |
1897 | 140k | letter_group = LetterGroupNo(rule++); |
1898 | 140k | if ((n_bytes = IsLetterGroup(tr, pre_ptr, letter_group, 1)) >= 0) { |
1899 | 35.5k | add_points = (20-distance_right); |
1900 | | // move pointer, if group was found |
1901 | 35.5k | pre_ptr -= (n_bytes-1); |
1902 | 35.5k | } else |
1903 | 104k | failed = 1; |
1904 | 140k | break; |
1905 | 34.0k | case RULE_NOTVOWEL: |
1906 | 34.0k | if (!IsLetter(tr, letter_w, 0)) { |
1907 | 16.6k | add_points = (20-distance_left); |
1908 | 16.6k | pre_ptr -= letter_xbytes; |
1909 | 16.6k | } else |
1910 | 17.3k | failed = 1; |
1911 | 34.0k | break; |
1912 | 37.6k | case RULE_DOUBLE: |
1913 | 37.6k | if (letter_w == last_letter_w) { |
1914 | 2.68k | add_points = (21-distance_left); |
1915 | 2.68k | pre_ptr -= letter_xbytes; |
1916 | 2.68k | } else |
1917 | 34.9k | failed = 1; |
1918 | 37.6k | break; |
1919 | 17.6k | case RULE_DIGIT: |
1920 | 17.6k | if (IsDigit(letter_w)) { |
1921 | 1.56k | add_points = (21-distance_left); |
1922 | 1.56k | pre_ptr -= letter_xbytes; |
1923 | 1.56k | } else |
1924 | 16.1k | failed = 1; |
1925 | 17.6k | break; |
1926 | 0 | case RULE_NONALPHA: |
1927 | 0 | if (!iswalpha(letter_w)) { |
1928 | 0 | add_points = (21-distance_right); |
1929 | 0 | pre_ptr -= letter_xbytes; |
1930 | 0 | } else |
1931 | 0 | failed = 1; |
1932 | 0 | break; |
1933 | 0 | case RULE_DOLLAR: |
1934 | 0 | pre_ptr++; |
1935 | 0 | command = *rule++; |
1936 | 0 | if ((command == DOLLAR_LIST) || ((command & 0xf0) == 0x20)) { |
1937 | 0 | DollarRule(word, word_start, consumed, group_length, word_buf, tr, command, &failed, &add_points); |
1938 | 0 | } |
1939 | 0 | break; |
1940 | 2.70M | case RULE_SYLLABLE: |
1941 | | // more than specified number of vowels to the left |
1942 | 2.70M | syllable_count = 1; |
1943 | 2.80M | while (*rule == RULE_SYLLABLE) { |
1944 | 93.4k | rule++; |
1945 | 93.4k | syllable_count++; // number of syllables to match |
1946 | 93.4k | } |
1947 | 2.70M | if (syllable_count <= tr->word_vowel_count) |
1948 | 2.21M | add_points = (18+syllable_count-distance_left); |
1949 | 490k | else |
1950 | 490k | failed = 1; |
1951 | 2.70M | break; |
1952 | 2.07M | case RULE_STRESSED: |
1953 | 2.07M | pre_ptr++; |
1954 | 2.07M | if (tr->word_stressed_count > 0) |
1955 | 1.64M | add_points = 19; |
1956 | 432k | else |
1957 | 432k | failed = 1; |
1958 | 2.07M | break; |
1959 | 778k | case RULE_NOVOWELS: |
1960 | 778k | { |
1961 | 778k | char *p = pre_ptr - letter_xbytes; |
1962 | 1.30M | while (letter_w != RULE_SPACE) { |
1963 | 1.12M | if (IsLetter(tr, letter_w, LETTERGP_VOWEL2)) { |
1964 | 590k | failed = 1; |
1965 | 590k | break; |
1966 | 590k | } |
1967 | 530k | p -= utf8_in2(&letter_w, p-1, 1); |
1968 | 530k | } |
1969 | 778k | if (!failed) |
1970 | 187k | add_points = 3; |
1971 | 778k | } |
1972 | 778k | break; |
1973 | 3.61k | case RULE_IFVERB: |
1974 | 3.61k | pre_ptr++; |
1975 | 3.61k | if (tr->expect_verb) |
1976 | 198 | add_points = 1; |
1977 | 3.41k | else |
1978 | 3.41k | failed = 1; |
1979 | 3.61k | break; |
1980 | 24 | case RULE_CAPITAL: |
1981 | 24 | pre_ptr++; |
1982 | 24 | if (word_flags & FLAG_FIRST_UPPER) |
1983 | 11 | add_points = 1; |
1984 | 13 | else |
1985 | 13 | failed = 1; |
1986 | 24 | break; |
1987 | 9.57k | case '.': |
1988 | | // dot in pre- section, match on any dot before this point in the word |
1989 | 29.4k | for (p = pre_ptr; *p && *p != ' '; p--) { |
1990 | 20.3k | if (*p == '.') { |
1991 | 441 | add_points = 50; |
1992 | 441 | break; |
1993 | 441 | } |
1994 | 20.3k | } |
1995 | 9.57k | if (!*p || *p == ' ') |
1996 | 9.13k | failed = 1; |
1997 | 9.57k | break; |
1998 | 172k | case '-': |
1999 | 172k | if ((letter == '-') || ((letter == ' ') && (word_flags & FLAG_HYPHEN))) |
2000 | 424 | add_points = (22-distance_right); // one point more than match against space |
2001 | 172k | else |
2002 | 172k | failed = 1; |
2003 | 172k | break; |
2004 | | |
2005 | 5.75k | case RULE_SKIPCHARS: { |
2006 | | // 'xyJ)' means 'skip characters backwards until xy' |
2007 | 5.75k | char *p = pre_ptr + 1; // to allow empty jump (without letter between), go one forward |
2008 | 5.75k | char *p2 = p; // pointer to previous character in word |
2009 | 5.75k | int g_bytes = -1; // bytes of successfully found character group |
2010 | | |
2011 | 50.5k | while ((*p != *rule) && (*p != RULE_SPACE) && (*p != 0) && (g_bytes == -1)) { |
2012 | 44.7k | p2 = p; |
2013 | 44.7k | p--; |
2014 | 44.7k | if (*rule == RULE_LETTERGP2) |
2015 | 44.7k | g_bytes = IsLetterGroup(tr, p2, LetterGroupNo(rule + 1), 1); |
2016 | 44.7k | } |
2017 | | |
2018 | | // if succeed, set pre_ptr to next character after 'xy' and remaining |
2019 | | // 'xy' part is checked as usual in following cycles of PRE rule characters |
2020 | 5.75k | if (*p == *rule) |
2021 | 567 | pre_ptr = p2; |
2022 | 5.75k | if (g_bytes >= 0) |
2023 | 980 | pre_ptr = p2 + 1; |
2024 | | |
2025 | 5.75k | } |
2026 | 5.75k | break; |
2027 | | |
2028 | 25.8M | default: |
2029 | 25.8M | if (letter == rb) { |
2030 | 1.26M | if (letter == RULE_SPACE) |
2031 | 59.0k | add_points = 4; |
2032 | 1.20M | else if ((letter & 0xc0) != 0x80) { |
2033 | | // not for non-initial UTF-8 bytes |
2034 | 1.11M | add_points = (21-distance_left); |
2035 | 1.11M | } |
2036 | 1.26M | } else |
2037 | 24.5M | failed = 1; |
2038 | 25.8M | break; |
2039 | 34.2M | } |
2040 | 34.2M | break; |
2041 | 124M | } |
2042 | | |
2043 | 124M | if (failed == 0) |
2044 | 11.5M | match.points += add_points; |
2045 | 124M | } |
2046 | | |
2047 | 115M | if ((failed == 2) && (unpron_ignore == 0)) { |
2048 | | // do we also need to check for 'start of word' ? |
2049 | 1.71M | if ((check_atstart == false) || (pre_ptr[-1] == ' ')) { |
2050 | 1.61M | if (check_atstart) |
2051 | 72.7k | match.points += 4; |
2052 | | |
2053 | | // matched OK, is this better than the last best match ? |
2054 | 1.61M | if (match.points >= best.points) { |
2055 | 1.24M | memcpy(&best, &match, sizeof(match)); |
2056 | 1.24M | total_consumed = consumed; |
2057 | 1.24M | } |
2058 | | |
2059 | 1.61M | if ((option_phonemes & espeakPHONEMES_TRACE) && (match.points > 0) && ((word_flags & FLAG_NO_TRACE) == 0)) { |
2060 | | // show each rule that matches, and it's points score |
2061 | 0 | int pts; |
2062 | 0 | char decoded_phonemes[80]; |
2063 | 0 | char output[80]; |
2064 | |
|
2065 | 0 | pts = match.points; |
2066 | 0 | if (group_length > 1) |
2067 | 0 | pts += 35; // to account for an extra letter matching |
2068 | 0 | DecodePhonemes(match.phonemes, decoded_phonemes); |
2069 | 0 | fprintf(f_trans, "%3d\t%s [%s]\n", pts, DecodeRule(group_chars, group_length, rule_start, word_flags, output), decoded_phonemes); |
2070 | 0 | } |
2071 | 1.61M | } |
2072 | 1.71M | } |
2073 | | |
2074 | | // skip phoneme string to reach start of next template |
2075 | 753M | while (*rule++ != 0) ; |
2076 | 115M | } |
2077 | | |
2078 | | // advance input data pointer |
2079 | 2.48M | total_consumed += group_length; |
2080 | 2.48M | if (total_consumed == 0) |
2081 | 1.34M | total_consumed = 1; // always advance over 1st letter |
2082 | | |
2083 | 2.48M | *word += total_consumed; |
2084 | | |
2085 | 2.48M | if (best.points == 0) |
2086 | 1.49M | best.phonemes = ""; |
2087 | 2.48M | memcpy(match_out, &best, sizeof(MatchRecord)); |
2088 | 2.48M | } |
2089 | | |
2090 | | int TranslateRules(Translator *tr, char *p_start, char *phonemes, int ph_size, char *end_phonemes, int word_flags, unsigned int *dict_flags) |
2091 | 1.05M | { |
2092 | | /* Translate a word bounded by space characters |
2093 | | Append the result to 'phonemes' and any standard prefix/suffix in 'end_phonemes' */ |
2094 | | |
2095 | 1.05M | unsigned char c, c2; |
2096 | 1.05M | unsigned int c12; |
2097 | 1.05M | int wc = 0; |
2098 | 1.05M | char *p2; // copy of p for use in double letter chain match |
2099 | 1.05M | int found; |
2100 | 1.05M | int g; // group chain number |
2101 | 1.05M | int g1; // first group for this letter |
2102 | 1.05M | int letter; |
2103 | 1.05M | int any_alpha = 0; |
2104 | 1.05M | int ix; |
2105 | 1.05M | unsigned int digit_count = 0; |
2106 | 1.05M | char *p; |
2107 | 1.05M | char word_buf[5]; |
2108 | 1.05M | const ALPHABET *alphabet; |
2109 | 1.05M | int dict_flags0 = 0; |
2110 | 1.05M | MatchRecord match1 = { 0 }; |
2111 | 1.05M | MatchRecord match2 = { 0 }; |
2112 | 1.05M | char ph_buf[N_PHONEME_BYTES]; |
2113 | 1.05M | char word_copy[N_WORD_BYTES]; |
2114 | 1.05M | static const char str_pause[2] = { phonPAUSE_NOLINK, 0 }; |
2115 | | |
2116 | 1.05M | if (tr->data_dictrules == NULL) |
2117 | 0 | return 0; |
2118 | | |
2119 | 1.05M | if (dict_flags != NULL) |
2120 | 672k | dict_flags0 = dict_flags[0]; |
2121 | | |
2122 | 66.6M | for (ix = 0; ix < (N_WORD_BYTES-1);) { |
2123 | 66.3M | c = p_start[ix]; |
2124 | 66.3M | word_copy[ix++] = c; |
2125 | 66.3M | if (c == 0) |
2126 | 794k | break; |
2127 | 66.3M | } |
2128 | 1.05M | word_copy[ix] = 0; |
2129 | | |
2130 | 1.05M | if ((option_phonemes & espeakPHONEMES_TRACE) && ((word_flags & FLAG_NO_TRACE) == 0)) { |
2131 | 0 | char wordbuf[120]; |
2132 | 0 | unsigned int ix; |
2133 | |
|
2134 | 0 | for (ix = 0; ((c = p_start[ix]) != ' ') && (c != 0) && (ix < (sizeof(wordbuf)-1)); ix++) |
2135 | 0 | wordbuf[ix] = c; |
2136 | 0 | wordbuf[ix] = 0; |
2137 | 0 | if (word_flags & FLAG_UNPRON_TEST) |
2138 | 0 | fprintf(f_trans, "Unpronouncable? '%s'\n", wordbuf); |
2139 | 0 | else |
2140 | 0 | fprintf(f_trans, "Translate '%s'\n", wordbuf); |
2141 | 0 | } |
2142 | | |
2143 | 1.05M | p = p_start; |
2144 | 1.05M | tr->word_vowel_count = 0; |
2145 | 1.05M | tr->word_stressed_count = 0; |
2146 | | |
2147 | 1.05M | if (end_phonemes != NULL) |
2148 | 664k | end_phonemes[0] = 0; |
2149 | | |
2150 | 3.50M | while (((c = *p) != ' ') && (c != 0)) { |
2151 | 2.72M | int wc_bytes = utf8_in(&wc, p); |
2152 | 2.72M | if (IsAlpha(wc)) |
2153 | 1.35M | any_alpha++; |
2154 | | |
2155 | 2.72M | int n = tr->groups2_count[c]; |
2156 | 2.72M | if (IsDigit(wc) && ((tr->langopts.tone_numbers == 0) || !any_alpha)) { |
2157 | | // lookup the number in *_list not *_rules |
2158 | 260k | char string[8]; |
2159 | 260k | char buf[40]; |
2160 | 260k | string[0] = '_'; |
2161 | 260k | memcpy(&string[1], p, wc_bytes); |
2162 | 260k | string[1+wc_bytes] = 0; |
2163 | 260k | Lookup(tr, string, buf); |
2164 | 260k | if (++digit_count >= 2) { |
2165 | 123k | strcat(buf, str_pause); |
2166 | 123k | digit_count = 0; |
2167 | 123k | } |
2168 | 260k | AppendPhonemes(tr, phonemes, ph_size, buf); |
2169 | 260k | p += wc_bytes; |
2170 | 260k | continue; |
2171 | 2.46M | } else { |
2172 | 2.46M | digit_count = 0; |
2173 | 2.46M | found = 0; |
2174 | | |
2175 | 2.46M | if (((ix = wc - tr->letter_bits_offset) >= 0) && (ix < 128)) { |
2176 | 1.49M | if (tr->groups3[ix] != NULL) { |
2177 | 48.9k | MatchRule(tr, &p, p_start, wc_bytes, tr->groups3[ix], &match1, word_flags, dict_flags0); |
2178 | 48.9k | found = 1; |
2179 | 48.9k | } |
2180 | 1.49M | } |
2181 | | |
2182 | 2.46M | if (!found && (n > 0)) { |
2183 | | // there are some 2 byte chains for this initial letter |
2184 | 544k | c2 = p[1]; |
2185 | 544k | c12 = c + (c2 << 8); // 2 characters |
2186 | | |
2187 | 544k | g1 = tr->groups2_start[c]; |
2188 | 4.95M | for (g = g1; g < (g1+n); g++) { |
2189 | 4.40M | if (tr->groups2_name[g] == c12) { |
2190 | 129k | found = 1; |
2191 | | |
2192 | 129k | p2 = p; |
2193 | 129k | MatchRule(tr, &p2, p_start, 2, tr->groups2[g], &match2, word_flags, dict_flags0); |
2194 | 129k | if (match2.points > 0) |
2195 | 72.7k | match2.points += 35; // to acount for 2 letters matching |
2196 | | |
2197 | | // now see whether single letter chain gives a better match ? |
2198 | 129k | MatchRule(tr, &p, p_start, 1, tr->groups1[c], &match1, word_flags, dict_flags0); |
2199 | | |
2200 | 129k | if (match2.points >= match1.points) { |
2201 | | // use match from the 2-letter group |
2202 | 76.2k | memcpy(&match1, &match2, sizeof(MatchRecord)); |
2203 | 76.2k | p = p2; |
2204 | 76.2k | } |
2205 | 129k | } |
2206 | 4.40M | } |
2207 | 544k | } |
2208 | | |
2209 | 2.46M | if (!found) { |
2210 | | // alphabetic, single letter chain |
2211 | 2.28M | if (tr->groups1[c] != NULL) |
2212 | 832k | MatchRule(tr, &p, p_start, 1, tr->groups1[c], &match1, word_flags, dict_flags0); |
2213 | 1.45M | else { |
2214 | | // no group for this letter, use default group |
2215 | 1.45M | MatchRule(tr, &p, p_start, 0, tr->groups1[0], &match1, word_flags, dict_flags0); |
2216 | | |
2217 | 1.45M | if ((match1.points == 0) && ((option_sayas & 0x10) == 0)) { |
2218 | 1.41M | n = utf8_in(&letter, p-1)-1; |
2219 | | |
2220 | 1.41M | if (tr->letter_bits_offset > 0) { |
2221 | | // not a Latin alphabet, switch to the default Latin alphabet language |
2222 | 636k | if ((letter <= 0x241) && iswalpha(letter)) { |
2223 | 82.8k | sprintf(phonemes, "%cen", phonSWITCH); |
2224 | 82.8k | return 0; |
2225 | 82.8k | } |
2226 | 636k | } |
2227 | | |
2228 | | // is it a bracket ? |
2229 | 1.33M | if (letter == 0xe000+'(') { |
2230 | 5.02k | if (pre_pause < tr->langopts.param[LOPT_BRACKET_PAUSE_ANNOUNCED]) |
2231 | 3.82k | pre_pause = tr->langopts.param[LOPT_BRACKET_PAUSE_ANNOUNCED]; // a bracket, already spoken by AnnouncePunctuation() |
2232 | 5.02k | } |
2233 | 1.33M | if (IsBracket(letter)) { |
2234 | 350k | if (pre_pause < tr->langopts.param[LOPT_BRACKET_PAUSE]) |
2235 | 75.8k | pre_pause = tr->langopts.param[LOPT_BRACKET_PAUSE]; |
2236 | 350k | } |
2237 | | |
2238 | | // no match, try removing the accent and re-translating the word |
2239 | 1.33M | if ((letter >= 0xc0) && (letter < N_REMOVE_ACCENT) && ((ix = remove_accent[letter-0xc0]) != 0)) { |
2240 | | // within range of the remove_accent table |
2241 | 19.6k | if ((p[-2] != ' ') || (p[n] != ' ')) { |
2242 | | // not the only letter in the word |
2243 | 7.93k | p2 = p-1; |
2244 | 7.93k | p[-1] = ix; |
2245 | 140k | while ((p[0] = p[n]) != ' ') p++; |
2246 | 15.8k | while (n-- > 0) *p++ = ' '; // replacement character must be no longer than original |
2247 | | |
2248 | 7.93k | if (tr->langopts.param[LOPT_DIERESES] && (lookupwchar(diereses_list, letter) > 0)) { |
2249 | | // vowel with dieresis, replace and continue from this point |
2250 | 0 | p = p2; |
2251 | 0 | continue; |
2252 | 0 | } |
2253 | | |
2254 | 7.93k | phonemes[0] = 0; // delete any phonemes which have been produced so far |
2255 | 7.93k | p = p_start; |
2256 | 7.93k | tr->word_vowel_count = 0; |
2257 | 7.93k | tr->word_stressed_count = 0; |
2258 | 7.93k | continue; // start again at the beginning of the word |
2259 | 7.93k | } |
2260 | 19.6k | } |
2261 | | |
2262 | 1.32M | if (((alphabet = AlphabetFromChar(letter)) != NULL) && (alphabet->offset != tr->letter_bits_offset)) { |
2263 | 117k | if (tr->langopts.alt_alphabet == alphabet->offset) { |
2264 | 334 | sprintf(phonemes, "%c%s", phonSWITCH, WordToString2(word_buf, tr->langopts.alt_alphabet_lang)); |
2265 | 334 | return 0; |
2266 | 334 | } |
2267 | 117k | if (alphabet->flags & AL_WORDS) { |
2268 | | // switch to the nominated language for this alphabet |
2269 | 62.3k | sprintf(phonemes, "%c%s", phonSWITCH, WordToString2(word_buf, alphabet->language)); |
2270 | 62.3k | return 0; |
2271 | 62.3k | } |
2272 | 117k | } |
2273 | 1.32M | } |
2274 | 1.45M | } |
2275 | | |
2276 | 2.13M | if (match1.points == 0) { |
2277 | 1.37M | if ((wc >= 0x300) && (wc <= 0x36f)) { |
2278 | | // combining accent inside a word, ignore |
2279 | 1.36M | } else if (IsAlpha(wc)) { |
2280 | 304k | if ((any_alpha > 1) || (p[wc_bytes-1] > ' ')) { |
2281 | | // an unrecognised character in a word, abort and then spell the word |
2282 | 81.2k | phonemes[0] = 0; |
2283 | 81.2k | if (dict_flags != NULL) |
2284 | 15.7k | dict_flags[0] |= FLAG_SPELLWORD; |
2285 | 81.2k | break; |
2286 | 81.2k | } |
2287 | 1.05M | } else { |
2288 | 1.05M | LookupLetter(tr, wc, -1, ph_buf, 0); |
2289 | 1.05M | if (ph_buf[0]) { |
2290 | 84.8k | match1.phonemes = ph_buf; |
2291 | 84.8k | match1.points = 1; |
2292 | 84.8k | } |
2293 | 1.05M | } |
2294 | 1.29M | p += (wc_bytes-1); |
2295 | 1.29M | } else |
2296 | 762k | tr->phonemes_repeat_count = 0; |
2297 | 2.13M | } |
2298 | 2.46M | } |
2299 | | |
2300 | 2.23M | if (match1.phonemes == NULL) |
2301 | 52.4k | match1.phonemes = ""; |
2302 | | |
2303 | 2.23M | if (match1.points > 0) { |
2304 | 1.02M | if (word_flags & FLAG_UNPRON_TEST) |
2305 | 6.82k | return match1.end_type | 1; |
2306 | | |
2307 | 1.01M | if ((match1.phonemes[0] == phonSWITCH) && ((word_flags & FLAG_DONT_SWITCH_TRANSLATOR) == 0)) { |
2308 | | // an instruction to switch language, return immediately so we can re-translate |
2309 | 27.8k | strcpy(phonemes, match1.phonemes); |
2310 | 27.8k | return 0; |
2311 | 27.8k | } |
2312 | | |
2313 | 987k | if ((option_phonemes & espeakPHONEMES_TRACE) && ((word_flags & FLAG_NO_TRACE) == 0)) |
2314 | 0 | fprintf(f_trans, "\n"); |
2315 | | |
2316 | 987k | match1.end_type &= ~SUFX_UNPRON; |
2317 | | |
2318 | 987k | if ((match1.end_type != 0) && (end_phonemes != NULL)) { |
2319 | | // a standard ending has been found, re-translate the word without it |
2320 | 19.5k | if ((match1.end_type & SUFX_P) && (word_flags & FLAG_NO_PREFIX)) { |
2321 | | // ignore the match on a prefix |
2322 | 17.3k | } else { |
2323 | 17.3k | if ((match1.end_type & SUFX_P) && ((match1.end_type & 0x7f) == 0)) { |
2324 | | // no prefix length specified |
2325 | 16 | match1.end_type |= p - p_start; |
2326 | 16 | } |
2327 | 17.3k | strcpy(end_phonemes, match1.phonemes); |
2328 | 17.3k | memcpy(p_start, word_copy, strlen(word_copy)); |
2329 | 17.3k | return match1.end_type; |
2330 | 17.3k | } |
2331 | 19.5k | } |
2332 | 970k | if (match1.del_fwd != NULL) |
2333 | 3.64k | *match1.del_fwd = REPLACED_E; |
2334 | 970k | AppendPhonemes(tr, phonemes, ph_size, match1.phonemes); |
2335 | 970k | } |
2336 | 2.23M | } |
2337 | | |
2338 | 860k | memcpy(p_start, word_copy, strlen(word_copy)); |
2339 | | |
2340 | 860k | return 0; |
2341 | 1.05M | } |
2342 | | |
2343 | | int TransposeAlphabet(Translator *tr, char *text) |
2344 | 6.29M | { |
2345 | | // transpose cyrillic alphabet (for example) into ascii (single byte) character codes |
2346 | | // return: number of bytes, bit 6: 1=used compression |
2347 | | |
2348 | 6.29M | int c; |
2349 | 6.29M | int offset; |
2350 | 6.29M | int min; |
2351 | 6.29M | int max; |
2352 | 6.29M | const char *map; |
2353 | 6.29M | char *p = text; |
2354 | 6.29M | char *p2; |
2355 | 6.29M | bool all_alpha = true; |
2356 | 6.29M | int pairs_start; |
2357 | 6.29M | int bufix; |
2358 | 6.29M | char buf[N_WORD_BYTES+1]; |
2359 | | |
2360 | 6.29M | offset = tr->transpose_min - 1; |
2361 | 6.29M | min = tr->transpose_min; |
2362 | 6.29M | max = tr->transpose_max; |
2363 | 6.29M | map = tr->transpose_map; |
2364 | | |
2365 | 6.29M | pairs_start = max - min + 2; |
2366 | | |
2367 | 6.29M | bufix = 0; |
2368 | 7.74M | do { |
2369 | 7.74M | p += utf8_in(&c, p); |
2370 | 7.74M | if (c != 0) { |
2371 | 7.14M | if ((c >= min) && (c <= max)) { |
2372 | 1.65M | if (map == NULL) |
2373 | 13.4k | buf[bufix++] = c - offset; |
2374 | 1.63M | else { |
2375 | | // get the code from the transpose map |
2376 | 1.63M | if (map[c - min] > 0) |
2377 | 1.43M | buf[bufix++] = map[c - min]; |
2378 | 200k | else { |
2379 | 200k | all_alpha = false; |
2380 | 200k | break; |
2381 | 200k | } |
2382 | 1.63M | } |
2383 | 5.49M | } else { |
2384 | 5.49M | all_alpha = false; |
2385 | 5.49M | break; |
2386 | 5.49M | } |
2387 | 7.14M | } |
2388 | 7.74M | } while ((c != 0) && (bufix < N_WORD_BYTES)); |
2389 | 6.29M | buf[bufix] = 0; |
2390 | | |
2391 | 6.29M | if (all_alpha) { |
2392 | | // compress to 6 bits per character |
2393 | 594k | int ix; |
2394 | 594k | int acc = 0; |
2395 | 594k | int bits = 0; |
2396 | | |
2397 | 594k | p = buf; |
2398 | 594k | p2 = buf; |
2399 | 2.00M | while ((c = *p++) != 0) { |
2400 | 1.41M | const short *pairs_list; |
2401 | 1.41M | if ((pairs_list = tr->frequent_pairs) != NULL) { |
2402 | 8.67k | int c2 = c + (*p << 8); |
2403 | 118k | for (ix = 0; c2 >= pairs_list[ix]; ix++) { |
2404 | 111k | if (c2 == pairs_list[ix]) { |
2405 | | // found an encoding for a 2-character pair |
2406 | 2.01k | c = ix + pairs_start; // 2-character codes start after the single letter codes |
2407 | 2.01k | p++; |
2408 | 2.01k | break; |
2409 | 2.01k | } |
2410 | 111k | } |
2411 | 8.67k | } |
2412 | 1.41M | acc = (acc << 6) + (c & 0x3f); |
2413 | 1.41M | bits += 6; |
2414 | | |
2415 | 1.41M | if (bits >= 8) { |
2416 | 686k | bits -= 8; |
2417 | 686k | *p2++ = (char)((acc >> bits) & 0xff); |
2418 | 686k | } |
2419 | 1.41M | } |
2420 | 594k | if (bits > 0) |
2421 | 543k | *p2++ = (char)((acc << (8-bits)) & 0xff); |
2422 | 594k | *p2 = 0; |
2423 | 594k | ix = p2 - buf; |
2424 | 594k | memcpy(text, buf, ix); |
2425 | 594k | return ix | 0x40; // bit 6 indicates compressed characters |
2426 | 594k | } |
2427 | 5.69M | return strlen(text); |
2428 | 6.29M | } |
2429 | | |
2430 | | /* Find an entry in the word_dict file for a specified word. |
2431 | | Returns NULL if no match, else returns 'word_end' |
2432 | | |
2433 | | word zero terminated word to match |
2434 | | word2 pointer to next word(s) in the input text (terminated by space) |
2435 | | |
2436 | | flags: returns dictionary flags which are associated with a matched word |
2437 | | |
2438 | | end_flags: indicates whether this is a retranslation after removing a suffix |
2439 | | */ |
2440 | | static const char *LookupDict2(Translator *tr, const char *word, const char *word2, |
2441 | | char *phonetic, unsigned int *flags, int end_flags, WORD_TAB *wtab, int wtab_remaining) |
2442 | 6.29M | { |
2443 | 6.29M | char *p; |
2444 | 6.29M | char *next; |
2445 | 6.29M | int hash; |
2446 | 6.29M | int phoneme_len; |
2447 | 6.29M | int wlen; |
2448 | 6.29M | unsigned char flag; |
2449 | 6.29M | unsigned int dictionary_flags; |
2450 | 6.29M | unsigned int dictionary_flags2; |
2451 | 6.29M | bool condition_failed = false; |
2452 | 6.29M | int n_chars; |
2453 | 6.29M | int no_phonemes; |
2454 | 6.29M | int skipwords; |
2455 | 6.29M | int ix; |
2456 | 6.29M | int c; |
2457 | 6.29M | const char *word_end; |
2458 | 6.29M | const char *word1; |
2459 | 6.29M | int wflags = 0; |
2460 | 6.29M | int lookup_symbol; |
2461 | 6.29M | char word_buf[N_WORD_BYTES+1]; |
2462 | 6.29M | char dict_flags_buf[80]; |
2463 | | |
2464 | 6.29M | if (wtab != NULL) |
2465 | 1.26M | wflags = wtab->flags; |
2466 | | |
2467 | 6.29M | lookup_symbol = flags[1] & FLAG_LOOKUP_SYMBOL; |
2468 | 6.29M | word1 = word; |
2469 | 6.29M | if (tr->transpose_min > 0) { |
2470 | 6.29M | strncpy0(word_buf, word, N_WORD_BYTES); |
2471 | 6.29M | wlen = TransposeAlphabet(tr, word_buf); // bit 6 indicates compressed characters |
2472 | 6.29M | word = word_buf; |
2473 | 6.29M | } else |
2474 | 0 | wlen = strlen(word); |
2475 | | |
2476 | 6.29M | hash = HashDictionary(word); |
2477 | 6.29M | p = tr->dict_hashtab[hash]; |
2478 | | |
2479 | 6.29M | if (p == NULL) { |
2480 | 0 | if (flags != NULL) |
2481 | 0 | *flags = 0; |
2482 | 0 | return 0; |
2483 | 0 | } |
2484 | | |
2485 | | // Find the first entry in the list for this hash value which matches. |
2486 | | // This corresponds to the last matching entry in the *_list file. |
2487 | | |
2488 | 114M | while (*p != 0) { |
2489 | 110M | next = p + (p[0] & 0xff); |
2490 | | |
2491 | 110M | if (((p[1] & 0x7f) != wlen) || (memcmp(word, &p[2], wlen & 0x3f) != 0)) { |
2492 | | // bit 6 of wlen indicates whether the word has been compressed; so we need to match on this also. |
2493 | 107M | p = next; |
2494 | 107M | continue; |
2495 | 107M | } |
2496 | | |
2497 | | // found matching entry. Decode the phonetic string |
2498 | 2.33M | word_end = word2; |
2499 | | |
2500 | 2.33M | dictionary_flags = 0; |
2501 | 2.33M | dictionary_flags2 = 0; |
2502 | 2.33M | no_phonemes = p[1] & 0x80; |
2503 | | |
2504 | 2.33M | p += ((p[1] & 0x3f) + 2); |
2505 | | |
2506 | 2.33M | if (no_phonemes) { |
2507 | 28.3k | phonetic[0] = 0; |
2508 | 28.3k | phoneme_len = 0; |
2509 | 2.30M | } else { |
2510 | 2.30M | phoneme_len = strlen(p); |
2511 | 2.30M | assert(phoneme_len < N_PHONEME_BYTES); |
2512 | 2.30M | strcpy(phonetic, p); |
2513 | 2.30M | p += (phoneme_len + 1); |
2514 | 2.30M | } |
2515 | | |
2516 | 3.60M | while (p < next) { |
2517 | | // examine the flags which follow the phoneme string |
2518 | | |
2519 | 1.82M | flag = *p++; |
2520 | 1.82M | if (flag >= 100) { |
2521 | | // conditional rule |
2522 | 83.3k | if (flag >= 132) { |
2523 | | // fail if this condition is set |
2524 | 3.76k | if ((tr->dict_condition & (1 << (flag-132))) != 0) |
2525 | 0 | condition_failed = true; |
2526 | 79.5k | } else { |
2527 | | // allow only if this condition is set |
2528 | 79.5k | if ((tr->dict_condition & (1 << (flag-100))) == 0) |
2529 | 68.4k | condition_failed = true; |
2530 | 79.5k | } |
2531 | 1.73M | } else if (flag > 80) { |
2532 | | // flags 81 to 90 match more than one word |
2533 | | // This comes after the other flags |
2534 | 549k | n_chars = next - p; |
2535 | 549k | skipwords = flag - 80; |
2536 | | |
2537 | | // don't use the contraction if any of the words are emphasized |
2538 | | // or has an embedded command, such as MARK |
2539 | 549k | if ((wtab != NULL) && (wtab_remaining > skipwords)) { |
2540 | 1.34M | for (ix = 0; ix <= skipwords && wtab[ix].length; ix++) { |
2541 | 856k | if (wtab[ix].flags & FLAG_EMPHASIZED2) |
2542 | 20.5k | condition_failed = true; |
2543 | | |
2544 | 856k | } |
2545 | 486k | } |
2546 | | |
2547 | 549k | if (strncmp(word2, p, n_chars) != 0) |
2548 | 548k | condition_failed = true; |
2549 | | |
2550 | 549k | if (condition_failed) { |
2551 | 548k | p = next; |
2552 | 548k | break; |
2553 | 548k | } |
2554 | | |
2555 | 879 | dictionary_flags |= FLAG_SKIPWORDS; |
2556 | 879 | dictionary_skipwords = skipwords; |
2557 | 879 | p = next; |
2558 | 879 | word_end = word2 + n_chars; |
2559 | 1.18M | } else if (flag > 64) { |
2560 | | // stressed syllable information, put in bits 0-3 |
2561 | 100k | dictionary_flags = (dictionary_flags & ~0xf) | (flag & 0xf); |
2562 | 100k | if ((flag & 0xc) == 0xc) |
2563 | 81.7k | dictionary_flags |= FLAG_STRESS_END; |
2564 | 1.08M | } else if (flag >= 32) |
2565 | 402k | dictionary_flags2 |= (1L << (flag-32)); |
2566 | 684k | else |
2567 | 684k | dictionary_flags |= (1L << flag); |
2568 | 1.82M | } |
2569 | | |
2570 | 2.33M | if (condition_failed) { |
2571 | 611k | condition_failed = false; |
2572 | 611k | continue; |
2573 | 611k | } |
2574 | | |
2575 | 1.72M | if ((end_flags & FLAG_SUFX) == 0) { |
2576 | | // no suffix has been removed |
2577 | 1.72M | if (dictionary_flags2 & FLAG_STEM) |
2578 | 10 | continue; // this word must have a suffix |
2579 | 1.72M | } |
2580 | | |
2581 | 1.72M | if ((end_flags & SUFX_P) && (dictionary_flags2 & (FLAG_ONLY | FLAG_ONLY_S))) |
2582 | 73 | continue; // $only or $onlys, don't match if a prefix has been removed |
2583 | | |
2584 | 1.72M | if (end_flags & FLAG_SUFX) { |
2585 | | // a suffix was removed from the word |
2586 | 3.09k | if (dictionary_flags2 & FLAG_ONLY) |
2587 | 970 | continue; // no match if any suffix |
2588 | | |
2589 | 2.12k | if ((dictionary_flags2 & FLAG_ONLY_S) && ((end_flags & FLAG_SUFX_S) == 0)) { |
2590 | | // only a 's' suffix allowed, but the suffix wasn't 's' |
2591 | 99 | continue; |
2592 | 99 | } |
2593 | 2.12k | } |
2594 | | |
2595 | 1.72M | if (dictionary_flags2 & FLAG_CAPITAL) { |
2596 | 222 | if (!(wflags & FLAG_FIRST_UPPER)) |
2597 | 29 | continue; |
2598 | 222 | } |
2599 | 1.72M | if (dictionary_flags2 & FLAG_ALLCAPS) { |
2600 | 22.4k | if (!(wflags & FLAG_ALL_UPPER)) |
2601 | 17.8k | continue; |
2602 | 22.4k | } |
2603 | 1.70M | if (dictionary_flags & FLAG_NEEDS_DOT) { |
2604 | 26 | if (!(wflags & FLAG_HAS_DOT)) |
2605 | 26 | continue; |
2606 | 26 | } |
2607 | | |
2608 | 1.70M | if ((dictionary_flags2 & FLAG_ATEND) && (word_end < translator->clause_end) && (lookup_symbol == 0)) { |
2609 | | // only use this pronunciation if it's the last word of the clause, or called from Lookup() |
2610 | 10.2k | continue; |
2611 | 10.2k | } |
2612 | | |
2613 | 1.69M | if ((dictionary_flags2 & FLAG_ATSTART) && !(wflags & FLAG_FIRST_WORD)) { |
2614 | | // only use this pronunciation if it's the first word of a clause |
2615 | 53 | continue; |
2616 | 53 | } |
2617 | | |
2618 | 1.69M | if ((dictionary_flags2 & FLAG_SENTENCE) && !(translator->clause_terminator & CLAUSE_TYPE_SENTENCE)) { |
2619 | | // only if this clause is a sentence , i.e. terminator is {. ? !} not {, : :} |
2620 | 222 | continue; |
2621 | 222 | } |
2622 | | |
2623 | 1.69M | if (dictionary_flags2 & FLAG_VERB) { |
2624 | | // this is a verb-form pronunciation |
2625 | | |
2626 | 1.05k | if (tr->expect_verb || (tr->expect_verb_s && (end_flags & FLAG_SUFX_S))) { |
2627 | | // OK, we are expecting a verb |
2628 | 296 | if ((tr->translator_name == L('e', 'n')) && (tr->prev_dict_flags[0] & FLAG_ALT7_TRANS) && (end_flags & FLAG_SUFX_S)) { |
2629 | | // lang=en, don't use verb form after 'to' if the word has 's' suffix |
2630 | 8 | continue; |
2631 | 8 | } |
2632 | 760 | } else { |
2633 | | // don't use the 'verb' pronunciation unless we are expecting a verb |
2634 | 760 | continue; |
2635 | 760 | } |
2636 | 1.05k | } |
2637 | 1.69M | if (dictionary_flags2 & FLAG_PAST) { |
2638 | 161 | if (!tr->expect_past) { |
2639 | | // don't use the 'past' pronunciation unless we are expecting past tense |
2640 | 151 | continue; |
2641 | 151 | } |
2642 | 161 | } |
2643 | 1.69M | if (dictionary_flags2 & FLAG_NOUN) { |
2644 | 24 | if ((!tr->expect_noun) || (end_flags & SUFX_V)) { |
2645 | | // don't use the 'noun' pronunciation unless we are expecting a noun |
2646 | 24 | continue; |
2647 | 24 | } |
2648 | 24 | } |
2649 | 1.69M | if (dictionary_flags2 & FLAG_NATIVE) { |
2650 | 1.30k | if (tr != translator) |
2651 | 243 | continue; // don't use if we've switched translators |
2652 | 1.30k | } |
2653 | 1.69M | if (dictionary_flags & FLAG_ALT2_TRANS) { |
2654 | | // language specific |
2655 | 952 | if ((tr->translator_name == L('h', 'u')) && !(tr->prev_dict_flags[0] & FLAG_ALT_TRANS)) |
2656 | 791 | continue; |
2657 | 952 | } |
2658 | | |
2659 | 1.69M | if (flags != NULL) { |
2660 | 1.69M | flags[0] = dictionary_flags | FLAG_FOUND_ATTRIBUTES; |
2661 | 1.69M | flags[1] = dictionary_flags2; |
2662 | 1.69M | } |
2663 | | |
2664 | 1.69M | if (phoneme_len == 0) { |
2665 | 15.9k | if (option_phonemes & espeakPHONEMES_TRACE) { |
2666 | 0 | print_dictionary_flags(flags, dict_flags_buf, sizeof(dict_flags_buf)); |
2667 | 0 | fprintf(f_trans, "Flags: %s %s\n", word1, dict_flags_buf); |
2668 | 0 | } |
2669 | 15.9k | return 0; // no phoneme translation found here, only flags. So use rules |
2670 | 15.9k | } |
2671 | | |
2672 | 1.67M | if (flags != NULL) |
2673 | 1.67M | flags[0] |= FLAG_FOUND; // this flag indicates word was found in dictionary |
2674 | | |
2675 | 1.67M | if (option_phonemes & espeakPHONEMES_TRACE) { |
2676 | 0 | char ph_decoded[N_WORD_PHONEMES]; |
2677 | 0 | bool textmode; |
2678 | |
|
2679 | 0 | DecodePhonemes(phonetic, ph_decoded); |
2680 | |
|
2681 | 0 | if ((dictionary_flags & FLAG_TEXTMODE) == 0) |
2682 | 0 | textmode = false; |
2683 | 0 | else |
2684 | 0 | textmode = true; |
2685 | |
|
2686 | 0 | if (textmode == translator->langopts.textmode) { |
2687 | | // only show this line if the word translates to phonemes, not replacement text |
2688 | 0 | if ((dictionary_flags & FLAG_SKIPWORDS) && (wtab != NULL)) { |
2689 | | // matched more than one word |
2690 | | // (check for wtab prevents showing RULE_SPELLING byte when speaking individual letters) |
2691 | 0 | memcpy(word_buf, word2, word_end-word2); |
2692 | 0 | word_buf[word_end-word2-1] = 0; |
2693 | 0 | fprintf(f_trans, "Found: '%s %s\n", word1, word_buf); |
2694 | 0 | } else |
2695 | 0 | fprintf(f_trans, "Found: '%s", word1); |
2696 | 0 | print_dictionary_flags(flags, dict_flags_buf, sizeof(dict_flags_buf)); |
2697 | 0 | fprintf(f_trans, "' [%s] %s\n", ph_decoded, dict_flags_buf); |
2698 | 0 | } |
2699 | 0 | } |
2700 | | |
2701 | 1.67M | ix = utf8_in(&c, word); |
2702 | 1.67M | if (flags != NULL && (word[ix] == 0) && !IsAlpha(c)) |
2703 | 546k | flags[0] |= FLAG_MAX3; |
2704 | 1.67M | return word_end; |
2705 | | |
2706 | 1.69M | } |
2707 | 4.59M | return 0; |
2708 | 6.29M | } |
2709 | | |
2710 | | |
2711 | | static int utf8_nbytes(const char *buf) |
2712 | 6.36M | { |
2713 | | // Returns the number of bytes for the first UTF-8 character in buf |
2714 | | |
2715 | 6.36M | unsigned char c = (unsigned char)buf[0]; |
2716 | 6.36M | if (c < 0x80) |
2717 | 5.64M | return 1; |
2718 | 716k | if (c < 0xe0) |
2719 | 168k | return 2; |
2720 | 548k | if (c < 0xf0) |
2721 | 431k | return 3; |
2722 | 117k | return 4; |
2723 | 548k | } |
2724 | | |
2725 | | /* Lookup a specified word in the word dictionary. |
2726 | | Returns phonetic data in 'phonetic' and bits in 'flags' |
2727 | | |
2728 | | end_flags: indicates if a suffix has been removed |
2729 | | */ |
2730 | | int LookupDictList(Translator *tr, char **wordptr, char *ph_out, unsigned int *flags, int end_flags, WORD_TAB *wtab, int wtab_remaining) |
2731 | 6.27M | { |
2732 | 6.27M | int length; |
2733 | 6.27M | const char *found; |
2734 | 6.27M | const char *word1; |
2735 | 6.27M | const char *word2; |
2736 | 6.27M | unsigned char c; |
2737 | 6.27M | int nbytes; |
2738 | 6.27M | int len; |
2739 | 6.27M | char word[N_WORD_BYTES]; |
2740 | 6.27M | static char word_replacement[N_WORD_BYTES]; |
2741 | | |
2742 | 6.27M | MAKE_MEM_UNDEFINED(&word_replacement, sizeof(word_replacement)); |
2743 | | |
2744 | 6.27M | length = 0; |
2745 | 6.27M | word2 = word1 = *wordptr; |
2746 | | |
2747 | 6.36M | while ((word2[nbytes = utf8_nbytes(word2)] == ' ') && (word2[nbytes+1] == '.')) { |
2748 | | // look for an abbreviation of the form a.b.c |
2749 | | // try removing the spaces between the dots and looking for a match |
2750 | 90.0k | if ((nbytes <= 0) || ((size_t)nbytes + 1 > sizeof(word) - (size_t)length)) { |
2751 | | /* Too long abbreviation, leave as it is */ |
2752 | 424 | length = 0; |
2753 | 424 | break; |
2754 | 424 | } |
2755 | 89.5k | memcpy(&word[length], word2, nbytes); |
2756 | 89.5k | length += nbytes; |
2757 | 89.5k | word[length++] = '.'; |
2758 | 89.5k | word2 += nbytes+3; |
2759 | 89.5k | } |
2760 | 6.27M | if (length > 0) { |
2761 | | // found an abbreviation containing dots |
2762 | 14.2k | nbytes = 0; |
2763 | 135k | while (((c = word2[nbytes]) != 0) && (c != ' ')) |
2764 | 121k | nbytes++; |
2765 | 14.2k | if (length + nbytes + 1 <= sizeof(word)) { |
2766 | 13.9k | memcpy(&word[length], word2, nbytes); |
2767 | 13.9k | word[length+nbytes] = 0; |
2768 | 13.9k | found = LookupDict2(tr, word, word2, ph_out, flags, end_flags, wtab, wtab_remaining); |
2769 | 13.9k | if (found) { |
2770 | | // set the skip words flag |
2771 | 135 | flags[0] |= FLAG_SKIPWORDS; |
2772 | 135 | dictionary_skipwords = length; |
2773 | 135 | return 1; |
2774 | 135 | } |
2775 | 13.9k | } |
2776 | 14.2k | } |
2777 | | |
2778 | 21.3M | for (length = 0; length < (N_WORD_BYTES-1); length++) { |
2779 | 21.3M | if (((c = *word1++) == 0) || (c == ' ')) |
2780 | 6.24M | break; |
2781 | | |
2782 | 15.0M | if ((c == '.') && (length > 0) && (IsDigit09(word[length-1]))) |
2783 | 26.1k | break; // needed for lang=hu, eg. "december 2.-ig" |
2784 | | |
2785 | 15.0M | word[length] = c; |
2786 | 15.0M | } |
2787 | 6.27M | word[length] = 0; |
2788 | | |
2789 | 6.27M | found = LookupDict2(tr, word, word1, ph_out, flags, end_flags, wtab, wtab_remaining); |
2790 | | |
2791 | 6.27M | if (flags[0] & FLAG_MAX3) { |
2792 | 552k | if (strcmp(ph_out, tr->phonemes_repeat) == 0) { |
2793 | 298k | tr->phonemes_repeat_count++; |
2794 | 298k | if (tr->phonemes_repeat_count > 3) |
2795 | 146k | ph_out[0] = 0; |
2796 | 298k | } else { |
2797 | 253k | strncpy0(tr->phonemes_repeat, ph_out, sizeof(tr->phonemes_repeat)); |
2798 | 253k | tr->phonemes_repeat_count = 1; |
2799 | 253k | } |
2800 | 552k | } else |
2801 | 5.72M | tr->phonemes_repeat_count = 0; |
2802 | | |
2803 | 6.27M | if ((found == 0) && (flags[1] & FLAG_ACCENT)) { |
2804 | 124 | int letter; |
2805 | 124 | word2 = word; |
2806 | 124 | if (*word2 == '_') word2++; |
2807 | 124 | len = utf8_in(&letter, word2); |
2808 | 124 | LookupAccentedLetter(tr, letter, ph_out); |
2809 | 124 | found = word2 + len; |
2810 | 124 | } |
2811 | | |
2812 | 6.27M | if (found == 0 && length >= 2) { |
2813 | 2.87M | ph_out[0] = 0; |
2814 | | |
2815 | | // try modifications to find a recognised word |
2816 | | |
2817 | 2.87M | if ((end_flags & FLAG_SUFX_E_ADDED) && (word[length-1] == 'e')) { |
2818 | | // try removing an 'e' which has been added by RemoveEnding |
2819 | 1.81k | word[length-1] = 0; |
2820 | 1.81k | found = LookupDict2(tr, word, word1, ph_out, flags, end_flags, wtab, wtab_remaining); |
2821 | 2.87M | } else if ((end_flags & SUFX_D) && (word[length-1] == word[length-2])) { |
2822 | | // try removing a double letter |
2823 | 391 | word[length-1] = 0; |
2824 | 391 | found = LookupDict2(tr, word, word1, ph_out, flags, end_flags, wtab, wtab_remaining); |
2825 | 391 | } |
2826 | 2.87M | } |
2827 | | |
2828 | 6.27M | if (found) { |
2829 | | // if textmode is the default, then words which have phonemes are marked. |
2830 | 1.67M | if (tr->langopts.textmode) |
2831 | 3.64k | *flags ^= FLAG_TEXTMODE; |
2832 | | |
2833 | 1.67M | if (*flags & FLAG_TEXTMODE) { |
2834 | | // the word translates to replacement text, not to phonemes |
2835 | | |
2836 | 35.0k | if (end_flags & FLAG_ALLOW_TEXTMODE) { |
2837 | | // only use replacement text if this is the original word, not if a prefix or suffix has been removed |
2838 | 34.2k | word_replacement[0] = 0; |
2839 | 34.2k | word_replacement[1] = ' '; |
2840 | 34.2k | sprintf(&word_replacement[2], "%s ", ph_out); // replacement word, preceded by zerochar and space |
2841 | | |
2842 | 34.2k | word1 = *wordptr; |
2843 | 34.2k | *wordptr = &word_replacement[2]; |
2844 | | |
2845 | 34.2k | if (option_phonemes & espeakPHONEMES_TRACE) { |
2846 | 0 | len = found - word1; |
2847 | 0 | memcpy(word, word1, len); // include multiple matching words |
2848 | 0 | word[len] = 0; |
2849 | 0 | fprintf(f_trans, "Replace: %s %s\n", word, *wordptr); |
2850 | 0 | } |
2851 | 34.2k | } |
2852 | | |
2853 | 35.0k | ph_out[0] = 0; |
2854 | 35.0k | return 0; |
2855 | 35.0k | } |
2856 | | |
2857 | 1.64M | return 1; |
2858 | 1.67M | } |
2859 | | |
2860 | 4.59M | ph_out[0] = 0; |
2861 | 4.59M | return 0; |
2862 | 6.27M | } |
2863 | | |
2864 | | extern char word_phonemes[N_WORD_PHONEMES]; // a word translated into phoneme codes |
2865 | | |
2866 | | int Lookup(Translator *tr, const char *word, char *ph_out) |
2867 | 4.92M | { |
2868 | | // Look up in *_list, returns dictionary flags[0] and phonemes |
2869 | | |
2870 | 4.92M | int flags0; |
2871 | 4.92M | unsigned int flags[2]; |
2872 | 4.92M | char *word1 = (char *)word; |
2873 | | |
2874 | 4.92M | flags[0] = 0; |
2875 | 4.92M | flags[1] = FLAG_LOOKUP_SYMBOL; |
2876 | 4.92M | if ((flags0 = LookupDictList(tr, &word1, ph_out, flags, FLAG_ALLOW_TEXTMODE, NULL, 0)) != 0) |
2877 | 1.44M | flags0 = flags[0]; |
2878 | | |
2879 | 4.92M | if (flags[0] & FLAG_TEXTMODE) { |
2880 | 17.5k | int say_as = option_sayas; |
2881 | 17.5k | option_sayas = 0; // don't speak replacement word as letter names |
2882 | | // NOTE: TranslateRoman checks text[-2] and IsLetterGroup looks |
2883 | | // for a heading \0, so pad the start of text to prevent |
2884 | | // it reading data on the stack. |
2885 | 17.5k | char text[80]; |
2886 | | |
2887 | 17.5k | text[0] = 0; |
2888 | 17.5k | text[1] = ' '; |
2889 | 17.5k | text[2] = ' '; |
2890 | 17.5k | strncpy0(text+3, word1, sizeof(text)-3); |
2891 | 17.5k | flags0 = TranslateWord(tr, text+3, NULL, NULL); |
2892 | 17.5k | strcpy(ph_out, word_phonemes); |
2893 | 17.5k | option_sayas = say_as; |
2894 | 17.5k | } |
2895 | 4.92M | return flags0; |
2896 | 4.92M | } |
2897 | | |
2898 | | static int LookupFlags(Translator *tr, const char *word, unsigned int flags_out[2]) |
2899 | 664 | { |
2900 | 664 | char buf[100]; |
2901 | 664 | static unsigned int flags[2]; |
2902 | 664 | char *word1 = (char *)word; |
2903 | | |
2904 | 664 | flags[0] = flags[1] = 0; |
2905 | 664 | LookupDictList(tr, &word1, buf, flags, 0, NULL, 0); |
2906 | 664 | flags_out[0] = flags[0]; |
2907 | 664 | flags_out[1] = flags[1]; |
2908 | 664 | return flags[0]; |
2909 | 664 | } |
2910 | | |
2911 | | int RemoveEnding(Translator *tr, char *word, int end_type, char *word_copy) |
2912 | 14.4k | { |
2913 | | /* Removes a standard suffix from a word, once it has been indicated by the dictionary rules. |
2914 | | end_type: bits 0-6 number of letters |
2915 | | bits 8-14 suffix flags |
2916 | | |
2917 | | word_copy: make a copy of the original word |
2918 | | This routine is language specific. In English it deals with reversing y->i and e-dropping |
2919 | | that were done when the suffix was added to the original word. |
2920 | | */ |
2921 | | |
2922 | 14.4k | int i; |
2923 | 14.4k | char *word_end; |
2924 | 14.4k | int len_ending; |
2925 | 14.4k | int end_flags; |
2926 | 14.4k | char ending[50] = {0}; |
2927 | | |
2928 | | // these lists are language specific, but are only relevant if the 'e' suffix flag is used |
2929 | 14.4k | static const char * const add_e_exceptions[] = { |
2930 | 14.4k | "ion", NULL |
2931 | 14.4k | }; |
2932 | | |
2933 | 14.4k | static const char * const add_e_additions[] = { |
2934 | 14.4k | "c", "rs", "ir", "ur", "ath", "ns", "u", |
2935 | 14.4k | "spong", // sponge |
2936 | 14.4k | "rang", // strange |
2937 | 14.4k | "larg", // large |
2938 | 14.4k | NULL |
2939 | 14.4k | }; |
2940 | | |
2941 | 143k | for (word_end = word; *word_end != ' '; word_end++) { |
2942 | | // replace discarded 'e's |
2943 | 129k | if (*word_end == REPLACED_E) |
2944 | 122 | *word_end = 'e'; |
2945 | 129k | } |
2946 | 14.4k | i = word_end - word; |
2947 | 14.4k | if (i >= N_WORD_BYTES) i = N_WORD_BYTES-1; |
2948 | | |
2949 | 14.4k | if (word_copy != NULL) { |
2950 | 14.1k | memcpy(word_copy, word, i); |
2951 | 14.1k | word_copy[i] = 0; |
2952 | 14.1k | } |
2953 | | |
2954 | | // look for multibyte characters to increase the number of bytes to remove |
2955 | 40.1k | for (len_ending = i = (end_type & 0x3f); i > 0; i--) { // num.of characters of the suffix |
2956 | 25.6k | word_end--; |
2957 | 26.1k | while (word_end >= word && (*word_end & 0xc0) == 0x80) { |
2958 | 467 | word_end--; // for multibyte characters |
2959 | 467 | len_ending++; |
2960 | 467 | } |
2961 | 25.6k | } |
2962 | | |
2963 | | // remove bytes from the end of the word and replace them by spaces |
2964 | 40.6k | for (i = 0; (i < len_ending) && (i < (int)sizeof(ending)-1); i++) { |
2965 | 26.1k | ending[i] = word_end[i]; |
2966 | 26.1k | word_end[i] = ' '; |
2967 | 26.1k | } |
2968 | 14.4k | ending[i] = 0; |
2969 | 14.4k | word_end--; // now pointing at last character of stem |
2970 | | |
2971 | 14.4k | end_flags = (end_type & 0xfff0) | FLAG_SUFX; |
2972 | | |
2973 | | /* add an 'e' to the stem if appropriate, |
2974 | | if stem ends in vowel+consonant |
2975 | | or stem ends in 'c' (add 'e' to soften it) */ |
2976 | | |
2977 | 14.4k | if (end_type & SUFX_I) { |
2978 | 1.68k | if (word_end[0] == 'i') |
2979 | 121 | word_end[0] = 'y'; |
2980 | 1.68k | } |
2981 | | |
2982 | 14.4k | if (end_type & SUFX_E) { |
2983 | 2.93k | if (tr->translator_name == L('n', 'l')) { |
2984 | 112 | if (((word_end[0] & 0x80) == 0) && ((word_end[-1] & 0x80) == 0) && IsVowel(tr, word_end[-1]) && IsLetter(tr, word_end[0], LETTERGP_C) && !IsVowel(tr, word_end[-2])) { |
2985 | | // double the vowel before the (ascii) final consonant |
2986 | 100 | word_end[1] = word_end[0]; |
2987 | 100 | word_end[0] = word_end[-1]; |
2988 | 100 | word_end[2] = ' '; |
2989 | 100 | } |
2990 | 2.81k | } else if (tr->translator_name == L('e', 'n')) { |
2991 | | // add 'e' to end of stem |
2992 | 2.81k | if (IsLetter(tr, word_end[-1], LETTERGP_VOWEL2) && IsLetter(tr, word_end[0], 1)) { |
2993 | | // vowel(incl.'y') + hard.consonant |
2994 | | |
2995 | 2.03k | const char *p; |
2996 | 4.07k | for (i = 0; (p = add_e_exceptions[i]) != NULL; i++) { |
2997 | 2.03k | int len = strlen(p); |
2998 | 2.03k | if (word_end + 1-len >= word && memcmp(p, &word_end[1-len], len) == 0) |
2999 | 0 | break; |
3000 | 2.03k | } |
3001 | 2.03k | if (p == NULL) |
3002 | 2.03k | end_flags |= FLAG_SUFX_E_ADDED; // no exception found |
3003 | 2.03k | } else { |
3004 | 784 | const char *p; |
3005 | 8.49k | for (i = 0; (p = add_e_additions[i]) != NULL; i++) { |
3006 | 7.72k | int len = strlen(p); |
3007 | 7.72k | if (word_end + 1-len >= word && memcmp(p, &word_end[1-len], len) == 0) { |
3008 | 15 | end_flags |= FLAG_SUFX_E_ADDED; |
3009 | 15 | break; |
3010 | 15 | } |
3011 | 7.72k | } |
3012 | 784 | } |
3013 | 2.81k | } else if (tr->langopts.suffix_add_e != 0) |
3014 | 0 | end_flags |= FLAG_SUFX_E_ADDED; |
3015 | | |
3016 | 2.93k | if (end_flags & FLAG_SUFX_E_ADDED) { |
3017 | 2.05k | utf8_out(tr->langopts.suffix_add_e, &word_end[1]); |
3018 | | |
3019 | 2.05k | if (option_phonemes & espeakPHONEMES_TRACE) |
3020 | 0 | fprintf(f_trans, "add e\n"); |
3021 | 2.05k | } |
3022 | 2.93k | } |
3023 | | |
3024 | 14.4k | if ((end_type & SUFX_V) && (tr->expect_verb == 0)) |
3025 | 3.51k | tr->expect_verb = 1; // this suffix indicates the verb pronunciation |
3026 | | |
3027 | | |
3028 | 14.4k | if ((strcmp(ending, "s") == 0) || (strcmp(ending, "es") == 0)) |
3029 | 3.90k | end_flags |= FLAG_SUFX_S; |
3030 | | |
3031 | 14.4k | if (ending[0] == '\'') |
3032 | 1.59k | end_flags &= ~FLAG_SUFX; // don't consider 's as an added suffix |
3033 | | |
3034 | 14.4k | return end_flags; |
3035 | 14.4k | } |
3036 | | |
3037 | 665 | static void DollarRule(char *word[], char *word_start, int consumed, int group_length, char word_buf[N_WORD_BYTES], Translator *tr, int command, int *failed, int *add_points) { |
3038 | | // $list or $p_alt |
3039 | | // make a copy of the word up to the post-match characters |
3040 | 665 | int ix = *word - word_start + consumed + group_length + 1; |
3041 | | |
3042 | 665 | if (ix+2 > N_WORD_BYTES) { |
3043 | 1 | *failed = 1; |
3044 | 1 | return; |
3045 | 1 | } |
3046 | | |
3047 | 664 | memcpy(word_buf, word_start-1, ix); |
3048 | 664 | word_buf[ix] = ' '; |
3049 | 664 | word_buf[ix+1] = 0; |
3050 | 664 | unsigned int flags[2]; |
3051 | 664 | LookupFlags(tr, &word_buf[1], flags); |
3052 | | |
3053 | 664 | if ((command == DOLLAR_LIST) && (flags[0] & FLAG_FOUND) && !(flags[1] & FLAG_ONLY)) |
3054 | 0 | *add_points = 23; |
3055 | 664 | else if (flags[0] & (1 << (BITNUM_FLAG_ALT + (command & 0xf)))) |
3056 | 243 | *add_points = 23; |
3057 | 421 | else |
3058 | 421 | *failed = 1; |
3059 | 664 | } |