Adopt use of gnulib for portability.
[pspp-builds.git] / src / command.c
index 4afe70be1130a96af955f2627cbe7724e516244e..4203ac32d0ea7f478ed1dd65a51bae61ed58f13a 100644 (file)
 
    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., 59 Temple Place - Suite 330, Boston, MA
-   02111-1307, USA. */
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
 
-/* AIX requires this to be the first thing in the file.  */
 #include <config.h>
-#if __GNUC__
-#define alloca __builtin_alloca
-#else
-#if HAVE_ALLOCA_H
-#include <alloca.h>
-#else
-#ifdef _AIX
-#pragma alloca
-#else
-#ifndef alloca                 /* predefined by HP cc +Olibcalls */
-char *alloca ();
-#endif
-#endif
-#endif
-#endif
-
-#include <assert.h>
+#include "error.h"
 #include "command.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
 #include <errno.h>
 #include "alloc.h"
+#include "dictionary.h"
 #include "error.h"
-#include "getline.h"
+#include "glob.h"
+#include "getl.h"
 #include "lexer.h"
 #include "main.h"
 #include "settings.h"
@@ -61,7 +46,9 @@ char *alloca ();
 #include <sys/wait.h>
 #endif
 
-#include "debug-print.h"
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
 \f
 /* Global variables. */
 
@@ -76,71 +63,83 @@ const char *cur_proc;
 /* A single command. */
 struct command
   {
-    /* Initialized statically. */
-    char cmd[22];              /* Command name. */
+    const char *name;          /* Command name. */
     int transition[4];         /* Transitions to make from each state. */
     int (*func) (void);                /* Function to call. */
-
-    /* Calculated at startup time. */
-    char *word[3];             /* cmd[], divided into individual words. */
-    struct command *next;      /* Next command with same word[0]. */
+    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*/
   };
 
 /* Define the command array. */
 #define DEFCMD(NAME, T1, T2, T3, T4, FUNC)             \
-       {NAME, {T1, T2, T3, T4}, FUNC, {NULL, NULL, NULL}, NULL},
-#define UNIMPL(NAME, T1, T2, T3, T4)                   \
-       {NAME, {T1, T2, T3, T4}, NULL, {NULL, NULL, NULL}, NULL},
-static struct command cmd_table[] = 
+       {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[] = 
   {
 #include "command.def"
-    {"", {ERRO, ERRO, ERRO, ERRO}, NULL, {NULL, NULL, NULL}, NULL},
   };
 #undef DEFCMD
+#undef DBGCMD
 #undef UNIMPL
-\f
-/* Command parser. */
 
-static struct command *figure_out_command (void);
 
-/* Breaks the `cmd' member of C into individual words and sets C's
-   word[] member appropriately. */
-static void
-split_words (struct command *c)
+/* Complete the line using the name of a command, 
+ * based upon the current prg_state
+ */
+char * 
+pspp_completion_function (const char *text,   int state)
 {
-  char *cmd, *save;
-  int i;
+  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  &&  ! test_mode ) ) 
+       {
+         skip++; 
+         continue;
+       }
+      
+      if ( text == 0 || 0 == strncasecmp (cmd->name, text, strlen(text)))
+       {
+         break;
+       }
+
+      skip++;
+    }
+  
+
+  return xstrdup(cmd->name);
 
-  cmd = xstrdup (c->cmd);
-  for (i = 0; i < 3; i++)
-    cmd = c->word[i] = strtok_r (i == 0 ? cmd : NULL, " -", &save);
 }
 
-/* Initializes the command parser. */
-void
-cmd_init (void)
-{
-  struct command *c;
 
-  /* Break up command names into words. */
-  for (c = cmd_table; c->cmd[0]; c++)
-    split_words (c);
 
-  /* Make chains of commands having the same first word. */
-  for (c = cmd_table; c->cmd[0]; c++)
-    {
-      struct command *first;
-      for (first = c; c[1].word[0] && !strcmp (c[0].word[0], c[1].word[0]); c++)
-       c->next = c + 1;
+#define COMMAND_CNT (sizeof commands / sizeof *commands)
+\f
+/* Command parser. */
 
-      c->next = NULL;
-    }
-}
+static const struct command *parse_command_name (void);
 
 /* Determines whether command C is appropriate to call in this
    part of a FILE TYPE structure. */
 static int
