/* PSPP - a program for statistical analysis.
- Copyright (C) 1997-9, 2000, 2009 Free Software Foundation, Inc.
+ Copyright (C) 1997-9, 2000, 2009, 2010 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
#include <config.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 <data/casereader.h>
-#include <data/dictionary.h>
-#include <data/procedure.h>
-#include <data/settings.h>
-#include <data/variable.h>
-#include <language/lexer/lexer.h>
-#include <language/prompt.h>
-#include <libpspp/assertion.h>
-#include <libpspp/compiler.h>
-#include <libpspp/message.h>
-#include <libpspp/message.h>
-#include <libpspp/str.h>
-#include <output/manager.h>
-#include <libpspp/getl.h>
-
-#if HAVE_SYS_WAIT_H
-#include <sys/wait.h>
-#endif
-
-#if HAVE_READLINE
-#include <readline/readline.h>
-#endif
+
+#include "data/casereader.h"
+#include "data/dictionary.h"
+#include "data/procedure.h"
+#include "data/settings.h"
+#include "data/variable.h"
+#include "language/lexer/command-name.h"
+#include "language/lexer/lexer.h"
+#include "language/prompt.h"
+#include "libpspp/assertion.h"
+#include "libpspp/compiler.h"
+#include "libpspp/message.h"
+#include "libpspp/str.h"
+#include "libpspp/getl.h"
+#include "output/text-item.h"
#include "xalloc.h"
#include "xmalloca.h"
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);
\f
/* Command parser. */
{
int result;
- som_new_series ();
-
result = do_parse_command (lexer, ds, state);
- if (cmd_result_is_failure (result))
- lex_discard_rest_of_command (lexer);
assert (!proc_is_open (ds));
unset_cmd_algorithm ();
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;
/* Read the command's first token. */
prompt_set_style (PROMPT_FIRST);
result = CMD_EOF;
goto finish;
}
- else if (lex_token (lexer) == '.')
+ else if (lex_token (lexer) == T_ENDCMD)
{
/* Null commands can result from extra empty lines. */
result = CMD_SUCCESS;
result = CMD_FAILURE;
goto finish;
}
- else if (command->function == NULL)
+ 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;
- 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;
}
-
- /* Execute command. */
- msg_set_command_name (command->name);
- som_set_command_name (command->name);
- result = command->function (lexer, ds);
- som_set_command_name (NULL);
- msg_set_command_name (NULL);
+ else
+ {
+ /* Execute command. */
+ result = command->function (lexer, ds);
+ }
assert (cmd_result_is_valid (result));
finish:
- if ( cmd_result_is_failure (result))
+ if (cmd_result_is_failure (result))
{
- const struct source_stream *cs = lex_get_source_stream (lexer);
-
- if ( source_stream_current_error_mode (cs) == ERRMODE_STOP )
+ lex_discard_rest_of_command (lexer);
+ if (source_stream_current_error_mode (
+ lex_get_source_stream (lexer)) == 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)
-{
- size_t match_len = 0;
-
- while (a_len > 0 && b_len > 0)
- {
- /* Mismatch always returns zero. */
- if (toupper ((unsigned char) *a++) != toupper ((unsigned char) *b++))
- return 0;
-
- /* Advance. */
- a_len--;
- b_len--;
- match_len++;
- }
-
- 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')
- {
- *word_len = 0;
- return NULL;
- }
-
- /* Special one-character word? */
- if (!isalnum ((unsigned char) *string))
- {
- *word_len = 1;
- return string;
- }
-
- /* Alphanumeric word. */
- *word_len = 1;
- while (isalnum ((unsigned char) string[*word_len]))
- (*word_len)++;
-
- return string;
-}
-
-/* Returns true if strings A and B can be confused based on
- their first three letters. */
-static bool
-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 false;
-
- /* 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);
-}
-
-/* Returns true if CMD can be confused with another command
- based on the first three letters of its first word. */
-static bool
-conflicting_3char_prefix_command (const struct command *cmd)
-{
- assert (cmd >= commands && cmd < commands + command_cnt);
+ if (opened)
+ text_item_submit (text_item_create (TEXT_ITEM_COMMAND_CLOSE,
+ command->name));
- 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)));
-}
-
-/* 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. */
- };
-
-/* 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++;
-
- return 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));
-/* 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;
+ *matchp = command_matcher_get_match (&cm);
+ missing_words = command_matcher_get_missing_words (&cm);
- for (cmd = commands; cmd < commands + command_cnt; cmd++)
- if (cmd_match_words (cmd, words, word_cnt, NULL) == COMPLETE_MATCH)
- return cmd;
+ command_matcher_destroy (&cm);
- 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)
-{
- size_t idx;
-
- for (idx = 0; idx < word_cnt; idx++)
- free (words[idx]);
-}
-
-/* Flags an error that the command whose name is given by the
- WORD_CNT words in WORDS is unknown. */
-static void
-unknown_command_error (struct lexer *lexer, char *const words[], size_t word_cnt)
-{
- if (word_cnt == 0)
- lex_error (lexer, _("expecting command name"));
- else
- {
- struct string s;
- size_t i;
-
- ds_init_empty (&s);
- for (i = 0; i < word_cnt; i++)
- {
- if (i != 0)
- ds_put_char (&s, ' ');
- ds_put_cstr (&s, words[i]);
- }
-
- msg (SE, _("Unknown command %s."), ds_cstr (&s));
-
- ds_destroy (&s);
- }
+ return missing_words;
}
/* Parse the command name and return a pointer to the corresponding
static const struct command *
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 (lex_token (lexer) == T_ID || (dash_possible && lex_token (lexer) == '-'))
- {
- int cmd_match_cnt;
+ const struct command *command;
+ int missing_words;
+ struct string s;
- assert (word_cnt < sizeof words / sizeof *words);
- 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 ("-");
- word_cnt++;
+ if (lex_token (lexer) == T_EXP
+ || lex_token (lexer) == T_ASTERISK
+ || lex_token (lexer) == T_LBRACK)
+ {
+ static const struct command c = { S_ANY, 0, "COMMENT", cmd_comment };
+ return &c;
+ }
- cmd_match_cnt = count_matching_commands (words, word_cnt,
- &dash_possible);
- if (cmd_match_cnt == 0)
- break;
- else if (cmd_match_cnt == 1)
+ command = NULL;
+ missing_words = 0;
+ ds_init_empty (&s);
+ for (;;)
+ {
+ if (lex_token (lexer) == T_DASH)
+ ds_put_byte (&s, '-');
+ else if (lex_token (lexer) == T_ID)
{
- const struct command *command = get_complete_match (words, word_cnt);
- if (command != NULL)
- {
- if (!(command->flags & F_KEEP_FINAL_TOKEN))
- lex_get (lexer);
- free_words (words, word_cnt);
- return command;
- }
+ if (!ds_is_empty (&s) && ds_last (&s) != '-')
+ ds_put_byte (&s, ' ');
+ ds_put_cstr (&s, lex_tokcstr (lexer));
}
- else /* cmd_match_cnt > 1 */
+ else if (lex_is_integer (lexer) && lex_integer (lexer) >= 0)
{
- /* Do we have a complete command name so far? */
- if (get_complete_match (words, word_cnt) != NULL)
- complete_word_cnt = word_cnt;
+ if (!ds_is_empty (&s) && ds_last (&s) != '-')
+ ds_put_byte (&s, ' ');
+ ds_put_format (&s, "%ld", lex_integer (lexer));
}
+ else
+ break;
+
+ missing_words = find_best_match (ds_ss (&s), &command);
+ if (missing_words <= 0)
+ break;
+
lex_get (lexer);
}
- /* 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 (lexer, words[word_cnt]);
- else
- lex_put_back (lexer, '-');
- 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));
+ }
+ else if (missing_words == 0)
+ {
+ if (!(command->flags & F_KEEP_FINAL_TOKEN))
+ lex_get (lexer);
+ }
+ else if (missing_words < 0)
+ {
+ assert (missing_words == -1);
+ assert (!(command->flags & F_KEEP_FINAL_TOKEN));
}
- /* We didn't get a valid command name. */
- unknown_command_error (lexer, words, word_cnt);
- free_words (words, word_cnt);
- return NULL;
+ ds_destroy (&s);
+ return command;
}
/* Returns true if COMMAND is allowed in STATE,
if (!lex_force_match_id (lexer, "FILE"))
return CMD_FAILURE;
- lex_match (lexer, '=');
+ lex_match (lexer, T_EQUALS);
if (!lex_force_string (lexer))
return CMD_FAILURE;
- if (remove (ds_cstr (lex_tokstr (lexer))) == -1)
+ if (remove (lex_tokcstr (lexer)) == -1)
{
msg (SW, _("Error removing `%s': %s."),
- ds_cstr (lex_tokstr (lexer)), strerror (errno));
+ lex_tokcstr (lexer), strerror (errno));
return CMD_FAILURE;
}
return CMD_SUCCESS;
}
-#if HAVE_FORK && HAVE_EXECL
-/* Spawn an interactive shell process. */
-static bool
-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 = 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);
- }
-
- case -1:
- msg (SE, _("Couldn't fork: %s."), strerror (errno));
- return false;
-
- default:
- assert (pid > 0);
- while (wait (NULL) != pid)
- ;
- return true;
- }
-}
-#else /* !(HAVE_FORK && HAVE_EXECL) */
-/* Don't know how to spawn an interactive shell. */
-static bool
-shell (void)
-{
- msg (SE, _("Interactive shell not supported on this platform."));
- return false;
-}
-#endif
-
-/* 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 (command) == -1)
- msg (SE, _("Error executing command: %s."), strerror (errno));
-
- return true;
-}
-
-/* Parses, performs the HOST command. */
-int
-cmd_host (struct lexer *lexer, struct dataset *ds UNUSED)
-{
- int look_ahead;
-
- if (settings_get_safer_mode ())
- {
- msg (SE, _("This command not allowed when the SAFER option is set."));
- return CMD_FAILURE;
- }
-
- look_ahead = lex_look_ahead (lexer);
- if (look_ahead == '.')
- {
- lex_get (lexer);
- return shell () ? CMD_SUCCESS : CMD_FAILURE;
- }
- 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)));
-
- 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 (struct lexer *lexer, struct dataset *ds)