add namespace to some names
[pspp] / src / language / stats / ctables.c
index 072b9f83a8d4f688d65c2a095f4498e454597f5a..5df2435b7b17f7eb785a4800a68738e525905613 100644 (file)
@@ -733,7 +733,7 @@ ctables_summary_spec_clone (struct ctables_summary_spec *dst,
                             const struct ctables_summary_spec *src)
 {
   *dst = *src;
-  dst->label = xstrdup (src->label);
+  dst->label = xstrdup_if_nonnull (src->label);
 }
 
 static void
@@ -3363,17 +3363,75 @@ merge_item_compare_3way (const struct merge_item *a, const struct merge_item *b)
 }
 
 static struct pivot_value *
-ctables_category_create_label (const struct ctables_category *cat,
-                               const struct variable *var,
-                               const union value *value)
+ctables_category_create_label__ (const struct ctables_category *cat,
+                                 const struct variable *var,
+                                 const union value *value)
 {
   return (cat->type == CCT_TOTAL || cat->type == CCT_SUBTOTAL
           ? pivot_value_new_user_text (cat->total_label, SIZE_MAX)
-          : cat->type == CCT_POSTCOMPUTE && cat->pc->label
-          ? pivot_value_new_user_text (cat->pc->label, SIZE_MAX)
           : pivot_value_new_var_value (var, value));
 }
 
+static struct pivot_value *
+ctables_postcompute_label (const struct ctables_categories *cats,
+                           const struct ctables_category *cat,
+                           const struct variable *var,
+                           const union value *value)
+{
+  struct substring in = ss_cstr (cat->pc->label);
+  struct substring target = ss_cstr (")LABEL[");
+
+  struct string out = DS_EMPTY_INITIALIZER;
+  for (;;)
+    {
+      size_t chunk = ss_find_substring (in, target);
+      if (chunk == SIZE_MAX)
+        {
+          if (ds_is_empty (&out))
+            return pivot_value_new_user_text (in.string, in.length);
+          else
+            {
+              ds_put_substring (&out, in);
+              return pivot_value_new_user_text_nocopy (ds_steal_cstr (&out));
+            }
+        }
+
+      ds_put_substring (&out, ss_head (in, chunk));
+      ss_advance (&in, chunk + target.length);
+
+      struct substring idx_s;
+      if (!ss_get_until (&in, ']', &idx_s))
+        goto error;
+      char *tail;
+      long int idx = strtol (idx_s.string, &tail, 10);
+      if (idx < 1 || idx > cats->n_cats || tail != ss_end (idx_s))
+        goto error;
+
+      struct ctables_category *cat2 = &cats->cats[idx - 1];
+      struct pivot_value *label2
+        = ctables_category_create_label__ (cat2, var, value);
+      char *label2_s = pivot_value_to_string_defaults (label2);
+      ds_put_cstr (&out, label2_s);
+      free (label2_s);
+      pivot_value_destroy (label2);
+    }
+
+error:
+  ds_destroy (&out);
+  return pivot_value_new_user_text (cat->pc->label, SIZE_MAX);
+}
+
+static struct pivot_value *
+ctables_category_create_label (const struct ctables_categories *cats,
+                               const struct ctables_category *cat,
+                               const struct variable *var,
+                               const union value *value)
+{
+  return (cat->type == CCT_POSTCOMPUTE && cat->pc->label
+          ? ctables_postcompute_label (cats, cat, var, value)
+          : ctables_category_create_label__ (cat, var, value));
+}
+
 static struct ctables_value *
 ctables_value_find__ (struct ctables_table *t, const union value *value,
                       int width, unsigned int hash)
@@ -3488,6 +3546,7 @@ struct ctables_pcexpr_evaluate_ctx
     const struct ctables_categories *cats;
     enum pivot_axis_type pc_a;
     size_t pc_a_idx;
+    size_t summary_idx;
   };
 
 static double ctables_pcexpr_evaluate (
@@ -3568,8 +3627,8 @@ found: ;
   const struct ctables_table *t = s->table;
   const struct ctables_nest *specs_nest = s->nests[t->summary_axis];
   const struct ctables_summary_spec_set *specs = &specs_nest->specs[tc->sv];
-  size_t j = 0 /* XXX */;
-  return ctables_summary_value (tc, &tc->summaries[j], &specs->specs[j]);
+  return ctables_summary_value (tc, &tc->summaries[ctx->summary_idx],
+                                &specs->specs[ctx->summary_idx]);
 }
 
 static double
