/*************************************************************************/
/* Copyright (c) 2004 */
/* Daniel Sleator, David Temperley, and John Lafferty */
/* Copyright (c) 2008, 2009, 2011, 2014 Linas Vepstas */
/* All rights reserved */
/* */
/* Use of the link grammar parsing system is subject to the terms of the */
/* license set forth in the LICENSE file included with this software. */
/* This license allows free redistribution and use in source and binary */
/* forms, with or without modification, subject to certain conditions. */
/* */
/*************************************************************************/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <wctype.h>
#include "command-line.h"
#include "parser-utilities.h"
static struct
{
int verbosity;
char * debug;
char * test;
char * dialect;
int timeout;
int memory;
int linkage_limit;
int islands_ok;
int repeatable_rand;
int spell_guess;
int short_length;
int batch_mode;
int panic_mode;
int allow_null;
#if USE_SAT_SOLVER
int use_sat_solver;
#endif
int echo_on;
Cost_Model_type cost_model;
float max_cost;
int screen_width;
int display_on;
ConstituentDisplayStyle display_constituents;
int display_postscript;
int display_ps_header;
int display_bad;
int display_links;
int display_walls;
int display_disjuncts;
int display_morphology;
int display_wordgraph;
panic_options panic;
} local, saved_defaults;
static const char *value_type[] =
{
"(integer) ", "(Boolean) ", "(float) ", "(string) ", "(command) ", ""
};
static int clear_cmd(const Switch*, int);
static int exit_cmd(const Switch*, int);
static int file_cmd(const Switch*, int);
static int help_cmd(const Switch*, int);
static int info_cmd(const Switch*, int);
static int panic_variables_cmd(const Switch*, int);
static int variables_cmd(const Switch*, int);
Switch default_switches[] =
{
{"bad", Bool, "Display of bad linkages", &local.display_bad},
{"batch", Bool, "Batch mode", &local.batch_mode},
{"constituents", Int, "Generate constituent output", &local.display_constituents},
{"cost-model", Int, UNDOC "Cost model used for ranking", &local.cost_model},
{"cost-max", Float, "Largest cost to be considered", &local.max_cost},
{"debug", String, "Comma-separated function names to debug", &local.debug},
{"dialect", String, "Comma-separated dialects", &local.dialect},
{"disjuncts", Bool, "Display of disjuncts used", &local.display_disjuncts},
{"echo", Bool, "Echoing of input sentence", &local.echo_on},
{"graphics", Bool, "Graphical display of linkage", &local.display_on},
{"islands-ok", Bool, "Use of null-linked islands", &local.islands_ok},
{"limit", Int, "The maximum linkages processed", &local.linkage_limit},
{"links", Bool, "Display of complete link data", &local.display_links},
{"memory", Int, UNDOC "Max memory allowed", &local.memory},
{"morphology", Bool, "Display word morphology", &local.display_morphology},
{"null", Bool, "Allow null links", &local.allow_null},
{"panic", Bool, "Use of \"panic mode\"", &local.panic_mode},
{"panic_short", Int, "Max length of all links", &local.panic.short_length},
{"panic_cost-max", Float, "Largest cost to be considered", &local.panic.max_cost},
{"panic_limit", Int, "The maximum linkages processed", &local.panic.linkage_limit},
{"panic_max-null-count", Int, "Max number of null links allowed", &local.panic.max_null_count},
{"panic_spell", Int, "Up to this many spell-guesses per unknown word", &local.panic.spell_guess},
{"panic_timeout", Int, "Abort panic parsing after this many seconds", &local.panic.timeout},
{"postscript", Bool, "Generate postscript output", &local.display_postscript},
{"ps-header", Bool, "Generate postscript header", &local.display_ps_header},
{"rand", Bool, "Use repeatable random numbers", &local.repeatable_rand},
{"short", Int, "Max length of short links", &local.short_length},
#if defined HAVE_HUNSPELL || defined HAVE_ASPELL
{"spell", Int, "Up to this many spell-guesses per unknown word", &local.spell_guess},
#endif /* HAVE_HUNSPELL */
{"test", String, "Comma-separated test features", &local.test},
{"timeout", Int, "Abort parsing after this many seconds", &local.timeout},
#ifdef USE_SAT_SOLVER
{"use-sat", Bool, "Use Boolean SAT-based parser", &local.use_sat_solver},
#endif /* USE_SAT_SOLVER */
{"verbosity", Int, "Level of detail in output", &local.verbosity},
{"walls", Bool, "Display wall words", &local.display_walls},
{"width", Int, "The width of the display", &local.screen_width},
{"wordgraph", Int, "Display sentence word-graph", &local.display_wordgraph},
{"clear", Cmd, "Clear the AtomSpace cache", clear_cmd},
{"exit", Cmd, "Exit the program", exit_cmd},
{"file", Cmd, "Read input from the specified filename", file_cmd},
{"help", Cmd, "List the commands and what they do", help_cmd},
{"panic_variables", Cmd, "List panic mode setup", panic_variables_cmd},
{"quit", Cmd, UNDOC "Exit the program", exit_cmd},
{"variables", Cmd, "List user-settable variables and their functions", variables_cmd},
{"!", Cmd, UNDOC "Print information on dictionary words", info_cmd},
{NULL, Cmd, NULL, NULL}
};
/*
* Record the parse options default values.
*
* Set the string parse options to their static default value so they
* don't point to a static (test, debug) or dynamic (dialect) memory of
* the library (but anyway we need to do that for the test,debug and
* verbosity parse options because they might have been set by command
* line arguments just before this function is invoked.)
*/
void save_default_opts(Command_Options *copts)
{
put_opts_in_local_vars(copts);
saved_defaults = local;
// XXX Set defaults assuming stable library settings
saved_defaults.test = (char *)"";
saved_defaults.debug = (char *)"";
saved_defaults.dialect = (char *)"";
saved_defaults.verbosity = 1;
}
static void restore_default_local_vars(void)
{
local = saved_defaults;
}
// Return the value of the static verbosity.
// This avoids the need to define "verbosity" as extern, which may clash
// with the library "verbosity", and make the parse options "verbosity"
// available before the main loop in link-parser.c.
int get_verbosity(void)
{
return local.verbosity;
}
/**
* Gets rid of all the white space in the string s.
*/
static void clean_up_string(char * s)
{
char * x, * y;
wchar_t p;
size_t len, w;
mbstate_t state;
memset (&state, 0, sizeof(mbstate_t));
len = strlen(s);
y = x = s;
while(*x != '\0')
{
w = mbrtowc(&p, x, len, &state);
if (0 == w) break;
if (0 > (ssize_t)w)
{
prt_error("Error: Unable to process UTF8 command input string.\n");
break;
}
len -= w;
if (!iswspace(p)) {
while(w) { *y = *x; x++; y++; w--; }
} else {
x += w;
}
}
*y = '\0';
}
/**
* Return TRUE if s points to a number:
* optional + or - followed by 1 or more
* digits.
*/
static bool is_numerical_rhs(char *s)
{
wchar_t p;
size_t len, w;
mbstate_t state;
memset (&state, 0, sizeof(mbstate_t));
len = strlen(s);
if (*s=='+' || *s == '-') s++;
if (*s == '\0') return false;
for (; *s != '\0'; s+=w)
{
w = mbrtowc(&p, s, len, &state);
if (0 == w) break;
if (0 > (ssize_t)w)
{
prt_error("Error: Unable to process UTF8 command input string.\n");
break;
}
len -= w;
if (!iswdigit(p)) return false;
}
return true;
}
static int ival(Switch s)
{
return *((int *) s.ptr);
}
static void setival(Switch s, int val)
{
*((int *) s.ptr) = val;
}
/**
* Return the value description for the given switch.
*/
static const char *switch_value_description(const Switch *as)
{
if (Bool == as->param_type)
return ival(*as) ? " (On)" : " (Off)";
if (Int == as->param_type)
return (-1 == ival(*as)) ? " (Unlimited)" : "";
return "";
}
/**
* Return a static buffer with a string value of the given switch.
*
* Since the static buffer is overwritten on each call, this function
* should not be used more than once as an argument of the same function.
*/
static const char *switch_value_string(const Switch *as)
{
static char buf[128]; /* Size of buf is much more than we need */
switch (as->param_type)
{
case Float: /* Float point print! */
snprintf(buf, sizeof(buf), "%.3f", *((float *)as->ptr));
break;
case Bool:
/* FALLTHRU */
case Int:
/* FALLTHRU (why another one is needed?) */
snprintf(buf, sizeof(buf), "%d", ival(*as));
break;
case String:
#if 0 /* It seems it is not a good idea. */
if ((NULL == *(char **)as->ptr) || ('\0' == **(char **)as->ptr))
strcpy(buf, "(not set)");
else
#endif
snprintf(buf, sizeof(buf), "%s", *(char **)as->ptr);
break;
case Cmd:
buf[0] = '\0'; /* No value to print. */
break;
default:
/* Internal error. */
snprintf(buf, sizeof(buf), "Unknown type %d\n", (int)as->param_type);
}
return buf;
}
#define HELPFILE_BASE "command-help-"
#define HELPFILE_EXT ".txt"
#define HELPFILE_LANG_TEMPLATE "LL" /* we use only the 2-letter language code */
#define HELPFILE_LANG_TEMPLATE_SIZE (sizeof(HELPFILE_LANG_TEMPLATE)-1)
#define HELPFILE_TEMPLATE_SIZE \
(sizeof(HELPFILE_BASE HELPFILE_EXT)+HELPFILE_LANG_TEMPLATE_SIZE)
#define DEFAULT_HELP_LANG "en"
static FILE *help_file;
/* Used in atexit() below. */
static void close_help_file(void)
{
fclose(help_file);
}
/**
* On each call, return the next locale to try for constructing the help
* file name (NULL if no more).
*
* @param nextlang true on calls to get the next language.
* @return Locale to try, NULL if no more.
*
* This function should be called until it returns NULL (it order to
* free memory that it may allocate).
* After it returns NULL, the code that uses it below doesn't call it again.
*
* See "Specifying a Priority List of Languages":
* www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html
*/
static const char *get_next_locale(void)
{
enum state
{Initial_s, Language_s, Next_language_s, Default_language_s, Final_s};
static int state = Initial_s;
static char *language;
char *lc_all;
const char *lang = NULL;
while (1)
{
switch (state)
{
case Initial_s:
lc_all = getenv("LC_ALL");
if ((NULL != lc_all) && (0 == strcmp(lc_all, "C")))
{
/* LC_ALL=C */
state = Default_language_s;
continue;
}
if ((NULL == lc_all) || ('\0' == *lc_all))
{
/* LC_ALL= */
lang = getenv("LANG");
if ((NULL != lang) && (0 == strcmp(lang, "C")))
{
/* LANG=C */
state = Default_language_s;
continue;
}
}
/* LC_ALL=x */
state = Language_s;
continue;
case Language_s:
language = getenv("LANGUAGE");
if ((language == NULL) || ('\0' == *language))
{
/* LANGUAGE= */
language = NULL; /* so it doesn't get freed at Finals_s */
state = Default_language_s;
if ((NULL != lang) && ('\0' != *lang))
{
/* LANG=x */
return lang;
}
/* LANG= */
continue;
}
/* LANGUAGE=x:y:z */
state = Next_language_s;
language = strdup(language); /* strdup() for strtok() */
return strtok(language, ":");
break;
case Next_language_s:
{
char *next_language = strtok(NULL, ":");
if (NULL == next_language)
{
/* LANGUAGE tokens exhausted */
state = Default_language_s;
continue;
}
/* Unchanged state. */
return next_language;
}
break;
case Default_language_s:
state = Final_s;
return DEFAULT_HELP_LANG;
case Final_s:
free(language);
state = Initial_s;
break;
/* NULL is returned below. */
}
break;
}
return NULL;
}
static FILE *open_help_file(int verbosity)
{
char *help_filename;
if (NULL != help_file)
{
rewind(help_file);
return help_file;
}
atexit(close_help_file);
/* Construct the help filename from its template. */
help_filename = malloc(HELPFILE_TEMPLATE_SIZE);
strcpy(help_filename, HELPFILE_BASE);
char *ll_pos = &help_filename[strlen(help_filename)];
strcpy(ll_pos, HELPFILE_LANG_TEMPLATE HELPFILE_EXT);
const char *ll;
while ((ll = get_next_locale()))
{
if (NULL != help_file)
continue; /* until get_next_locale() returns NULL */
memcpy(ll_pos, ll, HELPFILE_LANG_TEMPLATE_SIZE);
help_file = linkgrammar_open_data_file(help_filename);
}
if ((NULL == help_file) && (verbosity == D_USER_FILES))
{
prt_error("Error: Cannot open help file '%s': %s\n",
help_filename, strerror(errno));
}
free(help_filename);
return help_file;
}
/**
* Print basic info: name, description, current value and type.
* If `is_completion` is true, also display the variable value, and use
* fixed fields for the value related info and the description.
* This is intended for use from the command completion code.
*
* The display format is ([] denotes optional - if is_completion is true):
* varname[=varvalue] (vartype) - description
*/
void display_1line_help(const Switch *sp, bool is_completion)
{
int undoc = !!(UNDOC[0] == sp->description[0]);
int vtw = 0; /* value_type field width */
int vnw = 0; /* varname field width */
bool display_eq = is_completion && (Cmd != sp->param_type);
const char *value = "";
if (is_completion)
{
vtw = 10;
vnw = 18;
if (Cmd != sp->param_type)
value = switch_value_string(sp);
}
int n; /* actual varname and optional varvalue print length */
n = printf("%s%s%s", sp->string, display_eq ? "=" : " ", value);
if (is_completion) printf("%*s", MAX(0, vnw - n), "");
printf("%*s- %s\n", vtw, value_type[sp->param_type], sp->description+undoc);
}
static void display_help(const Switch *sp, Command_Options *copts)
{
char line[MAX_INPUT_LINE]; /* Maximum number of character in a help file line */
display_1line_help(sp, /*is_completion*/false);
if (Cmd != sp->param_type)
{
printf("Current value: %s\n", switch_value_string(sp));
restore_default_local_vars();
printf("Default value: %s\n", switch_value_string(sp));
put_opts_in_local_vars(copts);
}
FILE *hf = open_help_file(local.verbosity);
if (NULL == hf)
{
prt_error("Warning: Help file not found\n");
return;
}
bool help_found = false;
while (!help_found && (NULL != fgets(line, sizeof(line), hf)))
{
if ('[' != line[0]) continue;
{
#define CMDTAG_SEP ", \t]"
/* Allow for several names to map to the same help text,
* in a hope that the same help text can be used for the
* language bindings too (which have different option names
* from historical reasons).
* Note: We suppose the lines are not longer than MAX_INPUT_LINE.
* Longer lines may render the help text incorrectly.
* FIXME: Add command reference notation in the help text if
* needed.
* Help file format:
* % comment
* [cmd1 cmd2 ...]
* text ...
* [nextcmd ...]
* text ...
*/
char *t = strtok(line+1, CMDTAG_SEP);
if (NULL == t) continue;
do {
if (0 == strcasecmp(t, sp->string))
{
help_found = true;
break;
}
t = strtok(NULL, CMDTAG_SEP);
} while (NULL != t);
}
}
if (ferror(hf))
{
prt_error("Error: Reading help file: %s\n", strerror(errno));
return;
}
if (feof(hf))
{
if (local.verbosity == D_USER_FILES)
prt_error("Error: Cannot find command \"%s\" in help file\n",
sp->string);
}
else
{
help_found = false;
bool issue_blank_line = false;
while (NULL != fgets(line, sizeof(line), hf))
{
const size_t len = strlen(line);
if ((MAX_INPUT_LINE == len-1) && '\n' != line[MAX_INPUT_LINE-2])
{
prt_error("Warning: Help-file text line too long at offset %ld\n",
ftell(hf));
}
if (COMMENT_CHAR == line[0]) continue;
if ('[' == line[0]) break;
/* Suppress the ending blank lines of the help text. */
if (strspn(line, WHITESPACE) == len) /* we encountered a blank line */
{
issue_blank_line = true;
continue;
}
if (!help_found)
{
printf("\n"); /* issue a blank line separator after basic info */
help_found = true;
}
/* We have a line to print. Print a blank line before it if needed. */
if (issue_blank_line)
{
issue_blank_line = false;
printf("\n");
}
printf("%s", line);
}
if (!help_found) /* the command tag has no help text */
prt_error("Info: No help text found for command \"%s\"\n", sp->string);
}
}
#define URL_LINE "%-20s %s\n"
void print_url_info(void)
{
printf("\n");
#ifdef OVERVIEW
printf(URL_LINE, "Overview:", OVERVIEW);
#endif /* OVERVIEW */
#ifdef PACKAGE_URL
printf(URL_LINE, "Home page:", PACKAGE_URL);
printf(URL_LINE, "Documentation:", PACKAGE_URL"/dict/");
#endif /* PACKAGE_URL */
#ifdef DISCUSSION_GROUP
printf(URL_LINE, "Discussion group:", DISCUSSION_GROUP);
#endif /* DISCUSSION_GROUP */
#ifdef PACKAGE_BUGREPORT
printf(URL_LINE, "Report bugs to:" , PACKAGE_BUGREPORT"/issues");
#endif /* PACKAGE_BUGREPORT */
}
static int help_cmd(const Switch *uc, int n)
{
printf("Special commands always begin with \"!\". Command and variable names\n");
printf("can be abbreviated. Here is a list of the commands:\n\n");
printf(" !help command Show a detailed help for the given command.\n");
for (int i = 0; uc[i].string != NULL; i++)
{
if (Cmd != uc[i].param_type) continue;
if (UNDOC[0] == uc[i].description[0]) continue;
printf(" !%-15s ", uc[i].string);
printf("%s.\n", uc[i].description);
}
printf("\n");
printf(" !!<string> Print all the dictionary words that match <string>.\n");
printf(" A wildcard * may be used to find multiple matches.\n");
printf(" Issue \"!help !\" for more details.\n");
printf("\n");
printf(" !<var> Toggle the specified Boolean variable.\n");
printf(" !<var>=<val> Assign that value to that variable.\n");
print_url_info();
return 'c';
}
void setup_panic_parse_options(Command_Options *copts, int sentence_length)
{
Parse_Options opts = copts->popts;
parse_options_reset_resources(opts);
parse_options_set_max_parse_time(opts, local.panic.timeout);
/* Use panic_cost if it is greater than the current cost. */
parse_options_set_disjunct_cost(opts,
MAX(local.max_cost, local.panic.max_cost));
/* In case !null=0, min_null_count and max_null_count are already 0 as
* needed. In case !null=1, the parsing is done by default in 2 steps,
* when on the first step nulls are not allowed and in the second step
* nulls are allowed, and any of these steps may fail (the code is
* currently kept this way to make code tests easy). Since the code is
* not structured to potentially make a 2-step panic parsing, the panic
* parsing for the case of !null=1 will be done in one step.
* Note that min_null_count is always already set as needed. */
if (copts->allow_null)
{
/* Restrict max_null_count. */
parse_options_set_max_null_count(opts,
MIN(sentence_length, local.panic.max_null_count));
}
/* Restrict short_length. */
parse_options_set_short_length(opts,
MIN(local.short_length, local.panic.short_length));
parse_options_set_all_short_connectors(opts, 1);
/* Restrict spell. */
parse_options_set_spell_guess(opts,
MIN(local.spell_guess, local.panic.spell_guess));
if (local.verbosity > 1)
{
int n = fprintf(stdout, "Panic mode setup: "); /* Remember alignment. */
fprintf(stdout,
"!cost-max=%.2f !limit=%d !timeout=%d " "!spell=%d !short=%d\n"
"%*s(all_short=%d min_null_count=%d max_null_count=%d)\n",
parse_options_get_disjunct_cost(opts),
parse_options_get_linkage_limit(opts),
parse_options_get_max_parse_time(opts),
parse_options_get_spell_guess(opts),
parse_options_get_short_length(opts),
n, "", /* Align to legend length. */
parse_options_get_all_short_connectors(opts),
parse_options_get_min_null_count(opts),
parse_options_get_max_null_count(opts));
}
}
static int panic_variables_cmd(const Switch *uc, int n)
{
printf(" Variable Controls Value\n");
printf(" -------- -------- -----\n");
for (int i = 0; uc[i].string != NULL; i++)
{
if (Cmd == uc[i].param_type) continue;
if (strncasecmp("panic_", uc[i].string, 6) != 0) continue;
if (UNDOC[0] == uc[i].description[0]) continue;
printf(" %-21s", uc[i].string);
printf("%-*s", FIELD_WIDTH(uc[i].description, 46), uc[i].description);
printf("%5s", switch_value_string(&uc[i]));
printf("%s\n", switch_value_description(&uc[i]));
}
printf("\n");
printf("Set a variable as so: \"!panic_timeout=10\".\n");
printf("Get detailed help on a variable with: \"!help var\".\n");
return 'c';
}
static int variables_cmd(const Switch *uc, int n)
{
printf(" Variable Controls Value\n");
printf(" -------- -------- -----\n");
for (int i = 0; uc[i].string != NULL; i++)
{
if (Cmd == uc[i].param_type) continue;
if (strncasecmp("panic_", uc[i].string, 6) == 0) continue;
if (UNDOC[0] == uc[i].description[0]) continue;
printf(" %-13s", uc[i].string);
printf("%-*s", FIELD_WIDTH(uc[i].description, 46), uc[i].description);
printf("%5s", switch_value_string(&uc[i]));
printf("%s\n", switch_value_description(&uc[i]));
}
printf("\n");
printf("Toggle a Boolean variable as so: \"!batch\"; ");
printf("Set a variable as so: \"!width=100\".\n");
printf("Get detailed help on a variable with: \"!help var\".\n");
return 'c';
}
static int exit_cmd(const Switch *uc, int n)
{
return 'e';
}
static int clear_cmd(const Switch *uc, int n)
{
return 'k';
}
static int file_cmd(const Switch *uc, int n)
{
return 'f';
}
static int info_cmd(const Switch *uc, int n)
{
/* Dummy definition - the work is done done in
* x_issue_special_command() (see '!' there). */
return 'c';
}
const char helpmsg[] = "Type \"!help\" or \"!variables\".";
/**
* Handle a request for a particular command help.
*/
static int handle_help_command(const Switch *as, char *line,
Command_Options *copts)
{
char *dupline = strdup(line);
int count, j;
int rc = 0;
/* If we are here, it is not a command-line parameter. */
char *s = strtok(dupline, WHITESPACE);
if ((s != NULL) && strncasecmp(s, "help", strlen(s)) == 0)
{
s = strtok(NULL, WHITESPACE);
if (s != NULL)
{
/* This is a help request for the command name at s. */
j = -1; /* command index */
count = 0; /* number of matching commands */
/* Is it a unique abbreviation? */
for (int i = 0; as[i].string != NULL; i++)
{
if (strncasecmp(s, as[i].string, strlen(s)) == 0)
{
j = i;
if (strlen(as[i].string) == strlen(s))
{
count = 1;
break;
}
count++;
}
}
if (count == 1)
{
display_help(&as[j], copts);
rc = 'c'; /* Command done. */
}
else
{
rc = -1; /* Error indication. */
if (count > 1)
prt_error("Error: Ambiguous command: \"%s\". %s\n", s, helpmsg);
else
prt_error("Error: Undefined command: \"%s\". %s\n", s, helpmsg);
}
}
}
free(dupline);
return rc;
}
static int x_issue_special_command(char * line, Command_Options *copts, Dictionary dict)
{
char *s, *x, *y;
int count, j;
const Switch *as = default_switches;
line = &line[strspn(line, WHITESPACE)]; /* Trim initial whitespace */
if (NULL != dict) /* No dict if we are called from the command line */
{
int rc = handle_help_command(as, line, copts);
if (rc != 0) return rc;
}
s = line;
if (s[0] == '!')
{
Parse_Options opts = copts->popts;
char *out;
out = dict_display_word_info(dict, s+1, opts);
if (NULL != out)
{
printf("%s\n", out);
free(out);
out = dict_display_word_expr(dict, s+1, opts);
if (NULL != out)
{
printf("%s", out);
free(out);
}
else
{
prt_error("Error: '%s': Internal Error: Missing expression.\n", s+1);
}
}
else
{
printf("Token \"%s\" matches nothing in the dictionary.\n", s+1);
}
return 'c';
}
if (strchr(s, '=') == NULL)
{
j = -1;
count = 0;
/* Look for Boolean flippers or command abbreviations. */
for (int i = 0; as[i].string != NULL; i++)
{
if (((Bool == as[i].param_type) || (Cmd == as[i].param_type)) &&
strncasecmp(s, as[i].string, strcspn(s, WHITESPACE)) == 0)
{
j = i;
if (strlen(as[i].string) == strcspn(s, WHITESPACE))
{
count = 1;
break;
}
if (UNDOC[0] == as[i].description[0]) continue;
count++;
}
}
if (count > 1)
{
prt_error("Error: Ambiguous command \"%s\". %s\n", s, helpmsg);
return -1;
}
if (count == 1)
{
/* Flip Boolean value. */
if (Bool == as[j].param_type)
{
size_t junk = strcspn(s, WHITESPACE);
if (junk != strlen(s))
{
prt_error("Error: Junk after a boolean variable: \"%s\". %s\n",
&s[junk], helpmsg);
return -1;
}
setival(as[j], (0 == ival(as[j])));
int undoc = !!(UNDOC[0] == as[j].description[0]);
printf("%s turned %s.\n",
as[j].description+undoc, (ival(as[j]))? "on" : "off");
return 'c';
}
/* Found an abbreviated, but it wasn't a Boolean.
* It means it is a user command, to be handled below. */
return ((int (*)(const Switch*, int)) (as[j].ptr))(as, j);
}
}
clean_up_string(line);
/* Test here for an equation i.e. does the command line hold an equals sign? */
for (x=s; (*x != '=') && (*x != '\0'); x++)
;
if (*x == '=')
{
*x = '\0';
y = x+1;
x = s;
/* now x is the first word and y is the rest */
/* Figure out which command it is .. it'll be the j'th one */
j = -1;
count = 0;
for (int i = 0; as[i].string != NULL; i++)
{
if (Cmd == as[i].param_type) continue;
if (strncasecmp(x, as[i].string, strlen(x)) == 0)
{
j = i;
if (strlen(as[i].string) == strlen(x))
{
count = 1;
break;
}
if (UNDOC[0] == as[i].description[0]) continue;
count ++;
}
}
if (j<0)
{
prt_error("Error: There is no user variable called \"%s\".\n", x);
return -1;
}
if (count > 1)
{
prt_error("Error: Ambiguous variable \"%s\". %s\n", x, helpmsg);
return -1;
}
if ((as[j].param_type == Int) || (as[j].param_type == Bool))
{
int val = -1;
if (is_numerical_rhs(y)) val = atoi(y);
if ((0 == strcasecmp(y, "true")) || (0 == strcasecmp(y, "t"))) val = 1;
if ((0 == strcasecmp(y, "false")) || (0 == strcasecmp(y, "f"))) val = 0;
if ((val < 0) ||
((as[j].param_type == Bool) && (val != 0) && (val != 1)))
{
prt_error("Error: Invalid value \"%s\" for variable \"%s\". %s\n",
y, as[j].string, helpmsg);
return -1;
}
setival(as[j], val);
printf("%s set to %d\n", as[j].string, val);
return 'c';
}
else
if (as[j].param_type == Float)
{
char *err;
float val = strtof(y, &err);
if (('\0' == *y) || ('\0' != *err))
{
prt_error("Error: Invalid value \"%s\" for variable \"%s\". %s\n",
y, as[j].string, helpmsg);
return -1;
}
*((float *) as[j].ptr) = val;
printf("%s set to %5.3f\n", as[j].string, val);
return 'c';
}
else
if (as[j].param_type == String)
{
*((char **) as[j].ptr) = y;
printf("%s set to %s\n", (char *)as[j].string, y);
return 'c';
}
else
{
prt_error("Error: Internal error: Unknown variable type %d\n",
(int)as[j].param_type);
return -1;
}
}
/* Look for valid commands, but ones that needed an argument */
j = -1;
count = 0;
for (int i = 0; as[i].string != NULL; i++)
{
if ((Bool != as[i].param_type) && (Cmd != as[i].param_type) &&
strncasecmp(s, as[i].string, strlen(s)) == 0)
{
if ((UNDOC[0] == as[i].description[0]) &&
(strlen(as[i].string) != strlen(s))) continue;
j = i;
count++;
}
}
if (0 < count)
{
prt_error("Error: Variable \"%s\" requires a value. Try \"!help %s\".\n",
as[j].string, as[j].string);
return -1;
}
prt_error("Error: I can't interpret \"%s\" as a command. Try \"!help\" or \"!variables\".\n", line);
return -1;
}
void put_opts_in_local_vars(Command_Options* copts)
{
Parse_Options opts = copts->popts;
local.verbosity = parse_options_get_verbosity(opts);
local.debug = parse_options_get_debug(opts);
local.dialect = parse_options_get_dialect(opts);
local.test = parse_options_get_test(opts);
local.timeout = parse_options_get_max_parse_time(opts);;
local.memory = parse_options_get_max_memory(opts);;
local.linkage_limit = parse_options_get_linkage_limit(opts);
local.islands_ok = parse_options_get_islands_ok(opts);
local.repeatable_rand = parse_options_get_repeatable_rand(opts);
local.spell_guess = parse_options_get_spell_guess(opts);
local.short_length = parse_options_get_short_length(opts);
local.cost_model = parse_options_get_cost_model_type(opts);
local.max_cost = parse_options_get_disjunct_cost(opts);
#if USE_SAT_SOLVER
local.use_sat_solver = parse_options_get_use_sat_parser(opts);
#endif
local.screen_width = (int)copts->screen_width;
local.echo_on = copts->echo_on;
local.batch_mode = copts->batch_mode;
local.panic_mode = copts->panic_mode;
local.allow_null = copts->allow_null;
local.display_on = copts->display_on;
local.display_walls = copts->display_walls;
local.display_postscript = copts->display_postscript;
local.display_ps_header = copts->display_ps_header;
local.display_constituents = copts->display_constituents;
local.display_wordgraph = copts->display_wordgraph;
local.display_bad = copts->display_bad;
local.display_disjuncts = copts->display_disjuncts;
local.display_links = copts->display_links;
local.display_morphology = parse_options_get_display_morphology(opts);
local.panic = copts->panic;
}
void put_local_vars_in_opts(Command_Options* copts)
{
Parse_Options opts = copts->popts;
parse_options_set_verbosity(opts, local.verbosity);
parse_options_set_debug(opts, local.debug);
parse_options_set_test(opts, local.test);
parse_options_set_dialect(opts, local.dialect);
parse_options_set_max_parse_time(opts, local.timeout);
parse_options_set_max_memory(opts, local.memory);
parse_options_set_linkage_limit(opts, local.linkage_limit);
parse_options_set_islands_ok(opts, local.islands_ok);
parse_options_set_repeatable_rand(opts, local.repeatable_rand);
parse_options_set_spell_guess(opts, local.spell_guess);
parse_options_set_short_length(opts, local.short_length);
parse_options_set_cost_model_type(opts, local.cost_model);
parse_options_set_disjunct_cost(opts, local.max_cost);
#if USE_SAT_SOLVER
parse_options_set_use_sat_parser(opts, local.use_sat_solver);
#endif
parse_options_set_display_morphology(opts, local.display_morphology);
copts->screen_width = (size_t)local.screen_width;
copts->echo_on = local.echo_on;
copts->batch_mode = local.batch_mode;
copts->panic_mode = local.panic_mode;
copts->allow_null = local.allow_null;
copts->display_on = local.display_on;
copts->display_walls = local.display_walls;
copts->display_postscript = local.display_postscript;
copts->display_ps_header = local.display_ps_header;
copts->display_constituents = local.display_constituents;
copts->display_wordgraph = local.display_wordgraph;
copts->display_bad = local.display_bad;
copts->display_disjuncts = local.display_disjuncts;
copts->display_links = local.display_links;
copts->panic = local.panic;
}
int issue_special_command(const char * line, Command_Options* opts, Dictionary dict)
{
int rc;
put_opts_in_local_vars(opts);
char *cline = strdup(line);
rc = x_issue_special_command(cline, opts, dict);
put_local_vars_in_opts(opts);
/* Read back:
* - So we can see if the option has actually got changed.
* - We need non-stale addresses for the test and debug variables. */
put_opts_in_local_vars(opts);
free(cline);
return rc;
}
Command_Options* command_options_create(void)
{
Command_Options* co = malloc(sizeof (Command_Options));
co->popts = parse_options_create();
/* "Unlimited" screen width when writing to a file, auto-updated
* later, when writing to a tty. */
co->screen_width = 16381;
co->allow_null = true;
co->batch_mode = false;
co->echo_on = false;
co->panic_mode = true;
co->display_on = true;
co->display_walls = false;
co->display_postscript = false;
co->display_ps_header = false;
co->display_constituents = NO_DISPLAY;
co->display_bad = false;
co->display_disjuncts = false;
co->display_links = false;
co->display_wordgraph = 0;
co->panic.max_cost = 4.0f;
co->panic.linkage_limit = 1000;
co->panic.max_null_count = 10;
co->panic.short_length = 12;
co->panic.spell_guess = 0;
co->panic.timeout = 30;
return co;
}
void command_options_delete(Command_Options* co)
{
parse_options_delete(co->popts);
free(co);
}