basic parsing works
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 27 Dec 2021 20:36:52 +0000 (12:36 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 30 Dec 2021 22:10:12 +0000 (14:10 -0800)
src/language/stats/ctables.c
tests/automake.mk

index deb98beb9c0115cbd792fb6cfabbe3655c0bf094..585753664a549703c1acf09b93b6c3bc3f9d6872 100644 (file)
 
 #include "data/dataset.h"
 #include "data/dictionary.h"
+#include "data/mrset.h"
 #include "language/command.h"
 #include "language/lexer/format-parser.h"
 #include "language/lexer/lexer.h"
 #include "language/lexer/variable-parser.h"
+#include "libpspp/assertion.h"
 #include "libpspp/hmap.h"
 #include "libpspp/message.h"
 #include "output/pivot-table.h"
@@ -31,6 +33,7 @@
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
+#define N_(msgid) (msgid)
 
 enum ctables_vlabel
   {
@@ -67,6 +70,7 @@ struct ctables
        format.  Otherwise, this string is displayed. */
     char *missing;
 
+    /* Indexed by variable dictionary index. */
     enum ctables_vlabel *vlabels;
 
     bool mrsets_count_duplicates; /* MRSETS. */
@@ -218,89 +222,97 @@ struct ctables_axis
 
 static void ctables_axis_destroy (struct ctables_axis *);
 
+enum ctables_format
+  {
+    CTF_COUNT,
+    CTF_PERCENT,
+    CTF_GENERAL
+  };
+
 #define SUMMARIES                                                       \
     /* All variables. */                                                \
-    S(CTSF_COUNT, "COUNT")                                              \
-    S(CTSF_ECOUNT, "ECOUNT")                                            \
-    S(CTSF_ROWPCT_COUNT, "ROWPCT.COUNT")                                \
-    S(CTSF_COLPCT_COUNT, "COLPCT.COUNT")                                \
-    S(CTSF_TABLEPCT_COUNT, "TABLEPCT.COUNT")                            \
-    S(CTSF_SUBTABLEPCT_COUNT, "SUBTABLEPCT.COUNT")                      \
-    S(CTSF_LAYERPCT_COUNT, "LAYERPCT.COUNT")                            \
-    S(CTSF_LAYERROWPCT_COUNT, "LAYERROWPCT.COUNT")                      \
-    S(CTSF_LAYERCOLPCT_COUNT, "LAYERCOLPCT.COUNT")                      \
-    S(CTSF_ROWPCT_VALIDN, "ROWPCT.VALIDN")                              \
-    S(CTSF_COLPCT_VALIDN, "COLPCT.VALIDN")                              \
-    S(CTSF_TABLEPCT_VALIDN, "TABLEPCT.VALIDN")                          \
-    S(CTSF_SUBTABLEPCT_VALIDN, "SUBTABLEPCT.VALIDN")                    \
-    S(CTSF_LAYERPCT_VALIDN, "LAYERPCT.VALIDN")                          \
-    S(CTSF_LAYERROWPCT_VALIDN, "LAYERROWPCT.VALIDN")                    \
-    S(CTSF_LAYERCOLPCT_VALIDN, "LAYERCOLPCT.VALIDN")                    \
-    S(CTSF_ROWPCT_TOTALN, "ROWPCT.TOTALN")                              \
-    S(CTSF_COLPCT_TOTALN, "COLPCT.TOTALN")                              \
-    S(CTSF_TABLEPCT_TOTALN, "TABLEPCT.TOTALN")                          \
-    S(CTSF_SUBTABLEPCT_TOTALN, "SUBTABLEPCT.TOTALN")                    \
-    S(CTSF_LAYERPCT_TOTALN, "LAYERPCT.TOTALN")                          \
-    S(CTSF_LAYERROWPCT_TOTALN, "LAYERROWPCT.TOTALN")                    \
-    S(CTSF_LAYERCOLPCT_TOTALN, "LAYERCOLPCT.TOTALN")                    \
+    S(CTSF_COUNT, "COUNT", N_("Count"), CTF_COUNT)                      \
+    S(CTSF_ECOUNT, "ECOUNT", N_("Adjusted Count"), CTF_COUNT)           \
+    S(CTSF_ROWPCT_COUNT, "ROWPCT.COUNT", N_("Row %"), CTF_PERCENT)      \
+    S(CTSF_COLPCT_COUNT, "COLPCT.COUNT", N_("Column %"), CTF_PERCENT)   \
+    S(CTSF_TABLEPCT_COUNT, "TABLEPCT.COUNT", N_("Table %"), CTF_PERCENT) \
+    S(CTSF_SUBTABLEPCT_COUNT, "SUBTABLEPCT.COUNT", N_("Subtable %"), CTF_PERCENT) \
+    S(CTSF_LAYERPCT_COUNT, "LAYERPCT.COUNT", N_("Layer %"), CTF_PERCENT) \
+    S(CTSF_LAYERROWPCT_COUNT, "LAYERROWPCT.COUNT", N_("Layer Row %"), CTF_PERCENT) \
+    S(CTSF_LAYERCOLPCT_COUNT, "LAYERCOLPCT.COUNT", N_("Layer Column %"), CTF_PERCENT) \
+    S(CTSF_ROWPCT_VALIDN, "ROWPCT.VALIDN", N_("Row Valid N %"), CTF_PERCENT) \
+    S(CTSF_COLPCT_VALIDN, "COLPCT.VALIDN", N_("Column Valid N %"), CTF_PERCENT) \
+    S(CTSF_TABLEPCT_VALIDN, "TABLEPCT.VALIDN", N_("Table Valid N %"), CTF_PERCENT) \
+    S(CTSF_SUBTABLEPCT_VALIDN, "SUBTABLEPCT.VALIDN", N_("Subtable Valid N %"), CTF_PERCENT) \
+    S(CTSF_LAYERPCT_VALIDN, "LAYERPCT.VALIDN", N_("Layer Valid N %"), CTF_PERCENT) \
+    S(CTSF_LAYERROWPCT_VALIDN, "LAYERROWPCT.VALIDN", N_("Layer Row Valid N %"), CTF_PERCENT) \
+    S(CTSF_LAYERCOLPCT_VALIDN, "LAYERCOLPCT.VALIDN", N_("Layer Column Valid N %"), CTF_PERCENT) \
+    S(CTSF_ROWPCT_TOTALN, "ROWPCT.TOTALN", N_("Row Total N %"), CTF_PERCENT) \
+    S(CTSF_COLPCT_TOTALN, "COLPCT.TOTALN", N_("Column Total N %"), CTF_PERCENT) \
+    S(CTSF_TABLEPCT_TOTALN, "TABLEPCT.TOTALN", N_("Table Total N %"), CTF_PERCENT) \
+    S(CTSF_SUBTABLEPCT_TOTALN, "SUBTABLEPCT.TOTALN", N_("Subtable Total N %"), CTF_PERCENT) \
+    S(CTSF_LAYERPCT_TOTALN, "LAYERPCT.TOTALN", N_("Layer Total N %"), CTF_PERCENT) \
+    S(CTSF_LAYERROWPCT_TOTALN, "LAYERROWPCT.TOTALN", N_("Layer Row Total N %"), CTF_PERCENT) \
+    S(CTSF_LAYERCOLPCT_TOTALN, "LAYERCOLPCT.TOTALN", N_("Layer Column Total N %"), CTF_PERCENT) \
                                                                         \
     /* Scale variables, totals, and subtotals. */                       \
-    S(CTSF_MAXIMUM, "!MAXIMUM")                                         \
-    S(CTSF_MEAN, "!MEAN")                                               \
-    S(CTSF_MEDIAN, "!MEDIAN")                                           \
-    S(CTSF_MINIMUM, "!MINIMUM")                                         \
-    S(CTSF_MISSING, "!MISSING")                                         \
-    S(CTSF_MODE, "!MODE")                                               \
-    S(CTSF_PTILE, "!PTILE")                                             \
-    S(CTSF_RANGE, "!RANGE")                                             \
-    S(CTSF_SEMAN, "!SEMAN")                                             \
-    S(CTSF_STDDEV, "!STDDEV")                                           \
-    S(CTSF_SUM, "!SUM")                                                 \
-    S(CSTF_TOTALN, "!TOTALN")                                           \
-    S(CTSF_ETOTALN, "!ETOTALN")                                         \
-    S(CTSF_VALIDN, "!VALIDN")                                           \
-    S(CTSF_EVALIDN, "!EVALIDN")                                         \
-    S(CTSF_VARIANCE, "!VARIANCE")                                       \
-    S(CTSF_ROWPCT_SUM, "ROWPCT.SUM")                                    \
-    S(CTSF_COLPCT_SUM, "COLPCT.SUM")                                    \
-    S(CTSF_TABLEPCT_SUM, "TABLEPCT.SUM")                                \
-    S(CTSF_SUBTABLEPCT_SUM, "SUBTABLEPCT.SUM")                          \
-    S(CTSF_LAYERPCT_SUM, "LAYERPCT.SUM")                                \
-    S(CTSF_LAYERROWPCT_SUM, "LAYERROWPCT.SUM")                          \
-    S(CTSF_LAYERCOLPCT_SUM, "LAYERCOLPCT.SUM")                          \
+    S(CTSF_MAXIMUM, "MAXIMUM", N_("Maximum"), CTF_GENERAL)              \
+    S(CTSF_MEAN, "MEAN", N_("Mean"), CTF_GENERAL)                       \
+    S(CTSF_MEDIAN, "MEDIAN", N_("Median"), CTF_GENERAL)                 \
+    S(CTSF_MINIMUM, "MINIMUM", N_("Minimum"), CTF_GENERAL)              \
+    S(CTSF_MISSING, "MISSING", N_("Missing"), CTF_GENERAL)              \
+    S(CTSF_MODE, "MODE", N_("Mode"), CTF_GENERAL)                       \
+    S(CTSF_PTILE, "PTILE", N_("Percentile"), CTF_GENERAL)               \
+    S(CTSF_RANGE, "RANGE", N_("Range"), CTF_GENERAL)                    \
+    S(CTSF_SEMEAN, "SEMEAN", N_("Std Error of Mean"), CTF_GENERAL)      \
+    S(CTSF_STDDEV, "STDDEV", N_("Std Deviation"), CTF_GENERAL)          \
+    S(CTSF_SUM, "SUM", N_("Sum"), CTF_GENERAL)                          \
+    S(CSTF_TOTALN, "TOTALN", N_("Total N"), CTF_COUNT)                  \
+    S(CTSF_ETOTALN, "ETOTALN", N_("Adjusted Total N"), CTF_COUNT)       \
+    S(CTSF_VALIDN, "VALIDN", N_("Valid N"), CTF_COUNT)                  \
+    S(CTSF_EVALIDN, "EVALIDN", N_("Adjusted Valid N"), CTF_COUNT)       \
+    S(CTSF_VARIANCE, "VARIANCE", N_("Variance"), CTF_GENERAL)           \
+    S(CTSF_ROWPCT_SUM, "ROWPCT.SUM", N_("Row Sum %"), CTF_PERCENT)      \
+    S(CTSF_COLPCT_SUM, "COLPCT.SUM", N_("Column Sum %"), CTF_PERCENT)   \
+    S(CTSF_TABLEPCT_SUM, "TABLEPCT.SUM", N_("Table Sum %"), CTF_PERCENT) \
+    S(CTSF_SUBTABLEPCT_SUM, "SUBTABLEPCT.SUM", N_("Subtable Sum %"), CTF_PERCENT) \
+    S(CTSF_LAYERPCT_SUM, "LAYERPCT.SUM", N_("Layer Sum %"), CTF_PERCENT) \
+    S(CTSF_LAYERROWPCT_SUM, "LAYERROWPCT.SUM", N_("Layer Row Sum %"), CTF_PERCENT) \
+    S(CTSF_LAYERCOLPCT_SUM, "LAYERCOLPCT.SUM", N_("Layer Column Sum %"), CTF_PERCENT) \
                                                                         \
     /* Multiple response sets. */                                       \
-    S(CTSF_ROWPCT_RESPONSES, "ROWPCT.RESPONSES")                        \
-    S(CTSF_COLPCT_RESPONSES, "COLPCT.RESPONSES")                        \
-    S(CTSF_TABLEPCT_RESPONSES, "TABLEPCT.RESPONSES")                    \
-    S(CTSF_SUBTABLEPCT_RESPONSES, "SUBTABLEPCT.RESPONSES")              \
-    S(CTSF_LAYERPCT_RESPONSES, "LAYERPCT.RESPONSES")                    \
-    S(CTSF_LAYERROWPCT_RESPONSES, "LAYERROWPCT.RESPONSES")              \
-    S(CTSF_LAYERCOLPCT_RESPONSES, "LAYERCOLPCT.RESPONSES")              \
-    S(CTSF_ROWPCT_RESPONSES_COUNT, "ROWPCT.RESPONSES.COUNT")            \
-    S(CTSF_COLPCT_RESPONSES_COUNT, "COLPCT.RESPONSES.COUNT")            \
-    S(CTSF_TABLEPCT_RESPONSES_COUNT, "TABLEPCT.RESPONSES.COUNT")        \
-    S(CTSF_SUBTABLEPCT_RESPONSES_COUNT, "SUBTABLEPCT.RESPONSES.COUNT")  \
-    S(CTSF_LAYERPCT_RESPONSES_COUNT, "LAYERPCT.RESPONSES.COUNT")        \
-    S(CTSF_LAYERROWPCT_RESPONSES_COUNT, "LAYERROWPCT.RESPONSES.COUNT")  \
-    S(CTSF_LAYERCOLPCT_RESPONSES_COUNT, "LAYERCOLPCT.RESPONSES.COUNT")  \
-    S(CTSF_ROWPCT_COUNT_RESPONSES, "ROWPCT.COUNT.RESPONSES")            \
-    S(CTSF_COLPCT_COUNT_RESPONSES, "COLPCT.COUNT.RESPONSES")            \
-    S(CTSF_TABLEPCT_COUNT_RESPONSES, "TABLEPCT.COUNT.RESPONSES")        \
-    S(CTSF_SUBTABLEPCT_COUNT_RESPONSES, "SUBTABLEPCT.COUNT.RESPONSES")  \
-    S(CTSF_LAYERPCT_COUNT_RESPONSES, "LAYERPCT.COUNT.RESPONSES")        \
-    S(CTSF_LAYERROWPCT_COUNT_RESPONSES, "LAYERROWPCT.COUNT.RESPONSES")  \
-    S(CTSF_LAYERCOLPCT_COUNT_RESPONSES, "LAYERCOLPCT.COUNT.RESPONSES")
+    S(CTSF_RESPONSES, "RESPONSES", N_("Responses"), CTF_COUNT)           \
+    S(CTSF_ROWPCT_RESPONSES, "ROWPCT.RESPONSES", N_("Row Responses %"), CTF_PERCENT) \
+    S(CTSF_COLPCT_RESPONSES, "COLPCT.RESPONSES", N_("Column Responses %"), CTF_PERCENT) \
+    S(CTSF_TABLEPCT_RESPONSES, "TABLEPCT.RESPONSES", N_("Table Responses %"), CTF_PERCENT) \
+    S(CTSF_SUBTABLEPCT_RESPONSES, "SUBTABLEPCT.RESPONSES", N_("Subtable Responses %"), CTF_PERCENT) \
+    S(CTSF_LAYERPCT_RESPONSES, "LAYERPCT.RESPONSES", N_("Layer Responses %"), CTF_PERCENT) \
+    S(CTSF_LAYERROWPCT_RESPONSES, "LAYERROWPCT.RESPONSES", N_("Layer Row Responses %"), CTF_PERCENT) \
+    S(CTSF_LAYERCOLPCT_RESPONSES, "LAYERCOLPCT.RESPONSES", N_("Layer Column Responses %"), CTF_PERCENT) \
+    S(CTSF_ROWPCT_RESPONSES_COUNT, "ROWPCT.RESPONSES.COUNT", N_("Row Responses % (Base: Count)"), CTF_PERCENT) \
+    S(CTSF_COLPCT_RESPONSES_COUNT, "COLPCT.RESPONSES.COUNT", N_("Column Responses % (Base: Count)"), CTF_PERCENT) \
+    S(CTSF_TABLEPCT_RESPONSES_COUNT, "TABLEPCT.RESPONSES.COUNT", N_("Table Responses % (Base: Count)"), CTF_PERCENT) \
+    S(CTSF_SUBTABLEPCT_RESPONSES_COUNT, "SUBTABLEPCT.RESPONSES.COUNT", N_("Subtable Responses % (Base: Count)"), CTF_PERCENT) \
+    S(CTSF_LAYERPCT_RESPONSES_COUNT, "LAYERPCT.RESPONSES.COUNT", N_("Layer Responses % (Base: Count)"), CTF_PERCENT) \
+    S(CTSF_LAYERROWPCT_RESPONSES_COUNT, "LAYERROWPCT.RESPONSES.COUNT", N_("Layer Row Responses % (Base: Count)"), CTF_PERCENT) \
+    S(CTSF_LAYERCOLPCT_RESPONSES_COUNT, "LAYERCOLPCT.RESPONSES.COUNT", N_("Layer Column Responses % (Base: Count)"), CTF_PERCENT) \
+    S(CTSF_ROWPCT_COUNT_RESPONSES, "ROWPCT.COUNT.RESPONSES", N_("Row Count % (Base: Responses)"), CTF_PERCENT) \
+    S(CTSF_COLPCT_COUNT_RESPONSES, "COLPCT.COUNT.RESPONSES", N_("Column Count % (Base: Responses)"), CTF_PERCENT) \
+    S(CTSF_TABLEPCT_COUNT_RESPONSES, "TABLEPCT.COUNT.RESPONSES", N_("Table Count % (Base: Responses)"), CTF_PERCENT) \
+    S(CTSF_SUBTABLEPCT_COUNT_RESPONSES, "SUBTABLEPCT.COUNT.RESPONSES", N_("Subtable Count % (Base: Responses)"), CTF_PERCENT) \
+    S(CTSF_LAYERPCT_COUNT_RESPONSES, "LAYERPCT.COUNT.RESPONSES", N_("Layer Count % (Base: Responses)"), CTF_PERCENT) \
+    S(CTSF_LAYERROWPCT_COUNT_RESPONSES, "LAYERROWPCT.COUNT.RESPONSES", N_("Layer Row Count % (Base: Responses)"), CTF_PERCENT) \
+    S(CTSF_LAYERCOLPCT_COUNT_RESPONSES, "LAYERCOLPCT.RESPONSES.COUNT", N_("Layer Column Count % (Base: Responses)"), CTF_PERCENT)
 
 enum ctables_summary_function
   {
-#define S(ENUM, NAME) ENUM,
+#define S(ENUM, NAME, LABEL, FORMAT) ENUM,
     SUMMARIES
 #undef S
   };
 
 enum {
-#define S(ENUM, NAME) +1
+#define S(ENUM, NAME, LABEL, FORMAT) +1
   N_CTSF_FUNCTIONS = SUMMARIES
 #undef S
 };
@@ -308,6 +320,7 @@ enum {
 struct ctables_summary
   {
     enum ctables_summary_function function;
+    double percentile;          /* CTSF_PTILE only. */
     char *label;
     struct fmt_spec format;     /* XXX extra CTABLES formats */
   };
@@ -361,17 +374,17 @@ parse_ctables_summary_function (struct lexer *lexer,
       struct substring name;
     };
   static struct pair names[] = {
-#define S(ENUM, NAME) { ENUM, SS_LITERAL_INITIALIZER (NAME) },
+#define S(ENUM, NAME, LABEL, FORMAT) { ENUM, SS_LITERAL_INITIALIZER (NAME) },
     SUMMARIES
 
     /* The .COUNT suffix may be omitted. */
-    S(CTSF_ROWPCT_COUNT, "ROWPCT")
-    S(CTSF_COLPCT_COUNT, "COLPCT")
-    S(CTSF_TABLEPCT_COUNT, "TABLEPCT")
-    S(CTSF_SUBTABLEPCT_COUNT, "SUBTABLEPCT")
-    S(CTSF_LAYERPCT_COUNT, "LAYERPCT")
-    S(CTSF_LAYERROWPCT_COUNT, "LAYERROWPCT")
-    S(CTSF_LAYERCOLPCT_COUNT, "LAYERCOLPCT")
+    S(CTSF_ROWPCT_COUNT, "ROWPCT", _, _)
+    S(CTSF_COLPCT_COUNT, "COLPCT", _, _)
+    S(CTSF_TABLEPCT_COUNT, "TABLEPCT", _, _)
+    S(CTSF_SUBTABLEPCT_COUNT, "SUBTABLEPCT", _, _)
+    S(CTSF_LAYERPCT_COUNT, "LAYERPCT", _, _)
+    S(CTSF_LAYERROWPCT_COUNT, "LAYERROWPCT", _, _)
+    S(CTSF_LAYERCOLPCT_COUNT, "LAYERCOLPCT", _, _)
 #undef S
   };
 
@@ -432,14 +445,55 @@ struct ctables_axis_parse_ctx
   };
 
 static struct ctables_summary *
-add_summary (struct ctables_axis *axis, size_t *allocated_summaries)
+add_summary (struct ctables_axis *axis, enum ctables_summary_function function,
+             double percentile, size_t *allocated_summaries)
 {
   if (axis->n_summaries >= *allocated_summaries)
     axis->summaries = x2nrealloc (axis->summaries, allocated_summaries,
                                   sizeof *axis->summaries);
 
+  static const char *default_labels[] = {
+#define S(ENUM, NAME, LABEL, FORMAT) [ENUM] = LABEL,
+    SUMMARIES
+#undef S
+  };
+  char *label = (function == CTSF_PTILE
+                 ? xasprintf (_("Percentile %.2f"), percentile)
+                 : xstrdup (gettext (default_labels[function])));
+
+  static const enum ctables_format default_formats[] = {
+#define S(ENUM, NAME, LABEL, FORMAT) [ENUM] = FORMAT,
+    SUMMARIES
+#undef S
+  };
+  struct fmt_spec format;
+  switch (default_formats[function])
+    {
+    case CTF_COUNT:
+      format = (struct fmt_spec) { .type = FMT_F, .w = 40 };
+      break;
+
+    case CTF_PERCENT:
+      format = (struct fmt_spec) { .type = FMT_PCT, .w = 40, .d = 1 };
+      break;
+
+    case CTF_GENERAL:
+      format = *(axis->op == CTAO_VAR
+                 ? var_get_print_format (axis->var)
+                 : var_get_print_format (axis->mrset->vars[0]));
+      break;
+
+    default:
+      NOT_REACHED ();
+    }
+
   struct ctables_summary *s = &axis->summaries[axis->n_summaries++];
-  *s = (struct ctables_summary) { .function = CTSF_COUNT };
+  *s = (struct ctables_summary) {
+    .function = function,
+    .percentile = percentile,
+    .label = label,
+    .format = format,
+  };
   return s;
 }
 
@@ -502,11 +556,24 @@ ctables_axis_parse_primary (struct ctables_axis_parse_ctx *ctx)
     {
       do
         {
-          struct ctables_summary *s = add_summary (axis, &allocated_summaries);
-          if (!parse_ctables_summary_function (ctx->lexer, &s->function))
+          enum ctables_summary_function function;
+          if (!parse_ctables_summary_function (ctx->lexer, &function))
             goto error;
+
+          double percentile = 0;
+          if (function == CTSF_PTILE)
+            {
+              if (!lex_force_num_range_closed (ctx->lexer, "PTILE", 0, 100))
+                goto error;
+              percentile = lex_number (ctx->lexer);
+              lex_get (ctx->lexer);
+            }
+
+          struct ctables_summary *s = add_summary (axis, function, percentile,
+                                                   &allocated_summaries);
           if (lex_is_string (ctx->lexer))
             {
+              free (s->label);
               s->label = ss_xstrdup (lex_tokss (ctx->lexer));
               lex_get (ctx->lexer);
             }
@@ -522,12 +589,8 @@ ctables_axis_parse_primary (struct ctables_axis_parse_ctx *ctx)
       while (!lex_match (ctx->lexer, T_RBRACK));
     }
   else
-    {
-      struct ctables_summary *s = add_summary (axis, &allocated_summaries);
-      s->function = axis->scale ? CTSF_MEAN : CTSF_COUNT;
-      s->label = xstrdup (axis->scale ? _("Mean") : _("Count"));
-      s->format = (struct fmt_spec) { .type = FMT_F, .w = 40 };
-    }
+    add_summary (axis, axis->scale ? CTSF_MEAN : CTSF_COUNT, 0,
+                 &allocated_summaries);
   return axis;
 
 error:
@@ -542,7 +605,7 @@ ctables_axis_parse_nest (struct ctables_axis_parse_ctx *ctx)
   if (!lhs)
     return NULL;
 
-  while (lex_match (ctx->lexer, T_PLUS))
+  while (lex_match (ctx->lexer, T_GT))
     {
       struct ctables_axis *rhs = ctables_axis_parse_primary (ctx);
       if (!rhs)
@@ -593,12 +656,56 @@ ctables_axis_parse (struct lexer *lexer, struct dictionary *dict,
   return t->axes[a] != NULL;
 }
 
+static void
+ctables_chisq_destroy (struct ctables_chisq *chisq)
+{
+  free (chisq);
+}
+
+static void
+ctables_pairwise_destroy (struct ctables_pairwise *pairwise)
+{
+  free (pairwise);
+}
+
+static void
+ctables_table_uninit (struct ctables_table *t)
+{
+  if (!t)
+    return;
+
+  ctables_axis_destroy (t->axes[PIVOT_AXIS_COLUMN]);
+  ctables_axis_destroy (t->axes[PIVOT_AXIS_ROW]);
+  ctables_axis_destroy (t->axes[PIVOT_AXIS_LAYER]);
+  free (t->caption);
+  free (t->corner);
+  free (t->title);
+  ctables_chisq_destroy (t->chisq);
+  ctables_pairwise_destroy (t->pairwise);
+}
+
+static void
+ctables_destroy (struct ctables *ct)
+{
+  if (!ct)
+    return;
+
+  pivot_table_look_unref (ct->look);
+  free (ct->zero);
+  free (ct->missing);
+  free (ct->vlabels);
+  for (size_t i = 0; i < ct->n_tables; i++)
+    ctables_table_uninit (&ct->tables[i]);
+  free (ct->tables);
+  free (ct);
+}
+
 int
 cmd_ctables (struct lexer *lexer, struct dataset *ds)
 {
   size_t n_vars = dict_get_n_vars (dataset_dict (ds));
   enum ctables_vlabel *vlabels = xnmalloc (n_vars, sizeof *vlabels);
-  for (size_t i = 0; n_vars; i++)
+  for (size_t i = 0; i < n_vars; i++)
     vlabels[i] = CTVL_DEFAULT;
 
   struct ctables *ct = xmalloc (sizeof *ct);
@@ -829,8 +936,10 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
                 goto error;
             }
         }
+      if (lex_token (lexer) == T_ENDCMD)
+        break;
       if (!lex_force_match (lexer, T_SLASH))
-        goto error;
+        break;
 
       /* XXX Validate axes. */
       while (!lex_match_id (lexer, "TABLE") && lex_token (lexer) != T_ENDCMD)
@@ -1161,11 +1270,11 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
         }
     }
   while (lex_token (lexer) != T_ENDCMD);
-
+  ctables_destroy (ct);
   return CMD_SUCCESS;
 
 error:
-  /* XXX free */
+  ctables_destroy (ct);
   return CMD_FAILURE;
 }
 
index 7b5f9286be42608f4ebf66d11c56743123f3ff72..e5d3819c47188bf8e296c3cece8e5035a7be1837 100644 (file)
@@ -390,6 +390,7 @@ TESTSUITE_AT = \
        tests/language/stats/autorecode.at \
        tests/language/stats/correlations.at \
        tests/language/stats/crosstabs.at \
+       tests/language/stats/ctables.at \
        tests/language/stats/descriptives.at \
        tests/language/stats/examine.at \
        tests/language/stats/graph.at \