X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Flanguage%2Fcommand.c;h=f5db9731c1e6f6d30f8f77bf04d1a76f53c29bf7;hb=b401615e6db40bf74394839b96600afe3a868a95;hp=ed043d2197363fc0ef81cebae2f119f480573ad1;hpb=b74d09af5e07f954c18e7cdb8aca3af47fa10208;p=pspp-builds.git diff --git a/src/language/command.c b/src/language/command.c index ed043d21..f5db9731 100644 --- a/src/language/command.c +++ b/src/language/command.c @@ -1,52 +1,44 @@ -/* 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, 2010, 2011 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 +#include "language/command.h" #include #include #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 "data/casereader.h" +#include "data/dataset.h" +#include "data/dictionary.h" +#include "data/settings.h" +#include "data/variable.h" +#include "language/lexer/command-name.h" +#include "language/lexer/lexer.h" +#include "libpspp/assertion.h" +#include "libpspp/compiler.h" +#include "libpspp/i18n.h" +#include "libpspp/message.h" +#include "libpspp/str.h" +#include "output/text-item.h" + +#include "xalloc.h" +#include "xmalloca.h" #include "gettext.h" #define _(msgid) gettext (msgid) @@ -55,7 +47,7 @@ /* Returns true if RESULT is a valid "enum cmd_result", false otherwise. */ static inline bool -cmd_result_is_valid (enum cmd_result result) +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) @@ -66,7 +58,7 @@ cmd_result_is_valid (enum cmd_result result) /* Returns true if RESULT indicates success, false otherwise. */ bool -cmd_result_is_success (enum cmd_result result) +cmd_result_is_success (enum cmd_result result) { assert (cmd_result_is_valid (result)); return result > 0; @@ -75,7 +67,7 @@ cmd_result_is_success (enum cmd_result result) /* Returns true if RESULT indicates failure, false otherwise. */ bool -cmd_result_is_failure (enum cmd_result result) +cmd_result_is_failure (enum cmd_result result) { assert (cmd_result_is_valid (result)); return result < 0; @@ -84,19 +76,19 @@ cmd_result_is_failure (enum cmd_result result) /* Command processing states. */ enum states { - S_INITIAL = 0x01, /* Allowed before active file defined. */ - S_DATA = 0x02, /* Allowed after active file defined. */ + S_INITIAL = 0x01, /* Allowed before active dataset defined. */ + S_DATA = 0x02, /* Allowed after active dataset 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 +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. */ @@ -105,13 +97,13 @@ struct command enum states states; /* States in which command is allowed. */ enum flags flags; /* Other command requirements. */ const char *name; /* Command name. */ - int (*function) (void); /* Function to call. */ + int (*function) (struct lexer *, struct dataset *); /* Function to call. */ }; /* Define the command array. */ #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[] = +static const struct command commands[] = { #include "command.def" }; @@ -120,735 +112,435 @@ static const struct command commands[] = static const size_t command_cnt = sizeof commands / sizeof *commands; -static bool verify_valid_command (const struct command *, enum cmd_state); -static const struct command *find_command (const char *name); +static bool in_correct_state (const struct command *, enum cmd_state); +static bool report_state_mismatch (const struct command *, enum cmd_state); +static void set_completion_state (enum cmd_state); /* Command parser. */ -static const struct command *parse_command_name (void); -static enum cmd_result do_parse_command (enum cmd_state); +static const struct command *parse_command_name (struct lexer *, + int *n_tokens); +static enum cmd_result do_parse_command (struct lexer *, struct dataset *, enum cmd_state); /* 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 (enum cmd_state state) +cmd_parse_in_state (struct lexer *lexer, struct dataset *ds, + enum cmd_state state) { int result; - - som_new_series (); - result = do_parse_command (state); - if (cmd_result_is_failure (result)) - lex_discard_rest_of_command (); + result = do_parse_command (lexer, ds, state); + assert (!proc_is_open (ds)); unset_cmd_algorithm (); - dict_clear_aux (default_dict); + dict_clear_aux (dataset_dict (ds)); + if (!dataset_end_of_command (ds)) + result = CMD_CASCADING_FAILURE; return result; } +enum cmd_result +cmd_parse (struct lexer *lexer, struct dataset *ds) +{ + const struct dictionary *dict = dataset_dict (ds); + return cmd_parse_in_state (lexer, ds, + dataset_has_source (ds) && + dict_get_var_cnt (dict) > 0 ? + CMD_STATE_DATA : CMD_STATE_INITIAL); +} + + /* Parses an entire command, from command name to terminating dot. */ static enum cmd_result -do_parse_command (enum cmd_state state) +do_parse_command (struct lexer *lexer, + struct dataset *ds, enum cmd_state state) { - const struct command *command; + const struct command *command = NULL; enum cmd_result result; + bool opened = false; + int n_tokens; - /* Null commands can result from extra empty lines. */ - if (token == '.') - return CMD_SUCCESS; + /* Read the command's first token. */ + set_completion_state (state); + if (lex_token (lexer) == T_STOP) + { + result = CMD_EOF; + goto finish; + } + else if (lex_token (lexer) == T_ENDCMD) + { + /* Null commands can result from extra empty lines. */ + result = CMD_SUCCESS; + goto finish; + } /* Parse the command name. */ - command = parse_command_name (); + command = parse_command_name (lexer, &n_tokens); if (command == NULL) - return CMD_FAILURE; - else if (command->function == NULL) - return CMD_NOT_IMPLEMENTED; - else if ((command->flags & F_TESTING) && !get_testing_mode ()) + { + result = CMD_FAILURE; + goto finish; + } + text_item_submit (text_item_create (TEXT_ITEM_COMMAND_OPEN, command->name)); + opened = true; + + if (command->function == NULL) + { + msg (SE, _("%s is not yet implemented."), command->name); + result = CMD_NOT_IMPLEMENTED; + } + else if ((command->flags & F_TESTING) && !settings_get_testing_mode ()) { msg (SE, _("%s may be used only in testing mode."), command->name); - return CMD_FAILURE; + result = CMD_FAILURE; } - else if ((command->flags & F_ENHANCED) && get_syntax () != ENHANCED) + else if ((command->flags & F_ENHANCED) && settings_get_syntax () != ENHANCED) { msg (SE, _("%s may be used only in enhanced syntax mode."), command->name); - return CMD_FAILURE; + result = CMD_FAILURE; } - else if (!verify_valid_command (command, state)) - return CMD_FAILURE; - - /* Execute command. */ - msg_set_command_name (command->name); - tab_set_command_name (command->name); - result = command->function (); - tab_set_command_name (NULL); - msg_set_command_name (NULL); - - assert (cmd_result_is_valid (result)); - return result; -} - -static size_t -match_strings (const char *a, size_t a_len, - const char *b, size_t b_len) -{ - size_t match_len = 0; - - while (a_len > 0 && b_len > 0) + else if (!in_correct_state (command, state)) { - /* Mismatch always returns zero. */ - if (toupper ((unsigned char) *a++) != toupper ((unsigned char) *b++)) - return 0; - - /* Advance. */ - a_len--; - b_len--; - match_len++; + report_state_mismatch (command, state); + result = CMD_FAILURE; } - - return match_len; -} - -/* Returns the first character in the first word in STRING, - storing the word's length in *WORD_LEN. If no words remain, - returns a null pointer and stores 0 in *WORD_LEN. Words are - sequences of alphanumeric characters or single - non-alphanumeric characters. Words are delimited by - spaces. */ -static const char * -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') + else { - *word_len = 0; - return NULL; - } + /* Execute command. */ + int i; - /* Special one-character word? */ - if (!isalnum ((unsigned char) *string)) - { - *word_len = 1; - return string; + for (i = 0; i < n_tokens; i++) + lex_get (lexer); + result = command->function (lexer, ds); } - /* Alphanumeric word. */ - *word_len = 1; - while (isalnum ((unsigned char) string[*word_len])) - (*word_len)++; - - return string; -} - -/* Returns nonzero 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) -{ - size_t aw_len, bw_len; - const char *aw, *bw; - - aw = find_word (a, &aw_len); - bw = find_word (b, &bw_len); - assert (aw != NULL && bw != NULL); - - /* Words that are the same don't conflict. */ - if (aw_len == bw_len && !buf_compare_case (aw, bw, aw_len)) - return 0; - - /* Words that are otherwise the same in the first three letters - do conflict. */ - return ((aw_len > 3 && bw_len > 3) - || (aw_len == 3 && bw_len > 3) - || (bw_len == 3 && aw_len > 3)) && !buf_compare_case (aw, bw, 3); -} + assert (cmd_result_is_valid (result)); -/* Returns nonzero 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) -{ - assert (cmd >= commands && cmd < commands + command_cnt); +finish: + if (cmd_result_is_failure (result)) + lex_interactive_reset (lexer); + else if (result == CMD_SUCCESS) + result = lex_end_of_command (lexer); - return ((cmd > commands - && conflicting_3char_prefixes (cmd[-1].name, cmd[0].name)) - || (cmd < commands + command_cnt - && conflicting_3char_prefixes (cmd[0].name, cmd[1].name))); -} + lex_discard_rest_of_command (lexer); + while (lex_token (lexer) == T_ENDCMD) + lex_get (lexer); -/* Ways that a set of words can match a command name. */ -enum command_match - { - MISMATCH, /* Not a match. */ - PARTIAL_MATCH, /* The words begin the command name. */ - COMPLETE_MATCH /* The words are the command name. */ - }; + if (opened) + text_item_submit (text_item_create (TEXT_ITEM_COMMAND_CLOSE, + command->name)); -/* Figures out how well the WORD_CNT words in WORDS match CMD, - and returns the appropriate enum value. If WORDS are a - partial match for CMD and the next word in CMD is a dash, then - *DASH_POSSIBLE is set to 1 if DASH_POSSIBLE is non-null; - otherwise, *DASH_POSSIBLE is unchanged. */ -static enum command_match -cmd_match_words (const struct command *cmd, - char *const words[], size_t word_cnt, - int *dash_possible) -{ - const char *word; - size_t word_len; - size_t word_idx; - - for (word = find_word (cmd->name, &word_len), word_idx = 0; - word != NULL && word_idx < word_cnt; - word = find_word (word + word_len, &word_len), word_idx++) - if (word_len != strlen (words[word_idx]) - || buf_compare_case (word, words[word_idx], word_len)) - { - size_t match_chars = match_strings (word, word_len, - words[word_idx], - strlen (words[word_idx])); - if (match_chars == 0) - { - /* Mismatch. */ - return MISMATCH; - } - else if (match_chars == 1 || match_chars == 2) - { - /* One- and two-character abbreviations are not - acceptable. */ - return MISMATCH; - } - else if (match_chars == 3) - { - /* Three-character abbreviations are acceptable - in the first word of a command if there are - no name conflicts. They are always - acceptable after the first word. */ - if (word_idx == 0 && conflicting_3char_prefix_command (cmd)) - return MISMATCH; - } - else /* match_chars > 3 */ - { - /* Four-character and longer abbreviations are - always acceptable. */ - } - } - - if (word == NULL && word_idx == word_cnt) - { - /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR"}. */ - return COMPLETE_MATCH; - } - else if (word == NULL) - { - /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR", "BAZ"}. */ - return MISMATCH; - } - else - { - /* cmd->name = "FOO BAR BAZ", words[] = {"FOO", "BAR"}. */ - if (word[0] == '-' && dash_possible != NULL) - *dash_possible = 1; - return PARTIAL_MATCH; - } + return result; } -/* Returns the number of commands for which the WORD_CNT words in - WORDS are a partial or complete match. If some partial match - has a dash as the next word, then *DASH_POSSIBLE is set to 1, - otherwise it is set to 0. */ static int -count_matching_commands (char *const words[], size_t word_cnt, - int *dash_possible) +find_best_match (struct substring s, const struct command **matchp) { const struct command *cmd; - int cmd_match_count; + struct command_matcher cm; + int missing_words; - 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++; + command_matcher_init (&cm, s); + for (cmd = commands; cmd < &commands[command_cnt]; cmd++) + command_matcher_add (&cm, ss_cstr (cmd->name), CONST_CAST (void *, cmd)); - return cmd_match_count; -} + *matchp = command_matcher_get_match (&cm); + missing_words = command_matcher_get_missing_words (&cm); -/* Returns the command for which the WORD_CNT words in WORDS are - 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) -{ - 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; - - return NULL; -} + command_matcher_destroy (&cm); -/* 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; - abort (); + return missing_words; } -/* Frees the WORD_CNT words in WORDS. */ -static void -free_words (char *words[], size_t word_cnt) +static bool +parse_command_word (struct lexer *lexer, struct string *s, int n) { - size_t idx; - - for (idx = 0; idx < word_cnt; idx++) - free (words[idx]); -} + bool need_space = ds_last (s) != EOF && ds_last (s) != '-'; -/* 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) -{ - if (word_cnt == 0) - lex_error (_("expecting command name")); - else + switch (lex_next_token (lexer, n)) { - struct string s; - size_t i; - - ds_init_empty (&s); - for (i = 0; i < word_cnt; i++) + case T_DASH: + ds_put_byte (s, '-'); + return true; + + case T_ID: + if (need_space) + ds_put_byte (s, ' '); + ds_put_cstr (s, lex_next_tokcstr (lexer, n)); + return true; + + case T_POS_NUM: + if (lex_next_is_integer (lexer, n)) { - if (i != 0) - ds_put_char (&s, ' '); - ds_put_cstr (&s, words[i]); + int integer = lex_next_integer (lexer, n); + if (integer >= 0) + { + if (need_space) + ds_put_byte (s, ' '); + ds_put_format (s, "%ld", lex_next_integer (lexer, n)); + return true; + } } + return false; - msg (SE, _("Unknown command %s."), ds_cstr (&s)); - - ds_destroy (&s); + default: + return false; } } -/* Parse the command name and return a pointer to the corresponding - struct command if successful. - If not successful, return a null pointer. */ +/* Parses the command name. On success returns a pointer to the corresponding + struct command and stores the number of tokens in the command name into + *N_TOKENS. On failure, returns a null pointer and stores the number of + tokens required to determine that no command name was present into + *N_TOKENS. */ static const struct command * -parse_command_name (void) +parse_command_name (struct lexer *lexer, int *n_tokens) { - char *words[16]; - int word_cnt; - int complete_word_cnt; - int dash_possible; - - if (token == T_EXP || token == '*' || token == '[') - return find_command ("COMMENT"); - - dash_possible = 0; - word_cnt = complete_word_cnt = 0; - while (token == T_ID || (dash_possible && token == '-')) + const struct command *command; + int missing_words; + struct string s; + int word; + + command = NULL; + missing_words = 0; + ds_init_empty (&s); + word = 0; + while (parse_command_word (lexer, &s, word)) { - int cmd_match_cnt; - - assert (word_cnt < sizeof words / sizeof *words); - if (token == T_ID) - { - words[word_cnt] = ds_xstrdup (&tokstr); - str_uppercase (words[word_cnt]); - } - else if (token == '-') - words[word_cnt] = xstrdup ("-"); - word_cnt++; - - cmd_match_cnt = count_matching_commands (words, word_cnt, - &dash_possible); - if (cmd_match_cnt == 0) + missing_words = find_best_match (ds_ss (&s), &command); + if (missing_words <= 0) break; - else if (cmd_match_cnt == 1) - { - const struct command *command = get_complete_match (words, word_cnt); - if (command != NULL) - { - if (!(command->flags & F_KEEP_FINAL_TOKEN)) - lex_get (); - free_words (words, word_cnt); - return command; - } - } - else /* cmd_match_cnt > 1 */ - { - /* Do we have a complete command name so far? */ - if (get_complete_match (words, word_cnt) != NULL) - complete_word_cnt = word_cnt; - } - lex_get (); + word++; } - /* If we saw a complete command name earlier, drop back to - it. */ - if (complete_word_cnt) + if (command == NULL && missing_words > 0) { - int pushback_word_cnt; - const struct command *command; - - /* Get the command. */ - command = get_complete_match (words, complete_word_cnt); - assert (command != NULL); - - /* 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->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) - { - word_cnt--; - if (strcmp (words[word_cnt], "-")) - lex_put_back_id (words[word_cnt]); - else - lex_put_back ('-'); - free (words[word_cnt]); - } + ds_put_cstr (&s, " ."); + missing_words = find_best_match (ds_ss (&s), &command); + ds_truncate (&s, ds_length (&s) - 2); + } - free_words (words, word_cnt); - return command; + if (command == NULL) + { + if (ds_is_empty (&s)) + lex_error (lexer, _("expecting command name")); + else + msg (SE, _("Unknown command `%s'."), ds_cstr (&s)); } - /* We didn't get a valid command name. */ - unknown_command_error (words, word_cnt); - free_words (words, word_cnt); - return NULL; + ds_destroy (&s); + + *n_tokens = (word + 1) + missing_words; + return command; } /* Returns true if COMMAND is allowed in STATE, - false otherwise. - If COMMAND is not allowed, emits an appropriate error - message. */ + false otherwise. */ static bool -verify_valid_command (const struct command *command, enum cmd_state state) +in_correct_state (const struct command *command, enum cmd_state state) { - if ((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)) - return true; + 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) { - const char *allowed[3]; - int allowed_cnt; - char *s; - - allowed_cnt = 0; - if (command->states & S_INITIAL) - allowed[allowed_cnt++] = _("before the active file has been defined"); - else if (command->states & S_DATA) - allowed[allowed_cnt++] = _("after the active file has been defined"); - if (command->states & S_INPUT_PROGRAM) - allowed[allowed_cnt++] = _("inside INPUT PROGRAM"); - if (command->states & S_FILE_TYPE) - allowed[allowed_cnt++] = _("inside FILE TYPE"); - - if (allowed_cnt == 1) - s = xstrdup (allowed[0]); - else if (allowed_cnt == 2) - s = xasprintf (_("%s or %s"), allowed[0], allowed[1]); - else if (allowed_cnt == 3) - s = xasprintf (_("%s, %s, or %s"), allowed[0], allowed[1], allowed[2]); - else - abort (); - - msg (SE, _("%s is allowed only %s."), command->name, s); - - free (s); + switch (command->states) + { + /* One allowed state. */ + case S_INITIAL: + msg (SE, _("%s is allowed only before the active dataset has " + "been defined."), command->name); + break; + case S_DATA: + msg (SE, _("%s is allowed only after the active dataset 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 dataset 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 dataset 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 dataset 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 dataset 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 dataset 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 dataset 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); + msg (SE, _("%s is not allowed inside %s."), + command->name, "INPUT PROGRAM" ); else if (state == CMD_STATE_FILE_TYPE) - msg (SE, _("%s is not allowed inside FILE TYPE."), command->name); + msg (SE, _("%s is not allowed inside %s."), command->name, "FILE TYPE"); return false; } -/* Readline command name completion. */ +/* Command name completion. */ -#if HAVE_READLINE -static char *command_generator (const char *text, int state); +static enum cmd_state completion_state = CMD_STATE_INITIAL; -/* Returns a set of completions for TEXT. - This is of the proper form for assigning to - rl_attempted_completion_function. */ -char ** -pspp_attempted_completion_function (const char *text, - int start, int end UNUSED) +static void +set_completion_state (enum cmd_state state) { - if (start == 0) - { - /* Complete command name at start of line. */ - return rl_completion_matches (text, command_generator); - } - else - { - /* Otherwise don't do any completion. */ - rl_attempted_completion_over = 1; - return NULL; - } + completion_state = state; } -/* If STATE is 0, returns the first command name matching TEXT. - Otherwise, returns the next command name matching TEXT. - Returns a null pointer when no matches are left. */ -static char * -command_generator (const char *text, int 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) { - static const struct command *cmd; - - if (!state) - cmd = commands; - - for (; cmd < commands + command_cnt; cmd++) - if (!memcasecmp (cmd->name, text, strlen (text)) - && (!(cmd->flags & F_TESTING) || get_testing_mode ()) - && (!(cmd->flags & F_ENHANCED) || get_syntax () == ENHANCED)) - return xstrdup (cmd++->name); + 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; } -#endif /* HAVE_READLINE */ /* Simple commands. */ /* Parse and execute FINISH command. */ int -cmd_finish (void) +cmd_finish (struct lexer *lexer UNUSED, struct dataset *ds UNUSED) { 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 CMD_SUCCESS; } /* Parses, performs the EXECUTE procedure. */ int -cmd_execute (void) +cmd_execute (struct lexer *lexer UNUSED, 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 CMD_SUCCESS; } /* 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")) - return CMD_FAILURE; - lex_match ('='); - if (!lex_force_string ()) - return CMD_FAILURE; + char *filename; + int retval; - if (remove (ds_cstr (&tokstr)) == -1) + if (settings_get_safer_mode ()) { - msg (SW, _("Error removing `%s': %s."), - ds_cstr (&tokstr), strerror (errno)); + msg (SE, _("This command not allowed when the SAFER option is set.")); return CMD_FAILURE; } - return CMD_SUCCESS; -} - -#ifdef unix -/* Spawn a shell process. */ -static int -shell (void) -{ - int pid; - - pid = fork (); - switch (pid) - { - case 0: - { - const char *shell_fn; - char *shell_process; - - { - int i; - - for (i = 3; i < 20; i++) - close (i); - } - - 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); - strcpy (shell_process, "-"); - strcat (shell_process, cp); - if (strcmp (cp, "sh")) - shell_process[0] = '+'; - } - - execl (shell_fn, shell_process, NULL); - - _exit (1); - } - - case -1: - msg (SE, _("Couldn't fork: %s."), strerror (errno)); - return 0; - - default: - assert (pid > 0); - while (wait (NULL) != pid) - ; - return 1; - } -} -#endif /* unix */ + if (!lex_force_match_id (lexer, "FILE")) + return CMD_FAILURE; + lex_match (lexer, T_EQUALS); + if (!lex_force_string (lexer)) + return CMD_FAILURE; -/* Parses the HOST command argument and executes the specified - command. Returns a suitable command return code. */ -static int -run_command (void) -{ - const char *cmd; - int string; + filename = utf8_to_filename (lex_tokcstr (lexer)); + retval = remove (filename); + free (filename); - /* Handle either a string argument or a full-line argument. */ - { - int c = lex_look_ahead (); - - if (c == '\'' || c == '"') - { - lex_get (); - if (!lex_force_string ()) - return CMD_FAILURE; - cmd = ds_cstr (&tokstr); - string = 1; - } - else - { - cmd = lex_rest_of_line (NULL); - lex_discard_line (); - string = 0; - } - } - - /* Execute the command. */ - if (system (cmd) == -1) - msg (SE, _("Error executing command: %s."), strerror (errno)); - - /* Finish parsing. */ - if (string) + if (retval == -1) { - lex_get (); - - if (token != '.') - { - lex_error (_("expecting end of command")); - return CMD_FAILURE; - } + msg (SW, _("Error removing `%s': %s."), + lex_tokcstr (lexer), strerror (errno)); + return CMD_FAILURE; } - else - token = '.'; + lex_get (lexer); return CMD_SUCCESS; } -/* Parses, performs the HOST command. */ -int -cmd_host (void) -{ - 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 () == '.') - { - lex_get (); - code = shell () ? CMD_FAILURE : CMD_SUCCESS; - } - 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 - { - msg (SE, _("No operating system support for this command.")); - code = CMD_FAILURE; - } -#endif /* !unix */ - - return code; -} - /* Parses, performs the NEW FILE command. */ int -cmd_new_file (void) -{ - discard_variables (); - - return lex_end_of_command (); -} - -/* Parses a comment. */ -int -cmd_comment (void) +cmd_new_file (struct lexer *lexer UNUSED, struct dataset *ds) { - lex_skip_comment (); + dataset_clear (ds); return CMD_SUCCESS; }