Assorted improvements to diagnostics.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 12 Sep 2022 00:30:07 +0000 (17:30 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 12 Sep 2022 00:55:27 +0000 (17:55 -0700)
38 files changed:
src/language/command.c
src/language/control/do-if.c
src/language/control/loop.c
src/language/control/repeat.c
src/language/data-io/inpt-pgm.c
src/language/data-io/matrix-data.c
src/language/data-io/trim.c
src/language/dictionary/missing-values.c
src/language/dictionary/modify-variables.c
src/language/dictionary/mrsets.c
src/language/dictionary/rename-variables.c
src/language/dictionary/split-file.c
src/language/dictionary/sys-file-info.c
src/language/expressions/parse.c
src/language/expressions/public.h
src/language/lexer/value-parser.c
src/language/stats/crosstabs.c
src/language/stats/descriptives.c
src/language/xforms/compute.c
src/libpspp/message.c
src/libpspp/message.h
tests/data/dictionary.at
tests/language/command.at
tests/language/control/do-if.at
tests/language/control/do-repeat.at
tests/language/control/loop.at
tests/language/data-io/inpt-pgm.at
tests/language/data-io/matrix-data.at
tests/language/data-io/save-translate.at
tests/language/dictionary/missing-values.at
tests/language/dictionary/modify-variables.at
tests/language/dictionary/mrsets.at
tests/language/dictionary/rename-variables.at
tests/language/dictionary/split-file.at
tests/language/dictionary/sys-file-info.at
tests/language/expressions/parse.at
tests/language/stats/crosstabs.at
tests/language/stats/descriptives.at

index ab5bab904aa8911889075b0d04a7abedfad09c99..e67f6ab80800352ec1132cdb9d36866c87e63c3f 100644 (file)
@@ -548,7 +548,9 @@ cmd_erase (struct lexer *lexer, struct dataset *ds UNUSED)
 
   if (settings_get_safer_mode ())
     {
-      msg (SE, _("This command not allowed when the %s option is set."), "SAFER");
+      lex_ofs_error (lexer, 0, 0,
+                     _("This command not allowed when the %s option is set."),
+                     "SAFER");
       return CMD_FAILURE;
     }
 
index 162118488c60bcd987bbff41fabf8e9708578db7..69288e5c1497111d611558c9e9329c1074d49741 100644 (file)
@@ -147,9 +147,10 @@ cmd_do_if (struct lexer *lexer, struct dataset *ds)
 }
 
 int
-cmd_inside_do_if (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
+cmd_inside_do_if (struct lexer *lexer, struct dataset *ds UNUSED)
 {
-  msg (SE, _("This command cannot appear outside DO IF...END IF."));
+  lex_ofs_error (lexer, 0, lex_ofs (lexer) - 1,
+                 _("This command cannot appear outside DO IF...END IF."));
   return CMD_FAILURE;
 }
 
index 5c8cb14f09292d84100cce4ed90cbdabe92fbfbb..4dbc7c90bd7ff0c3f62bf444177d50efed3974f8 100644 (file)
@@ -122,9 +122,10 @@ cmd_loop (struct lexer *lexer, struct dataset *ds)
 }
 
 int
