CORRELATIONS: Improve error messages and coding style.
[pspp] / src / language / stats / ctables.c
index 7d2f15df5a853a715c410c1721a857d706203b5a..5359c4cabd3d010a8dd2c6ef55eeb44c31108ee7 100644 (file)
@@ -295,7 +295,7 @@ parse_ctables_summary_function (struct lexer *lexer,
         }
     }
 
-  lex_error (lexer, _("Expecting summary function name."));
+  lex_error (lexer, _("Syntax error expecting summary function name."));
   return false;
 }
 
@@ -1288,9 +1288,20 @@ parse_ctables_format_specifier (struct lexer *lexer, struct fmt_spec *format,
   else
     {
       *is_ctables_format = false;
-      return (parse_format_specifier (lexer, format)
-              && fmt_check_output (format)
-              && fmt_check_type_compat (format, VAL_NUMERIC));
+      if (!parse_format_specifier (lexer, format))
+        return false;
+
+      char *error = fmt_check_output__ (format);
+      if (!error)
+        error = fmt_check_type_compat__ (format, NULL, VAL_NUMERIC);
+      if (error)
+        {
+          lex_next_error (lexer, -1, -1, "%s", error);
+          free (error);
+          return false;
+        }
+
+      return true;
     }
 
   lex_get (lexer);
@@ -1602,8 +1613,7 @@ struct ctables_category
           };
       };
 
-    /* Source location.  This is null for CCT_TOTAL, CCT_VALUE, CCT_LABEL,
-       CCT_FUNCTION, CCT_EXCLUDED_MISSING. */
+    /* Source location (sometimes NULL). */
     struct msg_location *location;
   };
 
@@ -2895,6 +2905,7 @@ struct ctables_cell
     /* In struct ctables_section's 'cells' hmap.  Indexed by all the values in
        all the axes (except the scalar variable, if any). */
     struct hmap_node node;
+    struct ctables_section *section;
 
     /* The areas that contain this cell. */
     uint32_t omit_areas;
@@ -2974,6 +2985,7 @@ struct ctables_table
     enum pivot_axis_type label_axis[PIVOT_N_AXES];
     enum pivot_axis_type clabels_from_axis;
     enum pivot_axis_type clabels_to_axis;
+    int clabels_start_ofs, clabels_end_ofs;
     const struct variable *clabels_example;
     struct hmap clabels_values_map;
     struct ctables_value **clabels_values;
@@ -3079,29 +3091,10 @@ ctables_cell_compare_3way (const void *a_, const void *b_, const void *aux_)
   return 0;
 }
 
-static int
-ctables_cell_compare_leaf_3way (const void *a_, const void *b_,
-                                const void *aux UNUSED)
-{
-  struct ctables_cell *const *ap = a_;
-  struct ctables_cell *const *bp = b_;
-  const struct ctables_cell *a = *ap;
-  const struct ctables_cell *b = *bp;
-
-  for (enum pivot_axis_type axis = 0; axis < PIVOT_N_AXES; axis++)
-    {
-      int al = a->axes[axis].leaf;
-      int bl = b->axes[axis].leaf;
-      if (al != bl)
-        return al > bl ? 1 : -1;
-    }
-  return 0;
-}
-
 static struct ctables_area *
