CTABLES: Fix treatment of multiline titles.
[pspp] / src / language / stats / ctables.c
index d746a3356f8ce50b851f7d98b1c25dda989b752b..565c8ce1db10cbd512683973ee3c90968f3f24d2 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;
 }
 
@@ -890,7 +890,8 @@ ctables_pcexpr_parse_primary (struct lexer *lexer, struct dictionary *dict)
         }
       else
         {
-          lex_error (lexer, NULL);
+          lex_error (lexer,
+                     _("Syntax error expecting number or string or range."));
           return NULL;
         }
 
@@ -920,7 +921,7 @@ ctables_pcexpr_parse_primary (struct lexer *lexer, struct dictionary *dict)
     }
   else
     {
-      lex_error (lexer, NULL);
+      lex_error (lexer, _("Syntax error in postcompute expression."));
       return NULL;
     }
 
@@ -1288,9 +1289,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);
@@ -1533,7 +1545,6 @@ struct ctables_categories
     size_t n_refs;
     struct ctables_category *cats;
     size_t n_cats;
-    bool show_empty;
   };
 
 struct ctables_category
@@ -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;
   };
 
@@ -1723,7 +1733,7 @@ static bool
 ctables_categories_equal (const struct ctables_categories *a,
                           const struct ctables_categories *b)
 {
-  if (a->n_cats != b->n_cats || a->show_empty != b->show_empty)
+  if (a->n_cats != b->n_cats)
     return false;
 
   for (size_t i = 0; i < a->n_cats; i++)
@@ -1873,7 +1883,7 @@ ctables_table_parse_explicit_category (struct lexer *lexer,
     }
   else
     {
-      lex_error (lexer, NULL);
+      lex_error (lexer, _("Syntax error expecting category specification."));
       return false;
     }
 
@@ -2975,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;
@@ -2983,6 +2994,7 @@ struct ctables_table
     /* Indexed by variable dictionary index. */
     struct ctables_categories **categories;
     size_t n_categories;
+    bool *show_empty;
 
     double cilevel;
 
@@ -3492,8 +3504,9 @@ ctables_sort_clabels_values (struct ctables_table *t)
   const struct variable *v0 = t->clabels_example;
   int width = var_get_width (v0);
 
-  struct ctables_categories *c0 = t->categories[var_get_dict_index (v0)];
-  if (c0->show_empty)
+  size_t i0 = var_get_dict_index (v0);
+  struct ctables_categories *c0 = t->categories[i0];
+  if (t->show_empty[i0])
     {
       const struct val_labs *val_labs = var_get_value_labels (v0);
       for (const struct val_lab *vl = val_labs_first (val_labs); vl;
@@ -3942,6 +3955,7 @@ ctables_table_destroy (struct ctables_table *t)
   for (size_t i = 0; i < t->n_categories; i++)
     ctables_categories_unref (t->categories[i]);
   free (t->categories);
+  free (t->show_empty);
 
   for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
     {
@@ -4051,20 +4065,16 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
            & (FMT_CAT_DATE | FMT_CAT_TIME | FMT_CAT_DATE_COMPONENT)));
 
   struct ctables_categories *c = xmalloc (sizeof *c);
-  *c = (struct ctables_categories) { .n_refs = n_vars, .show_empty = true };
-  for (size_t i = 0; i < n_vars; i++)
-    {
-      struct ctables_categories **cp
-        = &t->categories[var_get_dict_index (vars[i])];
-      ctables_categories_unref (*cp);
-      *cp = c;
-    }
+  *c = (struct ctables_categories) { .n_refs = 1 };
+
+  bool set_categories = false;
 
   size_t allocated_cats = 0;
   int cats_start_ofs = -1;
   int cats_end_ofs = -1;
   if (lex_match (lexer, T_LBRACK))
     {
+      set_categories = true;
       cats_start_ofs = lex_ofs (lexer);
       do
         {
@@ -4092,10 +4102,13 @@ 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"))
         {
+          set_categories = true;
           lex_match (lexer, T_EQUALS);
           if (lex_match_id (lexer, "A"))
             cat.sort_ascending = true;
@@ -4109,7 +4122,8 @@ 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;
+          set_categories = true;
+          key_start_ofs = lex_ofs (lexer) - 1;
           lex_match (lexer, T_EQUALS);
           if (lex_match_id (lexer, "VALUE"))
             cat.type = CCT_VALUE;
@@ -4146,14 +4160,19 @@ 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;
             }
         }
       else if (!c->n_cats && lex_match_id (lexer, "MISSING"))
         {
+          set_categories = true;
           lex_match (lexer, T_EQUALS);
           if (lex_match_id (lexer, "INCLUDE"))
             cat.include_missing = true;
@@ -4167,6 +4186,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
         }
       else if (lex_match_id (lexer, "TOTAL"))
         {
+          set_categories = true;
           lex_match (lexer, T_EQUALS);
           if (!parse_bool (lexer, &show_totals))
             goto error;
@@ -4196,15 +4216,20 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
       else if (lex_match_id (lexer, "EMPTY"))
         {
           lex_match (lexer, T_EQUALS);
+
+          bool show_empty;
           if (lex_match_id (lexer, "INCLUDE"))
-            c->show_empty = true;
+            show_empty = true;
           else if (lex_match_id (lexer, "EXCLUDE"))
-            c->show_empty = false;
+            show_empty = false;
           else
             {
               lex_error_expecting (lexer, "INCLUDE", "EXCLUDE");
               goto error;
             }
+
+          for (size_t i = 0; i < n_vars; i++)
+            t->show_empty[var_get_dict_index (vars[i])] = show_empty;
         }
       else
         {
@@ -4219,6 +4244,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;
@@ -4371,10 +4399,22 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
         }
     }
 
+  if (set_categories)
+    for (size_t i = 0; i < n_vars; i++)
+      {
+        struct ctables_categories **cp
+          = &t->categories[var_get_dict_index (vars[i])];
+        ctables_categories_unref (*cp);
+        *cp = c;
+        c->n_refs++;
+      }
+
+  ctables_categories_unref (c);
   free (vars);
   return true;
 
 error:
+  ctables_categories_unref (c);
   free (vars);
   return false;
 }
@@ -4893,15 +4933,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;
@@ -4920,17 +4958,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++)
@@ -4940,41 +4990,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;
         }
     }
