MSAVE tests
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 19 Nov 2021 03:37:23 +0000 (19:37 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 19 Nov 2021 03:37:23 +0000 (19:37 -0800)
doc/matrices.texi
src/language/stats/matrix.c
tests/language/stats/matrix.at

index 565cdbd2571301dd97f7653baa36dd08e9bab62f..65dbd94b628cf1847dbcfa3d945b2fe0721f76f9 100644 (file)
@@ -2563,7 +2563,7 @@ warning and does not change the variable.
       [@t{/FNAMES}@t{=}@i{variable}@dots{}]@t{.}
 @end display
 
-The @code{MSAVE} command evaluates the @i{expression} specifies just
+The @code{MSAVE} command evaluates the @i{expression} specified just
 after the command name, and writes the resulting matrix to a matrix
 file (@pxref{Matrix Files}).
 
index ed43412a32075b6668f49f701372652e5e8c850e..f79a1f8a094675a89b96b2c404818b6ec2fcc8c0 100644 (file)
@@ -81,10 +81,15 @@ struct msave_common
     struct string_array variables;
     struct string_array fnames;
     struct string_array snames;
-    bool has_factors;
-    bool has_splits;
     size_t n_varnames;
 
+    /* Collects and owns factors and splits.  The individual msave_command
+       structs point to these but do not own them. */
+    struct matrix_expr **factors;
+    size_t n_factors, allocated_factors;
+    struct matrix_expr **splits;
+    size_t n_splits, allocated_splits;
+
     /* Execution state. */
     struct dictionary *dict;
     struct casewriter *writer;
@@ -2546,6 +2551,7 @@ matrix_to_vector (gsl_matrix *m)
   assert (!v.owner);
   v.owner = 1;
   m->owner = 0;
+  gsl_matrix_free (m);
   return xmemdup (&v, sizeof v);
 }
 
@@ -3474,11 +3480,10 @@ struct matrix_cmd
         struct msave_command
           {
             struct msave_common *common;
-            char *varname_;
             struct matrix_expr *expr;
             const char *rowtype;
-            struct matrix_expr *factors;
-            struct matrix_expr *splits;
+            const struct matrix_expr *factors;
+            const struct matrix_expr *splits;
           }
          msave;
 
@@ -5788,8 +5793,6 @@ parse_var_names (struct lexer *lexer, struct string_array *sa)
   string_array_clear (sa);
 
   struct dictionary *dict = dict_create (get_default_encoding ());