-ctables_area_insert (struct ctables_section *s, struct ctables_cell *cell,
-                       enum ctables_area_type area)
+ctables_area_insert (struct ctables_cell *cell, enum ctables_area_type area)
 {
+  struct ctables_section *s = cell->section;
   size_t hash = 0;
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
     {
@@ -3202,6 +3195,7 @@ ctables_cell_insert__ (struct ctables_section *s, const struct ccase *c,
     }
 
   cell = xmalloc (sizeof *cell);
+  cell->section = s;
   cell->hide = false;
   cell->sv = sv;
   cell->omit_areas = 0;
@@ -3264,7 +3258,7 @@ ctables_cell_insert__ (struct ctables_section *s, const struct ccase *c,
   for (size_t i = 0; i < specs->n; i++)
     ctables_summary_init (&cell->summaries[i], &specs->specs[i]);
   for (enum ctables_area_type at = 0; at < N_CTATS; at++)
-    cell->areas[at] = ctables_area_insert (s, cell, at);
+    cell->areas[at] = ctables_area_insert (cell, at);
   hmap_insert (&s->cells, &cell->node, hash);
   return cell;
 }
@@ -3448,7 +3442,7 @@ struct ctables_value
   };
 
 static struct ctables_value *
-ctables_value_find__ (struct ctables_table *t, const union value *value,
+ctables_value_find__ (const struct ctables_table *t, const union value *value,
                       int width, unsigned int hash)
 {
   struct ctables_value *clv;
@@ -3473,12 +3467,23 @@ ctables_value_insert (struct ctables_table *t, const union value *value,
     }
 }
 
-static struct ctables_value *
-ctables_value_find (struct ctables_table *t,
-                    const union value *value, int width)
+static const struct ctables_value *
+ctables_value_find (const struct ctables_cell *cell)
 {
-  return ctables_value_find__ (t, value, width,
-                               value_hash (value, width, 0));
+  const struct ctables_section *s = cell->section;
+  const struct ctables_table *t = s->table;
+  if (!t->clabels_example)
+    return NULL;
+
+  const struct ctables_nest *clabels_nest = s->nests[t->clabels_from_axis];
+  const struct variable *var = clabels_nest->vars[clabels_nest->n - 1];
+  const union value *value
+    = &cell->axes[t->clabels_from_axis].cvs[clabels_nest->n - 1].value;
+  int width = var_get_width (var);
+  const struct ctables_value *ctv = ctables_value_find__ (
+    t, value, width, value_hash (value, width, 0));
+  assert (ctv != NULL);
+  return ctv;
 }
 
 static int
@@ -4098,6 +4103,8 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
   bool show_totals = false;
   char *total_label = NULL;
   bool totals_before = false;
+  int key_start_ofs = 0;
+  int key_end_ofs = 0;
   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
     {
       if (!c->n_cats && lex_match_id (lexer, "ORDER"))
@@ -4115,7 +4122,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
         }
       else if (!c->n_cats && lex_match_id (lexer, "KEY"))
         {
-          int start_ofs = lex_ofs (lexer) - 1;
+          key_start_ofs = lex_ofs (lexer) - 1;
           lex_match (lexer, T_EQUALS);
           if (lex_match_id (lexer, "VALUE"))
             cat.type = CCT_VALUE;
@@ -4152,9 +4159,13 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
                   bool UNUSED b = lex_force_match (lexer, T_LPAREN);
                   goto error;
                 }
+            }
+          key_end_ofs = lex_ofs (lexer) - 1;
 
-              lex_ofs_error (lexer, start_ofs, lex_ofs (lexer) - 1,
-                              _("Data-dependent sorting is not implemented."));
+          if (cat.type == CCT_FUNCTION)
+            {
+              lex_ofs_error (lexer, key_start_ofs, key_end_ofs,
+                             _("Data-dependent sorting is not implemented."));
               goto error;
             }
         }
@@ -4225,6 +4236,9 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
 
   if (!c->n_cats)
     {
+      if (key_start_ofs)
+        cat.location = lex_ofs_location (lexer, key_start_ofs, key_end_ofs);
+
       if (c->n_cats >= allocated_cats)
         c->cats = x2nrealloc (c->cats, &allocated_cats, sizeof *c->cats);
       c->cats[c->n_cats++] = cat;
@@ -4490,6 +4504,47 @@ all_hidden_vlabels (const struct ctables_table *t, enum pivot_axis_type a)
   return true;
 }
 
