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