-  dict_create_var_assert (dict, "ROWTYPE_", 8);
-  dict_create_var_assert (dict, "VARNAME_", 8);
   char **names;
   size_t n_names;
   bool ok = parse_DATA_LIST_vars (lexer, dict, &names, &n_names,
@@ -5798,6 +5801,17 @@ parse_var_names (struct lexer *lexer, struct string_array *sa)
 
   if (ok)
     {
+      for (size_t i = 0; i < n_names; i++)
+        if (ss_equals_case (ss_cstr (names[i]), ss_cstr ("ROWTYPE_"))
+            || ss_equals_case (ss_cstr (names[i]), ss_cstr ("VARNAME_")))
+          {
+            msg (SE, _("Variable name %s is reserved."), names[i]);
+            for (size_t j = 0; j < n_names; j++)
+              free (names[i]);
+            free (names);
+            return false;
+          }
+
       string_array_clear (sa);
       sa->strings = names;
       sa->n = sa->allocated = n_names;
@@ -5806,7 +5820,7 @@ parse_var_names (struct lexer *lexer, struct string_array *sa)
 }
 
 static void
-msave_common_uninit (struct msave_common *common)
+msave_common_destroy (struct msave_common *common)
 {
   if (common)
     {
@@ -5814,6 +5828,19 @@ msave_common_uninit (struct msave_common *common)
       string_array_destroy (&common->variables);
       string_array_destroy (&common->fnames);
       string_array_destroy (&common->snames);
+
+      for (size_t i = 0; i < common->n_factors; i++)
+        matrix_expr_destroy (common->factors[i]);
+      free (common->factors);
+
+      for (size_t i = 0; i < common->n_splits; i++)
+        matrix_expr_destroy (common->splits[i]);
+      free (common->splits);
+
+      dict_unref (common->dict);
+      casewriter_destroy (common->writer);
+
+      free (common);
     }
 }
 
@@ -5843,16 +5870,19 @@ compare_variables (const char *keyword,
 static struct matrix_cmd *
 matrix_parse_msave (struct matrix_state *s)
 {
-  struct msave_common common = { .outfile = NULL };
+  struct msave_common *common = xmalloc (sizeof *common);
+  *common = (struct msave_common) { .outfile = NULL };
+
   struct matrix_cmd *cmd = xmalloc (sizeof *cmd);
   *cmd = (struct matrix_cmd) { .type = MCMD_MSAVE, .msave = { .expr = NULL } };
 
+  struct matrix_expr *splits = NULL;
+  struct matrix_expr *factors = NULL;
+
   struct msave_command *msave = &cmd->msave;
-  if (lex_token (s->lexer) == T_ID)
-    msave->varname_ = ss_xstrdup (lex_tokss (s->lexer));
   msave->expr = matrix_parse_exp (s);
   if (!msave->expr)
-    return NULL;
+    goto error;
 
   while (lex_match (s->lexer, T_SLASH))
     {
@@ -5868,42 +5898,42 @@ matrix_parse_msave (struct matrix_state *s)
         {
           lex_match (s->lexer, T_EQUALS);
 
-          fh_unref (common.outfile);
-          common.outfile = fh_parse (s->lexer, FH_REF_FILE, NULL);
-          if (!common.outfile)
+          fh_unref (common->outfile);
+          common->outfile = fh_parse (s->lexer, FH_REF_FILE, NULL);
+          if (!common->outfile)
             goto error;
         }
       else if (lex_match_id (s->lexer, "VARIABLES"))
         {
-          if (!parse_var_names (s->lexer, &common.variables))
+          if (!parse_var_names (s->lexer, &common->variables))
             goto error;
         }
       else if (lex_match_id (s->lexer, "FNAMES"))
         {
-          if (!parse_var_names (s->lexer, &common.fnames))
+          if (!parse_var_names (s->lexer, &common->fnames))
             goto error;
         }
       else if (lex_match_id (s->lexer, "SNAMES"))
         {
-          if (!parse_var_names (s->lexer, &common.snames))
+          if (!parse_var_names (s->lexer, &common->snames))
             goto error;
         }
       else if (lex_match_id (s->lexer, "SPLIT"))
         {
           lex_match (s->lexer, T_EQUALS);
 
-          matrix_expr_destroy (msave->splits);
-          msave->splits = matrix_parse_exp (s);
-          if (!msave->splits)
+          matrix_expr_destroy (splits);
+          splits = matrix_parse_exp (s);
+          if (!splits)
             goto error;
         }
       else if (lex_match_id (s->lexer, "FACTOR"))
         {
           lex_match (s->lexer, T_EQUALS);
 
-          matrix_expr_destroy (msave->factors);
-          msave->factors = matrix_parse_exp (s);
-          if (!msave->factors)
+          matrix_expr_destroy (factors);
+          factors = matrix_parse_exp (s);
+          if (!factors)
             goto error;
         }
       else
@@ -5918,49 +5948,31 @@ matrix_parse_msave (struct matrix_state *s)
       lex_sbc_missing ("TYPE");
       goto error;
     }
-  common.has_splits = msave->splits || common.snames.n;
-  common.has_factors = msave->factors || common.fnames.n;
-
-  struct msave_common *c = s->common ? s->common : &common;
-  if (c->fnames.n && !msave->factors)
-    {
-      msg (SE, _("FNAMES requires FACTOR."));
-      goto error;
-    }
-  if (c->snames.n && !msave->splits)
-    {
-      msg (SE, _("SNAMES requires SPLIT."));
-      goto error;
-    }
-  if (c->has_factors && !common.has_factors)
-    {
-      msg (SE, _("%s is required because it was present on the first "
-                 "MSAVE in this MATRIX command."), "FACTOR");
-      goto error;
-    }
-  if (c->has_splits && !common.has_splits)
-    {
-      msg (SE, _("%s is required because it was present on the first "
-                 "MSAVE in this MATRIX command."), "SPLIT");
-      goto error;
-    }
 
   if (!s->common)
     {
-      if (!common.outfile)
+      if (common->fnames.n && !factors)
+        {
+          msg (SE, _("FNAMES requires FACTOR."));
+          goto error;
+        }
+      if (common->snames.n && !splits)
+        {
+          msg (SE, _("SNAMES requires SPLIT."));
+          goto error;
+        }
+      if (!common->outfile)
         {
           lex_sbc_missing ("OUTFILE");
           goto error;
         }
-      s->common = xmemdup (&common, sizeof common);
+      s->common = common;
     }
   else
     {
-      if (common.outfile)
+      if (common->outfile)
         {
-          bool same = common.outfile == s->common->outfile;
-          fh_unref (common.outfile);
-          if (!same)
+          if (!fh_equal (common->outfile, s->common->outfile))
             {
               msg (SE, _("OUTFILE must name the same file on each MSAVE "
                          "within a single MATRIX command."));
@@ -5968,25 +5980,47 @@ matrix_parse_msave (struct matrix_state *s)
             }
         }
       if (!compare_variables ("VARIABLES",
-                              &common.variables, &s->common->variables)
-          || !compare_variables ("FNAMES", &common.fnames, &s->common->fnames)
-          || !compare_variables ("SNAMES", &common.snames, &s->common->snames))
+                              &common->variables, &s->common->variables)
+          || !compare_variables ("FNAMES", &common->fnames, &s->common->fnames)
+          || !compare_variables ("SNAMES", &common->snames, &s->common->snames))
         goto error;
-      msave_common_uninit (&common);
+      msave_common_destroy (common);
     }
   msave->common = s->common;
-  if (!msave->varname_)
-    msave->varname_ = xasprintf ("MAT%zu", ++s->common->n_varnames);
+
+  struct msave_common *c = s->common;
+  if (factors)
+    {
+      if (c->n_factors >= c->allocated_factors)
+        c->factors = x2nrealloc (c->factors, &c->allocated_factors,
+                                 sizeof *c->factors);
+      c->factors[c->n_factors++] = factors;
+    }
+  if (c->n_factors > 0)
+    msave->factors = c->factors[c->n_factors - 1];
+
+  if (splits)
+    {
+      if (c->n_splits >= c->allocated_splits)
+        c->splits = x2nrealloc (c->splits, &c->allocated_splits,
+                                sizeof *c->splits);
+      c->splits[c->n_splits++] = splits;
+    }
+  if (c->n_splits > 0)
+    msave->splits = c->splits[c->n_splits - 1];
+
   return cmd;
 
 error:
-  msave_common_uninit (&common);
+  matrix_expr_destroy (splits);
+  matrix_expr_destroy (factors);
+  msave_common_destroy (common);
   matrix_cmd_destroy (cmd);
   return NULL;
 }
 
 static gsl_vector *