+static int
+compare_ints_3way (int a, int b)
+{
+  return a < b ? -1 : a > b;
+}
+
+static int
+ctables_cell_compare_leaf_3way (const void *a_, const void *b_,
+                                const void *aux UNUSED)
+{
+  struct ctables_cell *const *ap = a_;
+  struct ctables_cell *const *bp = b_;
+  const struct ctables_cell *a = *ap;
+  const struct ctables_cell *b = *bp;
+
+  if (a == b)
+    {
+      assert (a_ == b_);
+      return 0;
+    }
+
+  for (enum pivot_axis_type axis = 0; axis < PIVOT_N_AXES; axis++)
+    {
+      int cmp = compare_ints_3way (a->axes[axis].leaf, b->axes[axis].leaf);
+      if (cmp)
+        return cmp;
+    }
+
+  const struct ctables_value *a_ctv = ctables_value_find (a);
+  const struct ctables_value *b_ctv = ctables_value_find (b);
+  if (a_ctv && b_ctv)
+    {
+      int cmp = compare_ints_3way (a_ctv->leaf, b_ctv->leaf);
+      if (cmp)
+        return cmp;
+    }
+  else
+    assert (!a_ctv && !b_ctv);
+  return 0;
+}
+
 static void
 ctables_table_output (struct ctables *ct, struct ctables_table *t)
 {
@@ -4795,6 +4850,7 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
           if (cell->hide)
             continue;
 
+          const struct ctables_value *ctv = ctables_value_find (cell);
           const struct ctables_nest *specs_nest = s->nests[t->summary_axis];
           const struct ctables_summary_spec_set *specs = &specs_nest->specs[cell->sv];
           for (size_t j = 0; j < specs->n; j++)
@@ -4805,16 +4861,8 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
               if (summary_dimension)
                 dindexes[n_dindexes++] = specs->specs[j].axis_idx;
 
-              if (categories_dimension)
-                {
-                  const struct ctables_nest *clabels_nest = s->nests[t->clabels_from_axis];
-                  const struct variable *var = clabels_nest->vars[clabels_nest->n - 1];
-                  const union value *value = &cell->axes[t->clabels_from_axis].cvs[clabels_nest->n - 1].value;
-                  const struct ctables_value *ctv = ctables_value_find (t, value, var_get_width (var));
-                  if (!ctv)
-                    continue;
-                  dindexes[n_dindexes++] = ctv->leaf;
-                }
+              if (ctv)
+                dindexes[n_dindexes++] = ctv->leaf;
 
               for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
                 if (d[a])
@@ -4865,15 +4913,13 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
 }
 
 static bool