-cmd_inside_loop (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
+cmd_inside_loop (struct lexer *lexer, struct dataset *ds UNUSED)
 {
-  msg (SE, _("This command cannot appear outside LOOP...END LOOP."));
+  lex_ofs_error (lexer, 0, lex_ofs (lexer) - 1,
+                 _("This command cannot appear outside LOOP...END LOOP."));
   return CMD_FAILURE;
 }
 
index cb6a6779fae4299cfe9bbf9fa3c1b721b37c77af..005ba8ba2054a7d9580b7ac136c715659002a139 100644 (file)
@@ -117,8 +117,9 @@ parse_specification (struct lexer *lexer, struct dictionary *dict,
        goto error;
       name = lex_tokcstr (lexer);
       if (dict_lookup_var (dict, name))
-        msg (SW, _("Dummy variable name `%s' hides dictionary variable `%s'."),
-             name, name);
+        lex_msg (lexer, SW,
+                 _("Dummy variable name `%s' hides dictionary variable `%s'."),
+                 name, name);
 
       size_t name_len = strlen (name);
       if (find_dummy_var (dummies, name, name_len))
@@ -423,8 +424,8 @@ parse_strings (struct lexer *lexer, struct dummy_var *dv)
 }
 \f
 int
-cmd_end_repeat (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
+cmd_end_repeat (struct lexer *lexer, struct dataset *ds UNUSED)
 {
-  msg (SE, _("No matching %s."), "DO REPEAT");
+  lex_ofs_error (lexer, 0, 1, _("No matching %s."), "DO REPEAT");
   return CMD_CASCADING_FAILURE;
 }
index 46ee3567f027b5341d5e3c8b103516de16b9db70..c429c6a120b14f91d3d536804db49e6cc53d009a 100644 (file)
@@ -96,8 +96,12 @@ emit_END_CASE (struct dataset *ds)
 int
 cmd_input_program (struct lexer *lexer, struct dataset *ds)
 {
+  struct msg_location *location = lex_ofs_location (lexer, 0, 1);
   if (!lex_match (lexer, T_ENDCMD))
-    return lex_end_of_command (lexer);
+    {
+      msg_location_destroy (location);
+      return lex_end_of_command (lexer);
+    }
 
   struct session *session = session_create (dataset_session (ds));
   struct dataset *inp_ds = dataset_create (session, "INPUT PROGRAM");
@@ -123,6 +127,7 @@ cmd_input_program (struct lexer *lexer, struct dataset *ds)
             msg (SE, _("Unexpected end-of-file within %s."), "INPUT PROGRAM");
           inside_input_program = false;
           destroy_input_program (inp);
+          msg_location_destroy (location);
           return result;
         }
     }
@@ -131,18 +136,27 @@ cmd_input_program (struct lexer *lexer, struct dataset *ds)
   inside_input_program = false;
   proc_pop_transformations (inp->ds, &inp->xforms);
 
+  struct msg_location *end = lex_ofs_location (lexer, 0, 2);
+  msg_location_merge (&location, end);
+  location->omit_underlines = true;
+  msg_location_destroy (end);
+
   if (!saw_DATA_LIST && !saw_END_FILE)
     {
-      msg (SE, _("Input program must contain %s or %s."), "DATA LIST", "END FILE");
+      msg_at (SE, location, _("Input program does not contain %s or %s."),
+              "DATA LIST", "END FILE");
       destroy_input_program (inp);
+      msg_location_destroy (location);
       return CMD_FAILURE;
     }
   if (dict_get_next_value_idx (dataset_dict (inp->ds)) == 0)
     {
-      msg (SE, _("Input program did not create any variables."));
+      msg_at (SE, location, _("Input program did not create any variables."));
       destroy_input_program (inp);
+      msg_location_destroy (location);
       return CMD_FAILURE;
     }
+  msg_location_destroy (location);
 
   /* Figure out how to initialize each input case. */
   inp->init = caseinit_create ();
index 4ae4ef70cd7b13ced5138a1d71a549e65e66b94b..56f691bf7c577291a1310bdaddd58ca9ade4ecd6 100644 (file)
@@ -871,8 +871,10 @@ parse_matrix_data_subvars (struct lexer *lexer, struct dictionary *dict,
                            struct variable ***vars, size_t **indexes,
                            size_t *n_vars)
 {
+  int start_ofs = lex_ofs (lexer);
   if (!parse_variables (lexer, dict, vars, n_vars, 0))
     return false;
+  int end_ofs = lex_ofs (lexer) - 1;
 
   *indexes = xnmalloc (*n_vars, sizeof **indexes);
   for (size_t i = 0; i < *n_vars; i++)
@@ -880,7 +882,8 @@ parse_matrix_data_subvars (struct lexer *lexer, struct dictionary *dict,
       struct variable *v = (*vars)[i];
       if (!strcasecmp (var_get_name (v), "ROWTYPE_"))
         {
-          msg (SE, _("ROWTYPE_ is not allowed on SPLIT or FACTORS."));
+          lex_ofs_error (lexer, start_ofs, end_ofs,
+                         _("ROWTYPE_ is not allowed on SPLIT or FACTORS."));
           goto error;
         }
       (*indexes)[i] = var_get_dict_index (v);
index cf493391e4d9ccdf5729a6d974ce1056ce8bb774..4b527152d3c13dc4f95542ce11c0b07318fa631d 100644 (file)
@@ -62,16 +62,19 @@ parse_dict_trim (struct lexer *lexer, struct dictionary *dict, bool relax)
 
 /* Check that OLD_NAME can be renamed to NEW_NAME in DICT.  */
 static bool
-check_rename (const struct dictionary *dict, const char *old_name, const char *new_name)
+check_rename (struct lexer *lexer, int start_ofs, int end_ofs,
+              const struct dictionary *dict,
+              const char *old_name, const char *new_name)
 {
   if (dict_lookup_var (dict, new_name) != NULL)
     {
-      msg (SE, _("Cannot rename %s as %s because there already exists "
-                 "a variable named %s.  To rename variables with "
-                 "overlapping names, use a single RENAME subcommand "
-                 "such as `/RENAME (A=B)(B=C)(C=A)', or equivalently, "
-                 "`/RENAME (A B C=B C A)'."),
-           old_name, new_name, new_name);
+      lex_ofs_error (lexer, start_ofs, end_ofs,
+                     _("Cannot rename %s as %s because a variable named %s "
+                       "already exists."),
+                     old_name, new_name, new_name);
+      msg (SN, _("To rename variables with overlapping names, use a single "
+                 "RENAME subcommand such as `/RENAME (A=B)(B=C)(C=A)', or "
+                 "equivalently, `/RENAME (A B C=B C A)'."));
       return false;
     }
   return true;
@@ -194,19 +197,21 @@ parse_dict_rename (struct lexer *lexer, struct dictionary *dict,
         {
           /* These 3 tokens have already been checked in the
              try_to_sequence function.  */
+          int start_ofs = lex_ofs (lexer);
           lex_get (lexer);
           lex_get (lexer);
           lex_get (lexer);
+          int end_ofs = lex_ofs (lexer) - 1;
 
           /* Make sure the new names are suitable.  */
           for (int i = first; i <= last; ++i)
             {
-              int sz = strlen (prefix) + intlog10 (last) + 1;
-              char *vn = malloc (sz);
-              snprintf (vn, sz, "%s%d", prefix, i);
+              char *vn = xasprintf ("%s%d", prefix, i);
 
-              if (!check_rename (dict, var_get_name (oldvars[n_newvars]), vn))
+              if (!check_rename (lexer, start_ofs, end_ofs,
+                                 dict, var_get_name (oldvars[n_newvars]), vn))
                 {
+                  free (vn);
                   free (prefix);
                   goto fail;
                 }
@@ -232,9 +237,11 @@ parse_dict_rename (struct lexer *lexer, struct dictionary *dict,
                 }
             }
 
-          if (!check_rename (dict, var_get_name (oldvars[n_newvars]), new_name))
+          int ofs = lex_ofs (lexer);
+          if (!check_rename (lexer, ofs, ofs,
+                             dict, var_get_name (oldvars[n_newvars]), new_name))
             goto fail;
-          newnames[n_newvars] = strdup (new_name);
+          newnames[n_newvars] = xstrdup (new_name);
           lex_get (lexer);
           n_newvars++;
         }
index 425c50db3e579b6b98bfae1cbd10e16e2bd06591..683a6d6c9119ef765c493f54dac50b07b89e7499 100644 (file)
@@ -27,6 +27,7 @@
 #include "data/variable.h"
 #include "language/command.h"
 #include "language/lexer/lexer.h"
+#include "language/lexer/token.h"
 #include "language/lexer/value-parser.h"
 #include "language/lexer/variable-parser.h"
 #include "libpspp/i18n.h"
@@ -58,6 +59,15 @@ cmd_missing_values (struct lexer *lexer, struct dataset *ds)
       for (i = 0; i < nv; i++)
         var_clear_missing_values (v[i]);
 
+      int start_ofs = lex_ofs (lexer);
+      int end_ofs;
+      for (end_ofs = start_ofs; ; end_ofs++)
+        {
+          enum token_type next = lex_ofs_token (lexer, end_ofs + 1)->type;
+          if (next == T_RPAREN || next == T_ENDCMD || next == T_STOP)
+            break;
+        }
+
       if (!lex_match (lexer, T_RPAREN))
         {
           struct missing_values mv;
@@ -88,9 +98,10 @@ cmd_missing_values (struct lexer *lexer, struct dataset *ds)
                         ? mv_add_num (&mv, x)
                         : mv_add_range (&mv, x, y)))
                     {
-                      msg (SE, _("Too many numeric missing values.  At most "
-                                 "three individual values or one value and "
-                                 "one range are allowed."));
+                      lex_ofs_error (lexer, start_ofs, end_ofs,
+                                     _("Too many numeric missing values.  At "
+                                       "most three individual values or one "
+                                       "value and one range are allowed."));
                       ok = false;
                     }
 
@@ -133,9 +144,10 @@ cmd_missing_values (struct lexer *lexer, struct dataset *ds)
                   if (!mv_add_str (&mv, CHAR_CAST (const uint8_t *, raw_s),
                                    strlen (raw_s)))
                     {
-                      msg (SE,
-                           _("Too many string missing values.  "
-                             "At most three individual values are allowed."));
+                      lex_ofs_error (lexer, start_ofs, end_ofs,
+                                     _("Too many string missing values.  "
+                                       "At most three individual values "
+                                       "are allowed."));
                       ok = false;
                     }
                   free (raw_s);
@@ -151,9 +163,10 @@ cmd_missing_values (struct lexer *lexer, struct dataset *ds)
                 var_set_missing_values (v[i], &mv);
               else
                 {
-                  msg (SE, _("Missing values provided are too long to assign "
-                             "to variable of width %d."),
-                       var_get_width (v[i]));
+                  lex_ofs_error (lexer, start_ofs, end_ofs,
+                                 _("Missing values are too long to assign "
+                                   "to variable %s with width %d."),
+                                 var_get_name (v[i]), var_get_width (v[i]));
                   ok = false;
                 }
             }
index e14b46e04fdd689b7cf0ff9740c9cb651ddfd64c..e23d6a418dea7c34ae61d08532d681fc94aada67 100644 (file)
@@ -303,18 +303,21 @@ cmd_modify_vars (struct lexer *lexer, struct dataset *ds)
            }
          already_encountered |= 4;
 
+          int start_ofs = lex_ofs (lexer) - 1;
          lex_match (lexer, T_EQUALS);
          if (!parse_variables (lexer, dataset_dict (ds),
                                 &drop_vars, &n_drop, PV_NONE))
            goto done;
+          int end_ofs = lex_ofs (lexer) - 1;
           vm.drop_vars = drop_vars;
           vm.n_drop = n_drop;
 
           if (n_drop == dict_get_n_vars (dataset_dict (ds)))
             {
-              msg (SE, _("%s may not be used to delete all variables "
-                         "from the active dataset dictionary.  "
-                         "Use %s instead."), "MODIFY VARS", "NEW FILE");
+              lex_ofs_error (lexer, start_ofs, end_ofs,
+                             _("%s may not be used to delete all variables "
+                               "from the active dataset dictionary.  "
+                               "Use %s instead."), "MODIFY VARS", "NEW FILE");
               goto done;
             }
        }
index 1f281221a258c6e49d3a0183ccb74c01321008fb..ffd97b68002342f4513ba9881ead70dd5bc86ec8 100644 (file)
@@ -79,15 +79,21 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
              enum mrset_type type)
 {
   const char *subcommand_name = type == MRSET_MD ? "MDGROUP" : "MCGROUP";
-  bool labelsource_varlabel;
-  bool has_value;
 
   struct mrset *mrset = XZALLOC (struct mrset);
   mrset->type = type;
   mrset->cat_source = MRSET_VARLABELS;
 
-  labelsource_varlabel = false;
-  has_value = false;
+  bool labelsource_varlabel = false;
+  bool has_value = false;
+
+  int vars_start = 0;
+  int vars_end = 0;
+  int value_ofs = 0;
+  int labelsource_start = 0;
+  int labelsource_end = 0;
+  int label_start = 0;
+  int label_end = 0;
   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
     {
       if (lex_match_id (lexer, "NAME"))
@@ -113,22 +119,27 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
             goto error;
 
           free (mrset->vars);
+          vars_start = lex_ofs (lexer);
           if (!parse_variables (lexer, dict, &mrset->vars, &mrset->n_vars,
                                 PV_SAME_TYPE | PV_NO_SCRATCH))
             goto error;
+          vars_end = lex_ofs (lexer) - 1;
 
           if (mrset->n_vars < 2)
             {
-              msg (SE, _("VARIABLES specified only variable %s on %s, but "
-                         "at least two variables are required."),
+              lex_ofs_error (lexer, vars_start, vars_end,
+                             _("VARIABLES specified only variable %s on %s, but "
+                               "at least two variables are required."),
                    var_get_name (mrset->vars[0]), subcommand_name);
               goto error;
             }
         }
       else if (lex_match_id (lexer, "LABEL"))
         {
+          label_start = lex_ofs (lexer) - 1;
           if (!lex_force_match (lexer, T_EQUALS) || !lex_force_string (lexer))
             goto error;
+          label_end = lex_ofs (lexer);
 
           free (mrset->label);
           mrset->label = ss_xstrdup (lex_tokss (lexer));
@@ -141,6 +152,8 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
             goto error;
 
           labelsource_varlabel = true;
+          labelsource_start = lex_ofs (lexer) - 3;
+          labelsource_end = lex_ofs (lexer) - 1;
         }
       else if (type == MRSET_MD && lex_match_id (lexer, "VALUE"))
         {
@@ -148,6 +161,7 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
             goto error;
 
           has_value = true;
+          value_ofs = lex_ofs (lexer);
           if (lex_is_number (lexer))
             {
               if (!lex_is_integer (lexer))
@@ -232,10 +246,11 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
         {
           if (mrset->width == 0)
             {
-              msg (SE, _("MDGROUP subcommand for group %s specifies a string "
-                         "VALUE, but the variables specified for this group "
-                         "are numeric."),
-                   mrset->name);
+              lex_ofs_error (lexer, value_ofs, value_ofs,
+                             _("MDGROUP subcommand for group %s specifies a "
+                               "string VALUE, but the variables specified for "
+                               "this group are numeric."),
+                             mrset->name);
               goto error;
             }
           else {
@@ -256,12 +271,14 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
               }
             if (mrset->width > min_width)
               {
-                msg (SE, _("VALUE string on MDGROUP subcommand for group "
-                           "%s is %d bytes long, but it must be no longer "
-                           "than the narrowest variable in the group, "
-                           "which is %s with a width of %d bytes."),
-                     mrset->name, mrset->width,
-                     var_get_name (shortest_var), min_width);
+                lex_ofs_error (lexer, value_ofs, value_ofs,
+                               _("VALUE string on MDGROUP subcommand for "
+                                 "group %s is %d bytes long, but it must be "
+                                 "no longer than the narrowest variable in "
+                                 "the group, which is %s with a width of "
+                                 "%d bytes."),
+                               mrset->name, mrset->width,
+                               var_get_name (shortest_var), min_width);
                 goto error;
               }
           }
@@ -270,9 +287,10 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
         {
           if (mrset->width != 0)
             {
-              msg (SE, _("MDGROUP subcommand for group %s specifies a string "
-                         "VALUE, but the variables specified for this group "
-                         "are numeric."),
+              lex_ofs_error (lexer, value_ofs, value_ofs,
+                             _("MDGROUP subcommand for group %s specifies a "
+                               "string VALUE, but the variables specified for "
+                               "this group are numeric."),
                    mrset->name);
               goto error;
             }
@@ -282,16 +300,24 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
       if (labelsource_varlabel)
         {
           if (mrset->cat_source != MRSET_COUNTEDVALUES)
-            msg (SW, _("MDGROUP subcommand for group %s specifies "
-                       "LABELSOURCE=VARLABEL but not "
-                       "CATEGORYLABELS=COUNTEDVALUES.  "
-                       "Ignoring LABELSOURCE."),
+            lex_ofs_msg (lexer, SW, labelsource_start, labelsource_end,
+                         _("MDGROUP subcommand for group %s specifies "
+                           "LABELSOURCE=VARLABEL but not "
+                           "CATEGORYLABELS=COUNTEDVALUES.  "
+                           "Ignoring LABELSOURCE."),
                  mrset->name);
           else if (mrset->label)
-            msg (SW, _("MDGROUP subcommand for group %s specifies both LABEL "
-                       "and LABELSOURCE, but only one of these subcommands "
-                       "may be used at a time.  Ignoring LABELSOURCE."),
-                 mrset->name);
+            {
+              msg (SW, _("MDGROUP subcommand for group %s specifies both "
+                         "LABEL and LABELSOURCE, but only one of these "
+                         "subcommands may be used at a time.  "
+                         "Ignoring LABELSOURCE."),
+                   mrset->name);
+              lex_ofs_msg (lexer, SN, label_start, label_end,
+                           _("Here is the %s setting."), "LABEL");
+              lex_ofs_msg (lexer, SN, labelsource_start, labelsource_end,
+                           _("Here is the %s setting."), "LABELSOURCE");
+            }
           else
             {
               size_t i;
@@ -328,12 +354,13 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
                   if (other_name == NULL)
                     stringi_map_insert (&seen, label, name);
                   else
-                    msg (SW, _("Variables %s and %s specified as part of "
-                               "multiple dichotomy group %s have the same "
-                               "variable label.  Categories represented by "
-                               "these variables will not be distinguishable "
-                               "in output."),
-                         other_name, name, mrset->name);
+                    lex_ofs_msg (lexer, SW, vars_start, vars_end,
+                                 _("Variables %s and %s specified as part of "
+                                   "multiple dichotomy group %s have the same "
+                                   "variable label.  Categories represented by "
+                                   "these variables will not be distinguishable "
+                                   "in output."),
+                                 other_name, name, mrset->name);
                 }
             }
           stringi_map_destroy (&seen);
@@ -358,11 +385,12 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
               val_labs = var_get_value_labels (var);
               label = val_labs_find (val_labs, &value);
               if (label == NULL)
-                msg (SW, _("Variable %s specified as part of multiple "
-                           "dichotomy group %s (which has "
-                           "CATEGORYLABELS=COUNTEDVALUES) has no value label "
-                           "for its counted value.  This category will not "
-                           "be distinguishable in output."),
+                lex_ofs_msg (lexer, SW, vars_start, vars_end,
+                             _("Variable %s specified as part of multiple "
+                               "dichotomy group %s (which has "
+                               "CATEGORYLABELS=COUNTEDVALUES) has no value "
+                               "label for its counted value.  This category "
+                               "will not be distinguishable in output."),
                      name, mrset->name);
               else
                 {
@@ -371,13 +399,14 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
                   if (other_name == NULL)
                     stringi_map_insert (&seen, label, name);
                   else
-                    msg (SW, _("Variables %s and %s specified as part of "
-                               "multiple dichotomy group %s (which has "
-                               "CATEGORYLABELS=COUNTEDVALUES) have the same "
-                               "value label for the group's counted "
-                               "value.  These categories will not be "
-                               "distinguishable in output."),
-                         other_name, name, mrset->name);
+                    lex_ofs_msg (lexer, SW, vars_start, vars_end,
+                                 _("Variables %s and %s specified as part of "
+                                   "multiple dichotomy group %s (which has "
+                                   "CATEGORYLABELS=COUNTEDVALUES) have the same "
+                                   "value label for the group's counted "
+                                   "value.  These categories will not be "
+                                   "distinguishable in output."),
+                                 other_name, name, mrset->name);
                 }
             }
           stringi_map_destroy (&seen);
@@ -429,12 +458,13 @@ parse_group (struct lexer *lexer, struct dictionary *dict,
                                               var_get_print_format (var),
                                               settings_get_fmt_settings ());
                           c->warned = true;
-                          msg (SW, _("Variables specified on MCGROUP should "
-                                     "have the same categories, but %s and %s "
-                                     "(and possibly others) in multiple "
-                                     "category group %s have different "
-                                     "value labels for value %s."),
-                               c->var_name, name, mrset->name, s);
+                          lex_ofs_msg (lexer, SW, vars_start, vars_end,
+                                       _("Variables specified on MCGROUP should "
+                                         "have the same categories, but %s and "
+                                         "%s (and possibly others) in multiple "
+                                         "category group %s have different "
+                                         "value labels for value %s."),
+                                       c->var_name, name, mrset->name, s);
                           free (s);
                         }
                       goto found;
@@ -536,8 +566,9 @@ parse_display (struct lexer *lexer, struct dictionary *dict)
   if (n == 0)
     {
       if (dict_get_n_mrsets (dict) == 0)
-        msg (SN, _("The active dataset dictionary does not contain any "
-                   "multiple response sets."));
+        lex_next_msg (lexer, SN, -1, -1,
+                      _("The active dataset dictionary does not contain any "
+                        "multiple response sets."));
       stringi_set_destroy (&mrset_names_set);
       return true;
     }
index 94331c4afe86d383db7aa3f90d4f861e762861e0..7d037a616687734679309246475b31d72b5aa1f9 100644 (file)
@@ -59,6 +59,7 @@ cmd_rename_variables (struct lexer *lexer, struct dataset *ds)
 
       if (!lex_match (lexer, T_LPAREN))
         opts |= PV_SINGLE;
+      int start_ofs = lex_ofs (lexer);
       if (!parse_variables (lexer, dataset_dict (ds),
                             &vars_to_be_renamed, &n_vars_to_be_renamed, opts))
        {
@@ -73,10 +74,12 @@ cmd_rename_variables (struct lexer *lexer, struct dataset *ds)
        {
          goto lossage;
        }
+      int end_ofs = lex_ofs (lexer) - 1;
       if (n_new_names != n_vars_to_be_renamed)
         {
-          msg (SE, _("Differing number of variables in old name list "
-                     "(%zu) and in new name list (%zu)."),
+          lex_ofs_error (lexer, start_ofs, end_ofs,
+                         _("Differing number of variables in old name list "
+                           "(%zu) and in new name list (%zu)."),
               n_vars_to_be_renamed, n_new_names);
           goto lossage;
         }
@@ -91,7 +94,8 @@ cmd_rename_variables (struct lexer *lexer, struct dataset *ds)
                          vars_to_be_renamed, new_names, n_new_names,
                          &err_name))
     {
-      msg (SE, _("Renaming would duplicate variable name %s."), err_name);
+      lex_ofs_error (lexer, 2, lex_ofs (lexer) - 1,
+                     _("Renaming would duplicate variable name %s."), err_name);
       goto lossage;
     }
 
index 8a134e1e769eca5562e57e31febb49b9c29b03d0..13eecbb4ce358ffd1d8ad2a7c6c61556343e8fa3 100644 (file)
@@ -55,13 +55,16 @@ cmd_split_file (struct lexer *lexer, struct dataset *ds)
                               : SPLIT_LAYERED);
 
       lex_match (lexer, T_BY);
+      int vars_start = lex_ofs (lexer);
       if (!parse_variables (lexer, dataset_dict (ds), &v, &n, PV_NO_DUPLICATE))
        return CMD_CASCADING_FAILURE;
+      int vars_end = lex_ofs (lexer) - 1;
 
       if (n > MAX_SPLITS)
         {
           verify (MAX_SPLITS == 8);
-          msg (SE, _("At most 8 split variables may be specified."));
+          lex_ofs_error (lexer, vars_start, vars_end,
+                         _("At most 8 split variables may be specified."));
           free (v);
           return CMD_CASCADING_FAILURE;
         }
index 6ff0f9072df3f7a7070acab9867d02705e147cbd..3dc375213788f6b1c4d65d403596ac4276596f64 100644 (file)
@@ -389,7 +389,7 @@ cmd_display (struct lexer *lexer, struct dataset *ds)
                                 vl, n, attribute_flags);
         }
       else
-        msg (SW, _("No variables to display."));
+        msg (SN, _("No variables to display."));
 
       free (vl);
     }
index 397121f913c51427d64258b41af4c39a8a83710e..d1aaee3b1749fccdba155127cc0456318a000e8d 100644 (file)
@@ -125,11 +125,13 @@ expr_parse_bool (struct lexer *lexer, struct dataset *ds)
 }
 
 /* Parses a numeric expression that is intended to be assigned to newly created
-   variable NEW_VAR_NAME.  (This allows for a better error message if the
-   expression is not numeric.)  Otherwise similar to expr_parse(). */
+   variable NEW_VAR_NAME at NEW_VAR_LOCATION.  (This allows for a better error
+   message if the expression is not numeric.)  Otherwise similar to
+   expr_parse(). */
 struct expression *
 expr_parse_new_variable (struct lexer *lexer, struct dataset *ds,
-                         const char *new_var_name)
+                         const char *new_var_name,
+                         const struct msg_location *new_var_location)
 {
   struct expression *e = expr_create (ds);
   struct expr_node *n = parse_expr (lexer, e);
@@ -142,10 +144,11 @@ expr_parse_new_variable (struct lexer *lexer, struct dataset *ds,
   atom_type actual_type = expr_node_returns (n);
   if (actual_type != OP_number && actual_type != OP_boolean)
     {
-      msg (SE, _("This command tries to create a new variable %s by assigning a "
-                 "string value to it, but this is not supported.  Use "
-                 "the STRING command to create the new variable with the "
-                 "correct width before assigning to it, e.g. STRING %s(A20)."),
+      msg_at (SE, new_var_location,
+              _("This command tries to create a new variable %s by assigning a "
+                "string value to it, but this is not supported.  Use "
+                "the STRING command to create the new variable with the "
+                "correct width before assigning to it, e.g. STRING %s(A20)."),
            new_var_name, new_var_name);
       expr_free (e);
       return NULL;
index 6eb539166b9237278dd7d843f75d26d74aeb1c04..064b45404f7d8d5355328d3accdc246bec7816bc 100644 (file)
@@ -26,13 +26,15 @@ struct dataset;
 struct dictionary;
 struct expression;
 struct lexer;
+struct msg_location;
 struct pool;
 union value;
 
 struct expression *expr_parse (struct lexer *, struct dataset *, enum val_type);
 struct expression *expr_parse_bool (struct lexer *, struct dataset *);
-struct expression *expr_parse_new_variable (struct lexer *, struct dataset *,
-                                            const char *new_var_name);
+struct expression *expr_parse_new_variable (
+  struct lexer *, struct dataset *,
+  const char *new_var_name, const struct msg_location *new_var_location);
 void expr_free (struct expression *);
 
 struct dataset;
index c91fa89da12474a7c8d115c9a60ab844691e468d..e823cf34f1b2a0b8eb23fbe77e5905fc18f9ab02 100644 (file)
@@ -144,8 +144,26 @@ parse_value (struct lexer *lexer, union value *v, const struct variable *var)
     return parse_number (lexer, &v->f, &var_get_print_format (var)->type);
   else if (lex_force_string (lexer))
     {
-      const char *s = lex_tokcstr (lexer);
-      value_copy_str_rpad (v, width, CHAR_CAST_BUG (const uint8_t *, s), ' ');
+      struct substring out;
+      if (recode_pedantically (var_get_encoding (var), "UTF-8",
+                               lex_tokss (lexer), NULL, &out))
+        {
+          lex_error (lexer, _("This string is not representable in the "
+                              "dataset encoding."));
+          return false;
+        }
+      if (out.length > width)
+        {
+          lex_error (lexer, _("This %zu-byte string is too long for "
+                              "variable %s with width %d."),
+                     out.length, var_get_name (var), width);
+          ss_dealloc (&out);
+          return false;
+        }
+
+      value_copy_buf_rpad (v, width, CHAR_CAST (const uint8_t *, out.string),
+                           out.length, ' ');
+      ss_dealloc (&out);
     }
   else
     return false;
index 1b203083e0768b6f738c7c7e67e8d2b5c1a277c3..2205544d901bb96a164eacf330b40ffd078a540f 100644 (file)
@@ -177,6 +177,10 @@ struct crosstabulation
     double *row_tot;           /* Row totals. */
     double *col_tot;           /* Column totals. */
     double total;              /* Grand total. */
+
+    /* Syntax. */
+    int start_ofs;
+    int end_ofs;
   };
 
 /* Integer mode variable info. */
@@ -237,7 +241,7 @@ static void tabulate_general_case (struct crosstabulation *, const struct ccase
                                    double weight);
 static void tabulate_integer_case (struct crosstabulation *, const struct ccase *,
                                    double weight);
-static void postcalc (struct crosstabs_proc *);
+static void postcalc (struct crosstabs_proc *, struct lexer *);
 
 static double
 round_weight (const struct crosstabs_proc *proc, double weight)
@@ -528,7 +532,7 @@ cmd_crosstabs (struct lexer *lexer, struct dataset *ds)
       casereader_destroy (group);
 
       /* Output. */
-      postcalc (&proc);
+      postcalc (&proc, lexer);
     }
   bool ok = casegrouper_destroy (grouper);
   ok = proc_commit (ds) && ok;
@@ -585,6 +589,7 @@ parse_crosstabs_tables (struct lexer *lexer, struct dataset *ds,
 
   size_t nx = 1;
   int n_by = 0;
+  int vars_start = lex_ofs (lexer);
   for (;;)
     {
       by = xnrealloc (by, n_by + 1, sizeof *by);
@@ -594,7 +599,9 @@ parse_crosstabs_tables (struct lexer *lexer, struct dataset *ds,
        goto done;
       if (xalloc_oversized (nx, by_nvar[n_by]))
         {
-          msg (SE, _("Too many cross-tabulation variables or dimensions."));
+          lex_ofs_error (
+            lexer, vars_start, lex_ofs (lexer),
+            _("Too many cross-tabulation variables or dimensions."));
           goto done;
         }
       nx *= by_nvar[n_by];
@@ -608,6 +615,7 @@ parse_crosstabs_tables (struct lexer *lexer, struct dataset *ds,
            break;
        }
     }
+  int vars_end = lex_ofs (lexer) - 1;
 
   int *by_iter = XCALLOC (n_by, int);
   proc->pivots = xnrealloc (proc->pivots,
@@ -625,6 +633,8 @@ parse_crosstabs_tables (struct lexer *lexer, struct dataset *ds,
         .n_consts = 0,
         .const_vars = NULL,
         .const_indexes = NULL,
+        .start_ofs = vars_start,
+        .end_ofs = vars_end,
       };
 
       for (int j = 0; j < n_by; j++)
@@ -856,7 +866,8 @@ static void enum_var_values (const struct crosstabulation *, int var_idx,
                              bool descending);
 static void free_var_values (const struct crosstabulation *, int var_idx);
 static void output_crosstabulation (struct crosstabs_proc *,
-                                struct crosstabulation *);
+                                    struct crosstabulation *,
+                                    struct lexer *);
 static void make_crosstabulation_subset (struct crosstabulation *xt,
                                      size_t row0, size_t row1,
                                      struct crosstabulation *subset);
@@ -865,7 +876,7 @@ static bool find_crosstab (struct crosstabulation *, size_t *row0p,
                            size_t *row1p);
 
 static void
-postcalc (struct crosstabs_proc *proc)
+postcalc (struct crosstabs_proc *proc, struct lexer *lexer)
 {
   /* Round hash table entries, if requested
 
@@ -912,7 +923,7 @@ postcalc (struct crosstabs_proc *proc)
   for (struct crosstabulation *xt = proc->pivots;
        xt < &proc->pivots[proc->n_pivots]; xt++)
     {
-      output_crosstabulation (proc, xt);
+      output_crosstabulation (proc, xt, lexer);
       if (proc->barchart)
         {
           int n_vars = (xt->n_vars > 2 ? 2 : xt->n_vars);
@@ -1127,7 +1138,8 @@ static void build_matrix (struct crosstabulation *);
 
 /* Output pivot table XT in the context of PROC. */
 static void
-output_crosstabulation (struct crosstabs_proc *proc, struct crosstabulation *xt)
+output_crosstabulation (struct crosstabs_proc *proc, struct crosstabulation *xt,
+                        struct lexer *lexer)
 {
   for (size_t i = 0; i < xt->n_vars; i++)
     enum_var_values (xt, i, proc->descending);
@@ -1143,8 +1155,9 @@ output_crosstabulation (struct crosstabs_proc *proc, struct crosstabulation *xt)
 
       /* TRANSLATORS: The %s here describes a crosstabulation.  It takes the
          form "var1 * var2 * var3 * ...".  */
-      msg (SW, _("Crosstabulation %s contained no non-missing cases."),
-           ds_cstr (&vars));
+      lex_ofs_msg (lexer, SW, xt->start_ofs, xt->end_ofs,
+                   _("Crosstabulation %s contained no non-missing cases."),
+                   ds_cstr (&vars));
 
       ds_destroy (&vars);
       for (size_t i = 0; i < xt->n_vars; i++)
index 5f8c98085288c8e8eb9b478c34ebde64f81a0d1c..ce4e19576b6f4835983d905405f4c0b5dfb68d86 100644 (file)
@@ -214,6 +214,7 @@ cmd_descriptives (struct lexer *lexer, struct dataset *ds)
   dsc->z_writer = NULL;
 
   /* Parse DESCRIPTIVES. */
+  int z_ofs = 0;
   while (lex_token (lexer) != T_ENDCMD)
     {
       if (lex_match_id (lexer, "MISSING"))
@@ -236,7 +237,10 @@ cmd_descriptives (struct lexer *lexer, struct dataset *ds)
             }
         }
       else if (lex_match_id (lexer, "SAVE"))
-        save_z_scores = 1;
+        {
+          save_z_scores = 1;
+          z_ofs = lex_ofs (lexer) - 1;
+        }
       else if (lex_match_id (lexer, "FORMAT"))
         {
           lex_match (lexer, T_EQUALS);
@@ -337,6 +341,7 @@ cmd_descriptives (struct lexer *lexer, struct dataset *ds)
                 {
                   if (!lex_force_id (lexer))
                     goto error;
+                  z_ofs = lex_ofs (lexer);
                   if (try_name (dict, dsc, lex_tokcstr (lexer)))
                     {
                       struct dsc_var *dsc_var = &dsc->vars[dsc->n_vars - 1];
@@ -396,8 +401,9 @@ cmd_descriptives (struct lexer *lexer, struct dataset *ds)
          that) when TEMPORARY is in effect, but in the meantime this at least
          prevents a use-after-free error.  See bug #38786.  */
       if (proc_make_temporary_transformations_permanent (ds))
-        msg (SW, _("DESCRIPTIVES with Z scores ignores TEMPORARY.  "
-                   "Temporary transformations will be made permanent."));
+        lex_ofs_msg (lexer, SW, z_ofs, z_ofs,
+                     _("DESCRIPTIVES with Z scores ignores TEMPORARY.  "
+                       "Temporary transformations will be made permanent."));
 
       proto = caseproto_create ();
       for (i = 0; i < 1 + 2 * n_zs; i++)
index 61e67f33fa448d902a1d4f562404745401dfade7..a1ea72506a16fc9ac59995e1e3cd3d9700c013d8 100644 (file)
@@ -46,6 +46,8 @@ struct lvalue;
    For a vector element, the `vector' member is non-null. */
 struct lvalue
   {
+    struct msg_location *location; /* Syntax for variable or vector. */
+
     struct variable *variable;   /* Destination variable. */
     bool is_new_variable;        /* Did we create the variable? */
 
@@ -76,6 +78,8 @@ struct compute_trns
     const struct vector *vector; /* Destination vector, if any. */
     struct expression *element;  /* Destination vector element expr. */
 
+    struct msg_location *lvalue_location;
+
     /* Rvalue. */
     struct expression *rvalue;  /* Rvalue expression. */
   };
@@ -159,11 +163,13 @@ compute_num_vec (void *compute_, struct ccase **c, casenumber case_num)
           || rindx < 1 || rindx > vector_get_n_vars (compute->vector))
         {
           if (index == SYSMIS)
-            msg (SW, _("When executing COMPUTE: SYSMIS is not a valid value "
-                       "as an index into vector %s."),
+            msg_at (SW, compute->lvalue_location,
+                    _("When executing COMPUTE: SYSMIS is not a valid value "
+                      "as an index into vector %s."),
                  vector_get_name (compute->vector));
           else
-            msg (SW, _("When executing COMPUTE: %.*g is not a valid value as "
+            msg_at (SW, compute->lvalue_location,
+                    _("When executing COMPUTE: %.*g is not a valid value as "
                        "an index into vector %s."),
                  DBL_DIG + 1, index, vector_get_name (compute->vector));
           return TRNS_CONTINUE;
@@ -214,16 +220,18 @@ compute_str_vec (void *compute_, struct ccase **c, casenumber case_num)
       rindx = floor (index + EPSILON);
       if (index == SYSMIS)
         {
-          msg (SW, _("When executing COMPUTE: SYSMIS is not a valid "
-                     "value as an index into vector %s."),
-               vector_get_name (compute->vector));
+          msg_at (SW, compute->lvalue_location,
+                  _("When executing COMPUTE: SYSMIS is not a valid "
+                    "value as an index into vector %s."),
+                  vector_get_name (compute->vector));
           return TRNS_CONTINUE;
         }
       else if (rindx < 1 || rindx > vector_get_n_vars (compute->vector))
         {
-          msg (SW, _("When executing COMPUTE: %.*g is not a valid value as "
-                     "an index into vector %s."),
-               DBL_DIG + 1, index, vector_get_name (compute->vector));
+          msg_at (SW, compute->lvalue_location,
+                  _("When executing COMPUTE: %.*g is not a valid value as "
+                    "an index into vector %s."),
+                  DBL_DIG + 1, index, vector_get_name (compute->vector));
           return TRNS_CONTINUE;
         }
 
@@ -317,7 +325,8 @@ parse_rvalue (struct lexer *lexer,
              const struct lvalue *lvalue, struct dataset *ds)
 {
   if (lvalue->is_new_variable)
-    return expr_parse_new_variable (lexer, ds, var_get_name (lvalue->variable));
+    return expr_parse_new_variable (lexer, ds, var_get_name (lvalue->variable),
+                                    lvalue->location);
   else
     return expr_parse (lexer, ds, lvalue_get_type (lvalue));
 }
@@ -327,11 +336,7 @@ static struct compute_trns *
 compute_trns_create (void)
 {
   struct compute_trns *compute = xmalloc (sizeof *compute);
-  compute->test = NULL;
-  compute->variable = NULL;
-  compute->vector = NULL;
-  compute->element = NULL;
-  compute->rvalue = NULL;
+  *compute = (struct compute_trns) { .test = NULL };
   return compute;
 }
 
@@ -343,6 +348,7 @@ compute_trns_free (void *compute_)
 
   if (compute != NULL)
     {
+      msg_location_destroy (compute->lvalue_location);
       expr_free (compute->test);
       expr_free (compute->element);
       expr_free (compute->rvalue);
@@ -357,17 +363,14 @@ static struct lvalue *
 lvalue_parse (struct lexer *lexer, struct dataset *ds)
 {
   struct dictionary *dict = dataset_dict (ds);
-  struct lvalue *lvalue;
 
-  lvalue = xmalloc (sizeof *lvalue);
-  lvalue->variable = NULL;
-  lvalue->is_new_variable = false;
-  lvalue->vector = NULL;
-  lvalue->element = NULL;
+  struct lvalue *lvalue = xmalloc (sizeof *lvalue);
+  *lvalue = (struct lvalue) { .variable = NULL };
 
   if (!lex_force_id (lexer))
     goto lossage;
 
+  int start_ofs = lex_ofs (lexer);
   if (lex_next_token (lexer, 1) == T_LPAREN)
     {
       /* Vector. */
@@ -401,6 +404,8 @@ lvalue_parse (struct lexer *lexer, struct dataset *ds)
         }
       lex_get (lexer);
     }
+  int end_ofs = lex_ofs (lexer) - 1;
+  lvalue->location = lex_ofs_location (lexer, start_ofs, end_ofs);
   return lvalue;
 
  lossage:
@@ -432,6 +437,9 @@ lvalue_finalize (struct lvalue *lvalue,
                 struct compute_trns *compute,
                 struct dictionary *dict)
 {
+  compute->lvalue_location = lvalue->location;
+  lvalue->location = NULL;
+
   if (lvalue->vector == NULL)
     {
       compute->variable = lvalue->variable;
@@ -464,5 +472,6 @@ lvalue_destroy (struct lvalue *lvalue, struct dictionary *dict)
   if (lvalue->is_new_variable)
     dict_delete_var (dict, lvalue->variable);
   expr_free (lvalue->element);
+  msg_location_destroy (lvalue->location);
   free (lvalue);
 }
index ac24d55d1238aa192e786c3faf03416fc688978f..07cc270787bab365b6b2a5f4e7d568e8ceeb0d3c 100644 (file)
@@ -423,7 +423,7 @@ msg_to_string (const struct msg *m)
 
           int c0 = ln == l0 ? loc->start.column : 1;
           int c1 = ln == l1 ? loc->end.column : ss_utf8_count_columns (line);
-          if (c0 > 0 && c1 >= c0)
+          if (c0 > 0 && c1 >= c0 && !loc->omit_underlines)
             {
               ds_put_cstr (&s, "\n      |");
               ds_put_byte_multiple (&s, ' ', c0);
index 24d2defe46f2e41ed5ce1253fee377841d1733d9..e0c98cebf78da3f154cc57c01ebfea53fec895e0 100644 (file)
@@ -112,6 +112,11 @@ struct msg_location
        Both 'start' and 'end' are inclusive, line-wise and column-wise.
     */
     struct msg_point start, end;
+
+    /* Normally, 'start' and 'end' contain column information, then displaying
+       the message will underline the location.  Setting this to true disables
+       displaying underlines. */
+    bool omit_underlines;
   };
 
 void msg_location_uninit (struct msg_location *);
index 86a7e07cb44f70643007e5f16622c9236d8cc03b..e7e589e88163b0f115635e943de7d531232030a8 100644 (file)
@@ -42,7 +42,9 @@ Table: Data List
 AÈIÖU,aeiou
 1.00,2.00
 
-dictionary.sps:8: error: RENAME VARIABLES: Renaming would duplicate variable name aèiöu.
+"dictionary.sps:8.17-8.29: error: RENAME VARIABLES: Renaming would duplicate variable name aèiöu.
+    8 | RENAME VARIABLE (aeiou=aèiöu).
+      |                 ^~~~~~~~~~~~~"
 ])
 
 AT_CLEANUP
index 71e50819e0307f1e5cdc97a50c654063d9634b2a..562c69b1304e22d306058eb4f1b3c028ded84711 100644 (file)
@@ -78,7 +78,9 @@ set safer on
 erase FILE='foobar'.
 ])
 AT_CHECK([pspp -O format=csv erase.sps], [1], [dnl
-erase.sps:3: error: ERASE: This command not allowed when the SAFER option is set.
+"erase.sps:3.1-3.5: error: ERASE: This command not allowed when the SAFER option is set.
+    3 | erase FILE='foobar'.
+      | ^~~~~"
 ])
 AT_CHECK([cat foobar], [0], [contents
 ])
index 04e965b226ba675a77827929306b8d5ad30043fd..b12cdaa4fdf3f0d3072011a91ba55f1f7e76fa80 100644 (file)
@@ -92,11 +92,17 @@ END IF.
 DO IF 0.
 ])
 AT_CHECK([pspp -O format=csv do-if.sps], [1], [dnl
-do-if.sps:6: error: END IF: This command cannot appear outside DO IF...END IF.
+"do-if.sps:6.1-6.6: error: END IF: This command cannot appear outside DO IF...END IF.
+    6 | END IF.
+      | ^~~~~~"
 
-do-if.sps:7: error: ELSE: This command cannot appear outside DO IF...END IF.
+"do-if.sps:7.1-7.4: error: ELSE: This command cannot appear outside DO IF...END IF.
+    7 | ELSE.
+      | ^~~~"
 
-do-if.sps:8: error: ELSE IF: This command cannot appear outside DO IF...END IF.
+"do-if.sps:8.1-8.7: error: ELSE IF: This command cannot appear outside DO IF...END IF.
+    8 | ELSE IF 1.
+      | ^~~~~~~"
 
 "do-if.sps:12.1-12.4: error: DO IF: Only one ELSE is allowed within DO IF...END IF.
    12 | ELSE.
index b40f7cf541259400c45952df4521bfedb8212555..22ffaa90a5f5c2c5ccd75f6f9096855e30ed2c92 100644 (file)
@@ -94,10 +94,14 @@ END REPEAT.
 LIST.
 ])
 AT_CHECK([pspp -o pspp.csv do-repeat.sps], [0], [dnl
-do-repeat.sps:8: warning: DO REPEAT: Dummy variable name `x' hides dictionary variable `x'.
+do-repeat.sps:8.11: warning: DO REPEAT: Dummy variable name `x' hides dictionary variable `x'.
+    8 | DO REPEAT x = 1 2 3.
+      |           ^
 ])
 AT_CHECK([cat pspp.csv], [0], [dnl
-do-repeat.sps:8: warning: DO REPEAT: Dummy variable name `x' hides dictionary variable `x'.
+"do-repeat.sps:8.11: warning: DO REPEAT: Dummy variable name `x' hides dictionary variable `x'.
+    8 | DO REPEAT x = 1 2 3.
+      |           ^"
 
 Table: Data List
 x,y
index 37125d93990c0b74b5d97373060dfadddadc4259..1e2c84e76df1a3b3f4aa55cbcd0f3cd10be01969 100644 (file)
@@ -303,10 +303,15 @@ END LOOP !.
 LOOP.
 ])
 AT_CHECK([pspp loop.sps], 1, [dnl
-loop.sps:2: error: BREAK: This command cannot appear outside LOOP...END LOOP.
-
-loop.sps:3: error: END LOOP: This command cannot appear outside LOOP...END
+loop.sps:2.1-2.5: error: BREAK: This command cannot appear outside LOOP...END
 LOOP.
+    2 | BREAK.
+      | ^~~~~
+
+loop.sps:3.1-3.8: error: END LOOP: This command cannot appear outside LOOP...
+END LOOP.
+    3 | END LOOP.
+      | ^~~~~~~~
 
 loop.sps:5: error: LOOP: Only one index clause may be specified.
 
index 79c71be8b66e5c5aac9d7cb7f40ac30834d5a9b8..a0054588dad8dfd5d24993daca36d7f068e6f71b 100644 (file)
@@ -59,7 +59,10 @@ END INPUT PROGRAM.
 EXECUTE.
 ])
 AT_CHECK([pspp -O format=csv input-program.sps], [1], [dnl
-input-program.sps:3: error: INPUT PROGRAM: Input program must contain DATA LIST or END FILE.
+"input-program.sps:1.1-3.17: error: INPUT PROGRAM: Input program does not contain DATA LIST or END FILE.
+    1 | INPUT PROGRAM.
+    2 | STRING firstname lastname (a24) / address (a80).
+    3 | END INPUT PROGRAM."
 
 "input-program.sps:4.1-4.7: error: EXECUTE: EXECUTE is allowed only after the active dataset has been defined.
     4 | EXECUTE.
@@ -342,7 +345,10 @@ END FILE.
 END INPUT PROGRAM.
 ])
 AT_CHECK([pspp input-program.sps], 1, [dnl
-input-program.sps:3: error: INPUT PROGRAM: Input program did not create any
-variables.
+input-program.sps:1.1-3.17: error: INPUT PROGRAM: Input program did not create
+any variables.
+    1 | INPUT PROGRAM.
+    2 | END FILE.
+    3 | END INPUT PROGRAM.
 ])
 AT_CLEANUP
\ No newline at end of file
index 9f65d6eca7f033ca8f3cf9a42e37910f43065bfb..1dd0812024f087e0ae6d89f98f6fe6c36d9e8eb5 100644 (file)
@@ -1191,9 +1191,13 @@ matrix-data.sps:1: error: MATRIX DATA: VARIABLES may not include VARNAME_.
     2 | MATRIX DATA VARIABLES=v v v.
       |                         ^"
 
-matrix-data.sps:3: error: MATRIX DATA: ROWTYPE_ is not allowed on SPLIT or FACTORS.
+"matrix-data.sps:3.47-3.54: error: MATRIX DATA: ROWTYPE_ is not allowed on SPLIT or FACTORS.
+    3 | MATRIX DATA VARIABLES=rowtype_ v1 v2 v3/SPLIT=rowtype_.
+      |                                               ^~~~~~~~"
 
-matrix-data.sps:4: error: MATRIX DATA: ROWTYPE_ is not allowed on SPLIT or FACTORS.
+"matrix-data.sps:4.49-4.56: error: MATRIX DATA: ROWTYPE_ is not allowed on SPLIT or FACTORS.
+    4 | MATRIX DATA VARIABLES=rowtype_ v1 v2 v3/FACTORS=rowtype_.
+      |                                                 ^~~~~~~~"
 
 matrix-data.sps:5: error: MATRIX DATA: v1 may not appear on both SPLIT and FACTORS.
 
index d2dd858059fffd74d2b676fe9b7ab4fe95d0bb92..b1f860e8e74466e29144a57d31144d83452df07f 100644 (file)
@@ -137,7 +137,11 @@ SAVE TRANSLATE
 ])
 
 AT_CHECK([pspp -O format=csv bad.sps], [1], [dnl
-"bad.sps:16: error: SAVE TRANSLATE: Cannot rename Var2 as Var3 because there already exists a variable named Var3.  To rename variables with overlapping names, use a single RENAME subcommand such as `/RENAME (A=B)(B=C)(C=A)', or equivalently, `/RENAME (A B C=B C A)'."
+"bad.sps:16.26-16.29: error: SAVE TRANSLATE: Cannot rename Var2 as Var3 because a variable named Var3 already exists.
+   16 |         (Var1 Var2 = one Var3 )
+      |                          ^~~~"
+
+"bad.sps:16: note: SAVE TRANSLATE: To rename variables with overlapping names, use a single RENAME subcommand such as `/RENAME (A=B)(B=C)(C=A)', or equivalently, `/RENAME (A B C=B C A)'."
 ])
 
 
index 15a779180f3f6c5866965aef154470a57f9b7d39..a7732c73510e017a7df4dc59eee1883f26797f79 100644 (file)
@@ -171,7 +171,9 @@ MISSING VALUES str1 ('abc', 'def', 'ghi', 'jkl').
 MISSING VALUES num1 (2 THRU 1).
 ])
 AT_CHECK([pspp -O format=csv missing-values.sps], [1], [dnl
-missing-values.sps:5: error: MISSING VALUES: Missing values provided are too long to assign to variable of width 3.
+"missing-values.sps:5.35-5.41: error: MISSING VALUES: Missing values are too long to assign to variable str2 with width 3.
+    5 | MISSING VALUES str1 str2 longstr ('abcde').
+      |                                   ^~~~~~~"
 
 "missing-values.sps:8.25-8.37: error: MISSING VALUES: Truncating missing value to maximum acceptable length (8 bytes).
     8 | MISSING VALUES longstr ('abcdefghijk').
@@ -187,13 +189,21 @@ missing-values.sps:5: error: MISSING VALUES: Missing values provided are too lon
 
 missing-values.sps:14: error: MISSING VALUES: Cannot mix numeric variables (e.g. num1) and string variables (e.g. str1) within a single list.
 
-missing-values.sps:17: error: MISSING VALUES: Too many numeric missing values.  At most three individual values or one value and one range are allowed.
+"missing-values.sps:17.22-17.31: error: MISSING VALUES: Too many numeric missing values.  At most three individual values or one value and one range are allowed.
+   17 | MISSING VALUES num1 (1, 2, 3, 4).
+      |                      ^~~~~~~~~~"
 
-missing-values.sps:18: error: MISSING VALUES: Too many numeric missing values.  At most three individual values or one value and one range are allowed.
+"missing-values.sps:18.22-18.39: error: MISSING VALUES: Too many numeric missing values.  At most three individual values or one value and one range are allowed.
+   18 | MISSING VALUES num1 (1 THRU 2, 3 THRU 4).
+      |                      ^~~~~~~~~~~~~~~~~~"
 
-missing-values.sps:19: error: MISSING VALUES: Too many numeric missing values.  At most three individual values or one value and one range are allowed.
+"missing-values.sps:19.22-19.35: error: MISSING VALUES: Too many numeric missing values.  At most three individual values or one value and one range are allowed.
+   19 | MISSING VALUES num1 (1, 2 THRU 3, 4).
+      |                      ^~~~~~~~~~~~~~"
 
-missing-values.sps:20: error: MISSING VALUES: Too many string missing values.  At most three individual values are allowed.
+"missing-values.sps:20.22-20.47: error: MISSING VALUES: Too many string missing values.  At most three individual values are allowed.
+   20 | MISSING VALUES str1 ('abc', 'def', 'ghi', 'jkl').
+      |                      ^~~~~~~~~~~~~~~~~~~~~~~~~~"
 
 "missing-values.sps:23.22-23.29: warning: MISSING VALUES: The high end of the range (1) is below the low end (2).  The range will be treated as if reversed.
    23 | MISSING VALUES num1 (2 THRU 1).
index f101ba72116e4ad5f4e58c88c54fff88bebf261d..be79d74877e983477739c29cb593e15c08eb9ced 100644 (file)
@@ -232,7 +232,9 @@ Table: Variables
 Name,Position
 y,1
 
-modify-variables.sps:16: error: MODIFY VARS: MODIFY VARS may not be used to delete all variables from the active dataset dictionary.  Use NEW FILE instead.
+"modify-variables.sps:16.14-16.19: error: MODIFY VARS: MODIFY VARS may not be used to delete all variables from the active dataset dictionary.  Use NEW FILE instead.
+   16 | MODIFY VARS /DROP y.
+      |              ^~~~~~"
 
 modify-variables.sps:17: error: Stopping syntax file processing here to avoid a cascade of dependent command failures.
 ])
index b72803c19e509279841ae413cd55c6c0d91acba4..f8f21a4c1c78733f75a49e84b8e0cf222979a720 100644 (file)
@@ -64,22 +64,38 @@ MRSETS
      VARIABLES=a b c d.
 ]])
 
