From 0499030ee8e638a481e6cb8d91646b7a88292da7 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 11 Sep 2022 17:30:07 -0700 Subject: [PATCH] Assorted improvements to diagnostics. --- src/language/command.c | 4 +- src/language/control/do-if.c | 5 +- src/language/control/loop.c | 5 +- src/language/control/repeat.c | 9 +- src/language/data-io/inpt-pgm.c | 20 ++- src/language/data-io/matrix-data.c | 5 +- src/language/data-io/trim.c | 33 +++-- src/language/dictionary/missing-values.c | 31 ++-- src/language/dictionary/modify-variables.c | 9 +- src/language/dictionary/mrsets.c | 137 +++++++++++------- src/language/dictionary/rename-variables.c | 10 +- src/language/dictionary/split-file.c | 5 +- src/language/dictionary/sys-file-info.c | 2 +- src/language/expressions/parse.c | 17 ++- src/language/expressions/public.h | 6 +- src/language/lexer/value-parser.c | 22 ++- src/language/stats/crosstabs.c | 31 ++-- src/language/stats/descriptives.c | 12 +- src/language/xforms/compute.c | 51 ++++--- src/libpspp/message.c | 2 +- src/libpspp/message.h | 5 + tests/data/dictionary.at | 4 +- tests/language/command.at | 4 +- tests/language/control/do-if.at | 12 +- tests/language/control/do-repeat.at | 8 +- tests/language/control/loop.at | 11 +- tests/language/data-io/inpt-pgm.at | 12 +- tests/language/data-io/matrix-data.at | 8 +- tests/language/data-io/save-translate.at | 6 +- tests/language/dictionary/missing-values.at | 20 ++- tests/language/dictionary/modify-variables.at | 4 +- tests/language/dictionary/mrsets.at | 66 ++++++--- tests/language/dictionary/rename-variables.at | 4 +- tests/language/dictionary/split-file.at | 4 +- tests/language/dictionary/sys-file-info.at | 2 +- tests/language/expressions/parse.at | 12 +- tests/language/stats/crosstabs.at | 4 +- tests/language/stats/descriptives.at | 8 +- 38 files changed, 419 insertions(+), 191 deletions(-) diff --git a/src/language/command.c b/src/language/command.c index ab5bab904a..e67f6ab808 100644 --- a/src/language/command.c +++ b/src/language/command.c @@ -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; } diff --git a/src/language/control/do-if.c b/src/language/control/do-if.c index 162118488c..69288e5c14 100644 --- a/src/language/control/do-if.c +++ b/src/language/control/do-if.c @@ -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; } diff --git a/src/language/control/loop.c b/src/language/control/loop.c index 5c8cb14f09..4dbc7c90bd 100644 --- a/src/language/control/loop.c +++ b/src/language/control/loop.c @@ -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; } diff --git a/src/language/control/repeat.c b/src/language/control/repeat.c index cb6a6779fa..005ba8ba20 100644 --- a/src/language/control/repeat.c +++ b/src/language/control/repeat.c @@ -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) } 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; } diff --git a/src/language/data-io/inpt-pgm.c b/src/language/data-io/inpt-pgm.c index 46ee3567f0..c429c6a120 100644 --- a/src/language/data-io/inpt-pgm.c +++ b/src/language/data-io/inpt-pgm.c @@ -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 (); diff --git a/src/language/data-io/matrix-data.c b/src/language/data-io/matrix-data.c index 4ae4ef70cd..56f691bf7c 100644 --- a/src/language/data-io/matrix-data.c +++ b/src/language/data-io/matrix-data.c @@ -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); diff --git a/src/language/data-io/trim.c b/src/language/data-io/trim.c index cf493391e4..4b527152d3 100644 --- a/src/language/data-io/trim.c +++ b/src/language/data-io/trim.c @@ -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++; } diff --git a/src/language/dictionary/missing-values.c b/src/language/dictionary/missing-values.c index 425c50db3e..683a6d6c91 100644 --- a/src/language/dictionary/missing-values.c +++ b/src/language/dictionary/missing-values.c @@ -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; } } diff --git a/src/language/dictionary/modify-variables.c b/src/language/dictionary/modify-variables.c index e14b46e04f..e23d6a418d 100644 --- a/src/language/dictionary/modify-variables.c +++ b/src/language/dictionary/modify-variables.c @@ -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; } } diff --git a/src/language/dictionary/mrsets.c b/src/language/dictionary/mrsets.c index 1f281221a2..ffd97b6800 100644 --- a/src/language/dictionary/mrsets.c +++ b/src/language/dictionary/mrsets.c @@ -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; } diff --git a/src/language/dictionary/rename-variables.c b/src/language/dictionary/rename-variables.c index 94331c4afe..7d037a6166 100644 --- a/src/language/dictionary/rename-variables.c +++ b/src/language/dictionary/rename-variables.c @@ -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; } diff --git a/src/language/dictionary/split-file.c b/src/language/dictionary/split-file.c index 8a134e1e76..13eecbb4ce 100644 --- a/src/language/dictionary/split-file.c +++ b/src/language/dictionary/split-file.c @@ -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; } diff --git a/src/language/dictionary/sys-file-info.c b/src/language/dictionary/sys-file-info.c index 6ff0f9072d..3dc3752137 100644 --- a/src/language/dictionary/sys-file-info.c +++ b/src/language/dictionary/sys-file-info.c @@ -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); } diff --git a/src/language/expressions/parse.c b/src/language/expressions/parse.c index 397121f913..d1aaee3b17 100644 --- a/src/language/expressions/parse.c +++ b/src/language/expressions/parse.c @@ -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; diff --git a/src/language/expressions/public.h b/src/language/expressions/public.h index 6eb539166b..064b45404f 100644 --- a/src/language/expressions/public.h +++ b/src/language/expressions/public.h @@ -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; diff --git a/src/language/lexer/value-parser.c b/src/language/lexer/value-parser.c index c91fa89da1..e823cf34f1 100644 --- a/src/language/lexer/value-parser.c +++ b/src/language/lexer/value-parser.c @@ -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; diff --git a/src/language/stats/crosstabs.c b/src/language/stats/crosstabs.c index 1b203083e0..2205544d90 100644 --- a/src/language/stats/crosstabs.c +++ b/src/language/stats/crosstabs.c @@ -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++) diff --git a/src/language/stats/descriptives.c b/src/language/stats/descriptives.c index 5f8c980852..ce4e19576b 100644 --- a/src/language/stats/descriptives.c +++ b/src/language/stats/descriptives.c @@ -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++) diff --git a/src/language/xforms/compute.c b/src/language/xforms/compute.c index 61e67f33fa..a1ea72506a 100644 --- a/src/language/xforms/compute.c +++ b/src/language/xforms/compute.c @@ -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); } diff --git a/src/libpspp/message.c b/src/libpspp/message.c index ac24d55d12..07cc270787 100644 --- a/src/libpspp/message.c +++ b/src/libpspp/message.c @@ -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); diff --git a/src/libpspp/message.h b/src/libpspp/message.h index 24d2defe46..e0c98cebf7 100644 --- a/src/libpspp/message.h +++ b/src/libpspp/message.h @@ -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 *); diff --git a/tests/data/dictionary.at b/tests/data/dictionary.at index 86a7e07cb4..e7e589e881 100644 --- a/tests/data/dictionary.at +++ b/tests/data/dictionary.at @@ -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 diff --git a/tests/language/command.at b/tests/language/command.at index 71e50819e0..562c69b130 100644 --- a/tests/language/command.at +++ b/tests/language/command.at @@ -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 ]) diff --git a/tests/language/control/do-if.at b/tests/language/control/do-if.at index 04e965b226..b12cdaa4fd 100644 --- a/tests/language/control/do-if.at +++ b/tests/language/control/do-if.at @@ -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. diff --git a/tests/language/control/do-repeat.at b/tests/language/control/do-repeat.at index b40f7cf541..22ffaa90a5 100644 --- a/tests/language/control/do-repeat.at +++ b/tests/language/control/do-repeat.at @@ -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 diff --git a/tests/language/control/loop.at b/tests/language/control/loop.at index 37125d9399..1e2c84e76d 100644 --- a/tests/language/control/loop.at +++ b/tests/language/control/loop.at @@ -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. diff --git a/tests/language/data-io/inpt-pgm.at b/tests/language/data-io/inpt-pgm.at index 79c71be8b6..a0054588da 100644 --- a/tests/language/data-io/inpt-pgm.at +++ b/tests/language/data-io/inpt-pgm.at @@ -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 diff --git a/tests/language/data-io/matrix-data.at b/tests/language/data-io/matrix-data.at index 9f65d6eca7..1dd0812024 100644 --- a/tests/language/data-io/matrix-data.at +++ b/tests/language/data-io/matrix-data.at @@ -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. diff --git a/tests/language/data-io/save-translate.at b/tests/language/data-io/save-translate.at index d2dd858059..b1f860e8e7 100644 --- a/tests/language/data-io/save-translate.at +++ b/tests/language/data-io/save-translate.at @@ -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)'." ]) diff --git a/tests/language/dictionary/missing-values.at b/tests/language/dictionary/missing-values.at index 15a779180f..a7732c7351 100644 --- a/tests/language/dictionary/missing-values.at +++ b/tests/language/dictionary/missing-values.at @@ -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). diff --git a/tests/language/dictionary/modify-variables.at b/tests/language/dictionary/modify-variables.at index f101ba7211..be79d74877 100644 --- a/tests/language/dictionary/modify-variables.at +++ b/tests/language/dictionary/modify-variables.at @@ -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. ]) diff --git a/tests/language/dictionary/mrsets.at b/tests/language/dictionary/mrsets.at index b72803c19e..f8f21a4c1c 100644 --- a/tests/language/dictionary/mrsets.at +++ b/tests/language/dictionary/mrsets.at @@ -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 diff --git a/tests/language/dictionary/rename-variables.at b/tests/language/dictionary/rename-variables.at index 2bac987693..3009cd948e 100644 --- a/tests/language/dictionary/rename-variables.at +++ b/tests/language/dictionary/rename-variables.at @@ -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 diff --git a/tests/language/dictionary/split-file.at b/tests/language/dictionary/split-file.at index aec4b7449f..2ea856593f 100644 --- a/tests/language/dictionary/split-file.at +++ b/tests/language/dictionary/split-file.at @@ -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 diff --git a/tests/language/dictionary/sys-file-info.at b/tests/language/dictionary/sys-file-info.at index 4f53b4e0ff..f70f466805 100644 --- a/tests/language/dictionary/sys-file-info.at +++ b/tests/language/dictionary/sys-file-info.at @@ -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 diff --git a/tests/language/expressions/parse.at b/tests/language/expressions/parse.at index 96e42c13ea..4f66b85339 100644 --- a/tests/language/expressions/parse.at +++ b/tests/language/expressions/parse.at @@ -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 diff --git a/tests/language/stats/crosstabs.at b/tests/language/stats/crosstabs.at index f51c3345a4..53a7335de9 100644 --- a/tests/language/stats/crosstabs.at +++ b/tests/language/stats/crosstabs.at @@ -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 diff --git a/tests/language/stats/descriptives.at b/tests/language/stats/descriptives.at index c06737d2b2..0c920130c9 100644 --- a/tests/language/stats/descriptives.at +++ b/tests/language/stats/descriptives.at @@ -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 -- 2.30.2