CORRELATIONS: Improve error messages and coding style.
[pspp] / src / language / stats / matrix.c
index c1e4adff8e793a05664add867495b80574ec9184..69c6323ef48152076b9c953bf2b4aaea13f2dc27 100644 (file)
@@ -89,9 +89,13 @@ struct msave_common
     /* Common configuration for all MSAVEs. */
     struct msg_location *location; /* Range of lines for first MSAVE. */
     struct file_handle *outfile;   /* Output file for all the MSAVEs. */
+    struct msg_location *outfile_location;
     struct string_array variables; /* VARIABLES subcommand. */
+    struct msg_location *variables_location;
     struct string_array fnames;    /* FNAMES subcommand. */
+    struct msg_location *fnames_location;
     struct string_array snames;    /* SNAMES subcommand. */
+    struct msg_location *snames_location;
 
     /* Collects and owns factors and splits.  The individual msave_command
        structs point to these but do not own them.  (This is because factors
@@ -3383,7 +3387,7 @@ matrix_expr_evaluate_exp_mat (const struct matrix_expr *e,
   if (bf != floor (bf) || bf <= LONG_MIN || bf > LONG_MAX)
     {
       msg_at (SE, matrix_expr_location (e->subs[1]),
-              _("Exponent %.1f in matrix multiplication is non-integer "
+              _("Exponent %.1f in matrix exponentiation is non-integer "
                 "or outside the valid range."), bf);
       return NULL;
     }
@@ -4742,7 +4746,8 @@ matrix_lvalue_parse (struct matrix_state *s)
     {
       if (!lvalue->var)
         {
-          msg (SE, _("Undefined variable %s."), lex_tokcstr (s->lexer));
+          lex_error (s->lexer, _("Undefined variable %s."),
+                     lex_tokcstr (s->lexer));
           goto error;
         }
 
@@ -5173,6 +5178,7 @@ struct matrix_command
 
         struct matrix_get
           {
+            struct lexer *lexer;
             struct matrix_lvalue *dst;
             struct dataset *dataset;
             struct file_handle *file;
@@ -5367,25 +5373,12 @@ matrix_print_parse (struct matrix_state *s)
 
   if (lex_token (s->lexer) != T_SLASH && lex_token (s->lexer) != T_ENDCMD)
     {
-      size_t depth = 0;
-      for (size_t i = 0; ; i++)
-        {
-          enum token_type t = lex_next_token (s->lexer, i);
-          if (t == T_LPAREN || t == T_LBRACK || t == T_LCURLY)
-            depth++;
-          else if ((t == T_RPAREN || t == T_RBRACK || t == T_RCURLY) && depth)
-            depth--;
-          else if ((t == T_SLASH && !depth) || t == T_ENDCMD || t == T_STOP)
-            {
-              if (i > 0)
-                cmd->print.title = lex_next_representation (s->lexer, 0, i - 1);
-              break;
-            }
-        }
-
+      int start_ofs = lex_ofs (s->lexer);
       cmd->print.expression = matrix_parse_exp (s);
       if (!cmd->print.expression)
         goto error;
+      cmd->print.title = lex_ofs_representation (s->lexer, start_ofs,
+                                                 lex_ofs (s->lexer) - 1);
     }
 
   while (lex_match (s->lexer, T_SLASH))
@@ -5937,7 +5930,7 @@ matrix_break_parse (struct matrix_state *s)
 {
   if (!s->in_loop)
     {
-      msg (SE, _("BREAK not inside LOOP."));
+      lex_next_error (s->lexer, -1, -1, _("BREAK not inside LOOP."));
       return NULL;
     }
 
@@ -6035,7 +6028,7 @@ matrix_release_parse (struct matrix_state *s)
           cmd->release.vars[cmd->release.n_vars++] = var;
         }
       else
-        lex_error (s->lexer, _("Variable name expected."));
+        lex_error (s->lexer, _("Syntax error expecting variable name."));
       lex_get (s->lexer);
 
       if (!lex_match (s->lexer, T_COMMA))
@@ -6142,10 +6135,15 @@ save_file_open (struct save_file *sf, gsl_matrix *m,
           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))
+              char *error = dict_id_is_valid__ (dict, name);
+              if (!error)
                 string_array_append_nocopy (&names, name);
               else
-                ok = false;
+                {
+                  msg_at (SE, save_location, "%s", error);
+                  free (error);
+                  ok = false;
+                }
             }
         }
       gsl_matrix_free (nm);
@@ -6267,6 +6265,8 @@ matrix_save_parse (struct matrix_state *s)
   if (!save->expression)
     goto error;
 
+  int names_start = 0;
+  int names_end = 0;
   while (lex_match (s->lexer, T_SLASH))
     {
       if (lex_match_id (s->lexer, "OUTFILE"))
@@ -6304,7 +6304,9 @@ matrix_save_parse (struct matrix_state *s)
         {
           lex_match (s->lexer, T_EQUALS);
           matrix_expr_destroy (names);
+          names_start = lex_ofs (s->lexer);
           names = matrix_parse_exp (s);
+          names_end = lex_ofs (s->lexer) - 1;
           if (!names)
             goto error;
         }
@@ -6333,7 +6335,7 @@ matrix_save_parse (struct matrix_state *s)
         fh = fh_ref (s->prev_save_file);
       else
         {
-          lex_sbc_missing ("OUTFILE");
+          lex_sbc_missing (s->lexer, "OUTFILE");
           goto error;
         }
     }
@@ -6342,7 +6344,8 @@ matrix_save_parse (struct matrix_state *s)
 
   if (variables.n && names)
     {
-      msg (SW, _("VARIABLES and NAMES both specified; ignoring NAMES."));
+      lex_ofs_msg (s->lexer, SW, names_start, names_end,
+                   _("Ignoring NAMES because VARIABLES was also specified."));
       matrix_expr_destroy (names);
       names = NULL;
     }
@@ -6454,6 +6457,10 @@ matrix_read_parse (struct matrix_state *s)
   if (!read->dst)
     goto error;
 
+  int by_ofs = 0;
+  int format_ofs = 0;
+  int record_width_start = 0, record_width_end = 0;
+
   int by = 0;
   int repetitions = 0;
   int record_width = 0;
@@ -6484,6 +6491,7 @@ matrix_read_parse (struct matrix_state *s)
         {
           lex_match (s->lexer, T_EQUALS);
 
+          record_width_start = lex_ofs (s->lexer);
           if (!lex_force_int_range (s->lexer, "FIELD", 1, INT_MAX))
             goto error;
           read->c1 = lex_integer (s->lexer);
@@ -6492,6 +6500,7 @@ matrix_read_parse (struct matrix_state *s)
               || !lex_force_int_range (s->lexer, "TO", read->c1, INT_MAX))
             goto error;
           read->c2 = lex_integer (s->lexer) + 1;
+          record_width_end = lex_ofs (s->lexer);
           lex_get (s->lexer);
 
           record_width = read->c2 - read->c1;
@@ -6501,12 +6510,20 @@ matrix_read_parse (struct matrix_state *s)
                                         read->c2 - read->c1))
                 goto error;
               by = lex_integer (s->lexer);
+              by_ofs = lex_ofs (s->lexer);
+              int field_end = lex_ofs (s->lexer);
               lex_get (s->lexer);
 
               if (record_width % by)
                 {
-                  msg (SE, _("BY %d does not evenly divide record width %d."),
-                       by, record_width);
+                  lex_ofs_error (
+                    s->lexer, record_width_start, field_end,
+                    _("Field width %d does not evenly divide record width %d."),
+                    by, record_width);
+                  lex_ofs_msg (s->lexer, SN, record_width_start, record_width_end,
+                               _("This syntax designates the record width."));
+                  lex_ofs_msg (s->lexer, SN, by_ofs, by_ofs,
+                               _("This syntax specifies the field width."));
                   goto error;
                 }
             }
@@ -6540,7 +6557,7 @@ matrix_read_parse (struct matrix_state *s)
         {
           if (seen_format)
             {
-              lex_sbc_only_once ("FORMAT");
+              lex_sbc_only_once (s->lexer, "FORMAT");
               goto error;
             }
           seen_format = true;
@@ -6550,6 +6567,7 @@ matrix_read_parse (struct matrix_state *s)
           if (lex_token (s->lexer) != T_STRING && !lex_force_id (s->lexer))
             goto error;
 
+          format_ofs = lex_ofs (s->lexer);
           const char *p = lex_tokcstr (s->lexer);
           if (c_isdigit (p[0]))
             {
@@ -6583,7 +6601,7 @@ matrix_read_parse (struct matrix_state *s)
 
   if (!read->c1)
     {
-      lex_sbc_missing ("FIELD");
+      lex_sbc_missing (s->lexer, "FIELD");
       goto error;
     }
 
@@ -6591,6 +6609,8 @@ matrix_read_parse (struct matrix_state *s)
     {
       msg (SE, _("SIZE is required for reading data into a full matrix "
                  "(as opposed to a submatrix)."));
+      msg_at (SN, read->dst->var_location,
+              _("This expression designates a full matrix."));
       goto error;
     }
 
@@ -6600,7 +6620,7 @@ matrix_read_parse (struct matrix_state *s)
         fh = fh_ref (s->prev_read_file);
       else
         {
-          lex_sbc_missing ("FILE");
+          lex_sbc_missing (s->lexer, "FILE");
           goto error;
         }
     }
@@ -6631,6 +6651,10 @@ matrix_read_parse (struct matrix_state *s)
     {
       msg (SE, _("%d repetitions cannot fit in record width %d."),
            repetitions, record_width);
+      lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                   _("This syntax designates the number of repetitions."));
+      lex_ofs_msg (s->lexer, SN, record_width_start, record_width_end,
+                   _("This syntax designates the record width."));
       goto error;
     }
   int w = (repetitions ? record_width / repetitions
@@ -6638,14 +6662,26 @@ matrix_read_parse (struct matrix_state *s)
            : by);
   if (by && w != by)
     {
+      msg (SE, _("This command specifies two different field widths."));
       if (repetitions)
-        msg (SE, _("FORMAT specifies %d repetitions with record width %d, "
-                   "which implies field width %d, "
-                   "but BY specifies field width %d."),
-             repetitions, record_width, w, by);
+        {
+          lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                       ngettext ("This syntax specifies %d repetition.",
+                                 "This syntax specifies %d repetitions.",
+                                 repetitions),
+                       repetitions);
+          lex_ofs_msg (s->lexer, SN, record_width_start, record_width_end,
+                       _("This syntax designates record width %d, "
+                         "which divided by %d repetitions implies "
+                         "field width %d."),
+                       record_width, repetitions, w);
+        }
       else
-        msg (SE, _("FORMAT specifies field width %d but BY specifies %d."),
-             w, by);
+        lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                     _("This syntax specifies field width %d."), w);
+
+      lex_ofs_msg (s->lexer, SN, by_ofs, by_ofs,
+                   _("This syntax specifies field width %d."), by);
       goto error;
     }
   read->w = w;
@@ -7001,6 +7037,10 @@ matrix_write_parse (struct matrix_state *s)
   if (!write->expression)
     goto error;
 
+  int by_ofs = 0;
+  int format_ofs = 0;
+  int record_width_start = 0, record_width_end = 0;
+
   int by = 0;
   int repetitions = 0;
   int record_width = 0;
@@ -7032,6 +7072,8 @@ matrix_write_parse (struct matrix_state *s)
         {
           lex_match (s->lexer, T_EQUALS);
 
+          record_width_start = lex_ofs (s->lexer);
+
           if (!lex_force_int_range (s->lexer, "FIELD", 1, INT_MAX))
             goto error;
           write->c1 = lex_integer (s->lexer);
@@ -7040,6 +7082,7 @@ matrix_write_parse (struct matrix_state *s)
               || !lex_force_int_range (s->lexer, "TO", write->c1, INT_MAX))
             goto error;
           write->c2 = lex_integer (s->lexer) + 1;
+          record_width_end = lex_ofs (s->lexer);
           lex_get (s->lexer);
 
           record_width = write->c2 - write->c1;
@@ -7048,13 +7091,21 @@ matrix_write_parse (struct matrix_state *s)
               if (!lex_force_int_range (s->lexer, "BY", 1,
                                         write->c2 - write->c1))
                 goto error;
+              by_ofs = lex_ofs (s->lexer);
+              int field_end = lex_ofs (s->lexer);
               by = lex_integer (s->lexer);
               lex_get (s->lexer);
 
               if (record_width % by)
                 {
-                  msg (SE, _("BY %d does not evenly divide record width %d."),
-                       by, record_width);
+                  lex_ofs_error (
+                    s->lexer, record_width_start, field_end,
+                    _("Field width %d does not evenly divide record width %d."),
+                    by, record_width);
+                  lex_ofs_msg (s->lexer, SN, record_width_start, record_width_end,
+                               _("This syntax designates the record width."));
+                  lex_ofs_msg (s->lexer, SN, by_ofs, by_ofs,
+                               _("This syntax specifies the field width."));
                   goto error;
                 }
             }
@@ -7080,7 +7131,7 @@ matrix_write_parse (struct matrix_state *s)
         {
           if (has_format || write->format)
             {
-              lex_sbc_only_once ("FORMAT");
+              lex_sbc_only_once (s->lexer, "FORMAT");
               goto error;
             }
 
@@ -7089,6 +7140,7 @@ matrix_write_parse (struct matrix_state *s)
           if (lex_token (s->lexer) != T_STRING && !lex_force_id (s->lexer))
             goto error;
 
+          format_ofs = lex_ofs (s->lexer);
           const char *p = lex_tokcstr (s->lexer);
           if (c_isdigit (p[0]))
             {
@@ -7125,7 +7177,7 @@ matrix_write_parse (struct matrix_state *s)
 
   if (!write->c1)
     {
-      lex_sbc_missing ("FIELD");
+      lex_sbc_missing (s->lexer, "FIELD");
       goto error;
     }
 
@@ -7135,7 +7187,7 @@ matrix_write_parse (struct matrix_state *s)
         fh = fh_ref (s->prev_write_file);
       else
         {
-          lex_sbc_missing ("OUTFILE");
+          lex_sbc_missing (s->lexer, "OUTFILE");
           goto error;
         }
     }
@@ -7164,8 +7216,10 @@ matrix_write_parse (struct matrix_state *s)
    */
   if (repetitions > record_width)
     {
-      msg (SE, _("%d repetitions cannot fit in record width %d."),
-           repetitions, record_width);
+      lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                   _("This syntax designates the number of repetitions."));
+      lex_ofs_msg (s->lexer, SN, record_width_start, record_width_end,
+                   _("This syntax designates the record width."));
       goto error;
     }
   int w = (repetitions ? record_width / repetitions
@@ -7173,14 +7227,26 @@ matrix_write_parse (struct matrix_state *s)
            : by);
   if (by && w != by)
     {
+      msg (SE, _("This command specifies two different field widths."));
       if (repetitions)
-        msg (SE, _("FORMAT specifies %d repetitions with record width %d, "
-                   "which implies field width %d, "
-                   "but BY specifies field width %d."),
-             repetitions, record_width, w, by);
+        {
+          lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                       ngettext ("This syntax specifies %d repetition.",
+                                 "This syntax specifies %d repetitions.",
+                                 repetitions),
+                       repetitions);
+          lex_ofs_msg (s->lexer, SN, record_width_start, record_width_end,
+                       _("This syntax designates record width %d, "
+                         "which divided by %d repetitions implies "
+                         "field width %d."),
+                       record_width, repetitions, w);
+        }
       else