@@ -3639,27 +3698,61 @@ ctables_pcexpr_evaluate (const struct ctables_pcexpr_evaluate_ctx *ctx,
   NOT_REACHED ();
 }
 
+/* XXX what if there is a postcompute in more than one dimension?? */
+static const struct ctables_postcompute *
+ctables_cell_postcompute (const struct ctables_section *s,
+                          const struct ctables_cell *cell,
+                          enum pivot_axis_type *pc_a_p,
+                          size_t *pc_a_idx_p)
+{
+  assert (cell->postcompute);
+  for (enum pivot_axis_type pc_a = 0; ; pc_a++)
+    {
+      assert (pc_a < PIVOT_N_AXES);
+      for (size_t pc_a_idx = 0; pc_a_idx < s->nests[pc_a]->n; pc_a_idx++)
+        {
+          const struct ctables_cell_value *cv = &cell->axes[pc_a].cvs[pc_a_idx];
+          if (cv->category->type == CCT_POSTCOMPUTE)
+            {
+              if (pc_a_p)
+                *pc_a_p = pc_a;
+              if (pc_a_idx_p)
+                *pc_a_idx_p = pc_a_idx;
+              return cv->category->pc;
+            }
+        }
+    }
+
+  NOT_REACHED ();
+}
+
 static double
 ctables_cell_calculate_postcompute (const struct ctables_section *s,
-                                    const struct ctables_cell *cell)
+                                    const struct ctables_cell *cell,
+                                    const struct ctables_summary_spec *ss,
+                                    struct fmt_spec *format,
+                                    bool *is_ctables_format,
+                                    size_t summary_idx)
 {
   enum pivot_axis_type pc_a;
   size_t pc_a_idx;
-  const struct ctables_postcompute *pc;
-  for (pc_a = 0; ; pc_a++)
+  const struct ctables_postcompute *pc = ctables_cell_postcompute (
+    s, cell, &pc_a, &pc_a_idx);
+
+  if (pc->specs)
     {
-      assert (pc_a < PIVOT_N_AXES);
-      for (pc_a_idx = 0; pc_a_idx < s->nests[pc_a]->n; pc_a_idx++)
+      for (size_t i = 0; i < pc->specs->n; i++)
         {
-          const struct ctables_cell_value *cv = &cell->axes[pc_a].cvs[pc_a_idx];
-          if (cv->category->type == CCT_POSTCOMPUTE)
+          const struct ctables_summary_spec *ss2 = &pc->specs->specs[i];
+          if (ss->function == ss2->function
+              && ss->percentile == ss2->percentile)
             {
-              pc = cv->category->pc;
-              goto found;
+              *format = ss2->format;
+              *is_ctables_format = ss2->is_ctables_format;
+              break;
             }
         }
     }
-found: ;
 
   const struct variable *var = s->nests[pc_a]->vars[pc_a_idx];
   const struct ctables_categories *cats = s->table->categories[
@@ -3670,6 +3763,7 @@ found: ;
     .cats = cats,
     .pc_a = pc_a,
     .pc_a_idx = pc_a_idx,
+    .summary_idx = summary_idx,
   };
   return ctables_pcexpr_evaluate (&ctx, pc->expr);
 }
@@ -3720,7 +3814,8 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
           const struct ctables_category *cat = ctables_categories_match (c, &value->value, var);
           assert (cat != NULL);
           pivot_category_create_leaf (d->root, ctables_category_create_label (
-                                        cat, t->clabels_example, &value->value));
+                                        c, cat, t->clabels_example,
+                                        &value->value));
         }
     }
 
@@ -3912,8 +4007,9 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
                       else if (level->type == CTL_CATEGORY)
                         {
                           const struct ctables_cell_value *cv = &cell->axes[a].cvs[level->var_idx];
-                          label = ctables_category_create_label (cv->category,
-                                                                 var, &cv->value);
+                          label = ctables_category_create_label (
+                            t->categories[var_get_dict_index (var)],
+                            cv->category, var, &cv->value);
                         }
                       else
                         NOT_REACHED ();
@@ -3974,15 +4070,18 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
 
               const struct ctables_summary_spec *ss = &specs->specs[j];
 
