Clean up how transformations work.
[pspp] / src / language / command.c
index 3da5da69919a510adbd56f4f39c79df1de31847b..bffd13ebb0d74b119de6d296b7e4f4fe3ba91f60 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2009, 2010 Free Software Foundation, Inc.
+   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
 #include <errno.h>
 
 #include "data/casereader.h"
+#include "data/dataset.h"
 #include "data/dictionary.h"
-#include "data/procedure.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 "language/prompt.h"
 #include "libpspp/assertion.h"
 #include "libpspp/compiler.h"
+#include "libpspp/i18n.h"
 #include "libpspp/message.h"
 #include "libpspp/str.h"
-#include "libpspp/getl.h"
-#include "output/text-item.h"
+#include "output/driver.h"
+#include "output/output-item.h"
 
-#include "xalloc.h"
 #include "xmalloca.h"
 
 #include "gettext.h"
 static inline bool
 cmd_result_is_valid (enum cmd_result result)
 {
-  return (result == CMD_SUCCESS || result == CMD_EOF || result == CMD_FINISH
-          || (result >= CMD_PRIVATE_FIRST && result <= CMD_PRIVATE_LAST)
-          || result == CMD_FAILURE || result == CMD_NOT_IMPLEMENTED
-          || result == CMD_CASCADING_FAILURE);
+  switch (result)
+    {
+    case CMD_SUCCESS:
+    case CMD_EOF:
+    case CMD_FINISH:
+    case CMD_FAILURE:
+    case CMD_NOT_IMPLEMENTED:
+    case CMD_CASCADING_FAILURE:
+      return true;
+
+    default:
+      return false;
+    }
 }
 
 /* Returns true if RESULT indicates success,
@@ -77,20 +86,23 @@ 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_INPUT_PROGRAM = 0x04,   /* Allowed in INPUT PROGRAM. */
-    S_FILE_TYPE = 0x08,       /* Allowed in FILE TYPE. */
-    S_ANY = 0x0f              /* Allowed anywhere. */
+    S_INITIAL = 1 << CMD_STATE_INITIAL,
+    S_DATA = 1 << CMD_STATE_DATA,
+    S_INPUT_PROGRAM = 1 << CMD_STATE_INPUT_PROGRAM,
+    S_FILE_TYPE = 1 << CMD_STATE_FILE_TYPE,
+    S_NESTED_DATA = 1 << CMD_STATE_NESTED_DATA,
+    S_NESTED_INPUT_PROGRAM = 1 << CMD_STATE_NESTED_INPUT_PROGRAM,
+
+    S_NESTED_ANY = S_NESTED_DATA | S_NESTED_INPUT_PROGRAM,
+    S_ANY = S_INITIAL | S_DATA | S_INPUT_PROGRAM | S_FILE_TYPE | S_NESTED_ANY,
   };
 
 /* Other command requirements. */
 enum flags
   {
-    F_ENHANCED = 0x10,        /* Allowed only in enhanced syntax mode. */
-    F_TESTING = 0x20,         /* Allowed only in testing mode. */
-    F_KEEP_FINAL_TOKEN = 0x40,/* Don't skip final token in command name. */
-    F_ABBREV = 0x80           /* Not a candidate for name completion. */
+    F_ENHANCED = 1 << 0,        /* Allowed only in enhanced syntax mode. */
+    F_TESTING = 1 << 1,         /* Allowed only in testing mode. */
+    F_ABBREV = 1 << 2           /* Not a candidate for name completion. */
   };
 
 /* A single command. */
@@ -112,15 +124,16 @@ static const struct command commands[] =
 #undef DEF_CMD
 #undef UNIMPL_CMD
 