-matrix_expr_evaluate_vector (struct matrix_expr *e, const char *name)
+matrix_expr_evaluate_vector (const struct matrix_expr *e, const char *name)
 {
   gsl_matrix *m = matrix_expr_evaluate (e);
   if (!m)
@@ -6021,8 +6055,9 @@ msave_create_dict (const struct msave_common *common)
   const char *dup_split = msave_add_vars (dict, &common->snames);
   if (dup_split)
     {
-      msg (SE, _("Duplicate SPLIT variable name %s."), dup_split);
-      goto error;
+      /* Should not be possible because the parser ensures that the names are
+         unique. */
+      NOT_REACHED ();
     }
 
   dict_create_var_assert (dict, "ROWTYPE_", 8);
@@ -6066,10 +6101,10 @@ matrix_cmd_execute_msave (struct msave_command *msave)
     for (size_t i = 0; i < m->size2; i++)
       string_array_append_nocopy (&common->variables,
                                   xasprintf ("COL%zu", i + 1));
-
-  if (m->size2 != common->variables.n)
+  else if (m->size2 != common->variables.n)
     {
-      msg (SE, _("Matrix on MSAVE has %zu columns instead of required %zu."),
+      msg (SE,
+           _("Matrix on MSAVE has %zu columns but there are %zu variables."),
            m->size2, common->variables.n);
       goto error;
     }
@@ -6084,11 +6119,10 @@ matrix_cmd_execute_msave (struct msave_command *msave)
         for (size_t i = 0; i < factors->size; i++)
           string_array_append_nocopy (&common->fnames,
                                       xasprintf ("FAC%zu", i + 1));
-
-      if (factors->size != common->fnames.n)
+      else if (factors->size != common->fnames.n)
         {
           msg (SE, _("There are %zu factor variables, "
-                     "but %zu split values were supplied."),
+                     "but %zu factor values were supplied."),
                common->fnames.n, factors->size);
           goto error;
         }
@@ -6100,16 +6134,15 @@ matrix_cmd_execute_msave (struct msave_command *msave)
       if (!splits)
         goto error;
 
-      if (!common->fnames.n)
+      if (!common->snames.n)
         for (size_t i = 0; i < splits->size; i++)
-          string_array_append_nocopy (&common->fnames,
+          string_array_append_nocopy (&common->snames,
                                       xasprintf ("SPL%zu", i + 1));
-
-      if (splits->size != common->fnames.n)
+      else if (splits->size != common->snames.n)
         {
           msg (SE, _("There are %zu split variables, "
                      "but %zu split values were supplied."),
-               common->fnames.n, splits->size);
+               common->snames.n, splits->size);
           goto error;
         }
     }