@@ -5051,7 +5099,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])
@@ -5288,8 +5336,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
@@ -5443,9 +5491,9 @@ ctables_section_add_empty_categories (struct ctables_section *s)
         if (k != s->nests[a]->scale_idx)
           {
             const struct variable *var = s->nests[a]->vars[k];
-            const struct ctables_categories *cats = s->table->categories[
-              var_get_dict_index (var)];
-            if (cats->show_empty)
+            size_t idx = var_get_dict_index (var);
+            const struct ctables_categories *cats = s->table->categories[idx];
+            if (s->table->show_empty[idx])
               {
                 show_empty = true;
                 ctables_add_category_occurrences (var, &s->occurrences[a][k], cats);
@@ -5598,14 +5646,7 @@ ctables_execute (struct dataset *ds, struct casereader *input,
   while (casegrouper_get_next_group (grouper, &group))
     {
       if (splitting)
-        {
-          struct ccase *c = casereader_peek (group, 0);
-          if (c != NULL)
-            {
-              output_split_file_values (ds, c);
-              case_unref (c);
-            }
-        }
+        output_split_file_values_peek (ds, group);
 
       bool warn_on_invalid = true;
       for (struct ccase *c = casereader_read (group); c;
@@ -5682,9 +5723,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;
@@ -5800,7 +5839,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);
@@ -6017,6 +6057,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"))
@@ -6087,7 +6128,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;
             }
 
@@ -6200,6 +6243,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;
         }
 
@@ -6227,7 +6279,6 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
         .n_refs = n_vars,
         .cats = cat,
         .n_cats = 1,
-        .show_empty = true,
       };
 
       struct ctables_categories **categories = xnmalloc (n_vars,
@@ -6235,6 +6286,9 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
       for (size_t i = 0; i < n_vars; i++)
         categories[i] = c;
 
+      bool *show_empty = xmalloc (n_vars);
+      memset (show_empty, true, n_vars);
+
       struct ctables_table *t = xmalloc (sizeof *t);
       *t = (struct ctables_table) {
         .ctables = ct,
@@ -6250,6 +6304,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
         .clabels_to_axis = PIVOT_AXIS_LAYER,
         .categories = categories,
         .n_categories = n_vars,
+        .show_empty = show_empty,
         .cilevel = 95,
       };
       ct->tables[ct->n_tables++] = t;
@@ -6343,7 +6398,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;
         }
@@ -6386,6 +6441,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;
@@ -6423,6 +6479,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"))
             {
@@ -6446,11 +6520,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
                     {
@@ -6463,7 +6537,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
                   while (lex_is_string (lexer))
                     {
                       if (!ds_is_empty (&s))
-                        ds_put_byte (&s, ' ');
+                        ds_put_byte (&s, '\n');
                       put_title_text (&s, lex_tokss (lexer), now,
                                       lexer, dataset_dict (ds),
                                       expr_start, expr_end);
@@ -6540,7 +6614,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);
@@ -6690,6 +6764,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;
             }
 
@@ -6698,19 +6782,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);