MATRIX SAVE positive tests.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 8 Nov 2021 01:27:19 +0000 (17:27 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 8 Nov 2021 01:27:19 +0000 (17:27 -0800)
src/language/stats/matrix.c
tests/language/stats/matrix.at

index 5214ee3d8be73b5b1c088cbe64ea659cb770cf18..bc9ae3363b775e9883054f7c55b0e3bbcf5a4692 100644 (file)
@@ -105,6 +105,20 @@ struct write_file
     struct u8_line *held;
   };
 
+struct save_file
+  {
+    struct file_handle *file;
+
+    /* Parameters from parsing the first SAVE command for 'file'. */
+    struct string_array variables;
+    struct matrix_expr *names;
+    struct stringi_set strings;
+
+    /* Results from the first attempt to open this save_file. */
+    bool error;
+    struct casewriter *writer;
+  };
+
 struct matrix_state
   {
     struct dataset *dataset;
@@ -112,7 +126,6 @@ struct matrix_state
     struct lexer *lexer;
     struct hmap vars;
     bool in_loop;
-    struct file_handle *prev_save_outfile;
     struct msave_common *common;
 
     struct file_handle *prev_read_file;
@@ -122,6 +135,10 @@ struct matrix_state
     struct file_handle *prev_write_file;
     struct write_file **write_files;
     size_t n_write_files;
+
+    struct file_handle *prev_save_file;
+    struct save_file **save_files;
+    size_t n_save_files;
   };
 
 static struct matrix_var *
@@ -3391,10 +3408,7 @@ struct matrix_cmd
         struct save_command
           {
             struct matrix_expr *expression;
-            struct file_handle *outfile;
-            struct string_array *variables;
-            struct matrix_expr *names;
-            struct stringi_set strings;
+            struct save_file *sf;
           }
         save;
 
@@ -3406,7 +3420,6 @@ struct matrix_cmd
             int c1, c2;
             enum fmt_type format;
             int w;
-            //int d;
             bool symmetric;
             bool reread;
           }
@@ -4346,18 +4359,178 @@ matrix_cmd_execute_release (struct release_command *cmd)
     }
 }
 \f