@@ -6130,6 +6163,8 @@ matrix_cmd_execute_msave (struct msave_command *msave)
       common->dict = dict;
     }
 
+  bool matrix = (!strcmp (msave->rowtype, "COV")
+                 || !strcmp (msave->rowtype, "CORR"));
   for (size_t y = 0; y < m->size1; y++)
     {
       struct ccase *c = case_create (dict_get_proto (common->dict));
@@ -6150,8 +6185,11 @@ matrix_cmd_execute_msave (struct msave_command *msave)
           *case_num_rw_idx (c, idx++) = gsl_vector_get (factors, i);
 
       /* VARNAME_. */
+      const char *varname_ = (matrix && y < common->variables.n
+                              ? common->variables.strings[y]
+                              : "");
       buf_copy_str_rpad (CHAR_CAST (char *, case_data_rw_idx (c, idx++)->s), 8,
-                         msave->varname_, ' ');
+                         varname_, ' ');
 
       /* Continuous variables. */
       for (size_t x = 0; x < m->size2; x++)
@@ -6930,10 +6968,7 @@ matrix_cmd_destroy (struct matrix_cmd *cmd)
       break;
 
     case MCMD_MSAVE:
-      free (cmd->msave.varname_);
       matrix_expr_destroy (cmd->msave.expr);
-      matrix_expr_destroy (cmd->msave.factors);
-      matrix_expr_destroy (cmd->msave.splits);
       break;
 
     case MCMD_MGET:
@@ -7063,12 +7098,7 @@ cmd_matrix (struct lexer *lexer, struct dataset *ds)
       free (var);
     }
   hmap_destroy (&state.vars);
-  if (state.common)
-    {
-      dict_unref (state.common->dict);
-      casewriter_destroy (state.common->writer);
-      free (state.common);
-    }
+  msave_common_destroy (state.common);
   fh_unref (state.prev_read_file);
   for (size_t i = 0; i < state.n_read_files; i++)
     read_file_destroy (state.read_files[i]);
index 46925189652117f714274d1e09b429bf96acd9e7..06b93749a59724b08949f9a0cfe97c79416533c2 100644 (file)
@@ -3720,3 +3720,273 @@ ncs2
 0     0
 ])
 AT_CLEANUP