-static const size_t command_cnt = sizeof commands / sizeof *commands;
+static const size_t n_commands = 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 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 (struct lexer *lexer);
+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
@@ -130,13 +143,14 @@ enum cmd_result
 cmd_parse_in_state (struct lexer *lexer, struct dataset *ds,
                    enum cmd_state state)
 {
+  struct session *session = dataset_session (ds);
   int result;
 
   result = do_parse_command (lexer, ds, state);
 
+  ds = session_active_dataset (session);
   assert (!proc_is_open (ds));
   unset_cmd_algorithm ();
-  dict_clear_aux (dataset_dict (ds));
   if (!dataset_end_of_command (ds))
     result = CMD_CASCADING_FAILURE;
 
@@ -148,8 +162,8 @@ cmd_parse (struct lexer *lexer, struct dataset *ds)
 {
   const struct dictionary *dict = dataset_dict (ds);
   return cmd_parse_in_state (lexer, ds,
-                            proc_has_active_file (ds) &&
-                            dict_get_var_cnt (dict) > 0 ?
+                            dataset_has_source (ds) &&
+                            dict_get_n_vars (dict) > 0 ?
                             CMD_STATE_DATA : CMD_STATE_INITIAL);
 }
 
@@ -161,13 +175,12 @@ do_parse_command (struct lexer *lexer,
                  struct dataset *ds, enum cmd_state state)
 {
   const struct command *command = NULL;
+  size_t nesting_level = SIZE_MAX;
   enum cmd_result result;
-  bool opened = false;
+  int n_tokens;
 
   /* Read the command's first token. */
-  prompt_set_style (PROMPT_FIRST);
   set_completion_state (state);
-  lex_get (lexer);
   if (lex_token (lexer) == T_STOP)
     {
       result = CMD_EOF;
@@ -180,17 +193,17 @@ do_parse_command (struct lexer *lexer,
       goto finish;
     }
 
-  prompt_set_style (PROMPT_LATER);
-
   /* Parse the command name. */
-  command = parse_command_name (lexer);
+  command = parse_command_name (lexer, &n_tokens);
   if (command == NULL)
     {
       result = CMD_FAILURE;
       goto finish;
     }
-  text_item_submit (text_item_create (TEXT_ITEM_COMMAND_OPEN, command->name));
-  opened = true;
+
+  nesting_level = output_open_group (group_item_create_nocopy (
+                                       utf8_to_title (command->name),
+                                       utf8_to_title (command->name)));
 
   if (command->function == NULL)
     {
@@ -216,26 +229,28 @@ do_parse_command (struct lexer *lexer,
   else
     {
       /* Execute command. */
+      int i;
+
+      for (i = 0; i < n_tokens; i++)
+        lex_get (lexer);
       result = command->function (lexer, ds);
     }
 
   assert (cmd_result_is_valid (result));
 
- finish:
+finish:
   if (cmd_result_is_failure (result))
-    {
-      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;
-       }
-    }
+    lex_interactive_reset (lexer);
+  else if (result == CMD_SUCCESS)
+    result = lex_end_of_command (lexer);
 
-  if (opened)
-    text_item_submit (text_item_create (TEXT_ITEM_COMMAND_CLOSE,
-                                        command->name));
+  lex_discard_rest_of_command (lexer);
+  if (result != CMD_EOF && result != CMD_FINISH)
+    while (lex_token (lexer) == T_ENDCMD)
+      lex_get (lexer);
+
+  if (nesting_level != SIZE_MAX)
+    output_close_groups (nesting_level);
 
   return result;
 }
@@ -248,7 +263,7 @@ find_best_match (struct substring s, const struct command **matchp)
   int missing_words;
 
   command_matcher_init (&cm, s);
-  for (cmd = commands; cmd < &commands[command_cnt]; cmd++)
+  for (cmd = commands; cmd < &commands[n_commands]; cmd++)
     command_matcher_add (&cm, ss_cstr (cmd->name), CONST_CAST (void *, cmd));
 
   *matchp = command_matcher_get_match (&cm);
@@ -259,51 +274,65 @@ find_best_match (struct substring s, const struct command **matchp)
   return missing_words;
 }
 
-/* Parse the command name and return a pointer to the corresponding
-   struct command if successful.
-   If not successful, return a null pointer. */
+static bool
+parse_command_word (struct lexer *lexer, struct string *s, int n)
+{
+  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;
+    }
+}
+
+/* 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)
+parse_command_name (struct lexer *lexer, int *n_tokens)
 {
   const struct command *command;
   int missing_words;
   struct string s;
-
-  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;
-    }
+  int word;
 
   command = NULL;
   missing_words = 0;
   ds_init_empty (&s);
-  for (;;)
+  word = 0;
+  while (parse_command_word (lexer, &s, word))
     {
-      if (lex_token (lexer) == T_DASH)
-        ds_put_byte (&s, '-');
-      else if (lex_token (lexer) == T_ID)
-        {
-          if (!ds_is_empty (&s) && ds_last (&s) != '-')
-            ds_put_byte (&s, ' ');
-          ds_put_cstr (&s, lex_tokid (lexer));
-        }
-      else if (lex_is_integer (lexer) && lex_integer (lexer) >= 0)
-        {
-          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);
+      word++;
     }
 
   if (command == NULL && missing_words > 0)
@@ -320,18 +349,10 @@ parse_command_name (struct lexer *lexer)
       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));
-    }
 
   ds_destroy (&s);
+
+  *n_tokens = (word + 1) + missing_words;
   return command;
 }
 
@@ -340,72 +361,72 @@ parse_command_name (struct lexer *lexer)
 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));
+  return command->states & (1 << state);
 }
 
 /* Emits an appropriate error message for trying to invoke
    COMMAND in STATE. */
-static bool
+static void
 report_state_mismatch (const struct command *command, enum cmd_state state)
 {
   assert (!in_correct_state (command, state));
-  if (state == CMD_STATE_INITIAL || state == CMD_STATE_DATA)
+
+  switch (state)
     {
-      switch (command->states)
+    case CMD_STATE_INITIAL:
+    case CMD_STATE_DATA:
+      switch ((int) command->states
+              & (S_INITIAL | S_DATA | S_INPUT_PROGRAM | S_FILE_TYPE))
         {
           /* One allowed state. */
         case S_INITIAL:
-          msg (SE, _("%s is allowed only before the active file has "
+          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 file has "
+          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);
+          msg (SE, _("%s is allowed only inside %s."),
+               command->name, "INPUT PROGRAM");
           break;
         case S_FILE_TYPE:
-          msg (SE, _("%s is allowed only inside FILE TYPE."), command->name);
+          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 file has "
-                     "been defined or inside INPUT PROGRAM."), command->name);
+          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 file has "
-                     "been defined or inside FILE TYPE."), command->name);
+          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 file has "
-                     "been defined or inside INPUT PROGRAM."), command->name);
+          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 file has "
-                     "been defined or inside FILE TYPE."), command->name);
+          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 INPUT PROGRAM "
-                     "or inside FILE TYPE."), command->name);
+          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 file has "
+          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 file has "
+          msg (SE, _("%s is allowed only before the active dataset has "
                      "been defined, inside INPUT PROGRAM, or inside "
                      "FILE TYPE."), command->name);
           break;