+static struct save_file *
+save_file_create (struct matrix_state *s, struct file_handle *fh,
+                  struct string_array *variables,
+                  struct matrix_expr *names,
+                  struct stringi_set *strings)
+{
+  for (size_t i = 0; i < s->n_save_files; i++)
+    {
+      struct save_file *sf = s->save_files[i];
+      if (sf->file == fh)
+        {
+          fh_unref (fh);
+
+          string_array_destroy (variables);
+          matrix_expr_destroy (names);
+          stringi_set_destroy (strings);
+
+          return sf;
+        }
+    }
+
+  struct save_file *sf = xmalloc (sizeof *sf);
+  *sf = (struct save_file) {
+    .file = fh,
+    .variables = *variables,
+    .names = names,
+    .strings = STRINGI_SET_INITIALIZER (sf->strings),
+  };
+
+  stringi_set_swap (&sf->strings, strings);
+  stringi_set_destroy (strings);
+
+  s->save_files = xrealloc (s->save_files,
+                             (s->n_save_files + 1) * sizeof *s->save_files);
+  s->save_files[s->n_save_files++] = sf;
+  return sf;
+}
+
+static struct casewriter *
+save_file_open (struct save_file *sf, gsl_matrix *m)
+{
+  if (sf->writer || sf->error)
+    {
+      if (sf->writer)
+        {
+          size_t n_variables = caseproto_get_n_widths (
+            casewriter_get_proto (sf->writer));
+          if (m->size2 != n_variables)
+            {
+              msg (ME, _("The first SAVE to %s within this matrix program "
+                         "had %zu columns, so a %zuĂ—%zu matrix cannot be "
+                         "saved to it."),
+                   fh_get_name (sf->file), n_variables, m->size1, m->size2);
+              return NULL;
+            }
+        }
+      return sf->writer;
+    }
+
+  bool ok = true;
+  struct dictionary *dict = dict_create (get_default_encoding ());
+
+  /* Fill 'names' with user-specified names if there were any, either from
+     sf->variables or sf->names. */
+  struct string_array names = { .n = 0 };
+  if (sf->variables.n)
+    string_array_clone (&names, &sf->variables);
+  else if (sf->names)
+    {
+      gsl_matrix *nm = matrix_expr_evaluate (sf->names);
+      if (nm && is_vector (nm))
+        {
+          gsl_vector nv = to_vector (nm);
+          for (size_t i = 0; i < nv.size; i++)
+            {
+              char *name = trimmed_string (gsl_vector_get (&nv, i));
+              if (dict_id_is_valid (dict, name, true))
+                string_array_append_nocopy (&names, name);
+              else
+                ok = false;
+            }
+        }
+      gsl_matrix_free (nm);
+    }
+
+  struct stringi_set strings;
+  stringi_set_clone (&strings, &sf->strings);
+
+  for (size_t i = 0; dict_get_var_cnt (dict) < m->size2; i++)
+    {
+      char tmp_name[64];
+      const char *name;
+      if (i < names.n)
+        name = names.strings[i];
+      else
+        {
+          snprintf (tmp_name, sizeof tmp_name, "COL%zu", i + 1);
+          name = tmp_name;
+        }
+
+      int width = stringi_set_delete (&strings, name) ? 8 : 0;
+      struct variable *var = dict_create_var (dict, name, width);
+      if (!var)
+        {
+          msg (ME, _("Duplicate variable name %s in SAVE statement."),
+               name);
+          ok = false;
+        }
+    }
+
+  if (!stringi_set_is_empty (&strings))
+    {
+      const char *example = stringi_set_node_get_string (
+        stringi_set_first (&strings));
+      msg (ME, ngettext ("STRINGS specified a variable %s, but no variable "
+                         "with that name was found on SAVE.",
+                         "STRINGS specified %2$zu variables, including %1$s, "
+                         "whose names were not found on SAVE.",
+                         stringi_set_count (&strings)),
+           example, stringi_set_count (&strings));
+      ok = false;
+    }
+  stringi_set_destroy (&strings);
+  string_array_destroy (&names);
+
+  if (!ok)
+    {
+      dict_unref (dict);
+      sf->error = true;
+      return NULL;
+    }
+
+  sf->writer = any_writer_open (sf->file, dict);
+  dict_unref (dict);
+  if (!sf->writer)
+    {
+      sf->error = true;
+      return NULL;
+    }
+  return sf->writer;
+}
+
+static void
+save_file_destroy (struct save_file *sf)
+{
+  if (sf)
+    {
+      fh_unref (sf->file);
+      string_array_destroy (&sf->variables);
+      matrix_expr_destroy (sf->names);
+      stringi_set_destroy (&sf->strings);
+      casewriter_destroy (sf->writer);
+      free (sf);
+    }
+}
+
 static struct matrix_cmd *
 matrix_parse_save (struct matrix_state *s)
 {
   struct matrix_cmd *cmd = xmalloc (sizeof *cmd);
   *cmd = (struct matrix_cmd) {
     .type = MCMD_SAVE,
-    .save = {
-      .strings = STRINGI_SET_INITIALIZER (cmd->save.strings)
-    }
+    .save = { .expression = NULL },
   };
 
+  struct file_handle *fh = NULL;
   struct save_command *save = &cmd->save;
+
+  struct string_array variables = STRING_ARRAY_INITIALIZER;
+  struct matrix_expr *names = NULL;
+  struct stringi_set strings = STRINGI_SET_INITIALIZER (strings);
+
   save->expression = matrix_parse_exp (s);
   if (!save->expression)
     goto error;
@@ -4368,11 +4541,11 @@ matrix_parse_save (struct matrix_state *s)
         {
           lex_match (s->lexer, T_EQUALS);
 
-          fh_unref (save->outfile);
-          save->outfile = (lex_match (s->lexer, T_ASTERISK)
-                           ? fh_inline_file ()
-                           : fh_parse (s->lexer, FH_REF_FILE, s->session));
-          if (!save->outfile)
+          fh_unref (fh);
+          fh = (lex_match (s->lexer, T_ASTERISK)
+                ? fh_inline_file ()
+                : fh_parse (s->lexer, FH_REF_FILE, s->session));
+          if (!fh)
             goto error;
         }
       else if (lex_match_id (s->lexer, "VARIABLES"))
@@ -4388,10 +4561,8 @@ matrix_parse_save (struct matrix_state *s)
           if (!ok)
             goto error;
 
-          string_array_destroy (save->variables);
-          if (!save->variables)
-            save->variables = xmalloc (sizeof *save->variables);
-          *save->variables = (struct string_array) {
+          string_array_clear (&variables);
+          variables = (struct string_array) {
             .strings = names,
             .n = n,
             .allocated = n,
@@ -4400,9 +4571,9 @@ matrix_parse_save (struct matrix_state *s)
       else if (lex_match_id (s->lexer, "NAMES"))
         {
           lex_match (s->lexer, T_EQUALS);
-          matrix_expr_destroy (save->names);
-          save->names = matrix_parse_exp (s);
-          if (!save->names)
+          matrix_expr_destroy (names);
+          names = matrix_parse_exp (s);
+          if (!names)
             goto error;
         }
       else if (lex_match_id (s->lexer, "STRINGS"))
@@ -4410,7 +4581,7 @@ matrix_parse_save (struct matrix_state *s)
           lex_match (s->lexer, T_EQUALS);
           while (lex_token (s->lexer) == T_ID)
             {
-              stringi_set_insert (&save->strings, lex_tokcstr (s->lexer));
+              stringi_set_insert (&strings, lex_tokcstr (s->lexer));
               lex_get (s->lexer);
               if (!lex_match (s->lexer, T_COMMA))
                 break;
@@ -4424,29 +4595,36 @@ matrix_parse_save (struct matrix_state *s)
         }
     }
 
-  if (!save->outfile)
+  if (!fh)
     {
-      if (s->prev_save_outfile)
-        save->outfile = fh_ref (s->prev_save_outfile);
+      if (s->prev_save_file)
+        fh = fh_ref (s->prev_save_file);
       else
         {
           lex_sbc_missing ("OUTFILE");
           goto error;
         }
     }
-  fh_unref (s->prev_save_outfile);
-  s->prev_save_outfile = fh_ref (save->outfile);
+  fh_unref (s->prev_save_file);
+  s->prev_save_file = fh_ref (fh);
 
-  if (save->variables && save->names)
+  if (variables.n && names)
     {
       msg (SW, _("VARIABLES and NAMES both specified; ignoring NAMES."));
-      matrix_expr_destroy (save->names);
-      save->names = NULL;
+      matrix_expr_destroy (names);
+      names = NULL;
     }
 
+  save->sf = save_file_create (s, fh, &variables, names, &strings);
+  fh = NULL;
+
   return cmd;
 
 error:
+  string_array_destroy (&variables);
+  matrix_expr_destroy (names);
+  stringi_set_destroy (&strings);
+  fh_unref (fh);
   matrix_cmd_destroy (cmd);
   return NULL;
 }
@@ -4454,90 +4632,18 @@ error:
 static void
 matrix_cmd_execute_save (const struct save_command *save)
 {
-  assert (save->outfile != fh_inline_file ()); /* XXX not yet implemented */
-
   gsl_matrix *m = matrix_expr_evaluate (save->expression);
   if (!m)
     return;
 
-  bool ok = true;
-  struct dictionary *dict = dict_create (get_default_encoding ());
-  struct string_array names = { .n = 0 };
-  if (save->variables)
-    string_array_clone (&names, save->variables);
-  else if (save->names)
-    {
-      gsl_matrix *nm = matrix_expr_evaluate (save->names);
-      if (nm && is_vector (nm))
-        {
-          gsl_vector nv = to_vector (nm);
-          for (size_t i = 0; i < nv.size; i++)
-            {
-              char *name = trimmed_string (gsl_vector_get (&nv, i));
-              if (dict_id_is_valid (dict, name, true))
-                string_array_append_nocopy (&names, name);
-              else
-                ok = false;
-            }
-        }
-    }
-
-  struct stringi_set strings;
-  stringi_set_clone (&strings, &save->strings);
-
-  for (size_t i = 0; dict_get_var_cnt (dict) < m->size2; i++)
-    {
-      char tmp_name[64];
-      const char *name;
-      if (i < names.n)
-        name = names.strings[i];
-      else
-        {
-          snprintf (tmp_name, sizeof tmp_name, "COL%zu", i + 1);
-          name = tmp_name;
-        }
-
-      int width = stringi_set_delete (&strings, name) ? 8 : 0;
-      struct variable *var = dict_create_var (dict, name, width);
-      if (!var)
-        {
-          msg (SE, _("Duplicate variable name %s in SAVE statement."),
-               name);
-          ok = false;
-        }
-    }
-
-  if (!stringi_set_is_empty (&strings))
-    {
-      const char *example = stringi_set_node_get_string (
-        stringi_set_first (&strings));
-      msg (SE, ngettext ("STRINGS specified a variable %s, but no variable "
-                         "with that name was found on SAVE.",
-                         "STRINGS specified %2$zu variables, including %1$s, "
-                         "whose names were not found on SAVE.",
-                         stringi_set_count (&strings)),
-           example, stringi_set_count (&strings));
-      ok = false;
-    }
-  stringi_set_destroy (&strings);
-  string_array_destroy (&names);
-
-  if (!ok)
-    {
-      gsl_matrix_free (m);
-      dict_unref (dict);
-      return;
-    }
-
-  struct casewriter *writer = any_writer_open (save->outfile, dict);
+  struct casewriter *writer = save_file_open (save->sf, m);
   if (!writer)
     {
       gsl_matrix_free (m);
-      dict_unref (dict);
       return;
     }
 
-  const struct caseproto *proto = dict_get_proto (dict);
+  const struct caseproto *proto = casewriter_get_proto (writer);
   for (size_t y = 0; y < m->size1; y++)
     {
       struct ccase *c = case_create (proto);
@@ -4553,10 +4659,7 @@ matrix_cmd_execute_save (const struct save_command *save)
         }
       casewriter_write (writer, c);
     }
-  casewriter_destroy (writer);
-
   gsl_matrix_free (m);
-  dict_unref (dict);
 }
 \f
 static struct matrix_cmd *
