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