-FILE_TYPE_okay (struct command *c)
+FILE_TYPE_okay (const struct command *c UNUSED)
+#if 0
 {
   int okay = 0;
   
@@ -148,26 +147,27 @@ FILE_TYPE_okay (struct command *c)
       && 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->cmd);
-#if 0
+    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->cmd);
+        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."));
-#endif
   else
     okay = 1;
 
-#if 0
   if (c->func == cmd_record_type)
     fty.had_rec_type = 1;
-#endif
 
   return okay;
 }
+#else
+{
+  return 1;
+}
+#endif
 
 /* Parses an entire PSPP command.  This includes everything from the
    command name to the terminating dot.  Does most of its work by
@@ -176,7 +176,7 @@ FILE_TYPE_okay (struct command *c)
 int
 cmd_parse (void)
 {
-  struct command *cp;  /* Iterator used to find the proper command. */
+  const struct command *cp;    /* Iterator used to find the proper command. */
 
 #if C_ALLOCA
   /* The generic alloca package performs garbage collection when it is
@@ -189,7 +189,7 @@ cmd_parse (void)
     return CMD_SUCCESS;
 
   /* Parse comments. */
-  if ((token == T_ID && !strcmp (tokid, "COMMENT"))
+  if ((token == T_ID && !strcasecmp (tokid, "COMMENT"))
       || token == T_EXP || token == '*' || token == '[')
     {
       lex_skip_comment ();
@@ -200,17 +200,17 @@ cmd_parse (void)
      always an ID token. */
   if (token != T_ID)
     {
-      msg (SE, _("This line does not begin with a valid command name."));
+      lex_error (_("expecting command name"));
       return CMD_FAILURE;
     }
 
   /* Parse the command name. */
-  cp = figure_out_command ();
+  cp = parse_command_name ();
   if (cp == NULL)
     return CMD_FAILURE;
   if (cp->func == NULL)
     {
-      msg (SE, _("%s is not yet implemented."), cp->cmd);
+      msg (SE, _("%s is not yet implemented."), cp->name);
       while (token && token != '.')
        lex_get ();
       return CMD_SUCCESS;
@@ -218,7 +218,8 @@ cmd_parse (void)
 
   /* If we're in a FILE TYPE structure, only certain commands can be
      allowed. */
-  if (pgm_state == STATE_INPUT && vfm_source == &file_type_source
+  if (pgm_state == STATE_INPUT
+      && case_source_is_class (vfm_source, &file_type_source_class)
       && !FILE_TYPE_okay (cp))
     return CMD_FAILURE;
 
@@ -237,15 +238,10 @@ cmd_parse (void)
        N_("%s is only allowed within an input program."),
       };
 
-      msg (SE, gettext (state_name[pgm_state]), cp->cmd);
+      msg (SE, gettext (state_name[pgm_state]), cp->name);
       return CMD_FAILURE;
     }
 
-#if DEBUGGING
-  if (cp->func != cmd_remark)
-    printf (_("%s command beginning\n"), cp->cmd);
-#endif
-
   /* The structured output manager numbers all its tables.  Increment
      the major table number for each separate procedure. */
   som_new_series ();
@@ -259,14 +255,14 @@ cmd_parse (void)
       const char *prev_proc;
       
       prev_proc = cur_proc;
-      cur_proc = cp->cmd;
+      cur_proc = cp->name;
       result = cp->func ();
       cur_proc = prev_proc;
     }
     
     /* Perform the state transition if the command completed
        successfully (at least in part). */
-    if (result != 0)
+    if (result != CMD_FAILURE)
       {
        pgm_state = cp->transition[pgm_state];
 
@@ -277,140 +273,343 @@ cmd_parse (void)
          }
       }
 
-#if DEBUGGING
-    if (cp->func != cmd_remark)
-      printf (_("%s command completed\n\n"), cp->cmd);
-#endif
-
     /* Pass the command's success value up to the caller. */
     return result;
   }
 }
 