@@ -421,13 +442,35 @@ report_state_mismatch (const struct command *command, enum cmd_state state)
         default:
           NOT_REACHED ();
         }
-    }
-  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");
+      break;
+
+    case CMD_STATE_INPUT_PROGRAM:
+      msg (SE, _("%s is not allowed inside %s."),
+           command->name, "INPUT PROGRAM");
+      break;
+
+    case CMD_STATE_FILE_TYPE:
+      msg (SE, _("%s is not allowed inside %s."), command->name, "FILE TYPE");
+      break;
+
+    case CMD_STATE_NESTED_DATA:
+    case CMD_STATE_NESTED_INPUT_PROGRAM:
+      switch ((int) command->states & S_NESTED_ANY)
+        {
+        case 0:
+          msg (SE, _("%s is not allowed inside DO IF or LOOP."), command->name);
+          break;
+
+        case S_NESTED_DATA:
+          msg (SE, _("In INPUT PROGRAM, "
+                     "%s is not allowed inside DO IF or LOOP."), command->name);
+          break;
 
-  return false;
+        default:
+          NOT_REACHED ();
+        }
+      break;
+    }
 }
 \f
 /* Command name completion. */
@@ -450,7 +493,7 @@ cmd_complete (const char *prefix, const struct command **cmd)
   if (*cmd == NULL)
     *cmd = commands;
 
