- struct ctables_category *best = NULL;
- size_t n_subtotals = 0;
- for (size_t i = 0; i < cats->n_cats; i++)
- {
- struct ctables_category *cat = &cats->cats[i];
- switch (e->op)
- {
- case CTPO_CAT_NUMBER:
- if (cat->type == CCT_NUMBER && cat->number == e->number)
- best = cat;
- break;
-
- case CTPO_CAT_STRING:
- if (cat->type == CCT_STRING && ss_equals (cat->string, e->string))
- best = cat;
- break;
-
- case CTPO_CAT_NRANGE:
- if (cat->type == CCT_NRANGE
- && cat->nrange[0] == e->nrange[0]
- && cat->nrange[1] == e->nrange[1])
- best = cat;
- break;
-
- case CTPO_CAT_SRANGE:
- if (cat->type == CCT_SRANGE
- && nullable_substring_equal (&cat->srange[0], &e->srange[0])
- && nullable_substring_equal (&cat->srange[1], &e->srange[1]))
- best = cat;
- break;
-
- case CTPO_CAT_MISSING:
- if (cat->type == CCT_MISSING)
- best = cat;
- break;
-
- case CTPO_CAT_OTHERNM:
- if (cat->type == CCT_OTHERNM)
- best = cat;
- break;
-
- case CTPO_CAT_SUBTOTAL:
- if (cat->type == CCT_SUBTOTAL)
- {
- n_subtotals++;
- if (e->subtotal_index == n_subtotals)
- return cat;
- else if (e->subtotal_index == 0)
- best = cat;
- }
- break;
-
- case CTPO_CAT_TOTAL:
- if (cat->type == CCT_TOTAL)
- return cat;
- break;
-
- case CTPO_CONSTANT:
- case CTPO_ADD:
- case CTPO_SUB:
- case CTPO_MUL:
- case CTPO_DIV:
- case CTPO_POW:
- case CTPO_NEG:
- NOT_REACHED ();
- }
- }
- if (e->op == CTPO_CAT_SUBTOTAL && e->subtotal_index == 0 && n_subtotals > 1)
- return NULL;
- return best;
-}
-
-static struct ctables_category *
-ctables_find_category_for_postcompute (const struct dictionary *dict,
- const struct ctables_categories *cats,
- enum fmt_type parse_format,
- const struct ctables_pcexpr *e)
-{
- if (parse_format != FMT_F)
- {
- if (e->op == CTPO_CAT_STRING)
- {
- double number;
- if (!parse_category_string (e->location, e->string, dict,
- parse_format, &number))
- return NULL;
-
- struct ctables_pcexpr e2 = {
- .op = CTPO_CAT_NUMBER,
- .number = number,
- .location = e->location,
- };
- return ctables_find_category_for_postcompute__ (cats, &e2);
- }
- else if (e->op == CTPO_CAT_SRANGE)
- {
- double nrange[2];
- if (!e->srange[0].string)
- nrange[0] = -DBL_MAX;
- else if (!parse_category_string (e->location, e->srange[0], dict,
- parse_format, &nrange[0]))
- return NULL;
-
- if (!e->srange[1].string)
- nrange[1] = DBL_MAX;
- else if (!parse_category_string (e->location, e->srange[1], dict,
- parse_format, &nrange[1]))
- return NULL;
-
- struct ctables_pcexpr e2 = {
- .op = CTPO_CAT_NRANGE,
- .nrange = { nrange[0], nrange[1] },
- .location = e->location,
- };
- return ctables_find_category_for_postcompute__ (cats, &e2);
- }
- }
- return ctables_find_category_for_postcompute__ (cats, e);
-}
-
-static bool
-ctables_recursive_check_postcompute (struct dictionary *dict,
- const struct ctables_pcexpr *e,
- struct ctables_category *pc_cat,
- const struct ctables_categories *cats,
- const struct msg_location *cats_location)
-{
- switch (e->op)
- {
- case CTPO_CAT_NUMBER:
- case CTPO_CAT_STRING:
- case CTPO_CAT_NRANGE:
- case CTPO_CAT_SRANGE:
- case CTPO_CAT_MISSING:
- case CTPO_CAT_OTHERNM:
- case CTPO_CAT_SUBTOTAL:
- case CTPO_CAT_TOTAL:
- {
- struct ctables_category *cat = ctables_find_category_for_postcompute (
- dict, cats, pc_cat->parse_format, e);
- if (!cat)
- {
- if (e->op == CTPO_CAT_SUBTOTAL && e->subtotal_index == 0)
- {
- size_t n_subtotals = 0;
- for (size_t i = 0; i < cats->n_cats; i++)
- n_subtotals += cats->cats[i].type == CCT_SUBTOTAL;
- if (n_subtotals > 1)
- {
- msg_at (SE, cats_location,
- ngettext ("These categories include %zu instance "
- "of SUBTOTAL or HSUBTOTAL, so references "
- "from computed categories must refer to "
- "subtotals by position, "
- "e.g. SUBTOTAL[1].",
- "These categories include %zu instances "
- "of SUBTOTAL or HSUBTOTAL, so references "
- "from computed categories must refer to "
- "subtotals by position, "
- "e.g. SUBTOTAL[1].",
- n_subtotals),
- n_subtotals);
- msg_at (SN, e->location,
- _("This is the reference that lacks a position."));
- return NULL;
- }
- }
-
- msg_at (SE, pc_cat->location,
- _("Computed category &%s references a category not included "
- "in the category list."),
- pc_cat->pc->name);
- msg_at (SN, e->location, _("This is the missing category."));
- if (e->op == CTPO_CAT_SUBTOTAL)
- msg_at (SN, cats_location,
- _("To fix the problem, add subtotals to the "
- "list of categories here."));
- else if (e->op == CTPO_CAT_TOTAL)
- msg (SN, _("To fix the problem, add TOTAL=YES to the variable's "
- "CATEGORIES specification."));
- else
- msg_at (SN, cats_location,
- _("To fix the problem, add the missing category to the "
- "list of categories here."));
- return false;
- }
- if (pc_cat->pc->hide_source_cats)
- cat->hide = true;
- return true;
- }
-
- case CTPO_CONSTANT:
- return true;
-
- case CTPO_ADD:
- case CTPO_SUB:
- case CTPO_MUL:
- case CTPO_DIV:
- case CTPO_POW:
- case CTPO_NEG:
- for (size_t i = 0; i < 2; i++)
- if (e->subs[i] && !ctables_recursive_check_postcompute (
- dict, e->subs[i], pc_cat, cats, cats_location))
- return false;
- return true;
- }
-
- NOT_REACHED ();
-}
-
-static bool
-all_strings (struct variable **vars, size_t n_vars,
- const struct ctables_category *cat)
-{
- for (size_t j = 0; j < n_vars; j++)
- if (var_is_numeric (vars[j]))
- {
- msg_at (SE, cat->location,
- _("This category specification may be applied only to string "
- "variables, but this subcommand tries to apply it to "
- "numeric variable %s."),
- var_get_name (vars[j]));
- return false;
- }
- return true;
-}
-
-static bool
-ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
- struct ctables *ct, struct ctables_table *t)
-{
- if (!lex_match_id (lexer, "VARIABLES"))
- return false;
- lex_match (lexer, T_EQUALS);
-
- struct variable **vars;
- size_t n_vars;
- if (!parse_variables (lexer, dict, &vars, &n_vars, PV_NO_SCRATCH))
- return false;
-
- const struct fmt_spec *common_format = var_get_print_format (vars[0]);
- for (size_t i = 1; i < n_vars; i++)
- {
- const struct fmt_spec *f = var_get_print_format (vars[i]);
- if (f->type != common_format->type)
- {
- common_format = NULL;
- break;
- }
- }
- bool parse_strings
- = (common_format
- && (fmt_get_category (common_format->type)
- & (FMT_CAT_DATE | FMT_CAT_TIME | FMT_CAT_DATE_COMPONENT)));
-
- struct ctables_categories *c = xmalloc (sizeof *c);
- *c = (struct ctables_categories) { .n_refs = n_vars, .show_empty = true };
- for (size_t i = 0; i < n_vars; i++)
- {
- struct ctables_categories **cp
- = &t->categories[var_get_dict_index (vars[i])];
- ctables_categories_unref (*cp);
- *cp = c;
- }
-
- size_t allocated_cats = 0;
- int cats_start_ofs = -1;
- int cats_end_ofs = -1;
- if (lex_match (lexer, T_LBRACK))
- {
- cats_start_ofs = lex_ofs (lexer);
- do
- {
- if (c->n_cats >= allocated_cats)
- c->cats = x2nrealloc (c->cats, &allocated_cats, sizeof *c->cats);
-
- 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))
- goto error;
- cat->location = lex_ofs_location (lexer, start_ofs, lex_ofs (lexer) - 1);
- c->n_cats++;
-
- lex_match (lexer, T_COMMA);
- }
- while (!lex_match (lexer, T_RBRACK));
- cats_end_ofs = lex_ofs (lexer) - 1;
- }
-
- struct ctables_category cat = {
- .type = CCT_VALUE,
- .include_missing = false,
- .sort_ascending = true,
- };
- bool show_totals = false;
- char *total_label = NULL;
- bool totals_before = false;
- while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
- {
- if (!c->n_cats && lex_match_id (lexer, "ORDER"))
- {
- lex_match (lexer, T_EQUALS);
- if (lex_match_id (lexer, "A"))
- cat.sort_ascending = true;
- else if (lex_match_id (lexer, "D"))
- cat.sort_ascending = false;
- else
- {
- lex_error_expecting (lexer, "A", "D");
- goto error;
- }
- }
- else if (!c->n_cats && lex_match_id (lexer, "KEY"))
- {
- lex_match (lexer, T_EQUALS);
- if (lex_match_id (lexer, "VALUE"))
- cat.type = CCT_VALUE;
- else if (lex_match_id (lexer, "LABEL"))
- cat.type = CCT_LABEL;
- else
- {
- cat.type = CCT_FUNCTION;
- if (!parse_ctables_summary_function (lexer, &cat.sort_function,
- &cat.weighted, &cat.area))
- goto error;
-
- if (lex_match (lexer, T_LPAREN))
- {
- cat.sort_var = parse_variable (lexer, dict);
- if (!cat.sort_var)
- goto error;
-
- if (cat.sort_function == CTSF_PTILE)
- {
- lex_match (lexer, T_COMMA);
- if (!lex_force_num_range_closed (lexer, "PTILE", 0, 100))
- goto error;
- cat.percentile = lex_number (lexer);
- lex_get (lexer);
- }
-
- if (!lex_force_match (lexer, T_RPAREN))
- goto error;
- }
- else if (ctables_function_availability (cat.sort_function)
- == CTFA_SCALE)
- {
- bool UNUSED b = lex_force_match (lexer, T_LPAREN);
- goto error;
- }
- }
- }
- else if (!c->n_cats && lex_match_id (lexer, "MISSING"))
- {
- lex_match (lexer, T_EQUALS);
- if (lex_match_id (lexer, "INCLUDE"))
- cat.include_missing = true;
- else if (lex_match_id (lexer, "EXCLUDE"))
- cat.include_missing = false;
- else
- {
- lex_error_expecting (lexer, "INCLUDE", "EXCLUDE");
- goto error;
- }
- }
- else if (lex_match_id (lexer, "TOTAL"))
- {
- lex_match (lexer, T_EQUALS);
- if (!parse_bool (lexer, &show_totals))
- goto error;
- }
- else if (lex_match_id (lexer, "LABEL"))
- {
- lex_match (lexer, T_EQUALS);
- if (!lex_force_string (lexer))
- goto error;
- free (total_label);
- total_label = ss_xstrdup (lex_tokss (lexer));
- lex_get (lexer);
- }
- else if (lex_match_id (lexer, "POSITION"))
- {
- lex_match (lexer, T_EQUALS);
- if (lex_match_id (lexer, "BEFORE"))
- totals_before = true;
- else if (lex_match_id (lexer, "AFTER"))
- totals_before = false;
- else
- {
- lex_error_expecting (lexer, "BEFORE", "AFTER");
- goto error;
- }
- }
- else if (lex_match_id (lexer, "EMPTY"))
- {
- lex_match (lexer, T_EQUALS);
- if (lex_match_id (lexer, "INCLUDE"))
- c->show_empty = true;
- else if (lex_match_id (lexer, "EXCLUDE"))
- c->show_empty = false;
- else
- {
- lex_error_expecting (lexer, "INCLUDE", "EXCLUDE");
- goto error;
- }
- }
- else
- {
- if (!c->n_cats)
- lex_error_expecting (lexer, "ORDER", "KEY", "MISSING",
- "TOTAL", "LABEL", "POSITION", "EMPTY");
- else
- lex_error_expecting (lexer, "TOTAL", "LABEL", "POSITION", "EMPTY");
- goto error;
- }
- }
-
- if (!c->n_cats)
- {
- if (c->n_cats >= allocated_cats)
- c->cats = x2nrealloc (c->cats, &allocated_cats, sizeof *c->cats);
- c->cats[c->n_cats++] = cat;
- }