-/* Parse the command name and return a pointer to the corresponding
-   struct command if successful.
-   If not successful, return a null pointer. */
-static struct command *
-figure_out_command (void)
+static size_t
+match_strings (const char *a, size_t a_len,
+               const char *b, size_t b_len) 
 {
-  static const char *unk =
-    N_("The identifier(s) specified do not form a valid command name:");
-
-  static const char *inc = 
-    N_("The identifier(s) specified do not form a complete command name:");
-
-  struct command *cp;
+  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++;
+    }
 
-  /* Parse the INCLUDE short form.
-     Note that `@' is a valid character in identifiers. */
-  if (tokid[0] == '@')
-    return &cmd_table[0];
+  return match_len;
+}
 
-  /* Find a command whose first word matches this identifier.
-     If it is the only command that begins with this word, return
-     it. */
-  for (cp = cmd_table; cp->cmd[0]; cp++)
-    if (lex_id_match (cp->word[0], tokid))
-      break;
+/* 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++;
 
-  if (cp->cmd[0] == '\0')
+  /* End of string? */
+  if (*string == '\0') 
     {
-      msg (SE, "%s %s.", gettext (unk), ds_value (&tokstr));
+      *word_len = 0;
       return NULL;
     }
 
-  if (cp->next == NULL)
-    return cp;
+  /* 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 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;
   
-  /* We know that there is more than one command starting with this
-     word.  Read the next word in the command name. */
+  /* 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 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);
+
+  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
   {
-    struct command *ocp = cp;
-    
-    /* Verify that the next token is an identifier, because we
-       must disambiguate this command name. */
-    lex_get ();
-    if (token != T_ID)
+    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))
       {
-       /* If there's a command whose name is the first word only,
-          return it.  This happens with, i.e., PRINT vs. PRINT
-          SPACE. */
-       if (ocp->word[1] == NULL)
-         return ocp;
-       
-       msg (SE, "%s %s.", gettext (inc), ds_value (&tokstr));
-       return NULL;
+        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.  */
+          }
       }
 
-    for (; cp; cp = cp->next)
-      if (cp->word[1] && lex_id_match (cp->word[1], tokid))
-       break;
+  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; 
+    }
+}
 
-    if (cp == NULL)
-      {
-       /* No match.  If there's a command whose name is the first
-          word only, return it.  This happens with, i.e., PRINT
-          vs. PRINT SPACE. */
-       if (ocp->word[1] == NULL)
-         return ocp;
-       
-       msg (SE, "%s %s %s.", gettext (unk), ocp->word[0], tokid);
-       return NULL;
-      }
+/* 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) 
+{
+  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++; 
+
+  return cmd_match_count;
+}
+
+/* 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;
   
-    /* Check whether the next token is an identifier.
-       If not, bail. */
-    if (!isalpha ((unsigned char) (lex_look_ahead ())))
-      {
-       /* Check whether there is an unambiguous interpretation.
-          If not, give an error. */
-       if (cp->word[2]
-           && cp->next
-           && !strcmp (cp->word[1], cp->next->word[1]))
-         {
-           msg (SE, "%s %s %s.", gettext (inc), ocp->word[0], ocp->word[1]);
-           return NULL;
-         }
-       else
-         return cp;
-      }
-  }
+  for (cmd = commands; cmd < commands + COMMAND_CNT; cmd++) 
+    if (cmd_match_words (cmd, words, word_cnt, NULL) == COMPLETE_MATCH) 
+      return cmd; 
+  
+  return NULL;
+}
+
+/* Frees the WORD_CNT words in WORDS. */
+static void
+free_words (char *words[], size_t word_cnt) 
+{
+  size_t idx;
   
-  /* If this command can have a third word, disambiguate based on it. */
-  if (cp->word[2]
-      || (cp->next
-         && cp->next->word[2]
-         && !strcmp (cp->word[1], cp->next->word[1])))
+  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 (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 (idx != 0)
+        *cp++ = ' ';
+      cp = stpcpy (cp, words[idx]);
+    }
+  *cp = '\0';
+
+  msg (SE, _("Unknown command %s."), name);
+
+  free (name);
+}
+
+
+/* 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)
+{
+  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 == '-')) 
     {
-      struct command *ocp = cp;
+      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 ("-");
+
+      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 & !test_mode ) 
+               goto error;
+              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 ();
-      assert (token == T_ID);
-
-      /* Try to find a command with this third word.
-        If found, bail. */
-      for (; cp; cp = cp->next)
-       if (cp->word[2]
-           && !strcmp (cp->word[1], ocp->word[1])
-           && lex_id_match (cp->word[2], tokid))
-         break;
-
-      if (cp != NULL)
-       return cp;
+    }
 