+
+AT_SETUP([MATRIX - MSAVE])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+MSAVE {1, 2; 3, 4}/TYPE=CORR/VARIABLES=X,Y/OUTFILE='matrix.sav'.
+MSAVE {5, 6; 7, 8; 9, 10}/TYPE=COV/VARIABLES=X,Y.
+MSAVE {11, 12}/TYPE=MEAN.
+MSAVE {13, 14}/TYPE=STDDEV.
+MSAVE {15, 16}/TYPE=N.
+MSAVE {17, 18}/TYPE=COUNT.
+END MATRIX.
+GET 'matrix.sav'.
+LIST.
+])
+AT_CHECK([pspp matrix.sps -O format=csv], [0], [dnl
+Table: Data List
+ROWTYPE_,VARNAME_,X,Y
+CORR,X,1.00,2.00
+CORR,Y,3.00,4.00
+COV,X,5.00,6.00
+COV,Y,7.00,8.00
+COV,,9.00,10.00
+MEAN,,11.00,12.00
+STDDEV,,13.00,14.00
+N,,15.00,16.00
+COUNT,,17.00,18.00
+])
+AT_CLEANUP
+
+AT_SETUP([MATRIX - MSAVE with factor variables])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+MSAVE {1, 2; 3, 4}/TYPE=CORR/FACTOR={1,1}/FNAMES=X,Y/OUTFILE='matrix.sav'.
+MSAVE {5, 6; 7, 8; 9, 10}/TYPE=COV.
+MSAVE {11, 12}/TYPE=MEAN.
+MSAVE {13, 14}/FACTOR={2,1}/TYPE=STDDEV.
+MSAVE {15, 16}/TYPE=N.
+MSAVE {17, 18}/FACTOR={1,2}/TYPE=COUNT.
+END MATRIX.
+GET 'matrix.sav'.
+LIST.
+
+MATRIX.
+MSAVE {1, 2; 3, 4}/TYPE=CORR/FACTOR={5,6,7,8}/OUTFILE='matrix2.sav'.
+END MATRIX.
+GET 'matrix2.sav'.
+LIST.
+])
+AT_CHECK([pspp matrix.sps -O format=csv], [0], [dnl
+Table: Data List
+ROWTYPE_,X,Y,VARNAME_,COL1,COL2
+CORR,1.00,1.00,COL1,1.00,2.00
+CORR,1.00,1.00,COL2,3.00,4.00
+COV,1.00,1.00,COL1,5.00,6.00
+COV,1.00,1.00,COL2,7.00,8.00
+COV,1.00,1.00,,9.00,10.00
+MEAN,1.00,1.00,,11.00,12.00
+STDDEV,2.00,1.00,,13.00,14.00
+N,2.00,1.00,,15.00,16.00
+COUNT,1.00,2.00,,17.00,18.00
+
+Table: Data List
+ROWTYPE_,FAC1,FAC2,FAC3,FAC4,VARNAME_,COL1,COL2
+CORR,5.00,6.00,7.00,8.00,COL1,1.00,2.00
+CORR,5.00,6.00,7.00,8.00,COL2,3.00,4.00
+])
+AT_CLEANUP
+
+AT_SETUP([MATRIX - MSAVE with split variables])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+MSAVE {1, 2; 3, 4}/TYPE=CORR/SPLIT={1,1}/SNAMES=X,Y/OUTFILE='matrix.sav'.
+MSAVE {5, 6; 7, 8; 9, 10}/TYPE=COV.
+MSAVE {11, 12}/TYPE=MEAN.
+MSAVE {13, 14}/SPLIT={2,1}/TYPE=STDDEV.
+MSAVE {15, 16}/TYPE=N.
+MSAVE {17, 18}/SPLIT={1,2}/TYPE=COUNT.
+END MATRIX.
+GET 'matrix.sav'.
+LIST.
+
+MATRIX.
+MSAVE {1, 2; 3, 4}/TYPE=CORR/SPLIT={5,6,7,8}/OUTFILE='matrix2.sav'.
+END MATRIX.
+GET 'matrix2.sav'.
+LIST.
+])
+AT_CHECK([pspp matrix.sps -O format=csv], [0], [dnl
+Table: Data List
+X,Y,ROWTYPE_,VARNAME_,COL1,COL2
+1.00,1.00,CORR,COL1,1.00,2.00
+1.00,1.00,CORR,COL2,3.00,4.00
+1.00,1.00,COV,COL1,5.00,6.00
+1.00,1.00,COV,COL2,7.00,8.00
+1.00,1.00,COV,,9.00,10.00
+1.00,1.00,MEAN,,11.00,12.00
+2.00,1.00,STDDEV,,13.00,14.00
+2.00,1.00,N,,15.00,16.00
+1.00,2.00,COUNT,,17.00,18.00
+
+Table: Data List
+SPL1,SPL2,SPL3,SPL4,ROWTYPE_,VARNAME_,COL1,COL2
+5.00,6.00,7.00,8.00,CORR,COL1,1.00,2.00
+5.00,6.00,7.00,8.00,CORR,COL2,3.00,4.00
+])
+AT_CLEANUP
+
+AT_SETUP([MATRIX - MSAVE with factor and split variables])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+MSAVE {1, 2; 3, 4}/TYPE=CORR/SPLIT=1/FACTOR=1/OUTFILE='matrix.sav'.
+MSAVE {5, 6; 7, 8; 9, 10}/TYPE=COV.
+MSAVE {11, 12}/FACTOR=2/TYPE=MEAN.
+MSAVE {13, 14}/FACTOR=1/SPLIT=2/TYPE=STDDEV.
+MSAVE {15, 16}/TYPE=N.
+MSAVE {17, 18}/FACTOR=2/TYPE=COUNT.
+END MATRIX.
+GET 'matrix.sav'.
+LIST.
+])
+AT_CHECK([pspp matrix.sps -O format=csv], [0], [dnl
+Table: Data List
+SPL1,ROWTYPE_,FAC1,VARNAME_,COL1,COL2
+1.00,CORR,1.00,COL1,1.00,2.00
+1.00,CORR,1.00,COL2,3.00,4.00
+1.00,COV,1.00,COL1,5.00,6.00
+1.00,COV,1.00,COL2,7.00,8.00
+1.00,COV,1.00,,9.00,10.00
+1.00,MEAN,2.00,,11.00,12.00
+2.00,STDDEV,1.00,,13.00,14.00
+2.00,N,1.00,,15.00,16.00
+2.00,COUNT,2.00,,17.00,18.00
+])
+AT_CLEANUP
+
+AT_SETUP([MATRIX - MSAVE - negative])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+MSAVE !.
+MSAVE 1/TYPE=!.
+MSAVE 1/OUTFILE=!.
+MSAVE 1/VARIABLES=!.
+MSAVE 1/FNAMES=!.
+MSAVE 1/SNAMES=!.
+MSAVE 1/SPLIT=!.
+MSAVE 1/FACTOR=!.
+MSAVE 1/!.
+MSAVE 1.
+MSAVE 1/TYPE=COV/FNAMES=x.
+MSAVE 1/TYPE=COV/SNAMES=x.
+MSAVE 1/TYPE=COV.
+
+MSAVE 1/TYPE=COV/OUTFILE='matrix.sav'
+    /FACTOR=1 /FNAMES=y
+    /SPLIT=2 /SNAMES=z
+    /VARIABLES=w.
+MSAVE 1/TYPE=COV/OUTFILE='matrix2.sav'.
+MSAVE 1/TYPE=COV/VARIABLES=x.
+MSAVE 1/TYPE=COV/FNAMES=x.
+MSAVE 1/TYPE=COV/SNAMES=x.
+END MATRIX.
+
+MATRIX.
+MSAVE 1/TYPE=COV/VARIABLES=x/OUTFILE='matrix3.sav'/FACTOR=1/SPLIT=2.
+MSAVE {1,2}/TYPE=COV/VARIABLES=x/OUTFILE='matrix3.sav'/FACTOR=1/SPLIT=2.
+MSAVE {1,2;3}/TYPE=COV.
+MSAVE 0/TYPE=COV/FACTOR={1,2}.
+MSAVE 0/TYPE=COV/FACTOR=1/SPLIT={1;2}.
+END MATRIX.
+
+MATRIX.
+MSAVE 1/TYPE=COV/OUTFILE='matrix4.sav'/SNAMES=x,x/SPLIT=1.
+END MATRIX.
+
+MATRIX.
+MSAVE 1/TYPE=COV/OUTFILE='matrix5.sav'/SNAMES=x/FNAMES=x/SPLIT=1/FACTOR=1.
+END MATRIX.
+
+MATRIX.
+MSAVE 1/TYPE=COV/OUTFILE='matrix6.sav'/VARIABLES=x/FNAMES=x/FACTOR=1.
+END MATRIX.
+
+MATRIX.
+MSAVE 1/TYPE=COV/OUTFILE='matrix6.sav'/VARIABLES=x/SNAMES=x/SPLIT=1.
+END MATRIX.
+
+MATRIX.
+MSAVE 1/TYPE=COV/OUTFILE='matrix7.sav'/SNAMES=VARNAME_.
+MSAVE 1/TYPE=COV/OUTFILE='matrix7.sav'/SNAMES=ROWTYPE_.
+MSAVE 1/TYPE=COV/OUTFILE='matrix7.sav'/FNAMES=VARNAME_.
+MSAVE 1/TYPE=COV/OUTFILE='matrix7.sav'/FNAMES=ROWTYPE_.
+MSAVE 1/TYPE=COV/OUTFILE='matrix7.sav'/VARIABLES=VARNAME_.
+MSAVE 1/TYPE=COV/OUTFILE='matrix7.sav'/VARIABLES=ROWTYPE_.
+END MATRIX.
+])
+AT_CHECK([pspp matrix.sps], [1], [dnl
+matrix.sps:2.7: error: MSAVE: Syntax error at `!'.
+
+matrix.sps:3.14: error: MSAVE: Syntax error at `!': expecting COV, CORR, MEAN,
+STDDEV, N, or COUNT.
+
+matrix.sps:4.17: error: MSAVE: Syntax error at `!': expecting a file name or
+handle name.
+
+matrix.sps:5.19: error: MSAVE: Syntax error at `!': expecting variable name.
+
+matrix.sps:6.16: error: MSAVE: Syntax error at `!': expecting variable name.
+
+matrix.sps:7.16: error: MSAVE: Syntax error at `!': expecting variable name.
+
+matrix.sps:8.15: error: MSAVE: Syntax error at `!'.
+
+matrix.sps:9.16: error: MSAVE: Syntax error at `!'.
+
+matrix.sps:10.9: error: MSAVE: Syntax error at `!': expecting TYPE, OUTFILE,
+VARIABLES, FNAMES, SNAMES, SPLIT, or FACTOR.
+
+matrix.sps:11: error: MSAVE: Required subcommand TYPE was not specified.
+
+matrix.sps:12: error: MSAVE: FNAMES requires FACTOR.
+
+matrix.sps:13: error: MSAVE: SNAMES requires SPLIT.
+
+matrix.sps:14: error: MSAVE: Required subcommand OUTFILE was not specified.
+
+matrix.sps:20: error: MSAVE: OUTFILE must name the same file on each MSAVE
+within a single MATRIX command.
+
+matrix.sps:21: error: MSAVE: VARIABLES must specify the same variables each
+time within a given MATRIX.
+
+matrix.sps:22: error: MSAVE: FNAMES must specify the same variables each time
+within a given MATRIX.
+
+matrix.sps:23: error: MSAVE: SNAMES must specify the same variables each time
+within a given MATRIX.
+
+matrix.sps:28: error: MATRIX: Matrix on MSAVE has 2 columns but there are 1
+variables.
+
+matrix.sps:29: error: MATRIX: All rows in a matrix must have the same number of
+columns, but this tries to stack matrices with 2 and 1 columns.
+
+matrix.sps:30: error: MATRIX: There are 1 factor variables, but 2 factor values
+were supplied.
+
+matrix.sps:31: error: MATRIX: There are 1 split variables, but 2 split values
+were supplied.
+
+matrix.sps:35: error: MSAVE: Variable x appears twice in variable list.
+
+matrix.sps:39: error: MATRIX: Duplicate or invalid FACTOR variable name x.
+
+matrix.sps:43: error: MATRIX: Duplicate or invalid variable name x.
+
+matrix.sps:47: error: MATRIX: Duplicate or invalid variable name x.
+
+matrix.sps:51: error: MSAVE: Variable name VARNAME_ is reserved.
+
+matrix.sps:52: error: MSAVE: Variable name ROWTYPE_ is reserved.
+
+matrix.sps:53: error: MSAVE: Variable name VARNAME_ is reserved.
+
+matrix.sps:54: error: MSAVE: Variable name ROWTYPE_ is reserved.
+
+matrix.sps:55: error: MSAVE: Variable name VARNAME_ is reserved.
+
+matrix.sps:56: error: MSAVE: Variable name ROWTYPE_ is reserved.
+])
+AT_CLEANUP
\ No newline at end of file