Coverage Report

Created: 2026-09-14 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}