/src/hunspell/src/hunspell/hashmgr.cxx
Line | Count | Source |
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 <algorithm> |
72 | | #include <cassert> |
73 | | #include <cstddef> |
74 | | #include <cstdlib> |
75 | | #include <cstring> |
76 | | #include <cstdio> |
77 | | #include <cctype> |
78 | | #include <limits> |
79 | | #include <sstream> |
80 | | #include <type_traits> |
81 | | #if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) |
82 | | #include <bit> |
83 | | #endif |
84 | | |
85 | | #include "hashmgr.hxx" |
86 | | #include "csutil.hxx" |
87 | | #include "atypes.hxx" |
88 | | #include "langnum.hxx" |
89 | | |
90 | | // build a hash table from a munched word list |
91 | | |
92 | | HashMgr::HashMgr(const char* tpath, const char* apath, const char* key) |
93 | 55.9k | : flag_mode(FLAG_CHAR) |
94 | 55.9k | , complexprefixes(0) |
95 | 55.9k | , utf8(0) |
96 | 55.9k | , forbiddenword(FORBIDDENWORD) // forbidden word signing flag |
97 | 55.9k | , langnum(0) |
98 | 55.9k | , csconv(nullptr) { |
99 | 55.9k | load_config(apath, key); |
100 | 55.9k | if (!csconv) |
101 | 54.4k | csconv = get_current_cs(SPELL_ENCODING); |
102 | 55.9k | int ec = load_tables(tpath, key); |
103 | 55.9k | if (ec) { |
104 | | /* error condition - what should we do here */ |
105 | 31.5k | fprintf(stderr, "Hash Manager Error : %d\n", ec); |
106 | 31.5k | free_table(); |
107 | | //keep table size to 1 to fix possible division with zero |
108 | 31.5k | tableptr.resize(1, nullptr); |
109 | 31.5k | } |
110 | 55.9k | } |
111 | | |
112 | 2.79M | void HashMgr::release_flags(unsigned short* astr, bool owned) { |
113 | 2.79M | if (owned) |
114 | 53.8k | delete[] astr; |
115 | 2.79M | } |
116 | | |
117 | 87.4k | void HashMgr::free_table() { |
118 | | // now pass through hash table freeing up everything |
119 | | // go through column by column of the table |
120 | 26.0M | for (auto ptr : tableptr) { |
121 | 26.0M | hentry* nt = nullptr; |
122 | 27.4M | while (ptr) { |
123 | 1.33M | nt = ptr->next; |
124 | 1.33M | release_flags(ptr->astr, ptr->var & H_OPT_OWNFLAGS); |
125 | 1.33M | arena_free(ptr); |
126 | 1.33M | ptr = nt; |
127 | 1.33M | } |
128 | 26.0M | } |
129 | 87.4k | tableptr.clear(); |
130 | 87.4k | } |
131 | | |
132 | 55.9k | HashMgr::~HashMgr() { |
133 | 55.9k | free_table(); |
134 | | |
135 | 55.9k | static_assert(std::is_trivially_destructible<unsigned short>::value, |
136 | 55.9k | "arena_free replaces delete[]; aliasf elements must have trivial destructors"); |
137 | 55.9k | for (auto& j : aliasf) |
138 | 4.21k | arena_free(j); |
139 | 55.9k | aliasf.clear(); |
140 | | |
141 | 55.9k | static_assert(std::is_trivially_destructible<char>::value, |
142 | 55.9k | "arena_free replaces delete[]; aliasm elements must have trivial destructors"); |
143 | 55.9k | for (auto& j : aliasm) |
144 | 3.87k | arena_free(j); |
145 | 55.9k | aliasm.clear(); |
146 | | |
147 | | #ifdef MOZILLA_CLIENT |
148 | | delete[] csconv; |
149 | | #endif |
150 | 55.9k | } |
151 | | |
152 | | // lookup a root word in the hashtable |
153 | | |
154 | 5.25G | struct hentry* HashMgr::lookup(const char* word, size_t len) const { |
155 | 5.25G | struct hentry* dp = tableptr[hash(word, len)]; |
156 | 5.25G | if (!dp) |
157 | 4.58G | return nullptr; |
158 | 1.34G | for (; dp != nullptr; dp = dp->next) { |
159 | 989M | if (strcmp(word, dp->word) == 0) |
160 | 308M | return dp; |
161 | 989M | } |
162 | 359M | return nullptr; |
163 | 668M | } |
164 | | |
165 | | // add a word to the hash table (private) |
166 | | int HashMgr::add_word(const std::string& in_word, |
167 | | int wcl, |
168 | | unsigned short* aff, |
169 | | int al, |
170 | | const std::string* in_desc, |
171 | | bool onlyupcase, |
172 | | int captype, |
173 | 2.78M | bool own_aff) { |
174 | | |
175 | 2.78M | if (al > std::numeric_limits<short>::max()) { |
176 | 17 | HUNSPELL_WARNING(stderr, "error: affix len %d is over max limit\n", al); |
177 | 17 | release_flags(aff, own_aff); |
178 | 17 | return 1; |
179 | 17 | } |
180 | | |
181 | 2.78M | const std::string* word = &in_word; |
182 | 2.78M | const std::string* desc = in_desc; |
183 | | |
184 | 2.78M | std::string* word_copy = nullptr; |
185 | 2.78M | std::string* desc_copy = nullptr; |
186 | 2.78M | if ((!ignorechars.empty() && !has_no_ignored_chars(in_word, ignorechars)) || complexprefixes) { |
187 | 660k | word_copy = new std::string(in_word); |
188 | | |
189 | 660k | if (!ignorechars.empty()) { |
190 | 196k | if (utf8) { |
191 | 76.5k | wcl = remove_ignored_chars_utf(*word_copy, ignorechars_utf16); |
192 | 120k | } else { |
193 | 120k | remove_ignored_chars(*word_copy, ignorechars); |
194 | 120k | } |
195 | 196k | } |
196 | | |
197 | 660k | if (complexprefixes) { |
198 | 521k | if (utf8) |
199 | 71.1k | wcl = reverseword_utf(*word_copy); |
200 | 450k | else |
201 | 450k | reverseword(*word_copy); |
202 | | |
203 | 521k | if (in_desc && aliasm.empty()) { |
204 | 115k | desc_copy = new std::string(*in_desc); |
205 | | |
206 | 115k | if (complexprefixes) { |
207 | 115k | if (utf8) |
208 | 17.5k | reverseword_utf(*desc_copy); |
209 | 98.2k | else |
210 | 98.2k | reverseword(*desc_copy); |
211 | 115k | } |
212 | 115k | desc = desc_copy; |
213 | 115k | } |
214 | 521k | } |
215 | | |
216 | 660k | word = word_copy; |
217 | 660k | } |
218 | | |
219 | | // limit of hp->blen |
220 | 2.78M | if (word->size() > std::numeric_limits<unsigned short>::max()) { |
221 | 324 | HUNSPELL_WARNING(stderr, "error: word len %ld is over max limit\n", word->size()); |
222 | 324 | delete desc_copy; |
223 | 324 | delete word_copy; |
224 | 324 | release_flags(aff, own_aff); |
225 | 324 | return 1; |
226 | 324 | } |
227 | | |
228 | 2.78M | bool upcasehomonym = false; |
229 | 2.78M | int descl = desc ? (!aliasm.empty() ? sizeof(char*) : desc->size() + 1) : 0; |
230 | | // variable-length hash record with word and optional fields |
231 | 2.78M | auto hp = |
232 | 2.78M | (struct hentry*)arena_alloc(sizeof(struct hentry) + word->size() + descl, |
233 | 2.78M | alignof(struct hentry)); |
234 | 2.78M | if (!hp) { |
235 | 0 | delete desc_copy; |
236 | 0 | delete word_copy; |
237 | 0 | release_flags(aff, own_aff); |
238 | 0 | return 1; |
239 | 0 | } |
240 | | |
241 | 2.78M | char* hpw = hp->word; |
242 | 2.78M | memcpy(hpw, word->data(), word->size()); |
243 | 2.78M | hpw[word->size()] = 0; |
244 | | |
245 | 2.78M | int i = hash(hpw, word->size()); |
246 | | |
247 | 2.78M | hp->blen = (unsigned short)word->size(); |
248 | 2.78M | hp->clen = (unsigned short)wcl; |
249 | 2.78M | hp->alen = (short)al; |
250 | 2.78M | hp->astr = aff; |
251 | 2.78M | hp->next = nullptr; |
252 | 2.78M | hp->next_homonym = nullptr; |
253 | 2.78M | hp->var = (captype == INITCAP) ? H_OPT_INITCAP : 0; |
254 | 2.78M | if (own_aff) |
255 | 42.8k | hp->var |= H_OPT_OWNFLAGS; |
256 | | |
257 | | // store the description string or its pointer |
258 | 2.78M | if (desc) { |
259 | 760k | hp->var |= H_OPT; |
260 | 760k | if (!aliasm.empty()) { |
261 | 56.9k | hp->var |= H_OPT_ALIASM; |
262 | 56.9k | store_pointer(hpw + word->size() + 1, get_aliasm(atoi(desc->c_str()))); |
263 | 703k | } else { |
264 | 703k | strcpy(hpw + word->size() + 1, desc->c_str()); |
265 | 703k | } |
266 | 760k | if (HENTRY_FIND(hp, MORPH_PHON)) { |
267 | 74.1k | hp->var |= H_OPT_PHON; |
268 | | // store ph: fields (pronounciation, misspellings, old orthography etc.) |
269 | | // of a morphological description in reptable to use in REP replacements. |
270 | 74.1k | size_t predicted = tableptr.size() / MORPH_PHON_RATIO; |
271 | 74.1k | if (reptable.capacity() < predicted) |
272 | 7.14k | reptable.reserve(predicted); |
273 | 74.1k | std::string fields = HENTRY_DATA(hp); |
274 | 74.1k | std::string::const_iterator iter = fields.begin(), start_piece = mystrsep(fields, iter); |
275 | 204k | while (start_piece != fields.end()) { |
276 | 130k | if (std::string(start_piece, iter).find(MORPH_PHON) == 0) { |
277 | 72.9k | std::string ph = std::string(start_piece, iter).substr(sizeof MORPH_PHON - 1); |
278 | 72.9k | if (!ph.empty()) { |
279 | 66.2k | std::vector<w_char> w; |
280 | 66.2k | size_t strippatt; |
281 | 66.2k | std::string wordpart; |
282 | | // dictionary based REP replacement, separated by "->" |
283 | | // for example "pretty ph:prity ph:priti->pretti" to handle |
284 | | // both prity -> pretty and pritier -> prettiest suggestions. |
285 | 66.2k | if (((strippatt = ph.find("->")) != std::string::npos) && |
286 | 5.84k | (strippatt > 0) && (strippatt < ph.size() - 2)) { |
287 | 4.93k | wordpart = ph.substr(strippatt + 2); |
288 | 4.93k | ph.erase(ph.begin() + strippatt, ph.end()); |
289 | 4.93k | } else |
290 | 61.3k | wordpart = in_word; |
291 | | // when the ph: field ends with the character *, |
292 | | // strip last character of the pattern and the replacement |
293 | | // to match in REP suggestions also at character changes, |
294 | | // for example, "pretty ph:prity*" results "prit->prett" |
295 | | // REP replacement instead of "prity->pretty", to get |
296 | | // prity->pretty and pritiest->prettiest suggestions. |
297 | 66.2k | if (ph.at(ph.size()-1) == '*') { |
298 | 9.77k | strippatt = 1; |
299 | 9.77k | size_t stripword = 0; |
300 | 9.77k | if (utf8) { |
301 | 11.2k | while ((strippatt < ph.size()) && |
302 | 10.1k | is_utf8_cont(ph.at(ph.size()-strippatt-1))) |
303 | 5.71k | ++strippatt; |
304 | 8.28k | while ((stripword < wordpart.size()) && |
305 | 7.14k | is_utf8_cont(wordpart.at(wordpart.size()-stripword-1))) |
306 | 2.79k | ++stripword; |
307 | 5.49k | } |
308 | 9.77k | ++strippatt; |
309 | 9.77k | ++stripword; |
310 | 9.77k | if ((ph.size() > strippatt) && (wordpart.size() > stripword)) { |
311 | 5.89k | ph.erase(ph.size()-strippatt, strippatt); |
312 | 5.89k | wordpart.erase(wordpart.size()-stripword, stripword); |
313 | 5.89k | } |
314 | 9.77k | } |
315 | | // capitalize lowercase pattern for capitalized words to support |
316 | | // good suggestions also for capitalized misspellings, eg. |
317 | | // Wednesday ph:wendsay |
318 | | // results wendsay -> Wednesday and Wendsay -> Wednesday, too. |
319 | 66.2k | if (captype == INITCAP) { |
320 | 22.8k | std::string ph_capitalized; |
321 | 22.8k | if (utf8) { |
322 | 7.86k | u8_u16(w, ph); |
323 | 7.86k | if (get_captype_utf8(w, langnum) == NOCAP) { |
324 | 6.45k | mkinitcap_utf(w, langnum); |
325 | 6.45k | u16_u8(ph_capitalized, w); |
326 | 6.45k | } |
327 | 15.0k | } else if (get_captype(ph, csconv) == NOCAP) |
328 | 9.57k | mkinitcap(ph_capitalized, csconv); |
329 | | |
330 | 22.8k | if (!ph_capitalized.empty()) { |
331 | | // add also lowercase word in the case of German or |
332 | | // Hungarian to support lowercase suggestions lowercased by |
333 | | // compound word generation or derivational suffixes |
334 | | // (for example by adjectival suffix "-i" of geographical |
335 | | // names in Hungarian: |
336 | | // Massachusetts ph:messzecsuzec |
337 | | // messzecsuzeci -> massachusettsi (adjective) |
338 | | // For lowercasing by conditional PFX rules, see |
339 | | // tests/germancompounding test example or the |
340 | | // Hungarian dictionary.) |
341 | 6.45k | if (langnum == LANG_de || langnum == LANG_hu) { |
342 | 3.37k | std::string wordpart_lower(wordpart); |
343 | 3.37k | if (utf8) { |
344 | 3.37k | u8_u16(w, wordpart_lower); |
345 | 3.37k | mkallsmall_utf(w, langnum); |
346 | 3.37k | u16_u8(wordpart_lower, w); |
347 | 3.37k | } else { |
348 | 0 | mkallsmall(wordpart_lower, csconv); |
349 | 0 | } |
350 | 3.37k | reptable.emplace_back(); |
351 | 3.37k | reptable.back().pattern.assign(ph); |
352 | 3.37k | reptable.back().outstrings[0].assign(wordpart_lower); |
353 | 3.37k | } |
354 | 6.45k | reptable.emplace_back(); |
355 | 6.45k | reptable.back().pattern.assign(ph_capitalized); |
356 | 6.45k | reptable.back().outstrings[0].assign(wordpart); |
357 | 6.45k | } |
358 | 22.8k | } |
359 | 66.2k | reptable.emplace_back(); |
360 | 66.2k | reptable.back().pattern.assign(ph); |
361 | 66.2k | reptable.back().outstrings[0].assign(wordpart); |
362 | 66.2k | } |
363 | 72.9k | } |
364 | 130k | start_piece = mystrsep(fields, iter); |
365 | 130k | } |
366 | 74.1k | } |
367 | 760k | } |
368 | | |
369 | 2.78M | struct hentry* dp = tableptr[i]; |
370 | 2.78M | if (!dp) { |
371 | 960k | tableptr[i] = hp; |
372 | 960k | delete desc_copy; |
373 | 960k | delete word_copy; |
374 | 960k | return 0; |
375 | 960k | } |
376 | 16.0M | while (dp->next != nullptr) { |
377 | 14.3M | if ((!dp->next_homonym) && (strcmp(hp->word, dp->word) == 0)) { |
378 | | // remove hidden onlyupcase homonym |
379 | 116k | if (!onlyupcase) { |
380 | 101k | if ((dp->astr) && TESTAFF(dp->astr, ONLYUPCASEFLAG, dp->alen)) { |
381 | 927 | release_flags(dp->astr, dp->var & H_OPT_OWNFLAGS); |
382 | 927 | dp->astr = hp->astr; |
383 | 927 | dp->alen = hp->alen; |
384 | 927 | dp->var &= ~H_OPT_OWNFLAGS; |
385 | 927 | dp->var |= (hp->var & H_OPT_OWNFLAGS); |
386 | 927 | arena_free(hp); |
387 | 927 | delete desc_copy; |
388 | 927 | delete word_copy; |
389 | 927 | return 0; |
390 | 100k | } else if (!dp->astr && dp->alen == 0 && |
391 | 92.1k | !hp->astr && hp->alen == 0) { |
392 | | // word already exists with no flags, skip duplicate |
393 | 88.8k | release_flags(hp->astr, hp->var & H_OPT_OWNFLAGS); |
394 | 88.8k | arena_free(hp); |
395 | 88.8k | delete desc_copy; |
396 | 88.8k | delete word_copy; |
397 | 88.8k | return 0; |
398 | 88.8k | } else { |
399 | 11.3k | dp->next_homonym = hp; |
400 | 11.3k | } |
401 | 101k | } else { |
402 | 15.6k | upcasehomonym = true; |
403 | 15.6k | } |
404 | 116k | } |
405 | 14.2M | dp = dp->next; |
406 | 14.2M | } |
407 | 1.73M | if (strcmp(hp->word, dp->word) == 0) { |
408 | | // remove hidden onlyupcase homonym |
409 | 1.62M | if (!onlyupcase) { |
410 | 1.47M | if ((dp->astr) && TESTAFF(dp->astr, ONLYUPCASEFLAG, dp->alen)) { |
411 | 4.13k | release_flags(dp->astr, dp->var & H_OPT_OWNFLAGS); |
412 | 4.13k | dp->astr = hp->astr; |
413 | 4.13k | dp->alen = hp->alen; |
414 | 4.13k | dp->var &= ~H_OPT_OWNFLAGS; |
415 | 4.13k | dp->var |= (hp->var & H_OPT_OWNFLAGS); |
416 | 4.13k | arena_free(hp); |
417 | 4.13k | delete desc_copy; |
418 | 4.13k | delete word_copy; |
419 | 4.13k | return 0; |
420 | 1.47M | } else if (!dp->astr && dp->alen == 0 && |
421 | 1.24M | !hp->astr && hp->alen == 0) { |
422 | | // word already exists with no flags, skip duplicate |
423 | 1.18M | release_flags(hp->astr, hp->var & H_OPT_OWNFLAGS); |
424 | 1.18M | arena_free(hp); |
425 | 1.18M | delete desc_copy; |
426 | 1.18M | delete word_copy; |
427 | 1.18M | return 0; |
428 | 1.18M | } else { |
429 | 290k | dp->next_homonym = hp; |
430 | 290k | } |
431 | 1.47M | } else { |
432 | 145k | upcasehomonym = true; |
433 | 145k | } |
434 | 1.62M | } |
435 | 544k | if (!upcasehomonym) { |
436 | 383k | dp->next = hp; |
437 | 383k | } else { |
438 | | // remove hidden onlyupcase homonym |
439 | 161k | release_flags(hp->astr, hp->var & H_OPT_OWNFLAGS); |
440 | 161k | arena_free(hp); |
441 | 161k | } |
442 | | |
443 | 544k | delete desc_copy; |
444 | 544k | delete word_copy; |
445 | 544k | return 0; |
446 | 1.73M | } |
447 | | |
448 | | int HashMgr::add_hidden_capitalized_word(const std::string& word, |
449 | | int wcl, |
450 | | unsigned short* flags, |
451 | | int flagslen, |
452 | | const std::string* dp, |
453 | 2.34M | int captype) { |
454 | 2.34M | if (flags == nullptr) |
455 | 2.00M | flagslen = 0; |
456 | | |
457 | | // add inner capitalized forms to handle the following allcap forms: |
458 | | // Mixed caps: OpenOffice.org -> OPENOFFICE.ORG |
459 | | // Allcaps with suffixes: CIA's -> CIA'S |
460 | 2.34M | if (((captype == HUHCAP) || (captype == HUHINITCAP) || |
461 | 1.96M | ((captype == ALLCAP) && (flagslen != 0))) && |
462 | 434k | !((flagslen != 0) && TESTAFF(flags, forbiddenword, flagslen))) { |
463 | 430k | auto flags2 = (unsigned short*)arena_alloc((flagslen + 1) * sizeof(unsigned short), |
464 | 430k | alignof(unsigned short)); |
465 | 430k | flags2[flagslen] = ONLYUPCASEFLAG; |
466 | 430k | if (flagslen) { |
467 | 100k | memcpy(flags2, flags, flagslen * sizeof(unsigned short)); |
468 | 100k | std::sort(flags2, flags2 + flagslen + 1); |
469 | 100k | } |
470 | 430k | if (utf8) { |
471 | 42.0k | std::string st; |
472 | 42.0k | std::vector<w_char> w; |
473 | 42.0k | u8_u16(w, word); |
474 | 42.0k | mkallsmall_utf(w, langnum); |
475 | 42.0k | mkinitcap_utf(w, langnum); |
476 | 42.0k | u16_u8(st, w); |
477 | 42.0k | return add_word(st, wcl, flags2, flagslen + 1, dp, true, INITCAP, false); |
478 | 388k | } else { |
479 | 388k | std::string new_word(word); |
480 | 388k | mkallsmall(new_word, csconv); |
481 | 388k | mkinitcap(new_word, csconv); |
482 | 388k | int ret = add_word(new_word, wcl, flags2, flagslen + 1, dp, true, INITCAP, false); |
483 | 388k | return ret; |
484 | 388k | } |
485 | 430k | } |
486 | 1.91M | return 0; |
487 | 2.34M | } |
488 | | |
489 | | // detect captype and modify word length for UTF-8 encoding |
490 | 2.35M | int HashMgr::get_clen_and_captype(const std::string& word, int* captype, std::vector<w_char> &workbuf) { |
491 | 2.35M | int len; |
492 | 2.35M | if (utf8) { |
493 | 307k | len = u8_u16(workbuf, word); |
494 | 307k | *captype = get_captype_utf8(workbuf, langnum); |
495 | 2.04M | } else { |
496 | 2.04M | len = word.size(); |
497 | 2.04M | *captype = get_captype(word, csconv); |
498 | 2.04M | } |
499 | 2.35M | return len; |
500 | 2.35M | } |
501 | | |
502 | 43.1k | int HashMgr::get_clen_and_captype(const std::string& word, int* captype) { |
503 | 43.1k | std::vector<w_char> workbuf; |
504 | 43.1k | return get_clen_and_captype(word, captype, workbuf); |
505 | 43.1k | } |
506 | | |
507 | | // remove word (personal dictionary function for standalone applications) |
508 | 31.2k | int HashMgr::remove(const std::string& word) { |
509 | 31.2k | struct hentry* dp = lookup(word.c_str(), word.size()); |
510 | 51.7k | while (dp) { |
511 | 20.5k | if (dp->alen == 0 || !TESTAFF(dp->astr, forbiddenword, dp->alen)) { |
512 | 13.1k | auto flags = new unsigned short[dp->alen + 1]; |
513 | 319k | for (int i = 0; i < dp->alen; i++) |
514 | 306k | flags[i] = dp->astr[i]; |
515 | 13.1k | flags[dp->alen] = forbiddenword; |
516 | 13.1k | release_flags(dp->astr, dp->var & H_OPT_OWNFLAGS); |
517 | 13.1k | dp->astr = flags; |
518 | 13.1k | dp->alen++; |
519 | 13.1k | dp->var |= H_OPT_OWNFLAGS; |
520 | 13.1k | std::sort(flags, flags + dp->alen); |
521 | 13.1k | } |
522 | 20.5k | dp = dp->next_homonym; |
523 | 20.5k | } |
524 | 31.2k | return 0; |
525 | 31.2k | } |
526 | | |
527 | | /* remove forbidden flag to add a personal word to the hash */ |
528 | 75.4k | void HashMgr::remove_forbidden_flag(const std::string& word) { |
529 | 75.4k | struct hentry* dp = lookup(word.c_str(), word.size()); |
530 | 75.4k | if (!dp) |
531 | 44.3k | return; |
532 | 92.3k | while (dp) { |
533 | 61.2k | if (dp->astr && TESTAFF(dp->astr, forbiddenword, dp->alen)) { |
534 | 8.66k | if (dp->alen == 1) { |
535 | 5.84k | release_flags(dp->astr, dp->var & H_OPT_OWNFLAGS); |
536 | 5.84k | dp->astr = nullptr; |
537 | 5.84k | dp->alen = 0; |
538 | 5.84k | dp->var &= ~H_OPT_OWNFLAGS; |
539 | 5.84k | } else { |
540 | 2.82k | auto newflags = new unsigned short[dp->alen - 1]; |
541 | 2.82k | int j = 0; |
542 | 204k | for (int i = 0; i < dp->alen; i++) { |
543 | 202k | if (dp->astr[i] != forbiddenword) |
544 | 198k | newflags[j++] = dp->astr[i]; |
545 | 202k | } |
546 | 2.82k | release_flags(dp->astr, dp->var & H_OPT_OWNFLAGS); |
547 | 2.82k | dp->astr = newflags; |
548 | 2.82k | dp->alen = (short)j; |
549 | 2.82k | dp->var |= H_OPT_OWNFLAGS; |
550 | 2.82k | } |
551 | 8.66k | } |
552 | 61.2k | dp = dp->next_homonym; |
553 | 61.2k | } |
554 | 31.1k | } |
555 | | |
556 | | // add a custom dic. word to the hash table (public) |
557 | 38.4k | int HashMgr::add(const std::string& word) { |
558 | 38.4k | remove_forbidden_flag(word); |
559 | 38.4k | int captype, al = 0; |
560 | 38.4k | unsigned short* flags = nullptr; |
561 | 38.4k | int wcl = get_clen_and_captype(word, &captype); |
562 | 38.4k | if (add_word(word, wcl, flags, al, nullptr, false, captype, true)) |
563 | 202 | return 1; |
564 | 38.2k | return add_hidden_capitalized_word(word, wcl, flags, al, nullptr, captype); |
565 | 38.4k | } |
566 | | |
567 | 0 | int HashMgr::add_with_flags(const std::string& word, const std::string& flags, const std::string& desc) { |
568 | 0 | remove_forbidden_flag(word); |
569 | 0 | int captype; |
570 | 0 | unsigned short *df; |
571 | 0 | int al = decode_flags(&df, flags, nullptr); |
572 | 0 | int wcl = get_clen_and_captype(word, &captype); |
573 | 0 | if (add_word(word, wcl, df, al, &desc, false, captype, true)) |
574 | 0 | return 1; |
575 | 0 | return add_hidden_capitalized_word(word, wcl, df, al, &desc, captype); |
576 | 0 | } |
577 | | |
578 | 36.9k | int HashMgr::add_with_affix(const std::string& word, const std::string& example) { |
579 | | // detect captype and modify word length for UTF-8 encoding |
580 | 36.9k | struct hentry* dp = lookup(example.c_str(), example.size()); |
581 | 36.9k | remove_forbidden_flag(word); |
582 | 36.9k | if (dp && dp->astr) { |
583 | 4.60k | int captype; |
584 | 4.60k | int wcl = get_clen_and_captype(word, &captype); |
585 | 4.60k | auto flags = new unsigned short[dp->alen]; |
586 | 4.60k | memcpy(flags, dp->astr, dp->alen * sizeof(unsigned short)); |
587 | 4.60k | if (add_word(word, wcl, flags, dp->alen, nullptr, false, captype, true)) |
588 | 82 | return 1; |
589 | 4.52k | return add_hidden_capitalized_word(word, wcl, flags, dp->alen, nullptr, captype); |
590 | 4.60k | } |
591 | 32.3k | return 1; |
592 | 36.9k | } |
593 | | |
594 | | // walk the hash table entry by entry - null at end |
595 | | // initialize: col=-1; hp = NULL; hp = walk_hashtable(&col, hp); |
596 | 3.27M | struct hentry* HashMgr::walk_hashtable(int& col, struct hentry* hp) const { |
597 | 3.27M | if (hp && hp->next != nullptr) |
598 | 380k | return hp->next; |
599 | 115M | for (col++; col < (int)tableptr.size(); ++col) { |
600 | 115M | if (tableptr[col]) |
601 | 2.76M | return tableptr[col]; |
602 | 115M | } |
603 | | // null at end and reset to start |
604 | 132k | col = -1; |
605 | 132k | return nullptr; |
606 | 2.89M | } |
607 | | |
608 | | // load a munched word list and build a hash table on the fly |
609 | 55.9k | int HashMgr::load_tables(const char* tpath, const char* key) { |
610 | | // open dictionary file |
611 | 55.9k | FileMgr* dict = new FileMgr(tpath, key); |
612 | | |
613 | | // first read the first line of file to get hash table size |
614 | 55.9k | std::string ts; |
615 | 55.9k | if (!dict->getline(ts)) { |
616 | 533 | fprintf(stderr, "error: empty dic file %s\n", tpath); |
617 | 533 | delete dict; |
618 | 533 | return 2; |
619 | 533 | } |
620 | 55.4k | mychomp(ts); |
621 | | |
622 | | /* remove byte order mark */ |
623 | 55.4k | if (ts.compare(0, 3, "\xEF\xBB\xBF", 3) == 0) { |
624 | 4 | ts.erase(0, 3); |
625 | 4 | } |
626 | | |
627 | 55.4k | int tablesize = atoi(ts.c_str()); |
628 | | |
629 | 55.4k | const int nExtra = 5 + USERWORD; |
630 | | #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) |
631 | | // covers full-form uncompressed dictionaries (~7M for uk_UA expanded) |
632 | | const int max_allowed = 10000000; |
633 | | #else |
634 | 55.4k | const int max_allowed = (10000 - 1 - nExtra) / int(sizeof(struct hentry*)); |
635 | 55.4k | #endif |
636 | | |
637 | 55.4k | if (tablesize <= 0 || tablesize >= max_allowed) { |
638 | 30.9k | fprintf(stderr, |
639 | 30.9k | "error: %s: line 1: missing or bad word count in the dic file\n", |
640 | 30.9k | tpath); |
641 | 30.9k | delete dict; |
642 | 30.9k | return 4; |
643 | 30.9k | } |
644 | 24.4k | tablesize += nExtra; |
645 | 24.4k | if ((tablesize & 1) == 0) |
646 | 14.2k | tablesize++; |
647 | | |
648 | | // allocate the hash table |
649 | 24.4k | tableptr.resize(tablesize, nullptr); |
650 | | |
651 | | // loop through all words on much list and add to hash |
652 | | // table and create word and affix strings |
653 | | |
654 | 24.4k | std::vector<w_char> workbuf; |
655 | | |
656 | 24.4k | int nLineCount(0); |
657 | 2.33M | while (dict->getline(ts)) { |
658 | 2.30M | ++nLineCount; |
659 | 2.30M | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
660 | | // limit words loaded to avoid O(n^2) hash chain walk timeout |
661 | 2.30M | if (nLineCount >= tablesize) |
662 | 123 | break; |
663 | 2.30M | #endif |
664 | 2.30M | mychomp(ts); |
665 | | // split each line into word and morphological description |
666 | 2.30M | size_t dp_pos = 0; |
667 | 2.75M | while ((dp_pos = ts.find(':', dp_pos)) != std::string::npos) { |
668 | 507k | if ((dp_pos > 3) && (ts[dp_pos - 3] == ' ' || ts[dp_pos - 3] == '\t')) { |
669 | 91.6k | for (dp_pos -= 3; dp_pos > 0 && (ts[dp_pos-1] == ' ' || ts[dp_pos-1] == '\t'); --dp_pos) |
670 | 27.6k | ; |
671 | 63.9k | if (dp_pos == 0) { // missing word |
672 | 949 | dp_pos = std::string::npos; |
673 | 62.9k | } else { |
674 | 62.9k | ++dp_pos; |
675 | 62.9k | } |
676 | 63.9k | break; |
677 | 63.9k | } |
678 | 443k | ++dp_pos; |
679 | 443k | } |
680 | | |
681 | | // tabulator is the old morphological field separator |
682 | 2.30M | size_t dp2_pos = ts.find('\t'); |
683 | 2.30M | if (dp2_pos != std::string::npos && (dp_pos == std::string::npos || dp2_pos < dp_pos)) { |
684 | 614k | dp_pos = dp2_pos + 1; |
685 | 614k | } |
686 | | |
687 | 2.30M | std::string dp; |
688 | 2.30M | if (dp_pos != std::string::npos) { |
689 | 632k | dp.assign(ts.substr(dp_pos)); |
690 | 632k | ts.resize(dp_pos - 1); |
691 | 632k | } |
692 | | |
693 | | // split each line into word and affix char strings |
694 | | // "\/" signs slash in words (not affix separator) |
695 | | // "/" at beginning of the line is word character (not affix separator) |
696 | 2.30M | size_t ap_pos = ts.find('/'); |
697 | 2.34M | while (ap_pos != std::string::npos) { |
698 | 400k | if (ap_pos == 0) { |
699 | 33.8k | ++ap_pos; |
700 | 33.8k | continue; |
701 | 366k | } else if (ts[ap_pos - 1] != '\\') |
702 | 363k | break; |
703 | | // replace "\/" with "/" |
704 | 3.09k | ts.erase(ap_pos - 1, 1); |
705 | 3.09k | ap_pos = ts.find('/', ap_pos); |
706 | 3.09k | } |
707 | | |
708 | 2.30M | unsigned short* flags; |
709 | 2.30M | int al; |
710 | 2.30M | if (ap_pos != std::string::npos && ap_pos != ts.size()) { |
711 | 359k | std::string ap(ts.substr(ap_pos + 1)); |
712 | 359k | ts.resize(ap_pos); |
713 | 359k | if (!aliasf.empty()) { |
714 | 10.6k | int index = atoi(ap.c_str()); |
715 | 10.6k | al = get_aliasf(index, &flags, dict); |
716 | 10.6k | if (!al) { |
717 | 6.51k | HUNSPELL_WARNING(stderr, "error: line %d: bad flag vector alias\n", |
718 | 6.51k | dict->getlinenum()); |
719 | 6.51k | } |
720 | 348k | } else { |
721 | 348k | al = decode_flags(&flags, ap, dict, /* arena = */ true); |
722 | 348k | if (al == -1) { |
723 | 0 | HUNSPELL_WARNING(stderr, "Can't allocate memory.\n"); |
724 | 0 | delete dict; |
725 | 0 | return 6; |
726 | 0 | } |
727 | 348k | std::sort(flags, flags + al); |
728 | 348k | } |
729 | 1.94M | } else { |
730 | 1.94M | al = 0; |
731 | 1.94M | flags = nullptr; |
732 | 1.94M | } |
733 | | |
734 | 2.30M | int captype; |
735 | 2.30M | int wcl = get_clen_and_captype(ts, &captype, workbuf); |
736 | 2.30M | const std::string* dp_str = dp.empty() ? nullptr : &dp; |
737 | | // add the word and its index plus its capitalized form optionally |
738 | | // flags are arena-allocated, so own_aff must be false |
739 | 2.30M | bool own = false; |
740 | 2.30M | if (add_word(ts, wcl, flags, al, dp_str, false, captype, own) || |
741 | 2.30M | add_hidden_capitalized_word(ts, wcl, flags, al, dp_str, captype)) { |
742 | 35 | delete dict; |
743 | 35 | return 5; |
744 | 35 | } |
745 | 2.30M | } |
746 | | |
747 | 24.3k | int ret(0); |
748 | | |
749 | | // reject ludicrous tablesizes |
750 | 24.3k | if (tablesize > 8192 + nExtra && tablesize > nLineCount * 10 + nExtra) { |
751 | 0 | HUNSPELL_WARNING(stderr, ".dic initial approximate word count line value of %d is too large for %d lines\n", tablesize, nLineCount); |
752 | 0 | ret = 3; |
753 | 0 | } |
754 | | |
755 | 24.3k | delete dict; |
756 | 24.3k | return ret; |
757 | 24.4k | } |
758 | | |
759 | | // the hash function is a simple load and rotate |
760 | | // algorithm borrowed |
761 | 5.25G | int HashMgr::hash(const char* word, size_t len) const { |
762 | 5.25G | unsigned long hv = 0; |
763 | 5.25G | size_t i = 0; |
764 | 25.7G | while (i < 4 && i < len) |
765 | 20.4G | hv = (hv << 8) | word[i++]; |
766 | 301G | while (i < len) { |
767 | 295G | ROTATE(hv, ROTATE_LEN); |
768 | 295G | hv ^= word[i++]; |
769 | 295G | } |
770 | 5.25G | return (unsigned long)hv % tableptr.size(); |
771 | 5.25G | } |
772 | | |
773 | 54.8k | int HashMgr::decode_flags(unsigned short** result, const std::string& flags, FileMgr* af) const { |
774 | 54.8k | return decode_flags(result, flags, af, /* arena = */ false); |
775 | 54.8k | } |
776 | | |
777 | 415k | int HashMgr::decode_flags(unsigned short** result, const std::string& flags, FileMgr* af, bool use_arena) const { |
778 | 415k | auto alloc = [&](int n) -> unsigned short* { |
779 | 393k | return use_arena ? (unsigned short*)this->arena_alloc(n * sizeof(unsigned short), |
780 | 344k | alignof(unsigned short)) |
781 | 393k | : new unsigned short[n]; |
782 | 393k | }; |
783 | 415k | int len; |
784 | 415k | if (flags.empty()) { |
785 | 22.0k | *result = nullptr; |
786 | 22.0k | return 0; |
787 | 22.0k | } |
788 | 393k | switch (flag_mode) { |
789 | 35.3k | case FLAG_LONG: { // two-character flags (1x2yZz -> 1x 2y Zz) |
790 | 35.3k | len = flags.size(); |
791 | 35.3k | if ((len & 1) == 1 && af != nullptr) |
792 | 21.2k | HUNSPELL_WARNING(stderr, "error: line %d: bad flagvector\n", |
793 | 21.2k | af->getlinenum()); |
794 | 35.3k | len >>= 1; |
795 | 35.3k | *result = alloc(len); |
796 | 590k | for (int i = 0; i < len; i++) { |
797 | 555k | unsigned short flag = ((unsigned short)((unsigned char)flags[i << 1]) << 8) | |
798 | 555k | ((unsigned short)((unsigned char)flags[(i << 1) | 1])); |
799 | | |
800 | 555k | (*result)[i] = flag; |
801 | 555k | } |
802 | 35.3k | break; |
803 | 0 | } |
804 | 10.3k | case FLAG_NUM: { // decimal numbers separated by comma (4521,23,233 -> 4521 |
805 | | // 23 233) |
806 | 1.83M | len = int(1 + std::count_if(flags.begin(), flags.end(), [](char c) { return c == ','; })); |
807 | 10.3k | *result = alloc(len); |
808 | 10.3k | unsigned short* dest = *result; |
809 | 10.3k | const char* src = flags.c_str(); |
810 | 1.84M | for (size_t p = 0; p < flags.size(); ++p) { |
811 | 1.83M | if (flags[p] == ',') { |
812 | 16.9k | int i = atoi(src); |
813 | 16.9k | if ((i > std::numeric_limits<unsigned short>::max() || i < 0) && af != nullptr) { |
814 | 4.20k | HUNSPELL_WARNING( |
815 | 4.20k | stderr, "error: line %d: flag id %d is out of range\n", |
816 | 4.20k | af->getlinenum(), i); |
817 | 4.20k | i = 0; |
818 | 4.20k | } |
819 | 16.9k | *dest = (unsigned short)i; |
820 | 16.9k | if (*dest == 0 && af != nullptr) |
821 | 10.7k | HUNSPELL_WARNING(stderr, "error: line %d: 0 is wrong flag id\n", |
822 | 10.7k | af->getlinenum()); |
823 | 16.9k | src = flags.c_str() + p + 1; |
824 | 16.9k | dest++; |
825 | 16.9k | } |
826 | 1.83M | } |
827 | 10.3k | int i = atoi(src); |
828 | 10.3k | if ((i > std::numeric_limits<unsigned short>::max() || i < 0) && af) { |
829 | 1.75k | HUNSPELL_WARNING(stderr, |
830 | 1.75k | "error: line %d: flag id %d is out of range\n", |
831 | 1.75k | af->getlinenum(), i); |
832 | 1.75k | i = 0; |
833 | 1.75k | } |
834 | 10.3k | *dest = (unsigned short)i; |
835 | 10.3k | if (*dest == 0 && af) |
836 | 6.36k | HUNSPELL_WARNING(stderr, "error: line %d: 0 is wrong flag id\n", |
837 | 6.36k | af->getlinenum()); |
838 | 10.3k | break; |
839 | 0 | } |
840 | 37.0k | case FLAG_UNI: { // UTF-8 characters |
841 | 37.0k | std::vector<w_char> w; |
842 | 37.0k | u8_u16(w, flags); |
843 | 37.0k | len = w.size(); |
844 | 37.0k | *result = alloc(len); |
845 | 37.0k | #if defined(_WIN32) || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)) || defined(__LITTLE_ENDIAN__) |
846 | 37.0k | memcpy(*result, w.data(), len * sizeof(unsigned short)); |
847 | | #else |
848 | | unsigned short* dest = *result; |
849 | | for (const w_char wc : w) { |
850 | | *dest = (unsigned short)wc; |
851 | | dest++; |
852 | | } |
853 | | #endif |
854 | 37.0k | break; |
855 | 0 | } |
856 | 311k | default: { // Ispell's one-character flags (erfg -> e r f g) |
857 | 311k | len = flags.size(); |
858 | 311k | *result = alloc(len); |
859 | 311k | unsigned short* dest = *result; |
860 | 12.3M | for (const char flag : flags) { |
861 | 12.3M | *dest = (unsigned char)flag; |
862 | 12.3M | dest++; |
863 | 12.3M | } |
864 | 311k | } |
865 | 393k | } |
866 | 393k | return len; |
867 | 393k | } |
868 | | |
869 | 1.37M | bool HashMgr::decode_flags(std::vector<unsigned short>& result, const std::string& flags, FileMgr* af) const { |
870 | 1.37M | if (flags.empty()) { |
871 | 1.01k | return false; |
872 | 1.01k | } |
873 | 1.37M | switch (flag_mode) { |
874 | 75.0k | case FLAG_LONG: { // two-character flags (1x2yZz -> 1x 2y Zz) |
875 | 75.0k | size_t len = flags.size(); |
876 | 75.0k | if ((len & 1) == 1) |
877 | 73.4k | HUNSPELL_WARNING(stderr, "error: line %d: bad flagvector\n", |
878 | 73.4k | af->getlinenum()); |
879 | 75.0k | len >>= 1; |
880 | 75.0k | size_t origsize = result.size(); |
881 | 75.0k | result.resize(origsize + len); |
882 | 246k | for (size_t i = 0; i < len; ++i) { |
883 | 171k | result[origsize + i] = ((unsigned short)((unsigned char)flags[i << 1]) << 8) | |
884 | 171k | ((unsigned short)((unsigned char)flags[(i << 1) | 1])); |
885 | 171k | } |
886 | 75.0k | break; |
887 | 0 | } |
888 | 64.4k | case FLAG_NUM: { // decimal numbers separated by comma (4521,23,233 -> 4521 |
889 | | // 23 233) |
890 | 64.4k | const char* src = flags.c_str(); |
891 | 260k | for (const char* p = src; *p; p++) { |
892 | 195k | if (*p == ',') { |
893 | 11.0k | int i = atoi(src); |
894 | 11.0k | if (i > std::numeric_limits<unsigned short>::max() || i < 0) { |
895 | 2.54k | HUNSPELL_WARNING( |
896 | 2.54k | stderr, "error: line %d: flag id %d is out of range\n", |
897 | 2.54k | af->getlinenum(), i); |
898 | 2.54k | i = 0; |
899 | 2.54k | } |
900 | 11.0k | result.push_back((unsigned short)i); |
901 | 11.0k | if (result.back() == 0) |
902 | 8.17k | HUNSPELL_WARNING(stderr, "error: line %d: 0 is wrong flag id\n", |
903 | 8.17k | af->getlinenum()); |
904 | 11.0k | src = p + 1; |
905 | 11.0k | } |
906 | 195k | } |
907 | 64.4k | int i = atoi(src); |
908 | 64.4k | if (i > std::numeric_limits<unsigned short>::max() || i < 0) { |
909 | 1.80k | HUNSPELL_WARNING(stderr, |
910 | 1.80k | "error: line %d: flag id %d is out of range\n", |
911 | 1.80k | af->getlinenum(), i); |
912 | 1.80k | i = 0; |
913 | 1.80k | } |
914 | 64.4k | result.push_back((unsigned short)i); |
915 | 64.4k | if (result.back() == 0) |
916 | 52.3k | HUNSPELL_WARNING(stderr, "error: line %d: 0 is wrong flag id\n", |
917 | 52.3k | af->getlinenum()); |
918 | 64.4k | break; |
919 | 0 | } |
920 | 714k | case FLAG_UNI: { // UTF-8 characters |
921 | 714k | std::vector<w_char> w; |
922 | 714k | u8_u16(w, flags); |
923 | 714k | size_t len = w.size(), origsize = result.size(); |
924 | 714k | result.resize(origsize + len); |
925 | 714k | #if defined(_WIN32) || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)) || defined(__LITTLE_ENDIAN__) |
926 | 714k | memcpy(result.data() + origsize, w.data(), len * sizeof(short)); |
927 | | #else |
928 | | for (size_t i = 0; i < len; ++i) |
929 | | result[origsize + i] = (unsigned short)w[i]; |
930 | | #endif |
931 | 714k | break; |
932 | 0 | } |
933 | 518k | default: { // Ispell's one-character flags (erfg -> e r f g) |
934 | 518k | result.reserve(flags.size()); |
935 | 3.98M | for (const char flag : flags) { |
936 | 3.98M | result.push_back((unsigned char)flag); |
937 | 3.98M | } |
938 | 518k | } |
939 | 1.37M | } |
940 | 1.37M | return true; |
941 | 1.37M | } |
942 | | |
943 | 404k | unsigned short HashMgr::decode_flag(const std::string& f) const { |
944 | 404k | unsigned short s = 0; |
945 | 404k | int i; |
946 | 404k | switch (flag_mode) { |
947 | 25.3k | case FLAG_LONG: |
948 | 25.3k | if (f.size() >= 2) |
949 | 12.4k | s = ((unsigned short)((unsigned char)f[0]) << 8) | ((unsigned short)((unsigned char)f[1])); |
950 | 25.3k | break; |
951 | 14.4k | case FLAG_NUM: |
952 | 14.4k | i = atoi(f.c_str()); |
953 | 14.4k | if (i > std::numeric_limits<unsigned short>::max() || i < 0) { |
954 | 1.43k | HUNSPELL_WARNING(stderr, "error: flag id %d is out of range\n", i); |
955 | 1.43k | i = 0; |
956 | 1.43k | } |
957 | 14.4k | s = (unsigned short)i; |
958 | 14.4k | break; |
959 | 25.7k | case FLAG_UNI: { |
960 | 25.7k | std::vector<w_char> w; |
961 | 25.7k | u8_u16(w, f); |
962 | 25.7k | if (!w.empty()) |
963 | 23.0k | s = (unsigned short)w[0]; |
964 | 25.7k | break; |
965 | 0 | } |
966 | 338k | default: |
967 | 338k | if (!f.empty()) |
968 | 333k | s = (unsigned char)f[0]; |
969 | 404k | } |
970 | 404k | if (s == 0) |
971 | 148k | HUNSPELL_WARNING(stderr, "error: 0 is wrong flag id\n"); |
972 | 404k | return s; |
973 | 404k | } |
974 | | |
975 | 55.4M | std::string HashMgr::encode_flag(unsigned short f) const { |
976 | 55.4M | if (f == 0) |
977 | 51.7M | return "(NULL)"; |
978 | 3.74M | std::string ch; |
979 | 3.74M | if (flag_mode == FLAG_LONG) { |
980 | 239 | ch.push_back((unsigned char)(f >> 8)); |
981 | 239 | ch.push_back((unsigned char)(f - ((f >> 8) << 8))); |
982 | 3.74M | } else if (flag_mode == FLAG_NUM) { |
983 | 124 | ch = std::to_string(f); |
984 | 3.74M | } else if (flag_mode == FLAG_UNI) { |
985 | | |
986 | 537k | #if defined(_WIN32) || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)) || defined(__LITTLE_ENDIAN__) |
987 | | |
988 | | #if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && defined __cpp_lib_bit_cast && __cpp_lib_bit_cast >= 201806L |
989 | | auto wc = std::bit_cast<w_char>(f); |
990 | | #else |
991 | 537k | w_char wc; |
992 | 537k | memcpy(&wc, &f, sizeof(unsigned short)); |
993 | 537k | #endif |
994 | | |
995 | | #else |
996 | | w_char wc; |
997 | | wc.h = (unsigned char)(f >> 8); |
998 | | wc.l = (unsigned char)(f & 0xff); |
999 | | #endif |
1000 | 537k | const std::vector<w_char> w = { wc }; |
1001 | 537k | u16_u8(ch, w); |
1002 | 3.20M | } else { |
1003 | 3.20M | ch.push_back((unsigned char)(f)); |
1004 | 3.20M | } |
1005 | 3.74M | return ch; |
1006 | 55.4M | } |
1007 | | |
1008 | | // read in aff file and set flag mode |
1009 | 55.9k | int HashMgr::load_config(const char* affpath, const char* key) { |
1010 | 55.9k | int firstline = 1; |
1011 | | |
1012 | | // open the affix file |
1013 | 55.9k | FileMgr* afflst = new FileMgr(affpath, key); |
1014 | | |
1015 | | // read in each line ignoring any that do not |
1016 | | // start with a known line type indicator |
1017 | | |
1018 | 55.9k | std::string line; |
1019 | 1.80M | while (afflst->getline(line)) { |
1020 | 1.76M | mychomp(line); |
1021 | | |
1022 | | /* remove byte order mark */ |
1023 | 1.76M | if (firstline) { |
1024 | 55.2k | firstline = 0; |
1025 | 55.2k | if (line.compare(0, 3, "\xEF\xBB\xBF", 3) == 0) { |
1026 | 99 | line.erase(0, 3); |
1027 | 99 | } |
1028 | 55.2k | } |
1029 | | |
1030 | | /* parse in the try string */ |
1031 | 1.76M | if ((line.compare(0, 4, "FLAG", 4) == 0) && line.size() > 4 && isspace(line[4])) { |
1032 | 14.5k | if (flag_mode != FLAG_CHAR) { |
1033 | 4.99k | HUNSPELL_WARNING(stderr, |
1034 | 4.99k | "error: line %d: multiple definitions of the FLAG " |
1035 | 4.99k | "affix file parameter\n", |
1036 | 4.99k | afflst->getlinenum()); |
1037 | 4.99k | } |
1038 | 14.5k | if (line.find("long") != std::string::npos) |
1039 | 3.24k | flag_mode = FLAG_LONG; |
1040 | 14.5k | if (line.find("num") != std::string::npos) |
1041 | 2.65k | flag_mode = FLAG_NUM; |
1042 | 14.5k | if (line.find("UTF-8") != std::string::npos) |
1043 | 1.96k | flag_mode = FLAG_UNI; |
1044 | 14.5k | if (flag_mode == FLAG_CHAR) { |
1045 | 4.87k | HUNSPELL_WARNING( |
1046 | 4.87k | stderr, |
1047 | 4.87k | "error: line %d: FLAG needs `num', `long' or `UTF-8' parameter\n", |
1048 | 4.87k | afflst->getlinenum()); |
1049 | 4.87k | } |
1050 | 14.5k | } |
1051 | | |
1052 | 1.76M | if (line.compare(0, 13, "FORBIDDENWORD", 13) == 0) { |
1053 | 6.10k | std::string st; |
1054 | 6.10k | if (!parse_string(line, st, afflst->getlinenum())) { |
1055 | 38 | delete afflst; |
1056 | 38 | return 1; |
1057 | 38 | } |
1058 | 6.06k | forbiddenword = decode_flag(st); |
1059 | 6.06k | } |
1060 | | |
1061 | 1.76M | if (line.compare(0, 3, "SET", 3) == 0) { |
1062 | 12.8k | if (!parse_string(line, enc, afflst->getlinenum())) { |
1063 | 279 | delete afflst; |
1064 | 279 | return 1; |
1065 | 279 | } |
1066 | 12.6k | if (enc == "UTF-8") { |
1067 | 11.1k | utf8 = 1; |
1068 | 11.1k | } else |
1069 | 1.48k | csconv = get_current_cs(enc); |
1070 | 12.6k | } |
1071 | | |
1072 | 1.76M | if (line.compare(0, 4, "LANG", 4) == 0) { |
1073 | 6.72k | if (!parse_string(line, lang, afflst->getlinenum())) { |
1074 | 213 | delete afflst; |
1075 | 213 | return 1; |
1076 | 213 | } |
1077 | 6.51k | langnum = get_lang_num(lang); |
1078 | 6.51k | } |
1079 | | |
1080 | | /* parse in the ignored characters (for example, Arabic optional diacritics |
1081 | | * characters */ |
1082 | 1.75M | if (line.compare(0, 6, "IGNORE", 6) == 0) { |
1083 | 6.53k | if (!parse_array(line, ignorechars, ignorechars_utf16, |
1084 | 6.53k | utf8, afflst->getlinenum())) { |
1085 | 261 | delete afflst; |
1086 | 261 | return 1; |
1087 | 261 | } |
1088 | 6.53k | } |
1089 | | |
1090 | 1.75M | if ((line.compare(0, 2, "AF", 2) == 0) && line.size() > 2 && isspace(line[2])) { |
1091 | 7.30k | if (!parse_aliasf(line, afflst)) { |
1092 | 6.84k | delete afflst; |
1093 | 6.84k | return 1; |
1094 | 6.84k | } |
1095 | 7.30k | } |
1096 | | |
1097 | 1.75M | if ((line.compare(0, 2, "AM", 2) == 0) && line.size() > 2 && isspace(line[2])) { |
1098 | 5.58k | if (!parse_aliasm(line, afflst)) { |
1099 | 4.13k | delete afflst; |
1100 | 4.13k | return 1; |
1101 | 4.13k | } |
1102 | 5.58k | } |
1103 | | |
1104 | 1.74M | if (line.compare(0, 15, "COMPLEXPREFIXES", 15) == 0) |
1105 | 8.48k | complexprefixes = 1; |
1106 | | |
1107 | | /* parse in the typical fault correcting table */ |
1108 | 1.74M | if (line.compare(0, 3, "REP", 3) == 0) { |
1109 | 2.39k | if (!parse_reptable(line, afflst)) { |
1110 | 2.31k | delete afflst; |
1111 | 2.31k | return 1; |
1112 | 2.31k | } |
1113 | 2.39k | } |
1114 | | |
1115 | | // don't check the full affix file, yet |
1116 | 1.74M | if (((line.compare(0, 3, "SFX", 3) == 0) || |
1117 | 1.69M | (line.compare(0, 3, "PFX", 3) == 0)) && |
1118 | 98.6k | line.size() > 3 && isspace(line[3]) && |
1119 | 46.6k | !reptable.empty()) // (REP table is in the end of Afrikaans aff file) |
1120 | 6 | break; |
1121 | 1.74M | } |
1122 | | |
1123 | 41.8k | delete afflst; |
1124 | 41.8k | return 0; |
1125 | 55.9k | } |
1126 | | |
1127 | | /* parse in the ALIAS table */ |
1128 | 7.30k | bool HashMgr::parse_aliasf(const std::string& line, FileMgr* af) { |
1129 | 7.30k | if (!aliasf.empty()) { |
1130 | 187 | HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", |
1131 | 187 | af->getlinenum()); |
1132 | 187 | return false; |
1133 | 187 | } |
1134 | 7.11k | int i = 0, np = 0, numaliasf = 0; |
1135 | 7.11k | auto iter = line.begin(), start_piece = mystrsep(line, iter); |
1136 | 21.1k | while (start_piece != line.end()) { |
1137 | 15.9k | switch (i) { |
1138 | 7.11k | case 0: { |
1139 | 7.11k | np++; |
1140 | 7.11k | break; |
1141 | 0 | } |
1142 | 5.31k | case 1: { |
1143 | 5.31k | numaliasf = atoi(std::string(start_piece, iter).c_str()); |
1144 | 5.31k | if (numaliasf < 1) { |
1145 | 1.92k | aliasf.clear(); |
1146 | 1.92k | aliasflen.clear(); |
1147 | 1.92k | HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", |
1148 | 1.92k | af->getlinenum()); |
1149 | 1.92k | return false; |
1150 | 1.92k | } |
1151 | 3.39k | aliasf.reserve(std::min(numaliasf, 16384)); |
1152 | 3.39k | aliasflen.reserve(std::min(numaliasf, 16384)); |
1153 | 3.39k | np++; |
1154 | 3.39k | break; |
1155 | 5.31k | } |
1156 | 3.52k | default: |
1157 | 3.52k | break; |
1158 | 15.9k | } |
1159 | 14.0k | ++i; |
1160 | 14.0k | start_piece = mystrsep(line, iter); |
1161 | 14.0k | } |
1162 | 5.19k | if (np != 2) { |
1163 | 1.79k | aliasf.clear(); |
1164 | 1.79k | aliasflen.clear(); |
1165 | 1.79k | HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", |
1166 | 1.79k | af->getlinenum()); |
1167 | 1.79k | return false; |
1168 | 1.79k | } |
1169 | | |
1170 | | /* now parse the numaliasf lines to read in the remainder of the table */ |
1171 | 15.8k | for (int j = 0; j < numaliasf; ++j) { |
1172 | 15.3k | std::string nl; |
1173 | 15.3k | unsigned short* alias = nullptr; |
1174 | 15.3k | unsigned aliaslen = 0; |
1175 | 15.3k | i = 0; |
1176 | 15.3k | if (af->getline(nl)) { |
1177 | 14.6k | mychomp(nl); |
1178 | 14.6k | iter = nl.begin(); |
1179 | 14.6k | start_piece = mystrsep(nl, iter); |
1180 | 14.6k | bool errored = false; |
1181 | 58.8k | while (!errored && start_piece != nl.end()) { |
1182 | 44.1k | switch (i) { |
1183 | 14.5k | case 0: { |
1184 | 14.5k | if (nl.compare(start_piece - nl.begin(), 2, "AF", 2) != 0) { |
1185 | 2.00k | errored = true; |
1186 | 2.00k | break; |
1187 | 2.00k | } |
1188 | 12.5k | break; |
1189 | 14.5k | } |
1190 | 12.5k | case 1: { |
1191 | 12.4k | std::string piece(start_piece, iter); |
1192 | 12.4k | aliaslen = |
1193 | 12.4k | (unsigned short)decode_flags(&alias, piece, af, /* arena = */ true); |
1194 | 12.4k | std::sort(alias, alias + aliaslen); |
1195 | 12.4k | break; |
1196 | 14.5k | } |
1197 | 17.1k | default: |
1198 | 17.1k | break; |
1199 | 44.1k | } |
1200 | 44.1k | ++i; |
1201 | 44.1k | start_piece = mystrsep(nl, iter); |
1202 | 44.1k | } |
1203 | 14.6k | } |
1204 | 15.3k | if (!alias) { |
1205 | 11.1k | for (int k = 0; k < j; ++k) { |
1206 | 8.21k | arena_free(aliasf[k]); |
1207 | 8.21k | } |
1208 | 2.93k | aliasf.clear(); |
1209 | 2.93k | aliasflen.clear(); |
1210 | 2.93k | HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", |
1211 | 2.93k | af->getlinenum()); |
1212 | 2.93k | return false; |
1213 | 2.93k | } |
1214 | | |
1215 | 12.4k | aliasf.push_back(alias); |
1216 | 12.4k | aliasflen.push_back(aliaslen); |
1217 | 12.4k | } |
1218 | 454 | return true; |
1219 | 3.39k | } |
1220 | | |
1221 | 176k | int HashMgr::is_aliasf() const { |
1222 | 176k | return !aliasf.empty(); |
1223 | 176k | } |
1224 | | |
1225 | 15.5k | int HashMgr::get_aliasf(int index, unsigned short** fvec, FileMgr* af) const { |
1226 | 15.5k | if (index > 0 && static_cast<size_t>(index) <= aliasflen.size()) { |
1227 | 4.76k | *fvec = aliasf[index - 1]; |
1228 | 4.76k | return aliasflen[index - 1]; |
1229 | 4.76k | } |
1230 | 10.8k | HUNSPELL_WARNING(stderr, "error: line %d: bad flag alias index: %d\n", |
1231 | 10.8k | af->getlinenum(), index); |
1232 | 10.8k | *fvec = nullptr; |
1233 | 10.8k | return 0; |
1234 | 15.5k | } |
1235 | | |
1236 | | /* parse morph alias definitions */ |
1237 | 5.58k | bool HashMgr::parse_aliasm(const std::string& line, FileMgr* af) { |
1238 | 5.58k | if (!aliasm.empty()) { |
1239 | 614 | HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", |
1240 | 614 | af->getlinenum()); |
1241 | 614 | return false; |
1242 | 614 | } |
1243 | 4.96k | int i = 0, np = 0, numaliasm = 0; |
1244 | 4.96k | auto iter = line.begin(), start_piece = mystrsep(line, iter); |
1245 | 25.7k | while (start_piece != line.end()) { |
1246 | 21.8k | switch (i) { |
1247 | 4.96k | case 0: { |
1248 | 4.96k | np++; |
1249 | 4.96k | break; |
1250 | 0 | } |
1251 | 4.93k | case 1: { |
1252 | 4.93k | numaliasm = atoi(std::string(start_piece, iter).c_str()); |
1253 | 4.93k | if (numaliasm < 1) { |
1254 | 1.11k | HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", |
1255 | 1.11k | af->getlinenum()); |
1256 | 1.11k | return false; |
1257 | 1.11k | } |
1258 | 3.82k | aliasm.reserve(std::min(numaliasm, 16384)); |
1259 | 3.82k | np++; |
1260 | 3.82k | break; |
1261 | 4.93k | } |
1262 | 11.9k | default: |
1263 | 11.9k | break; |
1264 | 21.8k | } |
1265 | 20.7k | ++i; |
1266 | 20.7k | start_piece = mystrsep(line, iter); |
1267 | 20.7k | } |
1268 | 3.85k | if (np != 2) { |
1269 | 34 | aliasm.clear(); |
1270 | 34 | HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", |
1271 | 34 | af->getlinenum()); |
1272 | 34 | return false; |
1273 | 34 | } |
1274 | | |
1275 | | /* now parse the numaliasm lines to read in the remainder of the table */ |
1276 | 12.1k | for (int j = 0; j < numaliasm; ++j) { |
1277 | 10.6k | std::string nl; |
1278 | 10.6k | char* alias = nullptr; |
1279 | 10.6k | if (af->getline(nl)) { |
1280 | 10.0k | mychomp(nl); |
1281 | 10.0k | iter = nl.begin(); |
1282 | 10.0k | i = 0; |
1283 | 10.0k | start_piece = mystrsep(nl, iter); |
1284 | 10.0k | bool errored = false; |
1285 | 44.6k | while (!errored && start_piece != nl.end()) { |
1286 | 34.5k | switch (i) { |
1287 | 9.67k | case 0: { |
1288 | 9.67k | if (nl.compare(start_piece - nl.begin(), 2, "AM", 2) != 0) { |
1289 | 1.33k | errored = true; |
1290 | 1.33k | break; |
1291 | 1.33k | } |
1292 | 8.34k | break; |
1293 | 9.67k | } |
1294 | 8.34k | case 1: { |
1295 | | // add the remaining of the line |
1296 | 8.28k | std::string::const_iterator end = nl.end(); |
1297 | 8.28k | std::string chunk(start_piece, end); |
1298 | 8.28k | if (complexprefixes) { |
1299 | 2.13k | if (utf8) |
1300 | 887 | reverseword_utf(chunk); |
1301 | 1.25k | else |
1302 | 1.25k | reverseword(chunk); |
1303 | 2.13k | } |
1304 | 8.28k | size_t sl = chunk.size() + 1; |
1305 | 8.28k | alias = (char*)arena_alloc(sl, alignof(char)); |
1306 | 8.28k | if (alias) { |
1307 | 8.28k | memcpy(alias, chunk.c_str(), sl); |
1308 | 8.28k | } |
1309 | 8.28k | break; |
1310 | 9.67k | } |
1311 | 16.5k | default: |
1312 | 16.5k | break; |
1313 | 34.5k | } |
1314 | 34.5k | ++i; |
1315 | 34.5k | start_piece = mystrsep(nl, iter); |
1316 | 34.5k | } |
1317 | 10.0k | } |
1318 | 10.6k | if (!alias) { |
1319 | 6.78k | for (int k = 0; k < j; ++k) { |
1320 | 4.40k | arena_free(aliasm[k]); |
1321 | 4.40k | } |
1322 | 2.38k | aliasm.clear(); |
1323 | 2.38k | HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", |
1324 | 2.38k | af->getlinenum()); |
1325 | 2.38k | return false; |
1326 | 2.38k | } |
1327 | 8.28k | aliasm.push_back(alias); |
1328 | 8.28k | } |
1329 | 1.44k | return true; |
1330 | 3.82k | } |
1331 | | |
1332 | 199k | int HashMgr::is_aliasm() const { |
1333 | 199k | return !aliasm.empty(); |
1334 | 199k | } |
1335 | | |
1336 | 69.3k | char* HashMgr::get_aliasm(int index) const { |
1337 | 69.3k | if (index > 0 && static_cast<size_t>(index) <= aliasm.size()) |
1338 | 15.2k | return aliasm[index - 1]; |
1339 | 54.1k | HUNSPELL_WARNING(stderr, "error: bad morph. alias index: %d\n", index); |
1340 | 54.1k | return nullptr; |
1341 | 69.3k | } |
1342 | | |
1343 | | /* parse in the typical fault correcting table */ |
1344 | 2.39k | bool HashMgr::parse_reptable(const std::string& line, FileMgr* af) { |
1345 | 2.39k | if (!reptable.empty()) { |
1346 | 9 | HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", |
1347 | 9 | af->getlinenum()); |
1348 | 9 | return false; |
1349 | 9 | } |
1350 | 2.38k | int numrep = -1, i = 0, np = 0; |
1351 | 2.38k | auto iter = line.begin(), start_piece = mystrsep(line, iter); |
1352 | 12.0k | while (start_piece != line.end()) { |
1353 | 9.87k | switch (i) { |
1354 | 2.38k | case 0: { |
1355 | 2.38k | np++; |
1356 | 2.38k | break; |
1357 | 0 | } |
1358 | 2.16k | case 1: { |
1359 | 2.16k | numrep = atoi(std::string(start_piece, iter).c_str()); |
1360 | 2.16k | if (numrep < 1) { |
1361 | 209 | HUNSPELL_WARNING(stderr, "error: line %d: incorrect entry number\n", |
1362 | 209 | af->getlinenum()); |
1363 | 209 | return false; |
1364 | 209 | } |
1365 | 1.95k | reptable.reserve(std::min(numrep, 16384)); |
1366 | 1.95k | np++; |
1367 | 1.95k | break; |
1368 | 2.16k | } |
1369 | 5.32k | default: |
1370 | 5.32k | break; |
1371 | 9.87k | } |
1372 | 9.66k | ++i; |
1373 | 9.66k | start_piece = mystrsep(line, iter); |
1374 | 9.66k | } |
1375 | 2.17k | if (np != 2) { |
1376 | 224 | HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", |
1377 | 224 | af->getlinenum()); |
1378 | 224 | return false; |
1379 | 224 | } |
1380 | | |
1381 | | /* now parse the numrep lines to read in the remainder of the table */ |
1382 | 8.11k | for (int j = 0; j < numrep; ++j) { |
1383 | 8.03k | std::string nl; |
1384 | 8.03k | reptable.emplace_back(); |
1385 | 8.03k | int type = 0; |
1386 | 8.03k | if (af->getline(nl)) { |
1387 | 7.58k | mychomp(nl); |
1388 | 7.58k | iter = nl.begin(); |
1389 | 7.58k | i = 0; |
1390 | 7.58k | start_piece = mystrsep(nl, iter); |
1391 | 7.58k | bool errored = false; |
1392 | 38.3k | while (!errored && start_piece != nl.end()) { |
1393 | 30.7k | switch (i) { |
1394 | 7.42k | case 0: { |
1395 | 7.42k | if (nl.compare(start_piece - nl.begin(), 3, "REP", 3) != 0) { |
1396 | 1.07k | errored = true; |
1397 | 1.07k | break; |
1398 | 1.07k | } |
1399 | 6.34k | break; |
1400 | 7.42k | } |
1401 | 6.34k | case 1: { |
1402 | 6.29k | if (*start_piece == '^') |
1403 | 531 | type = 1; |
1404 | 6.29k | reptable.back().pattern.assign(start_piece + type, iter); |
1405 | 6.29k | mystrrep(reptable.back().pattern, "_", " "); |
1406 | 6.29k | if (!reptable.back().pattern.empty() && reptable.back().pattern[reptable.back().pattern.size() - 1] == '$') { |
1407 | 1.92k | type += 2; |
1408 | 1.92k | reptable.back().pattern.resize(reptable.back().pattern.size() - 1); |
1409 | 1.92k | } |
1410 | 6.29k | break; |
1411 | 7.42k | } |
1412 | 6.16k | case 2: { |
1413 | 6.16k | reptable.back().outstrings[type].assign(start_piece, iter); |
1414 | 6.16k | mystrrep(reptable.back().outstrings[type], "_", " "); |
1415 | 6.16k | break; |
1416 | 7.42k | } |
1417 | 10.9k | default: |
1418 | 10.9k | break; |
1419 | 30.7k | } |
1420 | 30.7k | ++i; |
1421 | 30.7k | start_piece = mystrsep(nl, iter); |
1422 | 30.7k | } |
1423 | 7.58k | } |
1424 | 8.03k | if (reptable.back().pattern.empty() || reptable.back().outstrings[type].empty()) { |
1425 | 1.86k | HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", |
1426 | 1.86k | af->getlinenum()); |
1427 | 1.86k | reptable.clear(); |
1428 | 1.86k | return false; |
1429 | 1.86k | } |
1430 | 8.03k | } |
1431 | 85 | return true; |
1432 | 1.95k | } |
1433 | | |
1434 | | // return replacing table |
1435 | 5.11M | const std::vector<replentry>& HashMgr::get_reptable() const { |
1436 | 5.11M | return reptable; |
1437 | 5.11M | } |
1438 | | |
1439 | 3.56M | void* HashMgr::arena_alloc(size_t num_bytes, size_t alignment) const { |
1440 | | // Fixed-size 64KB chunks: small enough to avoid significant waste on small |
1441 | | // dictionaries, large enough to amortize per-chunk malloc overhead on large |
1442 | | // ones. make_unique throws std::bad_alloc on OOM. |
1443 | 3.56M | static const size_t MIN_CHUNK_SIZE = 65536; |
1444 | 3.56M | static const size_t MAX_ALIGNMENT = alignof(max_align_t); |
1445 | | // Chunk sizes are rounded up to MAX_ALIGNMENT below; with this invariant, |
1446 | | // any alignment that divides MAX_ALIGNMENT keeps aligned_offset within bounds. |
1447 | 3.56M | assert(alignment > 0 && alignment <= MAX_ALIGNMENT); |
1448 | | // Pad the offset up to the requested alignment before placing this allocation. |
1449 | | // make_unique returns memory aligned for any scalar, so chunk-start is fine. |
1450 | 3.56M | size_t aligned_offset = (current_chunk_offset + alignment - 1) & ~(alignment - 1); |
1451 | 3.56M | if (arena.empty() || current_chunk_size - aligned_offset < num_bytes) { |
1452 | | // Round the new chunk's size up to a multiple of MAX_ALIGNMENT so that an |
1453 | | // oversized num_bytes (>= MIN_CHUNK_SIZE) cannot leave a non-aligned |
1454 | | // current_chunk_size that would later cause aligned_offset to overshoot. |
1455 | | // Allocate before mutating current_chunk_size so a throwing make_unique |
1456 | | // leaves the HashMgr in a consistent state. |
1457 | 28.4k | size_t new_size = std::max(MIN_CHUNK_SIZE, num_bytes); |
1458 | 28.4k | new_size = (new_size + MAX_ALIGNMENT - 1) & ~(MAX_ALIGNMENT - 1); |
1459 | 28.4k | arena.push_back(std::make_unique<uint8_t[]>(new_size)); |
1460 | 28.4k | current_chunk_size = new_size; |
1461 | 28.4k | aligned_offset = 0; |
1462 | 28.4k | } |
1463 | | |
1464 | 3.56M | uint8_t* ptr = &arena.back()[aligned_offset]; |
1465 | 3.56M | current_chunk_offset = aligned_offset + num_bytes; |
1466 | 3.56M | ++outstanding_arena_allocations; |
1467 | 3.56M | return ptr; |
1468 | 3.56M | } |
1469 | | |
1470 | 2.79M | void HashMgr::arena_free(void*) const { |
1471 | | // The arena vector owns all allocations and frees them in bulk at HashMgr |
1472 | | // destruction, so this is a no-op for the memory itself. The counter is a |
1473 | | // memory-safety check: more arena_free calls than arena_alloc calls would |
1474 | | // indicate a double-free or use-after-free in Hunspell. Abort hard rather |
1475 | | // than silently desynchronize tracking, even in release builds. |
1476 | 2.79M | if (outstanding_arena_allocations == 0) { |
1477 | 0 | std::abort(); |
1478 | 0 | } |
1479 | 2.79M | --outstanding_arena_allocations; |
1480 | 2.79M | } |