X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Flanguage%2Fcommand.c;h=c00d94b9c4b3597bcc2416ae0e29fd56018b229f;hb=e9599a2f8e86d986fff1518682ba8942bd67d1c0;hp=397097ab70e40c6bba4410f41079fe4688ca8fbd;hpb=2322678e8fddbbf158b01b2720db2636404bba3b;p=pspp-builds.git diff --git a/src/language/command.c b/src/language/command.c index 397097ab..c00d94b9 100644 --- a/src/language/command.c +++ b/src/language/command.c @@ -1,280 +1,265 @@ -/* PSPP - computes sample statistics. - Copyright (C) 1997-9, 2000 Free Software Foundation, Inc. - Written by Ben Pfaff . +/* PSPP - a program for statistical analysis. + Copyright (C) 1997-9, 2000, 2009 Free Software Foundation, Inc. - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + along with this program. If not, see . */ #include -#include "message.h" -#include "command.h" + +#include + #include #include #include #include -#include "alloc.h" -#include "compiler.h" -#include "dictionary.h" -#include "message.h" -#include "lexer.h" -#include "settings.h" -#include "manager.h" -#include "str.h" -#include "table.h" -#include "variable.h" -#include "procedure.h" - -#if HAVE_UNISTD_H #include -#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if HAVE_SYS_WAIT_H #include #endif +#if HAVE_READLINE +#include +#endif + +#include "xalloc.h" +#include "xmalloca.h" + #include "gettext.h" #define _(msgid) gettext (msgid) #define N_(msgid) msgid -/* Global variables. */ +/* Returns true if RESULT is a valid "enum cmd_result", + false otherwise. */ +static inline bool +cmd_result_is_valid (enum cmd_result result) +{ + return (result == CMD_SUCCESS || result == CMD_EOF || result == CMD_FINISH + || (result >= CMD_PRIVATE_FIRST && result <= CMD_PRIVATE_LAST) + || result == CMD_FAILURE || result == CMD_NOT_IMPLEMENTED + || result == CMD_CASCADING_FAILURE); +} + +/* Returns true if RESULT indicates success, + false otherwise. */ +bool +cmd_result_is_success (enum cmd_result result) +{ + assert (cmd_result_is_valid (result)); + return result > 0; +} -/* A STATE_* constant giving the current program state. */ -int pgm_state; +/* Returns true if RESULT indicates failure, + false otherwise. */ +bool +cmd_result_is_failure (enum cmd_result result) +{ + assert (cmd_result_is_valid (result)); + return result < 0; +} -/* Static variables. */ +/* Command processing states. */ +enum states + { + S_INITIAL = 0x01, /* Allowed before active file defined. */ + S_DATA = 0x02, /* Allowed after active file defined. */ + S_INPUT_PROGRAM = 0x04, /* Allowed in INPUT PROGRAM. */ + S_FILE_TYPE = 0x08, /* Allowed in FILE TYPE. */ + S_ANY = 0x0f /* Allowed anywhere. */ + }; + +/* Other command requirements. */ +enum flags + { + F_ENHANCED = 0x10, /* Allowed only in enhanced syntax mode. */ + F_TESTING = 0x20, /* Allowed only in testing mode. */ + F_KEEP_FINAL_TOKEN = 0x40,/* Don't skip final token in command name. */ + F_ABBREV = 0x80 /* Not a candidate for name completion. */ + }; /* A single command. */ struct command { + enum states states; /* States in which command is allowed. */ + enum flags flags; /* Other command requirements. */ const char *name; /* Command name. */ - int transition[4]; /* Transitions to make from each state. */ - int (*func) (void); /* Function to call. */ - int skip_entire_name; /* If zero, we don't skip the - final token in the command name. */ - short debug; /* Set if this cmd available only in test mode*/ + int (*function) (struct lexer *, struct dataset *); /* Function to call. */ }; /* Define the command array. */ -#define DEFCMD(NAME, T1, T2, T3, T4, FUNC) \ - {NAME, {T1, T2, T3, T4}, FUNC, 1, 0}, -#define DBGCMD(NAME, T1, T2, T3, T4, FUNC) \ - {NAME, {T1, T2, T3, T4}, FUNC, 1, 1}, -#define SPCCMD(NAME, T1, T2, T3, T4, FUNC) \ - {NAME, {T1, T2, T3, T4}, FUNC, 0, 0}, -#define UNIMPL(NAME, T1, T2, T3, T4, DESC) \ - {NAME, {T1, T2, T3, T4}, NULL, 1, 0}, -static const struct command commands[] = +#define DEF_CMD(STATES, FLAGS, NAME, FUNCTION) {STATES, FLAGS, NAME, FUNCTION}, +#define UNIMPL_CMD(NAME, DESCRIPTION) {S_ANY, 0, NAME, NULL}, +static const struct command commands[] = { #include "command.def" }; -#undef DEFCMD -#undef DBGCMD -#undef UNIMPL +#undef DEF_CMD +#undef UNIMPL_CMD +static const size_t command_cnt = sizeof commands / sizeof *commands; -/* Complete the line using the name of a command, - * based upon the current prg_state - */ -char * -pspp_completion_function (const char *text, int state) -{ - static int skip=0; - const struct command *cmd = 0; - - for(;;) - { - if ( state + skip >= sizeof(commands)/ sizeof(struct command)) - { - skip = 0; - return 0; - } - - cmd = &commands[state + skip]; - - if ( cmd->transition[pgm_state] == STATE_ERROR || ( cmd->debug && ! get_testing_mode () ) ) - { - skip++; - continue; - } - - if ( text == 0 || 0 == strncasecmp (cmd->name, text, strlen(text))) - { - break; - } - - skip++; - } - - - return xstrdup(cmd->name); -} - - - -#define COMMAND_CNT (sizeof commands / sizeof *commands) +static bool in_correct_state (const struct command *, enum cmd_state); +static bool report_state_mismatch (const struct command *, enum cmd_state); +static const struct command *find_command (const char *name); +static void set_completion_state (enum cmd_state); /* Command parser. */ -static const struct command *parse_command_name (void); +static const struct command *parse_command_name (struct lexer *lexer); +static enum cmd_result do_parse_command (struct lexer *, struct dataset *, enum cmd_state); -/* Determines whether command C is appropriate to call in this - part of a FILE TYPE structure. */ -static int -FILE_TYPE_okay (const struct command *c UNUSED) -#if 0 +/* Parses an entire command, from command name to terminating + dot. On failure, skips to the terminating dot. + Returns the command's success or failure result. */ +enum cmd_result +cmd_parse_in_state (struct lexer *lexer, struct dataset *ds, + enum cmd_state state) { - int okay = 0; - - if (c->func != cmd_record_type - && c->func != cmd_data_list - && c->func != cmd_repeating_data - && c->func != cmd_end_file_type) - msg (SE, _("%s not allowed inside FILE TYPE/END FILE TYPE."), c->name); - /* FIXME */ - else if (c->func == cmd_repeating_data && fty.type == FTY_GROUPED) - msg (SE, _("%s not allowed inside FILE TYPE GROUPED/END FILE TYPE."), - c->name); - else if (!fty.had_rec_type && c->func != cmd_record_type) - msg (SE, _("RECORD TYPE must be the first command inside a " - "FILE TYPE structure.")); - else - okay = 1; + int result; + + som_new_series (); - if (c->func == cmd_record_type) - fty.had_rec_type = 1; + result = do_parse_command (lexer, ds, state); + if (cmd_result_is_failure (result)) + lex_discard_rest_of_command (lexer); - return okay; -} -#else -{ - return 1; + assert (!proc_is_open (ds)); + unset_cmd_algorithm (); + dict_clear_aux (dataset_dict (ds)); + if (!dataset_end_of_command (ds)) + result = CMD_CASCADING_FAILURE; + + return result; } -#endif -/* Parses an entire PSPP command. This includes everything from the - command name to the terminating dot. Does most of its work by - passing it off to the respective command dispatchers. Only called - by parse() in main.c. */ -int -cmd_parse (void) +enum cmd_result +cmd_parse (struct lexer *lexer, struct dataset *ds) { - const struct command *cp; /* Iterator used to find the proper command. */ - -#if C_ALLOCA - /* The generic alloca package performs garbage collection when it is - called with an argument of zero. */ - alloca (0); -#endif /* C_ALLOCA */ + const struct dictionary *dict = dataset_dict (ds); + return cmd_parse_in_state (lexer, ds, + proc_has_active_file (ds) && + dict_get_var_cnt (dict) > 0 ? + CMD_STATE_DATA : CMD_STATE_INITIAL); +} - /* Null commands can result from extra empty lines. */ - if (token == '.') - return CMD_SUCCESS; - /* Parse comments. */ - if ((token == T_ID && !strcasecmp (tokid, "COMMENT")) - || token == T_EXP || token == '*' || token == '[') +/* Parses an entire command, from command name to terminating + dot. */ +static enum cmd_result +do_parse_command (struct lexer *lexer, + struct dataset *ds, enum cmd_state state) +{ + const struct command *command; + enum cmd_result result; + + /* Read the command's first token. */ + prompt_set_style (PROMPT_FIRST); + set_completion_state (state); + lex_get (lexer); + if (lex_token (lexer) == T_STOP) { - lex_skip_comment (); - return CMD_SUCCESS; + result = CMD_EOF; + goto finish; } - - /* Otherwise the line must begin with a command name, which is - always an ID token. */ - if (token != T_ID) + else if (lex_token (lexer) == '.') { - lex_error (_("expecting command name")); - return CMD_FAILURE; + /* Null commands can result from extra empty lines. */ + result = CMD_SUCCESS; + goto finish; } + prompt_set_style (PROMPT_LATER); + /* Parse the command name. */ - cp = parse_command_name (); - if (cp == NULL) - return CMD_FAILURE; - if (cp->func == NULL) + command = parse_command_name (lexer); + if (command == NULL) { - msg (SE, _("%s is not yet implemented."), cp->name); - while (token && token != '.') - lex_get (); - return CMD_SUCCESS; + result = CMD_FAILURE; + goto finish; } - - /* If we're in a FILE TYPE structure, only certain commands can be - allowed. */ - if (pgm_state == STATE_INPUT - && case_source_is_class (vfm_source, &file_type_source_class) - && !FILE_TYPE_okay (cp)) - return CMD_FAILURE; - - /* Certain state transitions are not allowed. Check for these. */ - assert (pgm_state >= 0 && pgm_state < STATE_ERROR); - if (cp->transition[pgm_state] == STATE_ERROR) + else if (command->function == NULL) { - static const char *state_name[4] = - { - N_("%s is not allowed (1) before a command to specify the " - "input program, such as DATA LIST, (2) between FILE TYPE " - "and END FILE TYPE, (3) between INPUT PROGRAM and END " - "INPUT PROGRAM."), - N_("%s is not allowed within an input program."), - N_("%s is only allowed within an input program."), - N_("%s is only allowed within an input program."), - }; - - msg (SE, gettext (state_name[pgm_state]), cp->name); - return CMD_FAILURE; + msg (SE, _("%s is not yet implemented."), command->name); + result = CMD_NOT_IMPLEMENTED; + goto finish; + } + else if ((command->flags & F_TESTING) && !settings_get_testing_mode ()) + { + msg (SE, _("%s may be used only in testing mode."), command->name); + result = CMD_FAILURE; + goto finish; + } + else if ((command->flags & F_ENHANCED) && settings_get_syntax () != ENHANCED) + { + msg (SE, _("%s may be used only in enhanced syntax mode."), + command->name); + result = CMD_FAILURE; + goto finish; + } + else if (!in_correct_state (command, state)) + { + report_state_mismatch (command, state); + result = CMD_FAILURE; + goto finish; } - /* The structured output manager numbers all its tables. Increment - the major table number for each separate procedure. */ - som_new_series (); - - { - int result; - - /* Call the command dispatcher. */ - err_set_command_name (cp->name); - tab_set_command_name (cp->name); - result = cp->func (); - err_set_command_name (NULL); - tab_set_command_name (NULL); - - /* Perform the state transition if the command completed - successfully (at least in part). */ - if (result != CMD_FAILURE && result != CMD_CASCADING_FAILURE) - { - pgm_state = cp->transition[pgm_state]; + /* Execute command. */ + msg_set_command_name (command->name); + tab_set_command_name (command->name); + result = command->function (lexer, ds); + tab_set_command_name (NULL); + msg_set_command_name (NULL); - if (pgm_state == STATE_ERROR) - { - discard_variables (); - pgm_state = STATE_INIT; - } - } + assert (cmd_result_is_valid (result)); + + finish: + if ( cmd_result_is_failure (result)) + { + const struct source_stream *cs = lex_get_source_stream (lexer); - /* Pass the command's success value up to the caller. */ - return result; - } + if ( source_stream_current_error_mode (cs) == ERRMODE_STOP ) + { + msg (MW, _("Error encountered while ERROR=STOP is effective.")); + result = CMD_CASCADING_FAILURE; + } + } + + return result; } static size_t match_strings (const char *a, size_t a_len, - const char *b, size_t b_len) + const char *b, size_t b_len) { size_t match_len = 0; - - while (a_len > 0 && b_len > 0) + + while (a_len > 0 && b_len > 0) { /* Mismatch always returns zero. */ if (toupper ((unsigned char) *a++) != toupper ((unsigned char) *b++)) @@ -296,21 +281,21 @@ match_strings (const char *a, size_t a_len, non-alphanumeric characters. Words are delimited by spaces. */ static const char * -find_word (const char *string, size_t *word_len) +find_word (const char *string, size_t *word_len) { /* Skip whitespace and asterisks. */ while (isspace ((unsigned char) *string)) string++; /* End of string? */ - if (*string == '\0') + if (*string == '\0') { *word_len = 0; return NULL; } /* Special one-character word? */ - if (!isalnum ((unsigned char) *string)) + if (!isalnum ((unsigned char) *string)) { *word_len = 1; return string; @@ -324,10 +309,10 @@ find_word (const char *string, size_t *word_len) return string; } -/* Returns nonzero if strings A and B can be confused based on +/* Returns true if strings A and B can be confused based on their first three letters. */ -static int -conflicting_3char_prefixes (const char *a, const char *b) +static bool +conflicting_3char_prefixes (const char *a, const char *b) { size_t aw_len, bw_len; const char *aw, *bw; @@ -338,8 +323,8 @@ conflicting_3char_prefixes (const char *a, const char *b) /* Words that are the same don't conflict. */ if (aw_len == bw_len && !buf_compare_case (aw, bw, aw_len)) - return 0; - + return false; + /* Words that are otherwise the same in the first three letters do conflict. */ return ((aw_len > 3 && bw_len > 3) @@ -347,16 +332,16 @@ conflicting_3char_prefixes (const char *a, const char *b) || (bw_len == 3 && aw_len > 3)) && !buf_compare_case (aw, bw, 3); } -/* Returns nonzero if CMD can be confused with another command +/* Returns true if CMD can be confused with another command based on the first three letters of its first word. */ -static int -conflicting_3char_prefix_command (const struct command *cmd) +static bool +conflicting_3char_prefix_command (const struct command *cmd) { - assert (cmd >= commands && cmd < commands + COMMAND_CNT); + assert (cmd >= commands && cmd < commands + command_cnt); return ((cmd > commands && conflicting_3char_prefixes (cmd[-1].name, cmd[0].name)) - || (cmd < commands + COMMAND_CNT + || (cmd < commands + command_cnt && conflicting_3char_prefixes (cmd[0].name, cmd[1].name))); } @@ -391,18 +376,18 @@ cmd_match_words (const struct command *cmd, size_t match_chars = match_strings (word, word_len, words[word_idx], strlen (words[word_idx])); - if (match_chars == 0) + if (match_chars == 0) { /* Mismatch. */ return MISMATCH; } - else if (match_chars == 1 || match_chars == 2) + else if (match_chars == 1 || match_chars == 2) { /* One- and two-character abbreviations are not acceptable. */ - return MISMATCH; + return MISMATCH; } - else if (match_chars == 3) + else if (match_chars == 3) { /* Three-character abbreviations are acceptable in the first word of a command if there are @@ -411,29 +396,29 @@ cmd_match_words (const struct command *cmd, if (word_idx == 0 && conflicting_3char_prefix_command (cmd)) return MISMATCH; } - else /* match_chars > 3 */ + else /* match_chars > 3 */ { /* Four-character and longer abbreviations are always acceptable. */ } } - if (word == NULL && word_idx == word_cnt) + if (word == NULL && word_idx == word_cnt) { /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR"}. */ return COMPLETE_MATCH; } - else if (word == NULL) + else if (word == NULL) { /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR", "BAZ"}. */ - return MISMATCH; + return MISMATCH; } - else + else { /* cmd->name = "FOO BAR BAZ", words[] = {"FOO", "BAR"}. */ if (word[0] == '-' && dash_possible != NULL) *dash_possible = 1; - return PARTIAL_MATCH; + return PARTIAL_MATCH; } } @@ -443,16 +428,16 @@ cmd_match_words (const struct command *cmd, otherwise it is set to 0. */ static int count_matching_commands (char *const words[], size_t word_cnt, - int *dash_possible) + int *dash_possible) { const struct command *cmd; int cmd_match_count; cmd_match_count = 0; *dash_possible = 0; - for (cmd = commands; cmd < commands + COMMAND_CNT; cmd++) - if (cmd_match_words (cmd, words, word_cnt, dash_possible) != MISMATCH) - cmd_match_count++; + for (cmd = commands; cmd < commands + command_cnt; cmd++) + if (cmd_match_words (cmd, words, word_cnt, dash_possible) != MISMATCH) + cmd_match_count++; return cmd_match_count; } @@ -461,23 +446,36 @@ count_matching_commands (char *const words[], size_t word_cnt, a complete match. Returns a null pointer if no such command exists. */ static const struct command * -get_complete_match (char *const words[], size_t word_cnt) +get_complete_match (char *const words[], size_t word_cnt) { const struct command *cmd; - - for (cmd = commands; cmd < commands + COMMAND_CNT; cmd++) - if (cmd_match_words (cmd, words, word_cnt, NULL) == COMPLETE_MATCH) - return cmd; - + + for (cmd = commands; cmd < commands + command_cnt; cmd++) + if (cmd_match_words (cmd, words, word_cnt, NULL) == COMPLETE_MATCH) + return cmd; + return NULL; } +/* Returns the command with the given exact NAME. + Aborts if no such command exists. */ +static const struct command * +find_command (const char *name) +{ + const struct command *cmd; + + for (cmd = commands; cmd < commands + command_cnt; cmd++) + if (!strcmp (cmd->name, name)) + return cmd; + NOT_REACHED (); +} + /* Frees the WORD_CNT words in WORDS. */ static void -free_words (char *words[], size_t word_cnt) +free_words (char *words[], size_t word_cnt) { size_t idx; - + for (idx = 0; idx < word_cnt; idx++) free (words[idx]); } @@ -485,69 +483,71 @@ free_words (char *words[], size_t word_cnt) /* Flags an error that the command whose name is given by the WORD_CNT words in WORDS is unknown. */ static void -unknown_command_error (char *const words[], size_t word_cnt) +unknown_command_error (struct lexer *lexer, char *const words[], size_t word_cnt) { - size_t idx; - size_t words_len; - char *name, *cp; - - words_len = 0; - for (idx = 0; idx < word_cnt; idx++) - words_len += strlen (words[idx]); - - cp = name = xmalloc (words_len + word_cnt + 16); - for (idx = 0; idx < word_cnt; idx++) + if (word_cnt == 0) + lex_error (lexer, _("expecting command name")); + else { - if (idx != 0) - *cp++ = ' '; - cp = stpcpy (cp, words[idx]); - } - *cp = '\0'; + struct string s; + size_t i; - msg (SE, _("Unknown command %s."), name); + ds_init_empty (&s); + for (i = 0; i < word_cnt; i++) + { + if (i != 0) + ds_put_char (&s, ' '); + ds_put_cstr (&s, words[i]); + } - free (name); -} + msg (SE, _("Unknown command %s."), ds_cstr (&s)); + ds_destroy (&s); + } +} /* Parse the command name and return a pointer to the corresponding struct command if successful. If not successful, return a null pointer. */ static const struct command * -parse_command_name (void) +parse_command_name (struct lexer *lexer) { char *words[16]; int word_cnt; int complete_word_cnt; int dash_possible; + if (lex_token (lexer) == T_EXP || + lex_token (lexer) == '*' || lex_token (lexer) == '[') + return find_command ("COMMENT"); + dash_possible = 0; word_cnt = complete_word_cnt = 0; - while (token == T_ID || (dash_possible && token == '-')) + while (lex_token (lexer) == T_ID || (dash_possible && lex_token (lexer) == '-')) { int cmd_match_cnt; - + assert (word_cnt < sizeof words / sizeof *words); - if (token == T_ID) - words[word_cnt] = xstrdup (ds_c_str (&tokstr)); - else + if (lex_token (lexer) == T_ID) + { + words[word_cnt] = ds_xstrdup (lex_tokstr (lexer)); + str_uppercase (words[word_cnt]); + } + else if (lex_token (lexer) == '-') words[word_cnt] = xstrdup ("-"); - str_uppercase (words[word_cnt]); word_cnt++; cmd_match_cnt = count_matching_commands (words, word_cnt, &dash_possible); - if (cmd_match_cnt == 0) + if (cmd_match_cnt == 0) break; - else if (cmd_match_cnt == 1) + else if (cmd_match_cnt == 1) { const struct command *command = get_complete_match (words, word_cnt); - if (command != NULL) + if (command != NULL) { - if (command->skip_entire_name) - lex_get (); - if ( command->debug & !get_testing_mode () ) - goto error; + if (!(command->flags & F_KEEP_FINAL_TOKEN)) + lex_get (lexer); free_words (words, word_cnt); return command; } @@ -558,12 +558,12 @@ parse_command_name (void) if (get_complete_match (words, word_cnt) != NULL) complete_word_cnt = word_cnt; } - lex_get (); + lex_get (lexer); } /* If we saw a complete command name earlier, drop back to it. */ - if (complete_word_cnt) + if (complete_word_cnt) { int pushback_word_cnt; const struct command *command; @@ -575,103 +575,228 @@ parse_command_name (void) /* Figure out how many words we want to keep. We normally want to swallow the entire command. */ pushback_word_cnt = complete_word_cnt + 1; - if (!command->skip_entire_name) + if (command->flags & F_KEEP_FINAL_TOKEN) pushback_word_cnt--; - + /* FIXME: We only support one-token pushback. */ assert (pushback_word_cnt + 1 >= word_cnt); - while (word_cnt > pushback_word_cnt) + while (word_cnt > pushback_word_cnt) { word_cnt--; - if (strcmp (words[word_cnt], "-")) - lex_put_back_id (words[word_cnt]); + if (strcmp (words[word_cnt], "-")) + lex_put_back_id (lexer, words[word_cnt]); else - lex_put_back ('-'); + lex_put_back (lexer, '-'); free (words[word_cnt]); } - if ( command->debug && !get_testing_mode () ) - goto error; - free_words (words, word_cnt); return command; } -error: - unknown_command_error (words, word_cnt); + /* We didn't get a valid command name. */ + unknown_command_error (lexer, words, word_cnt); free_words (words, word_cnt); return NULL; } + +/* Returns true if COMMAND is allowed in STATE, + false otherwise. */ +static bool +in_correct_state (const struct command *command, enum cmd_state state) +{ + return ((state == CMD_STATE_INITIAL && command->states & S_INITIAL) + || (state == CMD_STATE_DATA && command->states & S_DATA) + || (state == CMD_STATE_INPUT_PROGRAM + && command->states & S_INPUT_PROGRAM) + || (state == CMD_STATE_FILE_TYPE && command->states & S_FILE_TYPE)); +} + +/* Emits an appropriate error message for trying to invoke + COMMAND in STATE. */ +static bool +report_state_mismatch (const struct command *command, enum cmd_state state) +{ + assert (!in_correct_state (command, state)); + if (state == CMD_STATE_INITIAL || state == CMD_STATE_DATA) + { + switch (command->states) + { + /* One allowed state. */ + case S_INITIAL: + msg (SE, _("%s is allowed only before the active file has " + "been defined."), command->name); + break; + case S_DATA: + msg (SE, _("%s is allowed only after the active file has " + "been defined."), command->name); + break; + case S_INPUT_PROGRAM: + msg (SE, _("%s is allowed only inside INPUT PROGRAM."), + command->name); + break; + case S_FILE_TYPE: + msg (SE, _("%s is allowed only inside FILE TYPE."), command->name); + break; + + /* Two allowed states. */ + case S_INITIAL | S_DATA: + NOT_REACHED (); + case S_INITIAL | S_INPUT_PROGRAM: + msg (SE, _("%s is allowed only before the active file has " + "been defined or inside INPUT PROGRAM."), command->name); + break; + case S_INITIAL | S_FILE_TYPE: + msg (SE, _("%s is allowed only before the active file has " + "been defined or inside FILE TYPE."), command->name); + break; + case S_DATA | S_INPUT_PROGRAM: + msg (SE, _("%s is allowed only after the active file has " + "been defined or inside INPUT PROGRAM."), command->name); + break; + case S_DATA | S_FILE_TYPE: + msg (SE, _("%s is allowed only after the active file has " + "been defined or inside FILE TYPE."), command->name); + break; + case S_INPUT_PROGRAM | S_FILE_TYPE: + msg (SE, _("%s is allowed only inside INPUT PROGRAM " + "or inside FILE TYPE."), command->name); + break; + + /* Three allowed states. */ + case S_DATA | S_INPUT_PROGRAM | S_FILE_TYPE: + msg (SE, _("%s is allowed only after the active file has " + "been defined, inside INPUT PROGRAM, or inside " + "FILE TYPE."), command->name); + break; + case S_INITIAL | S_INPUT_PROGRAM | S_FILE_TYPE: + msg (SE, _("%s is allowed only before the active file has " + "been defined, inside INPUT PROGRAM, or inside " + "FILE TYPE."), command->name); + break; + case S_INITIAL | S_DATA | S_FILE_TYPE: + NOT_REACHED (); + case S_INITIAL | S_DATA | S_INPUT_PROGRAM: + NOT_REACHED (); + + /* Four allowed states. */ + case S_INITIAL | S_DATA | S_INPUT_PROGRAM | S_FILE_TYPE: + NOT_REACHED (); + + default: + NOT_REACHED (); + } + } + else if (state == CMD_STATE_INPUT_PROGRAM) + msg (SE, _("%s is not allowed inside INPUT PROGRAM."), command->name); + else if (state == CMD_STATE_FILE_TYPE) + msg (SE, _("%s is not allowed inside FILE TYPE."), command->name); + + return false; +} + +/* Command name completion. */ + +static enum cmd_state completion_state = CMD_STATE_INITIAL; + +static void +set_completion_state (enum cmd_state state) +{ + completion_state = state; +} + +/* Returns the next possible completion of a command name that + begins with PREFIX, in the current command state, or a null + pointer if no completions remain. + Before calling the first time, set *CMD to a null pointer. */ +const char * +cmd_complete (const char *prefix, const struct command **cmd) +{ + if (*cmd == NULL) + *cmd = commands; + + for (; *cmd < commands + command_cnt; (*cmd)++) + if (!memcasecmp ((*cmd)->name, prefix, strlen (prefix)) + && (!((*cmd)->flags & F_TESTING) || settings_get_testing_mode ()) + && (!((*cmd)->flags & F_ENHANCED) || settings_get_syntax () == ENHANCED) + && !((*cmd)->flags & F_ABBREV) + && ((*cmd)->function != NULL) + && in_correct_state (*cmd, completion_state)) + return (*cmd)++->name; + + return NULL; +} /* Simple commands. */ /* Parse and execute FINISH command. */ int -cmd_finish (void) +cmd_finish (struct lexer *lexer UNUSED, struct dataset *ds UNUSED) { - return CMD_EOF; + return CMD_FINISH; } /* Parses the N command. */ int -cmd_n_of_cases (void) +cmd_n_of_cases (struct lexer *lexer, struct dataset *ds) { /* Value for N. */ int x; - if (!lex_force_int ()) + if (!lex_force_int (lexer)) return CMD_FAILURE; - x = lex_integer (); - lex_get (); - if (!lex_match_id ("ESTIMATED")) - dict_set_case_limit (default_dict, x); + x = lex_integer (lexer); + lex_get (lexer); + if (!lex_match_id (lexer, "ESTIMATED")) + dict_set_case_limit (dataset_dict (ds), x); - return lex_end_of_command (); + return lex_end_of_command (lexer); } /* Parses, performs the EXECUTE procedure. */ int -cmd_execute (void) +cmd_execute (struct lexer *lexer, struct dataset *ds) { - if (!procedure (NULL, NULL)) + bool ok = casereader_destroy (proc_open (ds)); + if (!proc_commit (ds) || !ok) return CMD_CASCADING_FAILURE; - return lex_end_of_command (); + return lex_end_of_command (lexer); } /* Parses, performs the ERASE command. */ int -cmd_erase (void) +cmd_erase (struct lexer *lexer, struct dataset *ds UNUSED) { - if (get_safer_mode ()) - { - msg (SE, _("This command not allowed when the SAFER option is set.")); - return CMD_FAILURE; - } - - if (!lex_force_match_id ("FILE")) + if (settings_get_safer_mode ()) + { + msg (SE, _("This command not allowed when the SAFER option is set.")); + return CMD_FAILURE; + } + + if (!lex_force_match_id (lexer, "FILE")) return CMD_FAILURE; - lex_match ('='); - if (!lex_force_string ()) + lex_match (lexer, '='); + if (!lex_force_string (lexer)) return CMD_FAILURE; - if (remove (ds_c_str (&tokstr)) == -1) + if (remove (ds_cstr (lex_tokstr (lexer))) == -1) { msg (SW, _("Error removing `%s': %s."), - ds_c_str (&tokstr), strerror (errno)); + ds_cstr (lex_tokstr (lexer)), strerror (errno)); return CMD_FAILURE; } return CMD_SUCCESS; } -#ifdef unix -/* Spawn a shell process. */ -static int +#if HAVE_FORK && HAVE_EXECL +/* Spawn an interactive shell process. */ +static bool shell (void) { int pid; - + pid = fork (); switch (pid) { @@ -679,10 +804,10 @@ shell (void) { const char *shell_fn; char *shell_process; - + { int i; - + for (i = 3; i < 20; i++) close (i); } @@ -690,17 +815,17 @@ shell (void) shell_fn = getenv ("SHELL"); if (shell_fn == NULL) shell_fn = "/bin/sh"; - + { const char *cp = strrchr (shell_fn, '/'); cp = cp ? &cp[1] : shell_fn; - shell_process = local_alloc (strlen (cp) + 8); + shell_process = xmalloca (strlen (cp) + 8); strcpy (shell_process, "-"); strcat (shell_process, cp); if (strcmp (cp, "sh")) shell_process[0] = '+'; } - + execl (shell_fn, shell_process, NULL); _exit (1); @@ -708,119 +833,94 @@ shell (void) case -1: msg (SE, _("Couldn't fork: %s."), strerror (errno)); - return 0; + return false; default: assert (pid > 0); while (wait (NULL) != pid) ; - return 1; + return true; } } -#endif /* unix */ - -/* Parses the HOST command argument and executes the specified - command. Returns a suitable command return code. */ -static int -run_command (void) +#else /* !(HAVE_FORK && HAVE_EXECL) */ +/* Don't know how to spawn an interactive shell. */ +static bool +shell (void) { - const char *cmd; - int string; - - /* Handle either a string argument or a full-line argument. */ - { - int c = lex_look_ahead (); + msg (SE, _("Interactive shell not supported on this platform.")); + return false; +} +#endif - if (c == '\'' || c == '"') - { - lex_get (); - if (!lex_force_string ()) - return CMD_FAILURE; - cmd = ds_c_str (&tokstr); - string = 1; - } - else - { - cmd = lex_rest_of_line (NULL); - lex_discard_line (); - string = 0; - } - } +/* Executes the specified COMMAND in a subshell. Returns true if + successful, false otherwise. */ +static bool +run_command (const char *command) +{ + if (system (NULL) == 0) + { + msg (SE, _("Command shell not supported on this platform.")); + return false; + } /* Execute the command. */ - if (system (cmd) == -1) + if (system (command) == -1) msg (SE, _("Error executing command: %s."), strerror (errno)); - /* Finish parsing. */ - if (string) - { - lex_get (); - - if (token != '.') - { - lex_error (_("expecting end of command")); - return CMD_TRAILING_GARBAGE; - } - } - else - token = '.'; - - return CMD_SUCCESS; + return true; } /* Parses, performs the HOST command. */ int -cmd_host (void) +cmd_host (struct lexer *lexer, struct dataset *ds UNUSED) { - int code; - - if (get_safer_mode ()) - { - msg (SE, _("This command not allowed when the SAFER option is set.")); - return CMD_FAILURE; - } - -#ifdef unix - /* Figure out whether to invoke an interactive shell or to execute a - single shell command. */ - if (lex_look_ahead () == '.') + int look_ahead; + + if (settings_get_safer_mode ()) { - lex_get (); - code = shell () ? CMD_PART_SUCCESS_MAYBE : CMD_SUCCESS; + msg (SE, _("This command not allowed when the SAFER option is set.")); + return CMD_FAILURE; } - else - code = run_command (); -#else /* !unix */ - /* Make sure that the system has a command interpreter, then run a - command. */ - if (system (NULL) != 0) - code = run_command (); - else + + look_ahead = lex_look_ahead (lexer); + if (look_ahead == '.') { - msg (SE, _("No operating system support for this command.")); - code = CMD_FAILURE; + lex_get (lexer); + return shell () ? CMD_SUCCESS : CMD_FAILURE; } -#endif /* !unix */ + else if (look_ahead == '\'' || look_ahead == '"') + { + bool ok; + + lex_get (lexer); + if (!lex_force_string (lexer)) + NOT_REACHED (); + ok = run_command (ds_cstr (lex_tokstr (lexer))); - return code; + lex_get (lexer); + return ok ? lex_end_of_command (lexer) : CMD_FAILURE; + } + else + { + bool ok = run_command (lex_rest_of_line (lexer)); + lex_discard_line (lexer); + return ok ? CMD_SUCCESS : CMD_FAILURE; + } } /* Parses, performs the NEW FILE command. */ int -cmd_new_file (void) +cmd_new_file (struct lexer *lexer, struct dataset *ds) { - discard_variables (); + proc_discard_active_file (ds); - return lex_end_of_command (); + return lex_end_of_command (lexer); } -/* Parses, performs the CLEAR TRANSFORMATIONS command. */ +/* Parses a comment. */ int -cmd_clear_transformations (void) +cmd_comment (struct lexer *lexer, struct dataset *ds UNUSED) { - cancel_transformations (); - /* FIXME: what about variables created by transformations? - They need to be properly initialized. */ - + lex_skip_comment (lexer); return CMD_SUCCESS; }