-  for (; *cmd < commands + command_cnt; (*cmd)++)
+  for (; *cmd < commands + n_commands; (*cmd)++)
     if (!memcasecmp ((*cmd)->name, prefix, strlen (prefix))
         && (!((*cmd)->flags & F_TESTING) || settings_get_testing_mode ())
         && (!((*cmd)->flags & F_ENHANCED) || settings_get_syntax () == ENHANCED)
@@ -475,36 +518,36 @@ cmd_finish (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
 int
 cmd_n_of_cases (struct lexer *lexer, struct dataset *ds)
 {
-  /* Value for N. */
-  int x;
-
-  if (!lex_force_int (lexer))
+  if (!lex_force_int_range (lexer, "N OF CASES", 1, LONG_MAX))
     return CMD_FAILURE;
-  x = lex_integer (lexer);
+  long n = lex_integer (lexer);
   lex_get (lexer);
   if (!lex_match_id (lexer, "ESTIMATED"))
-    dict_set_case_limit (dataset_dict (ds), x);
+    dict_set_case_limit (dataset_dict (ds), n);
 
-  return lex_end_of_command (lexer);
+  return CMD_SUCCESS;
 }
 
 /* Parses, performs the EXECUTE procedure. */
 int
-cmd_execute (struct lexer *lexer, struct dataset *ds)
+cmd_execute (struct lexer *lexer UNUSED, struct dataset *ds)
 {
   bool ok = casereader_destroy (proc_open (ds));
   if (!proc_commit (ds) || !ok)
     return CMD_CASCADING_FAILURE;
-  return lex_end_of_command (lexer);
+  return CMD_SUCCESS;
 }
 
 /* Parses, performs the ERASE command. */
 int
 cmd_erase (struct lexer *lexer, struct dataset *ds UNUSED)
 {
+  char *filename;
+  int retval;
+
   if (settings_get_safer_mode ())
     {
-      msg (SE, _("This command not allowed when the SAFER option is set."));
+      msg (SE, _("This command not allowed when the %s option is set."), "SAFER");
       return CMD_FAILURE;
     }
 
@@ -514,29 +557,25 @@ cmd_erase (struct lexer *lexer, struct dataset *ds UNUSED)
   if (!lex_force_string (lexer))
     return CMD_FAILURE;
 
-  if (remove (ds_cstr (lex_tokstr (lexer))) == -1)
+  filename = utf8_to_filename (lex_tokcstr (lexer));
+  retval = remove (filename);
+  free (filename);
+
+  if (retval == -1)
     {
       msg (SW, _("Error removing `%s': %s."),
-          ds_cstr (lex_tokstr (lexer)), strerror (errno));
+           lex_tokcstr (lexer), strerror (errno));
       return CMD_FAILURE;
     }
+  lex_get (lexer);
 
   return CMD_SUCCESS;
 }
 
 /* Parses, performs the NEW FILE command. */
 int
-cmd_new_file (struct lexer *lexer, struct dataset *ds)
-{
-  proc_discard_active_file (ds);
-
-  return lex_end_of_command (lexer);
-}
-
-/* Parses a comment. */
-int
-cmd_comment (struct lexer *lexer, struct dataset *ds UNUSED)
+cmd_new_file (struct lexer *lexer UNUSED, struct dataset *ds)
 {
-  lex_skip_comment (lexer);
+  dataset_clear (ds);
   return CMD_SUCCESS;
 }