+              struct fmt_spec format = specs->specs[j].format;
+              bool is_ctables_format = ss->is_ctables_format;
               double d = (cell->postcompute
-                          ? ctables_cell_calculate_postcompute (s, cell)
-                          : ctables_summary_value (cell, &cell->summaries[j], ss));
+                          ? ctables_cell_calculate_postcompute (
+                            s, cell, ss, &format, &is_ctables_format, j)
+                          : ctables_summary_value (cell, &cell->summaries[j],
+                                                   ss));
+
               struct pivot_value *value;
               if (ct->hide_threshold != 0
                   && d < ct->hide_threshold
-                  && (cell->postcompute
-                      ? false /* XXX */
-                      : ctables_summary_function_is_count (ss->function)))
+                  && ctables_summary_function_is_count (ss->function))
                 {
                   value = pivot_value_new_user_text_nocopy (
                     xasprintf ("<%d", ct->hide_threshold));
@@ -3991,18 +4090,17 @@ ctables_table_output (struct ctables *ct, struct ctables_table *t)
                 value = pivot_value_new_user_text (ct->zero, SIZE_MAX);
               else if (d == SYSMIS && ct->missing)
                 value = pivot_value_new_user_text (ct->missing, SIZE_MAX);
-              else if (specs->specs[j].is_ctables_format)
+              else if (is_ctables_format)
                 {
                   char *s = data_out_stretchy (&(union value) { .f = d },
-                                               "UTF-8",
-                                               &specs->specs[j].format,
+                                               "UTF-8", &format,
                                                &ct->ctables_formats, NULL);
                   value = pivot_value_new_user_text_nocopy (s);
                 }
               else
                 {
                   value = pivot_value_new_number (d);
-                  value->numeric.format = specs->specs[j].format;
+                  value->numeric.format = format;
                 }
               pivot_table_put (pt, dindexes, n_dindexes, value);
             }
@@ -4615,7 +4713,8 @@ struct operator
   };
 
 static const struct operator *
-match_operator (struct lexer *lexer, const struct operator ops[], size_t n_ops)
+ctable_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++)
     if (lex_token (lexer) == op->token)
@@ -4630,15 +4729,16 @@ match_operator (struct lexer *lexer, const struct operator ops[], size_t n_ops)
 }
 
 static struct 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)
+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, struct ctables_pcexpr *lhs)
 {
   for (int op_count = 0; ; op_count++)
     {
-      const struct operator *op = match_operator (lexer, ops, n_ops);
+      const struct operator *op
+        = ctable_pcexpr_match_operator (lexer, ops, n_ops);
       if (!op)
         {
           if (op_count > 1 && chain_warning)
@@ -4659,20 +4759,23 @@ parse_binary_operators__ (struct lexer *lexer, struct dictionary *dict,
 }
 
 static struct 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)
+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)
 {
   struct ctables_pcexpr *lhs = parse_next_level (lexer, dict);
   if (!lhs)
     return NULL;
 
-  return parse_binary_operators__ (lexer, dict, ops, n_ops, parse_next_level,
-                                   chain_warning, lhs);
+  return ctable_pcexpr_parse_binary_operators__ (lexer, dict, ops, n_ops,
+                                                 parse_next_level,
+                                                 chain_warning, lhs);
 }
 
-static struct ctables_pcexpr *parse_add (struct lexer *, struct dictionary *);
+static struct ctables_pcexpr *ctable_pcexpr_parse_add (struct lexer *,
+                                                       struct dictionary *);
 
 static struct ctables_pcexpr
 ctpo_cat_range (double low, double high)
@@ -4684,7 +4787,7 @@ ctpo_cat_range (double low, double high)
 }
 
 static struct ctables_pcexpr *