-        msg (SE, _("FORMAT specifies field width %d but BY specifies %d."),
-             w, by);
+        lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                     _("This syntax specifies field width %d."), w);
+
+      lex_ofs_msg (s->lexer, SN, by_ofs, by_ofs,
+                   _("This syntax specifies field width %d."), by);
       goto error;
     }
   if (w && !write->format)
@@ -7188,16 +7254,46 @@ matrix_write_parse (struct matrix_state *s)
       write->format = xmalloc (sizeof *write->format);
       *write->format = (struct fmt_spec) { .type = format, .w = w };
 
-      if (!fmt_check_output (write->format))
-        goto error;
-    };
+      char *error = fmt_check_output__ (write->format);
+      if (error)
+        {
+          msg (SE, "%s", error);
+          free (error);
+
+          if (has_format)
+            lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                         _("This syntax specifies format %s."),
+                         fmt_name (format));
+
+          if (repetitions)
+            {
+              lex_ofs_msg (s->lexer, SN, format_ofs, format_ofs,
+                           ngettext ("This syntax specifies %d repetition.",
+                                     "This syntax specifies %d repetitions.",
+                                     repetitions),
+                           repetitions);
+              lex_ofs_msg (s->lexer, SN, record_width_start, record_width_end,
+                           _("This syntax designates record width %d, "
+                             "which divided by %d repetitions implies "
+                             "field width %d."),
+                           record_width, repetitions, w);
+            }
+
+          if (by)
+            lex_ofs_msg (s->lexer, SN, by_ofs, by_ofs,
+                         _("This syntax specifies field width %d."), by);
+
+          goto error;
+        }
+    }
 
   if (write->format && fmt_var_width (write->format) > sizeof (double))
     {
-      char s[FMT_STRING_LEN_MAX + 1];
-      fmt_to_string (write->format, s);
-      msg (SE, _("Format %s is too wide for %zu-byte matrix elements."),
-           s, sizeof (double));
+      char fs[FMT_STRING_LEN_MAX + 1];
+      fmt_to_string (write->format, fs);
+      lex_ofs_error (s->lexer, format_ofs, format_ofs,
+                     _("Format %s is too wide for %zu-byte matrix elements."),
+                     fs, sizeof (double));
       goto error;
     }
 
