+static void
+ctables_value_insert (struct ctables_table *t, const union value *value,
+ int width)
+{
+ unsigned int hash = value_hash (value, width, 0);
+ struct ctables_value *clv = ctables_value_find__ (t, value, width, hash);
+ if (!clv)
+ {
+ clv = xmalloc (sizeof *clv);
+ value_clone (&clv->value, value, width);
+ hmap_insert (&t->clabels_values_map, &clv->node, hash);
+ }
+}
+
+static struct ctables_value *
+ctables_value_find (struct ctables_table *t,
+ const union value *value, int width)
+{
+ return ctables_value_find__ (t, value, width,
+ value_hash (value, width, 0));
+}
+
+static void
+ctables_table_add_section (struct ctables_table *t, enum pivot_axis_type a,
+ size_t ix[PIVOT_N_AXES])
+{
+ if (a < PIVOT_N_AXES)
+ {
+ size_t limit = MAX (t->stacks[a].n, 1);
+ for (ix[a] = 0; ix[a] < limit; ix[a]++)
+ ctables_table_add_section (t, a + 1, ix);
+ }
+ else
+ {
+ struct ctables_section *s = &t->sections[t->n_sections++];
+ *s = (struct ctables_section) {
+ .table = t,
+ .cells = HMAP_INITIALIZER (s->cells),
+ };
+ for (a = 0; a < PIVOT_N_AXES; a++)
+ if (t->stacks[a].n)
+ {
+ struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+ s->nests[a] = nest;
+ s->occurrences[a] = xnmalloc (nest->n, sizeof *s->occurrences[a]);
+ for (size_t i = 0; i < nest->n; i++)
+ hmap_init (&s->occurrences[a][i]);
+ }
+ for (enum ctables_area_type at = 0; at < N_CTATS; at++)
+ hmap_init (&s->areas[at]);
+ }
+}
+
+static double
+ctpo_add (double a, double b)
+{
+ return a + b;
+}
+
+static double
+ctpo_sub (double a, double b)
+{
+ return a - b;
+}
+
+static double
+ctpo_mul (double a, double b)
+{
+ return a * b;
+}
+
+static double
+ctpo_div (double a, double b)
+{
+ return b ? a / b : SYSMIS;
+}
+
+static double
+ctpo_pow (double a, double b)
+{
+ int save_errno = errno;
+ errno = 0;
+ double result = pow (a, b);
+ if (errno)
+ result = SYSMIS;
+ errno = save_errno;
+ return result;
+}
+
+static double
+ctpo_neg (double a, double b UNUSED)
+{
+ return -a;
+}
+
+struct ctables_pcexpr_evaluate_ctx
+ {
+ const struct ctables_cell *cell;
+ const struct ctables_section *section;
+ const struct ctables_categories *cats;
+ enum pivot_axis_type pc_a;
+ size_t pc_a_idx;
+ size_t summary_idx;
+ enum fmt_type parse_format;
+ };
+
+static double ctables_pcexpr_evaluate (
+ const struct ctables_pcexpr_evaluate_ctx *, const struct ctables_pcexpr *);
+
+static double
+ctables_pcexpr_evaluate_nonterminal (
+ const struct ctables_pcexpr_evaluate_ctx *ctx,
+ const struct ctables_pcexpr *e, size_t n_args,
+ double evaluate (double, double))
+{
+ double args[2] = { 0, 0 };
+ for (size_t i = 0; i < n_args; i++)
+ {
+ args[i] = ctables_pcexpr_evaluate (ctx, e->subs[i]);
+ if (!isfinite (args[i]) || args[i] == SYSMIS)
+ return SYSMIS;
+ }
+ return evaluate (args[0], args[1]);
+}
+
+static double
+ctables_pcexpr_evaluate_category (const struct ctables_pcexpr_evaluate_ctx *ctx,
+ const struct ctables_cell_value *pc_cv)
+{
+ const struct ctables_section *s = ctx->section;
+
+ size_t hash = 0;
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = s->nests[a];
+ for (size_t i = 0; i < nest->n; i++)
+ if (i != nest->scale_idx)
+ {
+ const struct ctables_cell_value *cv
+ = (a == ctx->pc_a && i == ctx->pc_a_idx ? pc_cv
+ : &ctx->cell->axes[a].cvs[i]);
+ hash = hash_pointer (cv->category, hash);
+ if (cv->category->type != CCT_TOTAL
+ && cv->category->type != CCT_SUBTOTAL
+ && cv->category->type != CCT_POSTCOMPUTE)
+ hash = value_hash (&cv->value,
+ var_get_width (nest->vars[i]), hash);
+ }
+ }
+
+ struct ctables_cell *tc;
+ HMAP_FOR_EACH_WITH_HASH (tc, struct ctables_cell, node, hash, &s->cells)
+ {
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = s->nests[a];
+ for (size_t i = 0; i < nest->n; i++)
+ if (i != nest->scale_idx)
+ {
+ const struct ctables_cell_value *p_cv
+ = (a == ctx->pc_a && i == ctx->pc_a_idx ? pc_cv
+ : &ctx->cell->axes[a].cvs[i]);
+ const struct ctables_cell_value *t_cv = &tc->axes[a].cvs[i];
+ if (p_cv->category != t_cv->category
+ || (p_cv->category->type != CCT_TOTAL
+ && p_cv->category->type != CCT_SUBTOTAL
+ && p_cv->category->type != CCT_POSTCOMPUTE
+ && !value_equal (&p_cv->value,
+ &t_cv->value,
+ var_get_width (nest->vars[i]))))
+ goto not_equal;
+ }
+ }