-ctables_check_label_position (struct ctables_table *t, enum pivot_axis_type a)
+ctables_check_label_position (struct ctables_table *t, struct lexer *lexer,
+                              enum pivot_axis_type a)
 {
   enum pivot_axis_type label_pos = t->label_axis[a];
   if (label_pos == a)
     return true;
 
-  const char *subcommand_name = a == PIVOT_AXIS_ROW ? "ROWLABELS" : "COLLABELS";
-  const char *pos_name = label_pos == PIVOT_AXIS_LAYER ? "LAYER" : "OPPOSITE";
-
   const struct ctables_stack *stack = &t->stacks[a];
   if (!stack->n)
     return true;
@@ -4892,17 +4938,29 @@ ctables_check_label_position (struct ctables_table *t, enum pivot_axis_type a)
   for (size_t i = 0; i < c0->n_cats; i++)
     if (c0->cats[i].type == CCT_FUNCTION)
       {
-        msg (SE, _("%s=%s is not allowed with sorting based "
-                   "on a summary function."),
-             subcommand_name, pos_name);
+        msg (SE, _("Category labels may not be moved to another axis when "
+                   "sorting by a summary function."));
+        lex_ofs_msg (lexer, SN, t->clabels_start_ofs, t->clabels_end_ofs,
+                     _("This syntax moves category labels to another axis."));
+        msg_at (SN, c0->cats[i].location,
+                _("This syntax requests sorting by a summary function."));
         return false;
       }
-  if (n0->n - 1 == n0->scale_idx)
+
+  for (size_t i = 0; i < stack->n; i++)
     {
-      msg (SE, _("%s=%s requires the variables to be moved to be categorical, "
-                 "but %s is a scale variable."),
-           subcommand_name, pos_name, var_get_name (v0));
-      return false;
+      const struct ctables_nest *ni = &stack->nests[i];
+      assert (ni->n > 0);
+      const struct variable *vi = ni->vars[ni->n - 1];
+      if (n0->n - 1 == ni->scale_idx)
+        {
+          msg (SE, _("To move category labels from one axis to another, "
+                     "the variables whose labels are to be moved must be "
+                     "categorical, but %s is scale."), var_get_name (vi));
+          lex_ofs_msg (lexer, SN, t->clabels_start_ofs, t->clabels_end_ofs,
+                       _("This syntax moves category labels to another axis."));
+          return false;
+        }
     }
 
   for (size_t i = 1; i < stack->n; i++)
@@ -4912,41 +4970,39 @@ ctables_check_label_position (struct ctables_table *t, enum pivot_axis_type a)
       const struct variable *vi = ni->vars[ni->n - 1];
       struct ctables_categories *ci = t->categories[var_get_dict_index (vi)];
 
-      if (ni->n - 1 == ni->scale_idx)
-        {
-          msg (SE, _("%s=%s requires the variables to be moved to be "
-                     "categorical, but %s is a scale variable."),
-               subcommand_name, pos_name, var_get_name (vi));
-          return false;
-        }
       if (var_get_width (v0) != var_get_width (vi))
         {
-          msg (SE, _("%s=%s requires the variables to be "
-                     "moved to have the same width, but %s has "
-                     "width %d and %s has width %d."),
-               subcommand_name, pos_name,
+          msg (SE, _("To move category labels from one axis to another, "
+                     "the variables whose labels are to be moved must all "
+                     "have the same width, but %s has width %d and %s has "
+                     "width %d."),
                var_get_name (v0), var_get_width (v0),
                var_get_name (vi), var_get_width (vi));
+          lex_ofs_msg (lexer, SN, t->clabels_start_ofs, t->clabels_end_ofs,
+                       _("This syntax moves category labels to another axis."));
           return false;
         }
       if (!val_labs_equal (var_get_value_labels (v0),
                            var_get_value_labels (vi)))
         {
-          msg (SE, _("%s=%s requires the variables to be "
-                     "moved to have the same value labels, but %s "
-                     "and %s have different value labels."),
-               subcommand_name, pos_name,
+          msg (SE, _("To move category labels from one axis to another, "
+                     "the variables whose labels are to be moved must all "
+                     "have the same value labels, but %s and %s have "
+                     "different value labels."),
                var_get_name (v0), var_get_name (vi));
+          lex_ofs_msg (lexer, SN, t->clabels_start_ofs, t->clabels_end_ofs,
+                       _("This syntax moves category labels to another axis."));
           return false;
         }
       if (!ctables_categories_equal (c0, ci))
         {
-          msg (SE, _("%s=%s requires the variables to be "
-                     "moved to have the same category "
-                     "specifications, but %s and %s have different "
-                     "category specifications."),
-               subcommand_name, pos_name,
+          msg (SE, _("To move category labels from one axis to another, "
+                     "the variables whose labels are to be moved must all "
+                     "have the same category specifications, but %s and %s "
+                     "have different category specifications."),
                var_get_name (v0), var_get_name (vi));
+          lex_ofs_msg (lexer, SN, t->clabels_start_ofs, t->clabels_end_ofs,
+                       _("This syntax moves category labels to another axis."));
           return false;
         }
     }
@@ -5023,7 +5079,7 @@ enumerate_sum_vars (const struct ctables_axis *a,
 }
 
 static bool
-ctables_prepare_table (struct ctables_table *t)
+ctables_prepare_table (struct ctables_table *t, struct lexer *lexer)
 {
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
     if (t->axes[a])
@@ -5260,8 +5316,8 @@ ctables_prepare_table (struct ctables_table *t)
   enumerate_sum_vars (t->axes[t->summary_axis],
                       &t->sum_vars, &t->n_sum_vars, &allocated_sum_vars);
 
-  return (ctables_check_label_position (t, PIVOT_AXIS_ROW)
-          && ctables_check_label_position (t, PIVOT_AXIS_COLUMN));
+  return (ctables_check_label_position (t, lexer, PIVOT_AXIS_ROW)
+          && ctables_check_label_position (t, lexer, PIVOT_AXIS_COLUMN));
 }
 
 static void
@@ -5654,9 +5710,7 @@ ctables_parse_pcompute (struct lexer *lexer, struct dictionary *dict,
   char *name = ss_xstrdup (lex_tokss (lexer));
 
   lex_get (lexer);
-  if (!lex_force_match (lexer, T_EQUALS)
-      || !lex_force_match_id (lexer, "EXPR")
-      || !lex_force_match (lexer, T_LPAREN))
+  if (!lex_force_match_phrase (lexer, "=EXPR("))
     {
       free (name);
       return false;
@@ -5772,7 +5826,8 @@ ctables_parse_pproperties (struct lexer *lexer, struct ctables *ct)
         = ctables_find_postcompute (ct, lex_tokcstr (lexer));
       if (!pc)
         {
-          msg (SE, _("Unknown computed category &%s."), lex_tokcstr (lexer));
+          lex_error (lexer, _("Unknown computed category &%s."),
+                     lex_tokcstr (lexer));
           goto error;
         }
       lex_get (lexer);
@@ -5989,6 +6044,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
           double widths[2] = { SYSMIS, SYSMIS };
           double units_per_inch = 72.0;
 
+          int start_ofs = lex_ofs (lexer);
           while (lex_token (lexer) != T_SLASH)
             {
               if (lex_match_id (lexer, "MINCOLWIDTH"))
@@ -6059,7 +6115,9 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
           if (widths[0] != SYSMIS && widths[1] != SYSMIS
               && widths[0] > widths[1])
             {
-              msg (SE, _("MINCOLWIDTH must not be greater than MAXCOLWIDTH."));
+              lex_ofs_error (lexer, start_ofs, lex_ofs (lexer) - 1,
+                             _("MINCOLWIDTH must not be greater than "
+                               "MAXCOLWIDTH."));
               goto error;
             }
 
@@ -6172,6 +6230,15 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
           lex_error_expecting (lexer, "FORMAT", "VLABELS", "MRSETS",
                                "SMISSING", "PCOMPUTE", "PPROPERTIES",
                                "WEIGHT", "HIDESMALLCOUNTS", "TABLE");
+          if (lex_match_id (lexer, "SLABELS")
+              || lex_match_id (lexer, "CLABELS")
+              || lex_match_id (lexer, "CRITERIA")
+              || lex_match_id (lexer, "CATEGORIES")
+              || lex_match_id (lexer, "TITLES")
+              || lex_match_id (lexer, "SIGTEST")
+              || lex_match_id (lexer, "COMPARETEST"))
+            lex_next_msg (lexer, SN, -1, -1,
+                          _("TABLE must appear before this subcommand."));
           goto error;
         }
 
@@ -6315,7 +6382,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
 
       if (lex_token (lexer) == T_ENDCMD)
         {
-          if (!ctables_prepare_table (t))
+          if (!ctables_prepare_table (t, lexer))
             goto error;
           break;
         }
@@ -6358,6 +6425,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
             }
           else if (lex_match_id (lexer, "CLABELS"))
             {
+              int start_ofs = lex_ofs (lexer) - 1;
               if (lex_match_id (lexer, "AUTO"))
                 {
                   t->label_axis[PIVOT_AXIS_ROW] = PIVOT_AXIS_ROW;
@@ -6395,6 +6463,24 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
                                        "COLLABELS");
                   goto error;
                 }
+              int end_ofs = lex_ofs (lexer) - 1;
+
+              if (t->label_axis[PIVOT_AXIS_ROW] != PIVOT_AXIS_ROW
+                  && t->label_axis[PIVOT_AXIS_COLUMN] != PIVOT_AXIS_COLUMN)
+                {
+                  msg (SE, _("ROWLABELS and COLLABELS may not both be "
+                             "specified."));
+
+                  lex_ofs_msg (lexer, SN, t->clabels_start_ofs,
+                               t->clabels_end_ofs,
+                               _("This is the first specification."));
+                  lex_ofs_msg (lexer, SN, start_ofs, end_ofs,
+                               _("This is the second specification."));
+                  goto error;
+                }
+
+              t->clabels_start_ofs = start_ofs;
+              t->clabels_end_ofs = end_ofs;
             }
           else if (lex_match_id (lexer, "CRITERIA"))
             {
@@ -6418,11 +6504,11 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
               do
                 {
                   char **textp;
-                  if (lex_match_id (lexer, "CAPTION"))
+                  if (lex_match_id (lexer, "CAPTIONS"))
                     textp = &t->caption;
-                  else if (lex_match_id (lexer, "CORNER"))
+                  else if (lex_match_id (lexer, "CORNERS"))
                     textp = &t->corner;
-                  else if (lex_match_id (lexer, "TITLE"))
+                  else if (lex_match_id (lexer, "TITLES"))
                     textp = &t->title;
                   else
                     {
@@ -6512,7 +6598,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
             }
           else if (lex_match_id (lexer, "COMPARETEST"))
             {
-              int start_ofs = lex_ofs (lexer);
+              int start_ofs = lex_ofs (lexer) - 1;
               if (!t->pairwise)
                 {
                   t->pairwise = xmalloc (sizeof *t->pairwise);
@@ -6662,6 +6748,16 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
               lex_error_expecting (lexer, "TABLE", "SLABELS", "CLABELS",
                                    "CRITERIA", "CATEGORIES", "TITLES",
                                    "SIGTEST", "COMPARETEST");
+              if (lex_match_id (lexer, "FORMAT")
+                  || lex_match_id (lexer, "VLABELS")
+                  || lex_match_id (lexer, "MRSETS")
+                  || lex_match_id (lexer, "SMISSING")
+                  || lex_match_id (lexer, "PCOMPUTE")
+                  || lex_match_id (lexer, "PPROPERTIES")
+                  || lex_match_id (lexer, "WEIGHT")
+                  || lex_match_id (lexer, "HIDESMALLCOUNTS"))
+                lex_next_msg (lexer, SN, -1, -1,
+                              _("This subcommand must appear before TABLE."));
               goto error;
             }
 
@@ -6670,19 +6766,12 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
         }
 
       if (t->label_axis[PIVOT_AXIS_ROW] != PIVOT_AXIS_ROW)
-        {
-          t->clabels_from_axis = PIVOT_AXIS_ROW;
-          if (t->label_axis[PIVOT_AXIS_COLUMN] != PIVOT_AXIS_COLUMN)
-            {
-              msg (SE, _("ROWLABELS and COLLABELS may not both be specified."));
-              goto error;
-            }
-        }
+        t->clabels_from_axis = PIVOT_AXIS_ROW;
       else if (t->label_axis[PIVOT_AXIS_COLUMN] != PIVOT_AXIS_COLUMN)
         t->clabels_from_axis = PIVOT_AXIS_COLUMN;
       t->clabels_to_axis = t->label_axis[t->clabels_from_axis];
 
-      if (!ctables_prepare_table (t))
+      if (!ctables_prepare_table (t, lexer))
         goto error;
     }
   while (lex_token (lexer) != T_ENDCMD);