@@ -6628,10 +6731,6 @@ matrix_cmd_destroy (struct matrix_cmd *cmd)
 
     case MCMD_SAVE:
       matrix_expr_destroy (cmd->save.expression);
-      fh_unref (cmd->save.outfile);
-      string_array_destroy (cmd->save.variables);
-      matrix_expr_destroy (cmd->save.names);
-      stringi_set_destroy (&cmd->save.strings);
       break;
 
     case MCMD_READ:
@@ -6785,7 +6884,6 @@ cmd_matrix (struct lexer *lexer, struct dataset *ds)
       free (var);
     }
   hmap_destroy (&state.vars);
-  fh_unref (state.prev_save_outfile);
   if (state.common)
     {
       dict_unref (state.common->dict);
@@ -6800,6 +6898,10 @@ cmd_matrix (struct lexer *lexer, struct dataset *ds)
   for (size_t i = 0; i < state.n_write_files; i++)
     write_file_destroy (state.write_files[i]);
   free (state.write_files);
+  fh_unref (state.prev_save_file);
+  for (size_t i = 0; i < state.n_save_files; i++)
+    save_file_destroy (state.save_files[i]);
+  free (state.save_files);
 
   return CMD_SUCCESS;
 }
index 306669e271a37afde80acf5e9fc66621a51451c2..d7966f415b671899d6eec23e8f6aa21005ffc328 100644 (file)
@@ -3096,3 +3096,35 @@ matrix.sps:25: error: MATRIX: GET: Variable d is not numeric.
 error: GET cannot read empty active file.
 ])
 AT_CLEANUP