-      /* If no command with this third word found, make sure that
-        there's a command with those first two words but without a
-        third word. */
-      cp = ocp;
-      if (cp->word[2])
-       {
-         msg (SE, "%s %s %s %s.",
-              gettext (unk), ocp->word[0], ocp->word[1], ds_value (&tokstr));
-         return 0;
-       }
+  /* 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]);
+        }
+
+      if ( command->debug && !test_mode ) 
+       goto error;
+
+      free_words (words, word_cnt);
+      return command;
     }
 
-  return cp;
+error:
+  unknown_command_error (words, word_cnt);
+  free_words (words, word_cnt);
+  return NULL;
 }
 \f
 /* Simple commands. */
@@ -451,108 +650,6 @@ cmd_finish (void)
   return CMD_SUCCESS;
 }
 
-/* Extracts a null-terminated 8-or-fewer-character PREFIX from STRING.
-   PREFIX is converted to lowercase.  Removes trailing spaces from
-   STRING as a side effect.  */
-static void
-extract_prefix (char *string, char *prefix)
-{
-  /* Length of STRING. */
-  int len;
-
-  /* Points to the null terminator in STRING (`end pointer'). */
-  char *ep;
-
-  /* Strip spaces from end of STRING. */
-  len = strlen (string);
-  while (len && isspace ((unsigned char) string[len - 1]))
-    string[--len] = 0;
-
-  /* Find null terminator. */
-  ep = memchr (string, '\0', 8);
-  if (!ep)
-    ep = &string[8];
-
-  /* Copy prefix, converting to lowercase. */
-  while (string < ep)
-    *prefix++ = tolower ((unsigned char) (*string++));
-  *prefix = 0;
-}
-
-/* Prints STRING on the console and to the listing file, replacing \n
-   by newline. */
-static void
-output_line (char *string)
-{
-  /* Location of \n in line read in. */
-  char *cp;
-
-  cp = strstr (string, "\\n");
-  while (cp)
-    {
-      *cp = 0;
-      tab_output_text (TAB_LEFT | TAT_NOWRAP, string);
-      string = &cp[2];
-      cp = strstr (string, "\\n");
-    }
-  tab_output_text (TAB_LEFT | TAT_NOWRAP, string);
-}
-
-/* Parse and execute REMARK command. */
-int
-cmd_remark ()
-{
-  /* Points to the line read in. */
-  char *s;
-
-  /* Index into s. */
-  char *cp;
-
-  /* 8-character sentinel used to terminate remark. */
-  char sentinel[9];
-
-  /* Beginning of line used to compare with SENTINEL. */
-  char prefix[9];
-
-  som_blank_line ();
-  
-  s = lex_rest_of_line (NULL);
-  if (*s == '-')
-    {
-      output_line (&s[1]);
-      return CMD_SUCCESS;
-    }
-
-  /* Read in SENTINEL from end of current line. */
-  cp = s;
-  while (isspace ((unsigned char) *cp))
-    cp++;
-  extract_prefix (cp, sentinel);
-  if (sentinel[0] == 0)
-    {
-      msg (SE, _("The sentinel may not be the empty string."));
-      return CMD_FAILURE;
-    }
-
-  /* Read in other lines until we encounter the sentinel. */
-  while (getl_read_line ())
-    {
-      extract_prefix (ds_value (&getl_buf), prefix);
-      if (!strcmp (sentinel, prefix))
-       break;
-
-      /* Output the line. */
-      output_line (ds_value (&getl_buf));
-    }
-
-  /* Calling lex_entire_line() forces the sentinel line to be
-     discarded. */
-  getl_prompt = GETL_PRPT_STANDARD;
-  lex_entire_line ();
-
-  return CMD_SUCCESS;
-}
-
 /* Parses the N command. */
 int
 cmd_n_of_cases (void)