-m4_define([DEFINE_MRSETS_OUTPUT],
-  [mrsets.sps:25: warning: MRSETS: Variables w and z specified as part of multiple dichotomy group $a have the same variable label.  Categories represented by these variables will not be distinguishable in output.
+m4_define([DEFINE_MRSETS_OUTPUT], [dnl
+"mrsets.sps:23.16-23.22: warning: MRSETS: Variables w and z specified as part of multiple dichotomy group $a have the same variable label.  Categories represented by these variables will not be distinguishable in output.
+   23 |      VARIABLES=w x y z
+      |                ^~~~~~~"
 
-mrsets.sps:29: warning: MRSETS: Variable z specified as part of multiple dichotomy group $b (which has CATEGORYLABELS=COUNTEDVALUES) has no value label for its counted value.  This category will not be distinguishable in output.
+"mrsets.sps:27.16-27.18: warning: MRSETS: Variable z specified as part of multiple dichotomy group $b (which has CATEGORYLABELS=COUNTEDVALUES) has no value label for its counted value.  This category will not be distinguishable in output.
+   27 |      VARIABLES=z y
+      |                ^~~"
 
-mrsets.sps:29: warning: MRSETS: Variable y specified as part of multiple dichotomy group $b (which has CATEGORYLABELS=COUNTEDVALUES) has no value label for its counted value.  This category will not be distinguishable in output.
+"mrsets.sps:27.16-27.18: warning: MRSETS: Variable y specified as part of multiple dichotomy group $b (which has CATEGORYLABELS=COUNTEDVALUES) has no value label for its counted value.  This category will not be distinguishable in output.
+   27 |      VARIABLES=z y
+      |                ^~~"
 
-mrsets.sps:34: warning: MRSETS: Variable x specified as part of multiple dichotomy group $c (which has CATEGORYLABELS=COUNTEDVALUES) has no value label for its counted value.  This category will not be distinguishable in output.
+"mrsets.sps:32.16-32.22: warning: MRSETS: Variable x specified as part of multiple dichotomy group $c (which has CATEGORYLABELS=COUNTEDVALUES) has no value label for its counted value.  This category will not be distinguishable in output.
+   32 |      VARIABLES=w x y z
+      |                ^~~~~~~"
 
-mrsets.sps:34: warning: MRSETS: Variables y and z specified as part of multiple dichotomy group $c (which has CATEGORYLABELS=COUNTEDVALUES) have the same value label for the group's counted value.  These categories will not be distinguishable in output.
+"mrsets.sps:32.16-32.22: warning: MRSETS: Variables y and z specified as part of multiple dichotomy group $c (which has CATEGORYLABELS=COUNTEDVALUES) have the same value label for the group's counted value.  These categories will not be distinguishable in output.
+   32 |      VARIABLES=w x y z
+      |                ^~~~~~~"
 
-mrsets.sps:38: warning: MRSETS: MDGROUP subcommand for group $d specifies LABELSOURCE=VARLABEL but not CATEGORYLABELS=COUNTEDVALUES.  Ignoring LABELSOURCE.
+"mrsets.sps:35.6-35.25: warning: MRSETS: MDGROUP subcommand for group $d specifies LABELSOURCE=VARLABEL but not CATEGORYLABELS=COUNTEDVALUES.  Ignoring LABELSOURCE.
+   35 |      LABELSOURCE=VARLABEL
+      |      ^~~~~~~~~~~~~~~~~~~~"
 
-"mrsets.sps:41: warning: MRSETS: Variables specified on MCGROUP should have the same categories, but w and y (and possibly others) in multiple category group $e have different value labels for value 1."
+"mrsets.sps:40.16-40.22: warning: MRSETS: Variables specified on MCGROUP should have the same categories, but w and y (and possibly others) in multiple category group $e have different value labels for value 1.
+   40 |      VARIABLES=w x y z
+      |                ^~~~~~~"
 
-"mrsets.sps:42: warning: MRSETS: Variables specified on MCGROUP should have the same categories, but a and c (and possibly others) in multiple category group $f have different value labels for value b."
+"mrsets.sps:42.16-42.22: warning: MRSETS: Variables specified on MCGROUP should have the same categories, but a and c (and possibly others) in multiple category group $f have different value labels for value b.
+   42 |      VARIABLES=a b c d.
+      |                ^~~~~~~"
 ])
 
 m4_define([MRSETS_DISPLAY_OUTPUT], [dnl
@@ -175,7 +191,9 @@ b
 c
 d"
 
-mrsets.sps:50: note: MRSETS: The active dataset dictionary does not contain any multiple response sets.
+"mrsets.sps:50.19-50.21: note: MRSETS: The active dataset dictionary does not contain any multiple response sets.
+   50 |     /DISPLAY NAME=ALL.
+      |                   ^~~"
 ])
 AT_CLEANUP
 
@@ -210,8 +228,10 @@ AT_DATA([mrsets.sps],
   [DEFINE_MRSETS_DATA
 MRSETS /MCGROUP NAME=$x VARIABLES=a.
 ])
-AT_CHECK([pspp -O format=csv mrsets.sps], [1],
-  ["mrsets.sps:6: error: MRSETS: VARIABLES specified only variable a on MCGROUP, but at least two variables are required."
+AT_CHECK([pspp -O format=csv mrsets.sps], [1], [dnl
+"mrsets.sps:6.35: error: MRSETS: VARIABLES specified only variable a on MCGROUP, but at least two variables are required.
+    6 | MRSETS /MCGROUP NAME=$x VARIABLES=a.
+      |                                   ^"
 ])
 AT_CLEANUP
 
@@ -273,10 +293,14 @@ AT_DATA([mrsets.sps],
 MRSETS /MDGROUP NAME=$group1 VARIABLES=a b VALUE=1.
 MRSETS /MDGROUP NAME=$group2 VARIABLES=x y VALUE='abc'.
 ])
-AT_CHECK([pspp -O format=csv mrsets.sps], [1],
-  ["mrsets.sps:6: error: MRSETS: MDGROUP subcommand for group $group1 specifies a string VALUE, but the variables specified for this group are numeric."
+AT_CHECK([pspp -O format=csv mrsets.sps], [1], [dnl
+"mrsets.sps:6.50: error: MRSETS: MDGROUP subcommand for group $group1 specifies a string VALUE, but the variables specified for this group are numeric.
+    6 | MRSETS /MDGROUP NAME=$group1 VARIABLES=a b VALUE=1.
+      |                                                  ^"
 
-"mrsets.sps:7: error: MRSETS: MDGROUP subcommand for group $group2 specifies a string VALUE, but the variables specified for this group are numeric."
+"mrsets.sps:7.50-7.54: error: MRSETS: MDGROUP subcommand for group $group2 specifies a string VALUE, but the variables specified for this group are numeric.
+    7 | MRSETS /MDGROUP NAME=$group2 VARIABLES=x y VALUE='abc'.
+      |                                                  ^~~~~"
 ])
 AT_CLEANUP
 
@@ -285,8 +309,10 @@ AT_DATA([mrsets.sps],
   [DEFINE_MRSETS_DATA
 MRSETS /MDGROUP NAME=$group1 VARIABLES=a b VALUE='abc'.
 ])
-AT_CHECK([pspp -O format=csv mrsets.sps], [1],
-  ["mrsets.sps:6: error: MRSETS: VALUE string on MDGROUP subcommand for group $group1 is 3 bytes long, but it must be no longer than the narrowest variable in the group, which is a with a width of 1 bytes."
+AT_CHECK([pspp -O format=csv mrsets.sps], [1], [dnl
+"mrsets.sps:6.50-6.54: error: MRSETS: VALUE string on MDGROUP subcommand for group $group1 is 3 bytes long, but it must be no longer than the narrowest variable in the group, which is a with a width of 1 bytes.
+    6 | MRSETS /MDGROUP NAME=$group1 VARIABLES=a b VALUE='abc'.
+      |                                                  ^~~~~"
 ])
 AT_CLEANUP
 
@@ -296,8 +322,10 @@ AT_DATA([mrsets.sps],
 MRSETS /MDGROUP NAME=$group1 VARIABLES=a b VALUE='a'
                 LABEL='label' LABELSOURCE=VARLABEL.
 ])
-AT_CHECK([pspp -O format=csv mrsets.sps], [0],
-  [mrsets.sps:7: warning: MRSETS: MDGROUP subcommand for group $group1 specifies LABELSOURCE=VARLABEL but not CATEGORYLABELS=COUNTEDVALUES.  Ignoring LABELSOURCE.
+AT_CHECK([pspp -O format=csv mrsets.sps], [0], [dnl
+"mrsets.sps:7.31-7.50: warning: MRSETS: MDGROUP subcommand for group $group1 specifies LABELSOURCE=VARLABEL but not CATEGORYLABELS=COUNTEDVALUES.  Ignoring LABELSOURCE.
+    7 |                 LABEL='label' LABELSOURCE=VARLABEL.
+      |                               ^~~~~~~~~~~~~~~~~~~~"
 ])
 AT_CLEANUP
 
index 2bac98769389c7d59d7c0df856bdfac61ba58a62..3009cd948e5501943315a4413566950b86ea1a07 100644 (file)
@@ -111,7 +111,9 @@ RENAME VARIABLES (brakeFluid=applecarts y=bananamobiles).
 ])
 
 AT_CHECK([pspp -o pspp.csv rename-variables.sps], [1], [dnl
-rename-variables.sps:2: error: RENAME VARIABLES: Differing number of variables in old name list (1) and in new name list (2).
+rename-variables.sps:2.19-2.41: error: RENAME VARIABLES: Differing number of variables in old name list (1) and in new name list (2).
+    2 | RENAME VARIABLES (brakeFluid=applecarts y=bananamobiles).
+      |                   ^~~~~~~~~~~~~~~~~~~~~~~
 ])
 AT_CLEANUP
 
index aec4b7449f02fb350d1d8c288d8eb0b833607ef7..2ea856593f25618e8e2fc3641484a747965241b7 100644 (file)
@@ -139,7 +139,9 @@ DATA LIST LIST NOTABLE /V1 TO V9.
 SPLIT FILE BY V1 TO V9.
 ])
 AT_CHECK([pspp split-file.sps], [1], [dnl
-split-file.sps:2: error: SPLIT FILE: At most 8 split variables may be
+split-file.sps:2.15-2.22: error: SPLIT FILE: At most 8 split variables may be
 specified.
+    2 | SPLIT FILE BY V1 TO V9.
+      |               ^~~~~~~~
 ])
 AT_CLEANUP
index 4f53b4e0ff64d628be2be084da6a5b47f0c9c673..f70f4668058fbf5822811eb35b697302e6ae2b98 100644 (file)
@@ -94,7 +94,7 @@ COMPUTE #x=0.
 DISPLAY SCRATCH.
 ])
 AT_CHECK([pspp -O format=csv sysfile-info.sps], [0], [dnl
-sysfile-info.sps:2: warning: DISPLAY: No variables to display.
+sysfile-info.sps:2: note: DISPLAY: No variables to display.
 
 Table: Variables
 Name
index 96e42c13ea179aac7cd4b9d4005494013a8544e6..4f66b8533968553953e2c9bf3b9b05f1481d045a 100644 (file)
@@ -90,10 +90,12 @@ DATA LIST NOTABLE/x 1(A).
 COMPUTE y='a'.
 ])
 AT_CHECK([pspp parse.sps], [1], [dnl
-parse.sps:2: error: COMPUTE: This command tries to create a new variable y by
+parse.sps:2.9: error: COMPUTE: This command tries to create a new variable y by
 assigning a string value to it, but this is not supported.  Use the STRING
 command to create the new variable with the correct width before assigning to
 it, e.g. STRING y(A20).
+    2 | COMPUTE y='a'.
+      |         ^
 ])
 AT_CLEANUP
 
@@ -208,8 +210,12 @@ this warning, insert parentheses.
     5 | COMPUTE c = 2**3**4.
       |             ^~~~~~~
 
-parse.sps:10: error: INPUT PROGRAM: Input program must contain DATA LIST or END
-FILE.
+parse.sps:1.1-10.17: error: INPUT PROGRAM: Input program does not contain DATA
+LIST or END FILE.
+    1 | INPUT PROGRAM.
+    2 | * These should provoke warnings.
+  ... |
+   10 | END INPUT PROGRAM.
 ])
 AT_CLEANUP
 
