-/* PSPP - computes sample statistics.
- Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
- Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012, 2013, 2014 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 <http://www.gnu.org/licenses/>. */
#include <config.h>
-#include <libpspp/message.h>
-#include <language/command.h>
+
+#include "language/command.h"
+
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
-#include <unistd.h>
-#include <libpspp/alloc.h>
-#include <libpspp/compiler.h>
-#include <data/dictionary.h>
-#include <libpspp/message.h>
-#include <language/lexer/lexer.h>
-#include <data/settings.h>
-#include <output/manager.h>
-#include <libpspp/str.h>
-#include <output/table.h>
-#include <data/variable.h>
-#include <procedure.h>
-
-#if HAVE_SYS_WAIT_H
-#include <sys/wait.h>
-#endif
+
+#include "data/casereader.h"
+#include "data/dataset.h"
+#include "data/dictionary.h"
+#include "data/session.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)
#define N_(msgid) msgid
\f
-/* 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)
+{
+ switch (result)
+ {
+ case CMD_SUCCESS:
+ case CMD_EOF:
+ case CMD_FINISH:
+ case CMD_DATA_LIST:
+ case CMD_END_CASE:
+ case CMD_END_FILE:
+ case CMD_FAILURE:
+ case CMD_NOT_IMPLEMENTED:
+ case CMD_CASCADING_FAILURE:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/* 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;
+}
\f
-/* Static variables. */
+/* Command processing states. */
+enum states
+ {
+ 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
+ {
+ F_ENHANCED = 0x10, /* Allowed only in enhanced syntax mode. */
+ F_TESTING = 0x20, /* Allowed only in testing mode. */
+ 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
-/* 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);
-}
+static const size_t command_cnt = sizeof commands / sizeof *commands;
-
-
-#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 void set_completion_state (enum cmd_state);
\f
/* Command parser. */
-static const struct command *parse_command_name (void);
+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);
-/* 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;
+ struct session *session = dataset_session (ds);
+ int result;
- if (c->func == cmd_record_type)
- fty.had_rec_type = 1;
+ result = do_parse_command (lexer, ds, state);
- return okay;
-}
-#else
-{
- return 1;
+ ds = session_active_dataset (session);
+ assert (!proc_is_open (ds));
+ unset_cmd_algorithm ();
+ 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,
+ dataset_has_source (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 = NULL;
+ enum cmd_result result;
+ bool opened = false;
+ int n_tokens;
+
+ /* Read the command's first token. */
+ set_completion_state (state);
+ 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) == T_ENDCMD)
{
- lex_error (_("expecting command name"));
- return CMD_FAILURE;
+ /* Null commands can result from extra empty lines. */
+ result = CMD_SUCCESS;
+ goto finish;
}
/* Parse the command name. */
- cp = parse_command_name ();
- if (cp == NULL)
- return CMD_FAILURE;
- if (cp->func == NULL)
+ command = parse_command_name (lexer, &n_tokens);
+ 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;
}
+ text_item_submit (text_item_create (TEXT_ITEM_COMMAND_OPEN, command->name));
+ opened = true;
- /* 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)
+ 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;
}
-
- /* 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];
-
- if (pgm_state == STATE_ERROR)
- {
- discard_variables ();
- pgm_state = STATE_INIT;
- }
- }
-
- /* Pass the command's success value up to the caller. */
- 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 ((command->flags & F_TESTING) && !settings_get_testing_mode ())
{
- /* Mismatch always returns zero. */
- if (toupper ((unsigned char) *a++) != toupper ((unsigned char) *b++))
- return 0;
-
- /* Advance. */
- a_len--;
- b_len--;
- match_len++;
+ msg (SE, _("%s may be used only in testing mode."), command->name);
+ 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 if ((command->flags & F_ENHANCED) && settings_get_syntax () != ENHANCED)
{
- *word_len = 0;
- return NULL;
+ msg (SE, _("%s may be used only in enhanced syntax mode."),
+ command->name);
+ result = CMD_FAILURE;
}
-
- /* Special one-character word? */
- if (!isalnum ((unsigned char) *string))
+ else if (!in_correct_state (command, state))
{
- *word_len = 1;
- return string;
+ report_state_mismatch (command, state);
+ result = CMD_FAILURE;
}
+ else
+ {
+ /* Execute command. */
+ int i;
- /* Alphanumeric word. */
- *word_len = 1;
- while (isalnum ((unsigned char) string[*word_len]))
- (*word_len)++;
-
- return string;
-}
+ for (i = 0; i < n_tokens; i++)
+ lex_get (lexer);
+ result = command->function (lexer, ds);
+ }
-/* 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);
+ if (result != CMD_EOF && result != CMD_FINISH)
+ 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);
+
+ 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) != '-';
+
+ switch (lex_next_token (lexer, n))
+ {
+ 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))
+ {
+ 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;
+
+ default:
+ return false;
+ }
}
-/* 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)
+/* 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 (struct lexer *lexer, int *n_tokens)
{
- size_t idx;
- size_t words_len;
- char *name, *cp;
+ 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))
+ {
+ missing_words = find_best_match (ds_ss (&s), &command);
+ if (missing_words <= 0)
+ break;
+ word++;
+ }
- words_len = 0;
- for (idx = 0; idx < word_cnt; idx++)
- words_len += strlen (words[idx]);
+ if (command == NULL && missing_words > 0)
+ {
+ ds_put_cstr (&s, " .");
+ missing_words = find_best_match (ds_ss (&s), &command);
+ ds_truncate (&s, ds_length (&s) - 2);
+ }
- cp = name = xmalloc (words_len + word_cnt + 16);
- for (idx = 0; idx < word_cnt; idx++)
+ if (command == NULL)
{
- if (idx != 0)
- *cp++ = ' ';
- cp = stpcpy (cp, words[idx]);
+ if (ds_is_empty (&s))
+ lex_error (lexer, _("expecting command name"));
+ else
+ msg (SE, _("Unknown command `%s'."), ds_cstr (&s));
}
- *cp = '\0';
- msg (SE, _("Unknown command %s."), name);
+ ds_destroy (&s);
- free (name);
+ *n_tokens = (word + 1) + missing_words;
+ return command;
}
+/* 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));
+}
-/* 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)
+/* 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)
{
- char *words[16];
- int word_cnt;
- int complete_word_cnt;
- int dash_possible;
-
- dash_possible = 0;
- word_cnt = complete_word_cnt = 0;
- while (token == T_ID || (dash_possible && token == '-'))
+ assert (!in_correct_state (command, state));
+ if (state == CMD_STATE_INITIAL || state == CMD_STATE_DATA)
{
- int cmd_match_cnt;
-
- assert (word_cnt < sizeof words / sizeof *words);
- if (token == T_ID)
- words[word_cnt] = xstrdup (ds_c_str (&tokstr));
- else
- 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)
- break;
- else if (cmd_match_cnt == 1)
- {
- const struct command *command = get_complete_match (words, word_cnt);
- if (command != NULL)
- {
- if (command->skip_entire_name)
- lex_get ();
- if ( command->debug & !get_testing_mode () )
- goto error;
- free_words (words, word_cnt);
- return command;
- }
- }
- else /* cmd_match_cnt > 1 */
+ switch ((int) command->states)
{
- /* Do we have a complete command name so far? */
- if (get_complete_match (words, word_cnt) != NULL)
- complete_word_cnt = word_cnt;
+ /* 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 %s."),
+ command->name, "INPUT PROGRAM");
+ break;
+ case S_FILE_TYPE:
+ msg (SE, _("%s is allowed only inside %s."), command->name, "FILE TYPE");
+ 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 %s."),
+ command->name, "INPUT PROGRAM");
+ break;
+ case S_INITIAL | S_FILE_TYPE:
+ msg (SE, _("%s is allowed only before the active dataset has been defined or inside %s."),
+ command->name, "FILE TYPE");
+ break;
+ case S_DATA | S_INPUT_PROGRAM:
+ msg (SE, _("%s is allowed only after the active dataset has been defined or inside %s."),
+ command->name, "INPUT PROGRAM");
+ break;
+ case S_DATA | S_FILE_TYPE:
+ msg (SE, _("%s is allowed only after the active dataset has been defined or inside %s."),
+ command->name, "FILE TYPE");
+ break;
+ case S_INPUT_PROGRAM | S_FILE_TYPE:
+ msg (SE, _("%s is allowed only inside %s or inside %s."), command->name,
+ "INPUT PROGRAM", "FILE TYPE");
+ 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 ();
}
- lex_get ();
}
+ else if (state == CMD_STATE_INPUT_PROGRAM)
+ 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 %s."), command->name, "FILE TYPE");
- /* If we saw a complete command name earlier, drop back to
- it. */
- if (complete_word_cnt)
- {
- 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->skip_entire_name)
- 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]);
- }
+ return false;
+}
+\f
+/* Command name completion. */
- if ( command->debug && !get_testing_mode () )
- goto error;
+static enum cmd_state completion_state = CMD_STATE_INITIAL;
- free_words (words, word_cnt);
- return command;
- }
+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;
-error:
- unknown_command_error (words, word_cnt);
- free_words (words, word_cnt);
return NULL;
}
\f
/* 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 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_c_str (&tokstr)) == -1)
+ if (settings_get_safer_mode ())
{
- msg (SW, _("Error removing `%s': %s."),
- ds_c_str (&tokstr), strerror (errno));
+ msg (SE, _("This command not allowed when the %s option is set."), "SAFER");
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_c_str (&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_TRAILING_GARBAGE;
- }
+ 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_PART_SUCCESS_MAYBE : 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)
+cmd_new_file (struct lexer *lexer UNUSED, struct dataset *ds)
{
- discard_variables ();
-
- return lex_end_of_command ();
-}
-
-/* Parses, performs the CLEAR TRANSFORMATIONS command. */
-int
-cmd_clear_transformations (void)
-{
- cancel_transformations ();
- /* FIXME: what about variables created by transformations?
- They need to be properly initialized. */
-
+ dataset_clear (ds);
return CMD_SUCCESS;
}