+ if (!lex_force_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"))
+ {
+ int start_ofs = lex_ofs (lexer) - 1;
+ 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.weighting, &cat.area))
+ goto error;