@@ -560,9 +657,6 @@ cmd_n_of_cases (void)
   /* Value for N. */
   int x;
 
-  lex_match_id ("N");
-  lex_match_id ("OF");
-  lex_match_id ("CASES");
   if (!lex_force_int ())
     return CMD_FAILURE;
   x = lex_integer ();
@@ -577,8 +671,7 @@ cmd_n_of_cases (void)
 int
 cmd_execute (void)
 {
-  lex_match_id ("EXECUTE");
-  procedure (NULL, NULL, NULL);
+  procedure (NULL, NULL);
   return lex_end_of_command ();
 }
 
@@ -586,30 +679,29 @@ cmd_execute (void)
 int
 cmd_erase (void)
 {
-  if (set_safer)
-    {
-      msg (SE, _("This command not allowed when the SAFER option is set."));
-      return CMD_FAILURE;
-    }
+  if ( safer_mode() ) 
+    { 
+      msg (SE, _("This command not allowed when the SAFER option is set.")); 
+      return CMD_FAILURE; 
+    } 
   
-  lex_match_id ("ERASE");
   if (!lex_force_match_id ("FILE"))
     return CMD_FAILURE;
   lex_match ('=');
   if (!lex_force_string ())
     return CMD_FAILURE;
 
-  if (remove (ds_value (&tokstr)) == -1)
+  if (remove (ds_c_str (&tokstr)) == -1)
     {
       msg (SW, _("Error removing `%s': %s."),
-          ds_value (&tokstr), strerror (errno));
+          ds_c_str (&tokstr), strerror (errno));
       return CMD_FAILURE;
     }
 
-  return lex_end_of_command ();
+  return CMD_SUCCESS;
 }
 
-#if unix
+#ifdef unix
 /* Spawn a shell process. */
 static int
 shell (void)
@@ -668,7 +760,7 @@ shell (void)
 static int
 run_command (void)
 {
-  char *cmd;
+  const char *cmd;
   int string;
 
   /* Handle either a string argument or a full-line argument. */
@@ -680,12 +772,13 @@ run_command (void)
        lex_get ();
        if (!lex_force_string ())
          return CMD_FAILURE;
-       cmd = ds_value (&tokstr);
+       cmd = ds_c_str (&tokstr);
        string = 1;
       }
     else
       {
        cmd = lex_rest_of_line (NULL);
+        lex_discard_line ();
        string = 0;
       }
   }
@@ -717,15 +810,13 @@ cmd_host (void)
 {
   int code;
 
-  if (set_safer)
-    {
-      msg (SE, _("This command not allowed when the SAFER option is set."));
-      return CMD_FAILURE;
-    }
-  
-  lex_match_id ("HOST");
+  if ( safer_mode() ) 
+    { 
+      msg (SE, _("This command not allowed when the SAFER option is set.")); 
+      return CMD_FAILURE; 
+    } 
 
-#if unix
+#ifdef unix
   /* Figure out whether to invoke an interactive shell or to execute a
      single shell command. */
   if (lex_look_ahead () == '.')
@@ -739,11 +830,11 @@ cmd_host (void)
   /* Make sure that the system has a command interpreter, then run a
      command. */
   if (system (NULL) != 0)
-    success = run_command ();
+    code = run_command ();
   else
     {
       msg (SE, _("No operating system support for this command."));
-      success = CMD_FAILURE;
+      code = CMD_FAILURE;
     }
 #endif /* !unix */
 
@@ -754,9 +845,6 @@ cmd_host (void)
 int
 cmd_new_file (void)
 {
-  lex_match_id ("NEW");
-  lex_match_id ("FILE");
-  
   discard_variables ();
 
   return lex_end_of_command ();
@@ -766,9 +854,6 @@ cmd_new_file (void)
 int
 cmd_clear_transformations (void)
 {
-  lex_match_id ("CLEAR");
-  lex_match_id ("TRANSFORMATIONS");
-
   if (getl_reading_script)
     {
       msg (SW, _("This command is not valid in a syntax file."));
@@ -776,6 +861,8 @@ cmd_clear_transformations (void)
     }
 
   cancel_transformations ();
+  /* FIXME: what about variables created by transformations?
+     They need to be properly initialized. */
 
   return CMD_SUCCESS;
 }