@@ -7304,6 +7400,7 @@ matrix_get_parse (struct matrix_state *s)
   *cmd = (struct matrix_command) {
     .type = MCMD_GET,
     .get = {
+      .lexer = s->lexer,
       .dataset = s->dataset,
       .user = { .treatment = MGET_ERROR },
       .system = { .treatment = MGET_ERROR },
@@ -7348,7 +7445,7 @@ matrix_get_parse (struct matrix_state *s)
 
           if (get->n_vars)
             {
-              lex_sbc_only_once ("VARIABLES");
+              lex_sbc_only_once (s->lexer, "VARIABLES");
               goto error;
             }
 
@@ -7431,7 +7528,7 @@ matrix_get_execute__ (struct matrix_command *cmd, struct casereader *reader,
 
   if (get->n_vars)
     {
-      if (!var_syntax_evaluate (get->vars, get->n_vars, dict,
+      if (!var_syntax_evaluate (get->lexer, get->vars, get->n_vars, dict,
                                 &vars, &n_vars, PV_NUMERIC))
         return;
     }
@@ -7553,7 +7650,7 @@ matrix_open_casereader (const struct matrix_command *cmd,
     {
       if (dict_get_n_vars (dataset_dict (dataset)) == 0)
         {
-          msg_at (ME, cmd->location,
+          msg_at (SE, cmd->location,
                   _("The %s command cannot read an empty active file."),
                   command_name);
           return false;
@@ -7592,21 +7689,39 @@ matrix_get_execute (struct matrix_command *cmd)
 
 static bool
 variables_changed (const char *keyword,
-                   const struct string_array *new,
-                   const struct string_array *old)
-{
-  if (new->n)
-    {
-      if (!old->n)
-        {
-          msg (SE, _("%s may only be specified on MSAVE if it was specified "
-                     "on the first MSAVE within MATRIX."), keyword);
+                   const struct string_array *new_vars,
+                   const struct msg_location *new_vars_location,
+                   const struct msg_location *new_location,
+                   const struct string_array *old_vars,
+                   const struct msg_location *old_vars_location,
+                   const struct msg_location *old_location)
+{
+  if (new_vars->n)
+    {
+      if (!old_vars->n)
+        {
+          msg_at (SE, new_location,
+                  _("%s may only be specified on MSAVE if it was specified "
+                    "on the first MSAVE within MATRIX."), keyword);
+          msg_at (SN, old_location,
+                  _("The first MSAVE in MATRIX did not specify %s."),
+                  keyword);
+          msg_at (SN, new_vars_location,
+                  _("This is the specification of %s on a later MSAVE."),
+                  keyword);
           return true;
         }
-      else if (!string_array_equal_case (old, new))
-        {
-          msg (SE, _("%s must specify the same variables each time within "
-                     "a given MATRIX."), keyword);
+      if (!string_array_equal_case (old_vars, new_vars))
+        {
+          msg_at (SE, new_location,
+                  _("%s must specify the same variables on each MSAVE "
+                    "within a given MATRIX."), keyword);
+          msg_at (SE, old_vars_location,
+                  _("This is the specification of %s on the first MSAVE."),
+                  keyword);
+          msg_at (SE, new_vars_location,
+                  _("This is a different specification of %s on a later MSAVE."),
+                  keyword);
           return true;
         }
     }
@@ -7618,14 +7733,25 @@ msave_common_changed (const struct msave_common *old,
                       const struct msave_common *new)
 {
   if (new->outfile && !fh_equal (old->outfile, new->outfile))
-    msg (SE, _("OUTFILE must name the same file on each MSAVE "
-               "within a single MATRIX command."));
-  else if (variables_changed ("VARIABLES", &new->variables, &old->variables)
-           || variables_changed ("FNAMES", &new->fnames, &old->fnames)
-           || variables_changed ("SNAMES", &new->snames, &old->snames))
-    msg_at (SN, old->location,
-            _("This is the location of the first MSAVE command."));
-  else
+    {
+      msg (SE, _("OUTFILE must name the same file on each MSAVE "
+                 "within a single MATRIX command."));
+      msg_at (SN, old->outfile_location,
+              _("This is the OUTFILE on the first MSAVE command."));
+      msg_at (SN, new->outfile_location,
+              _("This is the OUTFILE on a later MSAVE command."));
+      return false;
+    }
+
+  if (!variables_changed ("VARIABLES",
+                          &new->variables, new->variables_location, new->location,
+                          &old->variables, old->variables_location, old->location)
+      && !variables_changed ("FNAMES",
+                             &new->fnames, new->fnames_location, new->location,
+                             &old->fnames, old->fnames_location, old->location)
+      && !variables_changed ("SNAMES",
+                             &new->snames, new->snames_location, new->location,
+                             &old->snames, old->snames_location, old->location))
     return false;
 
   return true;
@@ -7638,9 +7764,13 @@ msave_common_destroy (struct msave_common *common)
     {
       msg_location_destroy (common->location);
       fh_unref (common->outfile);
+      msg_location_destroy (common->outfile_location);
       string_array_destroy (&common->variables);
+      msg_location_destroy (common->variables_location);
       string_array_destroy (&common->fnames);
+      msg_location_destroy (common->fnames_location);
       string_array_destroy (&common->snames);
+      msg_location_destroy (common->snames_location);
 
       for (size_t i = 0; i < common->n_factors; i++)
         matrix_expr_destroy (common->factors[i]);
@@ -7674,17 +7804,22 @@ match_rowtype (struct lexer *lexer)
 }
 
 static bool
-parse_var_names (struct lexer *lexer, struct string_array *sa)
+parse_var_names (struct lexer *lexer, struct string_array *sa,
+                 struct msg_location **locationp)
 {
   lex_match (lexer, T_EQUALS);
 
   string_array_clear (sa);
+  msg_location_destroy (*locationp);
+  *locationp = NULL;
 
   struct dictionary *dict = dict_create (get_default_encoding ());
   char **names;
   size_t n_names;
+  int start_ofs = lex_ofs (lexer);
   bool ok = parse_DATA_LIST_vars (lexer, dict, &names, &n_names,
                                   PV_NO_DUPLICATE | PV_NO_SCRATCH);
+  int end_ofs = lex_ofs (lexer) - 1;
   dict_unref (dict);
 
   if (ok)
@@ -7693,16 +7828,17 @@ parse_var_names (struct lexer *lexer, struct string_array *sa)
         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]);
+            lex_ofs_error (lexer, start_ofs, end_ofs,
+                           _("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;
+      *locationp = lex_ofs_location (lexer, start_ofs, end_ofs);
     }
   return ok;
 }
@@ -7741,23 +7877,30 @@ matrix_msave_parse (struct matrix_state *s)
           lex_match (s->lexer, T_EQUALS);
 
           fh_unref (common->outfile);
+          int start_ofs = lex_ofs (s->lexer);
           common->outfile = fh_parse (s->lexer, FH_REF_FILE, NULL);
           if (!common->outfile)
             goto error;
+          msg_location_destroy (common->outfile_location);
+          common->outfile_location = lex_ofs_location (s->lexer, start_ofs,
+                                                       lex_ofs (s->lexer) - 1);
         }
       else if (lex_match_id (s->lexer, "VARIABLES"))
         {
-          if (!parse_var_names (s->lexer, &common->variables))
+          if (!parse_var_names (s->lexer, &common->variables,
+                                &common->variables_location))
             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,
+                                &common->fnames_location))
             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,
+                                &common->snames_location))
             goto error;
         }
       else if (lex_match_id (s->lexer, "SPLIT"))
@@ -7787,7 +7930,7 @@ matrix_msave_parse (struct matrix_state *s)
     }
   if (!msave->rowtype)
     {
-      lex_sbc_missing ("TYPE");
+      lex_sbc_missing (s->lexer, "TYPE");
       goto error;
     }
 
@@ -7795,17 +7938,17 @@ matrix_msave_parse (struct matrix_state *s)
     {
       if (common->fnames.n && !factors)
         {
-          msg (SE, _("FNAMES requires FACTOR."));
+          msg_at (SE, common->fnames_location, _("FNAMES requires FACTOR."));
           goto error;
         }
       if (common->snames.n && !splits)
         {
-          msg (SE, _("SNAMES requires SPLIT."));
+          msg_at (SE, common->snames_location, _("SNAMES requires SPLIT."));
           goto error;
         }
       if (!common->outfile)
         {
-          lex_sbc_missing ("OUTFILE");
+          lex_sbc_missing (s->lexer, "OUTFILE");
           goto error;
         }
       common->location = lex_ofs_location (s->lexer, start_ofs,
@@ -7882,8 +8025,7 @@ msave_add_vars (struct dictionary *d, const struct string_array *vars)
 }
 
 static struct dictionary *
-msave_create_dict (const struct msave_common *common,
-                   const struct msg_location *location)
+msave_create_dict (const struct msave_common *common)
 {
   struct dictionary *dict = dict_create (get_default_encoding ());
 
@@ -7900,7 +8042,8 @@ msave_create_dict (const struct msave_common *common,
   const char *dup_factor = msave_add_vars (dict, &common->fnames);
   if (dup_factor)
     {
-      msg_at (SE, location, _("Duplicate or invalid FACTOR variable name %s."),
+      msg_at (SE, common->fnames_location,
+              _("Duplicate or invalid FACTOR variable name %s."),
               dup_factor);
       goto error;
     }
@@ -7910,7 +8053,8 @@ msave_create_dict (const struct msave_common *common,
   const char *dup_var = msave_add_vars (dict, &common->variables);
   if (dup_var)
     {
-      msg_at (SE, location, _("Duplicate or invalid variable name %s."),
+      msg_at (SE, common->variables_location,
+              _("Duplicate or invalid variable name %s."),
               dup_var);
       goto error;
     }
@@ -7989,7 +8133,7 @@ matrix_msave_execute (struct matrix_command *cmd)
 
   if (!common->writer)
     {
-      struct dictionary *dict = msave_create_dict (common, cmd->location);
+      struct dictionary *dict = msave_create_dict (common);
       if (!dict)
         goto error;
 
@@ -8864,7 +9008,8 @@ matrix_commands_parse (struct matrix_state *s, struct matrix_commands *c,
 
       if (lex_at_phrase (s->lexer, "END MATRIX"))
         {
-          msg (SE, _("Premature END MATRIX within %s."), command_name);
+          lex_next_error (s->lexer, 0, 1,
+                          _("Premature END MATRIX within %s."), command_name);
           return false;
         }