1 // This file is part of The New Aspell Copyright (C)
2 // 2002,2003,2004,2011,2019 by Kevin Atkinson under the GNU LGPL license
3 // version 2.0 or 2.1. You should have received a copy of the LGPL
4 // license along with this library if you did not you can find it at
5 // http://www.gnu.org/.
6
7 //
8 // NOTE: This program currently uses a very ugly mix of the internal
9 // API and the external C interface. The eventual goal is to
10 // use only the external C++ interface, however, the external
11 // C++ interface is currently incomplete. The C interface is
12 // used in some places because without the strings will not get
13 // converted properly when the encoding is not the same as the
14 // internal encoding used by Aspell.
15 //
16
17 #include <ctype.h>
18 #include "settings.h"
19
20 #ifdef USE_LOCALE
21 # include <locale.h>
22 #endif
23
24 #ifdef HAVE_LANGINFO_CODESET
25 # include <langinfo.h>
26 #endif
27
28 #include "errors.hpp"
29 #include "aspell.h"
30
31 #include <sys/types.h>
32 #include <sys/stat.h>
33
34 #ifdef USE_FILE_INO
35 # include <unistd.h>
36 # include <fcntl.h>
37 #endif
38
39 #include "asc_ctype.hpp"
40 #include "check_funs.hpp"
41 #include "config.hpp"
42 #include "convert.hpp"
43 #include "document_checker.hpp"
44 #include "enumeration.hpp"
45 #include "file_util.hpp"
46 #include "fstream.hpp"
47 #include "info.hpp"
48 #include "iostream.hpp"
49 #include "posib_err.hpp"
50 #include "speller.hpp"
51 #include "stack_ptr.hpp"
52 #include "string_enumeration.hpp"
53 #include "string_map.hpp"
54 #include "word_list.hpp"
55 #include "istream_enumeration.hpp"
56
57 #include "string_list.hpp"
58 #include "speller_impl.hpp"
59 #include "data.hpp"
60
61 #include "hash-t.hpp"
62 #include "hash_fun.hpp"
63
64 #include "gettext.h"
65
66 using namespace acommon;
67
68 using aspeller::Conv;
69
70 // action functions declarations
71
72 void print_ver();
73 void print_help(bool verbose = false);
74 void config();
75
76 void check();
77 void pipe();
78 void convt();
79 void normlz();
80 void filter();
81 void list();
82 void dicts();
83 void modes();
84 void filters();
85
86 void clean();
87 void master();
88 void personal();
89 void repl();
90 void soundslike();
91 void munch();
92 void expand();
93 void combine();
94 void munch_list();
95 void dump_affix();
96
print_error(ParmString msg)97 void print_error(ParmString msg)
98 {
99 CERR.printf(_("Error: %s\n"), msg.str());
100 }
101
print_error(ParmString msg,ParmString str)102 void print_error(ParmString msg, ParmString str)
103 {
104 CERR.put(_("Error: "));
105 CERR.printf(msg.str(), str.str());
106 CERR.put('\n');
107 }
108
109 #define EXIT_ON_ERR(command) \
110 do{PosibErrBase pe(command);\
111 if(pe.has_err()){print_error(pe.get_err()->mesg); exit(1);}\
112 } while(false)
113 #define EXIT_ON_ERR_SET(command, type, var)\
114 type var;\
115 do{PosibErr< type > pe(command);\
116 if(pe.has_err()){print_error(pe.get_err()->mesg); exit(1);}\
117 else {var=pe.data;}\
118 } while(false)
119 #define BREAK_ON_ERR(command) \
120 do{PosibErrBase pe(command);\
121 if(pe.has_err()){print_error(pe.get_err()->mesg); break;}\
122 } while(false)
123 #define BREAK_ON_ERR_SET(command, type, var)\
124 type var;\
125 do{PosibErr< type > pe(command);\
126 if(pe.has_err()){print_error(pe.get_err()->mesg); break;}\
127 else {var=pe.data;}\
128 } while(false)
129
130
131 /////////////////////////////////////////////////////////
132 //
133 // Command line options functions and classes
134 // (including main)
135 //
136
137 typedef Vector<String> Args;
138 typedef Config Options;
139 enum Action {do_create, do_merge, do_dump, do_test, do_other};
140
141 Args args;
142 StackPtr<Options> options;
143 Action action = do_other;
144
145 struct PossibleOption {
146 const char * name;
147 char abrv;
148 int num_arg;
149 bool is_command;
150 };
151
152 #define OPTION(name,abrv,num) {name,abrv,num,false}
153 #define COMMAND(name,abrv,num) {name,abrv,num,true}
154 #define ISPELL_COMP(abrv,num) {"",abrv,num,false}
155
156 const PossibleOption possible_options[] = {
157 OPTION("master", 'd', 1),
158 OPTION("personal", 'p', 1),
159 OPTION("ignore", 'W', 1),
160 OPTION("lang", 'l', 1),
161 OPTION("backup", 'b', 0),
162 OPTION("dont-backup", 'x', 0),
163 OPTION("run-together", 'C', 0),
164 OPTION("dont-run-together",'B', 0),
165 OPTION("guess", 'm', 0),
166 OPTION("dont-guess", 'P', 0),
167
168 COMMAND("usage", '?', 0),
169 COMMAND("help", '\0', 0),
170 COMMAND("version", 'v', 0),
171 COMMAND("config", '\0', 0),
172 COMMAND("dicts", '\0', 0),
173 COMMAND("check", 'c', 0),
174 COMMAND("pipe", 'a', 0),
175 COMMAND("list", '\0', 0),
176 COMMAND("conv", '\0', 2),
177 COMMAND("norm", '\0', 1),
178 COMMAND("filter", '\0', 0),
179 COMMAND("soundslike",'\0', 0),
180 COMMAND("munch", '\0', 0),
181 COMMAND("expand", '\0', 0),
182 COMMAND("combine", '\0', 0),
183 COMMAND("munch-list",'\0', 0),
184 COMMAND("clean", '\0', 0),
185 COMMAND("filters", '\0', 0),
186 COMMAND("modes", '\0', 0),
187
188 COMMAND("dump", '\0', 1),
189 COMMAND("create", '\0', 1),
190 COMMAND("merge", '\0', 1),
191
192 ISPELL_COMP('S',0), ISPELL_COMP('w',1), ISPELL_COMP('T',1),
193
194 {"",'\0'}, {"",'\0'}
195 };
196
197 const PossibleOption * possible_options_end = possible_options + sizeof(possible_options)/sizeof(PossibleOption) - 2;
198
199 struct ModeAbrv {
200 char abrv;
201 const char * mode;
202 const char * desc;
203 };
204 static const ModeAbrv mode_abrvs[] = {
205 {'e', "mode=email", N_("enter Email mode.")},
206 {'H', "mode=html", N_("enter HTML mode.")},
207 {'t', "mode=tex", N_("enter TeX mode.")},
208 {'n', "mode=nroff", N_("enter Nroff mode.")},
209 {'M', "mode=markdown", N_("enter Markdown mode.")},
210 };
211
212 static const ModeAbrv * mode_abrvs_end = mode_abrvs + sizeof(mode_abrvs)/sizeof(ModeAbrv);
213
find_option(char c)214 const PossibleOption * find_option(char c) {
215 const PossibleOption * i = possible_options;
216 while (i != possible_options_end && i->abrv != c)
217 ++i;
218 return i;
219 }
220
str_equal(const char * begin,const char * end,const char * other)221 static inline bool str_equal(const char * begin, const char * end,
222 const char * other)
223 {
224 while(begin != end && *begin == *other)
225 ++begin, ++other;
226 return (begin == end && *other == '\0');
227 }
228
find_option(const char * begin,const char * end)229 static const PossibleOption * find_option(const char * begin, const char * end) {
230 const PossibleOption * i = possible_options;
231 while (i != possible_options_end
232 && !str_equal(begin, end, i->name))
233 ++i;
234 return i;
235 }
236
find_option(const char * str)237 static const PossibleOption * find_option(const char * str) {
238 const PossibleOption * i = possible_options;
239 while (i != possible_options_end
240 && strcmp(str, i->name) != 0)
241 ++i;
242 return i;
243 }
244
line_buffer()245 static void line_buffer() {
246 #ifndef WIN32
247 // set up stdin and stdout to be line buffered
248 assert(setvbuf(stdin, 0, _IOLBF, 0) == 0);
249 assert(setvbuf(stdout, 0, _IOLBF, 0) == 0);
250 #endif
251 }
252
253 Conv dconv;
254 Conv uiconv;
255
main(int argc,const char * argv[])256 int main (int argc, const char *argv[])
257 {
258 options = new_config(); // this needs to be here because of a bug
259 // with static initlizers on Darwin.
260 #ifdef USE_LOCALE
261 setlocale (LC_ALL, "");
262 #endif
263 aspell_gettext_init();
264
265 options->set_committed_state(false);
266
267 if (argc == 1) {print_help(); return 0;}
268
269 int i = 1;
270 const PossibleOption * o;
271 const char * parm;
272
273 //
274 // process command line options by setting the appropriate options
275 // in "options" and/or pushing non-options onto "argv"
276 //
277 PossibleOption other_opt = OPTION("",'\0',0);
278 String option_name;
279 while (i != argc) {
280 if (argv[i][0] == '-') {
281 bool have_parm = false;
282 if (argv[i][1] == '-') {
283 // a long arg
284 const char * c = argv[i] + 2;
285 while(*c != '=' && *c != '\0') ++c;
286 o = find_option(argv[i] + 2, c);
287 if (o == possible_options_end) {
288 option_name.assign(argv[i] + 2, c - argv[i] - 2);
289 other_opt.name = option_name.c_str();
290 other_opt.num_arg = -1;
291 o = &other_opt;
292 }
293 if (*c == '=') {have_parm = true; ++c;}
294 parm = c;
295 } else {
296 // a short arg
297 const ModeAbrv * j = mode_abrvs;
298 while (j != mode_abrvs_end && j->abrv != argv[i][1]) ++j;
299 if (j == mode_abrvs_end) {
300 o = find_option(argv[i][1]);
301 if (argv[i][1] == 'v' && argv[i][2] == 'v')
302 // Hack for -vv
303 parm = argv[i] + 3;
304 else
305 parm = argv[i] + 2;
306 } else { // mode option
307 other_opt.name = "mode";
308 other_opt.num_arg = 1;
309 o = &other_opt;
310 parm = j->mode + 5;
311 }
312 if (*parm) have_parm = true;
313 }
314 if (o == possible_options_end) {
315 print_error(_("Invalid Option: %s"), argv[i]);
316 return 1;
317 }
318 int num_parms;
319 if (o->num_arg == 0) {
320 num_parms = 0;
321 if (parm[0] != '\0') {
322 print_error(_(" does not take any parameters."),
323 String(argv[i], parm - argv[i]));
324 return 1;
325 }
326 i += 1;
327 } else if (have_parm) {
328 num_parms = 1;
329 i += 1;
330 } else if (i + 1 == argc || argv[i+1][0] == '-') {
331 if (o->num_arg == -1) {
332 num_parms = 0;
333 i += 1;
334 } else {
335 print_error(_("You must specify a parameter for \"%s\"."), argv[i]);
336 return 1;
337 }
338 } else {
339 num_parms = o->num_arg;
340 parm = argv[i + 1];
341 i += 2;
342 }
343 if (o->is_command) {
344 args.push_back(o->name);
345 if (o->num_arg == 1)
346 args.push_back(parm);
347 } else if (o->name[0] != '\0') {
348 Config::Entry * entry = new Config::Entry;
349 entry->key = o->name;
350 entry->value = parm;
351 entry->need_conv = true;
352 if (num_parms == -1) {
353 entry->place_holder = args.size();
354 args.push_back(parm);
355 }
356 options->set(entry);
357 }
358 } else {
359 args.push_back(argv[i]);
360 i += 1;
361 }
362 }
363
364 EXIT_ON_ERR(options->read_in_settings());
365
366 const char * codeset = 0;
367 #ifdef HAVE_LANGINFO_CODESET
368 codeset = nl_langinfo(CODESET);
369 if (ascii_encoding(*options, codeset)) codeset = 0;
370 #endif
371
372 // #ifdef USE_LOCALE
373 // if (!options->have("encoding") && codeset)
374 // EXIT_ON_ERR(options->replace("encoding", codeset));
375 // #endif
376
377 Vector<int> to_remove;
378 EXIT_ON_ERR(options->commit_all(&to_remove, codeset));
379 for (int i = to_remove.size() - 1; i >= 0; --i) {
380 args.erase(args.begin() + to_remove[i]);
381 }
382
383 if (args.empty()) {
384 print_error(_("You must specify an action"));
385 return 1;
386 }
387
388 String action_str = args.front();
389 args.pop_front();
390 const PossibleOption * action_opt = find_option(action_str.str());
391 if (!action_opt->is_command) {
392 print_error(_("Unknown Action: %s"), action_str);
393 return 1;
394 } else if (action_opt->num_arg == 1 && args.empty()) {
395 print_error(_("You must specify a parameter for \"%s\"."), action_str);
396 return 1;
397 } else if (action_opt->num_arg > (int)args.size()) {
398 CERR.printf(_("Error: You must specify at least %d parameters for \"%s\".\n"),
399 action_opt->num_arg, action_str.str());
400 return 1;
401 }
402
403 //
404 // perform the requested action
405 //
406 if (action_str == "usage")
407 print_help();
408 else if (action_str == "help")
409 print_help(true);
410 else if (action_str == "version")
411 print_ver();
412 else if (action_str == "config")
413 config();
414 else if (action_str == "dicts")
415 dicts();
416 else if (action_str == "check")
417 check();
418 else if (action_str == "pipe")
419 pipe();
420 else if (action_str == "list")
421 list();
422 else if (action_str == "conv")
423 convt();
424 else if (action_str == "norm")
425 normlz();
426 else if (action_str == "filter")
427 filter();
428 else if (action_str == "soundslike")
429 soundslike();
430 else if (action_str == "munch")
431 munch();
432 else if (action_str == "expand")
433 expand();
434 else if (action_str == "combine")
435 combine();
436 else if (action_str == "munch-list")
437 munch_list();
438 else if (action_str == "clean")
439 clean();
440 else if (action_str == "filters")
441 filters();
442 else if (action_str == "modes")
443 modes();
444 else if (action_str == "dump")
445 action = do_dump;
446 else if (action_str == "create")
447 action = do_create;
448 else if (action_str == "merge")
449 action = do_merge;
450 else
451 abort(); // this should not happen
452
453 if (action != do_other) {
454 if (args.empty()) {
455 print_error(_("Unknown Action: %s"), action_str);
456 return 1;
457 }
458 String what_str = args.front();
459 args.pop_front();
460 if (what_str == "config")
461 config();
462 else if (what_str == "dicts")
463 dicts();
464 else if (what_str == "filters")
465 filters();
466 else if (what_str == "modes")
467 modes();
468 else if (what_str == "master")
469 master();
470 else if (what_str == "personal")
471 personal();
472 else if (what_str == "repl")
473 repl();
474 else if (what_str == "affix")
475 dump_affix();
476 else {
477 print_error(_("Unknown Action: %s"),
478 String(action_str + " " + what_str));
479 return 1;
480 }
481 }
482
483 return 0;
484
485 }
486
487
488 /////////////////////////////////////////////////////////
489 //
490 // Action Functions
491 //
492 //
493
494
setup_conv(const aspeller::Language * lang,Config * config)495 static Convert * setup_conv(const aspeller::Language * lang,
496 Config * config)
497 {
498 if (config->retrieve("encoding") != "none") {
499 PosibErr<Convert *> pe = new_convert_if_needed(*config,
500 lang->charmap(),
501 config->retrieve("encoding"),
502 NormTo);
503 if (pe.has_err()) {print_error(pe.get_err()->mesg); exit(1);}
504 return pe.data;
505 } else {
506 return 0;
507 }
508 }
509
setup_conv(Config * config,const aspeller::Language * lang)510 static Convert * setup_conv(Config * config,
511 const aspeller::Language * lang)
512 {
513 if (config->retrieve("encoding") != "none") {
514 PosibErr<Convert *> pe = new_convert_if_needed(*config,
515 config->retrieve("encoding"),
516 lang->charmap(),
517 NormFrom);
518 if (pe.has_err()) {print_error(pe.get_err()->mesg); exit(1);}
519 return pe.data;
520 } else {
521 return 0;
522 }
523 }
524
setup_display_conv()525 void setup_display_conv()
526 {
527 const char * gettext_enc = 0;
528 const char * env_enc = 0;
529 String doc_enc = options->retrieve("encoding");
530 String enc;
531 #ifdef ENABLE_NLS
532 gettext_enc = bind_textdomain_codeset("aspell", 0);
533 if (ascii_encoding(*options,gettext_enc)) gettext_enc = 0;
534 #endif
535 #ifdef HAVE_LANGINFO_CODESET
536 env_enc = nl_langinfo(CODESET);
537 if (ascii_encoding(*options, env_enc)) env_enc = 0;
538 #endif
539 if (gettext_enc && env_enc && strcmp(gettext_enc,env_enc) != 0)
540 {
541 fputs(("Error: bind_textdomain_codeset != nl_langinfo(CODESET)\n"), stderr);
542 exit(-1);
543 }
544 if (gettext_enc)
545 enc = gettext_enc;
546 else if (env_enc)
547 enc = env_enc;
548 else
549 enc = doc_enc;
550
551 EXIT_ON_ERR(dconv.setup(*options, doc_enc, enc, NormNone));
552 EXIT_ON_ERR(uiconv.setup(*options, enc, doc_enc, NormNone));
553 }
554
555
556 ///////////////////////////
557 //
558 // config
559 //
560
config()561 void config ()
562 {
563 if (args.size() == 0) {
564 load_all_filters(options);
565 options->write_to_stream(COUT);
566 } else {
567 EXIT_ON_ERR_SET(options->retrieve_any(args[0]), String, value);
568 COUT << value << "\n";
569 }
570 }
571
572 ///////////////////////////
573 //
574 // dicts
575 //
576
dicts()577 void dicts()
578 {
579 const DictInfoList * dlist = get_dict_info_list(options);
580
581 StackPtr<DictInfoEnumeration> dels(dlist->elements());
582
583 const DictInfo * entry;
584
585 while ( (entry = dels->next()) != 0)
586 puts(entry->name);
587 }
588
589 ///////////////////////////
590 //
591 // list available (filters/filter modes)
592 //
593
list_available(PosibErr<StringPairEnumeration * > (* fun)(Config *))594 void list_available(PosibErr<StringPairEnumeration *> (*fun)(Config *))
595 {
596 EXIT_ON_ERR_SET(fun(options), StringPairEnumeration *, els);
597 StringPair sp;
598 while (!els->at_end()) {
599 sp = els->next();
600 printf("%-14s %s\n", sp.first, gt_(sp.second));
601 }
602 delete els;
603 }
604
filters()605 void filters()
606 {
607 load_all_filters(options);
608 list_available(available_filters);
609 }
610
modes()611 void modes()
612 {
613 list_available(available_filter_modes);
614 }
615
616 ///////////////////////////
617 //
618 // pipe
619 //
620
621 // precond: strlen(str) > 0
trim_wspace(char * str)622 char * trim_wspace (char * str)
623 {
624 int last = strlen(str) - 1;
625 while (asc_isspace(str[0])) {
626 ++str;
627 --last;
628 }
629 while (last > 0 && asc_isspace(str[last])) {
630 --last;
631 }
632 str[last + 1] = '\0';
633 return str;
634 }
635
get_word_pair(char * line,char * & w1,char * & w2)636 bool get_word_pair(char * line, char * & w1, char * & w2)
637 {
638 w2 = strchr(line, ',');
639 if (!w2) {
640 print_error(_("Invalid Input"));
641 return false;
642 }
643 *w2 = '\0';
644 ++w2;
645 w1 = trim_wspace(line);
646 w2 = trim_wspace(w2);
647 return true;
648 }
649
print_elements(const AspellWordList * wl)650 void print_elements(const AspellWordList * wl) {
651 AspellStringEnumeration * els = aspell_word_list_elements(wl);
652 int count = 0;
653 const char * w;
654 String line;
655 while ( (w = aspell_string_enumeration_next(els)) != 0 ) {
656 ++count;
657 line += w;
658 line += ", ";
659 }
660 line.resize(line.size() - 2);
661 COUT.printf("%u: %s\n", count, line.c_str());
662 }
663
664 struct StatusFunInf
665 {
666 aspeller::SpellerImpl * real_speller;
667 Conv oconv;
668 bool verbose;
StatusFunInfStatusFunInf669 StatusFunInf(Convert * c) : oconv(c) {}
670 };
671
status_fun(void * d,Token,int correct)672 void status_fun(void * d, Token, int correct)
673 {
674 StatusFunInf * p = static_cast<StatusFunInf *>(d);
675 if (p->verbose && correct) {
676 const CheckInfo * ci = p->real_speller->check_info();
677 if (ci->compound)
678 COUT.put("-\n");
679 else if (ci->pre_flag || ci->suf_flag)
680 COUT.printf("+ %s\n", p->oconv(ci->word.str, ci->word.len));
681 else
682 COUT.put("*\n");
683 }
684 }
685
new_checker(AspellSpeller * speller,StatusFunInf & status_fun_inf)686 DocumentChecker * new_checker(AspellSpeller * speller,
687 StatusFunInf & status_fun_inf)
688 {
689 EXIT_ON_ERR_SET(new_document_checker(reinterpret_cast<Speller *>(speller)),
690 StackPtr<DocumentChecker>, checker);
691 checker->set_status_fun(status_fun, &status_fun_inf);
692 return checker.release();
693 }
694
695 #define BREAK_ON_SPELLER_ERR\
696 do {if (aspell_speller_error(speller)) {\
697 print_error(aspell_speller_error_message(speller)); break;\
698 } } while (false)
699
pipe()700 void pipe()
701 {
702 line_buffer();
703
704 bool terse_mode = true;
705 bool do_time = options->retrieve_bool("time");
706 bool suggest = options->retrieve_bool("suggest");
707 bool include_guesses = options->retrieve_bool("guess");
708 clock_t start,finish;
709
710 if (!options->have("mode") && !options->have("filter")) {
711 PosibErrBase err(options->replace("mode", "nroff"));
712 if (err.has_err())
713 CERR.printf(_("WARNING: Unable to enter Nroff mode: %s\n"),
714 err.get_err()->mesg);
715 }
716
717 start = clock();
718
719 AspellCanHaveError * ret
720 = new_aspell_speller(reinterpret_cast<AspellConfig *>(options.get()));
721 if (aspell_error(ret)) {
722 print_error(aspell_error_message(ret));
723 exit(1);
724 }
725 AspellSpeller * speller = to_aspell_speller(ret);
726 aspeller::SpellerImpl * real_speller = reinterpret_cast<aspeller::SpellerImpl *>(speller);
727 Config * config = real_speller->config();
728 Conv iconv(setup_conv(config, &real_speller->lang()));
729 Conv oconv(setup_conv(&real_speller->lang(), config));
730 MBLen mb_len;
731 if (!config->retrieve_bool("byte-offsets"))
732 mb_len.setup(*config, config->retrieve("encoding"));
733 if (do_time)
734 COUT << _("Time to load word list: ")
735 << (clock() - start)/(double)CLOCKS_PER_SEC << "\n";
736 StatusFunInf status_fun_inf(setup_conv(&real_speller->lang(), config));
737 status_fun_inf.real_speller = real_speller;
738 bool & print_star = status_fun_inf.verbose;
739 print_star = true;
740 StackPtr<DocumentChecker> checker(new_checker(speller, status_fun_inf));
741 int c;
742 const char * w;
743 CharVector buf;
744 char * line;
745 char * line0;
746 char * word;
747 char * word2;
748 int ignore;
749 PosibErrBase err;
750
751 print_ver();
752
753 for (;;) {
754 buf.clear();
755 fflush(stdout);
756 while (c = getchar(), c != '\n' && c != EOF)
757 buf.push_back(static_cast<char>(c));
758 buf.push_back('\n'); // always add new line so strlen > 0
759 buf.push_back('\0');
760 //CERR.printf("%s", buf.data());
761 line = buf.data();
762 ignore = 0;
763 switch (line[0]) {
764 case '\n':
765 if (c != EOF) continue;
766 else break;
767 case '*':
768 word = trim_wspace(line + 1);
769 aspell_speller_add_to_personal(speller, word, -1);
770 BREAK_ON_SPELLER_ERR;
771 break;
772 case '&':
773 word = trim_wspace(line + 1);
774 aspell_speller_add_to_personal
775 (speller,
776 real_speller->to_lower(word), -1);
777 BREAK_ON_SPELLER_ERR;
778 break;
779 case '@':
780 word = trim_wspace(line + 1);
781 aspell_speller_add_to_session(speller, word, -1);
782 BREAK_ON_SPELLER_ERR;
783 break;
784 case '#':
785 aspell_speller_save_all_word_lists(speller);
786 BREAK_ON_SPELLER_ERR;
787 break;
788 case '+':
789 word = trim_wspace(line + 1);
790 err = config->replace("mode", word);
791 if (err.get_err())
792 config->replace("mode", "tex");
793 reload_filters(real_speller);
794 checker.del();
795 checker = new_checker(speller, status_fun_inf);
796 break;
797 case '-':
798 config->remove("filter");
799 reload_filters(real_speller);
800 checker.del();
801 checker = new_checker(speller, status_fun_inf);
802 break;
803 case '~':
804 break;
805 case '!':
806 terse_mode = true;
807 print_star = false;
808 break;
809 case '%':
810 terse_mode = false;
811 print_star = true;
812 break;
813 case '$':
814 if (line[1] == '$') {
815 switch(line[2]) {
816 case 'r':
817 switch(line[3]) {
818 case 'a':
819 if (get_word_pair(line + 4, word, word2))
820 aspell_speller_store_replacement(speller, word, -1, word2, -1);
821 break;
822 }
823 break;
824 case 'c':
825 switch (line[3]) {
826 case 's':
827 if (get_word_pair(line + 4, word, word2))
828 BREAK_ON_ERR(err = config->replace(word, word2));
829 if (strcmp(word,"suggest") == 0)
830 suggest = config->retrieve_bool("suggest");
831 else if (strcmp(word,"time") == 0)
832 do_time = config->retrieve_bool("time");
833 else if (strcmp(word,"guess") == 0)
834 include_guesses = config->retrieve_bool("guess");
835 break;
836 case 'r':
837 word = trim_wspace(line + 4);
838 BREAK_ON_ERR_SET(config->retrieve(word), String, ret);
839 COUT.printl(ret);
840 break;
841 }
842 break;
843 case 'p':
844 switch (line[3]) {
845 case 'p':
846 print_elements(aspell_speller_personal_word_list(speller));
847 break;
848 case 's':
849 print_elements(aspell_speller_session_word_list(speller));
850 break;
851 }
852 break;
853 case 'l':
854 COUT.printl(config->retrieve("lang"));
855 break;
856 }
857 break;
858 } else {
859 // continue on (no break)
860 }
861 case '^':
862 ignore = 1;
863 default:
864 line0 = line;
865 line += ignore;
866 checker->process(line, strlen(line));
867 while (Token token = checker->next_misspelling()) {
868 word = line + token.offset;
869 word[token.len] = '\0';
870 const char * cword = iconv(word);
871 String guesses, guess;
872 const CheckInfo * ci = real_speller->check_info();
873 aspeller::CasePattern casep
874 = real_speller->lang().case_pattern(cword);
875 while (ci) {
876 guess.clear();
877 if (ci->pre_add && ci->pre_add[0])
878 guess.append(ci->pre_add, ci->pre_add_len).append('+');
879 guess.append(ci->word.str, ci->word.len);
880 if (ci->pre_strip_len > 0)
881 guess.append('-').append(ci->word.str, ci->pre_strip_len);
882 if (ci->suf_strip_len > 0)
883 guess.append('-').append(ci->word.str + ci->word.len - ci->suf_strip_len,
884 ci->suf_strip_len);
885 if (ci->suf_add && ci->suf_add[0])
886 guess.append('+').append(ci->suf_add, ci->suf_add_len);
887 guess.ensure_null_end();
888 real_speller->lang().fix_case(casep, guess.data(), guess.data());
889 guesses << ", " << oconv(guess.str());
890 ci = ci->next;
891 }
892 start = clock();
893 const AspellWordList * suggestions = 0;
894 if (suggest)
895 suggestions = aspell_speller_suggest(speller, word, -1);
896 finish = clock();
897 unsigned offset = mb_len(line0, token.offset + ignore);
898 if (suggestions && !aspell_word_list_empty(suggestions))
899 {
900 COUT.printf("& %s %u %u:", word,
901 aspell_word_list_size(suggestions), offset);
902 AspellStringEnumeration * els
903 = aspell_word_list_elements(suggestions);
904 if (options->retrieve_bool("reverse")) {
905 Vector<String> sugs;
906 sugs.reserve(aspell_word_list_size(suggestions));
907 while ( ( w = aspell_string_enumeration_next(els)) != 0)
908 sugs.push_back(w);
909 Vector<String>::reverse_iterator i = sugs.rbegin();
910 while (true) {
911 COUT.printf(" %s", i->c_str());
912 ++i;
913 if (i == sugs.rend()) break;
914 COUT.put(',');
915 }
916 } else {
917 while ( ( w = aspell_string_enumeration_next(els)) != 0) {
918 COUT.printf(" %s%s", w,
919 aspell_string_enumeration_at_end(els) ? "" : ",");
920 }
921 }
922 delete_aspell_string_enumeration(els);
923 if (include_guesses)
924 COUT.put(guesses);
925 COUT.put('\n');
926 } else {
927 if (guesses.empty())
928 COUT.printf("# %s %u\n", word, offset);
929 else
930 COUT.printf("? %s 0 %u: %s\n", word, offset,
931 guesses.c_str() + 2);
932 }
933 if (do_time)
934 COUT.printf(_("Suggestion Time: %f\n"),
935 (finish-start)/(double)CLOCKS_PER_SEC);
936 }
937 COUT.put('\n');
938 }
939 if (c == EOF) break;
940 }
941
942 delete_aspell_speller(speller);
943 }
944
945 ///////////////////////////
946 //
947 // check
948 //
949
950 enum UserChoice {None, Ignore, IgnoreAll, Replace, ReplaceAll,
951 Add, AddLower, Exit, Abort};
952
953 struct Mapping {
954 char primary[9];
955 UserChoice reverse[256];
956 void to_aspell();
957 void to_ispell();
operator []Mapping958 char & operator[] (UserChoice c) {return primary[c];}
operator []Mapping959 UserChoice & operator[] (char c)
960 {return reverse[static_cast<unsigned char>(c)];}
961 };
962
963 void abort_check();
964
965 void setup_display_conv();
966
check()967 void check()
968 {
969 String file_name;
970 String new_name;
971 FILE * in = 0;
972 FILE * out = 0;
973 Mapping mapping;
974 bool changed = false;
975
976 if (args.size() == 0) {
977 print_error(_("You must specify a file name."));
978 exit(-1);
979 } else if (args.size() > 1) {
980 print_error(_("Only one file name may be specified."));
981 exit(-1);
982 }
983
984 file_name = args[0];
985 new_name = file_name;
986 new_name += ".new";
987
988 in = fopen(file_name.c_str(), "r");
989 if (!in) {
990 print_error(_("Could not open the file \"%s\" for reading"), file_name);
991 exit(-1);
992 }
993
994 if (!options->have("mode"))
995 EXIT_ON_ERR(set_mode_from_extension(options, file_name));
996
997 String m = options->retrieve("keymapping");
998 if (m == "aspell")
999 mapping.to_aspell();
1000 else if (m == "ispell")
1001 mapping.to_ispell();
1002 else {
1003 print_error(_("Invalid keymapping: %s"), m);
1004 exit(-1);
1005 }
1006
1007 AspellCanHaveError * ret
1008 = new_aspell_speller(reinterpret_cast<AspellConfig *>(options.get()));
1009 if (aspell_error(ret)) {
1010 print_error(aspell_error_message(ret));
1011 exit(1);
1012 }
1013
1014 {
1015 struct stat st;
1016 fstat(fileno(in), &st);
1017 if (!S_ISREG(st.st_mode)) {
1018 print_error(_("\"%s\" is not a regular file"), file_name);
1019 exit(-1);
1020 }
1021 #ifdef USE_FILE_INO
1022 int fd = open(new_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, st.st_mode);
1023 if (fd >= 0) out = fdopen(fd, "w");
1024 #else
1025 out = fopen(new_name.c_str(), "w");
1026 #endif
1027 }
1028 if (!out) {
1029 print_error(_("Could not open the file \"%s\" for writing. File not saved."), file_name);
1030 exit(-1);
1031 }
1032
1033 setup_display_conv();
1034
1035 AspellSpeller * speller = to_aspell_speller(ret);
1036
1037 state = new CheckerString(speller,in,out,64);
1038
1039 word_choices = new Choices;
1040
1041 menu_choices = new Choices;
1042 menu_choices->push_back(Choice(mapping[Ignore], _("Ignore")));
1043 menu_choices->push_back(Choice(mapping[IgnoreAll], _("Ignore all")));
1044 menu_choices->push_back(Choice(mapping[Replace], _("Replace")));
1045 menu_choices->push_back(Choice(mapping[ReplaceAll], _("Replace all")));
1046 menu_choices->push_back(Choice(mapping[Add], _("Add")));
1047 menu_choices->push_back(Choice(mapping[AddLower], _("Add Lower")));
1048 menu_choices->push_back(Choice(mapping[Abort], _("Abort")));
1049 menu_choices->push_back(Choice(mapping[Exit], _("Exit")));
1050
1051 String word0, new_word;
1052 Vector<String> sug_con;
1053 StackPtr<StringMap> replace_list(new_string_map());
1054 const char * w;
1055
1056 begin_check();
1057
1058 while (state->next_misspelling()) {
1059
1060 char * word = state->get_real_word(word0);
1061
1062 //
1063 // check if it is in the replace list
1064 //
1065
1066 if ((w = replace_list->lookup(word)) != 0) {
1067 state->replace(w);
1068 continue;
1069 }
1070
1071 //
1072 // print the line with the misspelled word highlighted;
1073 //
1074
1075 display_misspelled_word();
1076
1077 //
1078 // print the suggestions and menu choices
1079 //
1080
1081 const AspellWordList * suggestions = aspell_speller_suggest(speller, word, -1);
1082 AspellStringEnumeration * els = aspell_word_list_elements(suggestions);
1083 sug_con.resize(0);
1084 while (sug_con.size() != 10
1085 && (w = aspell_string_enumeration_next(els)) != 0)
1086 sug_con.push_back(w);
1087 delete_aspell_string_enumeration(els);
1088
1089 // disable suspend
1090 unsigned int suggestions_size = sug_con.size();
1091 unsigned int suggestions_mid = suggestions_size / 2;
1092 if (suggestions_size % 2) suggestions_mid++; // if odd
1093 word_choices->resize(0);
1094 for (unsigned int j = 0; j != suggestions_mid; ++j) {
1095 word_choices->push_back(Choice('0' + j+1, sug_con[j]));
1096 if (j + suggestions_mid != suggestions_size)
1097 word_choices
1098 ->push_back(Choice(j+suggestions_mid+1 == 10
1099 ? '0'
1100 : '0' + j+suggestions_mid+1,
1101 sug_con[j+suggestions_mid]));
1102 }
1103 //enable suspend
1104 display_menu();
1105
1106 choice_prompt:
1107
1108 prompt("? ");
1109
1110 choice_loop:
1111
1112 //
1113 // Handle the users choice
1114 //
1115
1116 int choice;
1117 get_choice(choice);
1118
1119 if (choice == '0') choice = '9' + 1;
1120
1121 switch (mapping[choice]) {
1122 case Exit:
1123 goto exit_loop;
1124 case Abort: {
1125 prompt(_("Are you sure you want to abort (y/n)? "));
1126 get_choice(choice);
1127 /* TRANSLATORS: The user may input any of these characters to say "yes".
1128 MUST ONLY CONSIST OF ASCII CHARACTERS. */
1129 const char * yes_characters = _("Yy");
1130 if (strchr(yes_characters, choice) != 0)
1131 goto abort_loop;
1132 goto choice_prompt;
1133 }
1134 case Ignore:
1135 break;
1136 case IgnoreAll:
1137 aspell_speller_add_to_session(speller, word, -1);
1138 break;
1139 case Add:
1140 aspell_speller_add_to_personal(speller, word, -1);
1141 break;
1142 case AddLower:
1143 {
1144 // Emulate the c function add_to_personal, but add extra step to
1145 // convert word to lowercase. Yeah its a bit of a hack.
1146 Speller * sp = reinterpret_cast<Speller *>(speller);
1147 sp->temp_str_0.clear();
1148 sp->to_internal_->convert(word, -1, sp->temp_str_0);
1149 char * lower = sp->to_lower(sp->temp_str_0.mstr());
1150 PosibErr<void> ret = sp->add_to_personal(MutableString(lower));
1151 sp->err_.reset(ret.release_err());
1152 break;
1153 }
1154 case Replace:
1155 case ReplaceAll:
1156 // the string new_word is in the encoding of the document
1157 prompt(_("With: "));
1158 get_line(new_word);
1159 if (new_word.size() == 0)
1160 goto choice_prompt;
1161 if (new_word[0] >= '1' && new_word[0] < (char)suggestions_size + '1')
1162 new_word = sug_con[new_word[0]-'1'];
1163 state->replace(new_word);
1164 changed = true;
1165 if (mapping[choice] == ReplaceAll && (strcmp(word,new_word.str()) != 0))
1166 replace_list->replace(word, new_word);
1167 break;
1168 default:
1169 // the replasments are in the encoding of the document
1170 if (choice >= '1' && choice < (char)suggestions_size + '1') {
1171 state->replace(sug_con[choice-'1']);
1172 changed = true;
1173 } else {
1174 error(_("Sorry that is an invalid choice!"));
1175 goto choice_loop;
1176 }
1177 }
1178 }
1179 exit_loop:
1180 {
1181 aspell_speller_save_all_word_lists(speller);
1182 state.del(); // to close the file handles
1183 delete_aspell_speller(speller);
1184
1185 if (changed) {
1186
1187 bool keep_backup = options->retrieve_bool("backup");
1188 if (keep_backup) {
1189 String backup_name = file_name;
1190 backup_name += ".bak";
1191 rename_file(file_name, backup_name);
1192 }
1193 rename_file(new_name, file_name);
1194
1195 } else {
1196
1197 remove_file(new_name);
1198
1199 }
1200
1201 //end_check();
1202
1203 return;
1204 }
1205 abort_loop:
1206 {
1207 state->abort(); // to close the file handles
1208 delete_aspell_speller(speller);
1209
1210 remove_file(new_name);
1211
1212 return;
1213 }
1214 }
1215
1216 #define U (unsigned char)
1217
to_aspell()1218 void Mapping::to_aspell()
1219 {
1220 memset(this, 0, sizeof(Mapping));
1221 primary[Ignore ] = 'i';
1222 reverse[U'i'] = Ignore;
1223 reverse[U' '] = Ignore;
1224 reverse[U'\n'] = Ignore;
1225
1226 primary[IgnoreAll ] = 'I';
1227 reverse[U'I'] = IgnoreAll;
1228
1229 primary[Replace ] = 'r';
1230 reverse[U'r'] = Replace;
1231
1232 primary[ReplaceAll] = 'R';
1233 reverse[U'R'] = ReplaceAll;
1234
1235 primary[Add ] = 'a';
1236 reverse[U'A'] = Add;
1237 reverse[U'a'] = Add;
1238
1239 primary[AddLower ] = 'l';
1240 reverse[U'L'] = AddLower;
1241 reverse[U'l'] = AddLower;
1242
1243 primary[Abort ] = 'b';
1244 reverse[U'b'] = Abort;
1245 reverse[U'B'] = Abort;
1246 reverse[control('c')] = Abort;
1247
1248 primary[Exit ] = 'x';
1249 reverse[U'x'] = Exit;
1250 reverse[U'X'] = Exit;
1251 }
1252
to_ispell()1253 void Mapping::to_ispell()
1254 {
1255 memset(this, 0, sizeof(Mapping));
1256 primary[Ignore ] = ' ';
1257 reverse[U' '] = Ignore;
1258 reverse[U'\n'] = Ignore;
1259
1260 primary[IgnoreAll ] = 'A';
1261 reverse[U'A'] = IgnoreAll;
1262 reverse[U'a'] = IgnoreAll;
1263
1264 primary[Replace ] = 'R';
1265 reverse[U'R'] = ReplaceAll;
1266 reverse[U'r'] = Replace;
1267
1268 primary[ReplaceAll] = 'E';
1269 reverse[U'E'] = ReplaceAll;
1270 reverse[U'e'] = Replace;
1271
1272 primary[Add ] = 'I';
1273 reverse[U'I'] = Add;
1274 reverse[U'i'] = Add;
1275
1276 primary[AddLower ] = 'U';
1277 reverse[U'U'] = AddLower;
1278 reverse[U'u'] = AddLower;
1279
1280 primary[Abort ] = 'Q';
1281 reverse[U'Q'] = Abort;
1282 reverse[U'q'] = Abort;
1283 reverse[control('c')] = Abort;
1284
1285 primary[Exit ] = 'X';
1286 reverse[U'X'] = Exit;
1287 reverse[U'x'] = Exit;
1288 }
1289 #undef U
1290
1291 ///////////////////////////
1292 //
1293 // list
1294 //
1295
list()1296 void list()
1297 {
1298 AspellCanHaveError * ret
1299 = new_aspell_speller(reinterpret_cast<AspellConfig *>(options.get()));
1300 if (aspell_error(ret)) {
1301 print_error(aspell_error_message(ret));
1302 exit(1);
1303 }
1304 AspellSpeller * speller = to_aspell_speller(ret);
1305
1306 state = new CheckerString(speller,stdin,0,64);
1307
1308 String word;
1309
1310 while (state->next_misspelling()) {
1311
1312 state->get_real_word(word);
1313 COUT.printl(word);
1314
1315 }
1316
1317 state.del(); // to close the file handles
1318 delete_aspell_speller(speller);
1319 }
1320
1321 ///////////////////////////
1322 //
1323 // convt
1324 //
1325
convt()1326 void convt()
1327 {
1328 Conv conv;
1329 String buf1, buf2;
1330 const char * from = fix_encoding_str(args[0], buf1);
1331 const char * to = fix_encoding_str(args[1], buf2);
1332 Normalize norm = NormNone;
1333 if (strcmp(from, "utf-8") == 0 && strcmp(to, "utf-8") != 0)
1334 norm = NormFrom;
1335 else if (strcmp(from, "utf-8") != 0 && strcmp(to, "utf-8") == 0)
1336 norm = NormTo;
1337 if (args.size() > 2) {
1338 for (String::iterator i = args[2].begin(); i != args[2].end(); ++i)
1339 *i = asc_tolower(*i);
1340 options->replace("normalize", "true");
1341 if (args[2] == "none")
1342 options->replace("normalize", "false");
1343 else if (args[2] == "internal")
1344 options->replace("norm-strict", "false");
1345 else if (args[2] == "strict")
1346 options->replace("norm-strict", "true");
1347 else
1348 EXIT_ON_ERR(options->replace("norm-form", args[2]));
1349 }
1350 EXIT_ON_ERR(conv.setup(*options, args[0], args[1], norm));
1351 String line;
1352 while (CIN.getline(line))
1353 COUT.printl(conv(line));
1354 }
1355
normlz()1356 void normlz()
1357 {
1358 options->replace("normalize", "true");
1359 const char * from = args.size() < 3 ? "utf-8" : args[0].str();
1360 const char * to = args.size() < 3 ? "utf-8" : args[2].str();
1361 const char * intr = args.size() < 3 ? args[0].str() : args[1].str();
1362 String * form = (args.size() == 2 ? &args[1]
1363 : args.size() == 4 ? &args[3]
1364 : 0);
1365 Normalize decode_norm = NormTo;
1366 if (form) {
1367 for (String::iterator i = form->begin(); i != form->end(); ++i)
1368 *i = asc_tolower(*i);
1369 if (*form == "internal") {
1370 options->replace("norm-strict", "false");
1371 decode_norm = NormNone;
1372 } else if (*form == "strict") {
1373 options->replace("norm-strict", "true");
1374 decode_norm = NormNone;
1375 }
1376 if (decode_norm == NormTo) EXIT_ON_ERR(options->replace("norm-form", *form));
1377 }
1378 Conv encode,decode;
1379 EXIT_ON_ERR(encode.setup(*options, from, intr, NormFrom));
1380 EXIT_ON_ERR(decode.setup(*options, intr, to, decode_norm));
1381 String line;
1382 while (CIN.getline(line))
1383 COUT.printl(decode(encode(line)));
1384 }
1385
1386 ///////////////////////////
1387 //
1388 // filter
1389 //
1390
filter()1391 void filter()
1392 {
1393 line_buffer();
1394
1395 String enc = options->retrieve("encoding");
1396 if (enc == "none")
1397 enc="utf-8";
1398 String encoding;
1399 fix_encoding_str(enc, encoding);
1400
1401 EXIT_ON_ERR_SET(Decode::get_new(encoding, options.get()),
1402 StackPtr<Decode>, iconv);
1403 EXIT_ON_ERR_SET(Encode::get_new(encoding, options.get()),
1404 StackPtr<Encode>, oconv);
1405
1406 StackPtr<Filter> filter(new Filter);
1407 EXIT_ON_ERR(setup_filter(*filter, options.get(), true, true, false));
1408
1409 CharVector buf;
1410 FilterCharVector proc_str;
1411
1412 int c;
1413 for (;;) {
1414 buf.clear();
1415 while (c = getchar(), c != '\n' && c != EOF)
1416 buf.push_back(static_cast<char>(c));
1417 if (c == EOF) break;
1418 buf.push_back('\n');
1419 buf.push_back('\0');
1420 proc_str.clear();
1421 iconv->decode(buf.data(), buf.size()-1, proc_str);
1422 proc_str.append(0);
1423 FilterChar * begin = proc_str.pbegin();
1424 FilterChar * end = proc_str.pend() - 1;
1425 filter->process(begin, end);
1426 buf.clear();
1427 oconv->encode(begin,end,buf);
1428 if (buf.empty() || buf.back() != '\n')
1429 buf.push_back('\n');
1430 COUT.write(buf);
1431 }
1432 }
1433
1434 ///////////////////////////
1435 //
1436 // print_ver
1437 //
1438
print_ver()1439 void print_ver () {
1440 printf("@(#) International Ispell Version 3.1.20 "
1441 "(but really Aspell %s)\n", aspell_version_string());
1442 }
1443
1444 ///////////////////////////////////////////////////////////////////////
1445 //
1446 // These functions use implementation details of the default speller
1447 // module
1448 //
1449
1450 ///////////////////////////
1451 //
1452 // clean
1453 //
1454
clean()1455 void clean()
1456 {
1457 using namespace aspeller;
1458
1459 bool strict = args.size() != 0 && args[0] == "strict";
1460
1461 Config * config = options;
1462
1463 CachePtr<Language> lang;
1464 find_language(*config);
1465 PosibErr<Language *> res = new_language(*config);
1466 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1467 lang.reset(res.data);
1468 IstreamEnumeration in(CIN);
1469 WordListIterator wl_itr(&in, lang, &CERR);
1470 config->replace("validate-words", "true");
1471 config->replace("validate-affixes", "true");
1472 if (!strict)
1473 config->replace("clean-words", "true");
1474 config->replace("clean-affixes", "true");
1475 config->replace("skip-invalid-words", "true");
1476 wl_itr.init(*config);
1477 Conv oconv, oconv2;
1478 if (config->have("encoding")) {
1479 EXIT_ON_ERR(oconv.setup(*config, lang->charmap(), config->retrieve("encoding"), NormTo));
1480 EXIT_ON_ERR(oconv2.setup(*config, lang->charmap(), config->retrieve("encoding"), NormTo));
1481 } else {
1482 EXIT_ON_ERR(oconv.setup(*config, lang->charmap(), lang->data_encoding(), NormTo));
1483 EXIT_ON_ERR(oconv2.setup(*config, lang->charmap(), lang->data_encoding(), NormTo));
1484 }
1485 while (wl_itr.adv()) {
1486 if (*wl_itr->aff.str)
1487 COUT.printf("%s/%s\n", oconv(wl_itr->word), oconv2(wl_itr->aff));
1488 else
1489 COUT.printl(oconv(wl_itr->word));
1490 }
1491 }
1492
1493 ///////////////////////////
1494 //
1495 // master
1496 //
1497
dump(aspeller::Dict * lws,Convert * conv)1498 void dump (aspeller::Dict * lws, Convert * conv)
1499 {
1500 using namespace aspeller;
1501
1502 switch (lws->basic_type) {
1503 case Dict::basic_dict:
1504 {
1505 Dictionary * ws = static_cast<Dictionary *>(lws);
1506 StackPtr<WordEntryEnumeration> els(ws->detailed_elements());
1507 WordEntry * wi;
1508 while (wi = els->next(), wi) {
1509 wi->write(COUT,*ws->lang(), conv);
1510 COUT << '\n';
1511 }
1512 }
1513 break;
1514 case Dict::multi_dict:
1515 {
1516 StackPtr<DictsEnumeration> els(lws->dictionaries());
1517 Dict * ws;
1518 while (ws = els->next(), ws)
1519 dump (ws, conv);
1520 }
1521 break;
1522 default:
1523 abort();
1524 }
1525 }
1526
master()1527 void master () {
1528 using namespace aspeller;
1529
1530 if (args.size() != 0) {
1531 options->replace("master", args[0].c_str());
1532 }
1533
1534 Config * config = options;
1535
1536 if (action == do_create) {
1537
1538 find_language(*config);
1539 EXIT_ON_ERR(create_default_readonly_dict
1540 (new IstreamEnumeration(CIN),
1541 *config));
1542
1543 } else if (action == do_merge) {
1544
1545 print_error(_("Can't merge a master word list yet. Sorry."));
1546 exit (1);
1547
1548 } else if (action == do_dump) {
1549
1550 EXIT_ON_ERR_SET(add_data_set(config->retrieve("master-path"), *config), Dict *, d);
1551 StackPtr<Convert> conv(setup_conv(d->lang(), config));
1552 dump(d, conv);
1553 }
1554 }
1555
1556 ///////////////////////////
1557 //
1558 // personal
1559 //
1560
personal()1561 void personal () {
1562 using namespace aspeller;
1563
1564 if (args.size() != 0) {
1565 EXIT_ON_ERR(options->replace("personal", args[0]));
1566 }
1567 options->replace("module", "aspeller");
1568 if (action == do_create || action == do_merge) {
1569 CERR << _("Sorry \"create/merge personal\" is currently unimplemented.\n");
1570 exit(3);
1571
1572 // FIXME
1573 #if 0
1574 StackPtr<Speller> speller(new_speller(options));
1575
1576 if (action == do_create) {
1577 if (file_exists(speller->config()->retrieve("personal-path"))) {
1578 print_error(_("Sorry I won't overwrite \"%s\""),
1579 speller->config()->retrieve("personal-path"));
1580 exit (1);
1581 }
1582 speller->personal_word_list().data->clear();
1583 }
1584
1585 String word;
1586 while (CIN >> word)
1587 speller->add_to_personal(word);
1588
1589 speller->save_all_word_lists();
1590 #endif
1591
1592 } else { // action == do_dump
1593
1594 // FIXME: This is currently broken
1595
1596 Config * config = options;
1597 Dictionary * per = new_default_writable_dict(*config);
1598 EXIT_ON_ERR(per->load(config->retrieve("personal-path"), *config));
1599 StackPtr<WordEntryEnumeration> els(per->detailed_elements());
1600 StackPtr<Convert> conv(setup_conv(per->lang(), config));
1601
1602 WordEntry * wi;
1603 while (wi = els->next(), wi) {
1604 wi->write(COUT,*(per->lang()), conv);
1605 COUT.put('\n');
1606 }
1607 delete per;
1608 }
1609 }
1610
1611 ///////////////////////////
1612 //
1613 // repl
1614 //
1615
repl()1616 void repl() {
1617 using namespace aspeller;
1618
1619 if (args.size() != 0) {
1620 options->replace("repl", args[0].c_str());
1621 }
1622
1623 if (action == do_create || action == do_merge) {
1624
1625 CERR << _("Sorry \"create/merge repl\" is currently unimplemented.\n");
1626 exit(3);
1627
1628 // FIXME
1629 #if 0
1630 SpellerImpl speller(options);
1631
1632 if (action == do_create) {
1633 if (file_exists(speller->config()->retrieve("repl-path"))) {
1634 print_error(_("Sorry I won't overwrite \"%s\""),
1635 speller->config()->retrieve("repl-path"));
1636 exit (1);
1637 }
1638 speller->personal_repl().clear();
1639 }
1640
1641 try {
1642 String word,repl;
1643
1644 while (true) {
1645 get_word_pair(word,repl,':');
1646 EXIT_ON_ERR(speller->store_repl(word,repl,false));
1647 }
1648
1649 } catch (bad_cin) {}
1650
1651 EXIT_ON_ERR(speller->personal_repl().synchronize());
1652
1653 #endif
1654
1655 } else if (action == do_dump) {
1656
1657 // FIXME: This is currently broken
1658
1659 ReplacementDict * repl = new_default_replacement_dict(*options);
1660 repl->load(options->retrieve("repl-path"), *options);
1661 StackPtr<WordEntryEnumeration> els(repl->detailed_elements());
1662
1663 WordEntry * rl = 0;
1664 WordEntry words;
1665 Conv conv(setup_conv(repl->lang(), options));
1666 while ((rl = els->next())) {
1667 repl->repl_lookup(*rl, words);
1668 do {
1669 COUT << conv(rl->word) << ": " << conv(words.word) << "\n";
1670 } while (words.adv());
1671 }
1672 delete repl;
1673 }
1674 }
1675
1676 //////////////////////////
1677 //
1678 // soundslike
1679 //
1680
soundslike()1681 void soundslike() {
1682 using namespace aspeller;
1683 CachePtr<Language> lang;
1684 find_language(*options);
1685 PosibErr<Language *> res = new_language(*options);
1686 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1687 lang.reset(res.data);
1688 Conv iconv(setup_conv(options, lang));
1689 Conv oconv(setup_conv(lang, options));
1690 String word;
1691 String sl;
1692 line_buffer();
1693 while (CIN.getline(word)) {
1694 const char * w = iconv(word);
1695 lang->LangImpl::to_soundslike(sl, w);
1696 printf("%s\t%s\n", word.str(), oconv(sl));
1697 }
1698 }
1699
1700 //////////////////////////
1701 //
1702 // munch
1703 //
1704
munch()1705 void munch()
1706 {
1707 using namespace aspeller;
1708 CachePtr<Language> lang;
1709 find_language(*options);
1710 PosibErr<Language *> res = new_language(*options);
1711 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1712 lang.reset(res.data);
1713 Conv iconv(setup_conv(options, lang));
1714 Conv oconv(setup_conv(lang, options));
1715 String word;
1716 GuessInfo gi;
1717 line_buffer();
1718 while (CIN.getline(word)) {
1719 lang->munch(iconv(word), &gi);
1720 COUT << word;
1721 for (const aspeller::CheckInfo * ci = gi.head; ci; ci = ci->next)
1722 {
1723 COUT << ' ' << oconv(ci->word.str, ci->word.len) << '/';
1724 if (ci->pre_flag != 0) COUT << oconv(static_cast<char>(ci->pre_flag));
1725 if (ci->suf_flag != 0) COUT << oconv(static_cast<char>(ci->suf_flag));
1726 }
1727 COUT << '\n';
1728 }
1729 }
1730
1731 //////////////////////////
1732 //
1733 // expand
1734 //
1735
expand()1736 void expand()
1737 {
1738 int level = 1;
1739 if (args.size() > 0)
1740 level = atoi(args[0].c_str()); //FIXME: More verbose
1741 int limit = INT_MAX;
1742 if (args.size() > 1)
1743 limit = atoi(args[1].c_str());
1744
1745 using namespace aspeller;
1746 CachePtr<Language> lang;
1747 find_language(*options);
1748 PosibErr<Language *> res = new_language(*options);
1749 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1750 lang.reset(res.data);
1751 Conv iconv(setup_conv(options, lang));
1752 Conv oconv(setup_conv(lang, options));
1753 String word, buf;
1754 ObjStack exp_buf;
1755 WordAff * exp_list;
1756 line_buffer();
1757 while (CIN.getline(word)) {
1758 buf = word;
1759 char * w = iconv(buf.mstr(), buf.size());
1760 char * af = strchr(w, '/');
1761 size_t s;
1762 if (af != 0) {
1763 s = af - w;
1764 *af++ = '\0';
1765 } else {
1766 s = strlen(w);
1767 af = w + s;
1768 }
1769 exp_buf.reset();
1770 exp_list = lang->expand(w, af, exp_buf, limit);
1771 if (level <= 2) {
1772 if (level == 2)
1773 COUT << word << ' ';
1774 WordAff * p = exp_list;
1775 while (p) {
1776 COUT << oconv(p->word);
1777 if (limit < INT_MAX && p->aff[0]) COUT << '/' << oconv((const char *)p->aff);
1778 p = p->next;
1779 if (p) COUT << ' ';
1780 }
1781 COUT << '\n';
1782 } else if (level >= 3) {
1783 double ratio = 0;
1784 if (level >= 4) {
1785 for (WordAff * p = exp_list; p; p = p->next)
1786 ratio += p->word.size;
1787 ratio /= exp_list->word.size; // it is assumed the first
1788 // expansion is just the root
1789 }
1790 for (WordAff * p = exp_list; p; p = p->next) {
1791 COUT << word << ' ' << oconv(p->word);
1792 if (limit < INT_MAX && p->aff[0]) COUT << '/' << oconv((const char *)p->aff);
1793 if (level >= 4) COUT.printf(" %f\n", ratio);
1794 else COUT << '\n';
1795 }
1796 }
1797 }
1798 }
1799
1800 //////////////////////////
1801 //
1802 // combine
1803 //
1804
combine_aff(String & aff,const char * app)1805 static void combine_aff(String & aff, const char * app)
1806 {
1807 for (; *app; ++app) {
1808 if (!memchr(aff.c_str(),*app,aff.size()))
1809 aff.push_back(*app);
1810 }
1811 }
1812
print_wordaff(const String & base,const String & affs,Conv & oconv)1813 static void print_wordaff(const String & base, const String & affs, Conv & oconv)
1814 {
1815 if (base.empty()) return;
1816 COUT << oconv(base);
1817 if (affs.empty())
1818 COUT << '\n';
1819 else
1820 COUT.printf("/%s\n", oconv(affs));
1821 }
1822
lower_equal(aspeller::Language * l,ParmString a,ParmString b)1823 static bool lower_equal(aspeller::Language * l, ParmString a, ParmString b)
1824 {
1825 if (a.size() != b.size()) return false;
1826 if (l->to_lower(a[0]) != l->to_lower(b[0])) return false;
1827 return memcmp(a + 1, b + 1, a.size() - 1) == 0;
1828 }
1829
combine()1830 void combine()
1831 {
1832 using namespace aspeller;
1833 CachePtr<Language> lang;
1834 find_language(*options);
1835 PosibErr<Language *> res = new_language(*options);
1836 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1837 lang.reset(res.data);
1838 Conv iconv(setup_conv(options, lang));
1839 Conv oconv(setup_conv(lang, options));
1840 String word;
1841 String base;
1842 String affs;
1843 line_buffer();
1844 while (CIN.getline(word)) {
1845 word = iconv(word);
1846
1847 CharVector buf; buf.append(word.c_str(), word.size() + 1);
1848 char * w = buf.data();
1849 char * af = strchr(w, '/');
1850 size_t s;
1851 if (af != 0) {
1852 s = af - w;
1853 *af++ = '\0';
1854 } else {
1855 s = strlen(w);
1856 af = w + s;
1857 }
1858
1859 if (lower_equal(lang, base, w)) {
1860 if (lang->is_lower(base.str())) {
1861 combine_aff(affs, af);
1862 } else {
1863 base = w;
1864 combine_aff(affs, af);
1865 }
1866 } else {
1867 print_wordaff(base, affs, oconv);
1868 base = w;
1869 affs = af;
1870 }
1871
1872 }
1873 print_wordaff(base, affs, oconv);
1874 }
1875
1876 //////////////////////////
1877 //
1878 // munch list
1879 //
1880
1881 void munch_list_simple();
1882 void munch_list_complete(bool, bool);
1883
munch_list()1884 void munch_list()
1885 {
1886 bool simple = false;
1887 bool multi = false;
1888 bool simplify = true;
1889
1890 for (unsigned i = 0; i < args.size(); ++i) {
1891 if (args[i] == "simple") simple = true;
1892 else if (args[i] == "single") multi = false;
1893 else if (args[i] == "multi") multi = true;
1894 else if (args[i] == "keep") simplify = false;
1895 else
1896 {
1897 print_error(_("\"%s\" is not a valid flag for the \"munch-list\" command."),
1898 args[i]);
1899 exit(1);
1900 }
1901 }
1902 if (simple)
1903 munch_list_simple();
1904 else
1905 munch_list_complete(multi, simplify);
1906 }
1907
1908 //
1909 // munch list (simple version)
1910 //
1911
1912 // This version works the same way as the myspell "munch" program.
1913 // However, because the results depends on the hash table used and the
1914 // order of the word list it wonn't produce identical results.
1915
1916 struct SML_WordEntry {
1917 const char * word;
1918 char * aff;
1919 bool keep; // boolean
SML_WordEntrySML_WordEntry1920 SML_WordEntry(const char * w = 0) : word(w), aff(0), keep(false) {}
1921 };
1922
1923 struct SML_Parms {
1924 typedef SML_WordEntry Value;
1925 typedef const char * Key;
1926 static const bool is_multi = false;
1927 acommon::hash<const char *> hash;
equalSML_Parms1928 bool equal(Key x, Key y) {return strcmp(x,y) == 0;}
keySML_Parms1929 Key key(const Value & v) {return v.word;}
1930 };
1931
1932 typedef HashTable<SML_Parms> SML_Table;
1933
add_affix(SML_Table::iterator b,char aff)1934 static inline void add_affix(SML_Table::iterator b, char aff)
1935 {
1936 char * p = b->aff;
1937 if (p) {while (*p) {if (*p == aff) return; ++p;}}
1938 int s = p - b->aff;
1939 b->aff = (char *)realloc(b->aff, s + 2);
1940 b->aff[s + 0] = aff;
1941 b->aff[s + 1] = '\0';
1942 }
1943
munch_list_simple()1944 void munch_list_simple()
1945 {
1946 using namespace aspeller;
1947 CachePtr<Language> lang;
1948 find_language(*options);
1949 PosibErr<Language *> res = new_language(*options);
1950 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1951 lang.reset(res.data);
1952 Conv iconv(setup_conv(options, lang));
1953 Conv oconv(setup_conv(lang, options));
1954 String word, buf;
1955 ObjStack exp_buf;
1956 WordAff * exp_list;
1957 GuessInfo gi;
1958 SML_Table table;
1959 ObjStack table_buf;
1960
1961 // add words to dictionary
1962 while (CIN.getline(word)) {
1963 buf = word;
1964 char * w = iconv(buf.mstr(), buf.size());
1965 char * af = strchr(w, '/');
1966 size_t s;
1967 if (af != 0) {
1968 s = af - w;
1969 *af++ = '\0';
1970 } else {
1971 s = strlen(w);
1972 af = w + s;
1973 }
1974 exp_buf.reset();
1975 exp_list = lang->expand(w, af, exp_buf);
1976 for (WordAff * q = exp_list; q; q = q->next) {
1977 table.insert(SML_WordEntry(table_buf.dup(q->word)));
1978 }
1979 }
1980
1981 // now try to munch each word in the dictionary
1982 SML_Table::iterator p = table.begin();
1983 SML_Table::iterator end = table.end();
1984 String flags;
1985 for (; p != end; ++p)
1986 {
1987 const aspeller::CheckInfo * best = 0;
1988 unsigned min_base_size = INT_MAX;
1989 lang->munch(p->word, &gi);
1990 const aspeller::CheckInfo * ci = gi.head;
1991 while (ci)
1992 { {
1993 // check if the base word is in the dictionary
1994 SML_Table::iterator b = table.find(ci->word.str);
1995 if (b == table.end()) goto cont;
1996
1997 // check if all the words once expanded are in the dictionary
1998 // this included the exiting flags due to pre-suf cross products
1999 if (b->aff) flags = b->aff;
2000 else flags.clear();
2001 if (ci->pre_flag != 0) flags += ci->pre_flag;
2002 if (ci->suf_flag != 0) flags += ci->suf_flag;
2003 exp_buf.reset();
2004 exp_list = lang->expand(ci->word.str, flags, exp_buf);
2005 for (WordAff * q = exp_list; q; q = q->next) {
2006 if (!table.have(q->word)) goto cont;
2007 }
2008
2009 // the base word and flags are valid, now keep the one with the
2010 // smallest base word
2011 if (ci->word.len < min_base_size) {
2012 min_base_size = ci->word.len;
2013 best = ci;
2014 }
2015
2016 } cont:
2017 ci = ci->next;
2018 }
2019 // now add the base to the keep list if one exists
2020 // otherwise just keep the original word
2021 if (best) {
2022 SML_Table::iterator b = table.find(best->word.str);
2023 assert(b != table.end());
2024 if (best->pre_flag) add_affix(b, best->pre_flag);
2025 if (best->suf_flag) add_affix(b, best->suf_flag);
2026 b->keep = true;
2027 } else {
2028 p->keep = true;
2029 }
2030 }
2031
2032 // Print the entries in the table marked as "to keep"
2033 p = table.begin();
2034 for (; p != end; ++p)
2035 {
2036 if (p->keep) {
2037 COUT << oconv(p->word);
2038 if (p->aff) {
2039 COUT << '/' << oconv(p->aff);
2040 }
2041 COUT << '\n';
2042 }
2043 }
2044
2045 p = table.begin();
2046 for (; p != end; ++p)
2047 {
2048 if (p->aff) free(p->aff);
2049 p->aff = 0;
2050 }
2051 }
2052
2053 //
2054 // munch list (complete version)
2055 //
2056 //
2057 // This version will produce a smaller list than the simple version.
2058 // It is very close to the optimum result.
2059 //
2060
2061 //
2062 // Hash table to store the words
2063 //
2064
2065 struct CML_Entry {
2066 const char * word;
2067 char * aff;
2068 CML_Entry * parent;
2069 CML_Entry * next;
2070 int rank;
CML_EntryCML_Entry2071 CML_Entry(const char * w = 0) : word(w), aff(0), parent(0), next(0), rank(0) {}
2072 };
2073
2074 struct CML_Parms {
2075 typedef CML_Entry Value;
2076 typedef const char * Key;
2077 static const bool is_multi = true;
2078 acommon::hash<const char *> hash;
equalCML_Parms2079 bool equal(Key x, Key y) {return strcmp(x,y) == 0;}
keyCML_Parms2080 Key key(const Value & v) {return v.word;}
2081 };
2082
2083 typedef HashTable<CML_Parms> CML_Table;
2084
2085 //
2086 // add an affix to a word but keep the prefixes and suffixes separate
2087 //
2088
add_affix(CML_Table::iterator b,char aff,bool prefix)2089 static void add_affix(CML_Table::iterator b, char aff, bool prefix)
2090 {
2091 char * p = b->aff;
2092 int s = 3;
2093 if (p) {
2094 while (*p) {
2095 if (*p == aff) return;
2096 ++p;
2097 }
2098 s = (p - b->aff) + 2;
2099 }
2100 char * tmp = (char *)malloc(s);
2101 p = b->aff;
2102 char * q = tmp;
2103 if (p) {while (*p != '/') *q++ = *p++;}
2104 if (prefix) *q++ = aff;
2105 *q++ = '/';
2106 if (p) {p++; while (*p != '\0') *q++ = *p++;}
2107 if (!prefix) *q++ = aff;
2108 *q++ = '\0';
2109 assert(q - tmp == s);
2110 if (b->aff) free(b->aff);
2111 b->aff = tmp;
2112 }
2113
2114 //
2115 // Standard disjoint set algo with union by rank and path compression
2116 //
2117
link(CML_Entry * x,CML_Entry * y)2118 static void link(CML_Entry * x, CML_Entry * y)
2119 {
2120 if (x == y) return;
2121 if (x->rank > y->rank) {
2122 y->parent = x;
2123 } else {
2124 x->parent = y;
2125 if (x->rank == y->rank) y->rank++;
2126 }
2127 }
2128
find_set(CML_Entry * x)2129 static CML_Entry * find_set (CML_Entry * x)
2130 {
2131 if (x->parent)
2132 return x->parent = find_set(x->parent);
2133 else
2134 return x;
2135 }
2136
2137 //
2138 // Stuff to manage prefix-suffix combinations
2139 //
2140
2141 struct PreSuf {
2142 String pre;
2143 String suf;
getPreSuf2144 String & get(int i) {return i == 0 ? pre : suf;}
getPreSuf2145 const String & get(int i) const {return i == 0 ? pre : suf;}
PreSufPreSuf2146 PreSuf() : next(0) {}
2147 PreSuf * next;
2148 };
2149
2150 class PreSufList {
2151 public:
2152 PreSuf * head;
PreSufList()2153 PreSufList() : head(0) {}
add(PreSuf * to_add)2154 void add(PreSuf * to_add) {
2155 to_add->next = head;
2156 head = to_add;
2157 }
clear()2158 void clear() {
2159 while (head) {
2160 PreSuf * tmp = head;
2161 head = head->next;
2162 delete tmp;
2163 }
2164 }
transfer(PreSufList & other)2165 void transfer(PreSufList & other) {
2166 clear();
2167 head = other.head;
2168 other.head = 0;
2169 }
~PreSufList()2170 ~PreSufList() {
2171 clear();
2172 }
2173 };
2174
2175
2176 // Example of usage:
2177 // combine(in, res, 0)
2178 // Pre: in = [(ab, c) (ab, d) (c, de) (c, ef)]
2179 // Post: res = [(ab, cd), (c, def)]
combine(const PreSufList & in,PreSufList & res,int which)2180 static void combine(const PreSufList & in, PreSufList & res, int which)
2181 {
2182 const PreSuf * i = in.head;
2183 while (i) { {
2184 const String & s = i->get(which);
2185 for (const PreSuf * j = in.head; j != i; j = j->next) {
2186 if (j->get(which) == s) goto cont;
2187 }
2188 PreSuf * tmp = new PreSuf;
2189 tmp->pre = i->pre;
2190 tmp->suf = i->suf;
2191 String & b = tmp->get(!which);
2192 for (const PreSuf * j = i->next; j; j = j->next) {
2193 if (j->get(which) != s) continue;
2194 const String & a = j->get(!which);
2195 for (String::const_iterator x = a.begin(); x != a.end(); ++x) {
2196 if (memchr(b.data(), *x, b.size())) continue;
2197 b += *x;
2198 }
2199 }
2200 res.add(tmp);
2201 } cont:
2202 i = i->next;
2203 }
2204 }
2205
2206 //
2207 // Stuff used when pruning the list of base words
2208 //
2209
2210 struct Expansion {
2211 const char * word;
2212 char * aff; // modifying this will modify the affix entry in the hash table
2213 std::vector<bool> exp;
2214 std::vector<bool> orig_exp;
2215 };
2216
2217 // static void dump(const Vector<Expansion *> & working,
2218 // const Vector<CML_Table::iterator> & entries)
2219 // {
2220 // for (unsigned i = 0; i != working.size(); ++i) {
2221 // if (!working[i]) continue;
2222 // CERR.printf("%s/%s ", working[i]->word, working[i]->aff);
2223 // for (unsigned j = 0; j != working[i]->exp.size(); ++j) {
2224 // if (working[i]->exp[j])
2225 // CERR.printf("%s ", entries[j]->word);
2226 // }
2227 // CERR.put('\n');
2228 // }
2229 // CERR.put('\n');
2230 // }
2231
2232 // standard set algorithms on a bit vector
2233
subset(const std::vector<bool> & smaller,const std::vector<bool> & larger)2234 static bool subset(const std::vector<bool> & smaller,
2235 const std::vector<bool> & larger)
2236 {
2237 assert(smaller.size() == larger.size());
2238 unsigned s = larger.size();
2239 for (unsigned i = 0; i != s; ++i) {
2240 if (smaller[i] && !larger[i]) return false;
2241 }
2242 return true;
2243 }
2244
merge(std::vector<bool> & x,const std::vector<bool> & y)2245 static void merge(std::vector<bool> & x, const std::vector<bool> & y)
2246 {
2247 assert(x.size() == y.size());
2248 unsigned s = x.size();
2249 for (unsigned i = 0; i != s; ++i) {
2250 if (y[i]) x[i] = true;
2251 }
2252 }
2253
purge(std::vector<bool> & x,const std::vector<bool> & y)2254 static void purge(std::vector<bool> & x, const std::vector<bool> & y)
2255 {
2256 assert(x.size() == y.size());
2257 unsigned s = x.size();
2258 for (unsigned i = 0; i != s; ++i) {
2259 if (y[i]) x[i] = false;
2260 }
2261 }
2262
count(const std::vector<bool> & x)2263 static inline unsigned count(const std::vector<bool> & x) {
2264 unsigned c = 0;
2265 for (unsigned i = 0; i != x.size(); ++i) {
2266 if (x[i]) ++c;
2267 }
2268 return c;
2269 }
2270
2271 //
2272
2273 struct WorkingLt {
operator ()WorkingLt2274 bool operator() (Expansion * x, Expansion * y) {
2275
2276 // LARGEST number of expansions
2277 unsigned x_s = count(x->exp);
2278 unsigned y_s = count(y->exp);
2279 if (x_s != y_s) return x_s > y_s;
2280
2281 // SMALLEST base word
2282 x_s = strlen(x->word);
2283 y_s = strlen(y->word);
2284 if (x_s != y_s) return x_s < y_s;
2285
2286 // LARGEST affix string
2287 x_s = strlen(x->aff);
2288 y_s = strlen(y->aff);
2289 if (x_s != y_s) return x_s > y_s;
2290
2291 //
2292 int cmp = strcmp(x->word, y->word);
2293 if (cmp != 0) return cmp < 0;
2294
2295 //
2296 cmp = strcmp(x->aff, y->aff);
2297 return cmp < 0;
2298 }
2299 };
2300
2301 //
2302 // Finally the function that does the real work
2303 //
2304
munch_list_complete(bool multi,bool simplify)2305 void munch_list_complete(bool multi, bool simplify)
2306 {
2307 using namespace aspeller;
2308 CachePtr<Language> lang;
2309 find_language(*options);
2310 PosibErr<Language *> res = new_language(*options);
2311 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
2312 lang.reset(res.data);
2313 Conv iconv(setup_conv(options, lang));
2314 Conv oconv(setup_conv(lang, options));
2315 String word, buf;
2316 ObjStack exp_buf;
2317 WordAff * exp_list;
2318 GuessInfo gi;
2319 CML_Table table;
2320 ObjStack table_buf;
2321
2322 // add words to dictionary
2323 while (CIN.getline(word)) {
2324 buf = word;
2325 char * w = iconv(buf.mstr(), buf.size());
2326 char * af = strchr(w, '/');
2327 size_t s;
2328 if (af != 0) {
2329 s = af - w;
2330 *af++ = '\0';
2331 } else {
2332 s = strlen(w);
2333 af = w + s;
2334 }
2335 exp_buf.reset();
2336 exp_list = lang->expand(w, af, exp_buf);
2337 for (WordAff * q = exp_list; q; q = q->next) {
2338 if (!table.have(q->word)) // since it is a multi hash table
2339 table.insert(CML_Entry(table_buf.dup(q->word))).first;
2340 }
2341 }
2342
2343 // Now try to munch each word in the dictionary. This will also
2344 // group the base words into disjoint sets based on there expansion.
2345 CML_Table::iterator p = table.begin();
2346 CML_Table::iterator end = table.end();
2347 String flags;
2348 for (; p != end; ++p)
2349 {
2350 lang->munch(p->word, &gi, false);
2351 const aspeller::CheckInfo * ci = gi.head;
2352 while (ci)
2353 { {
2354 // check if the base word is in the dictionary
2355 CML_Table::iterator b = table.find(ci->word.str);
2356 if (b == table.end()) goto cont;
2357
2358 // check if all the words once expanded are in the dictionary
2359 char flags[2];
2360 assert(!(ci->pre_flag && ci->suf_flag));
2361 if (ci->pre_flag != 0) flags[0] = ci->pre_flag;
2362 else if (ci->suf_flag != 0) flags[0] = ci->suf_flag;
2363 flags[1] = '\0';
2364 exp_buf.reset();
2365 exp_list = lang->expand(ci->word.str, flags, exp_buf);
2366 for (WordAff * q = exp_list; q; q = q->next) {
2367 if (!table.have(q->word)) goto cont;
2368 }
2369
2370 // all the expansions are in the dictionary now add the affix to
2371 // the base word and figure out which disjoint set it belongs to
2372 add_affix(b, flags[0], ci->pre_flag != 0);
2373 CML_Entry * bs = find_set(&*b);
2374 for (WordAff * q = exp_list; q; q = q->next) {
2375 CML_Table::iterator w = table.find(q->word);
2376 assert(b != table.end());
2377 CML_Entry * ws = find_set(&*w);
2378 link(bs,ws);
2379 }
2380
2381 } cont:
2382 ci = ci->next;
2383 }
2384 }
2385
2386 // If a base word has both prefixes and suffixes try to combine them.
2387 // This can lead to multiple entries for the same base word. If "multi"
2388 // is true than include all the entries. Otherwise, only include the
2389 // one with the largest number of expansions. This is a greedy choice
2390 // that may not be optimal, but is close to it.
2391 p = table.begin();
2392 String pre,suf;
2393 CML_Entry * extras = 0;
2394 for (; p != end; ++p)
2395 {
2396 pre.clear(); suf.clear();
2397 if (!p->aff) continue;
2398 char * s = p->aff;
2399 while (*s != '/') pre += *s++;
2400 ++s;
2401 while (*s != '\0') suf += *s++;
2402 if (pre.empty()) {
2403
2404 strcpy(p->aff, suf.str());
2405
2406 } else if (suf.empty()) {
2407
2408 strcpy(p->aff, pre.str());
2409
2410 } else {
2411
2412 // Try all possible combinations and keep the ones which expand
2413 // to legal words.
2414
2415 PreSufList cross,tmp1,tmp2;
2416 PreSuf * ps = 0;
2417
2418 for (String::iterator pi = pre.begin(); pi != pre.end(); ++pi) {
2419 String::iterator si = suf.begin();
2420 while (si != suf.end()) { {
2421 char flags[3] = {*pi, *si, '\0'};
2422 exp_buf.reset();
2423 exp_list = lang->expand(p->word, flags, exp_buf);
2424 for (WordAff * q = exp_list; q; q = q->next) {
2425 if (!table.have(q->word)) goto cont2;
2426 }
2427 ps = new PreSuf;
2428 ps->pre += *pi;
2429 ps->suf += *si;
2430 cross.add(ps);
2431 } cont2:
2432 ++si;
2433 }
2434 }
2435
2436 // Now combine the legal cross pairs with other ones when
2437 // possible.
2438
2439 // final res = [ (pre, []) ([],suf),
2440 // (cross | combine first | combine second)
2441 // (cross | combine second | combine first)
2442 // | combine first
2443 // | combine second
2444 //
2445 // combine first [(ab, c) (ab, d) (c, de) (c, ef)]
2446 // = [(ab, cd), (c, def)]
2447
2448 combine(cross, tmp1, 0);
2449 combine(tmp1, tmp2, 1);
2450 tmp1.clear();
2451
2452 combine(cross, tmp1, 1);
2453 combine(tmp1, tmp2, 0);
2454 tmp1.clear();
2455
2456 cross.clear();
2457
2458 ps = new PreSuf;
2459 ps->pre = pre;
2460 tmp2.add(ps);
2461 ps = new PreSuf;
2462 ps->suf = suf;
2463 tmp2.add(ps);
2464
2465 combine(tmp2, tmp1, 0);
2466 combine(tmp1, cross, 1);
2467
2468 if (multi) {
2469
2470 // It is OK to have multiple entries with the same base word
2471 // so use them all.
2472
2473 ps = cross.head;
2474 assert(ps);
2475 memcpy(p->aff, ps->pre.data(), ps->pre.size());
2476 memcpy(p->aff + ps->pre.size(), ps->suf.str(), ps->suf.size() + 1);
2477
2478 ps = ps->next;
2479 CML_Entry * bs = find_set(&*p);
2480 for (; ps; ps = ps->next) {
2481
2482 CML_Entry * tmp = new CML_Entry;
2483 tmp->word = p->word;
2484 tmp->aff = (char *)malloc(ps->pre.size() + ps->suf.size() + 1);
2485 memcpy(tmp->aff, ps->pre.data(), ps->pre.size());
2486 memcpy(tmp->aff + ps->pre.size(), ps->suf.str(), ps->suf.size() + 1);
2487
2488 tmp->parent = bs;
2489
2490 tmp->next = extras;
2491 extras = tmp;
2492 }
2493
2494 } else {
2495
2496 // chose the one which has the largest number of expansions
2497
2498 int max_exp = 0;
2499 PreSuf * best = 0;
2500 String flags;
2501
2502 for (ps = cross.head; ps; ps = ps->next) {
2503 flags = ps->pre;
2504 flags += ps->suf;
2505 exp_buf.reset();
2506 exp_list = lang->expand(p->word, flags, exp_buf);
2507 int c = 0;
2508 for (WordAff * q = exp_list; q; q = q->next) ++c;
2509 if (c > max_exp) {max_exp = c; best = ps;}
2510 }
2511
2512 memcpy(p->aff, best->pre.data(), best->pre.size());
2513 memcpy(p->aff + best->pre.size(), best->suf.str(), best->suf.size() + 1);
2514 }
2515 }
2516 }
2517
2518 while (extras) {
2519 CML_Entry * tmp = extras;
2520 extras = extras->next;
2521 tmp->next = 0;
2522 table.insert(*tmp);
2523 delete tmp;
2524 }
2525
2526 // Create a linked list for each disjoint set
2527 p = table.begin();
2528 for (; p != end; ++p)
2529 {
2530 p->rank = -1;
2531 CML_Entry * bs = find_set(&*p);
2532 if (bs != &*p) {
2533 p->next = bs->next;
2534 bs->next = &*p;
2535 }
2536 }
2537
2538 // Now process each disjoint set independently
2539 p = table.begin();
2540 for (; p != end; ++p)
2541 {
2542 if (p->parent) continue;
2543
2544 Vector<CML_Table::iterator> entries;
2545 Vector<Expansion> expansions;
2546 Vector<Expansion *> to_keep;
2547 std::vector<bool> to_keep_exp;
2548 Vector<Expansion *> working;
2549 Vector<unsigned> to_remove;
2550
2551 // First assign numbers to each unique word. The rank field is
2552 // no longer used so use it to store the number.
2553 for (CML_Entry * q = &*p; q; q = q->next) {
2554 CML_Table::iterator e = table.find(q->word);
2555 if (e->rank == -1) {
2556 e->rank = entries.size();
2557 q->rank = entries.size();
2558 entries.push_back(e);
2559 } else {
2560 q->rank = e->rank;
2561 }
2562 if (q->aff) {
2563 Expansion tmp;
2564 tmp.word = q->word;
2565 tmp.aff = q->aff;
2566 expansions.push_back(tmp);
2567 }
2568 }
2569
2570 to_keep_exp.resize(entries.size());
2571 //for (int i = 0; i != to_keep_exp.size(); ++i) {
2572 // printf(">>> %d %d\n", i, (int)to_keep_exp[i]);
2573 //}
2574
2575 // Store the expansion of each base word in a bit vector and
2576 // add it to the working set
2577 for (Vector<Expansion>::iterator q = expansions.begin();
2578 q != expansions.end();
2579 ++q)
2580 {
2581 q->exp.resize(entries.size());
2582 exp_buf.reset();
2583 exp_list = lang->expand(q->word, q->aff, exp_buf);
2584 for (WordAff * i = exp_list; i; i = i->next) {
2585 CML_Table::iterator e = table.find(i->word);
2586 assert(0 <= e->rank && e->rank < (int)entries.size());
2587 q->exp[e->rank] = true;
2588 }
2589 q->orig_exp = q->exp;
2590 working.push_back(&*q);
2591 }
2592
2593 unsigned prev_working_size = INT_MAX;
2594
2595 // This loop will repeat until the working set is empty. This
2596 // will produce optimum results in most cases. Non optimum
2597 // results may be possible if step (4) is necessary, but in
2598 // practice this step is rarly necessary.
2599 do {
2600 prev_working_size = working.size();
2601
2602 // Sort the list based on WorkingLt. This is necessary every
2603 // time since the expansion list can change.
2604 std::sort(working.begin(), working.end(), WorkingLt());
2605
2606 // (1) Eliminate any elements which are a subset of others
2607 for (unsigned i = 0; i != working.size(); ++i) {
2608 if (!working[i]) continue;
2609 for (unsigned j = i + 1; j != working.size(); ++j) {
2610 if (!working[j]) continue;
2611 if (subset(working[j]->exp, working[i]->exp)) {
2612 working[j] = 0;
2613 }
2614 }
2615 }
2616
2617 // (2) Move any elements which expand to unique entree
2618 // into the to_keep list
2619 to_remove.clear();
2620 for (unsigned i = 0; i != entries.size(); ++i) {
2621 int n = -1;
2622 for (unsigned j = 0; j != working.size(); ++j) {
2623 if (working[j] && working[j]->exp[i]) {
2624 if (n == -1) n = j;
2625 else n = -2;
2626 }
2627 }
2628 if (n >= 0) to_remove.push_back(n);
2629 }
2630 for (unsigned i = 0; i != to_remove.size(); ++i) {
2631 unsigned n = to_remove[i];
2632 if (!working[n]) continue;
2633 to_keep.push_back(working[n]);
2634 merge(to_keep_exp, working[n]->exp);
2635 working[n] = 0;
2636 }
2637
2638 // (3) Eliminate any elements which are a subset of all the
2639 // elements in the to_keep list
2640 for (unsigned i = 0; i != working.size(); ++i) {
2641 if (working[i] && subset(working[i]->exp, to_keep_exp)) {
2642 working[i] = 0;
2643 }
2644 }
2645
2646 // Compact the working list
2647 {
2648 int i = 0, j = 0;
2649 while (j != (int)working.size()) {
2650 if (working[j]) {
2651 working[i] = working[j];
2652 ++i;
2653 }
2654 ++j;
2655 }
2656 working.resize(i);
2657 }
2658
2659 // (4) If none of the entries in working have been removed via
2660 // the above methods then make a greedy choice and move the
2661 // first element into the to_keep list.
2662 if (working.size() > 0 && working.size() == prev_working_size)
2663 {
2664 to_keep.push_back(working[0]);
2665 //CERR.printf("Making greedy choice! Choosing %s/%s.\n",
2666 // working[0]->word, working[0]->aff);
2667 merge(to_keep_exp, working[0]->exp);
2668 working.erase(working.begin(), working.begin() + 1);
2669 }
2670
2671 // (5) Trim the expansion list for any elements left in the
2672 // working set by removing the expansions that already exist in
2673 // the to_keep list
2674 for (unsigned i = 0; i != working.size(); ++i) {
2675 purge(working[i]->exp, to_keep_exp);
2676 }
2677
2678 } while (working.size() > 0);
2679
2680 if (simplify) {
2681
2682 // Remove unnecessary flags. A flag is unnecessary if it does
2683 // does not expand to any new words, that is words that are not
2684 // already covered by an earlier entries in the list.
2685
2686 for (unsigned i = 0; i != to_keep.size(); ++i) {
2687 to_keep[i]->exp = to_keep[i]->orig_exp;
2688 }
2689
2690 std::sort(to_keep.begin(), to_keep.end(), WorkingLt());
2691
2692 std::vector<bool> tally(entries.size());
2693 std::vector<bool> backup(entries.size());
2694 std::vector<bool> working(entries.size());
2695 String flags;
2696
2697 for (unsigned i = 0; i != to_keep.size(); ++i) {
2698
2699 backup = tally;
2700
2701 merge(tally, to_keep[i]->exp);
2702
2703 String flags_to_keep = to_keep[i]->aff;
2704 bool something_changed;
2705 do {
2706 something_changed = false;
2707 for (unsigned j = 0; j != flags_to_keep.size(); ++j) {
2708 flags.assign(flags_to_keep.data(), j);
2709 flags.append(flags_to_keep.data(j+1),
2710 flags_to_keep.size() - (j+1));
2711 working = backup;
2712 exp_buf.reset();
2713 exp_list = lang->expand(to_keep[i]->word, flags, exp_buf);
2714 for (WordAff * q = exp_list; q; q = q->next) {
2715 CML_Table::iterator e = table.find(q->word);
2716 working[e->rank] = true;
2717 }
2718 if (working == tally) {
2719 flags_to_keep = flags;
2720 something_changed = true;
2721 break;
2722 }
2723 }
2724 } while (something_changed);
2725
2726 if (flags_to_keep != to_keep[i]->aff) {
2727 memcpy(to_keep[i]->aff, flags_to_keep.str(), flags_to_keep.size() + 1);
2728 }
2729 }
2730
2731 }
2732
2733 // Finally print the resulting list
2734
2735 //printf("XXX %d %d\n", to_keep.size(), to_keep_exp.size());
2736 //for (int i = 0; i != to_keep_exp.size(); ++i) {
2737 // printf(">>> %d %d\n", i, (int)to_keep_exp[i]);
2738 //}
2739
2740 for (unsigned i = 0; i != to_keep.size(); ++i) {
2741 COUT << oconv(to_keep[i]->word);
2742 if (to_keep[i]->aff[0]) {
2743 COUT << '/';
2744 COUT << oconv(to_keep[i]->aff);
2745 }
2746 COUT << '\n';
2747 }
2748 for (unsigned i = 0; i != to_keep_exp.size(); ++i) {
2749 if (!to_keep_exp[i]) {
2750 assert(!entries[i]->aff);
2751 COUT.printf("%s\n", oconv(entries[i]->word));
2752 }
2753 }
2754 }
2755
2756 p = table.begin();
2757 for (; p != end; ++p)
2758 {
2759 if (p->aff) free(p->aff);
2760 p->aff = 0;
2761 }
2762 }
2763
2764
2765 //////////////////////////
2766 //
2767 // dump affix
2768 //
2769
dump_affix()2770 void dump_affix()
2771 {
2772 FStream in;
2773 EXIT_ON_ERR(aspeller::open_affix_file(*options, in));
2774
2775 String line;
2776 while (in.getline(line))
2777 COUT << line << '\n';
2778 }
2779
2780
2781
2782 ///////////////////////////////////////////////////////////////////////
2783
2784
2785 ///////////////////////////
2786 //
2787 // print_help
2788 //
2789
print_help_line(char abrv,char dont_abrv,const char * name,KeyInfoType type,const char * desc,bool no_dont=false)2790 void print_help_line(char abrv, char dont_abrv, const char * name,
2791 KeyInfoType type, const char * desc, bool no_dont = false)
2792 {
2793 String command;
2794 if (abrv != '\0') {
2795 command += '-';
2796 command += abrv;
2797 if (dont_abrv != '\0') {
2798 command += '|';
2799 command += '-';
2800 command += dont_abrv;
2801 }
2802 command += ',';
2803 }
2804 command += "--";
2805 if (type == KeyInfoBool && !no_dont) command += "[dont-]";
2806 if (type == KeyInfoList) command += "add|rem-";
2807 command += name;
2808 if (type == KeyInfoString || type == KeyInfoList)
2809 command += "=<str>";
2810 if (type == KeyInfoInt)
2811 command += "=<int>";
2812 const char * tdesc = _(desc);
2813 char buf[120];
2814 int len = snprintf(buf, 120, " %-27s", command.c_str());
2815 if (len == 29)
2816 printf("%s %s\n", buf, tdesc);
2817 else
2818 printf("%s\n%30s%s\n", buf, "", tdesc);
2819 }
2820
2821 namespace acommon {
2822 PosibErr<ConfigModule *> get_dynamic_filter(Config * config, ParmStr value);
2823 }
2824
2825 static const char * usage_text[] =
2826 {
2827 /* TRANSLATORS: These should all be formated to fit in 80 column or
2828 less */
2829 N_("Usage: aspell [options] <command>"),
2830 N_("<command> is one of:"),
2831 N_(" -?|usage display a brief usage message"),
2832 N_(" help display a detailed help message"),
2833 N_(" -c|check <file> spellchecks a file"),
2834 N_(" -a|pipe \"ispell -a\" compatibility mode"),
2835 N_(" [dump] config dumps the current configuration to stdout"),
2836 N_(" config <key> prints the current value of an option"),
2837 N_(" [dump] dicts | filters | modes"),
2838 N_(" lists available dictionaries / filters / filter modes"),
2839 N_("[options] is any of the following:")
2840 };
2841 static const unsigned usage_text_size = sizeof(usage_text)/sizeof(const char *);
2842
2843 static const char * help_text[] =
2844 {
2845 usage_text[0],
2846 "",
2847 usage_text[1],
2848 usage_text[2],
2849 usage_text[3],
2850 usage_text[4],
2851 usage_text[5],
2852 N_(" list produce a list of misspelled words from standard input"),
2853 usage_text[6],
2854 usage_text[7],
2855 N_(" soundslike returns the sounds like equivalent for each word entered"),
2856 N_(" munch generate possible root words and affixes"),
2857 N_(" expand [1-4] expands affix flags"),
2858 N_(" clean [strict] cleans a word list so that every line is a valid word"),
2859 N_(" filter filters input as if it was being spellchecked"),
2860 N_(" -v|version prints a version line"),
2861 N_(" munch-list [simple] [single|multi] [keep]"),
2862 N_(" reduce the size of a word list via affix compression"),
2863 N_(" conv <from> <to> [<norm-form>]"),
2864 N_(" converts from one encoding to another"),
2865 N_(" norm (<norm-map> | <from> <norm-map> <to>) [<norm-form>]"),
2866 N_(" perform Unicode normalization"),
2867 usage_text[8],
2868 usage_text[9],
2869 N_(" dump|create|merge master|personal|repl [<name>]"),
2870 N_(" dumps, creates or merges a master, personal, or replacement dictionary."),
2871 "",
2872 /* TRANSLATORS: "none", "internal" and "strict" are literal values
2873 and should not be translated. */
2874 N_(" <norm-form> normalization form to use, either none, internal, or strict"),
2875 "",
2876 usage_text[10],
2877 ""
2878 };
2879 static const unsigned help_text_size = sizeof(help_text)/sizeof(const char *);
2880
print_help(bool verbose)2881 void print_help (bool verbose) {
2882 load_all_filters(options);
2883 if (verbose) {
2884 printf(_("\n"
2885 "Aspell %s. Copyright 2000-2019 by Kevin Atkinson.\n"
2886 "\n"), aspell_version_string());
2887 for (unsigned i = 0; i < help_text_size; ++i)
2888 puts(gt_(help_text[i]));
2889 } else {
2890 for (unsigned i = 0; i < usage_text_size; ++i)
2891 puts(gt_(usage_text[i]));
2892 }
2893 StackPtr<KeyInfoEnumeration> els(options->possible_elements(true,false));
2894 const KeyInfo * k;
2895 while (k = els->next(), k) {
2896 if (k->desc == 0 || k->flags & KEYINFO_HIDDEN) continue;
2897 if (!verbose && !(k->flags & KEYINFO_COMMON)) continue;
2898 const PossibleOption * o = find_option(k->name);
2899 const char * name = k->name;
2900 print_help_line(o->abrv,
2901 strncmp((o+1)->name, "dont-", 5) == 0 ? (o+1)->abrv : '\0',
2902 name, k->type, k->desc);
2903 if (verbose && strcmp(name, "mode") == 0) {
2904 for (const ModeAbrv * j = mode_abrvs;
2905 j != mode_abrvs_end;
2906 ++j)
2907 {
2908 print_help_line(j->abrv, '\0', j->mode, KeyInfoBool, j->desc, true);
2909 }
2910 }
2911 }
2912
2913 if (verbose) {
2914 //
2915 putchar('\n');
2916 putchar('\n');
2917 puts(
2918 _("Available Dictionaries:\n"
2919 " Dictionaries can be selected directly via the \"-d\" or \"master\"\n"
2920 " option. They can also be selected indirectly via the \"lang\",\n"
2921 " \"variety\", and \"size\" options.\n"));
2922
2923 const DictInfoList * dlist = get_dict_info_list(options);
2924
2925 StackPtr<DictInfoEnumeration> dels(dlist->elements());
2926
2927 const DictInfo * entry;
2928
2929 while ( (entry = dels->next()) != 0)
2930 {
2931 printf(" %s\n", entry->name);
2932 }
2933
2934
2935 //
2936 putchar('\n');
2937 putchar('\n');
2938 fputs(
2939 _("Available Filters (and associated options):\n"
2940 " Filters can be added or removed via the \"filter\" option.\n"),
2941 stdout);
2942 for (Vector<ConfigModule>::const_iterator m = options->filter_modules.begin();
2943 m != options->filter_modules.end();
2944 ++m)
2945 {
2946 printf(_("\n %s filter: %s\n"), m->name, gt_(m->desc));
2947 for (k = m->begin; k != m->end; ++k) {
2948 const PossibleOption * o = find_option(k->name);
2949 const char * name = k->name;
2950 const KeyInfo * ok = options->keyinfo(name + 2);
2951 if (k == ok) name += 2;
2952 print_help_line(o->abrv,
2953 strncmp((o+1)->name, "dont-", 5) == 0 ? (o+1)->abrv : '\0',
2954 name, k->type, k->desc);
2955 }
2956 }
2957
2958 //
2959 putchar('\n');
2960 putchar('\n');
2961 puts(
2962 /* TRANSLATORS: This should be formated to fit in 80 column or less */
2963 _("Available Filter Modes:\n"
2964 " Filter Modes are reconfigured combinations of filters optimized for\n"
2965 " files of a specific type. A mode is selected via the \"mode\" option.\n"
2966 " This will happen implicitly if Aspell is able to identify the file\n"
2967 " type from the extension, and possibility the contents, of the file.\n"));
2968
2969 EXIT_ON_ERR_SET(available_filter_modes(options), StringPairEnumeration *, els);
2970 StringPair sp;
2971 while (!els->at_end()) {
2972 sp = els->next();
2973 printf(" %-14s %s\n", sp.first, gt_(sp.second));
2974 }
2975 delete els;
2976 }
2977 }
2978
2979