- type;
-
- struct ctables_category *subtotal;
-
- bool hide;
-
- union
- {
- double number; /* CCT_NUMBER. */
- struct substring string; /* CCT_STRING, in dictionary encoding. */
- double nrange[2]; /* CCT_NRANGE. */
- struct substring srange[2]; /* CCT_SRANGE. */
-
- struct
- {
- char *total_label; /* CCT_SUBTOTAL, CCT_TOTAL. */
- bool hide_subcategories; /* CCT_SUBTOTAL. */
- };
-
- /* CCT_POSTCOMPUTE. */
- struct
- {
- const struct ctables_postcompute *pc;
- enum fmt_type parse_format;
- };
-
- /* CCT_VALUE, CCT_LABEL, CCT_FUNCTION. */
- struct
- {
- bool include_missing;
- bool sort_ascending;
-
- /* CCT_FUNCTION. */
- enum ctables_summary_function sort_function;
- bool weighted;
- enum ctables_area_type area;
- struct variable *sort_var;
- double percentile;
- };
- };
-
- /* Source location. This is null for CCT_TOTAL, CCT_VALUE, CCT_LABEL,
- CCT_FUNCTION, CCT_EXCLUDED_MISSING. */
- struct msg_location *location;
- };
-
-static void
-ctables_category_uninit (struct ctables_category *cat)
-{
- if (!cat)
- return;
-
- msg_location_destroy (cat->location);
- switch (cat->type)
- {
- case CCT_NUMBER:
- case CCT_NRANGE:
- case CCT_MISSING:
- case CCT_OTHERNM:
- case CCT_POSTCOMPUTE:
- break;
-
- case CCT_STRING:
- ss_dealloc (&cat->string);
- break;
-
- case CCT_SRANGE:
- ss_dealloc (&cat->srange[0]);
- ss_dealloc (&cat->srange[1]);
- break;
-
- case CCT_SUBTOTAL:
- case CCT_TOTAL:
- free (cat->total_label);
- break;
-
- case CCT_VALUE:
- case CCT_LABEL:
- case CCT_FUNCTION:
- break;
-
- case CCT_EXCLUDED_MISSING:
- break;
- }
-}
-
-static bool
-nullable_substring_equal (const struct substring *a,
- const struct substring *b)
-{
- return !a->string ? !b->string : b->string && ss_equals (*a, *b);
-}
-
-static bool
-ctables_category_equal (const struct ctables_category *a,
- const struct ctables_category *b)
-{
- if (a->type != b->type)
- return false;
-
- switch (a->type)
- {
- case CCT_NUMBER:
- return a->number == b->number;
-
- case CCT_STRING:
- return ss_equals (a->string, b->string);
-
- case CCT_NRANGE:
- return a->nrange[0] == b->nrange[0] && a->nrange[1] == b->nrange[1];
-
- case CCT_SRANGE:
- return (nullable_substring_equal (&a->srange[0], &b->srange[0])
- && nullable_substring_equal (&a->srange[1], &b->srange[1]));
-
- case CCT_MISSING:
- case CCT_OTHERNM:
- return true;
-
- case CCT_POSTCOMPUTE:
- return a->pc == b->pc;
-
- case CCT_SUBTOTAL:
- case CCT_TOTAL:
- return !strcmp (a->total_label, b->total_label);
-
- case CCT_VALUE:
- case CCT_LABEL:
- case CCT_FUNCTION:
- return (a->include_missing == b->include_missing
- && a->sort_ascending == b->sort_ascending
- && a->sort_function == b->sort_function
- && a->sort_var == b->sort_var
- && a->percentile == b->percentile);
-
- case CCT_EXCLUDED_MISSING:
- return true;
- }
-
- NOT_REACHED ();
-}
-
-static void
-ctables_categories_unref (struct ctables_categories *c)
-{
- if (!c)
- return;
-
- assert (c->n_refs > 0);
- if (--c->n_refs)
- return;
-
- for (size_t i = 0; i < c->n_cats; i++)
- ctables_category_uninit (&c->cats[i]);
- free (c->cats);
- free (c);
-}
-
-static bool
-ctables_categories_equal (const struct ctables_categories *a,
- const struct ctables_categories *b)
-{
- if (a->n_cats != b->n_cats || a->show_empty != b->show_empty)
- return false;
-
- for (size_t i = 0; i < a->n_cats; i++)
- if (!ctables_category_equal (&a->cats[i], &b->cats[i]))
- return false;
-
- return true;
-}
-
-/* Chi-square test (SIGTEST). */
-struct ctables_chisq
- {
- double alpha;
- bool include_mrsets;
- bool all_visible;
- };
-
-/* Pairwise comparison test (COMPARETEST). */
-struct ctables_pairwise
- {
- enum { PROP, MEAN } type;
- double alpha[2];
- bool include_mrsets;
- bool meansvariance_allcats;
- bool all_visible;
- enum { BONFERRONI = 1, BH } adjust;
- bool merge;
- bool apa_style;
- bool show_sig;
- };
-
-struct ctables_axis
- {
- enum ctables_axis_op
- {
- /* Terminals. */
- CTAO_VAR,
-
- /* Nonterminals. */
- CTAO_STACK, /* + */
- CTAO_NEST, /* > */
- }
- op;
-
- union
- {
- /* Terminals. */
- struct
- {
- struct variable *var;
- bool scale;
- struct ctables_summary_spec_set specs[N_CSVS];
- };
-
- /* Nonterminals. */
- struct ctables_axis *subs[2];
- };
-
- struct msg_location *loc;
- };
-
-static void ctables_axis_destroy (struct ctables_axis *);
-
-struct ctables_summary_spec
- {
- /* The calculation to be performed.
-
- 'function' is the function to calculate. 'weighted' specifies whether
- to use weighted or unweighted data (for functions that do not support a
- choice, it must be true). 'calc_area' is the area over which the
- calculation takes place (for functions that target only an individual
- cell, it must be 0). For CTSF_PTILE only, 'percentile' is the
- percentile between 0 and 100 (for other functions it must be 0). */
- enum ctables_summary_function function;
- bool weighted;
- enum ctables_area_type calc_area;
- double percentile; /* CTSF_PTILE only. */
-
- /* How to display the result of the calculation.
-
- 'label' is a user-specified label, NULL if the user didn't specify
- one.
-
- 'user_area' is usually the same as 'calc_area', but when category labels
- are rotated from one axis to another it swaps rows and columns.
-
- 'format' is the format for displaying the output. If
- 'is_ctables_format' is true, then 'format.type' is one of the special
- CTEF_* formats instead of the standard ones. */
- char *label;
- enum ctables_area_type user_area;
- struct fmt_spec format;
- bool is_ctables_format; /* Is 'format' one of CTEF_*? */
-
- size_t axis_idx;
- size_t sum_var_idx;
- };
-
-static void
-ctables_summary_spec_clone (struct ctables_summary_spec *dst,
- const struct ctables_summary_spec *src)
-{
- *dst = *src;
- dst->label = xstrdup_if_nonnull (src->label);
-}
-
-static void
-ctables_summary_spec_uninit (struct ctables_summary_spec *s)
-{
- if (s)
- free (s->label);
-}
-
-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
- = (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]);
-
- *dst = (struct ctables_summary_spec_set) {
- .specs = specs,
- .n = src->n,
- .allocated = src->n,
- .var = src->var,
- .is_scale = src->is_scale,
- };
-}
-
-static void
-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);
-}
-
-static bool
-parse_col_width (struct lexer *lexer, const char *name, double *width)
-{
- lex_match (lexer, T_EQUALS);
- if (lex_match_id (lexer, "DEFAULT"))
- *width = SYSMIS;
- else if (lex_force_num_range_closed (lexer, name, 0, DBL_MAX))
- {
- *width = lex_number (lexer);
- lex_get (lexer);
- }
- else
- return false;
-
- return true;
-}
-
-static bool
-parse_bool (struct lexer *lexer, bool *b)
-{
- if (lex_match_id (lexer, "NO"))
- *b = false;
- else if (lex_match_id (lexer, "YES"))
- *b = true;
- else
- {
- lex_error_expecting (lexer, "YES", "NO");
- return false;
- }
- return true;
-}
-
-static enum ctables_function_availability
-ctables_function_availability (enum ctables_summary_function f)
-{
- static enum ctables_function_availability availability[] = {
-#define S(ENUM, NAME, TYPE, FORMAT, AVAILABILITY) [ENUM] = AVAILABILITY,
-#include "ctables.inc"
-#undef S
- };
-
- return availability[f];
-}
-
-static bool
-ctables_summary_function_is_count (enum ctables_summary_function f)
-{
- return f == CTSF_COUNT || f == CTSF_ECOUNT;
-}
-
-static bool
-parse_ctables_summary_function (struct lexer *lexer,
- enum ctables_summary_function *function,
- bool *weighted,
- enum ctables_area_type *area)
-{
- if (!lex_force_id (lexer))
- return false;
-
- struct substring name = lex_tokss (lexer);
- *weighted = !(ss_match_byte (&name, 'U') || ss_match_byte (&name, 'u'));
-
- bool has_area = false;
- *area = 0;
- for (enum ctables_area_type at = 0; at < N_CTATS; at++)
- if (ss_match_string_case (&name, ss_cstr (ctables_area_type_name[at])))
- {
- has_area = true;
- *area = at;
-
- if (ss_equals_case (name, ss_cstr ("PCT")))
- {
- /* Special case where .COUNT suffix is omitted. */
- *function = CTSF_areaPCT_COUNT;
- lex_get (lexer);
- return true;
- }
- break;
- }
-
- for (int f = 0; f < N_CTSF_FUNCTIONS; f++)
- {
- const struct ctables_function_info *cfi = &ctables_function_info[f];
- if (ss_equals_case (cfi->basename, name))
- {
- *function = f;
- if (!*weighted && !cfi->may_be_unweighted)
- break;
- if (has_area != cfi->is_area)
- break;
-
- lex_get (lexer);
- return true;
- }
- }
-
- lex_error (lexer, _("Expecting summary function name."));
- return false;
-}
-
-static void
-ctables_axis_destroy (struct ctables_axis *axis)
-{
- if (!axis)
- return;
-
- switch (axis->op)
- {
- case CTAO_VAR:
- for (size_t i = 0; i < N_CSVS; i++)
- ctables_summary_spec_set_uninit (&axis->specs[i]);
- break;
-
- case CTAO_STACK:
- case CTAO_NEST:
- ctables_axis_destroy (axis->subs[0]);
- ctables_axis_destroy (axis->subs[1]);
- break;
- }
- msg_location_destroy (axis->loc);
- free (axis);
-}
-
-static struct ctables_axis *
-ctables_axis_new_nonterminal (enum ctables_axis_op op,
- struct ctables_axis *sub0,
- struct ctables_axis *sub1,
- struct lexer *lexer, int start_ofs)
-{
- struct ctables_axis *axis = xmalloc (sizeof *axis);
- *axis = (struct ctables_axis) {
- .op = op,
- .subs = { sub0, sub1 },
- .loc = lex_ofs_location (lexer, start_ofs, lex_ofs (lexer) - 1),
- };
- return axis;
-}
-
-struct ctables_axis_parse_ctx
- {
- struct lexer *lexer;
- struct dictionary *dict;
- struct ctables *ct;
- struct ctables_table *t;
- };
-
-static struct fmt_spec
-ctables_summary_default_format (enum ctables_summary_function function,
- const struct variable *var)
-{
- static const enum ctables_format default_formats[] = {
-#define S(ENUM, NAME, TYPE, FORMAT, AVAILABILITY) [ENUM] = FORMAT,
-#include "ctables.inc"
-#undef S
- };
- switch (default_formats[function])
- {
- case CTF_COUNT:
- return (struct fmt_spec) { .type = FMT_F, .w = 40 };
-
- case CTF_PERCENT:
- return (struct fmt_spec) { .type = FMT_PCT, .w = 40, .d = 1 };
-
- case CTF_GENERAL:
- return *var_get_print_format (var);
-
- default:
- NOT_REACHED ();
- }
-}
-
-static const char *
-ctables_summary_label__ (const struct ctables_summary_spec *spec)
-{
- bool w = spec->weighted;
- enum ctables_area_type a = spec->user_area;
- switch (spec->function)
- {
- case CTSF_COUNT:
- return w ? N_("Count") : N_("Unweighted Count");
-
- case CTSF_ECOUNT:
- return N_("Adjusted Count");
-
- case CTSF_areaPCT_COUNT:
- switch (a)
- {
- case CTAT_TABLE: return w ? N_("Table %") : N_("Unweighted Table %");
- case CTAT_LAYER: return w ? N_("Layer %") : N_("Unweighted Layer %");
- case CTAT_LAYERROW: return w ? N_("Layer Row %") : N_("Unweighted Layer Row %");
- case CTAT_LAYERCOL: return w ? N_("Layer Column %") : N_("Unweighted Layer Column %");
- case CTAT_SUBTABLE: return w ? N_("Subtable %") : N_("Unweighted Subtable %");
- case CTAT_ROW: return w ? N_("Row %") : N_("Unweighted Row %");
- case CTAT_COL: return w ? N_("Column %") : N_("Unweighted Column %");
- }
- NOT_REACHED ();
-
- case CTSF_areaPCT_VALIDN:
- switch (a)
- {
- case CTAT_TABLE: return w ? N_("Table Valid N %") : N_("Unweighted Table Valid N %");
- case CTAT_LAYER: return w ? N_("Layer Valid N %") : N_("Unweighted Layer Valid N %");
- case CTAT_LAYERROW: return w ? N_("Layer Row Valid N %") : N_("Unweighted Layer Row Valid N %");
- case CTAT_LAYERCOL: return w ? N_("Layer Column Valid N %") : N_("Unweighted Layer Column Valid N %");
- case CTAT_SUBTABLE: return w ? N_("Subtable Valid N %") : N_("Unweighted Subtable Valid N %");
- case CTAT_ROW: return w ? N_("Row Valid N %") : N_("Unweighted Row Valid N %");
- case CTAT_COL: return w ? N_("Column Valid N %") : N_("Unweighted Column Valid N %");
- }
- NOT_REACHED ();
-
- case CTSF_areaPCT_TOTALN:
- switch (a)
- {
- case CTAT_TABLE: return w ? N_("Table Total N %") : N_("Unweighted Table Total N %");
- case CTAT_LAYER: return w ? N_("Layer Total N %") : N_("Unweighted Layer Total N %");
- case CTAT_LAYERROW: return w ? N_("Layer Row Total N %") : N_("Unweighted Layer Row Total N %");
- case CTAT_LAYERCOL: return w ? N_("Layer Column Total N %") : N_("Unweighted Layer Column Total N %");
- case CTAT_SUBTABLE: return w ? N_("Subtable Total N %") : N_("Unweighted Subtable Total N %");
- case CTAT_ROW: return w ? N_("Row Total N %") : N_("Unweighted Row Total N %");
- case CTAT_COL: return w ? N_("Column Total N %") : N_("Unweighted Column Total N %");
- }
- NOT_REACHED ();
-
- case CTSF_MAXIMUM: return N_("Maximum");
- case CTSF_MEAN: return w ? N_("Mean") : N_("Unweighted Mean");
- case CTSF_MEDIAN: return N_("Median");
- case CTSF_MINIMUM: return N_("Minimum");
- case CTSF_MISSING: return N_("Missing");
- case CTSF_MODE: return N_("Mode");
- case CTSF_PTILE: NOT_REACHED ();
- case CTSF_RANGE: return N_("Range");
- case CTSF_SEMEAN: return N_("Std Error of Mean");
- case CTSF_STDDEV: return N_("Std Deviation");
- case CTSF_SUM: return N_("Sum");
- case CTSF_TOTALN: return N_("Total N");
- case CTSF_ETOTALN: return N_("Adjusted Total N");
- case CTSF_VALIDN: return N_("Valid N");
- case CTSF_EVALIDN: return N_("Adjusted Valid N");
- case CTSF_VARIANCE: return N_("Variance");
- case CTSF_areaPCT_SUM:
- switch (a)
- {
- case CTAT_TABLE: return w ? N_("Table Sum %") : N_("Unweighted Table Sum %");
- case CTAT_LAYER: return w ? N_("Layer Sum %") : N_("Unweighted Layer Sum %");
- case CTAT_LAYERROW: return w ? N_("Layer Row Sum %") : N_("Unweighted Layer Row Sum %");
- case CTAT_LAYERCOL: return w ? N_("Layer Column Sum %") : N_("Unweighted Layer Column Sum %");
- case CTAT_SUBTABLE: return w ? N_("Subtable Sum %") : N_("Unweighted Subtable Sum %");
- case CTAT_ROW: return w ? N_("Row Sum %") : N_("Unweighted Row Sum %");
- case CTAT_COL: return w ? N_("Column Sum %") : N_("Unweighted Column Sum %");
- }
- NOT_REACHED ();
-
- case CTSF_areaID:
- switch (a)
- {
- /* Don't bother translating these: they are for developers only. */
- case CTAT_TABLE: return "Table ID";
- case CTAT_LAYER: return "Layer ID";
- case CTAT_LAYERROW: return "Layer Row ID";
- case CTAT_LAYERCOL: return "Layer Column ID";
- case CTAT_SUBTABLE: return "Subtable ID";
- case CTAT_ROW: return "Row ID";
- case CTAT_COL: return "Column ID";
- }
- NOT_REACHED ();
- }
-
- NOT_REACHED ();
-}
-
-static struct pivot_value *
-ctables_summary_label (const struct ctables_summary_spec *spec, double cilevel)
-{
- if (!spec->label)
- {
- if (spec->function == CTSF_PTILE)
- {
- double p = spec->percentile;
- char *s = (spec->weighted
- ? xasprintf (_("Percentile %.2f"), p)
- : xasprintf (_("Unweighted Percentile %.2f"), p));
- return pivot_value_new_user_text_nocopy (s);
- }
- else
- return pivot_value_new_text (ctables_summary_label__ (spec));
- }
- else
- {
- struct substring in = ss_cstr (spec->label);
- struct substring target = ss_cstr (")CILEVEL");
-
- struct string out = DS_EMPTY_INITIALIZER;
- for (;;)
- {
- size_t chunk = ss_find_substring (in, target);
- ds_put_substring (&out, ss_head (in, chunk));
- ss_advance (&in, chunk);
- if (!in.length)
- return pivot_value_new_user_text_nocopy (ds_steal_cstr (&out));
-
- ss_advance (&in, target.length);
- ds_put_format (&out, "%g", cilevel);
- }
- }
-}
-
-static const char *
-ctables_summary_function_name (enum ctables_summary_function function,
- bool weighted,
- enum ctables_area_type area,
- char *buffer, size_t bufsize)
-{
- const struct ctables_function_info *cfi = &ctables_function_info[function];
- snprintf (buffer, bufsize, "%s%s%s",
- weighted ? "" : "U",
- cfi->is_area ? ctables_area_type_name[area] : "",
- cfi->basename.string);
- return buffer;
-}
-
-static bool
-add_summary_spec (struct ctables_axis *axis,
- enum ctables_summary_function function, bool weighted,
- enum ctables_area_type area, double percentile,
- const char *label, const struct fmt_spec *format,
- bool is_ctables_format, const struct msg_location *loc,
- enum ctables_summary_variant sv)
-{
- if (axis->op == CTAO_VAR)
- {
- char function_name[128];
- ctables_summary_function_name (function, weighted, area,
- function_name, sizeof function_name);
- const char *var_name = var_get_name (axis->var);
- switch (ctables_function_availability (function))
- {
-#if 0
- case CTFA_MRSETS:
- msg_at (SE, loc, _("Summary function %s applies only to multiple "
- "response sets."), function_name);
- msg_at (SN, axis->loc, _("'%s' is not a multiple response set."),
- var_name);
- return false;
-#endif
-
- case CTFA_SCALE:
- if (!axis->scale && sv != CSV_TOTAL)
- {
- msg_at (SE, loc,
- _("Summary function %s applies only to scale variables."),
- function_name);
- msg_at (SN, axis->loc, _("'%s' is not a scale variable."),
- var_name);
- return false;
- }
- break;
-
- case CTFA_ALL:
- break;
- }
-
- struct ctables_summary_spec_set *set = &axis->specs[sv];
- if (set->n >= set->allocated)
- set->specs = x2nrealloc (set->specs, &set->allocated,
- sizeof *set->specs);
-
- struct ctables_summary_spec *dst = &set->specs[set->n++];
- *dst = (struct ctables_summary_spec) {
- .function = function,
- .weighted = weighted,
- .calc_area = area,
- .user_area = area,
- .percentile = percentile,
- .label = xstrdup_if_nonnull (label),
- .format = (format ? *format
- : ctables_summary_default_format (function, axis->var)),
- .is_ctables_format = is_ctables_format,
- };
- return true;
- }
- else
- {
- for (size_t i = 0; i < 2; i++)
- if (!add_summary_spec (axis->subs[i], function, weighted, area,
- percentile, label, format, is_ctables_format,
- loc, sv))
- return false;
- return true;
- }
-}
-
-static struct ctables_axis *ctables_axis_parse_stack (
- struct ctables_axis_parse_ctx *);
-
-
-static struct ctables_axis *
-ctables_axis_parse_primary (struct ctables_axis_parse_ctx *ctx)
-{
- if (lex_match (ctx->lexer, T_LPAREN))
- {
- struct ctables_axis *sub = ctables_axis_parse_stack (ctx);
- if (!sub || !lex_force_match (ctx->lexer, T_RPAREN))
- {
- ctables_axis_destroy (sub);
- return NULL;
- }
- return sub;
- }
-
- if (!lex_force_id (ctx->lexer))
- return NULL;
-
- int start_ofs = lex_ofs (ctx->lexer);
- struct variable *var = parse_variable (ctx->lexer, ctx->dict);
- if (!var)
- return NULL;
-
- struct ctables_axis *axis = xmalloc (sizeof *axis);
- *axis = (struct ctables_axis) { .op = CTAO_VAR, .var = var };
-
- axis->scale = (lex_match_phrase (ctx->lexer, "[S]") ? true
- : lex_match_phrase (ctx->lexer, "[C]") ? false
- : var_get_measure (var) == MEASURE_SCALE);
- axis->loc = lex_ofs_location (ctx->lexer, start_ofs,
- lex_ofs (ctx->lexer) - 1);
- if (axis->scale && var_is_alpha (var))
- {
- msg_at (SE, axis->loc, _("Cannot use string variable %s as a scale "
- "variable."),
- var_get_name (var));
- ctables_axis_destroy (axis);
- return NULL;
- }
-
- return axis;
-}
-
-static bool
-has_digit (const char *s)
-{
- return s[strcspn (s, "0123456789")] != '\0';
-}
-
-static bool
-parse_ctables_format_specifier (struct lexer *lexer, struct fmt_spec *format,
- bool *is_ctables_format)
-{
- char type[FMT_TYPE_LEN_MAX + 1];
- if (!parse_abstract_format_specifier__ (lexer, type, &format->w, &format->d))
- return false;
-
- if (!strcasecmp (type, "NEGPAREN"))
- format->type = CTEF_NEGPAREN;
- else if (!strcasecmp (type, "NEQUAL"))
- format->type = CTEF_NEQUAL;
- else if (!strcasecmp (type, "PAREN"))
- format->type = CTEF_PAREN;
- else if (!strcasecmp (type, "PCTPAREN"))
- format->type = CTEF_PCTPAREN;
- else
- {
- *is_ctables_format = false;
- return (parse_format_specifier (lexer, format)
- && fmt_check_output (format)
- && fmt_check_type_compat (format, VAL_NUMERIC));
- }
-
- lex_get (lexer);
- if (format->w < 2)
- {
- lex_next_error (lexer, -1, -1,
- _("Output format %s requires width 2 or greater."), type);
- return false;
- }
- else if (format->d > format->w - 1)
- {
- lex_next_error (lexer, -1, -1, _("Output format %s requires width "
- "greater than decimals."), type);
- return false;
- }
- else
- {
- *is_ctables_format = true;
- return true;
- }
-}
-
-static struct ctables_axis *
-ctables_axis_parse_postfix (struct ctables_axis_parse_ctx *ctx)
-{
- struct ctables_axis *sub = ctables_axis_parse_primary (ctx);
- if (!sub || !lex_match (ctx->lexer, T_LBRACK))
- return sub;
-
- enum ctables_summary_variant sv = CSV_CELL;
- for (;;)
- {
- int start_ofs = lex_ofs (ctx->lexer);
-
- /* Parse function. */
- enum ctables_summary_function function;
- bool weighted;
- enum ctables_area_type area;
- if (!parse_ctables_summary_function (ctx->lexer, &function, &weighted,
- &area))
- goto error;
-
- /* Parse percentile. */
- double percentile = 0;
- if (function == CTSF_PTILE)
- {
- if (!lex_force_num_range_closed (ctx->lexer, "PTILE", 0, 100))
- goto error;
- percentile = lex_number (ctx->lexer);
- lex_get (ctx->lexer);
- }
-
- /* Parse label. */
- char *label = NULL;
- if (lex_is_string (ctx->lexer))
- {
- label = ss_xstrdup (lex_tokss (ctx->lexer));
- lex_get (ctx->lexer);
- }
-
- /* Parse format. */
- struct fmt_spec format;
- const struct fmt_spec *formatp;
- bool is_ctables_format = false;
- if (lex_token (ctx->lexer) == T_ID
- && has_digit (lex_tokcstr (ctx->lexer)))
- {
- if (!parse_ctables_format_specifier (ctx->lexer, &format,
- &is_ctables_format))
- {
- free (label);
- goto error;
- }
- formatp = &format;
- }
- else
- formatp = NULL;
-
- struct msg_location *loc = lex_ofs_location (ctx->lexer, start_ofs,
- lex_ofs (ctx->lexer) - 1);
- add_summary_spec (sub, function, weighted, area, percentile, label,
- formatp, is_ctables_format, loc, sv);
- free (label);
- msg_location_destroy (loc);
-
- lex_match (ctx->lexer, T_COMMA);
- if (sv == CSV_CELL && lex_match_id (ctx->lexer, "TOTALS"))
- {
- if (!lex_force_match (ctx->lexer, T_LBRACK))
- goto error;
- sv = CSV_TOTAL;
- }
- else if (lex_match (ctx->lexer, T_RBRACK))
- {
- if (sv == CSV_TOTAL && !lex_force_match (ctx->lexer, T_RBRACK))
- goto error;
- return sub;
- }
- }
-
-error:
- ctables_axis_destroy (sub);
- return NULL;
-}
-
-static const struct ctables_axis *
-find_scale (const struct ctables_axis *axis)
-{
- if (!axis)
- return NULL;
- else if (axis->op == CTAO_VAR)
- return axis->scale ? axis : NULL;
- else
- {
- for (size_t i = 0; i < 2; i++)
- {
- const struct ctables_axis *scale = find_scale (axis->subs[i]);
- if (scale)
- return scale;
- }
- return NULL;
- }
-}
-
-static const struct ctables_axis *
-find_categorical_summary_spec (const struct ctables_axis *axis)
-{
- if (!axis)
- return NULL;
- else if (axis->op == CTAO_VAR)
- return !axis->scale && axis->specs[CSV_CELL].n ? axis : NULL;
- else
- {
- for (size_t i = 0; i < 2; i++)
- {
- const struct ctables_axis *sum
- = find_categorical_summary_spec (axis->subs[i]);
- if (sum)
- return sum;
- }
- return NULL;
- }
-}
-
-static struct ctables_axis *
-ctables_axis_parse_nest (struct ctables_axis_parse_ctx *ctx)
-{
- int start_ofs = lex_ofs (ctx->lexer);
- struct ctables_axis *lhs = ctables_axis_parse_postfix (ctx);
- if (!lhs)
- return NULL;
-
- while (lex_match (ctx->lexer, T_GT))
- {
- struct ctables_axis *rhs = ctables_axis_parse_postfix (ctx);
- if (!rhs)
- {
- ctables_axis_destroy (lhs);
- return NULL;
- }
-
- struct ctables_axis *nest = ctables_axis_new_nonterminal (
- CTAO_NEST, lhs, rhs, ctx->lexer, start_ofs);
-
- const struct ctables_axis *outer_scale = find_scale (lhs);
- const struct ctables_axis *inner_scale = find_scale (rhs);
- if (outer_scale && inner_scale)
- {
- msg_at (SE, nest->loc, _("Cannot nest scale variables."));
- msg_at (SN, outer_scale->loc, _("This is an outer scale variable."));
- msg_at (SN, inner_scale->loc, _("This is an inner scale variable."));
- ctables_axis_destroy (nest);
- return NULL;
- }
-
- const struct ctables_axis *outer_sum = find_categorical_summary_spec (lhs);
- if (outer_sum)
- {
- msg_at (SE, nest->loc,
- _("Summaries may only be requested for categorical variables "
- "at the innermost nesting level."));
- msg_at (SN, outer_sum->loc,
- _("This outer categorical variable has a summary."));
- ctables_axis_destroy (nest);
- return NULL;
- }
-
- lhs = nest;
- }