1 /*************************************************************************/
2 /* Copyright (c) 2004 */
3 /* Daniel Sleator, David Temperley, and John Lafferty */
4 /* Copyright (c) 2008, 2009, 2011, 2014 Linas Vepstas */
5 /* All rights reserved */
6 /* */
7 /* Use of the link grammar parsing system is subject to the terms of the */
8 /* license set forth in the LICENSE file included with this software. */
9 /* This license allows free redistribution and use in source and binary */
10 /* forms, with or without modification, subject to certain conditions. */
11 /* */
12 /*************************************************************************/
13
14 #include <errno.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <wchar.h>
18 #include <wctype.h>
19
20 #include "command-line.h"
21 #include "parser-utilities.h"
22
23 static struct
24 {
25 int verbosity;
26 char * debug;
27 char * test;
28 char * dialect;
29 int timeout;
30 int memory;
31 int linkage_limit;
32 int islands_ok;
33 int repeatable_rand;
34 int spell_guess;
35 int short_length;
36 int batch_mode;
37 int panic_mode;
38 int allow_null;
39 int use_sat_solver;
40 int echo_on;
41 Cost_Model_type cost_model;
42 double max_cost;
43 int screen_width;
44 int display_on;
45 ConstituentDisplayStyle display_constituents;
46 int display_postscript;
47 int display_ps_header;
48 int display_bad;
49 int display_links;
50 int display_walls;
51 int display_disjuncts;
52 int display_morphology;
53 int display_wordgraph;
54 } local, local_saved;
55
56 static const char *value_type[] =
57 {
58 "(integer) ", "(Boolean) ", "(float) ", "(string) ", "(command) ", ""
59 };
60
61 static int variables_cmd(const Switch*, int);
62 static int file_cmd(const Switch*, int);
63 static int help_cmd(const Switch*, int);
64 static int exit_cmd(const Switch*, int);
65 static int info_cmd(const Switch*, int);
66
67 Switch default_switches[] =
68 {
69 {"bad", Bool, "Display of bad linkages", &local.display_bad},
70 {"batch", Bool, "Batch mode", &local.batch_mode},
71 {"constituents", Int, "Generate constituent output", &local.display_constituents},
72 {"cost-model", Int, UNDOC "Cost model used for ranking", &local.cost_model},
73 {"cost-max", Float, "Largest cost to be considered", &local.max_cost},
74 {"debug", String, "Comma-separated function names to debug", &local.debug},
75 {"dialect", String, "Comma-separated dialects", &local.dialect},
76 {"disjuncts", Bool, "Display of disjuncts used", &local.display_disjuncts},
77 {"echo", Bool, "Echoing of input sentence", &local.echo_on},
78 {"graphics", Bool, "Graphical display of linkage", &local.display_on},
79 {"islands-ok", Bool, "Use of null-linked islands", &local.islands_ok},
80 {"limit", Int, "The maximum linkages processed", &local.linkage_limit},
81 {"links", Bool, "Display of complete link data", &local.display_links},
82 {"memory", Int, UNDOC "Max memory allowed", &local.memory},
83 {"morphology", Bool, "Display word morphology", &local.display_morphology},
84 {"null", Bool, "Allow null links", &local.allow_null},
85 {"panic", Bool, "Use of \"panic mode\"", &local.panic_mode},
86 {"postscript", Bool, "Generate postscript output", &local.display_postscript},
87 {"ps-header", Bool, "Generate postscript header", &local.display_ps_header},
88 {"rand", Bool, "Use repeatable random numbers", &local.repeatable_rand},
89 {"short", Int, "Max length of short links", &local.short_length},
90 #if defined HAVE_HUNSPELL || defined HAVE_ASPELL
91 {"spell", Int, "Up to this many spell-guesses per unknown word", &local.spell_guess},
92 {"test", String, "Comma-separated test features", &local.test},
93 #endif /* HAVE_HUNSPELL */
94 {"timeout", Int, "Abort parsing after this many seconds", &local.timeout},
95 #ifdef USE_SAT_SOLVER
96 {"use-sat", Bool, "Use Boolean SAT-based parser", &local.use_sat_solver},
97 #endif /* USE_SAT_SOLVER */
98 {"verbosity", Int, "Level of detail in output", &local.verbosity},
99 {"walls", Bool, "Display wall words", &local.display_walls},
100 {"width", Int, "The width of the display", &local.screen_width},
101 {"wordgraph", Int, "Display sentence word-graph", &local.display_wordgraph},
102 {"exit", Cmd, "Exit the program", exit_cmd},
103 {"file", Cmd, "Read input from the specified filename", file_cmd},
104 {"help", Cmd, "List the commands and what they do", help_cmd},
105 {"quit", Cmd, UNDOC "Exit the program", exit_cmd},
106 {"variables", Cmd, "List user-settable variables and their functions", variables_cmd},
107 {"!", Cmd, UNDOC "Print information on dictionary words", info_cmd},
108 {NULL, Cmd, NULL, NULL}
109 };
110
111 static void put_opts_in_local_vars(Command_Options *);
112
113 /*
114 * A way to record the options default values.
115 */
save_default_opts(Command_Options * copts)116 void save_default_opts(Command_Options *copts)
117 {
118 put_opts_in_local_vars(copts);
119 local_saved = local;
120 }
121
restore_default_local_vars(void)122 static void restore_default_local_vars(void)
123 {
124 local = local_saved;
125 }
126
127 /**
128 * Gets rid of all the white space in the string s.
129 */
clean_up_string(char * s)130 static void clean_up_string(char * s)
131 {
132 char * x, * y;
133 wchar_t p;
134 size_t len, w;
135 mbstate_t state;
136 memset (&state, 0, sizeof(mbstate_t));
137
138 len = strlen(s);
139 y = x = s;
140 while(*x != '\0')
141 {
142 w = mbrtowc(&p, x, len, &state);
143 if (0 == w) break;
144 if (0 > (ssize_t)w)
145 {
146 prt_error("Unable to process UTF8 command input string.\n");
147 break;
148 }
149 len -= w;
150
151 if (!iswspace(p)) {
152 while(w) { *y = *x; x++; y++; w--; }
153 } else {
154 x += w;
155 }
156 }
157 *y = '\0';
158 }
159
160 /**
161 * Return TRUE if s points to a number:
162 * optional + or - followed by 1 or more
163 * digits.
164 */
is_numerical_rhs(char * s)165 static bool is_numerical_rhs(char *s)
166 {
167 wchar_t p;
168 size_t len, w;
169 mbstate_t state;
170 memset (&state, 0, sizeof(mbstate_t));
171
172 len = strlen(s);
173
174 if (*s=='+' || *s == '-') s++;
175 if (*s == '\0') return false;
176
177 for (; *s != '\0'; s+=w)
178 {
179 w = mbrtowc(&p, s, len, &state);
180 if (0 == w) break;
181 if (0 > (ssize_t)w)
182 {
183 prt_error("Unable to process UTF8 command input string.\n");
184 break;
185 }
186 len -= w;
187 if (!iswdigit(p)) return false;
188 }
189 return true;
190 }
191
ival(Switch s)192 static int ival(Switch s)
193 {
194 return *((int *) s.ptr);
195 }
196
setival(Switch s,int val)197 static void setival(Switch s, int val)
198 {
199 *((int *) s.ptr) = val;
200 }
201
202 /**
203 * Return the value description for the given switch.
204 */
switch_value_description(const Switch * as)205 static const char *switch_value_description(const Switch *as)
206 {
207 if (Bool == as->param_type)
208 return ival(*as) ? " (On)" : " (Off)";
209 if (Int == as->param_type)
210 return (-1 == ival(*as)) ? " (Unlimited)" : "";
211
212 return "";
213 }
214
215 /**
216 * Return a static buffer with a string value of the given switch.
217 *
218 * Since the static buffer is overwritten on each call, this function
219 * should not use more than once as an argument of the same function.
220 */
switch_value_string(const Switch * as)221 static const char *switch_value_string(const Switch *as)
222 {
223 static char buf[128]; /* Size of buf is much more than we need */
224
225 switch (as->param_type)
226 {
227 case Float: /* Float point print! */
228 snprintf(buf, sizeof(buf), "%.2f", *((double *)as->ptr));
229 break;
230 case Bool:
231 /* FALLTHRU */
232 case Int:
233 /* FALLTHRU (why another one is needed?) */
234 snprintf(buf, sizeof(buf), "%d", ival(*as));
235 break;
236 case String:
237 #if 0 /* It seems it is not a good idea. */
238 if ((NULL == *(char **)as->ptr) || ('\0' == **(char **)as->ptr))
239 strcpy(buf, "(not set)");
240 else
241 #endif
242 snprintf(buf, sizeof(buf), "%s", *(char **)as->ptr);
243 break;
244 case Cmd:
245 buf[0] = '\0'; /* No value to print. */
246 break;
247 default:
248 /* Internal error. */
249 snprintf(buf, sizeof(buf), "Unknown type %d\n", (int)as->param_type);
250 }
251
252 return buf;
253 }
254
255 #define HELPFILE_BASE "command-help-"
256 #define HELPFILE_EXT ".txt"
257 #define HELPFILE_LANG_TEMPLATE "LL" /* we use only the 2-letter language code */
258 #define HELPFILE_LANG_TEMPLATE_SIZE (sizeof(HELPFILE_LANG_TEMPLATE)-1)
259 #define HELPFILE_TEMPLATE_SIZE \
260 (sizeof(HELPFILE_BASE HELPFILE_EXT)+HELPFILE_LANG_TEMPLATE_SIZE)
261 #define D_USER_FILES 3 /* Debug level for files */
262 #define DEFAULT_HELP_LANG "en"
263
264 static FILE *help_file;
265 /* Used in atexit() below. */
close_help_file(void)266 static void close_help_file(void)
267 {
268 fclose(help_file);
269 }
270
271 /**
272 * On each call, return the next locale to try for constructing the help
273 * file name (NULL if no more).
274 *
275 * @param nextlang true on calls to get the next language.
276 * @return Locale to try, NULL if no more.
277 *
278 * This function should be called until it returns NULL (it order to
279 * free memory that it may allocate).
280 * After it returns NULL, the code that uses it below doesn't call it again.
281 *
282 * See "Specifying a Priority List of Languages":
283 * www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html
284 */
get_next_locale(void)285 static const char *get_next_locale(void)
286 {
287 enum state
288 {Initial_s, Language_s, Next_language_s, Default_language_s, Final_s};
289 static int state = Initial_s;
290 static char *language;
291 char *lc_all;
292 const char *lang = NULL;
293
294 while (1)
295 {
296 switch (state)
297 {
298 case Initial_s:
299 lc_all = getenv("LC_ALL");
300 if ((NULL != lc_all) && (0 == strcmp(lc_all, "C")))
301 {
302 /* LC_ALL=C */
303 state = Default_language_s;
304 continue;
305 }
306
307 if ((NULL == lc_all) || ('\0' == *lc_all))
308 {
309 /* LC_ALL= */
310 lang = getenv("LANG");
311 if ((NULL != lang) && (0 == strcmp(lang, "C")))
312 {
313 /* LANG=C */
314 state = Default_language_s;
315 continue;
316 }
317 }
318 /* LC_ALL=x */
319 state = Language_s;
320 continue;
321
322 case Language_s:
323 language = getenv("LANGUAGE");
324 if ((language == NULL) || ('\0' == *language))
325 {
326 /* LANGUAGE= */
327 language = NULL; /* so it doesn't get freed at Finals_s */
328 state = Default_language_s;
329 if ((NULL != lang) && ('\0' != *lang))
330 {
331 /* LANG=x */
332 return lang;
333 }
334 /* LANG= */
335 continue;
336 }
337
338 /* LANGUAGE=x:y:z */
339 state = Next_language_s;
340 language = strdup(language); /* strdup() for strtok() */
341 return strtok(language, ":");
342 break;
343
344 case Next_language_s:
345 {
346 char *next_language = strtok(NULL, ":");
347 if (NULL == next_language)
348 {
349 /* LANGUAGE tokens exhausted */
350 state = Default_language_s;
351 continue;
352 }
353 /* Unchanged state. */
354 return next_language;
355 }
356 break;
357
358 case Default_language_s:
359 state = Final_s;
360 return DEFAULT_HELP_LANG;
361
362 case Final_s:
363 free(language);
364 state = Initial_s;
365 break;
366 /* NULL is returned below. */
367 }
368 break;
369 }
370
371 return NULL;
372 }
373
open_help_file(int verbosity)374 static FILE *open_help_file(int verbosity)
375 {
376 char *help_filename;
377
378 if (NULL != help_file)
379 {
380 rewind(help_file);
381 return help_file;
382 }
383 atexit(close_help_file);
384
385 /* Construct the help filename from its template. */
386 help_filename = malloc(HELPFILE_TEMPLATE_SIZE);
387 strcpy(help_filename, HELPFILE_BASE);
388 char *ll_pos = &help_filename[strlen(help_filename)];
389 strcpy(ll_pos, HELPFILE_LANG_TEMPLATE HELPFILE_EXT);
390
391 const char *ll;
392 while ((ll = get_next_locale()))
393 {
394 if (NULL != help_file)
395 continue; /* until get_next_locale() returns NULL */
396 memcpy(ll_pos, ll, HELPFILE_LANG_TEMPLATE_SIZE);
397 help_file = linkgrammar_open_data_file(help_filename);
398 }
399
400 if ((NULL == help_file) && (verbosity > D_USER_FILES))
401 {
402 prt_error("Error: Cannot open help file '%s': %s\n",
403 help_filename, strerror(errno));
404 }
405
406 free(help_filename);
407 return help_file;
408 }
409
410 /**
411 * Print basic info: name, description, current value and type.
412 * If `is_completion` is true, also display the variable value, and use
413 * fixed fields for the value related info and the description.
414 * This is intended for use from the command completion code.
415 *
416 * The display format is ([] denotes optional - if is_completion is true):
417 * varname[=varvalue] (vartype) - description
418 */
display_1line_help(const Switch * sp,bool is_completion)419 void display_1line_help(const Switch *sp, bool is_completion)
420 {
421 int undoc = !!(UNDOC[0] == sp->description[0]);
422 int vtw = 0; /* value_type field width */
423 int vnw = 0; /* varname field width */
424 bool display_eq = is_completion && (Cmd != sp->param_type);
425 const char *value = "";
426
427 if (is_completion)
428 {
429 vtw = 10;
430 vnw = 18;
431 if (Cmd != sp->param_type)
432 value = switch_value_string(sp);
433 }
434
435 int n; /* actual varname and optional varvalue print length */
436 printf("%s%s%s%n", sp->string, display_eq ? "=" : " ", value, &n);
437 if (is_completion) printf("%*s", MAX(0, vnw - n), "");
438 printf("%*s- %s\n", vtw, value_type[sp->param_type], sp->description+undoc);
439 }
440
display_help(const Switch * sp,Command_Options * copts)441 static void display_help(const Switch *sp, Command_Options *copts)
442 {
443 char line[MAX_INPUT]; /* Maximum number of character in a help file line */
444
445 display_1line_help(sp, /*is_completion*/false);
446 if (Cmd != sp->param_type)
447 {
448 printf("Current value: %s\n",switch_value_string(sp));
449 restore_default_local_vars();
450 printf("Default value: %s\n", switch_value_string(sp));
451 put_opts_in_local_vars(copts);
452 }
453
454 FILE *hf = open_help_file(local.verbosity);
455 if (NULL == hf)
456 {
457 prt_error("Warning: Help file not found\n");
458 return;
459 }
460
461 bool help_found = false;
462 while (!help_found && (NULL != fgets(line, sizeof(line), hf)))
463 {
464 if ('[' != line[0]) continue;
465 {
466 #define CMDTAG_SEP ", \t]"
467 /* Allow for several names to map to the same help text,
468 * in a hope that the same help text can be used for the
469 * language bindings too (which have different option names
470 * from historical reasons).
471 * Note: We suppose the lines are not longer than MAX_INPUT.
472 * Longer lines may render the help text incorrectly.
473 * FIXME: Add command reference notation in the help text if
474 * needed.
475 * Help file format:
476 * % comment
477 * [cmd1 cmd2 ...]
478 * text ...
479 * [nextcmd ...]
480 * text ...
481 */
482 char *t = strtok(line+1, CMDTAG_SEP);
483 if (NULL == t) continue;
484 do {
485 if (0 == strcasecmp(t, sp->string))
486 {
487 help_found = true;
488 break;
489 }
490 t = strtok(NULL, CMDTAG_SEP);
491 } while (NULL != t);
492 }
493 }
494
495 if (ferror(hf))
496 {
497 prt_error("Error: Reading help file: %s\n", strerror(errno));
498 return;
499 }
500
501 if (feof(hf))
502 {
503 if (local.verbosity >= D_USER_FILES)
504 prt_error("Error: Cannot find command \"%s\" in help file\n",
505 sp->string);
506 }
507 else
508 {
509 help_found = false;
510 bool issue_blank_line = false;
511 while (NULL != fgets(line, sizeof(line), hf))
512 {
513 const size_t len = strlen(line);
514 if ((MAX_INPUT == len-1) && '\n' != line[MAX_INPUT-2])
515 {
516 prt_error("Warning: Help-file text line too long at offset %ld\n",
517 ftell(hf));
518 }
519 if (COMMENT_CHAR == line[0]) continue;
520 if ('[' == line[0]) break;
521
522 /* Suppress the ending blank lines of the help text. */
523 if (strspn(line, WHITESPACE) == len) /* we encountered a blank line */
524 {
525 issue_blank_line = true;
526 continue;
527 }
528
529 if (!help_found)
530 {
531 printf("\n"); /* issue a blank line separator after basic info */
532 help_found = true;
533 }
534
535 /* We have a line to print. Print a blank line before it if needed. */
536 if (issue_blank_line)
537 {
538 issue_blank_line = false;
539 printf("\n");
540 }
541 printf("%s", line);
542 }
543 if (!help_found) /* the command tag has no help text */
544 prt_error("Info: No help text found for command \"%s\"\n", sp->string);
545 }
546 }
547
548 #define URL_LINE "%-20s %s\n"
print_url_info(void)549 void print_url_info(void)
550 {
551 printf("\n");
552 #ifdef OVERVIEW
553 printf(URL_LINE, "Overview:", OVERVIEW);
554 #endif /* OVERVIEW */
555 #ifdef PACKAGE_URL
556 printf(URL_LINE, "Home page:", PACKAGE_URL);
557 printf(URL_LINE, "Documentation:", PACKAGE_URL"/dict/");
558 #endif /* PACKAGE_URL */
559 #ifdef DISCUSSION_GROUP
560 printf(URL_LINE, "Discussion group:", DISCUSSION_GROUP);
561 #endif /* DISCUSSION_GROUP */
562 #ifdef PACKAGE_BUGREPORT
563 printf(URL_LINE, "Report bugs to:" , PACKAGE_BUGREPORT"/issues");
564 #endif /* PACKAGE_BUGREPORT */
565 }
566
help_cmd(const Switch * uc,int n)567 static int help_cmd(const Switch *uc, int n)
568 {
569 printf("Special commands always begin with \"!\". Command and variable names\n");
570 printf("can be abbreviated. Here is a list of the commands:\n\n");
571
572 printf(" !help command Show a detailed help for the given command.\n");
573 for (int i = 0; uc[i].string != NULL; i++)
574 {
575 if (Cmd != uc[i].param_type) continue;
576 if (UNDOC[0] == uc[i].description[0]) continue;
577 printf(" !%-14s ", uc[i].string);
578 printf("%s.\n", uc[i].description);
579 }
580
581 printf("\n");
582 printf(" !!<string> Print all the dictionary words that match <string>.\n");
583 printf(" A wildcard * may be used to find multiple matches.\n");
584 printf(" Issue \"!help !\" for more details.\n");
585 printf("\n");
586 printf(" !<var> Toggle the specified Boolean variable.\n");
587 printf(" !<var>=<val> Assign that value to that variable.\n");
588
589 print_url_info();
590
591 return 'c';
592 }
593
variables_cmd(const Switch * uc,int n)594 static int variables_cmd(const Switch *uc, int n)
595 {
596 printf(" Variable Controls Value\n");
597 printf(" -------- -------- -----\n");
598 for (int i = 0; uc[i].string != NULL; i++)
599 {
600 if (Cmd == uc[i].param_type) continue;
601 if (UNDOC[0] == uc[i].description[0]) continue;
602 printf(" %-13s", uc[i].string);
603 printf("%-*s", FIELD_WIDTH(uc[i].description, 46), uc[i].description);
604 printf("%5s", switch_value_string(&uc[i]));
605 printf("%s\n", switch_value_description(&uc[i]));
606 }
607
608 printf("\n");
609 printf("Toggle a Boolean variable as so: \"!batch\"; ");
610 printf("Set a variable as so: \"!width=100\".\n");
611 printf("Get detailed help on a variable with: \"!help var\".\n");
612 return 'c';
613 }
614
exit_cmd(const Switch * uc,int n)615 static int exit_cmd(const Switch *uc, int n)
616 {
617 return 'e';
618 }
619
file_cmd(const Switch * uc,int n)620 static int file_cmd(const Switch *uc, int n)
621 {
622 return 'f';
623 }
624
info_cmd(const Switch * uc,int n)625 static int info_cmd(const Switch *uc, int n)
626 {
627 /* Dummy definition - the work is done done in
628 * x_issue_special_command() (see '!' there). */
629 return 'c';
630 }
631
x_issue_special_command(char * line,Command_Options * copts,Dictionary dict)632 static int x_issue_special_command(char * line, Command_Options *copts, Dictionary dict)
633 {
634 char *s, *x, *y;
635 int count, j;
636 const Switch *as = default_switches;
637 const char helpmsg[] = "Type \"!help\" or \"!variables\".";
638
639 /* Handle a request for a particular command help. */
640 if (NULL != dict)
641 {
642 char *dupline = strdup(line);
643 /* If we are here, it is not a command-line parameter. */
644 s = strtok(dupline, WHITESPACE);
645 if ((s != NULL) && strncasecmp(s, "help", strlen(s)) == 0)
646 {
647 s = strtok(NULL, WHITESPACE);
648 if (s != NULL)
649 {
650 /* This is a help request for the command name at s. */
651 j = -1; /* command index */
652 count = 0; /* number of matching commands */
653
654 /* Is it a unique abbreviation? */
655 for (int i = 0; as[i].string != NULL; i++)
656 {
657 if (strncasecmp(s, as[i].string, strlen(s)) == 0)
658 {
659 count++;
660 j = i;
661 }
662 }
663
664 if (count == 1)
665 {
666 free(dupline);
667 display_help(&as[j], copts);
668 return 'c';
669 }
670
671 if (count > 1)
672 prt_error("Ambiguous command: \"%s\". %s\n", s, helpmsg);
673 else
674 prt_error("Undefined command: \"%s\". %s\n", s, helpmsg);
675
676 free(dupline);
677 return -1;
678 }
679 }
680 free(dupline);
681 }
682
683 s = line;
684 if (s[0] == '!')
685 {
686 Parse_Options opts = copts->popts;
687 char *out;
688
689 out = dict_display_word_info(dict, s+1, opts);
690 if (NULL != out)
691 {
692 printf("%s\n", out);
693 free(out);
694 out = dict_display_word_expr(dict, s+1, opts);
695 if (NULL != out)
696 {
697 printf("%s", out);
698 free(out);
699 }
700 else
701 {
702 prt_error("Error: '%s': Internal Error: Missing expression.\n", s+1);
703 }
704 }
705 else
706 {
707 printf("Token \"%s\" matches nothing in the dictionary.\n", s+1);
708 }
709
710 return 'c';
711 }
712
713 clean_up_string(line);
714 j = -1;
715 count = 0;
716
717 /* Look for Boolean flippers or command abbreviations. */
718 for (int i = 0; as[i].string != NULL; i++)
719 {
720 if (((Bool == as[i].param_type) || (Cmd == as[i].param_type)) &&
721 strncasecmp(s, as[i].string, strlen(s)) == 0)
722 {
723 if ((UNDOC[0] == as[i].description[0]) &&
724 (strlen(as[i].string) != strlen(s))) continue;
725 count++;
726 j = i;
727 }
728 }
729
730 if (count > 1)
731 {
732 prt_error("Ambiguous command \"%s\". %s\n", s, helpmsg);
733 return -1;
734 }
735 else if (count == 1)
736 {
737 /* Flip Boolean value. */
738 if (Bool == as[j].param_type)
739 {
740 setival(as[j], (0 == ival(as[j])));
741 int undoc = !!(UNDOC[0] == as[j].description[0]);
742 printf("%s turned %s.\n",
743 as[j].description+undoc, (ival(as[j]))? "on" : "off");
744 return 'c';
745 }
746
747 /* Found an abbreviated, but it wasn't a Boolean.
748 * It means it is a user command, to be handled below. */
749 return ((int (*)(const Switch*, int)) (as[j].ptr))(as, j);
750 }
751
752 /* Test here for an equation i.e. does the command line hold an equals sign? */
753 for (x=s; (*x != '=') && (*x != '\0'); x++)
754 ;
755 if (*x == '=')
756 {
757 *x = '\0';
758 y = x+1;
759 x = s;
760 /* now x is the first word and y is the rest */
761
762 /* Figure out which command it is .. it'll be the j'th one */
763 j = -1;
764 for (int i = 0; as[i].string != NULL; i++)
765 {
766 if (Cmd == as[i].param_type) continue;
767 if (strncasecmp(x, as[i].string, strlen(x)) == 0)
768 {
769 if ((UNDOC[0] == as[i].description[0]) &&
770 (strlen(as[i].string) != strlen(x))) continue;
771 j = i;
772 count ++;
773 }
774 }
775
776 if (j<0)
777 {
778 prt_error("Error: There is no user variable called \"%s\".\n", x);
779 return -1;
780 }
781
782 if (count > 1)
783 {
784 prt_error("Error: Ambiguous variable \"%s\". %s\n", x, helpmsg);
785 return -1;
786 }
787
788 if ((as[j].param_type == Int) || (as[j].param_type == Bool))
789 {
790 int val = -1;
791 if (is_numerical_rhs(y)) val = atoi(y);
792
793 if ((0 == strcasecmp(y, "true")) || (0 == strcasecmp(y, "t"))) val = 1;
794 if ((0 == strcasecmp(y, "false")) || (0 == strcasecmp(y, "f"))) val = 0;
795
796 if ((val < 0) ||
797 ((as[j].param_type == Bool) && (val != 0) && (val != 1)))
798 {
799 prt_error("Error: Invalid value %s for variable \"%s\". %s\n",
800 y, as[j].string, helpmsg);
801 return -1;
802 }
803
804 setival(as[j], val);
805 printf("%s set to %d\n", as[j].string, val);
806 return 'c';
807 }
808 else
809 if (as[j].param_type == Float)
810 {
811 char *err;
812 double val = strtod(y, &err);
813 if (('\0' == *y) || ('\0' != *err))
814 {
815 prt_error("Error: Invalid value %s for variable \"%s\". %s\n",
816 y, as[j].string, helpmsg);
817 return -1;
818 }
819
820 *((double *) as[j].ptr) = val;
821 printf("%s set to %5.2f\n", as[j].string, val);
822 return 'c';
823 }
824 else
825 if (as[j].param_type == String)
826 {
827 *((char **) as[j].ptr) = y;
828 printf("%s set to %s\n", (char *)as[j].string, y);
829 return 'c';
830 }
831 else
832 {
833 prt_error("Error: Internal error: Unknown variable type %d\n",
834 (int)as[j].param_type);
835 return -1;
836 }
837 }
838
839 /* Look for valid commands, but ones that needed an argument */
840 j = -1;
841 count = 0;
842 for (int i = 0; as[i].string != NULL; i++)
843 {
844 if ((Bool != as[i].param_type) && (Cmd != as[i].param_type) &&
845 strncasecmp(s, as[i].string, strlen(s)) == 0)
846 {
847 if ((UNDOC[0] == as[i].description[0]) &&
848 (strlen(as[i].string) != strlen(s))) continue;
849 j = i;
850 count++;
851 }
852 }
853
854 if (0 < count)
855 {
856 prt_error("Error: Variable \"%s\" requires a value. Try \"!help %s\".\n",
857 as[j].string, as[j].string);
858 return -1;
859 }
860
861 prt_error("Error: I can't interpret \"%s\" as a command. Try \"!help\" or \"!variables\".\n", line);
862 return -1;
863 }
864
put_opts_in_local_vars(Command_Options * copts)865 static void put_opts_in_local_vars(Command_Options* copts)
866 {
867 Parse_Options opts = copts->popts;
868 local.verbosity = parse_options_get_verbosity(opts);
869 local.debug = parse_options_get_debug(opts);
870 local.dialect = parse_options_get_dialect(opts);
871 local.test = parse_options_get_test(opts);
872 local.timeout = parse_options_get_max_parse_time(opts);;
873 local.memory = parse_options_get_max_memory(opts);;
874 local.linkage_limit = parse_options_get_linkage_limit(opts);
875 local.islands_ok = parse_options_get_islands_ok(opts);
876 local.repeatable_rand = parse_options_get_repeatable_rand(opts);
877 local.spell_guess = parse_options_get_spell_guess(opts);
878 local.short_length = parse_options_get_short_length(opts);
879 local.cost_model = parse_options_get_cost_model_type(opts);
880 local.max_cost = parse_options_get_disjunct_cost(opts);
881 local.use_sat_solver = parse_options_get_use_sat_parser(opts);
882
883 local.screen_width = (int)copts->screen_width;
884 local.echo_on = copts->echo_on;
885 local.batch_mode = copts->batch_mode;
886 local.panic_mode = copts->panic_mode;
887 local.allow_null = copts->allow_null;
888 local.display_on = copts->display_on;
889 local.display_walls = copts->display_walls;
890 local.display_postscript = copts->display_postscript;
891 local.display_ps_header = copts->display_ps_header;
892 local.display_constituents = copts->display_constituents;
893 local.display_wordgraph = copts->display_wordgraph;
894
895 local.display_bad = copts->display_bad;
896 local.display_disjuncts = copts->display_disjuncts;
897 local.display_links = copts->display_links;
898
899 local.display_morphology = parse_options_get_display_morphology(opts);
900 }
901
put_local_vars_in_opts(Command_Options * copts)902 static void put_local_vars_in_opts(Command_Options* copts)
903 {
904 Parse_Options opts = copts->popts;
905 parse_options_set_verbosity(opts, local.verbosity);
906 parse_options_set_debug(opts, local.debug);
907 parse_options_set_test(opts, local.test);
908 parse_options_set_dialect(opts, local.dialect);
909 parse_options_set_max_parse_time(opts, local.timeout);
910 parse_options_set_max_memory(opts, local.memory);
911 parse_options_set_linkage_limit(opts, local.linkage_limit);
912 parse_options_set_islands_ok(opts, local.islands_ok);
913 parse_options_set_repeatable_rand(opts, local.repeatable_rand);
914 parse_options_set_spell_guess(opts, local.spell_guess);
915 parse_options_set_short_length(opts, local.short_length);
916 parse_options_set_cost_model_type(opts, local.cost_model);
917 parse_options_set_disjunct_cost(opts, local.max_cost);
918 #ifdef USE_SAT_SOLVER
919 parse_options_set_use_sat_parser(opts, local.use_sat_solver);
920 #endif
921 parse_options_set_display_morphology(opts, local.display_morphology);
922
923 copts->screen_width = (size_t)local.screen_width;
924 copts->echo_on = local.echo_on;
925 copts->batch_mode = local.batch_mode;
926 copts->panic_mode = local.panic_mode;
927 copts->allow_null = local.allow_null;
928 copts->display_on = local.display_on;
929 copts->display_walls = local.display_walls;
930 copts->display_postscript = local.display_postscript;
931 copts->display_ps_header = local.display_ps_header;
932 copts->display_constituents = local.display_constituents;
933 copts->display_wordgraph = local.display_wordgraph;
934
935 copts->display_bad = local.display_bad;
936 copts->display_disjuncts = local.display_disjuncts;
937 copts->display_links = local.display_links;
938 }
939
issue_special_command(const char * line,Command_Options * opts,Dictionary dict)940 int issue_special_command(const char * line, Command_Options* opts, Dictionary dict)
941 {
942 int rc;
943 Parse_Options save = NULL;
944
945 if (strncmp(line, "panic_", 6) == 0)
946 {
947 line += 6;
948 save = opts->popts;
949 opts->popts = opts->panic_opts;
950 }
951
952 put_opts_in_local_vars(opts);
953 char *cline = strdup(line);
954 rc = x_issue_special_command(cline, opts, dict);
955 put_local_vars_in_opts(opts);
956 /* Read back:
957 * - So we can see if the option has actually got changed.
958 * - We need non-stale addresses for the test and debug variables. */
959 put_opts_in_local_vars(opts);
960 free(cline);
961
962 if (save) opts->popts = save;
963 return rc;
964 }
965
966
command_options_create(void)967 Command_Options* command_options_create(void)
968 {
969 Command_Options* co = malloc(sizeof (Command_Options));
970 co->popts = parse_options_create();
971 co->panic_opts = parse_options_create();
972
973 /* "Unlimited" screen width when writing to a file, auto-updated
974 * later, when writing to a tty. */
975 co->screen_width = 16381;
976 co->allow_null = true;
977 co->batch_mode = false;
978 co->echo_on = false;
979 co->panic_mode = false;
980 co->display_on = true;
981 co->display_walls = false;
982 co->display_postscript = false;
983 co->display_ps_header = false;
984 co->display_constituents = NO_DISPLAY;
985
986 co->display_bad = false;
987 co->display_disjuncts = false;
988 co->display_links = false;
989 co->display_wordgraph = 0;
990 return co;
991 }
992
command_options_delete(Command_Options * co)993 void command_options_delete(Command_Options* co)
994 {
995 parse_options_delete(co->panic_opts);
996 parse_options_delete(co->popts);
997 free(co);
998 }
999