work on docs
[pspp] / src / language / stats / ctables.c
index 78ba956aa4997ff138f588666580190e27744fbd..a573afaad1ba99075265128c73a58b47fae3c557 100644 (file)
@@ -69,136 +69,18 @@ enum ctables_vlabel
    - upper confidence limits (*.UCL)
    - standard error (*.SE)
  */
-#define SUMMARIES                                                       \
-    /* All variables. */                                                \
-    S(CTSF_COUNT, "COUNT", N_("Count"), CTF_COUNT, CTFA_ALL)            \
-    S(CTSF_ECOUNT, "ECOUNT", N_("Adjusted Count"), CTF_COUNT, CTFA_ALL) \
-    S(CTSF_ROWPCT_COUNT, "ROWPCT.COUNT", N_("Row %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_COLPCT_COUNT, "COLPCT.COUNT", N_("Column %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_TABLEPCT_COUNT, "TABLEPCT.COUNT", N_("Table %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_SUBTABLEPCT_COUNT, "SUBTABLEPCT.COUNT", N_("Subtable %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERPCT_COUNT, "LAYERPCT.COUNT", N_("Layer %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERROWPCT_COUNT, "LAYERROWPCT.COUNT", N_("Layer Row %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERCOLPCT_COUNT, "LAYERCOLPCT.COUNT", N_("Layer Column %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ROWPCT_VALIDN, "ROWPCT.VALIDN", N_("Row Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_COLPCT_VALIDN, "COLPCT.VALIDN", N_("Column Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_TABLEPCT_VALIDN, "TABLEPCT.VALIDN", N_("Table Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_SUBTABLEPCT_VALIDN, "SUBTABLEPCT.VALIDN", N_("Subtable Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERPCT_VALIDN, "LAYERPCT.VALIDN", N_("Layer Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERROWPCT_VALIDN, "LAYERROWPCT.VALIDN", N_("Layer Row Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERCOLPCT_VALIDN, "LAYERCOLPCT.VALIDN", N_("Layer Column Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ROWPCT_TOTALN, "ROWPCT.TOTALN", N_("Row Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_COLPCT_TOTALN, "COLPCT.TOTALN", N_("Column Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_TABLEPCT_TOTALN, "TABLEPCT.TOTALN", N_("Table Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_SUBTABLEPCT_TOTALN, "SUBTABLEPCT.TOTALN", N_("Subtable Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERPCT_TOTALN, "LAYERPCT.TOTALN", N_("Layer Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERROWPCT_TOTALN, "LAYERROWPCT.TOTALN", N_("Layer Row Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_LAYERCOLPCT_TOTALN, "LAYERCOLPCT.TOTALN", N_("Layer Column Total N %"), CTF_PERCENT, CTFA_ALL) \
-                                                                        \
-    /* All variables (unweighted.) */ \
-    S(CTSF_UCOUNT, "UCOUNT", N_("Unweighted Count"), CTF_COUNT, CTFA_ALL)            \
-    S(CTSF_UROWPCT_COUNT, "UROWPCT.COUNT", N_("Unweighted Row %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UCOLPCT_COUNT, "UCOLPCT.COUNT", N_("Unweighted Column %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UTABLEPCT_COUNT, "UTABLEPCT.COUNT", N_("Unweighted Table %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_USUBTABLEPCT_COUNT, "USUBTABLEPCT.COUNT", N_("Unweighted Subtable %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERPCT_COUNT, "ULAYERPCT.COUNT", N_("Unweighted Layer %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERROWPCT_COUNT, "ULAYERROWPCT.COUNT", N_("Unweighted Layer Row %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERCOLPCT_COUNT, "ULAYERCOLPCT.COUNT", N_("Unweighted Layer Column %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UROWPCT_VALIDN, "UROWPCT.VALIDN", N_("Unweighted Row Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UCOLPCT_VALIDN, "UCOLPCT.VALIDN", N_("Unweighted Column Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UTABLEPCT_VALIDN, "UTABLEPCT.VALIDN", N_("Unweighted Table Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_USUBTABLEPCT_VALIDN, "USUBTABLEPCT.VALIDN", N_("Unweighted Subtable Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERPCT_VALIDN, "ULAYERPCT.VALIDN", N_("Unweighted Layer Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERROWPCT_VALIDN, "ULAYERROWPCT.VALIDN", N_("Unweighted Layer Row Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERCOLPCT_VALIDN, "ULAYERCOLPCT.VALIDN", N_("Unweighted Layer Column Valid N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UROWPCT_TOTALN, "UROWPCT.TOTALN", N_("Unweighted Row Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UCOLPCT_TOTALN, "UCOLPCT.TOTALN", N_("Unweighted Column Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_UTABLEPCT_TOTALN, "UTABLEPCT.TOTALN", N_("Unweighted Table Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_USUBTABLEPCT_TOTALN, "USUBTABLEPCT.TOTALN", N_("Unweighted Subtable Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERPCT_TOTALN, "ULAYERPCT.TOTALN", N_("Unweighted Layer Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERROWPCT_TOTALN, "ULAYERROWPCT.TOTALN", N_("Unweighted Layer Row Total N %"), CTF_PERCENT, CTFA_ALL) \
-    S(CTSF_ULAYERCOLPCT_TOTALN, "ULAYERCOLPCT.TOTALN", N_("Unweighted Layer Column Total N %"), CTF_PERCENT, CTFA_ALL) \
-                                                                        \
-    /* Scale variables, totals, and subtotals. */                       \
-    S(CTSF_MAXIMUM, "MAXIMUM", N_("Maximum"), CTF_GENERAL, CTFA_SCALE)  \
-    S(CTSF_MEAN, "MEAN", N_("Mean"), CTF_GENERAL, CTFA_SCALE)           \
-    S(CTSF_MEDIAN, "MEDIAN", N_("Median"), CTF_GENERAL, CTFA_SCALE)     \
-    S(CTSF_MINIMUM, "MINIMUM", N_("Minimum"), CTF_GENERAL, CTFA_SCALE)  \
-    S(CTSF_MISSING, "MISSING", N_("Missing"), CTF_GENERAL, CTFA_SCALE)  \
-    S(CTSF_MODE, "MODE", N_("Mode"), CTF_GENERAL, CTFA_SCALE)           \
-    S(CTSF_PTILE, "PTILE", N_("Percentile"), CTF_GENERAL, CTFA_SCALE)   \
-    S(CTSF_RANGE, "RANGE", N_("Range"), CTF_GENERAL, CTFA_SCALE)        \
-    S(CTSF_SEMEAN, "SEMEAN", N_("Std Error of Mean"), CTF_GENERAL, CTFA_SCALE) \
-    S(CTSF_STDDEV, "STDDEV", N_("Std Deviation"), CTF_GENERAL, CTFA_SCALE) \
-    S(CTSF_SUM, "SUM", N_("Sum"), CTF_GENERAL, CTFA_SCALE)              \
-    S(CSTF_TOTALN, "TOTALN", N_("Total N"), CTF_COUNT, CTFA_SCALE)      \
-    S(CTSF_ETOTALN, "ETOTALN", N_("Adjusted Total N"), CTF_COUNT, CTFA_SCALE) \
-    S(CTSF_VALIDN, "VALIDN", N_("Valid N"), CTF_COUNT, CTFA_SCALE)      \
-    S(CTSF_EVALIDN, "EVALIDN", N_("Adjusted Valid N"), CTF_COUNT, CTFA_SCALE) \
-    S(CTSF_VARIANCE, "VARIANCE", N_("Variance"), CTF_GENERAL, CTFA_SCALE) \
-    S(CTSF_ROWPCT_SUM, "ROWPCT.SUM", N_("Row Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_COLPCT_SUM, "COLPCT.SUM", N_("Column Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_TABLEPCT_SUM, "TABLEPCT.SUM", N_("Table Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_SUBTABLEPCT_SUM, "SUBTABLEPCT.SUM", N_("Subtable Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_LAYERPCT_SUM, "LAYERPCT.SUM", N_("Layer Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_LAYERROWPCT_SUM, "LAYERROWPCT.SUM", N_("Layer Row Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_LAYERCOLPCT_SUM, "LAYERCOLPCT.SUM", N_("Layer Column Sum %"), CTF_PERCENT, CTFA_SCALE) \
-                                                                        \
-    /* Scale variables, totals, and subtotals (unweighted). */                       \
-    S(CTSF_UMEAN, "UMEAN", N_("Unweighted Mean"), CTF_GENERAL, CTFA_SCALE)           \
-    S(CTSF_UMEDIAN, "UMEDIAN", N_("Unweighted Median"), CTF_GENERAL, CTFA_SCALE)     \
-    S(CTSF_UMISSING, "UMISSING", N_("Unweighted Missing"), CTF_GENERAL, CTFA_SCALE)  \
-    S(CTSF_UMODE, "UMODE", N_("Unweighted Mode"), CTF_GENERAL, CTFA_SCALE)           \
-    S(CTSF_UPTILE, "UPTILE", N_("Unweighted Percentile"), CTF_GENERAL, CTFA_SCALE)   \
-    S(CTSF_USEMEAN, "USEMEAN", N_("Unweighted Std Error of Mean"), CTF_GENERAL, CTFA_SCALE) \
-    S(CTSF_USTDDEV, "USTDDEV", N_("Unweighted Std Deviation"), CTF_GENERAL, CTFA_SCALE) \
-    S(CTSF_USUM, "USUM", N_("Unweighted Sum"), CTF_GENERAL, CTFA_SCALE)              \
-    S(CSTF_UTOTALN, "UTOTALN", N_("Unweighted Total N"), CTF_COUNT, CTFA_SCALE)      \
-    S(CTSF_UVALIDN, "UVALIDN", N_("Unweighted Valid N"), CTF_COUNT, CTFA_SCALE)      \
-    S(CTSF_UVARIANCE, "UVARIANCE", N_("Unweighted Variance"), CTF_GENERAL, CTFA_SCALE) \
-    S(CTSF_UROWPCT_SUM, "UROWPCT.SUM", N_("Unweighted Row Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_UCOLPCT_SUM, "UCOLPCT.SUM", N_("Unweighted Column Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_UTABLEPCT_SUM, "UTABLEPCT.SUM", N_("Unweighted Table Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_USUBTABLEPCT_SUM, "USUBTABLEPCT.SUM", N_("Unweighted Subtable Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_ULAYERPCT_SUM, "ULAYERPCT.SUM", N_("Unweighted Layer Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_ULAYERROWPCT_SUM, "ULAYERROWPCT.SUM", N_("Unweighted Layer Row Sum %"), CTF_PERCENT, CTFA_SCALE) \
-    S(CTSF_ULAYERCOLPCT_SUM, "ULAYERCOLPCT.SUM", N_("Unweighted Layer Column Sum %"), CTF_PERCENT, CTFA_SCALE) \
-
-#if 0         /* Multiple response sets not yet implemented. */
-  S(CTSF_RESPONSES, "RESPONSES", N_("Responses"), CTF_COUNT, CTFA_MRSETS) \
-    S(CTSF_ROWPCT_RESPONSES, "ROWPCT.RESPONSES", N_("Row Responses %"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_COLPCT_RESPONSES, "COLPCT.RESPONSES", N_("Column Responses %"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_TABLEPCT_RESPONSES, "TABLEPCT.RESPONSES", N_("Table Responses %"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_SUBTABLEPCT_RESPONSES, "SUBTABLEPCT.RESPONSES", N_("Subtable Responses %"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERPCT_RESPONSES, "LAYERPCT.RESPONSES", N_("Layer Responses %"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERROWPCT_RESPONSES, "LAYERROWPCT.RESPONSES", N_("Layer Row Responses %"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERCOLPCT_RESPONSES, "LAYERCOLPCT.RESPONSES", N_("Layer Column Responses %"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_ROWPCT_RESPONSES_COUNT, "ROWPCT.RESPONSES.COUNT", N_("Row Responses % (Base: Count)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_COLPCT_RESPONSES_COUNT, "COLPCT.RESPONSES.COUNT", N_("Column Responses % (Base: Count)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_TABLEPCT_RESPONSES_COUNT, "TABLEPCT.RESPONSES.COUNT", N_("Table Responses % (Base: Count)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_SUBTABLEPCT_RESPONSES_COUNT, "SUBTABLEPCT.RESPONSES.COUNT", N_("Subtable Responses % (Base: Count)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERPCT_RESPONSES_COUNT, "LAYERPCT.RESPONSES.COUNT", N_("Layer Responses % (Base: Count)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERROWPCT_RESPONSES_COUNT, "LAYERROWPCT.RESPONSES.COUNT", N_("Layer Row Responses % (Base: Count)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERCOLPCT_RESPONSES_COUNT, "LAYERCOLPCT.RESPONSES.COUNT", N_("Layer Column Responses % (Base: Count)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_ROWPCT_COUNT_RESPONSES, "ROWPCT.COUNT.RESPONSES", N_("Row Count % (Base: Responses)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_COLPCT_COUNT_RESPONSES, "COLPCT.COUNT.RESPONSES", N_("Column Count % (Base: Responses)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_TABLEPCT_COUNT_RESPONSES, "TABLEPCT.COUNT.RESPONSES", N_("Table Count % (Base: Responses)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_SUBTABLEPCT_COUNT_RESPONSES, "SUBTABLEPCT.COUNT.RESPONSES", N_("Subtable Count % (Base: Responses)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERPCT_COUNT_RESPONSES, "LAYERPCT.COUNT.RESPONSES", N_("Layer Count % (Base: Responses)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERROWPCT_COUNT_RESPONSES, "LAYERROWPCT.COUNT.RESPONSES", N_("Layer Row Count % (Base: Responses)"), CTF_PERCENT, CTFA_MRSETS) \
-    S(CTSF_LAYERCOLPCT_COUNT_RESPONSES, "LAYERCOLPCT.RESPONSES.COUNT", N_("Layer Column Count % (Base: Responses)"), CTF_PERCENT, CTFA_MRSETS)
-#endif
 
 enum ctables_summary_function
   {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) ENUM,
-    SUMMARIES
+#include "ctables.inc"
 #undef S
   };
 
 enum {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) +1
-  N_CTSF_FUNCTIONS = SUMMARIES
+  N_CTSF_FUNCTIONS =
+#include "ctables.inc"
 #undef S
 };
 
@@ -227,6 +109,7 @@ struct ctables_domain
 
     const struct ctables_cell *example;
 
+    size_t sequence;
     double d_valid;             /* Dictionary weight. */
     double d_count;
     double d_total;
@@ -445,6 +328,8 @@ struct ctables_stack
     size_t n;
   };
 
+static void ctables_stack_uninit (struct ctables_stack *);
+
 struct ctables_value
   {
     struct hmap_node node;
@@ -470,6 +355,8 @@ struct ctables_section
     struct hmap domains[N_CTDTS]; /* Contains "struct ctables_domain"s. */
   };
 
+static void ctables_section_uninit (struct ctables_section *);
+
 struct ctables_table
   {
     struct ctables *ctables;
@@ -611,6 +498,7 @@ ctables_category_uninit (struct ctables_category *cat)
   if (!cat)
     return;
 
+  msg_location_destroy (cat->location);
   switch (cat->type)
     {
     case CCT_NUMBER:
@@ -830,7 +718,8 @@ static void
 ctables_summary_spec_set_clone (struct ctables_summary_spec_set *dst,
                                 const struct ctables_summary_spec_set *src)
 {
-  struct ctables_summary_spec *specs = xnmalloc (src->n, sizeof *specs);
+  struct ctables_summary_spec *specs
+    = (src->n ? xnmalloc (src->n, sizeof *specs) : NULL);
   for (size_t i = 0; i < src->n; i++)
     ctables_summary_spec_clone (&specs[i], &src->specs[i]);
 
@@ -848,6 +737,7 @@ ctables_summary_spec_set_uninit (struct ctables_summary_spec_set *set)
 {
   for (size_t i = 0; i < set->n; i++)
     ctables_summary_spec_uninit (&set->specs[i]);
+  free (set->listwise_vars);
   free (set->specs);
 }
 
@@ -888,7 +778,7 @@ ctables_function_availability (enum ctables_summary_function f)
 {
   static enum ctables_function_availability availability[] = {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = AVAILABILITY,
-    SUMMARIES
+#include "ctables.inc"
 #undef S
   };
 
@@ -898,102 +788,9 @@ ctables_function_availability (enum ctables_summary_function f)
 static bool
 ctables_summary_function_is_count (enum ctables_summary_function f)
 {
-  switch (f)
-    {
-    case CTSF_COUNT:
-    case CTSF_ECOUNT:
-    case CTSF_ROWPCT_COUNT:
-    case CTSF_COLPCT_COUNT:
-    case CTSF_TABLEPCT_COUNT:
-    case CTSF_SUBTABLEPCT_COUNT:
-    case CTSF_LAYERPCT_COUNT:
-    case CTSF_LAYERROWPCT_COUNT:
-    case CTSF_LAYERCOLPCT_COUNT:
-    case CTSF_UCOUNT:
-    case CTSF_UROWPCT_COUNT:
-    case CTSF_UCOLPCT_COUNT:
-    case CTSF_UTABLEPCT_COUNT:
-    case CTSF_USUBTABLEPCT_COUNT:
-    case CTSF_ULAYERPCT_COUNT:
-    case CTSF_ULAYERROWPCT_COUNT:
-    case CTSF_ULAYERCOLPCT_COUNT:
-      return true;
-
-    case CTSF_ROWPCT_VALIDN:
-    case CTSF_COLPCT_VALIDN:
-    case CTSF_TABLEPCT_VALIDN:
-    case CTSF_SUBTABLEPCT_VALIDN:
-    case CTSF_LAYERPCT_VALIDN:
-    case CTSF_LAYERROWPCT_VALIDN:
-    case CTSF_LAYERCOLPCT_VALIDN:
-    case CTSF_ROWPCT_TOTALN:
-    case CTSF_COLPCT_TOTALN:
-    case CTSF_TABLEPCT_TOTALN:
-    case CTSF_SUBTABLEPCT_TOTALN:
-    case CTSF_LAYERPCT_TOTALN:
-    case CTSF_LAYERROWPCT_TOTALN:
-    case CTSF_LAYERCOLPCT_TOTALN:
-    case CTSF_MAXIMUM:
-    case CTSF_MEAN:
-    case CTSF_MEDIAN:
-    case CTSF_MINIMUM:
-    case CTSF_MISSING:
-    case CTSF_MODE:
-    case CTSF_PTILE:
-    case CTSF_RANGE:
-    case CTSF_SEMEAN:
-    case CTSF_STDDEV:
-    case CTSF_SUM:
-    case CSTF_TOTALN:
-    case CTSF_ETOTALN:
-    case CTSF_VALIDN:
-    case CTSF_EVALIDN:
-    case CTSF_VARIANCE:
-    case CTSF_ROWPCT_SUM:
-    case CTSF_COLPCT_SUM:
-    case CTSF_TABLEPCT_SUM:
-    case CTSF_SUBTABLEPCT_SUM:
-    case CTSF_LAYERPCT_SUM:
-    case CTSF_LAYERROWPCT_SUM:
-    case CTSF_LAYERCOLPCT_SUM:
-    case CTSF_UROWPCT_VALIDN:
-    case CTSF_UCOLPCT_VALIDN:
-    case CTSF_UTABLEPCT_VALIDN:
-    case CTSF_USUBTABLEPCT_VALIDN:
-    case CTSF_ULAYERPCT_VALIDN:
-    case CTSF_ULAYERROWPCT_VALIDN:
-    case CTSF_ULAYERCOLPCT_VALIDN:
-    case CTSF_UROWPCT_TOTALN:
-    case CTSF_UCOLPCT_TOTALN:
-    case CTSF_UTABLEPCT_TOTALN:
-    case CTSF_USUBTABLEPCT_TOTALN:
-    case CTSF_ULAYERPCT_TOTALN:
-    case CTSF_ULAYERROWPCT_TOTALN:
-    case CTSF_ULAYERCOLPCT_TOTALN:
-    case CTSF_UMEAN:
-    case CTSF_UMEDIAN:
-    case CTSF_UMISSING:
-    case CTSF_UMODE:
-    case CTSF_UPTILE:
-    case CTSF_USEMEAN:
-    case CTSF_USTDDEV:
-    case CTSF_USUM:
-    case CSTF_UTOTALN:
-    case CTSF_UVALIDN:
-    case CTSF_UVARIANCE:
-    case CTSF_UROWPCT_SUM:
-    case CTSF_UCOLPCT_SUM:
-    case CTSF_UTABLEPCT_SUM:
-    case CTSF_USUBTABLEPCT_SUM:
-    case CTSF_ULAYERPCT_SUM:
-    case CTSF_ULAYERROWPCT_SUM:
-    case CTSF_ULAYERCOLPCT_SUM:
-      return false;
-  }
-  NOT_REACHED ();
+  return f == CTSF_COUNT || f == CTSF_ECOUNT || f == CTSF_UCOUNT;
 }
 
-
 static bool
 parse_ctables_summary_function (struct lexer *lexer,
                                 enum ctables_summary_function *f)
@@ -1006,8 +803,7 @@ parse_ctables_summary_function (struct lexer *lexer,
   static struct pair names[] = {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) \
     { ENUM, SS_LITERAL_INITIALIZER (NAME) },
-    SUMMARIES
-
+#include "ctables.inc"
     /* The .COUNT suffix may be omitted. */
     S(CTSF_ROWPCT_COUNT, "ROWPCT", _, _, _)
     S(CTSF_COLPCT_COUNT, "COLPCT", _, _, _)
@@ -1086,7 +882,7 @@ ctables_summary_default_format (enum ctables_summary_function function,
 {
   static const enum ctables_format default_formats[] = {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = FORMAT,
-    SUMMARIES
+#include "ctables.inc"
 #undef S
   };
   switch (default_formats[function])
@@ -1112,7 +908,7 @@ ctables_summary_label (const struct ctables_summary_spec *spec, double cilevel)
     {
       static const char *default_labels[] = {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = LABEL,
-        SUMMARIES
+#include "ctables.inc"
 #undef S
       };
 
@@ -1146,7 +942,7 @@ ctables_summary_function_name (enum ctables_summary_function function)
 {
   static const char *names[] = {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = NAME,
-    SUMMARIES
+#include "ctables.inc"
 #undef S
   };
   return names[function];
@@ -1536,13 +1332,33 @@ ctables_table_destroy (struct ctables_table *t)
   if (!t)
     return;
 
+  for (size_t i = 0; i < t->n_sections; i++)
+    ctables_section_uninit (&t->sections[i]);
+  free (t->sections);
+
   for (size_t i = 0; i < t->n_categories; i++)
     ctables_categories_unref (t->categories[i]);
   free (t->categories);
 
-  ctables_axis_destroy (t->axes[PIVOT_AXIS_COLUMN]);
-  ctables_axis_destroy (t->axes[PIVOT_AXIS_ROW]);
-  ctables_axis_destroy (t->axes[PIVOT_AXIS_LAYER]);
+  for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+    {
+      ctables_axis_destroy (t->axes[a]);
+      ctables_stack_uninit (&t->stacks[a]);
+    }
+  free (t->summary_specs.specs);
+
+  struct ctables_value *ctv, *next_ctv;
+  HMAP_FOR_EACH_SAFE (ctv, next_ctv, struct ctables_value, node,
+                      &t->clabels_values_map)
+    {
+      value_destroy (&ctv->value, var_get_width (t->clabels_example));
+      hmap_delete (&t->clabels_values_map, &ctv->node);
+      free (ctv);
+    }
+  hmap_destroy (&t->clabels_values_map);
+  free (t->clabels_values);
+
+  free (t->sum_vars);
   free (t->caption);
   free (t->corner);
   free (t->title);
@@ -1557,6 +1373,24 @@ ctables_destroy (struct ctables *ct)
   if (!ct)
     return;
 
+  struct ctables_postcompute *pc, *next_pc;
+  HMAP_FOR_EACH_SAFE (pc, next_pc, struct ctables_postcompute, hmap_node,
+                      &ct->postcomputes)
+    {
+      free (pc->name);
+      msg_location_destroy (pc->location);
+      ctables_pcexpr_destroy (pc->expr);
+      free (pc->label);
+      if (pc->specs)
+        {
+          ctables_summary_spec_set_uninit (pc->specs);
+          free (pc->specs);
+        }
+      hmap_delete (&ct->postcomputes, &pc->hmap_node);
+      free (pc);
+    }
+
+  fmt_settings_uninit (&ct->ctables_formats);
   pivot_table_look_unref (ct->look);
   free (ct->zero);
   free (ct->missing);
@@ -1686,7 +1520,10 @@ ctables_table_parse_explicit_category (struct lexer *lexer,
           else
             {
               if (!lex_force_string (lexer))
-                return false;
+                {
+                  ss_dealloc (&s);
+                  return false;
+                }
               struct substring sr1 = parse_substring (lexer, dict);
               *cat = cct_srange (s, sr1);
             }
@@ -2025,7 +1862,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
           int start_ofs = lex_ofs (lexer);
           struct ctables_category *cat = &c->cats[c->n_cats];
           if (!ctables_table_parse_explicit_category (lexer, dict, ct, cat))
-            return false;
+            goto error;
           cat->location = lex_ofs_location (lexer, start_ofs, lex_ofs (lexer) - 1);
           c->n_cats++;
 
@@ -2055,7 +1892,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
           else
             {
               lex_error_expecting (lexer, "A", "D");
-              return false;
+              goto error;
             }
         }
       else if (!c->n_cats && lex_match_id (lexer, "KEY"))
@@ -2069,31 +1906,31 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
             {
               cat.type = CCT_FUNCTION;
               if (!parse_ctables_summary_function (lexer, &cat.sort_function))
-                return false;
+                goto error;
 
               if (lex_match (lexer, T_LPAREN))
                 {
                   cat.sort_var = parse_variable (lexer, dict);
                   if (!cat.sort_var)
-                    return false;
+                    goto error;
 
                   if (cat.sort_function == CTSF_PTILE)
                     {
                       lex_match (lexer, T_COMMA);
                       if (!lex_force_num_range_closed (lexer, "PTILE", 0, 100))
-                        return false;
+                        goto error;
                       cat.percentile = lex_number (lexer);
                       lex_get (lexer);
                     }
 
                   if (!lex_force_match (lexer, T_RPAREN))
-                    return false;
+                    goto error;
                 }
               else if (ctables_function_availability (cat.sort_function)
                        == CTFA_SCALE)
                 {
                   bool UNUSED b = lex_force_match (lexer, T_LPAREN);
-                  return false;
+                  goto error;
                 }
             }
         }
@@ -2107,20 +1944,20 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
           else
             {
               lex_error_expecting (lexer, "INCLUDE", "EXCLUDE");
-              return false;
+              goto error;
             }
         }
       else if (lex_match_id (lexer, "TOTAL"))
         {
           lex_match (lexer, T_EQUALS);
           if (!parse_bool (lexer, &show_totals))
-            return false;
+            goto error;
         }
       else if (lex_match_id (lexer, "LABEL"))
         {
           lex_match (lexer, T_EQUALS);
           if (!lex_force_string (lexer))
-            return false;
+            goto error;
           free (total_label);
           total_label = ss_xstrdup (lex_tokss (lexer));
           lex_get (lexer);
@@ -2135,7 +1972,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
           else
             {
               lex_error_expecting (lexer, "BEFORE", "AFTER");
-              return false;
+              goto error;
             }
         }
       else if (lex_match_id (lexer, "EMPTY"))
@@ -2148,7 +1985,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
           else
             {
               lex_error_expecting (lexer, "INCLUDE", "EXCLUDE");
-              return false;
+              goto error;
             }
         }
       else
@@ -2158,7 +1995,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
                                  "TOTAL", "LABEL", "POSITION", "EMPTY");
           else
             lex_error_expecting (lexer, "TOTAL", "LABEL", "POSITION", "EMPTY");
-          return false;
+          goto error;
         }
     }
 
@@ -2225,8 +2062,6 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
 
   if (cats_start_ofs != -1)
     {
-      struct msg_location *cats_location
-        = lex_ofs_location (lexer, cats_start_ofs, cats_end_ofs);
       for (size_t i = 0; i < c->n_cats; i++)
         {
           struct ctables_category *cat = &c->cats[i];
@@ -2234,9 +2069,13 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
             {
             case CCT_POSTCOMPUTE:
               cat->parse_format = parse_strings ? common_format->type : FMT_F;
-              if (!ctables_recursive_check_postcompute (dict, cat->pc->expr,
-                                                        cat, c, cats_location))
-                return false;
+              struct msg_location *cats_location
+                = lex_ofs_location (lexer, cats_start_ofs, cats_end_ofs);
+              bool ok = ctables_recursive_check_postcompute (
+                dict, cat->pc->expr, cat, c, cats_location);
+              msg_location_destroy (cats_location);
+              if (!ok)
+                goto error;
               break;
 
             case CCT_NUMBER:
@@ -2250,7 +2089,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
                               "subcommand tries to apply it to string "
                               "variable %s."),
                             var_get_name (vars[j]));
-                    return false;
+                    goto error;
                   }
               break;
 
@@ -2260,7 +2099,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
                   double n;
                   if (!parse_category_string (cat->location, cat->string, dict,
                                               common_format->type, &n))
-                    return false;
+                    goto error;
 
                   ss_dealloc (&cat->string);
 
@@ -2268,7 +2107,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
                   cat->number = n;
                 }
               else if (!all_strings (vars, n_vars, cat))
-                return false;
+                goto error;
               break;
 
             case CCT_SRANGE:
@@ -2281,14 +2120,14 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
                   else if (!parse_category_string (cat->location,
                                                    cat->srange[0], dict,
                                                    common_format->type, &n[0]))
-                    return false;
+                    goto error;
 
                   if (!cat->srange[1].string)
                     n[1] = DBL_MAX;
                   else if (!parse_category_string (cat->location,
                                                    cat->srange[1], dict,
                                                    common_format->type, &n[1]))
-                    return false;
+                    goto error;
 
                   ss_dealloc (&cat->srange[0]);
                   ss_dealloc (&cat->srange[1]);
@@ -2298,7 +2137,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
                   cat->nrange[1] = n[1];
                 }
               else if (!all_strings (vars, n_vars, cat))
-                return false;
+                goto error;
               break;
 
             case CCT_MISSING:
@@ -2314,14 +2153,22 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
         }
     }
 
+  free (vars);
   return true;
+
+error:
+  free (vars);
+  return false;
 }
 
 static void
 ctables_nest_uninit (struct ctables_nest *nest)
 {
-  if (nest)
-    free (nest->vars);
+  free (nest->vars);
+  for (enum ctables_summary_variant sv = 0; sv < N_CSVS; sv++)
+    ctables_summary_spec_set_uninit (&nest->specs[sv]);
+  for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+    free (nest->domains[dt]);
 }
 
 static void
@@ -2352,7 +2199,6 @@ nest_fts (struct ctables_stack s0, struct ctables_stack s1)
 
         size_t allocate = a->n + b->n;
         struct variable **vars = xnmalloc (allocate, sizeof *vars);
-        enum pivot_axis_type *axes = xnmalloc (allocate, sizeof *axes);
         size_t n = 0;
         for (size_t k = 0; k < a->n; k++)
           vars[n++] = a->vars[k];
@@ -2537,6 +2383,15 @@ ctables_summary_init (union ctables_summary *s,
       s->count = 0;
       break;
 
+    case CTSF_ROW_ID:
+    case CTSF_COL_ID:
+    case CTSF_TABLE_ID:
+    case CTSF_SUBTABLE_ID:
+    case CTSF_LAYER_ID:
+    case CTSF_LAYERROW_ID:
+    case CTSF_LAYERCOL_ID:
+      break;
+
     case CTSF_MAXIMUM:
     case CTSF_MINIMUM:
     case CTSF_RANGE:
@@ -2655,6 +2510,15 @@ ctables_summary_uninit (union ctables_summary *s,
     case CTSF_UVALIDN:
       break;
 
+    case CTSF_ROW_ID:
+    case CTSF_COL_ID:
+    case CTSF_TABLE_ID:
+    case CTSF_SUBTABLE_ID:
+    case CTSF_LAYER_ID:
+    case CTSF_LAYERROW_ID:
+    case CTSF_LAYERCOL_ID:
+      break;
+
     case CTSF_MAXIMUM:
     case CTSF_MINIMUM:
     case CTSF_RANGE:
@@ -2800,6 +2664,15 @@ ctables_summary_add (union ctables_summary *s,
         s->count += 1.0;
       break;
 
+    case CTSF_ROW_ID:
+    case CTSF_COL_ID:
+    case CTSF_TABLE_ID:
+    case CTSF_SUBTABLE_ID:
+    case CTSF_LAYER_ID:
+    case CTSF_LAYERROW_ID:
+    case CTSF_LAYERCOL_ID:
+      break;
+
     case CTSF_MISSING:
       if (is_missing)
         s->count += d_weight;
@@ -2937,6 +2810,7 @@ ctables_function_domain (enum ctables_summary_function function)
     case CTSF_UCOLPCT_SUM:
     case CTSF_UCOLPCT_TOTALN:
     case CTSF_UCOLPCT_VALIDN:
+    case CTSF_COL_ID:
       return CTDT_COL;
 
     case CTSF_LAYERCOLPCT_COUNT:
@@ -2947,6 +2821,7 @@ ctables_function_domain (enum ctables_summary_function function)
     case CTSF_ULAYERCOLPCT_SUM:
     case CTSF_ULAYERCOLPCT_TOTALN:
     case CTSF_ULAYERCOLPCT_VALIDN:
+    case CTSF_LAYERCOL_ID:
       return CTDT_LAYERCOL;
 
     case CTSF_LAYERPCT_COUNT:
@@ -2957,6 +2832,7 @@ ctables_function_domain (enum ctables_summary_function function)
     case CTSF_ULAYERPCT_SUM:
     case CTSF_ULAYERPCT_TOTALN:
     case CTSF_ULAYERPCT_VALIDN:
+    case CTSF_LAYER_ID:
       return CTDT_LAYER;
 
     case CTSF_LAYERROWPCT_COUNT:
@@ -2967,6 +2843,7 @@ ctables_function_domain (enum ctables_summary_function function)
     case CTSF_ULAYERROWPCT_SUM:
     case CTSF_ULAYERROWPCT_TOTALN:
     case CTSF_ULAYERROWPCT_VALIDN:
+    case CTSF_LAYERROW_ID:
       return CTDT_LAYERROW;
 
     case CTSF_ROWPCT_COUNT:
@@ -2977,6 +2854,7 @@ ctables_function_domain (enum ctables_summary_function function)
     case CTSF_UROWPCT_SUM:
     case CTSF_UROWPCT_TOTALN:
     case CTSF_UROWPCT_VALIDN:
+    case CTSF_ROW_ID:
       return CTDT_ROW;
 
     case CTSF_SUBTABLEPCT_COUNT:
@@ -2987,6 +2865,7 @@ ctables_function_domain (enum ctables_summary_function function)
     case CTSF_USUBTABLEPCT_SUM:
     case CTSF_USUBTABLEPCT_TOTALN:
     case CTSF_USUBTABLEPCT_VALIDN:
+    case CTSF_SUBTABLE_ID:
       return CTDT_SUBTABLE;
 
     case CTSF_TABLEPCT_COUNT:
@@ -2997,6 +2876,7 @@ ctables_function_domain (enum ctables_summary_function function)
     case CTSF_UTABLEPCT_SUM:
     case CTSF_UTABLEPCT_TOTALN:
     case CTSF_UTABLEPCT_VALIDN:
+    case CTSF_TABLE_ID:
       return CTDT_TABLE;
     }
 
@@ -3080,6 +2960,13 @@ ctables_function_is_pctsum (enum ctables_summary_function function)
     case CTSF_UTABLEPCT_COUNT:
     case CTSF_UTABLEPCT_TOTALN:
     case CTSF_UTABLEPCT_VALIDN:
+    case CTSF_ROW_ID:
+    case CTSF_COL_ID:
+    case CTSF_TABLE_ID:
+    case CTSF_SUBTABLE_ID:
+    case CTSF_LAYER_ID:
+    case CTSF_LAYERROW_ID:
+    case CTSF_LAYERCOL_ID:
       return false;
 
     case CTSF_COLPCT_SUM:
@@ -3114,6 +3001,15 @@ ctables_summary_value (const struct ctables_cell *cell,
     case CTSF_UCOUNT:
       return s->count;
 
+    case CTSF_ROW_ID:
+    case CTSF_COL_ID:
+    case CTSF_TABLE_ID:
+    case CTSF_SUBTABLE_ID:
+    case CTSF_LAYER_ID:
+    case CTSF_LAYERROW_ID:
+    case CTSF_LAYERCOL_ID:
+      return cell->domains[ctables_function_domain (ss->function)]->sequence;
+
     case CTSF_ROWPCT_COUNT:
     case CTSF_COLPCT_COUNT:
     case CTSF_TABLEPCT_COUNT:
@@ -3390,10 +3286,19 @@ ctables_cell_compare_3way (const void *a_, const void *b_, const void *aux_)
             {
               const char *a_label = var_lookup_value_label (var, a_val);
               const char *b_label = var_lookup_value_label (var, b_val);
-              int cmp = (a_label
-                         ? (b_label ? strcmp (a_label, b_label) : 1)
-                         : (b_label ? -1 : value_compare_3way (
-                              a_val, b_val, var_get_width (var))));
+              int cmp;
+              if (a_label)
+                {
+                  if (!b_label)
+                    return -1;
+                  cmp = strcmp (a_label, b_label);
+                }
+              else
+                {
+                  if (b_label)
+                    return 1;
+                  cmp = value_compare_3way (a_val, b_val, var_get_width (var));
+                }
               if (cmp)
                 return a_cv->category->sort_ascending ? cmp : -cmp;
             }
@@ -3406,6 +3311,25 @@ 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;
+}
+
 /* Algorithm:
 
    For each row:
@@ -4394,6 +4318,37 @@ ctables_cell_calculate_postcompute (const struct ctables_section *s,
   return ctables_pcexpr_evaluate (&ctx, pc->expr);
 }
 
+static char *
+ctables_format (double d, const struct fmt_spec *format,
+                const struct fmt_settings *settings)
+{
+  const union value v = { .f = d };
+  char *s = data_out_stretchy (&v, "UTF-8", format, settings, NULL);
+
+  /* The custom-currency specifications for NEQUAL, PAREN, and PCTPAREN don't
+     produce the results we want for negative numbers, putting the negative
+     sign in the wrong spot, before the prefix instead of after it.  We can't,
+     in fact, produce the desired results using a custom-currency
+     specification.  Instead, we postprocess the output, moving the negative
+     sign into place:
+
+         NEQUAL:   "-N=3"  => "N=-3"
+         PAREN:    "-(3)"  => "(-3)"
+         PCTPAREN: "-(3%)" => "(-3%)"
+
+     This transformation doesn't affect NEGPAREN. */
+  char *minus_src = strchr (s, '-');
+  if (minus_src && (minus_src == s || minus_src[-1] != 'E'))
+    {
+      char *n_equals = strstr (s, "N=");
+      char *lparen = strchr (s, '(');
+      char *minus_dst = n_equals ? n_equals + 1 : lparen;
+      if (minus_dst)
+        move_element (s, minus_dst - s + 1, 1, minus_src - s, minus_dst - s);
+    }
+  return s;
+}
+
 static void
 ctables_table_output (struct ctables *ct, struct ctables_table *t)
 {
@@ -4476,7 +4431,7 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
               {
                 struct ctables_section *s = &t->sections[j];
                 sections[n_sections++] = s;
-                n_total_cells += s->cells.count;
+                n_total_cells += hmap_count (&s->cells);
 
                 size_t depth = s->nests[a]->n;
                 max_depth = MAX (depth, max_depth);
@@ -4500,6 +4455,24 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
           struct ctables_cell_sort_aux aux = { .nest = nest, .a = a };
           sort (sorted, n_sorted, sizeof *sorted, ctables_cell_compare_3way, &aux);
 
+#if 0
+          if (a == PIVOT_AXIS_ROW)
+            {
+              size_t ids[N_CTDTS];
+              memset (ids, 0, sizeof ids);
+              for (size_t j = 0; j < n_sorted; j++)
+                {
+                  struct ctables_cell *cell = sorted[j];
+                  for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+                    {
+                      struct ctables_domain *domain = cell->domains[dt];
+                      if (!domain->sequence)
+                        domain->sequence = ++ids[dt];
+                    }
+                }
+            }
+#endif
+
 #if 0
           for (size_t j = 0; j < n_sorted; j++)
             {
@@ -4652,9 +4625,45 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
             }
           free (sorted);
           free (groups);
+          free (levels);
+          free (sections);
         }
     }
 
+  {
+    size_t n_total_cells = 0;
+    for (size_t j = 0; j < t->n_sections; j++)
+      n_total_cells += hmap_count (&t->sections[j].cells);
+
+    struct ctables_cell **sorted = xnmalloc (n_total_cells, sizeof *sorted);
+    size_t n_sorted = 0;
+    for (size_t j = 0; j < t->n_sections; j++)
+      {
+        const struct ctables_section *s = &t->sections[j];
+        struct ctables_cell *cell;
+        HMAP_FOR_EACH (cell, struct ctables_cell, node, &s->cells)
+          if (!cell->hide)
+            sorted[n_sorted++] = cell;
+      }
+    assert (n_sorted <= n_total_cells);
+    sort (sorted, n_sorted, sizeof *sorted, ctables_cell_compare_leaf_3way,
+          NULL);
+    size_t ids[N_CTDTS];
+    memset (ids, 0, sizeof ids);
+    for (size_t j = 0; j < n_sorted; j++)
+      {
+        struct ctables_cell *cell = sorted[j];
+        for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+          {
+            struct ctables_domain *domain = cell->domains[dt];
+            if (!domain->sequence)
+              domain->sequence = ++ids[dt];
+          }
+      }
+
+    free (sorted);
+  }
+
   for (size_t i = 0; i < t->n_sections; i++)
     {
       struct ctables_section *s = &t->sections[i];
@@ -4718,17 +4727,14 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
               else if (d == SYSMIS && ct->missing)
                 value = pivot_value_new_user_text (ct->missing, SIZE_MAX);
               else if (is_ctables_format)
-                {
-                  char *s = data_out_stretchy (&(union value) { .f = d },
-                                               "UTF-8", &format,
-                                               &ct->ctables_formats, NULL);
-                  value = pivot_value_new_user_text_nocopy (s);
-                }
+                value = pivot_value_new_user_text_nocopy (
+                  ctables_format (d, &format, &ct->ctables_formats));
               else
                 {
                   value = pivot_value_new_number (d);
                   value->numeric.format = format;
                 }
+              /* XXX should text values be right-justified? */
               pivot_table_put (pt, dindexes, n_dindexes, value);
             }
         }
@@ -4989,10 +4995,13 @@ ctables_prepare_table (struct ctables_table *t)
                   listwise_vars[n++] = other_nest->vars[other_nest->scale_idx];
                 }
             }
-          for (size_t j = 0; j < N_CSVS; j++)
+          for (enum ctables_summary_variant sv = 0; sv < N_CSVS; sv++)
             {
-              nest->specs[j].listwise_vars = listwise_vars;
-              nest->specs[j].n_listwise_vars = n;
+              if (sv > 0)
+                listwise_vars = xmemdup (listwise_vars,
+                                         n * sizeof *listwise_vars);
+              nest->specs[sv].listwise_vars = listwise_vars;
+              nest->specs[sv].n_listwise_vars = n;
             }
         }
     }
@@ -5035,6 +5044,7 @@ ctables_prepare_table (struct ctables_table *t)
           j++;
         }
     }
+  free (items);
 
 #if 0
   for (size_t j = 0; j < merged->n; j++)
@@ -5335,6 +5345,24 @@ ctables_section_clear (struct ctables_section *s)
     }
 }
 
+static void
+ctables_section_uninit (struct ctables_section *s)
+{
+  ctables_section_clear (s);
+
+  for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+    {
+      struct ctables_nest *nest = s->nests[a];
+      for (size_t i = 0; i < nest->n; i++)
+        hmap_destroy (&s->occurrences[a][i]);
+      free (s->occurrences[a]);
+    }
+
+  hmap_destroy (&s->cells);
+  for (size_t i = 0; i < N_CTDTS; i++)
+    hmap_destroy (&s->domains[i]);
+}
+
 static void
 ctables_table_clear (struct ctables_table *t)
 {
@@ -5501,7 +5529,7 @@ struct operator
   };
 
 static const struct operator *
-ctable_pcexpr_match_operator (struct lexer *lexer,
+ctables_pcexpr_match_operator (struct lexer *lexer,
                               const struct operator ops[], size_t n_ops)
 {
   for (const struct operator *op = ops; op < ops + n_ops; op++)
@@ -5517,7 +5545,7 @@ ctable_pcexpr_match_operator (struct lexer *lexer,
 }
 
 static struct ctables_pcexpr *
-ctable_pcexpr_parse_binary_operators__ (
+ctables_pcexpr_parse_binary_operators__ (
   struct lexer *lexer, struct dictionary *dict,
   const struct operator ops[], size_t n_ops,
   parse_recursively_func *parse_next_level,
@@ -5526,7 +5554,7 @@ ctable_pcexpr_parse_binary_operators__ (
   for (int op_count = 0; ; op_count++)
     {
       const struct operator *op
-        = ctable_pcexpr_match_operator (lexer, ops, n_ops);
+        = ctables_pcexpr_match_operator (lexer, ops, n_ops);
       if (!op)
         {
           if (op_count > 1 && chain_warning)
@@ -5547,23 +5575,22 @@ ctable_pcexpr_parse_binary_operators__ (
 }
 
 static struct ctables_pcexpr *
-ctable_pcexpr_parse_binary_operators (struct lexer *lexer,
-                                      struct dictionary *dict,
-                                      const struct operator ops[], size_t n_ops,
-                                      parse_recursively_func *parse_next_level,
-                                      const char *chain_warning)
+ctables_pcexpr_parse_binary_operators (
+  struct lexer *lexer, struct dictionary *dict,
+  const struct operator ops[], size_t n_ops,
+  parse_recursively_func *parse_next_level, const char *chain_warning)
 {
   struct ctables_pcexpr *lhs = parse_next_level (lexer, dict);
   if (!lhs)
     return NULL;
 
-  return ctable_pcexpr_parse_binary_operators__ (lexer, dict, ops, n_ops,
+  return ctables_pcexpr_parse_binary_operators__ (lexer, dict, ops, n_ops,
                                                  parse_next_level,
                                                  chain_warning, lhs);
 }
 
-static struct ctables_pcexpr *ctable_pcexpr_parse_add (struct lexer *,
-                                                       struct dictionary *);
+static struct ctables_pcexpr *ctables_pcexpr_parse_add (struct lexer *,
+                                                        struct dictionary *);
 
 static struct ctables_pcexpr
 ctpo_cat_nrange (double low, double high)
@@ -5584,7 +5611,7 @@ ctpo_cat_srange (struct substring low, struct substring high)
 }
 
 static struct ctables_pcexpr *
-ctable_pcexpr_parse_primary (struct lexer *lexer, struct dictionary *dict)
+ctables_pcexpr_parse_primary (struct lexer *lexer, struct dictionary *dict)
 {
   int start_ofs = lex_ofs (lexer);
   struct ctables_pcexpr e;
@@ -5630,7 +5657,7 @@ ctable_pcexpr_parse_primary (struct lexer *lexer, struct dictionary *dict)
             }
           else
             {
-              if (lex_force_num (lexer))
+              if (!lex_force_num (lexer))
                 return false;
               e = ctpo_cat_nrange (-DBL_MAX, lex_number (lexer));
               lex_get (lexer);
@@ -5701,7 +5728,7 @@ ctable_pcexpr_parse_primary (struct lexer *lexer, struct dictionary *dict)
     }
   else if (lex_match (lexer, T_LPAREN))
     {
-      struct ctables_pcexpr *ep = ctable_pcexpr_parse_add (lexer, dict);
+      struct ctables_pcexpr *ep = ctables_pcexpr_parse_add (lexer, dict);
       if (!ep)
         return NULL;
       if (!lex_force_match (lexer, T_RPAREN))
@@ -5735,7 +5762,7 @@ ctables_pcexpr_allocate_neg (struct ctables_pcexpr *sub,
 }
 
 static struct ctables_pcexpr *
-ctable_pcexpr_parse_exp (struct lexer *lexer, struct dictionary *dict)
+ctables_pcexpr_parse_exp (struct lexer *lexer, struct dictionary *dict)
 {
   static const struct operator op = { T_EXP, CTPO_POW };
 
@@ -5745,9 +5772,9 @@ ctable_pcexpr_parse_exp (struct lexer *lexer, struct dictionary *dict)
       "To disable this warning, insert parentheses.");
 
   if (lex_token (lexer) != T_NEG_NUM || lex_next_token (lexer, 1) != T_EXP)
-    return ctable_pcexpr_parse_binary_operators (lexer, dict, &op, 1,
-                                                 ctable_pcexpr_parse_primary,
-                                                 chain_warning);
+    return ctables_pcexpr_parse_binary_operators (lexer, dict, &op, 1,
+                                                  ctables_pcexpr_parse_primary,
+                                                  chain_warning);
 
   /* Special case for situations like "-5**6", which must be parsed as
      -(5**6). */
@@ -5761,9 +5788,9 @@ ctable_pcexpr_parse_exp (struct lexer *lexer, struct dictionary *dict)
   };
   lex_get (lexer);
 
-  struct ctables_pcexpr *node = ctable_pcexpr_parse_binary_operators__ (
+  struct ctables_pcexpr *node = ctables_pcexpr_parse_binary_operators__ (
     lexer, dict, &op, 1,
-    ctable_pcexpr_parse_primary, chain_warning, lhs);
+    ctables_pcexpr_parse_primary, chain_warning, lhs);
   if (!node)
     return NULL;
 
@@ -5772,13 +5799,13 @@ ctable_pcexpr_parse_exp (struct lexer *lexer, struct dictionary *dict)
 
 /* Parses the unary minus level. */
 static struct ctables_pcexpr *
-ctable_pcexpr_parse_neg (struct lexer *lexer, struct dictionary *dict)
+ctables_pcexpr_parse_neg (struct lexer *lexer, struct dictionary *dict)
 {
   int start_ofs = lex_ofs (lexer);
   if (!lex_match (lexer, T_DASH))
-    return ctable_pcexpr_parse_exp (lexer, dict);
+    return ctables_pcexpr_parse_exp (lexer, dict);
 
-  struct ctables_pcexpr *inner = ctable_pcexpr_parse_neg (lexer, dict);
+  struct ctables_pcexpr *inner = ctables_pcexpr_parse_neg (lexer, dict);
   if (!inner)
     return NULL;
 
@@ -5787,7 +5814,7 @@ ctable_pcexpr_parse_neg (struct lexer *lexer, struct dictionary *dict)
 
 /* Parses the multiplication and division level. */
 static struct ctables_pcexpr *
-ctable_pcexpr_parse_mul (struct lexer *lexer, struct dictionary *dict)
+ctables_pcexpr_parse_mul (struct lexer *lexer, struct dictionary *dict)
 {
   static const struct operator ops[] =
     {
@@ -5795,14 +5822,14 @@ ctable_pcexpr_parse_mul (struct lexer *lexer, struct dictionary *dict)
       { T_SLASH, CTPO_DIV },
     };
 
-  return ctable_pcexpr_parse_binary_operators (lexer, dict, ops,
+  return ctables_pcexpr_parse_binary_operators (lexer, dict, ops,
                                                sizeof ops / sizeof *ops,
-                                               ctable_pcexpr_parse_neg, NULL);
+                                               ctables_pcexpr_parse_neg, NULL);
 }
 
 /* Parses the addition and subtraction level. */
 static struct ctables_pcexpr *
-ctable_pcexpr_parse_add (struct lexer *lexer, struct dictionary *dict)
+ctables_pcexpr_parse_add (struct lexer *lexer, struct dictionary *dict)
 {
   static const struct operator ops[] =
     {
@@ -5811,9 +5838,9 @@ ctable_pcexpr_parse_add (struct lexer *lexer, struct dictionary *dict)
       { T_NEG_NUM, CTPO_ADD },
     };
 
-  return ctable_pcexpr_parse_binary_operators (lexer, dict,
+  return ctables_pcexpr_parse_binary_operators (lexer, dict,
                                                ops, sizeof ops / sizeof *ops,
-                                               ctable_pcexpr_parse_mul, NULL);
+                                               ctables_pcexpr_parse_mul, NULL);
 }
 
 static struct ctables_postcompute *
@@ -5853,10 +5880,11 @@ ctables_parse_pcompute (struct lexer *lexer, struct dictionary *dict,
     }
 
   int expr_start = lex_ofs (lexer);
-  struct ctables_pcexpr *expr = ctable_pcexpr_parse_add (lexer, dict);
+  struct ctables_pcexpr *expr = ctables_pcexpr_parse_add (lexer, dict);
   int expr_end = lex_ofs (lexer) - 1;
   if (!expr || !lex_force_match (lexer, T_RPAREN))
     {
+      ctables_pcexpr_destroy (expr);
       free (name);
       return false;
     }
@@ -6471,15 +6499,21 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
       if (n_summaries > 1)
         {
           msg (SE, _("Summaries may appear only on one axis."));
-          if (summaries[PIVOT_AXIS_ROW])
-            msg_at (SN, summaries[PIVOT_AXIS_ROW]->loc,
-                    _("This variable on the rows axis has a summary."));
-          if (summaries[PIVOT_AXIS_COLUMN])
-            msg_at (SN, summaries[PIVOT_AXIS_COLUMN]->loc,
-                    _("This variable on the columns axis has a summary."));
-          if (summaries[PIVOT_AXIS_LAYER])
-            msg_at (SN, summaries[PIVOT_AXIS_LAYER]->loc,
-                    _("This variable on the layers axis has a summary."));
+          for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+            if (summaries[a])
+              {
+                msg_at (SN, summaries[a]->loc,
+                        a == PIVOT_AXIS_ROW
+                        ? _("This variable on the rows axis has a summary.")
+                        : a == PIVOT_AXIS_COLUMN
+                        ? _("This variable on the columns axis has a summary.")
+                        : _("This variable on the layers axis has a summary."));
+                if (scales[a])
+                  msg_at (SN, summaries[a]->loc,
+                          _("This is a scale variable, so it always has a "
+                            "summary even if the syntax does not explicitly "
+                            "specify one."));
+              }
           goto error;
         }
       for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)