+
+AT_SETUP([MATRIX - SAVE])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+SAVE {1,2,3; 4,5,6}/OUTFILE='matrix.sav'.
+SAVE {7,8,9}/VARIABLES=a b c d.
+
+SAVE {1,2,3}/OUTFILE='matrix2.sav'/VARIABLES=v01 TO v03.
+SAVE {4,5,6}/NAMES={'x', 'y', 'z', 'w'}.
+
+SAVE {1,'abcd',3}/OUTFILE='matrix3.sav'/NAMES={'a', 'b', 'c'}/STRINGS=b.
+SAVE {4,'xyzw',6}/STRINGS=a, b.
+END MATRIX.
+])
+AT_CHECK([pspp matrix.sps])
+AT_CHECK([pspp-convert matrix.sav matrix.csv && cat matrix.csv], [0], [dnl
+COL1,COL2,COL3
+1,2,3
+4,5,6
+7,8,9
+])
+AT_CHECK([pspp-convert matrix2.sav matrix2.csv && cat matrix2.csv], [0], [dnl
+v01,v02,v03
+1,2,3
+4,5,6
+])
+AT_CHECK([pspp-convert matrix3.sav matrix3.csv && cat matrix3.csv], [0], [dnl
+a,b,c
+1,abcd,3
+4,xyzw,6
+])
+AT_CLEANUP
\ No newline at end of file