-parse_primary (struct lexer *lexer, struct dictionary *dict)
+ctable_pcexpr_parse_primary (struct lexer *lexer, struct dictionary *dict)
 {
   int start_ofs = lex_ofs (lexer);
   struct ctables_pcexpr e;
@@ -4768,7 +4871,7 @@ parse_primary (struct lexer *lexer, struct dictionary *dict)
     }
   else if (lex_match (lexer, T_LPAREN))
     {
-      struct ctables_pcexpr *ep = parse_add (lexer, dict);
+      struct ctables_pcexpr *ep = ctable_pcexpr_parse_add (lexer, dict);
       if (!ep)
         return NULL;
       if (!lex_force_match (lexer, T_RPAREN))
@@ -4802,7 +4905,7 @@ ctables_pcexpr_allocate_neg (struct ctables_pcexpr *sub,
 }
 
 static struct ctables_pcexpr *
-parse_exp (struct lexer *lexer, struct dictionary *dict)
+ctable_pcexpr_parse_exp (struct lexer *lexer, struct dictionary *dict)
 {
   static const struct operator op = { T_EXP, CTPO_POW };
 
@@ -4812,8 +4915,9 @@ 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 parse_binary_operators (lexer, dict, &op, 1,
-                                   parse_primary, chain_warning);
+    return ctable_pcexpr_parse_binary_operators (lexer, dict, &op, 1,
+                                                 ctable_pcexpr_parse_primary,
+                                                 chain_warning);
 
   /* Special case for situations like "-5**6", which must be parsed as
      -(5**6). */
@@ -4827,8 +4931,9 @@ parse_exp (struct lexer *lexer, struct dictionary *dict)
   };
   lex_get (lexer);
 
-  struct ctables_pcexpr *node = parse_binary_operators__ (
-    lexer, dict, &op, 1, parse_primary, chain_warning, lhs);
+  struct ctables_pcexpr *node = ctable_pcexpr_parse_binary_operators__ (
+    lexer, dict, &op, 1,
+    ctable_pcexpr_parse_primary, chain_warning, lhs);
   if (!node)
     return NULL;
 
@@ -4837,13 +4942,13 @@ parse_exp (struct lexer *lexer, struct dictionary *dict)
 
 /* Parses the unary minus level. */
 static struct ctables_pcexpr *
-parse_neg (struct lexer *lexer, struct dictionary *dict)
+ctable_pcexpr_parse_neg (struct lexer *lexer, struct dictionary *dict)
 {
   int start_ofs = lex_ofs (lexer);
   if (!lex_match (lexer, T_DASH))
-    return parse_exp (lexer, dict);
+    return ctable_pcexpr_parse_exp (lexer, dict);
 
-  struct ctables_pcexpr *inner = parse_neg (lexer, dict);
+  struct ctables_pcexpr *inner = ctable_pcexpr_parse_neg (lexer, dict);
   if (!inner)
     return NULL;
 
@@ -4852,7 +4957,7 @@ parse_neg (struct lexer *lexer, struct dictionary *dict)
 
 /* Parses the multiplication and division level. */
 static struct ctables_pcexpr *
-parse_mul (struct lexer *lexer, struct dictionary *dict)
+ctable_pcexpr_parse_mul (struct lexer *lexer, struct dictionary *dict)
 {
   static const struct operator ops[] =
     {
@@ -4860,13 +4965,14 @@ parse_mul (struct lexer *lexer, struct dictionary *dict)
       { T_SLASH, CTPO_DIV },
     };
 
-  return parse_binary_operators (lexer, dict, ops, sizeof ops / sizeof *ops,
-                                 parse_neg, NULL);
+  return ctable_pcexpr_parse_binary_operators (lexer, dict, ops,
+                                               sizeof ops / sizeof *ops,
+                                               ctable_pcexpr_parse_neg, NULL);
 }
 
 /* Parses the addition and subtraction level. */
 static struct ctables_pcexpr *
-parse_add (struct lexer *lexer, struct dictionary *dict)
+ctable_pcexpr_parse_add (struct lexer *lexer, struct dictionary *dict)
 {
   static const struct operator ops[] =
     {
@@ -4875,8 +4981,9 @@ parse_add (struct lexer *lexer, struct dictionary *dict)
       { T_NEG_NUM, CTPO_ADD },
     };
 
-  return parse_binary_operators (lexer, dict, ops, sizeof ops / sizeof *ops,
-                                 parse_mul, NULL);
+  return ctable_pcexpr_parse_binary_operators (lexer, dict,
+                                               ops, sizeof ops / sizeof *ops,
+                                               ctable_pcexpr_parse_mul, NULL);
 }
 
 static struct ctables_postcompute *
@@ -4911,7 +5018,7 @@ ctables_parse_pcompute (struct lexer *lexer, struct dictionary *dict,
     }
 
   int expr_start = lex_ofs (lexer);
-  struct ctables_pcexpr *expr = parse_add (lexer, dict);
+  struct ctables_pcexpr *expr = ctable_pcexpr_parse_add (lexer, dict);
   int expr_end = lex_ofs (lexer) - 1;
   if (!expr || !lex_force_match (lexer, T_RPAREN))
     {
@@ -4978,9 +5085,8 @@ ctables_parse_pproperties_format (struct lexer *lexer,
 
       /* Parse format. */
       struct fmt_spec format;
-      if (!parse_format_specifier (lexer, &format)
-          || !fmt_check_output (&format)
-          || !fmt_check_type_compat (&format, VAL_NUMERIC))
+      bool is_ctables_format;
+      if (!parse_ctables_format_specifier (lexer, &format, &is_ctables_format))
         goto error;
 
       if (sss->n >= sss->allocated)
@@ -4990,6 +5096,7 @@ ctables_parse_pproperties_format (struct lexer *lexer,
         .function = function,
         .percentile = percentile,
         .format = format,
+        .is_ctables_format = is_ctables_format,
       };
     }
   return true;