Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/extensions/spellcheck/hunspell/src/./../../../../../extensions/spellcheck/hunspell/src/affixmgr.cxx
Warning:line 4475, column 13
Value stored to 'nummap' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_hunspell_src0.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/extensions/spellcheck/hunspell/src -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/extensions/spellcheck/hunspell/src -resource-dir /usr/lib/llvm-23/lib/clang/23 -include /root/firefox-clang/config/gcc_hidden.h -include /root/firefox-clang/obj-x86_64-pc-linux-gnu/mozilla-config.h -include hunspell_alloc_hooks.h -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -D _GLIBCXX_ASSERTIONS=1 -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D HUNSPELL_STATIC -D MOZ_HAS_MOZGLUE -D MOZILLA_INTERNAL_API -D IMPL_LIBXUL -D MOZ_SUPPORT_LEAKCHECKING -D STATIC_EXPORTABLE_JS_API -I /root/firefox-clang/extensions/spellcheck/hunspell/src -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/extensions/spellcheck/hunspell/src -I /root/firefox-clang/extensions/spellcheck/hunspell/glue -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/ipc/ipdl/_ipdlheaders -I /root/firefox-clang/ipc/chromium/src -I /root/firefox-clang/third_party/abseil-cpp -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-23/lib/clang/23/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-error=pessimizing-move -Wno-error=large-by-value-copy=128 -Wno-error=implicit-int-float-conversion -Wno-error=thread-safety-analysis -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -Wno-character-conversion -Wno-implicit-fallthrough -std=gnu++20 -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -fdiagnostics-absolute-paths -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -mllvm -dwarf-linkage-names=Abstract -faddrsig -fdwarf2-cfi-asm -o /tmp/scan-build-2026-10-17-231001-351276-1 -x c++ Unified_cpp_hunspell_src0.cpp
1/* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * Copyright (C) 2002-2022 Németh László
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * Hunspell is based on MySpell which is Copyright (C) 2002 Kevin Hendricks.
17 *
18 * Contributor(s): David Einstein, Davide Prina, Giuseppe Modugno,
19 * Gianluca Turconi, Simon Brouwer, Noll János, Bíró Árpád,
20 * Goldman Eleonóra, Sarlós Tamás, Bencsáth Boldizsár, Halácsy Péter,
21 * Dvornik László, Gefferth András, Nagy Viktor, Varga Dániel, Chris Halls,
22 * Rene Engelhard, Bram Moolenaar, Dafydd Jones, Harri Pitkänen
23 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37/*
38 * Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada
39 * And Contributors. All rights reserved.
40 *
41 * Redistribution and use in source and binary forms, with or without
42 * modification, are permitted provided that the following conditions
43 * are met:
44 *
45 * 1. Redistributions of source code must retain the above copyright
46 * notice, this list of conditions and the following disclaimer.
47 *
48 * 2. Redistributions in binary form must reproduce the above copyright
49 * notice, this list of conditions and the following disclaimer in the
50 * documentation and/or other materials provided with the distribution.
51 *
52 * 3. All modifications to the source code must be clearly marked as
53 * such. Binary redistributions based on modified source code
54 * must be clearly marked as modified versions in the documentation
55 * and/or other materials provided with the distribution.
56 *
57 * THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS AND CONTRIBUTORS
58 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
59 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
60 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
61 * KEVIN B. HENDRICKS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
62 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
63 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
64 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
65 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
66 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
67 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
68 * SUCH DAMAGE.
69 */
70
71#include <cstdlib>
72#include <cstring>
73#include <cstdio>
74#include <cctype>
75#include <ctime>
76
77#include <algorithm>
78#include <chrono>
79#include <memory>
80#include <limits>
81#include <string>
82#include <unordered_set>
83#include <vector>
84
85#include "affixmgr.hxx"
86#include "affentry.hxx"
87#include "hunspelltrace.hxx"
88#include "langnum.hxx"
89
90#include "csutil.hxx"
91
92namespace {
93 // the distinct records of one analysis, in the order they were first added. a record is a run of
94 // text between two breakchar separators
95 class DistinctRecords {
96 public:
97 explicit DistinctRecords(char breakchar) : sep(breakchar) {}
98
99 // add each record of add that is not held already
100 void append(const std::string& add) {
101 size_t pos = 0;
102 while (pos < add.size()) {
103 size_t end = add.find(sep, pos);
104 size_t len = (end == std::string::npos ? add.size() : end) - pos;
105 if (len) {
106 std::string record(add, pos, len);
107 if (seen.insert(record).second)
108 records.push_back(std::move(record));
109 }
110 if (end == std::string::npos)
111 break;
112 pos = end + 1;
113 }
114 }
115
116 // the records joined back into one string, each one terminated by breakchar
117 std::string join() const {
118 std::string text;
119 for (const std::string& record : records) {
120 text.append(record);
121 text.push_back(sep);
122 }
123 return text;
124 }
125
126 private:
127 std::vector<std::string> records;
128 // the same records again, to answer "have I seen this one" without a walk over records
129 std::unordered_set<std::string> seen;
130 char sep;
131 };
132}
133
134AffixMgr::AffixMgr(const char* affpath,
135 const std::vector<std::unique_ptr<HashMgr>>& ptr,
136 const char* key)
137 : alldic(ptr)
138 , pHMgr(ptr[0].get()) {
139
140 // register hash manager and load affix data from aff file
141 csconv = nullptr;
142 utf8 = 0;
143 complexprefixes = 0;
144 parsedmaptable = false;
145 parsedbreaktable = false;
146 iconvtable = nullptr;
147 oconvtable = nullptr;
148 // allow simplified compound forms (see 3rd field of CHECKCOMPOUNDPATTERN)
149 simplifiedcpd = 0;
150 parsedcheckcpd = false;
151 parseddefcpd = false;
152 phone = nullptr;
153 compoundflag = FLAG_NULL0x00; // permits word in compound forms
154 compoundbegin = FLAG_NULL0x00; // may be first word in compound forms
155 compoundmiddle = FLAG_NULL0x00; // may be middle word in compound forms
156 compoundend = FLAG_NULL0x00; // may be last word in compound forms
157 compoundroot = FLAG_NULL0x00; // compound word signing flag
158 compoundpermitflag = FLAG_NULL0x00; // compound permitting flag for suffixed word
159 compoundforbidflag = FLAG_NULL0x00; // compound fordidden flag for suffixed word
160 compoundmoresuffixes = 0; // allow more suffixes within compound words
161 checkcompounddup = 0; // forbid double words in compounds
162 checkcompoundrep = 0; // forbid bad compounds (may be non-compound word with
163 // a REP substitution)
164 checkcompoundcase =
165 0; // forbid upper and lowercase combinations at word bounds
166 checkcompoundtriple = 0; // forbid compounds with triple letters
167 simplifiedtriple = 0; // allow simplified triple letters in compounds
168 // (Schiff+fahrt -> Schiffahrt)
169 forbiddenword = FORBIDDENWORD65510; // forbidden word signing flag
170 nosuggest = FLAG_NULL0x00; // don't suggest words signed with NOSUGGEST flag
171 nongramsuggest = FLAG_NULL0x00;
172 langnum = 0; // language code (see http://l10n.openoffice.org/languages.html)
173 needaffix = FLAG_NULL0x00; // forbidden root, allowed only with suffixes
174 cpdwordmax = -1; // default: unlimited wordcount in compound words
175 cpdmin = -1; // undefined
176 cpdmaxsyllable = 0; // default: unlimited syllablecount in compound words
177 pfxappnd = nullptr; // previous prefix for counting syllables of the prefix BUG
178 sfxappnd = nullptr; // previous suffix for counting syllables of the suffix BUG
179 sfxextra = 0; // modifier for syllable count of sfxappnd BUG
180 checknum = 0; // checking numbers, and word with numbers
181 havecontclass = 0; // flags of possible continuing classes (double affix)
182 // LEMMA_PRESENT: not put root into the morphological output. Lemma presents
183 // in morhological description in dictionary file. It's often combined with
184 // PSEUDOROOT.
185 lemma_present = FLAG_NULL0x00;
186 circumfix = FLAG_NULL0x00;
187 onlyincompound = FLAG_NULL0x00;
188 maxngramsugs = -1; // undefined
189 maxdiff = -1; // undefined
190 onlymaxdiff = 0;
191 maxcpdsugs = -1; // undefined
192 nosplitsugs = 0;
193 sugswithdots = 0;
194 keepcase = 0;
195 forceucase = 0;
196 warn = 0;
197 forbidwarn = 0;
198 checksharps = 0;
199 substandard = FLAG_NULL0x00;
200 fullstrip = 0;
201
202 sfx = nullptr;
203 pfx = nullptr;
204
205 for (int i = 0; i < SETSIZE256; i++) {
206 pStart[i] = nullptr;
207 sStart[i] = nullptr;
208 pFlag[i] = nullptr;
209 sFlag[i] = nullptr;
210 }
211
212 memset(contclasses, 0, CONTSIZE65536 * sizeof(char));
213
214 if (parse_file(affpath, key)) {
215 fprintf(stderrstderr, "Failure loading aff file %s\n", affpath);
216 }
217
218 /* get encoding for CHECKCOMPOUNDCASE */
219 if (!utf8) {
220 csconv = get_current_cs(get_encoding());
221 for (int i = 0; i <= 255; i++) {
222 if ((csconv[i].cupper != csconv[i].clower) &&
223 (wordchars.find((char)i) == std::string::npos)) {
224 wordchars.push_back((char)i);
225 }
226 }
227 }
228
229 // default BREAK definition
230 if (!parsedbreaktable) {
231 breaktable.emplace_back("-");
232 breaktable.emplace_back("^-");
233 breaktable.emplace_back("-$");
234 parsedbreaktable = true;
235 }
236
237#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
238 // not entirely sure this is invalid, so only for fuzzing for now
239 if (iconvtable && !iconvtable->check_against_breaktable(breaktable)) {
240 delete iconvtable;
241 iconvtable = nullptr;
242 }
243#endif
244
245 if (cpdmin == -1)
246 cpdmin = MINCPDLEN3;
247}
248
249AffixMgr::~AffixMgr() {
250 // pass through linked prefix entries and clean up
251 for (int i = 0; i < SETSIZE256; i++) {
252 pFlag[i] = nullptr;
253 PfxEntry* ptr = pStart[i];
254 PfxEntry* nptr = nullptr;
255 while (ptr) {
256 nptr = ptr->getNext();
257 delete (ptr);
258 ptr = nptr;
259 }
260 }
261
262 // pass through linked suffix entries and clean up
263 for (int j = 0; j < SETSIZE256; j++) {
264 sFlag[j] = nullptr;
265 SfxEntry* ptr = sStart[j];
266 SfxEntry* nptr = nullptr;
267 while (ptr) {
268 nptr = ptr->getNext();
269 delete (ptr);
270 ptr = nptr;
271 }
272 sStart[j] = nullptr;
273 }
274
275 delete iconvtable;
276 delete oconvtable;
277 delete phone;
278
279 FREE_FLAG(compoundflag)compoundflag = 0;
280 FREE_FLAG(compoundbegin)compoundbegin = 0;
281 FREE_FLAG(compoundmiddle)compoundmiddle = 0;
282 FREE_FLAG(compoundend)compoundend = 0;
283 FREE_FLAG(compoundpermitflag)compoundpermitflag = 0;
284 FREE_FLAG(compoundforbidflag)compoundforbidflag = 0;
285 FREE_FLAG(compoundroot)compoundroot = 0;
286 FREE_FLAG(forbiddenword)forbiddenword = 0;
287 FREE_FLAG(nosuggest)nosuggest = 0;
288 FREE_FLAG(nongramsuggest)nongramsuggest = 0;
289 FREE_FLAG(needaffix)needaffix = 0;
290 FREE_FLAG(lemma_present)lemma_present = 0;
291 FREE_FLAG(circumfix)circumfix = 0;
292 FREE_FLAG(onlyincompound)onlyincompound = 0;
293
294 cpdwordmax = 0;
295 pHMgr = nullptr;
296 cpdmin = 0;
297 cpdmaxsyllable = 0;
298 checknum = 0;
299#ifdef MOZILLA_CLIENT1
300 delete[] csconv;
301#endif
302}
303
304void AffixMgr::finishFileMgr(FileMgr* afflst) {
305 delete afflst;
306
307 // convert affix trees to sorted list
308 process_pfx_tree_to_list();
309 process_sfx_tree_to_list();
310}
311
312// read in aff file and build up prefix and suffix entry objects
313int AffixMgr::parse_file(const char* affpath, const char* key) {
314
315 // checking flag duplication
316 char dupflags[CONTSIZE65536];
317 char dupflags_ini = 1;
318
319 // first line indicator for removing byte order mark
320 int firstline = 1;
321
322 // open the affix file
323 FileMgr* afflst = new FileMgr(affpath, key);
324
325 // step one is to parse the affix file building up the internal
326 // affix data structures
327
328 // read in each line ignoring any that do not
329 // start with a known line type indicator
330 std::string line;
331 while (afflst->getline(line)) {
332 mychomp(line);
333
334 /* remove byte order mark */
335 if (firstline) {
336 firstline = 0;
337 // Affix file begins with byte order mark: possible incompatibility with
338 // old Hunspell versions
339 if (line.compare(0, 3, "\xEF\xBB\xBF", 3) == 0) {
340 line.erase(0, 3);
341 }
342 }
343
344 /* parse in the keyboard string */
345 if (line.compare(0, 3, "KEY", 3) == 0) {
346 if (!parse_string(line, keystring, afflst->getlinenum())) {
347 finishFileMgr(afflst);
348 return 1;
349 }
350 }
351
352 /* parse in the try string */
353 if (line.compare(0, 3, "TRY", 3) == 0) {
354 if (!parse_string(line, trystring, afflst->getlinenum())) {
355 finishFileMgr(afflst);
356 return 1;
357 }
358 }
359
360 /* parse in the name of the character set used by the .dict and .aff */
361 if (line.compare(0, 3, "SET", 3) == 0) {
362 if (!parse_string(line, encoding, afflst->getlinenum())) {
363 finishFileMgr(afflst);
364 return 1;
365 }
366 if (encoding == "UTF-8") {
367 utf8 = 1;
368 }
369 }
370
371 /* parse COMPLEXPREFIXES for agglutinative languages with right-to-left
372 * writing system */
373 if (line.compare(0, 15, "COMPLEXPREFIXES", 15) == 0)
374 complexprefixes = 1;
375
376 /* parse in the flag used by the controlled compound words */
377 if (line.compare(0, 12, "COMPOUNDFLAG", 12) == 0) {
378 if (!parse_flag(line, &compoundflag, afflst)) {
379 finishFileMgr(afflst);
380 return 1;
381 }
382 }
383
384 /* parse in the flag used by compound words */
385 if (line.compare(0, 13, "COMPOUNDBEGIN", 13) == 0) {
386 if (complexprefixes) {
387 if (!parse_flag(line, &compoundend, afflst)) {
388 finishFileMgr(afflst);
389 return 1;
390 }
391 } else {
392 if (!parse_flag(line, &compoundbegin, afflst)) {
393 finishFileMgr(afflst);
394 return 1;
395 }
396 }
397 }
398
399 /* parse in the flag used by compound words */
400 if (line.compare(0, 14, "COMPOUNDMIDDLE", 14) == 0) {
401 if (!parse_flag(line, &compoundmiddle, afflst)) {
402 finishFileMgr(afflst);
403 return 1;
404 }
405 }
406
407 /* parse in the flag used by compound words */
408 if (line.compare(0, 11, "COMPOUNDEND", 11) == 0) {
409 if (complexprefixes) {
410 if (!parse_flag(line, &compoundbegin, afflst)) {
411 finishFileMgr(afflst);
412 return 1;
413 }
414 } else {
415 if (!parse_flag(line, &compoundend, afflst)) {
416 finishFileMgr(afflst);
417 return 1;
418 }
419 }
420 }
421
422 /* parse in the data used by compound_check() method */
423 if (line.compare(0, 15, "COMPOUNDWORDMAX", 15) == 0) {
424 if (!parse_num(line, &cpdwordmax, afflst)) {
425 finishFileMgr(afflst);
426 return 1;
427 }
428 }
429
430 /* parse in the flag sign compounds in dictionary */
431 if (line.compare(0, 12, "COMPOUNDROOT", 12) == 0) {
432 if (!parse_flag(line, &compoundroot, afflst)) {
433 finishFileMgr(afflst);
434 return 1;
435 }
436 }
437
438 /* parse in the flag used by compound_check() method */
439 if (line.compare(0, 18, "COMPOUNDPERMITFLAG", 18) == 0) {
440 if (!parse_flag(line, &compoundpermitflag, afflst)) {
441 finishFileMgr(afflst);
442 return 1;
443 }
444 }
445
446 /* parse in the flag used by compound_check() method */
447 if (line.compare(0, 18, "COMPOUNDFORBIDFLAG", 18) == 0) {
448 if (!parse_flag(line, &compoundforbidflag, afflst)) {
449 finishFileMgr(afflst);
450 return 1;
451 }
452 }
453
454 if (line.compare(0, 20, "COMPOUNDMORESUFFIXES", 20) == 0) {
455 compoundmoresuffixes = 1;
456 }
457
458 if (line.compare(0, 16, "CHECKCOMPOUNDDUP", 16) == 0) {
459 checkcompounddup = 1;
460 }
461
462 if (line.compare(0, 16, "CHECKCOMPOUNDREP", 16) == 0) {
463 checkcompoundrep = 1;
464 }
465
466 if (line.compare(0, 19, "CHECKCOMPOUNDTRIPLE", 19) == 0) {
467 checkcompoundtriple = 1;
468 }
469
470 if (line.compare(0, 16, "SIMPLIFIEDTRIPLE", 16) == 0) {
471 simplifiedtriple = 1;
472 }
473
474 if (line.compare(0, 17, "CHECKCOMPOUNDCASE", 17) == 0) {
475 checkcompoundcase = 1;
476 }
477
478 if (line.compare(0, 9, "NOSUGGEST", 9) == 0) {
479 if (!parse_flag(line, &nosuggest, afflst)) {
480 finishFileMgr(afflst);
481 return 1;
482 }
483 }
484
485 if (line.compare(0, 14, "NONGRAMSUGGEST", 14) == 0) {
486 if (!parse_flag(line, &nongramsuggest, afflst)) {
487 finishFileMgr(afflst);
488 return 1;
489 }
490 }
491
492 /* parse in the flag used by forbidden words */
493 if (line.compare(0, 13, "FORBIDDENWORD", 13) == 0) {
494 if (!parse_flag(line, &forbiddenword, afflst)) {
495 finishFileMgr(afflst);
496 return 1;
497 }
498 }
499
500 /* parse in the flag used by forbidden words (is deprecated) */
501 if (line.compare(0, 13, "LEMMA_PRESENT", 13) == 0) {
502 if (!parse_flag(line, &lemma_present, afflst)) {
503 finishFileMgr(afflst);
504 return 1;
505 }
506 }
507
508 /* parse in the flag used by circumfixes */
509 if (line.compare(0, 9, "CIRCUMFIX", 9) == 0) {
510 if (!parse_flag(line, &circumfix, afflst)) {
511 finishFileMgr(afflst);
512 return 1;
513 }
514 }
515
516 /* parse in the flag used by fogemorphemes */
517 if (line.compare(0, 14, "ONLYINCOMPOUND", 14) == 0) {
518 if (!parse_flag(line, &onlyincompound, afflst)) {
519 finishFileMgr(afflst);
520 return 1;
521 }
522 }
523
524 /* parse in the flag used by `needaffixs' (is deprecated) */
525 if (line.compare(0, 10, "PSEUDOROOT", 10) == 0) {
526 if (!parse_flag(line, &needaffix, afflst)) {
527 finishFileMgr(afflst);
528 return 1;
529 }
530 }
531
532 /* parse in the flag used by `needaffixs' */
533 if (line.compare(0, 9, "NEEDAFFIX", 9) == 0) {
534 if (!parse_flag(line, &needaffix, afflst)) {
535 finishFileMgr(afflst);
536 return 1;
537 }
538 }
539
540 /* parse in the minimal length for words in compounds */
541 if (line.compare(0, 11, "COMPOUNDMIN", 11) == 0) {
542 if (!parse_num(line, &cpdmin, afflst)) {
543 finishFileMgr(afflst);
544 return 1;
545 }
546 if (cpdmin < 1)
547 cpdmin = 1;
548 }
549
550 /* parse in the max. words and syllables in compounds */
551 if (line.compare(0, 16, "COMPOUNDSYLLABLE", 16) == 0) {
552 if (!parse_cpdsyllable(line, afflst)) {
553 finishFileMgr(afflst);
554 return 1;
555 }
556 }
557
558 /* parse in the flag used by compound_check() method */
559 if (line.compare(0, 11, "SYLLABLENUM", 11) == 0) {
560 if (!parse_string(line, cpdsyllablenum, afflst->getlinenum())) {
561 finishFileMgr(afflst);
562 return 1;
563 }
564 }
565
566 /* parse in the flag used by the controlled compound words */
567 if (line.compare(0, 8, "CHECKNUM", 8) == 0) {
568 checknum = 1;
569 }
570
571 /* parse in the extra word characters */
572 if (line.compare(0, 9, "WORDCHARS", 9) == 0) {
573 if (!parse_array(line, wordchars, wordchars_utf16,
574 utf8, afflst->getlinenum())) {
575 finishFileMgr(afflst);
576 return 1;
577 }
578 }
579
580 /* parse in the ignored characters (for example, Arabic optional diacretics
581 * charachters */
582 if (line.compare(0, 6, "IGNORE", 6) == 0) {
583 if (!parse_array(line, ignorechars, ignorechars_utf16,
584 utf8, afflst->getlinenum())) {
585 finishFileMgr(afflst);
586 return 1;
587 }
588 }
589
590 /* parse in the input conversion table */
591 if (line.compare(0, 5, "ICONV", 5) == 0) {
592 if (!parse_convtable(line, afflst, &iconvtable, "ICONV")) {
593 finishFileMgr(afflst);
594 return 1;
595 }
596 }
597
598 /* parse in the output conversion table */
599 if (line.compare(0, 5, "OCONV", 5) == 0) {
600 if (!parse_convtable(line, afflst, &oconvtable, "OCONV")) {
601 finishFileMgr(afflst);
602 return 1;
603 }
604 }
605
606 /* parse in the phonetic translation table */
607 if (line.compare(0, 5, "PHONE", 5) == 0) {
608 if (!parse_phonetable(line, afflst)) {
609 finishFileMgr(afflst);
610 return 1;
611 }
612 }
613
614 /* parse in the checkcompoundpattern table */
615 if (line.compare(0, 20, "CHECKCOMPOUNDPATTERN", 20) == 0) {
616 if (!parse_checkcpdtable(line, afflst)) {
617 finishFileMgr(afflst);
618 return 1;
619 }
620 }
621
622 /* parse in the defcompound table */
623 if (line.compare(0, 12, "COMPOUNDRULE", 12) == 0) {
624 if (!parse_defcpdtable(line, afflst)) {
625 finishFileMgr(afflst);
626 return 1;
627 }
628 }
629
630 /* parse in the related character map table */
631 if (line.compare(0, 3, "MAP", 3) == 0) {
632 if (!parse_maptable(line, afflst)) {
633 finishFileMgr(afflst);
634 return 1;
635 }
636 }
637
638 /* parse in the word breakpoints table */
639 if (line.compare(0, 5, "BREAK", 5) == 0) {
640 if (!parse_breaktable(line, afflst)) {
641 finishFileMgr(afflst);
642 return 1;
643 }
644 }
645
646 /* parse in the language for language specific codes */
647 if (line.compare(0, 4, "LANG", 4) == 0) {
648 if (!parse_string(line, lang, afflst->getlinenum())) {
649 finishFileMgr(afflst);
650 return 1;
651 }
652 langnum = get_lang_num(lang);
653 }
654
655 if (line.compare(0, 7, "VERSION", 7) == 0) {
656 size_t startpos = line.find_first_not_of(" \t", 7);
657 if (startpos != std::string::npos) {
658 version = line.substr(startpos);
659 }
660 }
661
662 if (line.compare(0, 12, "MAXNGRAMSUGS", 12) == 0) {
663 if (!parse_num(line, &maxngramsugs, afflst)) {
664 finishFileMgr(afflst);
665 return 1;
666 }
667 }
668
669 if (line.compare(0, 11, "ONLYMAXDIFF", 11) == 0)
670 onlymaxdiff = 1;
671
672 if (line.compare(0, 7, "MAXDIFF", 7) == 0) {
673 if (!parse_num(line, &maxdiff, afflst)) {
674 finishFileMgr(afflst);
675 return 1;
676 }
677 }
678
679 if (line.compare(0, 10, "MAXCPDSUGS", 10) == 0) {
680 if (!parse_num(line, &maxcpdsugs, afflst)) {
681 finishFileMgr(afflst);
682 return 1;
683 }
684 }
685
686 if (line.compare(0, 11, "NOSPLITSUGS", 11) == 0) {
687 nosplitsugs = 1;
688 }
689
690 if (line.compare(0, 9, "FULLSTRIP", 9) == 0) {
691 fullstrip = 1;
692 }
693
694 if (line.compare(0, 12, "SUGSWITHDOTS", 12) == 0) {
695 sugswithdots = 1;
696 }
697
698 /* parse in the flag used by forbidden words */
699 if (line.compare(0, 8, "KEEPCASE", 8) == 0) {
700 if (!parse_flag(line, &keepcase, afflst)) {
701 finishFileMgr(afflst);
702 return 1;
703 }
704 }
705
706 /* parse in the flag used by `forceucase' */
707 if (line.compare(0, 10, "FORCEUCASE", 10) == 0) {
708 if (!parse_flag(line, &forceucase, afflst)) {
709 finishFileMgr(afflst);
710 return 1;
711 }
712 }
713
714 /* parse in the flag used by `warn' */
715 if (line.compare(0, 4, "WARN", 4) == 0) {
716 if (!parse_flag(line, &warn, afflst)) {
717 finishFileMgr(afflst);
718 return 1;
719 }
720 }
721
722 if (line.compare(0, 10, "FORBIDWARN", 10) == 0) {
723 forbidwarn = 1;
724 }
725
726 /* parse in the flag used by the affix generator */
727 if (line.compare(0, 11, "SUBSTANDARD", 11) == 0) {
728 if (!parse_flag(line, &substandard, afflst)) {
729 finishFileMgr(afflst);
730 return 1;
731 }
732 }
733
734 if (line.compare(0, 11, "CHECKSHARPS", 11) == 0) {
735 checksharps = 1;
736 }
737
738 /* parse this affix: P - prefix, S - suffix */
739 // affix type
740 char ft = ' ';
741 if (line.compare(0, 3, "PFX", 3) == 0)
742 ft = complexprefixes ? 'S' : 'P';
743 if (line.compare(0, 3, "SFX", 3) == 0)
744 ft = complexprefixes ? 'P' : 'S';
745 if (ft != ' ') {
746 if (dupflags_ini) {
747 memset(dupflags, 0, sizeof(dupflags));
748 dupflags_ini = 0;
749 }
750 if (!parse_affix(line, ft, afflst, dupflags)) {
751 finishFileMgr(afflst);
752 return 1;
753 }
754 }
755 }
756
757 finishFileMgr(afflst);
758 // affix trees are sorted now
759
760 // now we can speed up performance greatly taking advantage of the
761 // relationship between the affixes and the idea of "subsets".
762
763 // View each prefix as a potential leading subset of another and view
764 // each suffix (reversed) as a potential trailing subset of another.
765
766 // To illustrate this relationship if we know the prefix "ab" is found in the
767 // word to examine, only prefixes that "ab" is a leading subset of need be
768 // examined.
769 // Furthermore is "ab" is not present then none of the prefixes that "ab" is
770 // is a subset need be examined.
771 // The same argument goes for suffix string that are reversed.
772
773 // Then to top this off why not examine the first char of the word to quickly
774 // limit the set of prefixes to examine (i.e. the prefixes to examine must
775 // be leading supersets of the first character of the word (if they exist)
776
777 // To take advantage of this "subset" relationship, we need to add two links
778 // from entry. One to take next if the current prefix is found (call it
779 // nexteq)
780 // and one to take next if the current prefix is not found (call it nextne).
781
782 // Since we have built ordered lists, all that remains is to properly
783 // initialize
784 // the nextne and nexteq pointers that relate them
785
786 process_pfx_order();
787 process_sfx_order();
788
789 return 0;
790}
791
792// we want to be able to quickly access prefix information
793// both by prefix flag, and sorted by prefix string itself
794// so we need to set up two indexes
795
796int AffixMgr::build_pfxtree(PfxEntry* pfxptr) {
797 PfxEntry* ptr;
798 PfxEntry* pptr;
799 PfxEntry* ep = pfxptr;
800
801 // get the right starting points
802 const char* key = ep->getKey();
803 const auto flg = (unsigned char)(ep->getFlag() & 0x00FF);
804
805 // first index by flag which must exist
806 ptr = pFlag[flg];
807 ep->setFlgNxt(ptr);
808 pFlag[flg] = ep;
809
810 // handle the special case of null affix string
811 if (*key == '\0') {
812 // always inset them at head of list at element 0
813 ptr = pStart[0];
814 ep->setNext(ptr);
815 pStart[0] = ep;
816 return 0;
817 }
818
819 // now handle the normal case
820 ep->setNextEQ(nullptr);
821 ep->setNextNE(nullptr);
822
823 unsigned char sp = *((const unsigned char*)key);
824 ptr = pStart[sp];
825
826 // handle the first insert
827 if (!ptr) {
828 pStart[sp] = ep;
829 return 0;
830 }
831
832 // otherwise use binary tree insertion so that a sorted
833 // list can easily be generated later
834 pptr = nullptr;
835 for (;;) {
836 pptr = ptr;
837 if (strcmp(ep->getKey(), ptr->getKey()) <= 0) {
838 ptr = ptr->getNextEQ();
839 if (!ptr) {
840 pptr->setNextEQ(ep);
841 break;
842 }
843 } else {
844 ptr = ptr->getNextNE();
845 if (!ptr) {
846 pptr->setNextNE(ep);
847 break;
848 }
849 }
850 }
851 return 0;
852}
853
854// we want to be able to quickly access suffix information
855// both by suffix flag, and sorted by the reverse of the
856// suffix string itself; so we need to set up two indexes
857int AffixMgr::build_sfxtree(SfxEntry* sfxptr) {
858
859 sfxptr->initReverseWord();
860
861 SfxEntry* ptr;
862 SfxEntry* pptr;
863 SfxEntry* ep = sfxptr;
864
865 /* get the right starting point */
866 const char* key = ep->getKey();
867 const auto flg = (unsigned char)(ep->getFlag() & 0x00FF);
868
869 // first index by flag which must exist
870 ptr = sFlag[flg];
871 ep->setFlgNxt(ptr);
872 sFlag[flg] = ep;
873
874 // next index by affix string
875
876 // handle the special case of null affix string
877 if (*key == '\0') {
878 // always inset them at head of list at element 0
879 ptr = sStart[0];
880 ep->setNext(ptr);
881 sStart[0] = ep;
882 return 0;
883 }
884
885 // now handle the normal case
886 ep->setNextEQ(nullptr);
887 ep->setNextNE(nullptr);
888
889 unsigned char sp = *((const unsigned char*)key);
890 ptr = sStart[sp];
891
892 // handle the first insert
893 if (!ptr) {
894 sStart[sp] = ep;
895 return 0;
896 }
897
898 // otherwise use binary tree insertion so that a sorted
899 // list can easily be generated later
900 pptr = nullptr;
901 for (;;) {
902 pptr = ptr;
903 if (strcmp(ep->getKey(), ptr->getKey()) <= 0) {
904 ptr = ptr->getNextEQ();
905 if (!ptr) {
906 pptr->setNextEQ(ep);
907 break;
908 }
909 } else {
910 ptr = ptr->getNextNE();
911 if (!ptr) {
912 pptr->setNextNE(ep);
913 break;
914 }
915 }
916 }
917 return 0;
918}
919
920// convert from binary tree to sorted list
921int AffixMgr::process_pfx_tree_to_list() {
922 for (int i = 1; i < SETSIZE256; i++) {
923 pStart[i] = process_pfx_in_order(pStart[i], nullptr);
924 }
925 return 0;
926}
927
928PfxEntry* AffixMgr::process_pfx_in_order(PfxEntry* ptr, PfxEntry* nptr) {
929 if (ptr) {
930 nptr = process_pfx_in_order(ptr->getNextNE(), nptr);
931 ptr->setNext(nptr);
932 nptr = process_pfx_in_order(ptr->getNextEQ(), ptr);
933 }
934 return nptr;
935}
936
937// convert from binary tree to sorted list
938int AffixMgr::process_sfx_tree_to_list() {
939 for (int i = 1; i < SETSIZE256; i++) {
940 sStart[i] = process_sfx_in_order(sStart[i], nullptr);
941 }
942 return 0;
943}
944
945SfxEntry* AffixMgr::process_sfx_in_order(SfxEntry* ptr, SfxEntry* nptr) {
946 if (ptr) {
947 nptr = process_sfx_in_order(ptr->getNextNE(), nptr);
948 ptr->setNext(nptr);
949 nptr = process_sfx_in_order(ptr->getNextEQ(), ptr);
950 }
951 return nptr;
952}
953
954// reinitialize the PfxEntry links NextEQ and NextNE to speed searching
955// using the idea of leading subsets this time
956int AffixMgr::process_pfx_order() {
957 PfxEntry* ptr;
958
959 // loop through each prefix list starting point
960 for (int i = 1; i < SETSIZE256; i++) {
961 ptr = pStart[i];
962
963 // look through the remainder of the list
964 // and find next entry with affix that
965 // the current one is not a subset of
966 // mark that as destination for NextNE
967 // use next in list that you are a subset
968 // of as NextEQ
969
970 for (; ptr != nullptr; ptr = ptr->getNext()) {
971 PfxEntry* nptr = ptr->getNext();
972 for (; nptr != nullptr; nptr = nptr->getNext()) {
973 if (!isSubset(ptr->getKey(), nptr->getKey()))
974 break;
975 }
976 ptr->setNextNE(nptr);
977 ptr->setNextEQ(nullptr);
978 if ((ptr->getNext()) &&
979 isSubset(ptr->getKey(), (ptr->getNext())->getKey()))
980 ptr->setNextEQ(ptr->getNext());
981 }
982
983 // now clean up by adding smart search termination strings:
984 // if you are already a superset of the previous prefix
985 // but not a subset of the next, search can end here
986 // so set NextNE properly
987
988 ptr = pStart[i];
989 for (; ptr != nullptr; ptr = ptr->getNext()) {
990 PfxEntry* nptr = ptr->getNext();
991 PfxEntry* mptr = nullptr;
992 for (; nptr != nullptr; nptr = nptr->getNext()) {
993 if (!isSubset(ptr->getKey(), nptr->getKey()))
994 break;
995 mptr = nptr;
996 }
997 if (mptr)
998 mptr->setNextNE(nullptr);
999 }
1000 }
1001 return 0;
1002}
1003
1004// initialize the SfxEntry links NextEQ and NextNE to speed searching
1005// using the idea of leading subsets this time
1006int AffixMgr::process_sfx_order() {
1007 SfxEntry* ptr;
1008
1009 // loop through each prefix list starting point
1010 for (int i = 1; i < SETSIZE256; i++) {
1011 ptr = sStart[i];
1012
1013 // look through the remainder of the list
1014 // and find next entry with affix that
1015 // the current one is not a subset of
1016 // mark that as destination for NextNE
1017 // use next in list that you are a subset
1018 // of as NextEQ
1019
1020 for (; ptr != nullptr; ptr = ptr->getNext()) {
1021 SfxEntry* nptr = ptr->getNext();
1022 for (; nptr != nullptr; nptr = nptr->getNext()) {
1023 if (!isSubset(ptr->getKey(), nptr->getKey()))
1024 break;
1025 }
1026 ptr->setNextNE(nptr);
1027 ptr->setNextEQ(nullptr);
1028 if ((ptr->getNext()) &&
1029 isSubset(ptr->getKey(), (ptr->getNext())->getKey()))
1030 ptr->setNextEQ(ptr->getNext());
1031 }
1032
1033 // now clean up by adding smart search termination strings:
1034 // if you are already a superset of the previous suffix
1035 // but not a subset of the next, search can end here
1036 // so set NextNE properly
1037
1038 ptr = sStart[i];
1039 for (; ptr != nullptr; ptr = ptr->getNext()) {
1040 SfxEntry* nptr = ptr->getNext();
1041 SfxEntry* mptr = nullptr;
1042 for (; nptr != nullptr; nptr = nptr->getNext()) {
1043 if (!isSubset(ptr->getKey(), nptr->getKey()))
1044 break;
1045 mptr = nptr;
1046 }
1047 if (mptr)
1048 mptr->setNextNE(nullptr);
1049 }
1050 }
1051 return 0;
1052}
1053
1054// add flags to the result for dictionary debugging
1055std::string& AffixMgr::debugflag(std::string& result, unsigned short flag) {
1056 std::string st = encode_flag(flag);
1057 result.push_back(MSEP_FLD' ');
1058 result.append(MORPH_FLAG"fl:");
1059 result.append(st);
1060 return result;
1061}
1062
1063// Count match positions in an .aff condition pattern: each [...] group
1064// counts as one, each non-grouped codepoint counts as one.
1065int AffixMgr::condlen(const std::string& s) {
1066 int l = 0;
1067 bool group = false;
1068 size_t i = 0;
1069 while (i < s.size()) {
1070 if (s[i] == '[') {
1071 group = true;
1072 ++l;
1073 ++i;
1074 } else if (s[i] == ']') {
1075 group = false;
1076 ++i;
1077 } else if (group) {
1078 ++i;
1079 } else {
1080 ++l;
1081 i = utf8 ? utf8_next(s, i) : i + 1;
1082 }
1083 }
1084 return l;
1085}
1086
1087int AffixMgr::encodeit(AffEntry& entry, const std::string& cs) {
1088 if (cs.compare(".") != 0) {
1089 int n = condlen(cs);
1090 if (n > std::numeric_limits<unsigned char>::max()) {
1091 HUNSPELL_WARNING(stderrstderr, "error: condition length %d is over max limit\n", n);
1092 return 1;
1093 }
1094 entry.numconds = (unsigned char)n;
1095 const size_t cslen = cs.size();
1096 const size_t short_part = std::min<size_t>(MAXCONDLEN20, cslen);
1097 memcpy(entry.c.conds, cs.data(), short_part);
1098 if (short_part < MAXCONDLEN20) {
1099 //blank out the remaining space
1100 memset(entry.c.conds + short_part, 0, MAXCONDLEN20 - short_part);
1101 } else if (cs[MAXCONDLEN20]) {
1102 //there is more conditions than fit in fixed space, so its
1103 //a long condition
1104 entry.opts |= aeLONGCOND(1 << 4);
1105 size_t remaining = cs.size() - MAXCONDLEN_1(20 - sizeof(char*));
1106 entry.c.l.conds2 = new char[1 + remaining];
1107 memcpy(entry.c.l.conds2, cs.data() + MAXCONDLEN_1(20 - sizeof(char*)), remaining);
1108 entry.c.l.conds2[remaining] = 0;
1109 }
1110 } else {
1111 entry.numconds = 0;
1112 entry.c.conds[0] = '\0';
1113 }
1114 return 0;
1115}
1116
1117// return 1 if s1 is a leading subset of s2 (dots are for infixes)
1118inline int AffixMgr::isSubset(const char* s1, const char* s2) {
1119 while (((*s1 == *s2) || (*s1 == '.')) && (*s1 != '\0') && (*s2 != '\0')) {
1120 s1++;
1121 s2++;
1122 }
1123 return (*s1 == '\0');
1124}
1125
1126// check word for prefixes
1127struct hentry* AffixMgr::prefix_check(const std::string& word,
1128 int start,
1129 int len,
1130 char in_compound,
1131 AffixScratch& scratch,
1132 const FLAGunsigned short needflag,
1133 const FLAGunsigned short avoidflag) {
1134 struct hentry* rv = nullptr;
1135
1136 pfx = nullptr;
1137 pfxappnd = nullptr;
1138 sfxappnd = nullptr;
1139 sfxextra = 0;
1140
1141 TraceCtx* t = trace_on(scratch.trace);
1142 int candidates = 0;
1143
1144 // first handle the special case of 0 length prefixes
1145 PfxEntry* pe = pStart[0];
1146 while (pe) {
1147 if (
1148 // fogemorpheme
1149 ((in_compound != IN_CPD_NOT0) ||
1150 !(pe->getCont() &&
1151 (TESTAFF(pe->getCont(), onlyincompound, pe->getContLen())(std::binary_search(pe->getCont(), pe->getCont() + pe->
getContLen(), onlyincompound))
))) &&
1152 // permit prefixes in compounds
1153 ((in_compound != IN_CPD_END2) ||
1154 (pe->getCont() &&
1155 (TESTAFF(pe->getCont(), compoundpermitflag, pe->getContLen())(std::binary_search(pe->getCont(), pe->getCont() + pe->
getContLen(), compoundpermitflag))
)))) {
1156 // check prefix
1157 ++candidates;
1158 rv = pe->checkword(word, start, len, in_compound, needflag, scratch);
1159 // Skip a stem with the avoid flag and keep scanning the other prefixes.
1160 if (rv && avoidflag != FLAG_NULL0x00 && TESTAFF(rv->astr, avoidflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, avoidflag
))
) {
1161 trace_avoidflag(t, avoidflag, rv);
1162 rv = nullptr;
1163 }
1164 if (rv) {
1165 pfx = pe; // BUG: pfx not stateless
1166 return rv;
1167 }
1168 }
1169 pe = pe->getNext();
1170 }
1171
1172 // now handle the general case
1173 unsigned char sp = word[start];
1174 PfxEntry* pptr = pStart[sp];
1175
1176 while (pptr) {
1177 if (isSubset(pptr->getKey(), word.c_str() + start)) {
1178 if (
1179 // fogemorpheme
1180 ((in_compound != IN_CPD_NOT0) ||
1181 !(pptr->getCont() &&
1182 (TESTAFF(pptr->getCont(), onlyincompound, pptr->getContLen())(std::binary_search(pptr->getCont(), pptr->getCont() + pptr
->getContLen(), onlyincompound))
))) &&
1183 // permit prefixes in compounds
1184 ((in_compound != IN_CPD_END2) ||
1185 (pptr->getCont() && (TESTAFF(pptr->getCont(), compoundpermitflag,(std::binary_search(pptr->getCont(), pptr->getCont() + pptr
->getContLen(), compoundpermitflag))
1186 pptr->getContLen())(std::binary_search(pptr->getCont(), pptr->getCont() + pptr
->getContLen(), compoundpermitflag))
)))) {
1187 // check prefix
1188 ++candidates;
1189 rv = pptr->checkword(word, start, len, in_compound, needflag, scratch);
1190 if (rv && avoidflag != FLAG_NULL0x00 && TESTAFF(rv->astr, avoidflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, avoidflag
))
) {
1191 trace_avoidflag(t, avoidflag, rv);
1192 rv = nullptr;
1193 }
1194 if (rv) {
1195 pfx = pptr; // BUG: pfx not stateless
1196 return rv;
1197 }
1198 }
1199 pptr = pptr->getNextEQ();
1200 } else {
1201 pptr = pptr->getNextNE();
1202 }
1203 }
1204
1205 // a pass that had nothing to try says so, which is different from a pass that
1206 // did not run
1207 if (t && candidates == 0)
1208 trace(*t, "pfx \"%s\" candidates=0", word.substr(start, len).c_str());
1209
1210 return nullptr;
1211}
1212
1213// check word for prefixes and two-level suffixes
1214struct hentry* AffixMgr::prefix_check_twosfx(const std::string& word,
1215 int start,
1216 int len,
1217 char in_compound,
1218 AffixScratch& scratch,
1219 const FLAGunsigned short needflag) {
1220 struct hentry* rv = nullptr;
1221
1222 pfx = nullptr;
1223 sfxappnd = nullptr;
1224 sfxextra = 0;
1225
1226 // first handle the special case of 0 length prefixes
1227 PfxEntry* pe = pStart[0];
1228
1229 while (pe) {
1230 rv = pe->check_twosfx(word, start, len, in_compound, needflag, scratch);
1231 if (rv)
1232 return rv;
1233 pe = pe->getNext();
1234 }
1235
1236 // now handle the general case
1237 unsigned char sp = word[start];
1238 PfxEntry* pptr = pStart[sp];
1239
1240 while (pptr) {
1241 if (isSubset(pptr->getKey(), word.c_str() + start)) {
1242 rv = pptr->check_twosfx(word, start, len, in_compound, needflag, scratch);
1243 if (rv) {
1244 pfx = pptr;
1245 return rv;
1246 }
1247 pptr = pptr->getNextEQ();
1248 } else {
1249 pptr = pptr->getNextNE();
1250 }
1251 }
1252
1253 return nullptr;
1254}
1255
1256// check word for prefixes and morph
1257std::string AffixMgr::prefix_check_morph(const std::string& word,
1258 int start,
1259 int len,
1260 char in_compound,
1261 AffixScratch& scratch,
1262 const FLAGunsigned short needflag) {
1263
1264 // many prefixes can analyse a word the same way
1265 DistinctRecords result(MSEP_REC'\n');
1266
1267 pfx = nullptr;
1268 sfxappnd = nullptr;
1269 sfxextra = 0;
1270
1271 // first handle the special case of 0 length prefixes
1272 PfxEntry* pe = pStart[0];
1273 while (pe) {
1274 std::string st = pe->check_morph(word, start, len, in_compound, needflag, scratch);
1275 if (!st.empty()) {
1276 result.append(st);
1277 }
1278 pe = pe->getNext();
1279 }
1280
1281 // now handle the general case
1282 unsigned char sp = word[start];
1283 PfxEntry* pptr = pStart[sp];
1284
1285 while (pptr) {
1286 if (isSubset(pptr->getKey(), word.c_str() + start)) {
1287 std::string st = pptr->check_morph(word, start, len, in_compound, needflag, scratch);
1288 if (!st.empty()) {
1289 // fogemorpheme
1290 if ((in_compound != IN_CPD_NOT0) ||
1291 !((pptr->getCont() && (TESTAFF(pptr->getCont(), onlyincompound,(std::binary_search(pptr->getCont(), pptr->getCont() + pptr
->getContLen(), onlyincompound))
1292 pptr->getContLen())(std::binary_search(pptr->getCont(), pptr->getCont() + pptr
->getContLen(), onlyincompound))
)))) {
1293 result.append(st);
1294 pfx = pptr;
1295 }
1296 }
1297 pptr = pptr->getNextEQ();
1298 } else {
1299 pptr = pptr->getNextNE();
1300 }
1301 }
1302
1303 return result.join();
1304}
1305
1306// check word for prefixes and morph and two-level suffixes
1307std::string AffixMgr::prefix_check_twosfx_morph(const std::string& word,
1308 int start,
1309 int len,
1310 char in_compound,
1311 AffixScratch& scratch,
1312 const FLAGunsigned short needflag) {
1313 // every prefix crosses with every pair of suffixes
1314 DistinctRecords result(MSEP_REC'\n');
1315
1316 pfx = nullptr;
1317 sfxappnd = nullptr;
1318 sfxextra = 0;
1319
1320 // first handle the special case of 0 length prefixes
1321 PfxEntry* pe = pStart[0];
1322 while (pe) {
1323 std::string st = pe->check_twosfx_morph(word, start, len, in_compound, needflag, scratch);
1324 if (!st.empty()) {
1325 result.append(st);
1326 }
1327 pe = pe->getNext();
1328 }
1329
1330 // now handle the general case
1331 unsigned char sp = word[start];
1332 PfxEntry* pptr = pStart[sp];
1333
1334 while (pptr) {
1335 if (isSubset(pptr->getKey(), word.c_str() + start)) {
1336 std::string st = pptr->check_twosfx_morph(word, start, len, in_compound, needflag, scratch);
1337 if (!st.empty()) {
1338 result.append(st);
1339 pfx = pptr;
1340 }
1341 pptr = pptr->getNextEQ();
1342 } else {
1343 pptr = pptr->getNextNE();
1344 }
1345 }
1346
1347 return result.join();
1348}
1349
1350// Is word a non-compound with a REP substitution (see checkcompoundrep)?
1351int AffixMgr::cpdrep_check(const std::string& in_word,
1352 int wl,
1353 AffixScratch& scratch,
1354 bool& timelimit_exceeded,
1355 std::chrono::steady_clock::time_point clock_time_start) {
1356 if ((wl < 2) || get_reptable().empty())
1357 return 0;
1358
1359 std::string word(in_word, 0, wl);
1360
1361 for (const auto& i : get_reptable()) {
1362 if (timelimit_exceeded || std::chrono::steady_clock::now() - clock_time_start > TIMELIMIT_MSstd::chrono::milliseconds(50)) {
1363 timelimit_exceeded = true;
1364 return 0;
1365 }
1366 // use only available mid patterns
1367 if (!i.outstrings[0].empty()) {
1368 size_t r = 0;
1369 const size_t lenp = i.pattern.size();
1370 // search every occurence of the pattern in the word
1371 while ((r = word.find(i.pattern, r)) != std::string::npos) {
1372 std::string candidate(word);
1373 candidate.replace(r, lenp, i.outstrings[0]);
1374 if (candidate_check(candidate, scratch))
1375 return 1;
1376 ++r; // search for the next letter
1377 }
1378 }
1379 }
1380
1381 return 0;
1382}
1383
1384// forbid compound words, if they are in the dictionary as a
1385// word pair separated by space
1386int AffixMgr::cpdwordpair_check(const std::string& word,
1387 int wl,
1388 AffixScratch& scratch,
1389 bool& timelimit_exceeded,
1390 std::chrono::steady_clock::time_point clock_time_start) {
1391 TraceCtx* t = trace_on(scratch.trace);
1392 int pair_found = 0;
1393 std::string pair;
1394
1395 {
1396 // this check puts a space at every position in turn, so the trace reports the verdict alone
1397 TraceSuppress no_trace(scratch.trace);
1398
1399 if (wl > 2) {
1400 std::string candidate(word, 0, wl);
1401 for (size_t i = 1; i < candidate.size(); i++) {
1402 if (timelimit_exceeded || std::chrono::steady_clock::now() - clock_time_start > TIMELIMIT_MSstd::chrono::milliseconds(50)) {
1403 timelimit_exceeded = true;
1404 break;
1405 }
1406 // go to end of the UTF-8 character
1407 if (utf8 && is_utf8_cont(candidate[i]))
1408 continue;
1409 candidate.insert(i, 1, ' ');
1410 if (candidate_check(candidate, scratch)) {
1411 pair_found = 1;
1412 pair = candidate;
1413 break;
1414 }
1415 candidate.erase(i, 1);
1416 }
1417 }
1418 }
1419
1420 if (t) {
1421 if (pair_found)
1422 trace(*t, "test wordpair pair=\"%s\" -> fail, the parts are a known word pair", pair.c_str());
1423 else if (!timelimit_exceeded)
1424 trace(*t, "test wordpair -> pass, no space splits this into a known word pair");
1425 }
1426
1427 return pair_found;
1428}
1429
1430// a CHECKCOMPOUNDPATTERN half may leave its flag out, and then any word is allowed to stand there
1431static std::string trace_cond_flag(const AffixMgr* pAMgr, FLAGunsigned short cond) {
1432 if (cond == FLAG_NULL0x00)
1433 return "(any)";
1434 return trace_flag(pAMgr, cond);
1435}
1436
1437// one side of a compound join, as the entry it was found in and the flags that entry carries
1438static std::string trace_join_word(const AffixMgr* pAMgr, const hentry* entry) {
1439 if (!entry)
1440 return "(none)";
1441 return "\"" + std::string(entry->word, entry->blen) + "\"/" +
1442 trace_flags(pAMgr, entry->astr, entry->alen);
1443}
1444
1445// one side of a compound join carries a flag when the dictionary entry has it, or when a prefix or
1446// suffix that built that side passes it on through its continuation classes
1447static bool join_side_has_flag(const hentry* entry, FLAGunsigned short cond, PfxEntry* p, SfxEntry* s) {
1448 return (entry->astr && TESTAFF(entry->astr, cond, entry->alen)(std::binary_search(entry->astr, entry->astr + entry->
alen, cond))
) ||
1449 (p && p->getCont() && TESTAFF(p->getCont(), cond, p->getContLen())(std::binary_search(p->getCont(), p->getCont() + p->
getContLen(), cond))
) ||
1450 (s && s->getCont() && TESTAFF(s->getCont(), cond, s->getContLen())(std::binary_search(s->getCont(), s->getCont() + s->
getContLen(), cond))
);
1451}
1452
1453// forbid compoundings when there are special patterns at word bound
1454int AffixMgr::cpdpat_check(const std::string& word,
1455 size_t pos,
1456 hentry* r1,
1457 hentry* r2,
1458 const char /*affixed*/,
1459 const TraceCtx* t,
1460 PfxEntry* p1,
1461 SfxEntry* s1,
1462 PfxEntry* p2,
1463 SfxEntry* s2) {
1464 for (auto& i : checkcpdtable) {
1465 size_t len;
1466 bool right_text_ok = isSubset(i.pattern2.c_str(), word.c_str() + pos);
1467 bool left_flag_ok = right_text_ok &&
1468 (!r1 || !i.cond || join_side_has_flag(r1, i.cond, p1, s1));
1469 bool right_flag_ok = left_flag_ok &&
1470 (!r2 || !i.cond2 || join_side_has_flag(r2, i.cond2, p2, s2));
1471 // zero length pattern => only TESTAFF
1472 // zero pattern (0/flag) => unmodified stem (zero affixes allowed)
1473 bool left_text_ok = right_flag_ok &&
1474 (i.pattern.empty() ||
1475 ((i.pattern[0] == '0' && r1->blen <= pos &&
1476 strncmp(word.c_str() + pos - r1->blen, r1->word, r1->blen) == 0) ||
1477 (i.pattern[0] != '0' &&
1478 ((len = i.pattern.size()) != 0) && len <= pos &&
1479 strncmp(word.c_str() + pos - len, i.pattern.c_str(), len) == 0)));
1480
1481 if (t) {
1482 std::string fields = "left=\"" + i.pattern + "\"/" + trace_cond_flag(this, i.cond) +
1483 " right=\"" + i.pattern2 + "\"/" + trace_cond_flag(this, i.cond2) +
1484 " first=" + trace_join_word(this, r1) +
1485 " second=" + trace_join_word(this, r2);
1486 if (left_text_ok)
1487 trace(*t, "test cpdpattern %s -> fail, this pair is forbidden at the join", fields.c_str());
1488 else if (!right_text_ok)
1489 trace(*t, "test cpdpattern %s -> pass, the text after the join does not start with \"%s\"",
1490 fields.c_str(), i.pattern2.c_str());
1491 else if (!left_flag_ok)
1492 trace(*t, "test cpdpattern %s -> pass, \"%s\" has no %s", fields.c_str(),
1493 std::string(r1->word, r1->blen).c_str(), trace_flag(this, i.cond).c_str());
1494 else if (!right_flag_ok)
1495 trace(*t, "test cpdpattern %s -> pass, \"%s\" has no %s", fields.c_str(),
1496 std::string(r2->word, r2->blen).c_str(), trace_flag(this, i.cond2).c_str());
1497 else
1498 trace(*t, "test cpdpattern %s -> pass, the text before the join does not end with \"%s\"",
1499 fields.c_str(), i.pattern.c_str());
1500 }
1501
1502 if (left_text_ok)
1503 return 1;
1504 }
1505 return 0;
1506}
1507
1508// forbid compounding with neighbouring upper and lower case characters at word
1509// bounds
1510int AffixMgr::cpdcase_check(const std::string& word, int pos) {
1511 if (utf8) {
1512 const char* p;
1513 const char* wordp = word.c_str();
1514 for (p = wordp + pos - 1; p > wordp && is_utf8_cont(*p); p--)
1515 ;
1516 std::string pair(p);
1517 std::vector<w_char> pair_u;
1518 u8_u16(pair_u, pair);
1519 unsigned short a = pair_u.size() > 1 ? (unsigned short)pair_u[1] : 0,
1520 b = !pair_u.empty() ? (unsigned short)pair_u[0] : 0;
1521 if (((unicodetoupper(a, langnum) == a && unicodetolower(a, langnum) != a) ||
1522 (unicodetoupper(b, langnum) == b && unicodetolower(b, langnum) != b)) &&
1523 (a != '-') && (b != '-'))
1524 return 1;
1525 } else {
1526 const unsigned char a = word[pos - 1], b = word[pos];
1527 if ((csconv[a].ccase || csconv[b].ccase) && (a != '-') && (b != '-'))
1528 return 1;
1529 }
1530 return 0;
1531}
1532
1533struct metachar_data {
1534 size_t btpp; // metacharacter (*, ?) position for backtracking
1535 signed short btwp; // word position for metacharacters
1536 int btnum; // number of matched characters in metacharacter
1537};
1538
1539// check compound patterns
1540int AffixMgr::defcpd_check(hentry*** words,
1541 short wnum,
1542 short maxwordnum,
1543 hentry* rv,
1544 hentry** def,
1545 char all) {
1546 int w = 0;
1547
1548 if (!*words) {
1549 w = 1;
1550 *words = def;
1551 }
1552
1553 if (!*words) {
1554 return 0;
1555 }
1556
1557 if (wnum >= maxwordnum) {
1558 if (w)
1559 *words = nullptr;
1560 return 0;
1561 }
1562
1563 std::vector<metachar_data> btinfo(1);
1564
1565 short bt = 0;
1566
1567 (*words)[wnum] = rv;
1568
1569 // has the last word COMPOUNDRULE flag?
1570 if (rv->alen == 0) {
1571 (*words)[wnum] = nullptr;
1572 if (w)
1573 *words = nullptr;
1574 return 0;
1575 }
1576 int ok = 0;
1577 for (auto& i : defcpdtable) {
1578 for (auto& j : i) {
1579 if (j != '*' && j != '?' &&
1580 TESTAFF(rv->astr, j, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, j
))
) {
1581 ok = 1;
1582 break;
1583 }
1584 }
1585 }
1586 if (ok == 0) {
1587 (*words)[wnum] = nullptr;
1588 if (w)
1589 *words = nullptr;
1590 return 0;
1591 }
1592
1593 for (auto& i : defcpdtable) {
1594 size_t pp = 0; // pattern position
1595 signed short wp = 0; // "words" position
1596 int ok2 = 1;
1597 ok = 1;
1598 do {
1599 while ((pp < i.size()) && (wp <= wnum)) {
1600 if (((pp + 1) < i.size()) &&
1601 ((i[pp + 1] == '*') ||
1602 (i[pp + 1] == '?'))) {
1603 int wend = (i[pp + 1] == '?') ? wp : wnum;
1604 ok2 = 1;
1605 pp += 2;
1606 btinfo[bt].btpp = pp;
1607 btinfo[bt].btwp = wp;
1608 while (wp <= wend) {
1609 if (!(*words)[wp] ||
1610 !(*words)[wp]->alen ||
1611 !TESTAFF((*words)[wp]->astr, i[pp - 2],(std::binary_search((*words)[wp]->astr, (*words)[wp]->astr
+ (*words)[wp]->alen, i[pp - 2]))
1612 (*words)[wp]->alen)(std::binary_search((*words)[wp]->astr, (*words)[wp]->astr
+ (*words)[wp]->alen, i[pp - 2]))
) {
1613 ok2 = 0;
1614 break;
1615 }
1616 wp++;
1617 }
1618 if (wp <= wnum)
1619 ok2 = 0;
1620 btinfo[bt].btnum = wp - btinfo[bt].btwp;
1621 if (btinfo[bt].btnum > 0) {
1622 ++bt;
1623 btinfo.resize(bt+1);
1624 }
1625 if (ok2)
1626 break;
1627 } else {
1628 ok2 = 1;
1629 if (!(*words)[wp] || !(*words)[wp]->alen ||
1630 !TESTAFF((*words)[wp]->astr, i[pp],(std::binary_search((*words)[wp]->astr, (*words)[wp]->astr
+ (*words)[wp]->alen, i[pp]))
1631 (*words)[wp]->alen)(std::binary_search((*words)[wp]->astr, (*words)[wp]->astr
+ (*words)[wp]->alen, i[pp]))
) {
1632 ok = 0;
1633 break;
1634 }
1635 pp++;
1636 wp++;
1637 if ((i.size() == pp) && !(wp > wnum))
1638 ok = 0;
1639 }
1640 }
1641 if (ok && ok2) {
1642 size_t r = pp;
1643 while ((i.size() > r) && ((r + 1) < i.size()) &&
1644 ((i[r + 1] == '*') ||
1645 (i[r + 1] == '?')))
1646 r += 2;
1647 if (i.size() <= r)
1648 return 1;
1649 }
1650 // backtrack
1651 if (bt)
1652 do {
1653 ok = 1;
1654 btinfo[bt - 1].btnum--;
1655 pp = btinfo[bt - 1].btpp;
1656 wp = btinfo[bt - 1].btwp + (signed short)btinfo[bt - 1].btnum;
1657 } while ((btinfo[bt - 1].btnum < 0) && --bt);
1658 } while (bt);
1659
1660 if (ok && ok2 && (!all || (i.size() <= pp)))
1661 return 1;
1662
1663 // check zero ending
1664 while (ok && ok2 && (i.size() > pp) &&
1665 ((pp + 1) < i.size()) &&
1666 ((i[pp + 1] == '*') ||
1667 (i[pp + 1] == '?')))
1668 pp += 2;
1669 if (ok && ok2 && (i.size() <= pp))
1670 return 1;
1671 }
1672 (*words)[wnum] = nullptr;
1673 if (w)
1674 *words = nullptr;
1675 return 0;
1676}
1677
1678inline int AffixMgr::candidate_check(const std::string& word, AffixScratch& scratch) {
1679
1680 struct hentry* rv = lookup(word.c_str(), word.size());
1681 if (rv)
1682 return 1;
1683
1684 // rv = prefix_check(word,0,len,1);
1685 // if (rv) return 1;
1686
1687 rv = affix_check(word, 0, word.size(), scratch);
1688 if (rv)
1689 return 1;
1690 return 0;
1691}
1692
1693// calculate number of syllable for compound-checking
1694short AffixMgr::get_syllable(const std::string& word) {
1695 if (cpdmaxsyllable == 0)
1696 return 0;
1697
1698 short num = 0;
1699
1700 if (!utf8) {
1701 num = (short)std::count_if(word.begin(), word.end(),
1702 [&](char c) {
1703 return std::binary_search(cpdvowels.begin(), cpdvowels.end(), c);
1704 });
1705 } else if (!cpdvowels_utf16.empty()) {
1706 std::vector<w_char> w;
1707 u8_u16(w, word);
1708 num = (short)std::count_if(w.begin(), w.end(),
1709 [&](w_char wc) {
1710 return std::binary_search(cpdvowels_utf16.begin(), cpdvowels_utf16.end(), wc);
1711 });
1712 }
1713
1714 return num;
1715}
1716
1717void AffixMgr::setcminmax(size_t* cmin, size_t* cmax, const char* word, size_t len) {
1718 if (utf8) {
1719 int i;
1720 for (*cmin = 0, i = 0; (i < cpdmin) && *cmin < len; i++) {
1721 for ((*cmin)++; *cmin < len && is_utf8_cont(word[*cmin]); (*cmin)++)
1722 ;
1723 }
1724 for (*cmax = len, i = 0; (i < (cpdmin - 1)) && *cmax > 0; i++) {
1725 for ((*cmax)--; *cmax > 0 && is_utf8_cont(word[*cmax]); (*cmax)--)
1726 ;
1727 }
1728 } else {
1729 *cmin = cpdmin;
1730 *cmax = len - cpdmin + 1;
1731 }
1732}
1733
1734// check if compound word is correctly spelled
1735// hu_mov_rule = spec. Hungarian rule (XXX)
1736struct hentry* AffixMgr::compound_check(const std::string& word,
1737 short wordnum,
1738 short numsyllable,
1739 short maxwordnum,
1740 short wnum,
1741 hentry** words,
1742 hentry** rwords,
1743 char hu_mov_rule,
1744 char is_sug,
1745 int* info,
1746 AffixScratch& scratch) {
1747 short oldnumsyllable, oldnumsyllable2, oldwordnum, oldwordnum2;
1748 hentry *rv = nullptr, *rv_first;
1749 std::string st;
1750 char ch = '\0', affixed;
1751 size_t cmin, cmax;
1752 int striple = 0, soldi = 0, oldcmin = 0, oldcmax = 0, oldlen = 0, checkedstriple = 0;
1753 hentry** oldwords = words;
1754 size_t scpd = 0, len = word.size();
1755
1756 TraceCtx* t = trace_on(scratch.trace);
1757
1758 // protect subsequent words[wnum + 1] reads and any recursion
1759 if (wnum + 1 >= maxwordnum)
1760 return nullptr;
1761
1762 int checked_prefix;
1763
1764 // add a time limit to handle possible
1765 // combinatorical explosion of the overlapping words
1766
1767 HUNSPELL_THREAD_LOCALthread_local std::chrono::steady_clock::time_point clock_time_start;
1768 HUNSPELL_THREAD_LOCALthread_local bool timelimit_exceeded;
1769
1770 // get the current time
1771 std::chrono::steady_clock::time_point clock_now = std::chrono::steady_clock::now();
1772
1773 if (wnum == 0) {
1774 // set the start time
1775 clock_time_start = clock_now;
1776 timelimit_exceeded = false;
1777 }
1778 else if (clock_now - clock_time_start > TIMELIMIT_MSstd::chrono::milliseconds(50))
1779 timelimit_exceeded = true;
1780
1781 setcminmax(&cmin, &cmax, word.c_str(), len);
1782
1783 // with SIMPLIFIEDTRIPLE a second part that starts at a doubled letter is one letter longer
1784 // than the surface shows, so the split can go one letter past cmax
1785 size_t cmaxtriple = simplifiedtriple ? (utf8 ? utf8_next(word, cmax) : cmax + 1) : cmax;
1786
1787 st.assign(word);
1788
1789 for (size_t i = cmin; i < cmaxtriple; ++i) {
1790 // go to end of the UTF-8 character
1791 if (utf8) {
1792 for (; is_utf8_cont(st[i]); i++)
1793 ;
1794 if (i >= cmaxtriple)
1795 return nullptr;
1796 }
1797 if (i >= cmax && !(i > 2 && word[i - 1] == word[i - 2]))
1798 break;
1799
1800 words = oldwords;
1801 int onlycpdrule = (words) ? 1 : 0;
1802
1803 do { // onlycpdrule loop
1804
1805 oldnumsyllable = numsyllable;
1806 oldwordnum = wordnum;
1807 checked_prefix = 0;
1808
1809 do { // simplified checkcompoundpattern loop
1810
1811 if (timelimit_exceeded ||
1812 std::chrono::steady_clock::now() - clock_time_start > TIMELIMIT_MSstd::chrono::milliseconds(50)) {
1813 if (t && !timelimit_exceeded)
1814 trace(*t, "test timelimit -> fail, the compound search stops here and gives up");
1815 timelimit_exceeded = true;
1816 return nullptr;
1817 }
1818
1819 if (scpd > 0) {
1820 for (; scpd <= checkcpdtable.size() &&
1821 (checkcpdtable[scpd - 1].pattern3.empty() ||
1822 i > word.size() ||
1823 word.compare(i, checkcpdtable[scpd - 1].pattern3.size(), checkcpdtable[scpd - 1].pattern3) != 0);
1824 scpd++)
1825 ;
1826
1827 if (scpd > checkcpdtable.size())
1828 break; // break simplified checkcompoundpattern loop
1829 st.replace(i, std::string::npos, checkcpdtable[scpd - 1].pattern);
1830 soldi = i;
1831 i += checkcpdtable[scpd - 1].pattern.size();
1832 st.replace(i, std::string::npos, checkcpdtable[scpd - 1].pattern2);
1833 st.replace(i + checkcpdtable[scpd - 1].pattern2.size(), std::string::npos,
1834 word.substr(soldi + checkcpdtable[scpd - 1].pattern3.size()));
1835
1836 oldlen = len;
1837 len += checkcpdtable[scpd - 1].pattern.size() +
1838 checkcpdtable[scpd - 1].pattern2.size() -
1839 checkcpdtable[scpd - 1].pattern3.size();
1840 oldcmin = cmin;
1841 oldcmax = cmax;
1842 setcminmax(&cmin, &cmax, st.c_str(), len);
1843
1844 cmax = len - cpdmin + 1;
1845 }
1846
1847 if (i >= st.size())
1848 return nullptr;
1849
1850 ch = st[i];
1851 if (t) {
1852 // a non-zero scpd means this split is the retry that one CHECKCOMPOUNDPATTERN entry asks
1853 // for, with the text at the join replaced by the two sides of that entry
1854 if (scpd != 0)
1855 trace(*t, "split at=%d left=\"%s\" right=\"%s\" cpdpattern=%d", (int)i,
1856 st.substr(0, i).c_str(), st.substr(i).c_str(), (int)scpd);
1857 else
1858 trace(*t, "split at=%d left=\"%s\" right=\"%s\"", (int)i,
1859 st.substr(0, i).c_str(), st.substr(i).c_str());
1860 }
1861 // everything this split point tries belongs to the split
1862 TraceScope split_depth(t);
1863 st[i] = '\0';
1864
1865 sfx = nullptr;
1866 pfx = nullptr;
1867
1868 // FIRST WORD
1869
1870 affixed = 1;
1871 rv = lookup(st.c_str(), i); // perhaps without prefix
1872 if (t) {
1873 if (rv)
1874 trace(*t, "first \"%s\" -> entry \"%s\" flags=%s", st.c_str(),
1875 rv->word, trace_flags(this, rv->astr, rv->alen).c_str());
1876 else
1877 trace(*t, "first \"%s\" -> miss", st.c_str());
1878 }
1879
1880 // forbid dictionary stems with COMPOUNDFORBIDFLAG in
1881 // compound words, overriding the effect of COMPOUNDPERMITFLAG
1882 if ((rv) && compoundforbidflag &&
1883 TESTAFF(rv->astr, compoundforbidflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundforbidflag
))
&& !hu_mov_rule) {
1884 bool would_continue = !onlycpdrule && simplifiedcpd;
1885 if (!scpd && would_continue) {
1886 // given the while conditions that continue jumps to, this situation
1887 // never ends
1888 HUNSPELL_WARNING(stderrstderr, "break infinite loop\n");
1889 break;
1890 }
1891
1892 if (scpd > 0 && would_continue) {
1893 // under these conditions we loop again, but the assumption above
1894 // appears to be that cmin and cmax are the original values they
1895 // had in the outside loop
1896 cmin = oldcmin;
1897 cmax = oldcmax;
1898 }
1899 continue;
1900 }
1901
1902 // search homonym with compound flag
1903 while ((rv) && !hu_mov_rule &&
1904 ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, needaffix
))
) ||
1905 !((compoundflag && !words && !onlycpdrule &&
1906 TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
1907 (compoundbegin && !wordnum && !onlycpdrule &&
1908 TESTAFF(rv->astr, compoundbegin, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundbegin
))
) ||
1909 (compoundmiddle && wordnum && !words && !onlycpdrule &&
1910 TESTAFF(rv->astr, compoundmiddle, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundmiddle
))
) ||
1911 (!defcpdtable.empty() && onlycpdrule &&
1912 ((!words && !wordnum &&
1913 defcpd_check(&words, wnum, maxwordnum, rv, rwords, 0)) ||
1914 (words &&
1915 defcpd_check(&words, wnum, maxwordnum, rv, rwords, 0))))) ||
1916 (scpd != 0 && checkcpdtable[scpd - 1].cond != FLAG_NULL0x00 &&
1917 !TESTAFF(rv->astr, checkcpdtable[scpd - 1].cond, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, checkcpdtable
[scpd - 1].cond))
))) {
1918 rv = rv->next_homonym;
1919 }
1920
1921 if (rv)
1922 affixed = 0;
1923
1924 if (!rv) {
1925 if (onlycpdrule)
1926 break;
1927 if (compoundflag &&
1928 !(rv = prefix_check(st, 0, i,
1929 hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1,
1930 scratch, compoundflag))) {
1931 if (((rv = suffix_check(st, 0, i, 0, nullptr, scratch, FLAG_NULL0x00, compoundflag, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1)) ||
1932 (compoundmoresuffixes && (rv = suffix_check_twosfx(st, 0, i, 0, nullptr, scratch, compoundflag)))) &&
1933 !hu_mov_rule && sfx->getCont() &&
1934 ((compoundforbidflag && TESTAFF(sfx->getCont(), compoundforbidflag, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
) ||
1935 (compoundend && TESTAFF(sfx->getCont(), compoundend, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundend))
))) {
1936 rv = nullptr;
1937 // the suffix is dropped with the word it built
1938 sfx = nullptr;
1939 }
1940 }
1941
1942 if (rv ||
1943 (((wordnum == 0) && compoundbegin &&
1944 ((rv = suffix_check(st, 0, i, 0, nullptr, scratch, FLAG_NULL0x00, compoundbegin, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1)) ||
1945 (compoundmoresuffixes && (rv = suffix_check_twosfx(st, 0, i, 0, nullptr, scratch,
1946 compoundbegin))) || // twofold suffixes + compound
1947 (rv = prefix_check(st, 0, i, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1, scratch, compoundbegin)))) ||
1948 ((wordnum > 0) && compoundmiddle &&
1949 ((rv = suffix_check(st, 0, i, 0, nullptr, scratch, FLAG_NULL0x00, compoundmiddle, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1)) ||
1950 (compoundmoresuffixes && (rv = suffix_check_twosfx(st, 0, i, 0, nullptr, scratch,
1951 compoundmiddle))) || // twofold suffixes + compound
1952 (rv = prefix_check(st, 0, i, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1, scratch, compoundmiddle))))))
1953 checked_prefix = 1;
1954 // else check forbiddenwords and needaffix
1955 } else if (rv->astr && (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
1956 TESTAFF(rv->astr, needaffix, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, needaffix
))
||
1957 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
||
1958 (is_sug && nosuggest &&
1959 TESTAFF(rv->astr, nosuggest, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, nosuggest
))
))) {
1960 st[i] = ch;
1961 // continue;
1962 break;
1963 }
1964
1965 // check non_compound flag in suffix and prefix
1966 if ((rv) && !hu_mov_rule &&
1967 ((pfx && pfx->getCont() &&
1968 TESTAFF(pfx->getCont(), compoundforbidflag, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundforbidflag))
) ||
1969 (sfx && sfx->getCont() &&
1970 TESTAFF(sfx->getCont(), compoundforbidflag,(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
1971 sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
))) {
1972 rv = nullptr;
1973 }
1974
1975 // check compoundend flag in suffix and prefix
1976 if ((rv) && !checked_prefix && compoundend && !hu_mov_rule &&
1977 ((pfx && pfx->getCont() &&
1978 TESTAFF(pfx->getCont(), compoundend, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundend))
) ||
1979 (sfx && sfx->getCont() &&
1980 TESTAFF(sfx->getCont(), compoundend, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundend))
))) {
1981 rv = nullptr;
1982 }
1983
1984 // check compoundmiddle flag in suffix and prefix
1985 if ((rv) && !checked_prefix && (wordnum == 0) && compoundmiddle &&
1986 !hu_mov_rule &&
1987 ((pfx && pfx->getCont() &&
1988 TESTAFF(pfx->getCont(), compoundmiddle, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundmiddle))
) ||
1989 (sfx && sfx->getCont() &&
1990 TESTAFF(sfx->getCont(), compoundmiddle, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundmiddle))
))) {
1991 rv = nullptr;
1992 }
1993
1994 // check forbiddenwords
1995 if ((rv) && (rv->astr) &&
1996 (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
1997 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
||
1998 (is_sug && nosuggest && TESTAFF(rv->astr, nosuggest, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, nosuggest
))
))) {
1999 return nullptr;
2000 }
2001
2002 // increment word number, if the second root has a compoundroot flag
2003 if ((rv) && compoundroot &&
2004 (TESTAFF(rv->astr, compoundroot, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundroot
))
)) {
2005 wordnum++;
2006 }
2007
2008 // first word is acceptable in compound words?
2009 if (((rv) &&
2010 (checked_prefix || (words && words[wnum]) ||
2011 (compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
2012 ((oldwordnum == 0) && compoundbegin &&
2013 TESTAFF(rv->astr, compoundbegin, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundbegin
))
) ||
2014 ((oldwordnum > 0) && compoundmiddle &&
2015 TESTAFF(rv->astr, compoundmiddle, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundmiddle
))
)
2016
2017 // LANG_hu section: spec. Hungarian rule
2018 || ((langnum == LANG_hu) && hu_mov_rule &&
2019 (TESTAFF((std::binary_search(rv->astr, rv->astr + rv->alen, 'F'
))
2020 rv->astr, 'F',(std::binary_search(rv->astr, rv->astr + rv->alen, 'F'
))
2021 rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'F'
))
|| // XXX hardwired Hungarian dictionary codes
2022 TESTAFF(rv->astr, 'G', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'G'
))
||
2023 TESTAFF(rv->astr, 'H', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'H'
))
))
2024 // END of LANG_hu section
2025 ) &&
2026 (
2027 // test CHECKCOMPOUNDPATTERN conditions
2028 scpd == 0 || checkcpdtable[scpd - 1].cond == FLAG_NULL0x00 ||
2029 join_side_has_flag(rv, checkcpdtable[scpd - 1].cond, pfx, sfx)) &&
2030 !((checkcompoundtriple && scpd == 0 &&
2031 !words && i < word.size() && // test triple letters
2032 (word[i - 1] == word[i]) &&
2033 (((i > 1) && (word[i - 1] == word[i - 2])) ||
2034 ((word[i - 1] == word[i + 1])) // may be word[i+1] == '\0'
2035 )) ||
2036 (checkcompoundcase && scpd == 0 && !words && i < word.size() &&
2037 cpdcase_check(word, i))))
2038 // LANG_hu section: spec. Hungarian rule
2039 || ((!rv) && (langnum == LANG_hu) && hu_mov_rule &&
2040 (rv = affix_check(st, 0, i, scratch)) &&
2041 (sfx && sfx->getCont() &&
2042 ( // XXX hardwired Hungarian dic. codes
2043 TESTAFF(sfx->getCont(), (unsigned short)'x',(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), (unsigned short)'x'))
2044 sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), (unsigned short)'x'))
||
2045 TESTAFF((std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), (unsigned short)'%'))
2046 sfx->getCont(), (unsigned short)'%',(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), (unsigned short)'%'))
2047 sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), (unsigned short)'%'))
)))) { // first word is ok condition
2048
2049 // LANG_hu section: spec. Hungarian rule
2050 if (langnum == LANG_hu) {
2051 // calculate syllable number of the word
2052 numsyllable += get_syllable(st.substr(0, i));
2053 // + 1 word, if syllable number of the prefix > 1 (hungarian
2054 // convention)
2055 if (pfx && (get_syllable(pfx->getKey()) > 1))
2056 wordnum++;
2057 }
2058 // END of LANG_hu section
2059
2060 // NEXT WORD(S)
2061 rv_first = rv;
2062 // the affixes that built the first word, so its continuation classes stay reachable once
2063 // the second word has overwritten pfx and sfx
2064 PfxEntry* rv_first_pfx = pfx;
2065 SfxEntry* rv_first_sfx = sfx;
2066 st[i] = ch;
2067
2068 do { // striple loop
2069
2070 // check simplifiedtriple
2071 if (simplifiedtriple) {
2072 if (striple) {
2073 checkedstriple = 1;
2074 i--; // check "fahrt" instead of "ahrt" in "Schiffahrt"
2075 } else if (i > 2 && i <= word.size() && word[i - 1] == word[i - 2]) {
2076 striple = 1;
2077 // past cmax the surface split is too short, so only the form with the letter
2078 // put back is tried
2079 if (i >= cmax) {
2080 checkedstriple = 1;
2081 i--;
2082 }
2083 }
2084 }
2085
2086 rv = lookup(st.c_str() + i, st.size() - i); // perhaps without prefix
2087 if (t) {
2088 if (rv)
2089 trace(*t, "second \"%s\" -> entry \"%s\" flags=%s", st.c_str() + i,
2090 rv->word, trace_flags(this, rv->astr, rv->alen).c_str());
2091 else
2092 trace(*t, "second \"%s\" -> miss", st.c_str() + i);
2093 }
2094
2095 // search homonym with compound flag
2096 while ((rv) && ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, needaffix
))
) ||
2097 !((compoundflag && !words && TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
2098 (compoundend && !words && TESTAFF(rv->astr, compoundend, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundend
))
) ||
2099 (!defcpdtable.empty() && words && defcpd_check(&words, wnum + 1, maxwordnum, rv, nullptr, 1))) ||
2100 (scpd != 0 && checkcpdtable[scpd - 1].cond2 != FLAG_NULL0x00 &&
2101 !TESTAFF(rv->astr, checkcpdtable[scpd - 1].cond2, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, checkcpdtable
[scpd - 1].cond2))
))) {
2102 rv = rv->next_homonym;
2103 }
2104
2105 // check FORCEUCASE
2106 if (rv && forceucase &&
2107 (TESTAFF(rv->astr, forceucase, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forceucase
))
) &&
2108 !(info && *info & SPELL_ORIGCAP(1 << 5)))
2109 rv = nullptr;
2110
2111 if (rv && words && words[wnum + 1])
2112 return rv_first;
2113
2114 oldnumsyllable2 = numsyllable;
2115 oldwordnum2 = wordnum;
2116
2117 // LANG_hu section: spec. Hungarian rule, XXX hardwired dictionary
2118 // code
2119 if ((rv) && (langnum == LANG_hu) &&
2120 (TESTAFF(rv->astr, 'I', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'I'
))
) &&
2121 !(TESTAFF(rv->astr, 'J', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'J'
))
)) {
2122 numsyllable--;
2123 }
2124 // END of LANG_hu section
2125
2126 // increment word number, if the second root has a compoundroot flag
2127 if ((rv) && (compoundroot) &&
2128 (TESTAFF(rv->astr, compoundroot, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundroot
))
)) {
2129 wordnum++;
2130 }
2131
2132 // check forbiddenwords
2133 if ((rv) && (rv->astr) &&
2134 (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
2135 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
||
2136 (is_sug && nosuggest &&
2137 TESTAFF(rv->astr, nosuggest, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, nosuggest
))
)))
2138 return nullptr;
2139
2140 // second word is acceptable, as a root?
2141 // hungarian conventions: compounding is acceptable,
2142 // when compound forms consist of 2 words, or if more,
2143 // then the syllable number of root words must be 6, or lesser.
2144
2145 if ((rv) &&
2146 ((compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
2147 (compoundend && TESTAFF(rv->astr, compoundend, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundend
))
)) &&
2148 (((cpdwordmax == -1) || (wordnum + 1 < cpdwordmax)) ||
2149 ((cpdmaxsyllable != 0) &&
2150 (numsyllable + get_syllable(std::string(HENTRY_WORD(rv)&(rv->word[0]), rv->blen)) <=
2151 cpdmaxsyllable))) &&
2152 (
2153 // test CHECKCOMPOUNDPATTERN
2154 checkcpdtable.empty() || scpd != 0 ||
2155 (i < word.size() && !cpdpat_check(word, i, rv_first, rv, 0, t, rv_first_pfx, rv_first_sfx, nullptr, nullptr))) &&
2156 ((!checkcompounddup || (rv != rv_first)))
2157 // test CHECKCOMPOUNDPATTERN conditions
2158 &&
2159 (scpd == 0 || checkcpdtable[scpd - 1].cond2 == FLAG_NULL0x00 ||
2160 TESTAFF(rv->astr, checkcpdtable[scpd - 1].cond2, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, checkcpdtable
[scpd - 1].cond2))
)) {
2161 // forbid compound word, if it is a non-compound word with typical
2162 // fault
2163 if ((checkcompoundrep && cpdrep_check(word, len, scratch, timelimit_exceeded, clock_time_start)) ||
2164 cpdwordpair_check(word, len, scratch, timelimit_exceeded, clock_time_start))
2165 return nullptr;
2166 return rv_first;
2167 }
2168
2169 numsyllable = oldnumsyllable2;
2170 wordnum = oldwordnum2;
2171
2172 // perhaps second word has prefix or/and suffix
2173 sfx = nullptr;
2174 sfxflag = FLAG_NULL0x00;
2175 // the affixes that built the second word, kept because affix_check clears the members
2176 PfxEntry* rv_second_pfx = nullptr;
2177 SfxEntry* rv_second_sfx = nullptr;
2178 rv = (compoundflag && !onlycpdrule && i < word.size()) ? affix_check(word, i, word.size() - i, scratch, compoundflag, IN_CPD_END2, FLAG_NULL0x00, &rv_second_pfx, &rv_second_sfx)
2179 : nullptr;
2180 if (!rv && compoundend && !onlycpdrule) {
2181 sfx = nullptr;
2182 pfx = nullptr;
2183 if (i < word.size())
2184 rv = affix_check(word, i, word.size() - i, scratch, compoundend, IN_CPD_END2, FLAG_NULL0x00, &rv_second_pfx, &rv_second_sfx);
2185 }
2186
2187 if (!rv && !defcpdtable.empty() && words) {
2188 if (i < word.size())
2189 rv = affix_check(word, i, word.size() - i, scratch, 0, IN_CPD_END2);
2190 if (rv && defcpd_check(&words, wnum + 1, maxwordnum, rv, nullptr, 1))
2191 return rv_first;
2192 rv = nullptr;
2193 }
2194
2195 // test CHECKCOMPOUNDPATTERN conditions (allowed forms)
2196 if (rv &&
2197 !(scpd == 0 || checkcpdtable[scpd - 1].cond2 == FLAG_NULL0x00 ||
2198 join_side_has_flag(rv, checkcpdtable[scpd - 1].cond2, rv_second_pfx,
2199 rv_second_sfx)))
2200 rv = nullptr;
2201
2202 // test CHECKCOMPOUNDPATTERN conditions (forbidden compounds)
2203 if (rv && !checkcpdtable.empty() && scpd == 0 &&
2204 cpdpat_check(word, i, rv_first, rv, affixed, t, rv_first_pfx, rv_first_sfx, rv_second_pfx, rv_second_sfx))
2205 rv = nullptr;
2206
2207 // check non_compound flag in suffix and prefix
2208 if ((rv) && ((pfx && pfx->getCont() &&
2209 TESTAFF(pfx->getCont(), compoundforbidflag,(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundforbidflag))
2210 pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundforbidflag))
) ||
2211 (sfx && sfx->getCont() &&
2212 TESTAFF(sfx->getCont(), compoundforbidflag,(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
2213 sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
))) {
2214 rv = nullptr;
2215 }
2216
2217 // check FORCEUCASE
2218 if (rv && forceucase &&
2219 (TESTAFF(rv->astr, forceucase, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forceucase
))
) &&
2220 !(info && *info & SPELL_ORIGCAP(1 << 5)))
2221 rv = nullptr;
2222
2223 // check forbiddenwords
2224 if ((rv) && (rv->astr) &&
2225 (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
2226 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
||
2227 (is_sug && nosuggest &&
2228 TESTAFF(rv->astr, nosuggest, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, nosuggest
))
)))
2229 return nullptr;
2230
2231 // pfxappnd = prefix of word+i, or NULL
2232 // calculate syllable number of prefix.
2233 // hungarian convention: when syllable number of prefix is more,
2234 // than 1, the prefix+word counts as two words.
2235
2236 if (langnum == LANG_hu) {
2237 if (i < word.size()) {
2238 // calculate syllable number of the word
2239 numsyllable += get_syllable(word.substr(i));
2240 }
2241
2242 // - affix syllable num.
2243 // XXX only second suffix (inflections, not derivations)
2244 if (sfxappnd) {
2245 std::string tmp(sfxappnd);
2246 reverseword(tmp);
2247 numsyllable -= short(get_syllable(tmp) + sfxextra);
2248 } else {
2249 numsyllable -= short(sfxextra);
2250 }
2251
2252 // + 1 word, if syllable number of the prefix > 1 (hungarian
2253 // convention)
2254 if (pfx && (get_syllable(pfx->getKey()) > 1))
2255 wordnum++;
2256
2257 // increment syllable num, if last word has a SYLLABLENUM flag
2258 // and the suffix is beginning `s'
2259
2260 if (!cpdsyllablenum.empty()) {
2261 switch (sfxflag) {
2262 case 'c': {
2263 numsyllable += 2;
2264 break;
2265 }
2266 case 'J': {
2267 numsyllable += 1;
2268 break;
2269 }
2270 case 'I': {
2271 if (rv && TESTAFF(rv->astr, 'J', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'J'
))
)
2272 numsyllable += 1;
2273 break;
2274 }
2275 }
2276 }
2277 }
2278
2279 // increment word number, if the second word has a compoundroot flag
2280 if ((rv) && (compoundroot) &&
2281 (TESTAFF(rv->astr, compoundroot, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundroot
))
)) {
2282 wordnum++;
2283 }
2284 // second word is acceptable, as a word with prefix or/and suffix?
2285 // hungarian conventions: compounding is acceptable,
2286 // when compound forms consist 2 word, otherwise
2287 // the syllable number of root words is 6, or lesser.
2288 if ((rv) &&
2289 (((cpdwordmax == -1) || (wordnum + 1 < cpdwordmax)) ||
2290 ((cpdmaxsyllable != 0) && (numsyllable <= cpdmaxsyllable))) &&
2291 ((!checkcompounddup || (rv != rv_first)))) {
2292 // forbid compound word, if it is a non-compound word with typical
2293 // fault
2294 if ((checkcompoundrep && cpdrep_check(word, len, scratch, timelimit_exceeded, clock_time_start)) ||
2295 cpdwordpair_check(word, len, scratch, timelimit_exceeded, clock_time_start))
2296 return nullptr;
2297 return rv_first;
2298 }
2299
2300 numsyllable = oldnumsyllable2;
2301 wordnum = oldwordnum2;
2302
2303 // perhaps second word is a compound word (recursive call)
2304 // (only if SPELL_COMPOUND_2 is not set and maxwordnum is not exceeded)
2305 if ((!info || !(*info & SPELL_COMPOUND_2(1 << 7))) && wordnum + 2 < maxwordnum && wnum + 1 < maxwordnum) {
2306 rv = compound_check(st.substr(i), wordnum + 1,
2307 numsyllable, maxwordnum, wnum + 1, words, rwords, 0,
2308 is_sug, info, scratch);
2309
2310 if (rv && !checkcpdtable.empty() && i < word.size() &&
2311 ((scpd == 0 &&
2312 cpdpat_check(word, i, rv_first, rv, affixed, t, rv_first_pfx, rv_first_sfx, nullptr, nullptr)) ||
2313 (scpd != 0 &&
2314 !cpdpat_check(word, i, rv_first, rv, affixed, t, rv_first_pfx, rv_first_sfx, nullptr, nullptr))))
2315 rv = nullptr;
2316 } else {
2317 rv = nullptr;
2318 }
2319 if (rv) {
2320 // forbid compound word, if it is a non-compound word with typical
2321 // fault, or a dictionary word pair
2322
2323 if (cpdwordpair_check(word, len, scratch, timelimit_exceeded, clock_time_start))
2324 return nullptr;
2325
2326 if (checkcompoundrep || forbiddenword) {
2327 if (checkcompoundrep && cpdrep_check(word, len, scratch, timelimit_exceeded, clock_time_start))
2328 return nullptr;
2329
2330 // check first part
2331 if (i < word.size() && word.compare(i, rv->blen, rv->word, rv->blen) == 0) {
2332 char r = st[i + rv->blen];
2333 st[i + rv->blen] = '\0';
2334
2335 if ((checkcompoundrep && cpdrep_check(st, i + rv->blen, scratch, timelimit_exceeded, clock_time_start)) ||
2336 cpdwordpair_check(st, i + rv->blen, scratch, timelimit_exceeded, clock_time_start)) {
2337 st[ + i + rv->blen] = r;
2338 continue;
2339 }
2340
2341 if (forbiddenword) {
2342 struct hentry* rv2 = lookup(word.c_str(), word.size());
2343 if (!rv2 && len <= word.size())
2344 rv2 = affix_check(word, 0, len, scratch);
2345 if (rv2 && rv2->astr &&
2346 TESTAFF(rv2->astr, forbiddenword, rv2->alen)(std::binary_search(rv2->astr, rv2->astr + rv2->alen
, forbiddenword))
&&
2347 (strncmp(rv2->word, st.c_str(), i + rv->blen) == 0)) {
2348 return nullptr;
2349 }
2350 }
2351 st[i + rv->blen] = r;
2352 }
2353 }
2354 return rv_first;
2355 }
2356 } while (striple && !checkedstriple); // end of striple loop
2357
2358 if (checkedstriple) {
2359 i++;
2360 checkedstriple = 0;
2361 striple = 0;
2362 }
2363
2364 } // first word is ok condition
2365
2366 if (soldi != 0) {
2367 i = soldi;
2368 len = oldlen;
2369 cmin = oldcmin;
2370 cmax = oldcmax;
2371 }
2372 scpd++;
2373
2374 } while (!onlycpdrule && simplifiedcpd &&
2375 scpd <= checkcpdtable.size()); // end of simplifiedcpd loop
2376
2377 scpd = 0;
2378 wordnum = oldwordnum;
2379 numsyllable = oldnumsyllable;
2380
2381 if (soldi != 0) {
2382 i = soldi;
2383 st.assign(word); // XXX add more optim.
2384 soldi = 0;
2385 len = oldlen;
2386 cmin = oldcmin;
2387 cmax = oldcmax;
2388 } else
2389 st[i] = ch;
2390
2391 } while (!defcpdtable.empty() && oldwordnum == 0 &&
2392 onlycpdrule++ < 1); // end of onlycpd loop
2393 }
2394
2395 return nullptr;
2396}
2397
2398// check if compound word is correctly spelled
2399// hu_mov_rule = spec. Hungarian rule (XXX)
2400int AffixMgr::compound_check_morph(const std::string& word,
2401 short wordnum,
2402 short numsyllable,
2403 short maxwordnum,
2404 short wnum,
2405 hentry** words,
2406 hentry** rwords,
2407 char hu_mov_rule,
2408 std::string& result,
2409 const std::string* partresult,
2410 AffixScratch& scratch) {
2411 short oldnumsyllable, oldnumsyllable2, oldwordnum, oldwordnum2;
2412 hentry *rv = nullptr, *rv_first;
2413 std::string st, presult;
2414 char ch, affixed = 0;
2415 int checked_prefix, ok = 0;
2416 size_t cmin, cmax;
2417 hentry** oldwords = words;
2418 size_t len = word.size();
2419
2420 // protect subsequent words[wnum + 1] reads and any recursion
2421 if (wnum + 1 >= maxwordnum)
2422 return 0;
2423
2424 // add a time limit to handle possible
2425 // combinatorical explosion of the overlapping words
2426
2427 HUNSPELL_THREAD_LOCALthread_local std::chrono::steady_clock::time_point clock_time_start;
2428 HUNSPELL_THREAD_LOCALthread_local bool timelimit_exceeded;
2429
2430 // get the current time
2431 std::chrono::steady_clock::time_point clock_now = std::chrono::steady_clock::now();
2432
2433 if (wnum == 0) {
2434 // set the start time
2435 clock_time_start = clock_now;
2436 timelimit_exceeded = false;
2437 }
2438 else if (clock_now - clock_time_start > TIMELIMIT_MSstd::chrono::milliseconds(50))
2439 timelimit_exceeded = true;
2440
2441 setcminmax(&cmin, &cmax, word.c_str(), len);
2442
2443 st.assign(word);
2444
2445 for (size_t i = cmin; i < cmax; ++i) {
2446 // go to end of the UTF-8 character
2447 if (utf8) {
2448 for (; is_utf8_cont(st[i]); i++)
2449 ;
2450 if (i >= cmax)
2451 return 0;
2452 }
2453
2454 words = oldwords;
2455 int onlycpdrule = (words) ? 1 : 0;
2456
2457 do { // onlycpdrule loop
2458
2459 if (timelimit_exceeded ||
2460 std::chrono::steady_clock::now() - clock_time_start > TIMELIMIT_MSstd::chrono::milliseconds(50)) {
2461 timelimit_exceeded = true;
2462 return 0;
2463 }
2464
2465 if (result.size() > MAXMORPHRESULT(4 * 1024 * 1024))
2466 return 0;
2467
2468 oldnumsyllable = numsyllable;
2469 oldwordnum = wordnum;
2470 checked_prefix = 0;
2471
2472 if (i >= st.size())
2473 return 0;
2474
2475 ch = st[i];
2476 st[i] = '\0';
2477 sfx = nullptr;
2478
2479 // FIRST WORD
2480
2481 affixed = 1;
2482
2483 presult.clear();
2484 if (partresult)
2485 presult.append(*partresult);
2486
2487 rv = lookup(st.c_str(), i); // perhaps without prefix
2488
2489 // forbid dictionary stems with COMPOUNDFORBIDFLAG in
2490 // compound words, overriding the effect of COMPOUNDPERMITFLAG
2491 if ((rv) && compoundforbidflag &&
2492 TESTAFF(rv->astr, compoundforbidflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundforbidflag
))
&& !hu_mov_rule)
2493 continue;
2494
2495 // search homonym with compound flag
2496 while ((rv) && !hu_mov_rule &&
2497 ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, needaffix
))
) ||
2498 !((compoundflag && !words && !onlycpdrule &&
2499 TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
2500 (compoundbegin && !wordnum && !onlycpdrule &&
2501 TESTAFF(rv->astr, compoundbegin, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundbegin
))
) ||
2502 (compoundmiddle && wordnum && !words && !onlycpdrule &&
2503 TESTAFF(rv->astr, compoundmiddle, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundmiddle
))
) ||
2504 (!defcpdtable.empty() && onlycpdrule &&
2505 ((!words && !wordnum &&
2506 defcpd_check(&words, wnum, maxwordnum, rv, rwords, 0)) ||
2507 (words &&
2508 defcpd_check(&words, wnum, maxwordnum, rv, rwords, 0))))))) {
2509 rv = rv->next_homonym;
2510 }
2511
2512
2513 if (rv)
2514 affixed = 0;
2515
2516 if (rv) {
2517 presult.push_back(MSEP_FLD' ');
2518 presult.append(MORPH_PART"pa:");
2519 presult.append(st, 0, i);
2520 if (!HENTRY_FIND(rv, MORPH_STEM"st:")) {
2521 presult.push_back(MSEP_FLD' ');
2522 presult.append(MORPH_STEM"st:");
2523 presult.append(st, 0, i);
2524 }
2525 if (HENTRY_DATA(rv)) {
2526 presult.push_back(MSEP_FLD' ');
2527 presult.append(HENTRY_DATA2(rv));
2528 }
2529 }
2530
2531 if (!rv) {
2532 if (compoundflag &&
2533 !(rv =
2534 prefix_check(st, 0, i, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1,
2535 scratch, compoundflag))) {
2536 if (((rv = suffix_check(st, 0, i, 0, nullptr, scratch, FLAG_NULL0x00, compoundflag, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1)) ||
2537 (compoundmoresuffixes && (rv = suffix_check_twosfx(st, 0, i, 0, nullptr, scratch, compoundflag)))) &&
2538 !hu_mov_rule && sfx->getCont() &&
2539 ((compoundforbidflag && TESTAFF(sfx->getCont(), compoundforbidflag, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
) ||
2540 (compoundend && TESTAFF(sfx->getCont(), compoundend, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundend))
))) {
2541 rv = nullptr;
2542 }
2543 }
2544
2545 if (rv ||
2546 (((wordnum == 0) && compoundbegin &&
2547 ((rv = suffix_check(st, 0, i, 0, nullptr, scratch, FLAG_NULL0x00, compoundbegin, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1)) ||
2548 (compoundmoresuffixes && (rv = suffix_check_twosfx(st, 0, i, 0, nullptr, scratch,
2549 compoundbegin))) || // twofold suffix+compound
2550 (rv = prefix_check(st, 0, i, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1, scratch, compoundbegin)))) ||
2551 ((wordnum > 0) && compoundmiddle &&
2552 ((rv = suffix_check(st, 0, i, 0, nullptr, scratch, FLAG_NULL0x00, compoundmiddle, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1)) ||
2553 (compoundmoresuffixes && (rv = suffix_check_twosfx(st, 0, i, 0, nullptr, scratch,
2554 compoundmiddle))) || // twofold suffix+compound
2555 (rv = prefix_check(st, 0, i, hu_mov_rule ? IN_CPD_OTHER3 : IN_CPD_BEGIN1, scratch, compoundmiddle)))))) {
2556 std::string p;
2557 if (compoundflag)
2558 p = affix_check_morph(st, 0, i, scratch, compoundflag);
2559 if (p.empty()) {
2560 if ((wordnum == 0) && compoundbegin) {
2561 p = affix_check_morph(st, 0, i, scratch, compoundbegin);
2562 } else if ((wordnum > 0) && compoundmiddle) {
2563 p = affix_check_morph(st, 0, i, scratch, compoundmiddle);
2564 }
2565 }
2566 presult.push_back(MSEP_FLD' ');
2567 presult.append(MORPH_PART"pa:");
2568 presult.append(st, 0, i);
2569 if (!p.empty()) {
2570 line_uniq_app(p, MSEP_REC'\n');
2571 if (!p.empty() && p[0] != MSEP_FLD' ')
2572 presult.push_back(MSEP_FLD' ');
2573 presult.append(p);
2574 }
2575 checked_prefix = 1;
2576 }
2577 // else check forbiddenwords
2578 } else if (rv->astr && (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
2579 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
||
2580 TESTAFF(rv->astr, needaffix, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, needaffix
))
)) {
2581 st[i] = ch;
2582 continue;
2583 }
2584
2585 // check non_compound flag in suffix and prefix
2586 if ((rv) && !hu_mov_rule &&
2587 ((pfx && pfx->getCont() &&
2588 TESTAFF(pfx->getCont(), compoundforbidflag, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundforbidflag))
) ||
2589 (sfx && sfx->getCont() &&
2590 TESTAFF(sfx->getCont(), compoundforbidflag, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
))) {
2591 continue;
2592 }
2593
2594 // check compoundend flag in suffix and prefix
2595 if ((rv) && !checked_prefix && compoundend && !hu_mov_rule &&
2596 ((pfx && pfx->getCont() &&
2597 TESTAFF(pfx->getCont(), compoundend, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundend))
) ||
2598 (sfx && sfx->getCont() &&
2599 TESTAFF(sfx->getCont(), compoundend, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundend))
))) {
2600 continue;
2601 }
2602
2603 // check compoundmiddle flag in suffix and prefix
2604 if ((rv) && !checked_prefix && (wordnum == 0) && compoundmiddle &&
2605 !hu_mov_rule &&
2606 ((pfx && pfx->getCont() &&
2607 TESTAFF(pfx->getCont(), compoundmiddle, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundmiddle))
) ||
2608 (sfx && sfx->getCont() &&
2609 TESTAFF(sfx->getCont(), compoundmiddle, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundmiddle))
))) {
2610 rv = nullptr;
2611 }
2612
2613 // check forbiddenwords
2614 if ((rv) && (rv->astr) && (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
2615 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
))
2616 continue;
2617
2618 // increment word number, if the second root has a compoundroot flag
2619 if ((rv) && (compoundroot) &&
2620 (TESTAFF(rv->astr, compoundroot, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundroot
))
)) {
2621 wordnum++;
2622 }
2623
2624 // first word is acceptable in compound words?
2625 if (((rv) &&
2626 (checked_prefix || (words && words[wnum]) || (compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
2627 ((oldwordnum == 0) && compoundbegin && TESTAFF(rv->astr, compoundbegin, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundbegin
))
) ||
2628 ((oldwordnum > 0) && compoundmiddle && TESTAFF(rv->astr, compoundmiddle, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundmiddle
))
)
2629 // LANG_hu section: spec. Hungarian rule
2630 || ((langnum == LANG_hu) && // hu_mov_rule
2631 hu_mov_rule &&
2632 (TESTAFF(rv->astr, 'F', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'F'
))
|| TESTAFF(rv->astr, 'G', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'G'
))
|| TESTAFF(rv->astr, 'H', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'H'
))
))
2633 // END of LANG_hu section
2634 ) &&
2635 !((checkcompoundtriple && !words && // test triple letters
2636 (word[i - 1] == word[i]) &&
2637 (((i > 1) && (word[i - 1] == word[i - 2])) || ((word[i - 1] == word[i + 1])) // may be word[i+1] == '\0'
2638 )) ||
2639 (
2640 // test CHECKCOMPOUNDPATTERN
2641 !checkcpdtable.empty() && !words && cpdpat_check(word, i, rv, nullptr, affixed, nullptr, nullptr, nullptr, nullptr, nullptr)) ||
2642 (checkcompoundcase && !words && cpdcase_check(word, i))))
2643 // LANG_hu section: spec. Hungarian rule
2644 || ((!rv) && (langnum == LANG_hu) && hu_mov_rule && (rv = affix_check(st, 0, i, scratch)) &&
2645 (sfx && sfx->getCont() &&
2646 (TESTAFF(sfx->getCont(), (unsigned short)'x', sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), (unsigned short)'x'))
||
2647 TESTAFF(sfx->getCont(), (unsigned short)'%', sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), (unsigned short)'%'))
)))
2648 // END of LANG_hu section
2649 ) {
2650 // LANG_hu section: spec. Hungarian rule
2651 if (langnum == LANG_hu) {
2652 // calculate syllable number of the word
2653 numsyllable += get_syllable(st.substr(0, i));
2654
2655 // + 1 word, if syllable number of the prefix > 1 (hungarian
2656 // convention)
2657 if (pfx && (get_syllable(pfx->getKey()) > 1))
2658 wordnum++;
2659 }
2660 // END of LANG_hu section
2661
2662 // NEXT WORD(S)
2663 rv_first = rv;
2664 rv = lookup(word.c_str() + i, word.size() - i); // perhaps without prefix
2665
2666 // search homonym with compound flag
2667 while ((rv) && ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, needaffix
))
) ||
2668 !((compoundflag && !words && TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
2669 (compoundend && !words && TESTAFF(rv->astr, compoundend, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundend
))
) ||
2670 (!defcpdtable.empty() && words && defcpd_check(&words, wnum + 1, maxwordnum, rv, nullptr, 1))))) {
2671 rv = rv->next_homonym;
2672 }
2673
2674 if (rv && words && words[wnum + 1]) {
2675 result.append(presult);
2676 result.push_back(MSEP_FLD' ');
2677 result.append(MORPH_PART"pa:");
2678 result.append(word, i, word.size());
2679 if (complexprefixes && HENTRY_DATA(rv))
2680 result.append(HENTRY_DATA2(rv));
2681 if (!HENTRY_FIND(rv, MORPH_STEM"st:")) {
2682 result.push_back(MSEP_FLD' ');
2683 result.append(MORPH_STEM"st:");
2684 result.append(HENTRY_WORD(rv)&(rv->word[0]));
2685 }
2686 // store the pointer of the hash entry
2687 if (!complexprefixes && HENTRY_DATA(rv)) {
2688 result.push_back(MSEP_FLD' ');
2689 result.append(HENTRY_DATA2(rv));
2690 }
2691 result.push_back(MSEP_REC'\n');
2692 return 0;
2693 }
2694
2695 oldnumsyllable2 = numsyllable;
2696 oldwordnum2 = wordnum;
2697
2698 // LANG_hu section: spec. Hungarian rule
2699 if ((rv) && (langnum == LANG_hu) &&
2700 (TESTAFF(rv->astr, 'I', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'I'
))
) &&
2701 !(TESTAFF(rv->astr, 'J', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'J'
))
)) {
2702 numsyllable--;
2703 }
2704 // END of LANG_hu section
2705 // increment word number, if the second root has a compoundroot flag
2706 if ((rv) && (compoundroot) &&
2707 (TESTAFF(rv->astr, compoundroot, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundroot
))
)) {
2708 wordnum++;
2709 }
2710
2711 // check forbiddenwords
2712 if ((rv) && (rv->astr) &&
2713 (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
2714 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
)) {
2715 st[i] = ch;
2716 continue;
2717 }
2718
2719 // second word is acceptable, as a root?
2720 // hungarian conventions: compounding is acceptable,
2721 // when compound forms consist of 2 words, or if more,
2722 // then the syllable number of root words must be 6, or lesser.
2723 if ((rv) &&
2724 ((compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundflag
))
) ||
2725 (compoundend && TESTAFF(rv->astr, compoundend, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundend
))
)) &&
2726 (((cpdwordmax == -1) || (wordnum + 1 < cpdwordmax)) ||
2727 ((cpdmaxsyllable != 0) &&
2728 (numsyllable + get_syllable(std::string(HENTRY_WORD(rv)&(rv->word[0]), rv->blen)) <=
2729 cpdmaxsyllable))) &&
2730 ((!checkcompounddup || (rv != rv_first)))) {
2731 // bad compound word
2732 result.append(presult);
2733 result.push_back(MSEP_FLD' ');
2734 result.append(MORPH_PART"pa:");
2735 result.append(word, i, word.size());
2736
2737 if (HENTRY_DATA(rv)) {
2738 if (complexprefixes)
2739 result.append(HENTRY_DATA2(rv));
2740 if (!HENTRY_FIND(rv, MORPH_STEM"st:")) {
2741 result.push_back(MSEP_FLD' ');
2742 result.append(MORPH_STEM"st:");
2743 result.append(HENTRY_WORD(rv)&(rv->word[0]));
2744 }
2745 // store the pointer of the hash entry
2746 if (!complexprefixes) {
2747 result.push_back(MSEP_FLD' ');
2748 result.append(HENTRY_DATA2(rv));
2749 }
2750 }
2751 result.push_back(MSEP_REC'\n');
2752 ok = 1;
2753 }
2754
2755 numsyllable = oldnumsyllable2;
2756 wordnum = oldwordnum2;
2757
2758 // perhaps second word has prefix or/and suffix
2759 sfx = nullptr;
2760 sfxflag = FLAG_NULL0x00;
2761
2762 if (compoundflag && !onlycpdrule)
2763 rv = affix_check(word, i, word.size() - i, scratch, compoundflag);
2764 else
2765 rv = nullptr;
2766
2767 if (!rv && compoundend && !onlycpdrule) {
2768 sfx = nullptr;
2769 pfx = nullptr;
2770 rv = affix_check(word, i, word.size() - i, scratch, compoundend, IN_CPD_END2);
2771 }
2772
2773 if (!rv && !defcpdtable.empty() && words) {
2774 rv = affix_check(word, i, word.size() - i, scratch, 0, IN_CPD_END2);
2775 if (rv && words && defcpd_check(&words, wnum + 1, maxwordnum, rv, nullptr, 1)) {
2776 std::string m;
2777 if (compoundflag)
2778 m = affix_check_morph(word, i, word.size() - i, scratch, compoundflag);
2779 if (m.empty() && compoundend) {
2780 m = affix_check_morph(word, i, word.size() - i, scratch, compoundend);
2781 }
2782 result.append(presult);
2783 if (!m.empty()) {
2784 result.push_back(MSEP_FLD' ');
2785 result.append(MORPH_PART"pa:");
2786 result.append(word, i, word.size());
2787 line_uniq_app(m, MSEP_REC'\n');
2788 result.append(m);
2789 }
2790 result.push_back(MSEP_REC'\n');
2791 ok = 1;
2792 }
2793 }
2794
2795 // check non_compound flag in suffix and prefix
2796 if ((rv) &&
2797 ((pfx && pfx->getCont() &&
2798 TESTAFF(pfx->getCont(), compoundforbidflag, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), compoundforbidflag))
) ||
2799 (sfx && sfx->getCont() &&
2800 TESTAFF(sfx->getCont(), compoundforbidflag,(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
2801 sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundforbidflag))
))) {
2802 rv = nullptr;
2803 }
2804
2805 // check forbiddenwords
2806 if ((rv) && (rv->astr) &&
2807 (TESTAFF(rv->astr, forbiddenword, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, forbiddenword
))
||
2808 TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 65511
))
) &&
2809 (!TESTAFF(rv->astr, needaffix, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, needaffix
))
)) {
2810 st[i] = ch;
2811 continue;
2812 }
2813
2814 if (langnum == LANG_hu) {
2815 // calculate syllable number of the word
2816 numsyllable += get_syllable(word.c_str() + i);
2817
2818 // - affix syllable num.
2819 // XXX only second suffix (inflections, not derivations)
2820 if (sfxappnd) {
2821 std::string tmp(sfxappnd);
2822 reverseword(tmp);
2823 numsyllable -= short(get_syllable(tmp) + sfxextra);
2824 } else {
2825 numsyllable -= short(sfxextra);
2826 }
2827
2828 // + 1 word, if syllable number of the prefix > 1 (hungarian
2829 // convention)
2830 if (pfx && (get_syllable(pfx->getKey()) > 1))
2831 wordnum++;
2832
2833 // increment syllable num, if last word has a SYLLABLENUM flag
2834 // and the suffix is beginning `s'
2835
2836 if (!cpdsyllablenum.empty()) {
2837 switch (sfxflag) {
2838 case 'c': {
2839 numsyllable += 2;
2840 break;
2841 }
2842 case 'J': {
2843 numsyllable += 1;
2844 break;
2845 }
2846 case 'I': {
2847 if (rv && TESTAFF(rv->astr, 'J', rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, 'J'
))
)
2848 numsyllable += 1;
2849 break;
2850 }
2851 }
2852 }
2853 }
2854
2855 // increment word number, if the second word has a compoundroot flag
2856 if ((rv) && (compoundroot) &&
2857 (TESTAFF(rv->astr, compoundroot, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, compoundroot
))
)) {
2858 wordnum++;
2859 }
2860 // second word is acceptable, as a word with prefix or/and suffix?
2861 // hungarian conventions: compounding is acceptable,
2862 // when compound forms consist 2 word, otherwise
2863 // the syllable number of root words is 6, or lesser.
2864 if ((rv) &&
2865 (((cpdwordmax == -1) || (wordnum + 1 < cpdwordmax)) ||
2866 ((cpdmaxsyllable != 0) && (numsyllable <= cpdmaxsyllable))) &&
2867 ((!checkcompounddup || (rv != rv_first)))) {
2868 std::string m;
2869 if (compoundflag)
2870 m = affix_check_morph(word, i, word.size() - i, scratch, compoundflag, IN_CPD_END2);
2871 if (m.empty() && compoundend) {
2872 m = affix_check_morph(word, i, word.size() - i, scratch, compoundend, IN_CPD_END2);
2873 }
2874 result.append(presult);
2875 if (!m.empty()) {
2876 result.push_back(MSEP_FLD' ');
2877 result.append(MORPH_PART"pa:");
2878 result.append(word, i, word.size());
2879 line_uniq_app(m, MSEP_REC'\n');
2880 result.push_back(MSEP_FLD' ');
2881 result.append(m);
2882 }
2883 result.push_back(MSEP_REC'\n');
2884 ok = 1;
2885 }
2886
2887 numsyllable = oldnumsyllable2;
2888 wordnum = oldwordnum2;
2889
2890 // perhaps second word is a compound word (recursive call)
2891 if ((wordnum + 2 < maxwordnum) && (wnum + 1 < maxwordnum) && (ok == 0)) {
2892 compound_check_morph(word.substr(i), wordnum + 1,
2893 numsyllable, maxwordnum, wnum + 1, words, rwords, 0,
2894 result, &presult, scratch);
2895 } else {
2896 rv = nullptr;
2897 }
2898 }
2899 st[i] = ch;
2900 wordnum = oldwordnum;
2901 numsyllable = oldnumsyllable;
2902
2903 } while (!defcpdtable.empty() && oldwordnum == 0 &&
2904 onlycpdrule++ < 1); // end of onlycpd loop
2905 }
2906 return 0;
2907}
2908
2909
2910inline int AffixMgr::isRevSubset(const char* s1,
2911 const char* end_of_s2,
2912 int len) {
2913 while ((len > 0) && (*s1 != '\0') && ((*s1 == *end_of_s2) || (*s1 == '.'))) {
2914 s1++;
2915 end_of_s2--;
2916 len--;
2917 }
2918 return (*s1 == '\0');
2919}
2920
2921// a circumfix is one affix split in two halves, so the flag has to be on both the prefix and the
2922// suffix, or on neither of them
2923bool AffixMgr::circumfix_ok(PfxEntry* pfx, SfxEntry* sfx, const TraceCtx* t) const {
2924 if (!circumfix)
2925 return true;
2926 bool in_prefix = pfx && pfx->getCont() &&
2927 TESTAFF(pfx->getCont(), circumfix, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), circumfix))
;
2928 bool in_suffix = sfx->getCont() &&
2929 TESTAFF(sfx->getCont(), circumfix, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), circumfix))
;
2930 if (t)
2931 trace_circumfix(*t, this, circumfix, pfx, sfx, in_prefix, in_suffix);
2932 return in_prefix == in_suffix;
2933}
2934
2935// decide whether a suffix entry may be applied at all, before the suffix itself is matched
2936// against the word
2937// the stem was found and then dropped again for carrying the flag the caller asked to keep away
2938// from
2939void AffixMgr::trace_avoidflag(TraceCtx* t,
2940 const FLAGunsigned short avoidflag,
2941 const struct hentry* stem) const {
2942 if (!t)
2943 return;
2944 TraceScope trace_depth(t);
2945 trace_test(*t, "avoidflag", this, avoidflag, "dic", stem->astr, stem->alen,
2946 "fail, the caller is skipping stems with this flag");
2947}
2948
2949bool AffixMgr::suffix_applicable(PfxEntry* pfx,
2950 SfxEntry* sfx,
2951 const FLAGunsigned short cclass,
2952 char in_compound,
2953 const TraceCtx* t) const {
2954 // suffixes are only allowed at the beginning of a compound when they are signed with the
2955 // compoundpermitflag flag
2956 if (in_compound == IN_CPD_BEGIN1 &&
2957 !(sfx->getCont() && compoundpermitflag &&
2958 TESTAFF(sfx->getCont(), compoundpermitflag, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), compoundpermitflag))
)) {
2959 if (t)
2960 trace_test(*t, "compoundpermit", this, compoundpermitflag, "sfx-cont",
2961 sfx->getCont(), sfx->getContLen(),
2962 "fail, a suffix at the start of a compound needs this flag");
2963 return false;
2964 }
2965
2966 if (!circumfix_ok(pfx, sfx, t))
2967 return false;
2968
2969 // a fogemorpheme is only allowed inside a compound
2970 if (!in_compound && sfx->getCont() &&
2971 TESTAFF(sfx->getCont(), onlyincompound, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), onlyincompound))
) {
2972 if (t)
2973 trace_test(*t, "onlyincompound", this, onlyincompound, "sfx-cont",
2974 sfx->getCont(), sfx->getContLen(),
2975 "fail, this suffix is only allowed inside a compound");
2976 return false;
2977 }
2978
2979 // a needaffix suffix needs a further affix, so it is allowed either as a second suffix or
2980 // when a prefix is present that does not itself need one
2981 if (!cclass && sfx->getCont() &&
2982 TESTAFF(sfx->getCont(), needaffix, sfx->getContLen())(std::binary_search(sfx->getCont(), sfx->getCont() + sfx
->getContLen(), needaffix))
&&
2983 !(pfx && !(pfx->getCont() &&
2984 TESTAFF(pfx->getCont(), needaffix, pfx->getContLen())(std::binary_search(pfx->getCont(), pfx->getCont() + pfx
->getContLen(), needaffix))
))) {
2985 if (t)
2986 trace_test(*t, "needaffix", this, needaffix, "sfx-cont", sfx->getCont(),
2987 sfx->getContLen(),
2988 "fail, this suffix needs a further affix and has none");
2989 return false;
2990 }
2991
2992 return true;
2993}
2994
2995// check word for suffixes
2996struct hentry* AffixMgr::suffix_check(const std::string& word,
2997 int start,
2998 int len,
2999 int sfxopts,
3000 PfxEntry* ppfx,
3001 AffixScratch& scratch,
3002 const FLAGunsigned short cclass,
3003 const FLAGunsigned short needflag,
3004 char in_compound,
3005 const FLAGunsigned short avoidflag) {
3006 struct hentry* rv = nullptr;
3007
3008 TraceCtx* t = trace_on(scratch.trace);
3009 int candidates = 0;
3010 // a pass that had nothing to try says so, which is different from a pass that
3011 // did not run
3012 auto report_empty_pass = [t, &word, start, len, sfxopts, &candidates]() {
3013 if (t && candidates == 0)
3014 trace(*t, "sfx \"%s\" candidates=0%s", word.substr(start, len).c_str(),
3015 (sfxopts & aeXPRODUCT(1 << 0)) != 0 ? " xprod=Y" : "");
3016 };
3017 // a rule the suffix conditions turned away never reaches checkword, so name
3018 // the rule here and run the conditions again to say which of them refused it
3019 auto report_refused = [this, t, ppfx, cclass, in_compound,
3020 &candidates](SfxEntry* se) {
3021 if (!t)
3022 return;
3023 ++candidates;
3024 trace_affix(*t, "sfx", this, *se);
3025 TraceScope trace_depth(t);
3026 suffix_applicable(ppfx, se, cclass, in_compound, t);
3027 };
3028
3029 // first handle the special case of 0 length suffixes
3030 SfxEntry* se = sStart[0];
3031
3032 while (se) {
3033 if (!cclass || se->getCont()) {
3034 if (suffix_applicable(ppfx, se, cclass, in_compound, nullptr)) {
3035 ++candidates;
3036 rv = se->checkword(word, start, len, sfxopts, ppfx,
3037 (FLAGunsigned short)cclass, needflag,
3038 (in_compound ? 0 : onlyincompound),
3039 scratch);
3040 // Skip a stem with the avoid flag and keep scanning the other suffixes.
3041 if (rv && avoidflag != FLAG_NULL0x00 && TESTAFF(rv->astr, avoidflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, avoidflag
))
) {
3042 trace_avoidflag(t, avoidflag, rv);
3043 rv = nullptr;
3044 }
3045 if (rv) {
3046 sfx = se; // BUG: sfx not stateless
3047 return rv;
3048 }
3049 } else {
3050 report_refused(se);
3051 }
3052 }
3053 se = se->getNext();
3054 }
3055
3056 // now handle the general case
3057 if (len == 0) {
3058 report_empty_pass();
3059 return nullptr; // FULLSTRIP
3060 }
3061 unsigned char sp = word[start + len - 1];
3062 SfxEntry* sptr = sStart[sp];
3063
3064 while (sptr) {
3065 if (isRevSubset(sptr->getKey(), word.c_str() + start + len - 1, len)) {
3066 if (!suffix_applicable(ppfx, sptr, cclass, in_compound, nullptr))
3067 report_refused(sptr);
3068 else if (in_compound != IN_CPD_END2 || ppfx ||
3069 !(sptr->getCont() &&
3070 TESTAFF(sptr->getCont(), onlyincompound, sptr->getContLen())(std::binary_search(sptr->getCont(), sptr->getCont() + sptr
->getContLen(), onlyincompound))
)) {
3071 ++candidates;
3072 rv = sptr->checkword(word, start, len, sfxopts, ppfx,
3073 cclass, needflag,
3074 (in_compound ? 0 : onlyincompound),
3075 scratch);
3076 if (rv && avoidflag != FLAG_NULL0x00 && TESTAFF(rv->astr, avoidflag, rv->alen)(std::binary_search(rv->astr, rv->astr + rv->alen, avoidflag
))
) {
3077 trace_avoidflag(t, avoidflag, rv);
3078 rv = nullptr;
3079 }
3080 if (rv) {
3081 sfx = sptr; // BUG: sfx not stateless
3082 sfxflag = sptr->getFlag(); // BUG: sfxflag not stateless
3083 if (!sptr->getCont())
3084 sfxappnd = sptr->getKey(); // BUG: sfxappnd not stateless
3085 // LANG_hu section: spec. Hungarian rule
3086 else if (langnum == LANG_hu && sptr->getKeyLen() &&
3087 sptr->getKey()[0] == 'i' && sptr->getKey()[1] != 'y' &&
3088 sptr->getKey()[1] != 't') {
3089 sfxextra = 1;
3090 }
3091 // END of LANG_hu section
3092 return rv;
3093 }
3094 }
3095 sptr = sptr->getNextEQ();
3096 } else {
3097 sptr = sptr->getNextNE();
3098 }
3099 }
3100
3101 report_empty_pass();
3102
3103 return nullptr;
3104}
3105
3106// check word for two-level suffixes
3107struct hentry* AffixMgr::suffix_check_twosfx(const std::string& word,
3108 int start,
3109 int len,
3110 int sfxopts,
3111 PfxEntry* ppfx,
3112 AffixScratch& scratch,
3113 const FLAGunsigned short needflag) {
3114 struct hentry* rv = nullptr;
3115
3116 // first handle the special case of 0 length suffixes
3117 SfxEntry* se = sStart[0];
3118 while (se) {
3119 if (contclasses[se->getFlag()]) {
3120 rv = se->check_twosfx(word, start, len, sfxopts, ppfx, needflag, scratch);
3121 if (rv)
3122 return rv;
3123 }
3124 se = se->getNext();
3125 }
3126
3127 // now handle the general case
3128 if (len == 0)
3129 return nullptr; // FULLSTRIP
3130 unsigned char sp = word[start + len - 1];
3131 SfxEntry* sptr = sStart[sp];
3132
3133 while (sptr) {
3134 if (isRevSubset(sptr->getKey(), word.c_str() + start + len - 1, len)) {
3135 if (contclasses[sptr->getFlag()]) {
3136 rv = sptr->check_twosfx(word, start, len, sfxopts, ppfx, needflag, scratch);
3137 if (rv) {
3138 sfxflag = sptr->getFlag(); // BUG: sfxflag not stateless
3139 if (!sptr->getCont())
3140 sfxappnd = sptr->getKey(); // BUG: sfxappnd not stateless
3141 return rv;
3142 }
3143 }
3144 sptr = sptr->getNextEQ();
3145 } else {
3146 sptr = sptr->getNextNE();
3147 }
3148 }
3149
3150 return nullptr;
3151}
3152
3153// check word for two-level suffixes and morph
3154std::string AffixMgr::suffix_check_twosfx_morph(const std::string& word,
3155 int start,
3156 int len,
3157 int sfxopts,
3158 PfxEntry* ppfx,
3159 AffixScratch& scratch,
3160 const FLAGunsigned short needflag) {
3161 std::string result2;
3162 std::string result3;
3163 // the second suffix runs the whole suffix table again for every first suffix
3164 DistinctRecords result(MSEP_REC'\n');
3165
3166 // first handle the special case of 0 length suffixes
3167 SfxEntry* se = sStart[0];
3168 while (se) {
3169 if (contclasses[se->getFlag()]) {
3170 std::string st = se->check_twosfx_morph(word, start, len, sfxopts, ppfx, needflag, scratch);
3171 if (!st.empty()) {
3172 std::string analysis;
3173 if (ppfx) {
3174 if (ppfx->getMorph()) {
3175 analysis.append(ppfx->getMorph());
3176 analysis.push_back(MSEP_FLD' ');
3177 } else
3178 debugflag(analysis, ppfx->getFlag());
3179 }
3180 analysis.append(st);
3181 if (se->getMorph()) {
3182 analysis.push_back(MSEP_FLD' ');
3183 analysis.append(se->getMorph());
3184 } else
3185 debugflag(analysis, se->getFlag());
3186 result.append(analysis);
3187 }
3188 }
3189 se = se->getNext();
3190 }
3191
3192 // now handle the general case
3193 if (len == 0)
3194 return { }; // FULLSTRIP
3195 unsigned char sp = word[start + len - 1];
3196 SfxEntry* sptr = sStart[sp];
3197
3198 while (sptr) {
3199 if (isRevSubset(sptr->getKey(), word.c_str() + start + len - 1, len)) {
3200 if (contclasses[sptr->getFlag()]) {
3201 std::string st = sptr->check_twosfx_morph(word, start, len, sfxopts, ppfx, needflag, scratch);
3202 if (!st.empty()) {
3203 sfxflag = sptr->getFlag(); // BUG: sfxflag not stateless
3204 if (!sptr->getCont())
3205 sfxappnd = sptr->getKey(); // BUG: sfxappnd not stateless
3206 result2.assign(st);
3207
3208 result3.clear();
3209
3210 if (sptr->getMorph()) {
3211 result3.push_back(MSEP_FLD' ');
3212 result3.append(sptr->getMorph());
3213 } else
3214 debugflag(result3, sptr->getFlag());
3215 strlinecat(result2, result3);
3216 result.append(result2);
3217 }
3218 }
3219 sptr = sptr->getNextEQ();
3220 } else {
3221 sptr = sptr->getNextNE();
3222 }
3223 }
3224
3225 return result.join();
3226}
3227
3228std::string AffixMgr::suffix_check_morph(const std::string& word,
3229 int start,
3230 int len,
3231 int sfxopts,
3232 PfxEntry* ppfx,
3233 AffixScratch& scratch,
3234 const FLAGunsigned short cclass,
3235 const FLAGunsigned short needflag,
3236 char in_compound) {
3237 std::string result;
3238
3239 struct hentry* rv = nullptr;
3240
3241 // first handle the special case of 0 length suffixes
3242 SfxEntry* se = sStart[0];
3243 while (se) {
3244 if (!cclass || se->getCont()) {
3245 if (suffix_applicable(ppfx, se, cclass, in_compound, nullptr))
3246 rv = se->checkword(word, start, len, sfxopts, ppfx, cclass,
3247 needflag, FLAG_NULL0x00, scratch);
3248 while (rv) {
3249 if (ppfx) {
3250 if (ppfx->getMorph()) {
3251 result.append(ppfx->getMorph());
3252 result.push_back(MSEP_FLD' ');
3253 } else
3254 debugflag(result, ppfx->getFlag());
3255 }
3256 if (complexprefixes && HENTRY_DATA(rv))
3257 result.append(HENTRY_DATA2(rv));
3258 if (!HENTRY_FIND(rv, MORPH_STEM"st:")) {
3259 result.push_back(MSEP_FLD' ');
3260 result.append(MORPH_STEM"st:");
3261 result.append(HENTRY_WORD(rv)&(rv->word[0]));
3262 }
3263
3264 if (!complexprefixes && HENTRY_DATA(rv)) {
3265 result.push_back(MSEP_FLD' ');
3266 result.append(HENTRY_DATA2(rv));
3267 }
3268 if (se->getMorph()) {
3269 result.push_back(MSEP_FLD' ');
3270 result.append(se->getMorph());
3271 } else
3272 debugflag(result, se->getFlag());
3273 result.push_back(MSEP_REC'\n');
3274 rv = se->get_next_homonym(rv, sfxopts, ppfx, cclass, needflag);
3275 }
3276 }
3277 se = se->getNext();
3278 }
3279
3280 // now handle the general case
3281 if (len == 0)
3282 return { }; // FULLSTRIP
3283 unsigned char sp = word[start + len - 1];
3284 SfxEntry* sptr = sStart[sp];
3285
3286 while (sptr) {
3287 if (isRevSubset(sptr->getKey(), word.c_str() + start + len - 1, len)) {
3288 if (suffix_applicable(ppfx, sptr, cclass, in_compound, nullptr))
3289 rv = sptr->checkword(word, start, len, sfxopts, ppfx, cclass,
3290 needflag, FLAG_NULL0x00, scratch);
3291 while (rv) {
3292 if (ppfx) {
3293 if (ppfx->getMorph()) {
3294 result.append(ppfx->getMorph());
3295 result.push_back(MSEP_FLD' ');
3296 } else
3297 debugflag(result, ppfx->getFlag());
3298 }
3299 if (complexprefixes && HENTRY_DATA(rv))
3300 result.append(HENTRY_DATA2(rv));
3301 if (!HENTRY_FIND(rv, MORPH_STEM"st:")) {
3302 result.push_back(MSEP_FLD' ');
3303 result.append(MORPH_STEM"st:");
3304 result.append(HENTRY_WORD(rv)&(rv->word[0]));
3305 }
3306
3307 if (!complexprefixes && HENTRY_DATA(rv)) {
3308 result.push_back(MSEP_FLD' ');
3309 result.append(HENTRY_DATA2(rv));
3310 }
3311
3312 if (sptr->getMorph()) {
3313 result.push_back(MSEP_FLD' ');
3314 result.append(sptr->getMorph());
3315 } else
3316 debugflag(result, sptr->getFlag());
3317 result.push_back(MSEP_REC'\n');
3318 rv = sptr->get_next_homonym(rv, sfxopts, ppfx, cclass, needflag);
3319 }
3320 sptr = sptr->getNextEQ();
3321 } else {
3322 sptr = sptr->getNextNE();
3323 }
3324 }
3325
3326 return result;
3327}
3328
3329// check if word with affixes is correctly spelled
3330struct hentry* AffixMgr::affix_check(const std::string& word,
3331 int start,
3332 int len,
3333 AffixScratch& scratch,
3334 const FLAGunsigned short needflag,
3335 char in_compound,
3336 const FLAGunsigned short avoidflag,
3337 PfxEntry** found_pfx,
3338 SfxEntry** found_sfx) {
3339
3340 TraceCtx* t = trace_on(scratch.trace);
3341 // the affixes that built the word are reported before the members holding them are cleared
3342 auto report_form = [this, t, &word, start, len, found_pfx, found_sfx](const struct hentry* rv) {
3343 if (!rv)
3344 return;
3345 if (t)
3346 trace_form(*t, this, word.substr(start, len), rv->word, pfx, sfx);
3347 if (found_pfx)
3348 *found_pfx = pfx;
3349 if (found_sfx)
3350 *found_sfx = sfx;
3351 };
3352
3353 // check all prefixes (also crossed with suffixes if allowed)
3354 struct hentry* rv = prefix_check(word, start, len, in_compound, scratch, needflag, avoidflag);
3355 if (rv) {
3356 report_form(rv);
3357 return rv;
3358 }
3359
3360 // if still not found check all suffixes
3361 rv = suffix_check(word, start, len, 0, nullptr, scratch, FLAG_NULL0x00, needflag, in_compound, avoidflag);
3362
3363 if (havecontclass) {
3364 if (rv)
3365 report_form(rv);
3366
3367 sfx = nullptr;
3368 pfx = nullptr;
3369
3370 if (rv)
3371 return rv;
3372 // if still not found check all two-level suffixes
3373 rv = suffix_check_twosfx(word, start, len, 0, nullptr, scratch, needflag);
3374
3375 if (rv) {
3376 report_form(rv);
3377 return rv;
3378 }
3379 // if still not found check all two-level suffixes
3380 rv = prefix_check_twosfx(word, start, len, IN_CPD_NOT0, scratch, needflag);
3381 }
3382
3383 report_form(rv);
3384
3385 return rv;
3386}
3387
3388// check if word with affixes is correctly spelled
3389std::string AffixMgr::affix_check_morph(const std::string& word,
3390 int start,
3391 int len,
3392 AffixScratch& scratch,
3393 const FLAGunsigned short needflag,
3394 char in_compound) {
3395 // the four checks below overlap
3396 DistinctRecords result(MSEP_REC'\n');
3397
3398 // check all prefixes (also crossed with suffixes if allowed)
3399 std::string st = prefix_check_morph(word, start, len, in_compound, scratch);
3400 if (!st.empty()) {
3401 result.append(st);
3402 }
3403
3404 // if still not found check all suffixes
3405 st = suffix_check_morph(word, start, len, 0, nullptr, scratch, '\0', needflag, in_compound);
3406 if (!st.empty()) {
3407 result.append(st);
3408 }
3409
3410 if (havecontclass) {
3411 sfx = nullptr;
3412 pfx = nullptr;
3413 // if still not found check all two-level suffixes
3414 st = suffix_check_twosfx_morph(word, start, len, 0, nullptr, scratch, needflag);
3415 if (!st.empty()) {
3416 result.append(st);
3417 }
3418
3419 // if still not found check all two-level suffixes
3420 st = prefix_check_twosfx_morph(word, start, len, IN_CPD_NOT0, scratch, needflag);
3421 if (!st.empty()) {
3422 result.append(st);
3423 }
3424 }
3425
3426 return result.join();
3427}
3428
3429// morphcmp(): compare MORPH_DERI_SFX, MORPH_INFL_SFX and MORPH_TERM_SFX fields
3430// in the first line of the inputs
3431// return 0, if inputs equal
3432// return 1, if inputs may equal with a secondary suffix
3433// otherwise return -1
3434static int morphcmp(const char* s, const char* t) {
3435 bool se = false, te = false;
3436 const char* sl;
3437 const char* tl;
3438 const char* olds;
3439 const char* oldt;
3440 if (!s || !t)
3441 return 1;
3442 olds = s;
3443 sl = strchr(s, '\n');
3444 s = strstr(s, MORPH_DERI_SFX"ds:");
3445 if (!s || (sl && sl < s))
3446 s = strstr(olds, MORPH_INFL_SFX"is:");
3447 if (!s || (sl && sl < s)) {
3448 s = strstr(olds, MORPH_TERM_SFX"ts:");
3449 olds = nullptr;
3450 }
3451 oldt = t;
3452 tl = strchr(t, '\n');
3453 t = strstr(t, MORPH_DERI_SFX"ds:");
3454 if (!t || (tl && tl < t))
3455 t = strstr(oldt, MORPH_INFL_SFX"is:");
3456 if (!t || (tl && tl < t))
3457 t = strstr(oldt, MORPH_TERM_SFX"ts:");
3458 while (s && t && (!sl || sl > s) && (!tl || tl > t)) {
3459 s += MORPH_TAG_LENstrlen("st:");
3460 t += MORPH_TAG_LENstrlen("st:");
3461 se = false;
3462 te = false;
3463 // both values empty: matched at the shared field terminator.
3464 // skip the inner loop, which would step past the '\0'.
3465 if (*s == '\0' && *t == '\0') {
3466 se = true;
3467 te = true;
3468 }
3469 while ((*s == *t) && !se && !te) {
3470 s++;
3471 t++;
3472 switch (*s) {
3473 case ' ':
3474 case '\n':
3475 case '\t':
3476 case '\0':
3477 se = true;
3478 }
3479 switch (*t) {
3480 case ' ':
3481 case '\n':
3482 case '\t':
3483 case '\0':
3484 te = true;
3485 }
3486 }
3487 if (!se || !te) {
3488 // not terminal suffix difference
3489 if (olds)
3490 return -1;
3491 return 1;
3492 }
3493 olds = s;
3494 s = strstr(s, MORPH_DERI_SFX"ds:");
3495 if (!s || (sl && sl < s))
3496 s = strstr(olds, MORPH_INFL_SFX"is:");
3497 if (!s || (sl && sl < s)) {
3498 s = strstr(olds, MORPH_TERM_SFX"ts:");
3499 olds = nullptr;
3500 }
3501 oldt = t;
3502 t = strstr(t, MORPH_DERI_SFX"ds:");
3503 if (!t || (tl && tl < t))
3504 t = strstr(oldt, MORPH_INFL_SFX"is:");
3505 if (!t || (tl && tl < t))
3506 t = strstr(oldt, MORPH_TERM_SFX"ts:");
3507 }
3508 if (!s && !t && se && te)
3509 return 0;
3510 return 1;
3511}
3512
3513std::string AffixMgr::morphgen(const char* ts,
3514 int wl,
3515 const unsigned short* ap,
3516 unsigned short al,
3517 const char* morph,
3518 const char* targetmorph,
3519 int level,
3520 const FLAGunsigned short avoidflag) {
3521 // handle suffixes
3522 if (!morph)
3523 return {};
3524
3525 // check substandard flag
3526 if (TESTAFF(ap, substandard, al)(std::binary_search(ap, ap + al, substandard)))
3527 return {};
3528
3529 if (morphcmp(morph, targetmorph) == 0)
3530 return ts;
3531
3532 size_t stemmorphcatpos;
3533 std::string mymorph;
3534
3535 // use input suffix fields, if exist
3536 if (strstr(morph, MORPH_INFL_SFX"is:") || strstr(morph, MORPH_DERI_SFX"ds:")) {
3537 mymorph.assign(morph);
3538 mymorph.push_back(MSEP_FLD' ');
3539 stemmorphcatpos = mymorph.size();
3540 } else {
3541 stemmorphcatpos = std::string::npos;
3542 }
3543
3544 for (int i = 0; i < al; i++) {
3545 // A derivational suffix whose own continuation class re-enables the same
3546 // suffix would otherwise be applied twice here, doubling the ending (for
3547 // example the Hungarian -szeru adjective suffix producing peldaszeruszeru).
3548 // Skip the flag that was just applied one level up.
3549 if (avoidflag != FLAG_NULL0x00 && ap[i] == avoidflag)
3550 continue;
3551 const auto c = (unsigned char)(ap[i] & 0x00FF);
3552 SfxEntry* sptr = sFlag[c];
3553 while (sptr) {
3554 if (sptr->getFlag() == ap[i] && sptr->getMorph() &&
3555 ((sptr->getContLen() == 0) ||
3556 // don't generate forms with substandard affixes
3557 !TESTAFF(sptr->getCont(), substandard, sptr->getContLen())(std::binary_search(sptr->getCont(), sptr->getCont() + sptr
->getContLen(), substandard))
)) {
3558 const char* stemmorph;
3559 if (stemmorphcatpos != std::string::npos) {
3560 mymorph.replace(stemmorphcatpos, std::string::npos, sptr->getMorph());
3561 stemmorph = mymorph.c_str();
3562 } else {
3563 stemmorph = sptr->getMorph();
3564 }
3565
3566 int cmp = morphcmp(stemmorph, targetmorph);
3567
3568 if (cmp == 0) {
3569 std::string newword = sptr->add(ts, wl);
3570 if (!newword.empty()) {
3571 hentry* check = pHMgr->lookup(newword.c_str(), newword.size()); // XXX extra dic
3572 if (!check || !check->astr ||
3573 !(TESTAFF(check->astr, forbiddenword, check->alen)(std::binary_search(check->astr, check->astr + check->
alen, forbiddenword))
||
3574 TESTAFF(check->astr, ONLYUPCASEFLAG, check->alen)(std::binary_search(check->astr, check->astr + check->
alen, 65511))
)) {
3575 return newword;
3576 }
3577 }
3578 }
3579
3580 // recursive call for secondary suffixes
3581 if ((level == 0) && (cmp == 1) && (sptr->getContLen() > 0) &&
3582 !TESTAFF(sptr->getCont(), substandard, sptr->getContLen())(std::binary_search(sptr->getCont(), sptr->getCont() + sptr
->getContLen(), substandard))
) {
3583 std::string newword = sptr->add(ts, wl);
3584 if (!newword.empty()) {
3585 std::string newword2 =
3586 morphgen(newword.c_str(), newword.size(), sptr->getCont(),
3587 sptr->getContLen(), stemmorph, targetmorph, 1,
3588 sptr->getFlag());
3589
3590 if (!newword2.empty()) {
3591 return newword2;
3592 }
3593 }
3594 }
3595 }
3596 sptr = sptr->getFlgNxt();
3597 }
3598 }
3599 return { };
3600}
3601
3602namespace {
3603 // replaces strdup with ansi version
3604 char* mystrdup(const char* s) {
3605 char* d = nullptr;
3606 if (s) {
3607 size_t sl = strlen(s) + 1;
3608 d = new char[sl];
3609 memcpy(d, s, sl);
3610 }
3611 return d;
3612 }
3613}
3614
3615int AffixMgr::expand_rootword(struct guessword* wlst,
3616 int maxn,
3617 const char* ts,
3618 int wl,
3619 const unsigned short* ap,
3620 unsigned short al,
3621 const char* bad,
3622 int badl,
3623 const char* phon) {
3624 int nh = 0;
3625 // first add root word to list
3626 if ((nh < maxn) &&
3627 !(al && ((needaffix && TESTAFF(ap, needaffix, al)(std::binary_search(ap, ap + al, needaffix))) ||
3628 (onlyincompound && TESTAFF(ap, onlyincompound, al)(std::binary_search(ap, ap + al, onlyincompound)))))) {
3629 wlst[nh].word = mystrdup(ts);
3630 wlst[nh].allow = false;
3631 wlst[nh].orig = nullptr;
3632 nh++;
3633 // add special phonetic version
3634 if (phon && (nh < maxn)) {
3635 wlst[nh].word = mystrdup(phon);
3636 wlst[nh].allow = false;
3637 wlst[nh].orig = mystrdup(ts);
3638 nh++;
3639 }
3640 }
3641
3642 // handle suffixes
3643 for (int i = 0; i < al; i++) {
3644 const auto c = (unsigned char)(ap[i] & 0x00FF);
3645 SfxEntry* sptr = sFlag[c];
3646 while (sptr) {
3647 if ((sptr->getFlag() == ap[i]) &&
3648 (!sptr->getKeyLen() ||
3649 ((badl > sptr->getKeyLen()) &&
3650 (strcmp(sptr->getAffix(), bad + badl - sptr->getKeyLen()) == 0))) &&
3651 // check needaffix flag
3652 !(sptr->getCont() &&
3653 ((needaffix &&
3654 TESTAFF(sptr->getCont(), needaffix, sptr->getContLen())(std::binary_search(sptr->getCont(), sptr->getCont() + sptr
->getContLen(), needaffix))
) ||
3655 (circumfix &&
3656 TESTAFF(sptr->getCont(), circumfix, sptr->getContLen())(std::binary_search(sptr->getCont(), sptr->getCont() + sptr
->getContLen(), circumfix))
) ||
3657 (onlyincompound &&
3658 TESTAFF(sptr->getCont(), onlyincompound, sptr->getContLen())(std::binary_search(sptr->getCont(), sptr->getCont() + sptr
->getContLen(), onlyincompound))
)))) {
3659 std::string newword = sptr->add(ts, wl);
3660 if (!newword.empty()) {
3661 if (nh < maxn) {
3662 wlst[nh].word = mystrdup(newword.c_str());
3663 wlst[nh].allow = sptr->allowCross();
3664 wlst[nh].orig = nullptr;
3665 nh++;
3666 // add special phonetic version
3667 if (phon && (nh < maxn)) {
3668 std::string prefix(phon);
3669 std::string key(sptr->getKey());
3670 reverseword(key);
3671 prefix.append(key);
3672 wlst[nh].word = mystrdup(prefix.c_str());
3673 wlst[nh].allow = false;
3674 wlst[nh].orig = mystrdup(newword.c_str());
3675 nh++;
3676 }
3677 }
3678 }
3679 }
3680 sptr = sptr->getFlgNxt();
3681 }
3682 }
3683
3684 int n = nh;
3685
3686 // handle cross products of prefixes and suffixes
3687 for (int j = 1; j < n; j++)
3688 if (wlst[j].allow) {
3689 for (int k = 0; k < al; k++) {
3690 const auto c = (unsigned char)(ap[k] & 0x00FF);
3691 PfxEntry* cptr = pFlag[c];
3692 while (cptr) {
3693 if ((cptr->getFlag() == ap[k]) && cptr->allowCross() &&
3694 (!cptr->getKeyLen() ||
3695 ((badl > cptr->getKeyLen()) &&
3696 (strncmp(cptr->getKey(), bad, cptr->getKeyLen()) == 0)))) {
3697 int l1 = strlen(wlst[j].word);
3698 std::string newword = cptr->add(wlst[j].word, l1);
3699 if (!newword.empty()) {
3700 if (nh < maxn) {
3701 wlst[nh].word = mystrdup(newword.c_str());
3702 wlst[nh].allow = cptr->allowCross();
3703 wlst[nh].orig = nullptr;
3704 nh++;
3705 }
3706 }
3707 }
3708 cptr = cptr->getFlgNxt();
3709 }
3710 }
3711 }
3712
3713 // now handle pure prefixes
3714 for (int m = 0; m < al; m++) {
3715 const auto c = (unsigned char)(ap[m] & 0x00FF);
3716 PfxEntry* ptr = pFlag[c];
3717 while (ptr) {
3718 if ((ptr->getFlag() == ap[m]) &&
3719 (!ptr->getKeyLen() ||
3720 ((badl > ptr->getKeyLen()) &&
3721 (strncmp(ptr->getKey(), bad, ptr->getKeyLen()) == 0))) &&
3722 // check needaffix flag
3723 !(ptr->getCont() &&
3724 ((needaffix &&
3725 TESTAFF(ptr->getCont(), needaffix, ptr->getContLen())(std::binary_search(ptr->getCont(), ptr->getCont() + ptr
->getContLen(), needaffix))
) ||
3726 (circumfix &&
3727 TESTAFF(ptr->getCont(), circumfix, ptr->getContLen())(std::binary_search(ptr->getCont(), ptr->getCont() + ptr
->getContLen(), circumfix))
) ||
3728 (onlyincompound &&
3729 TESTAFF(ptr->getCont(), onlyincompound, ptr->getContLen())(std::binary_search(ptr->getCont(), ptr->getCont() + ptr
->getContLen(), onlyincompound))
)))) {
3730 std::string newword = ptr->add(ts, wl);
3731 if (!newword.empty()) {
3732 if (nh < maxn) {
3733 wlst[nh].word = mystrdup(newword.c_str());
3734 wlst[nh].allow = ptr->allowCross();
3735 wlst[nh].orig = nullptr;
3736 nh++;
3737 }
3738 }
3739 }
3740 ptr = ptr->getFlgNxt();
3741 }
3742 }
3743
3744 return nh;
3745}
3746
3747// return replacing table
3748const std::vector<replentry>& AffixMgr::get_reptable() const {
3749 return pHMgr->get_reptable();
3750}
3751
3752// return iconv table
3753RepList* AffixMgr::get_iconvtable() const {
3754 if (!iconvtable)
3755 return nullptr;
3756 return iconvtable;
3757}
3758
3759// return oconv table
3760RepList* AffixMgr::get_oconvtable() const {
3761 if (!oconvtable)
3762 return nullptr;
3763 return oconvtable;
3764}
3765
3766// return replacing table
3767struct phonetable* AffixMgr::get_phonetable() const {
3768 if (!phone)
3769 return nullptr;
3770 return phone;
3771}
3772
3773// return character map table
3774const std::vector<mapentry>& AffixMgr::get_maptable() const {
3775 return maptable;
3776}
3777
3778// return character map table
3779const std::vector<std::string>& AffixMgr::get_breaktable() const {
3780 return breaktable;
3781}
3782
3783// return text encoding of dictionary
3784const std::string& AffixMgr::get_encoding() {
3785 if (encoding.empty())
3786 encoding = SPELL_ENCODING"ISO8859-1";
3787 return encoding;
3788}
3789
3790// return text encoding of dictionary
3791int AffixMgr::get_langnum() const {
3792 return langnum;
3793}
3794
3795// return double prefix option
3796int AffixMgr::get_complexprefixes() const {
3797 return complexprefixes;
3798}
3799
3800// return FULLSTRIP option
3801int AffixMgr::get_fullstrip() const {
3802 return fullstrip;
3803}
3804
3805FLAGunsigned short AffixMgr::get_keepcase() const {
3806 return keepcase;
3807}
3808
3809FLAGunsigned short AffixMgr::get_forceucase() const {
3810 return forceucase;
3811}
3812
3813FLAGunsigned short AffixMgr::get_warn() const {
3814 return warn;
3815}
3816
3817int AffixMgr::get_forbidwarn() const {
3818 return forbidwarn;
3819}
3820
3821int AffixMgr::get_checksharps() const {
3822 return checksharps;
3823}
3824
3825std::string AffixMgr::encode_flag(unsigned short aflag) const {
3826 return pHMgr->encode_flag(aflag);
3827}
3828
3829// return the preferred ignore string for suggestions
3830const char* AffixMgr::get_ignore() const {
3831 if (ignorechars.empty())
3832 return nullptr;
3833 return ignorechars.c_str();
3834}
3835
3836// return the preferred ignore string for suggestions
3837const std::vector<w_char>& AffixMgr::get_ignore_utf16() const {
3838 return ignorechars_utf16;
3839}
3840
3841// return the keyboard string for suggestions
3842const std::string& AffixMgr::get_key_string() {
3843 if (keystring.empty())
3844 keystring = SPELL_KEYSTRING"qwertyuiop|asdfghjkl|zxcvbnm";
3845 return keystring;
3846}
3847
3848// return the preferred try string for suggestions
3849const std::string& AffixMgr::get_try_string() const {
3850 return trystring;
3851}
3852
3853// return the preferred try string for suggestions
3854const std::string& AffixMgr::get_wordchars() const {
3855 return wordchars;
3856}
3857
3858const std::vector<w_char>& AffixMgr::get_wordchars_utf16() const {
3859 return wordchars_utf16;
3860}
3861
3862// is there compounding?
3863int AffixMgr::get_compound() const {
3864 return compoundflag || compoundbegin || !defcpdtable.empty();
3865}
3866
3867// return the compound words control flag
3868FLAGunsigned short AffixMgr::get_compoundflag() const {
3869 return compoundflag;
3870}
3871
3872// return the forbidden words control flag
3873FLAGunsigned short AffixMgr::get_forbiddenword() const {
3874 return forbiddenword;
3875}
3876
3877// return the forbidden words control flag
3878FLAGunsigned short AffixMgr::get_nosuggest() const {
3879 return nosuggest;
3880}
3881
3882// return the forbidden words control flag
3883FLAGunsigned short AffixMgr::get_nongramsuggest() const {
3884 return nongramsuggest;
3885}
3886
3887// return the substandard root/affix control flag
3888FLAGunsigned short AffixMgr::get_substandard() const {
3889 return substandard;
3890}
3891
3892// return the forbidden words flag modify flag
3893FLAGunsigned short AffixMgr::get_needaffix() const {
3894 return needaffix;
3895}
3896
3897// return the circumfix flag
3898FLAGunsigned short AffixMgr::get_circumfix() const {
3899 return circumfix;
3900}
3901
3902// return the onlyincompound flag
3903FLAGunsigned short AffixMgr::get_onlyincompound() const {
3904 return onlyincompound;
3905}
3906
3907// return the value of suffix
3908const std::string& AffixMgr::get_version() const {
3909 return version;
3910}
3911
3912// utility method to look up root words in hash table
3913struct hentry* AffixMgr::lookup(const char* word, size_t len) {
3914 struct hentry* he = nullptr;
3915 for (size_t i = 0; i < alldic.size() && !he; ++i) {
3916 he = alldic[i]->lookup(word, len);
3917 }
3918 return he;
3919}
3920
3921// return the value of suffix
3922int AffixMgr::have_contclass() const {
3923 return havecontclass;
3924}
3925
3926// return utf8
3927int AffixMgr::get_utf8() const {
3928 return utf8;
3929}
3930
3931int AffixMgr::get_maxngramsugs() const {
3932 return maxngramsugs;
3933}
3934
3935int AffixMgr::get_maxcpdsugs() const {
3936 return maxcpdsugs;
3937}
3938
3939int AffixMgr::get_maxdiff() const {
3940 return maxdiff;
3941}
3942
3943int AffixMgr::get_onlymaxdiff() const {
3944 return onlymaxdiff;
3945}
3946
3947// return nosplitsugs
3948int AffixMgr::get_nosplitsugs() const {
3949 return nosplitsugs;
3950}
3951
3952// return sugswithdots
3953int AffixMgr::get_sugswithdots() const {
3954 return sugswithdots;
3955}
3956
3957/* parse flag */
3958bool AffixMgr::parse_flag(const std::string& line, unsigned short* out, FileMgr* af) {
3959 if (*out != FLAG_NULL0x00 && !(*out >= DEFAULTFLAGS65510)) {
3960 HUNSPELL_WARNING(
3961 stderrstderr,
3962 "error: line %d: multiple definitions of an affix file parameter\n",
3963 af->getlinenum());
3964 return false;
3965 }
3966 std::string s;
3967 if (!parse_string(line, s, af->getlinenum()))
3968 return false;
3969 *out = pHMgr->decode_flag(s);
3970 return true;
3971}
3972
3973/* parse num */
3974bool AffixMgr::parse_num(const std::string& line, int* out, FileMgr* af) {
3975 if (*out != -1) {
3976 HUNSPELL_WARNING(
3977 stderrstderr,
3978 "error: line %d: multiple definitions of an affix file parameter\n",
3979 af->getlinenum());
3980 return false;
3981 }
3982 std::string s;
3983 if (!parse_string(line, s, af->getlinenum()))
3984 return false;
3985 *out = atoi(s.c_str());
3986 return true;
3987}
3988
3989/* parse in the max syllablecount of compound words and */
3990bool AffixMgr::parse_cpdsyllable(const std::string& line, FileMgr* af) {
3991 int i = 0;
3992 int np = 0;
3993 auto iter = line.begin(), start_piece = mystrsep(line, iter);
3994 while (start_piece != line.end()) {
3995 switch (i) {
3996 case 0: {
3997 np++;
3998 break;
3999 }
4000 case 1: {
4001 cpdmaxsyllable = atoi(std::string(start_piece, iter).c_str());
4002 np++;
4003 break;
4004 }
4005 case 2: {
4006 if (!utf8) {
4007 cpdvowels.assign(start_piece, iter);
4008 std::sort(cpdvowels.begin(), cpdvowels.end());
4009 } else {
4010 std::string piece(start_piece, iter);
4011 u8_u16(cpdvowels_utf16, piece);
4012 std::sort(cpdvowels_utf16.begin(), cpdvowels_utf16.end());
4013 }
4014 np++;
4015 break;
4016 }
4017 default:
4018 break;
4019 }
4020 ++i;
4021 start_piece = mystrsep(line, iter);
4022 }
4023 if (np < 2) {
4024 HUNSPELL_WARNING(stderrstderr,
4025 "error: line %d: missing compoundsyllable information\n",
4026 af->getlinenum());
4027 return false;
4028 }
4029 if (np == 2)
4030 cpdvowels = "AEIOUaeiou";
4031 return true;
4032}
4033
4034bool AffixMgr::parse_convtable(const std::string& line,
4035 FileMgr* af,
4036 RepList** rl,
4037 const std::string& keyword) {
4038 if (*rl) {
4039 HUNSPELL_WARNING(stderrstderr, "error: line %d: multiple table definitions\n",
4040 af->getlinenum());
4041 return false;
4042 }
4043 int i = 0;
4044 int np = 0;
4045 int numrl = 0;
4046 auto iter = line.begin(), start_piece = mystrsep(line, iter);
4047 while (start_piece != line.end()) {
4048 switch (i) {
4049 case 0: {
4050 np++;
4051 break;
4052 }
4053 case 1: {
4054 numrl = atoi(std::string(start_piece, iter).c_str());
4055 if (numrl < 1) {
4056 HUNSPELL_WARNING(stderrstderr, "error: line %d: incorrect entry number\n",
4057 af->getlinenum());
4058 return false;
4059 }
4060 *rl = new RepList(numrl);
4061 if (!*rl)
4062 return false;
4063 np++;
4064 break;
4065 }
4066 default:
4067 break;
4068 }
4069 ++i;
4070 start_piece = mystrsep(line, iter);
4071 }
4072 if (np != 2) {
4073 HUNSPELL_WARNING(stderrstderr, "error: line %d: missing data\n",
4074 af->getlinenum());
4075 return false;
4076 }
4077
4078 /* now parse the num lines to read in the remainder of the table */
4079 for (int j = 0; j < numrl; j++) {
4080 std::string nl;
4081 if (!af->getline(nl))
4082 return false;
4083 mychomp(nl);
4084 i = 0;
4085 std::string pattern;
4086 std::string pattern2;
4087 iter = nl.begin();
4088 start_piece = mystrsep(nl, iter);
4089 while (start_piece != nl.end()) {
4090 {
4091 switch (i) {
4092 case 0: {
4093 if (nl.compare(start_piece - nl.begin(), keyword.size(), keyword, 0, keyword.size()) != 0) {
4094 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4095 af->getlinenum());
4096 delete *rl;
4097 *rl = nullptr;
4098 return false;
4099 }
4100 break;
4101 }
4102 case 1: {
4103 pattern.assign(start_piece, iter);
4104 break;
4105 }
4106 case 2: {
4107 pattern2.assign(start_piece, iter);
4108 break;
4109 }
4110 default:
4111 break;
4112 }
4113 ++i;
4114 }
4115 start_piece = mystrsep(nl, iter);
4116 }
4117 if (pattern.empty() || pattern2.empty()) {
4118 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4119 af->getlinenum());
4120 return false;
4121 }
4122
4123 (*rl)->add(pattern, pattern2);
4124 }
4125 return true;
4126}
4127
4128/* parse in the typical fault correcting table */
4129bool AffixMgr::parse_phonetable(const std::string& line, FileMgr* af) {
4130 if (phone) {
4131 HUNSPELL_WARNING(stderrstderr, "error: line %d: multiple table definitions\n",
4132 af->getlinenum());
4133 return false;
4134 }
4135 std::unique_ptr<phonetable> new_phone;
4136 int num = -1;
4137 int i = 0;
4138 int np = 0;
4139 auto iter = line.begin(), start_piece = mystrsep(line, iter);
4140 while (start_piece != line.end()) {
4141 switch (i) {
4142 case 0: {
4143 np++;
4144 break;
4145 }
4146 case 1: {
4147 num = atoi(std::string(start_piece, iter).c_str());
4148 if (num < 1) {
4149 HUNSPELL_WARNING(stderrstderr, "error: line %d: bad entry number\n",
4150 af->getlinenum());
4151 return false;
4152 }
4153 new_phone = std::make_unique<phonetable>();
4154 new_phone->utf8 = (char)utf8;
4155 np++;
4156 break;
4157 }
4158 default:
4159 break;
4160 }
4161 ++i;
4162 start_piece = mystrsep(line, iter);
4163 }
4164 if (np != 2) {
4165 HUNSPELL_WARNING(stderrstderr, "error: line %d: missing data\n",
4166 af->getlinenum());
4167 return false;
4168 }
4169
4170 /* now parse the phone->num lines to read in the remainder of the table */
4171 for (int j = 0; j < num; ++j) {
4172 std::string nl;
4173 if (!af->getline(nl))
4174 return false;
4175 mychomp(nl);
4176 i = 0;
4177 const size_t old_size = new_phone->rules.size();
4178 iter = nl.begin();
4179 start_piece = mystrsep(nl, iter);
4180 while (start_piece != nl.end()) {
4181 {
4182 switch (i) {
4183 case 0: {
4184 if (nl.compare(start_piece - nl.begin(), 5, "PHONE", 5) != 0) {
4185 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4186 af->getlinenum());
4187 return false;
4188 }
4189 break;
4190 }
4191 case 1: {
4192 new_phone->rules.emplace_back(start_piece, iter);
4193 break;
4194 }
4195 case 2: {
4196 new_phone->rules.emplace_back(start_piece, iter);
4197 mystrrep(new_phone->rules.back(), "_", "");
4198 break;
4199 }
4200 default:
4201 break;
4202 }
4203 ++i;
4204 }
4205 start_piece = mystrsep(nl, iter);
4206 }
4207 if (new_phone->rules.size() != old_size + 2) {
4208 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4209 af->getlinenum());
4210 return false;
4211 }
4212 }
4213 new_phone->rules.emplace_back("");
4214 new_phone->rules.emplace_back("");
4215 init_phonet_hash(*new_phone);
4216 phone = new_phone.release();
4217 return true;
4218}
4219
4220/* parse in the checkcompoundpattern table */
4221bool AffixMgr::parse_checkcpdtable(const std::string& line, FileMgr* af) {
4222 if (parsedcheckcpd) {
4223 HUNSPELL_WARNING(stderrstderr, "error: line %d: multiple table definitions\n",
4224 af->getlinenum());
4225 return false;
4226 }
4227 parsedcheckcpd = true;
4228 int numcheckcpd = -1;
4229 int i = 0;
4230 int np = 0;
4231 auto iter = line.begin(), start_piece = mystrsep(line, iter);
4232 while (start_piece != line.end()) {
4233 switch (i) {
4234 case 0: {
4235 np++;
4236 break;
4237 }
4238 case 1: {
4239 numcheckcpd = atoi(std::string(start_piece, iter).c_str());
4240 if (numcheckcpd < 1) {
4241 HUNSPELL_WARNING(stderrstderr, "error: line %d: bad entry number\n",
4242 af->getlinenum());
4243 return false;
4244 }
4245 checkcpdtable.reserve(std::min(numcheckcpd, 16384));
4246 np++;
4247 break;
4248 }
4249 default:
4250 break;
4251 }
4252 ++i;
4253 start_piece = mystrsep(line, iter);
4254 }
4255 if (np != 2) {
4256 HUNSPELL_WARNING(stderrstderr, "error: line %d: missing data\n",
4257 af->getlinenum());
4258 return false;
4259 }
4260
4261 /* now parse the numcheckcpd lines to read in the remainder of the table */
4262 for (int j = 0; j < numcheckcpd; ++j) {
4263 std::string nl;
4264 if (!af->getline(nl))
4265 return false;
4266 mychomp(nl);
4267 i = 0;
4268 checkcpdtable.emplace_back();
4269 iter = nl.begin();
4270 start_piece = mystrsep(nl, iter);
4271 while (start_piece != nl.end()) {
4272 switch (i) {
4273 case 0: {
4274 if (nl.compare(start_piece - nl.begin(), 20, "CHECKCOMPOUNDPATTERN", 20) != 0) {
4275 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4276 af->getlinenum());
4277 checkcpdtable.clear();
4278 return false;
4279 }
4280 break;
4281 }
4282 case 1: {
4283 checkcpdtable.back().pattern.assign(start_piece, iter);
4284 size_t slash_pos = checkcpdtable.back().pattern.find('/');
4285 if (slash_pos != std::string::npos) {
4286 std::string chunk(checkcpdtable.back().pattern, slash_pos + 1);
4287 checkcpdtable.back().pattern.resize(slash_pos);
4288 checkcpdtable.back().cond = pHMgr->decode_flag(chunk);
4289 }
4290 break;
4291 }
4292 case 2: {
4293 checkcpdtable.back().pattern2.assign(start_piece, iter);
4294 size_t slash_pos = checkcpdtable.back().pattern2.find('/');
4295 if (slash_pos != std::string::npos) {
4296 std::string chunk(checkcpdtable.back().pattern2, slash_pos + 1);
4297 checkcpdtable.back().pattern2.resize(slash_pos);
4298 checkcpdtable.back().cond2 = pHMgr->decode_flag(chunk);
4299 }
4300 break;
4301 }
4302 case 3: {
4303 checkcpdtable.back().pattern3.assign(start_piece, iter);
4304 simplifiedcpd = 1;
4305 break;
4306 }
4307 default:
4308 break;
4309 }
4310 i++;
4311 start_piece = mystrsep(nl, iter);
4312 }
4313 }
4314 return true;
4315}
4316
4317/* parse in the compound rule table */
4318bool AffixMgr::parse_defcpdtable(const std::string& line, FileMgr* af) {
4319 if (parseddefcpd) {
4320 HUNSPELL_WARNING(stderrstderr, "error: line %d: multiple table definitions\n",
4321 af->getlinenum());
4322 return false;
4323 }
4324 parseddefcpd = true;
4325 int numdefcpd = -1;
4326 int i = 0;
4327 int np = 0;
4328 auto iter = line.begin(), start_piece = mystrsep(line, iter);
4329 while (start_piece != line.end()) {
4330 switch (i) {
4331 case 0: {
4332 np++;
4333 break;
4334 }
4335 case 1: {
4336 numdefcpd = atoi(std::string(start_piece, iter).c_str());
4337 if (numdefcpd < 1) {
4338 HUNSPELL_WARNING(stderrstderr, "error: line %d: bad entry number\n",
4339 af->getlinenum());
4340 return false;
4341 }
4342 defcpdtable.reserve(std::min(numdefcpd, 16384));
4343 np++;
4344 break;
4345 }
4346 default:
4347 break;
4348 }
4349 ++i;
4350 start_piece = mystrsep(line, iter);
4351 }
4352 if (np != 2) {
4353 HUNSPELL_WARNING(stderrstderr, "error: line %d: missing data\n",
4354 af->getlinenum());
4355 return false;
4356 }
4357
4358 /* now parse the numdefcpd lines to read in the remainder of the table */
4359 for (int j = 0; j < numdefcpd; ++j) {
4360 std::string nl;
4361 if (!af->getline(nl))
4362 return false;
4363 mychomp(nl);
4364 i = 0;
4365 defcpdtable.emplace_back();
4366 iter = nl.begin();
4367 start_piece = mystrsep(nl, iter);
4368 while (start_piece != nl.end()) {
4369 switch (i) {
4370 case 0: {
4371 if (nl.compare(start_piece - nl.begin(), 12, "COMPOUNDRULE", 12) != 0) {
4372 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4373 af->getlinenum());
4374 numdefcpd = 0;
4375 return false;
4376 }
4377 break;
4378 }
4379 case 1: { // handle parenthesized flags
4380 if (std::find(start_piece, iter, '(') != iter) {
4381 for (auto k = start_piece; k != iter; ++k) {
4382 auto chb = k, che = k + 1;
4383 if (*k == '(') {
4384 auto parpos = std::find(k, iter, ')');
4385 if (parpos != iter) {
4386 chb = k + 1;
4387 che = parpos;
4388 k = parpos;
4389 }
4390 }
4391
4392 if (*chb == '*' || *chb == '?') {
4393 defcpdtable.back().push_back((FLAGunsigned short)*chb);
4394 } else {
4395 pHMgr->decode_flags(defcpdtable.back(), std::string(chb, che), af);
4396 }
4397 }
4398 } else {
4399 pHMgr->decode_flags(defcpdtable.back(), std::string(start_piece, iter), af);
4400 }
4401 break;
4402 }
4403 default:
4404 break;
4405 }
4406 ++i;
4407 start_piece = mystrsep(nl, iter);
4408 }
4409 if (defcpdtable.back().empty()) {
4410 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4411 af->getlinenum());
4412 return false;
4413 }
4414 }
4415 return true;
4416}
4417
4418/* parse in the character map table */
4419bool AffixMgr::parse_maptable(const std::string& line, FileMgr* af) {
4420 if (parsedmaptable) {
4421 HUNSPELL_WARNING(stderrstderr, "error: line %d: multiple table definitions\n",
4422 af->getlinenum());
4423 return false;
4424 }
4425 parsedmaptable = true;
4426 int nummap = -1;
4427 int i = 0;
4428 int np = 0;
4429 auto iter = line.begin(), start_piece = mystrsep(line, iter);
4430 while (start_piece != line.end()) {
4431 switch (i) {
4432 case 0: {
4433 np++;
4434 break;
4435 }
4436 case 1: {
4437 nummap = atoi(std::string(start_piece, iter).c_str());
4438 if (nummap < 1) {
4439 HUNSPELL_WARNING(stderrstderr, "error: line %d: bad entry number\n",
4440 af->getlinenum());
4441 return false;
4442 }
4443 maptable.reserve(std::min(nummap, 16384));
4444 np++;
4445 break;
4446 }
4447 default:
4448 break;
4449 }
4450 ++i;
4451 start_piece = mystrsep(line, iter);
4452 }
4453 if (np != 2) {
4454 HUNSPELL_WARNING(stderrstderr, "error: line %d: missing data\n",
4455 af->getlinenum());
4456 return false;
4457 }
4458
4459 /* now parse the nummap lines to read in the remainder of the table */
4460 for (int j = 0; j < nummap; ++j) {
4461 std::string nl;
4462 if (!af->getline(nl))
4463 return false;
4464 mychomp(nl);
4465 i = 0;
4466 maptable.emplace_back();
4467 iter = nl.begin();
4468 start_piece = mystrsep(nl, iter);
4469 while (start_piece != nl.end()) {
4470 switch (i) {
4471 case 0: {
4472 if (nl.compare(start_piece - nl.begin(), 3, "MAP", 3) != 0) {
4473 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4474 af->getlinenum());
4475 nummap = 0;
Value stored to 'nummap' is never read
4476 return false;
4477 }
4478 break;
4479 }
4480 case 1: {
4481 for (auto k = start_piece; k != iter; ++k) {
4482 auto chb = k, che = k + 1;
4483 if (*k == '(') {
4484 auto parpos = std::find(k, iter, ')');
4485 if (parpos != iter) {
4486 chb = k + 1;
4487 che = parpos;
4488 k = parpos;
4489 }
4490 } else {
4491 if (utf8 && (*k & 0xc0) == 0xc0) {
4492 ++k;
4493 while (k != iter && is_utf8_cont(*k))
4494 ++k;
4495 che = k;
4496 --k;
4497 }
4498 }
4499 if (chb == che) {
4500 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4501 af->getlinenum());
4502 }
4503
4504 maptable.back().emplace_back(chb, che);
4505 }
4506 break;
4507 }
4508 default:
4509 break;
4510 }
4511 ++i;
4512 start_piece = mystrsep(nl, iter);
4513 }
4514 if (maptable.back().empty()) {
4515 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4516 af->getlinenum());
4517 return false;
4518 }
4519 }
4520 return true;
4521}
4522
4523/* parse in the word breakpoint table */
4524bool AffixMgr::parse_breaktable(const std::string& line, FileMgr* af) {
4525 if (parsedbreaktable) {
4526 HUNSPELL_WARNING(stderrstderr, "error: line %d: multiple table definitions\n",
4527 af->getlinenum());
4528 return false;
4529 }
4530 parsedbreaktable = true;
4531 int numbreak = -1;
4532 int i = 0;
4533 int np = 0;
4534 auto iter = line.begin(), start_piece = mystrsep(line, iter);
4535 while (start_piece != line.end()) {
4536 switch (i) {
4537 case 0: {
4538 np++;
4539 break;
4540 }
4541 case 1: {
4542 numbreak = atoi(std::string(start_piece, iter).c_str());
4543 if (numbreak < 0) {
4544 HUNSPELL_WARNING(stderrstderr, "error: line %d: bad entry number\n",
4545 af->getlinenum());
4546 return false;
4547 }
4548 if (numbreak == 0)
4549 return true;
4550 breaktable.reserve(std::min(numbreak, 16384));
4551 np++;
4552 break;
4553 }
4554 default:
4555 break;
4556 }
4557 ++i;
4558 start_piece = mystrsep(line, iter);
4559 }
4560 if (np != 2) {
4561 HUNSPELL_WARNING(stderrstderr, "error: line %d: missing data\n",
4562 af->getlinenum());
4563 return false;
4564 }
4565
4566 /* now parse the numbreak lines to read in the remainder of the table */
4567 for (int j = 0; j < numbreak; ++j) {
4568 std::string nl;
4569 if (!af->getline(nl))
4570 return false;
4571 mychomp(nl);
4572 i = 0;
4573 iter = nl.begin();
4574 start_piece = mystrsep(nl, iter);
4575 while (start_piece != nl.end()) {
4576 switch (i) {
4577 case 0: {
4578 if (nl.compare(start_piece - nl.begin(), 5, "BREAK", 5) != 0) {
4579 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4580 af->getlinenum());
4581 numbreak = 0;
4582 return false;
4583 }
4584 break;
4585 }
4586 case 1: {
4587 breaktable.emplace_back(start_piece, iter);
4588 break;
4589 }
4590 default:
4591 break;
4592 }
4593 ++i;
4594 start_piece = mystrsep(nl, iter);
4595 }
4596 }
4597
4598 if (breaktable.size() != static_cast<size_t>(numbreak)) {
4599 HUNSPELL_WARNING(stderrstderr, "error: line %d: table is corrupt\n",
4600 af->getlinenum());
4601 return false;
4602 }
4603
4604 return true;
4605}
4606
4607void AffixMgr::reverse_condition(std::string& piece) {
4608 if (piece.empty())
4609 return;
4610
4611 int neg = 0;
4612 // iterate backwards; k wraps to npos (SIZE_MAX) when decremented past 0
4613 for (size_t k = piece.size() - 1; k != std::string::npos; --k) {
4614 switch (piece[k]) {
4615 case '[': {
4616 if (neg)
4617 piece[k + 1] = '[';
4618 else
4619 piece[k] = ']';
4620 break;
4621 }
4622 case ']': {
4623 piece[k] = '[';
4624 if (neg)
4625 piece[k + 1] = '^';
4626 neg = 0;
4627 break;
4628 }
4629 case '^': {
4630 if (piece[k + 1] == ']')
4631 neg = 1;
4632 else if (neg)
4633 piece[k + 1] = piece[k];
4634 break;
4635 }
4636 default: {
4637 if (neg)
4638 piece[k + 1] = piece[k];
4639 }
4640 }
4641 }
4642}
4643
4644class entries_container {
4645 std::vector<AffEntry*> entries;
4646 AffixMgr* m_mgr;
4647 char m_at;
4648public:
4649 entries_container(char at, AffixMgr* mgr)
4650 : m_mgr(mgr)
4651 , m_at(at) {
4652 }
4653 void release() {
4654 entries.clear();
4655 }
4656 void initialize(int numents,
4657 char opts, unsigned short aflag) {
4658 entries.reserve(std::min(numents, 16384));
4659
4660 if (m_at == 'P') {
4661 entries.push_back(new PfxEntry(m_mgr));
4662 } else {
4663 entries.push_back(new SfxEntry(m_mgr));
4664 }
4665
4666 entries.back()->opts = opts;
4667 entries.back()->aflag = aflag;
4668 }
4669
4670 AffEntry* add_entry(char opts) {
4671 if (m_at == 'P') {
4672 entries.push_back(new PfxEntry(m_mgr));
4673 } else {
4674 entries.push_back(new SfxEntry(m_mgr));
4675 }
4676 AffEntry* ret = entries.back();
4677 ret->opts = entries[0]->opts & opts;
4678 return ret;
4679 }
4680
4681 AffEntry* first_entry() { return entries.empty() ? nullptr : entries[0]; }
4682
4683 ~entries_container() {
4684 for (auto& entry : entries) {
4685 delete entry;
4686 }
4687 }
4688
4689 std::vector<AffEntry*>::iterator begin() { return entries.begin(); }
4690 std::vector<AffEntry*>::iterator end() { return entries.end(); }
4691};
4692
4693bool AffixMgr::parse_affix(const std::string& line,
4694 const char at,
4695 FileMgr* af,
4696 char* dupflags) {
4697 int numents = 0; // number of AffEntry structures to parse
4698
4699 unsigned short aflag = 0; // affix char identifier
4700
4701 char ff = 0;
4702 char xprod = 0;
4703 int headerline = af->getlinenum();
4704 entries_container affentries(at, this);
4705
4706 int i = 0;
4707
4708// checking lines with bad syntax
4709#ifdef DEBUG1
4710 int basefieldnum = 0;
4711#endif
4712
4713 // split affix header line into pieces
4714
4715 int np = 0;
4716 auto iter = line.begin(), start_piece = mystrsep(line, iter);
4717 while (start_piece != line.end()) {
4718 switch (i) {
4719 // piece 1 - is type of affix
4720 case 0: {
4721 np++;
4722 break;
4723 }
4724
4725 // piece 2 - is affix char
4726 case 1: {
4727 np++;
4728 aflag = pHMgr->decode_flag(std::string(start_piece, iter));
4729 if (((at == 'S') && (dupflags[aflag] & dupSFX(1 << 0))) ||
4730 ((at == 'P') && (dupflags[aflag] & dupPFX(1 << 1)))) {
4731 HUNSPELL_WARNING(
4732 stderrstderr,
4733 "error: line %d: multiple definitions of an affix flag\n",
4734 af->getlinenum());
4735 }
4736 dupflags[aflag] += (char)((at == 'S') ? dupSFX(1 << 0) : dupPFX(1 << 1));
4737 break;
4738 }
4739 // piece 3 - is cross product indicator
4740 case 2: {
4741 np++;
4742 xprod = *start_piece;
4743 if (xprod == 'Y')
4744 ff = aeXPRODUCT(1 << 0);
4745 break;
4746 }
4747
4748 // piece 4 - is number of affentries
4749 case 3: {
4750 np++;
4751 numents = atoi(std::string(start_piece, iter).c_str());
4752 if ((numents <= 0) || ((std::numeric_limits<size_t>::max() /
4753 sizeof(AffEntry)) < static_cast<size_t>(numents))) {
4754 std::string err = pHMgr->encode_flag(aflag);
4755 HUNSPELL_WARNING(stderrstderr, "error: line %d: affix %s: bad entry number\n",
4756 af->getlinenum(), err.c_str());
4757 return false;
4758 }
4759
4760 char opts = ff;
4761 if (utf8)
4762 opts |= aeUTF8(1 << 1);
4763 if (pHMgr->is_aliasf())
4764 opts |= aeALIASF(1 << 2);
4765 if (pHMgr->is_aliasm())
4766 opts |= aeALIASM(1 << 3);
4767 affentries.initialize(numents, opts, aflag);
4768 }
4769
4770 default:
4771 break;
4772 }
4773 ++i;
4774 start_piece = mystrsep(line, iter);
4775 }
4776 // check to make sure we parsed enough pieces
4777 if (np != 4) {
4778 std::string err = pHMgr->encode_flag(aflag);
4779 HUNSPELL_WARNING(stderrstderr, "error: line %d: affix %s: missing data\n",
4780 af->getlinenum(), err.c_str());
4781 return false;
4782 }
4783
4784 // now parse numents affentries for this affix
4785 AffEntry* entry = affentries.first_entry();
4786 for (int ent = 0; ent < numents; ++ent) {
4787 std::string nl;
4788 if (!af->getline(nl))
4789 return false;
4790 mychomp(nl);
4791 int ruleline = af->getlinenum();
4792
4793 iter = nl.begin();
4794 i = 0;
4795 np = 0;
4796
4797 // split line into pieces
4798 start_piece = mystrsep(nl, iter);
4799 while (start_piece != nl.end()) {
4800 switch (i) {
4801 // piece 1 - is type
4802 case 0: {
4803 np++;
4804 if (ent != 0)
4805 entry = affentries.add_entry((char)(aeXPRODUCT(1 << 0) | aeUTF8(1 << 1) | aeALIASF(1 << 2) | aeALIASM(1 << 3)));
4806 break;
4807 }
4808
4809 // piece 2 - is affix char
4810 case 1: {
4811 np++;
4812 std::string chunk(start_piece, iter);
4813 if (pHMgr->decode_flag(chunk) != aflag) {
4814 std::string err = pHMgr->encode_flag(aflag);
4815 HUNSPELL_WARNING(stderrstderr,
4816 "error: line %d: affix %s is corrupt\n",
4817 af->getlinenum(), err.c_str());
4818 return false;
4819 }
4820
4821 if (ent != 0) {
4822 AffEntry* start_entry = affentries.first_entry();
4823 entry->aflag = start_entry->aflag;
4824 }
4825 break;
4826 }
4827
4828 // piece 3 - is string to strip or 0 for null
4829 case 2: {
4830 np++;
4831 entry->strip = std::string(start_piece, iter);
4832 if (complexprefixes) {
4833 if (utf8)
4834 reverseword_utf(entry->strip);
4835 else
4836 reverseword(entry->strip);
4837 }
4838 if (entry->strip.compare("0") == 0) {
4839 entry->strip.clear();
4840 }
4841 break;
4842 }
4843
4844 // piece 4 - is affix string or 0 for null
4845 case 3: {
4846 entry->morphcode = nullptr;
4847 entry->contclass = nullptr;
4848 entry->contclasslen = 0;
4849 np++;
4850 std::string::const_iterator dash = std::find(start_piece, iter, '/');
4851 if (dash != iter) {
4852 entry->appnd = std::string(start_piece, dash);
4853 std::string dash_str(dash + 1, iter);
4854
4855 if (!ignorechars.empty() && !has_no_ignored_chars(entry->appnd, ignorechars)) {
4856 if (utf8) {
4857 remove_ignored_chars_utf(entry->appnd, ignorechars_utf16);
4858 } else {
4859 remove_ignored_chars(entry->appnd, ignorechars);
4860 }
4861 }
4862
4863 if (complexprefixes) {
4864 if (utf8)
4865 reverseword_utf(entry->appnd);
4866 else
4867 reverseword(entry->appnd);
4868 }
4869
4870 if (pHMgr->is_aliasf()) {
4871 int index = atoi(dash_str.c_str());
4872 entry->contclasslen = (unsigned short)pHMgr->get_aliasf(
4873 index, &(entry->contclass), af);
4874 if (!entry->contclasslen)
4875 HUNSPELL_WARNING(stderrstderr,
4876 "error: bad affix flag alias: \"%s\"\n",
4877 dash_str.c_str());
4878 } else {
4879 entry->contclasslen = (unsigned short)pHMgr->decode_flags(
4880 &(entry->contclass), dash_str, af);
4881 std::sort(entry->contclass, entry->contclass + entry->contclasslen);
4882 }
4883
4884 havecontclass = 1;
4885 for (unsigned short _i = 0; _i < entry->contclasslen; _i++) {
4886 contclasses[(entry->contclass)[_i]] = 1;
4887 }
4888 } else {
4889 entry->appnd = std::string(start_piece, iter);
4890
4891 if (!ignorechars.empty() && !has_no_ignored_chars(entry->appnd, ignorechars)) {
4892 if (utf8) {
4893 remove_ignored_chars_utf(entry->appnd, ignorechars_utf16);
4894 } else {
4895 remove_ignored_chars(entry->appnd, ignorechars);
4896 }
4897 }
4898
4899 if (complexprefixes) {
4900 if (utf8)
4901 reverseword_utf(entry->appnd);
4902 else
4903 reverseword(entry->appnd);
4904 }
4905 }
4906
4907 if (entry->appnd.compare("0") == 0) {
4908 entry->appnd.clear();
4909 }
4910 break;
4911 }
4912
4913 // piece 5 - is the conditions descriptions
4914 case 4: {
4915 std::string chunk(start_piece, iter);
4916 np++;
4917 if (complexprefixes) {
4918 if (utf8)
4919 reverseword_utf(chunk);
4920 else
4921 reverseword(chunk);
4922 reverse_condition(chunk);
4923 }
4924 if (!entry->strip.empty() && chunk != "." &&
4925 redundant_condition(at, entry->strip, chunk,
4926 af->getlinenum())) {
4927 chunk = ".";
4928 entry->opts |= aeREDUNDANTCOND(1 << 5);
4929 }
4930 if (at == 'S') {
4931 reverseword(chunk);
4932 reverse_condition(chunk);
4933 }
4934 if (encodeit(*entry, chunk))
4935 return false;
4936 break;
4937 }
4938
4939 case 5: {
4940 std::string chunk(start_piece, iter);
4941 np++;
4942 if (pHMgr->is_aliasm()) {
4943 int index = atoi(chunk.c_str());
4944 entry->morphcode = pHMgr->get_aliasm(index);
4945 } else {
4946 if (complexprefixes) { // XXX - fix me for morph. gen.
4947 if (utf8)
4948 reverseword_utf(chunk);
4949 else
4950 reverseword(chunk);
4951 }
4952 // add the remaining of the line
4953 std::string::const_iterator end = nl.end();
4954 if (iter != end) {
4955 chunk.append(iter, end);
4956 }
4957 entry->morphcode = mystrdup(chunk.c_str());
4958 }
4959 break;
4960 }
4961 default:
4962 break;
4963 }
4964 i++;
4965 start_piece = mystrsep(nl, iter);
4966 }
4967 // check to make sure we parsed enough pieces
4968 if (np < 4) {
4969 std::string err = pHMgr->encode_flag(aflag);
4970 HUNSPELL_WARNING(stderrstderr, "error: line %d: affix %s is corrupt\n",
4971 af->getlinenum(), err.c_str());
4972 return false;
4973 }
4974
4975 entry->line = ruleline;
4976 entry->headerline = headerline;
4977 entry->xprod = xprod;
4978
4979#ifdef DEBUG1
4980 // detect unnecessary fields, excepting comments
4981 if (basefieldnum) {
4982 int fieldnum =
4983 !(entry->morphcode) ? 5 : ((*(entry->morphcode) == '#') ? 5 : 6);
4984 if (fieldnum != basefieldnum)
4985 HUNSPELL_WARNING(stderrstderr, "warning: line %d: bad field number\n",
4986 af->getlinenum());
4987 } else {
4988 basefieldnum =
4989 !(entry->morphcode) ? 5 : ((*(entry->morphcode) == '#') ? 5 : 6);
4990 }
4991#endif
4992 }
4993
4994 // now create SfxEntry or PfxEntry objects and use links to
4995 // build an ordered (sorted by affix string) list
4996 auto start = affentries.begin(), end = affentries.end();
4997 for (auto affentry = start; affentry != end; ++affentry) {
4998 if (at == 'P') {
4999 build_pfxtree(static_cast<PfxEntry*>(*affentry));
5000 } else {
5001 build_sfxtree(static_cast<SfxEntry*>(*affentry));
5002 }
5003 }
5004
5005 //contents belong to AffixMgr now
5006 affentries.release();
5007
5008 return true;
5009}
5010
5011int AffixMgr::redundant_condition(char ft,
5012 const std::string& strip,
5013 const std::string& cond,
5014 int linenum) {
5015 int stripl = strip.size(), condl = cond.size(), i, j, neg, in;
5016 if (ft == 'P') { // prefix
5017 if (strip.compare(0, condl, cond) == 0)
5018 return 1;
5019 if (utf8) {
5020 } else {
5021 for (i = 0, j = 0; (i < stripl) && (j < condl); i++, j++) {
5022 if (cond[j] != '[') {
5023 if (cond[j] != strip[i]) {
5024 HUNSPELL_WARNING(stderrstderr,
5025 "warning: line %d: incompatible stripping "
5026 "characters and condition\n",
5027 linenum);
5028 return 0;
5029 }
5030 } else {
5031 neg = (cond[j + 1] == '^') ? 1 : 0;
5032 in = 0;
5033 do {
5034 j++;
5035 if (strip[i] == cond[j])
5036 in = 1;
5037 } while ((j < (condl - 1)) && (cond[j] != ']'));
5038 if (j == (condl - 1) && (cond[j] != ']')) {
5039 HUNSPELL_WARNING(stderrstderr,
5040 "error: line %d: missing ] in condition:\n%s\n",
5041 linenum, cond.c_str());
5042 return 0;
5043 }
5044 if ((!neg && !in) || (neg && in)) {
5045 HUNSPELL_WARNING(stderrstderr,
5046 "warning: line %d: incompatible stripping "
5047 "characters and condition\n",
5048 linenum);
5049 return 0;
5050 }
5051 }
5052 }
5053 if (j >= condl)
5054 return 1;
5055 }
5056 } else { // suffix
5057 if ((stripl >= condl) && strip.compare(stripl - condl, std::string::npos, cond) == 0)
5058 return 1;
5059 if (utf8) {
5060 } else {
5061 for (i = stripl - 1, j = condl - 1; (i >= 0) && (j >= 0); i--, j--) {
5062 if (cond[j] != ']') {
5063 if (cond[j] != strip[i]) {
5064 HUNSPELL_WARNING(stderrstderr,
5065 "warning: line %d: incompatible stripping "
5066 "characters and condition\n",
5067 linenum);
5068 return 0;
5069 }
5070 } else if (j > 0) {
5071 in = 0;
5072 do {
5073 j--;
5074 if (strip[i] == cond[j])
5075 in = 1;
5076 } while ((j > 0) && (cond[j] != '['));
5077 if ((j == 0) && (cond[j] != '[')) {
5078 HUNSPELL_WARNING(stderrstderr,
5079 "error: line: %d: missing ] in condition:\n%s\n",
5080 linenum, cond.c_str());
5081 return 0;
5082 }
5083 neg = (cond[j + 1] == '^') ? 1 : 0;
5084 if ((!neg && !in) || (neg && in)) {
5085 HUNSPELL_WARNING(stderrstderr,
5086 "warning: line %d: incompatible stripping "
5087 "characters and condition\n",
5088 linenum);
5089 return 0;
5090 }
5091 }
5092 }
5093 if (j < 0)
5094 return 1;
5095 }
5096 }
5097 return 0;
5098}
5099
5100std::vector<std::string> AffixMgr::get_suffix_words(short unsigned* suff,
5101 int len,
5102 const std::string& root_word) {
5103 std::vector<std::string> slst;
5104 AffixScratch scratch;
5105 short unsigned* start_ptr = suff;
5106 for (auto ptr : sStart) {
5107 while (ptr) {
5108 suff = start_ptr;
5109 for (int i = 0; i < len; i++) {
5110 if ((*suff) == ptr->getFlag()) {
5111 std::string nw(root_word);
5112 nw.append(ptr->getAffix());
5113 hentry* ht = ptr->checkword(nw, 0, nw.size(), 0, nullptr, 0, 0, 0, scratch);
5114 if (ht) {
5115 slst.push_back(std::move(nw));
5116 }
5117 }
5118 suff++;
5119 }
5120 ptr = ptr->getNext();
5121 }
5122 }
5123 return slst;
5124}