index f51c3345a4132c92ac83573634f009c954aea7d6..53a7335de9fa8bdf31a53afce43f48945ad7f1a4 100644 (file)
@@ -682,7 +682,9 @@ Table: Summary
 ,N,Percent,N,Percent,N,Percent
 X1 × X2,0,.0%,1,100.0%,1,100.0%
 
-crosstabs.sps:8: warning: CROSSTABS: Crosstabulation X1 × X2 contained no non-missing cases.
+"crosstabs.sps:8.20-8.27: warning: CROSSTABS: Crosstabulation X1 × X2 contained no non-missing cases.
+    8 | CROSSTABS /TABLES= X1 by X2.
+      |                    ^~~~~~~~"
 ])
 AT_CLEANUP
 
index c06737d2b251f20d46547f18123fb302bd612080..0c920130c955b0eab32dd28d5479419c4a35c7c2 100644 (file)
@@ -340,10 +340,14 @@ DESCRIPTIVES /VAR=abc/SAVE.
 LIST.
 ])
 AT_CHECK([pspp -o pspp.csv -o pspp.txt descriptives.sps], [0], [dnl
-descriptives.sps:15: warning: DESCRIPTIVES: DESCRIPTIVES with Z scores ignores TEMPORARY.  Temporary transformations will be made permanent.
+descriptives.sps:15.23-15.26: warning: DESCRIPTIVES: DESCRIPTIVES with Z scores ignores TEMPORARY.  Temporary transformations will be made permanent.
+   15 | DESCRIPTIVES /VAR=abc/SAVE.
+      |                       ^~~~
 ])
 AT_CHECK([cat pspp.csv], [0], [dnl
-descriptives.sps:15: warning: DESCRIPTIVES: DESCRIPTIVES with Z scores ignores TEMPORARY.  Temporary transformations will be made permanent.
+"descriptives.sps:15.23-15.26: warning: DESCRIPTIVES: DESCRIPTIVES with Z scores ignores TEMPORARY.  Temporary transformations will be made permanent.
+   15 | DESCRIPTIVES /VAR=abc/SAVE.
+      |                       ^~~~"
 
 Table: Mapping of Variables to Z-scores
 Source,Target