From 77ec6632b990724c3dbc8bdafca2d1d9b53037ec Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 27 Dec 2021 17:51:33 -0800 Subject: [PATCH] start validating --- src/language/stats/ctables.c | 136 +++++++++++++++++++++++++++++--- tests/language/stats/ctables.at | 11 ++- 2 files changed, 133 insertions(+), 14 deletions(-) diff --git a/src/language/stats/ctables.c b/src/language/stats/ctables.c index 69ed11df70..7b289ed2c7 100644 --- a/src/language/stats/ctables.c +++ b/src/language/stats/ctables.c @@ -415,6 +415,8 @@ struct ctables_axis /* Nonterminals. */ struct ctables_axis *subs[2]; }; + + struct msg_location *loc; }; static void ctables_axis_destroy (struct ctables_axis *); @@ -524,6 +526,7 @@ parse_ctables_summary_function (struct lexer *lexer, if (ss_equals_case (names[i].name, lex_tokss (lexer))) { *f = names[i].function; + lex_get (lexer); return true; } @@ -551,16 +554,22 @@ ctables_axis_destroy (struct ctables_axis *axis) 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 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 } }; + *axis = (struct ctables_axis) { + .op = op, + .subs = { sub0, sub1 }, + .loc = lex_ofs_location (lexer, start_ofs, lex_ofs (lexer) - 1), + }; return axis; } @@ -673,6 +682,7 @@ ctables_axis_parse_primary (struct ctables_axis_parse_ctx *ctx) if (!lex_force_id (ctx->lexer)) return NULL; + int start_ofs = lex_ofs (ctx->lexer); struct ctables_var var; if (!ctables_var_parse (ctx->lexer, ctx->dict, &var)) return NULL; @@ -685,10 +695,12 @@ ctables_axis_parse_primary (struct ctables_axis_parse_ctx *ctx) : lex_match_phrase (ctx->lexer, "[S]") ? true : lex_match_phrase (ctx->lexer, "[C]") ? false : var_get_measure (var.var) == MEASURE_SCALE); + axis->loc = lex_ofs_location (ctx->lexer, start_ofs, + lex_ofs (ctx->lexer) - 1); - size_t allocated_summaries = 0; if (lex_match (ctx->lexer, T_LBRACK)) { + size_t allocated_summaries = 0; do { enum ctables_summary_function function; @@ -723,9 +735,6 @@ ctables_axis_parse_primary (struct ctables_axis_parse_ctx *ctx) } while (!lex_match (ctx->lexer, T_RBRACK)); } - else - add_summary (axis, axis->scale ? CTSF_MEAN : CTSF_COUNT, 0, - &allocated_summaries); return axis; error: @@ -733,9 +742,57 @@ error: return NULL; } +static const struct ctables_axis * +find_scale (const struct ctables_axis *axis) +{ + if (!axis) + return NULL; + else if (axis->op == CTAO_VAR) + { + if (axis->scale) + { + assert (!axis->var.is_mrset); + return axis; + } + else + return 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 (const struct ctables_axis *axis) +{ + if (!axis) + return NULL; + else if (axis->op == CTAO_VAR) + return !axis->scale && axis->n_summaries ? axis : NULL; + else + { + for (size_t i = 0; i < 2; i++) + { + const struct ctables_axis *sum + = find_categorical_summary (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_primary (ctx); if (!lhs) return NULL; @@ -746,7 +803,33 @@ ctables_axis_parse_nest (struct ctables_axis_parse_ctx *ctx) if (!rhs) return NULL; - lhs = ctables_axis_new_nonterminal (CTAO_NEST, lhs, rhs); + 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 (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; } return lhs; @@ -755,6 +838,7 @@ ctables_axis_parse_nest (struct ctables_axis_parse_ctx *ctx) static struct ctables_axis * ctables_axis_parse_stack (struct ctables_axis_parse_ctx *ctx) { + int start_ofs = lex_ofs (ctx->lexer); struct ctables_axis *lhs = ctables_axis_parse_nest (ctx); if (!lhs) return NULL; @@ -765,7 +849,8 @@ ctables_axis_parse_stack (struct ctables_axis_parse_ctx *ctx) if (!rhs) return NULL; - lhs = ctables_axis_new_nonterminal (CTAO_STACK, lhs, rhs); + lhs = ctables_axis_new_nonterminal (CTAO_STACK, lhs, rhs, + ctx->lexer, start_ofs); } return lhs; @@ -1299,7 +1384,6 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds) lex_match (lexer, T_EQUALS); if (!ctables_axis_parse (lexer, dataset_dict (ds), ct, t, PIVOT_AXIS_ROW)) goto error; - if (lex_match (lexer, T_BY)) { if (!ctables_axis_parse (lexer, dataset_dict (ds), @@ -1313,6 +1397,37 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds) goto error; } } + + if (!t->axes[PIVOT_AXIS_ROW] && !t->axes[PIVOT_AXIS_COLUMN] + && !t->axes[PIVOT_AXIS_LAYER]) + { + lex_error (lexer, _("At least one variable must be specified.")); + goto error; + } + + const struct ctables_axis *scales[PIVOT_N_AXES]; + size_t n_scales = 0; + for (size_t i = 0; i < 3; i++) + { + scales[i] = find_scale (t->axes[i]); + if (scales[i]) + n_scales++; + } + if (n_scales > 1) + { + msg (SE, _("Scale variables may appear only on one dimension.")); + if (scales[PIVOT_AXIS_ROW]) + msg_at (SN, scales[PIVOT_AXIS_ROW]->loc, + _("This scale variable appears in the rows dimension.")); + if (scales[PIVOT_AXIS_COLUMN]) + msg_at (SN, scales[PIVOT_AXIS_COLUMN]->loc, + _("This scale variable appears in the columns dimension.")); + if (scales[PIVOT_AXIS_LAYER]) + msg_at (SN, scales[PIVOT_AXIS_LAYER]->loc, + _("This scale variable appears in the layer dimension.")); + goto error; + } + if (lex_token (lexer) == T_ENDCMD) break; if (!lex_force_match (lexer, T_SLASH)) @@ -1336,8 +1451,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds) t->slabels_position = PIVOT_AXIS_LAYER; else { - lex_error_expecting (lexer, "COLUMN", "ROW", - "LAYER"); + lex_error_expecting (lexer, "COLUMN", "ROW", "LAYER"); goto error; } } diff --git a/tests/language/stats/ctables.at b/tests/language/stats/ctables.at index 6b1eb3ae08..72aac4bf85 100644 --- a/tests/language/stats/ctables.at +++ b/tests/language/stats/ctables.at @@ -1,9 +1,14 @@ AT_BANNER([CTABLES]) AT_SETUP([CTABLES parsing]) -AT_DATA([ctables.sps], [dnl -DATA LIST LIST NOTABLE /x y z. +AT_DATA([ctables.sps], +[[DATA LIST LIST NOTABLE /x y z. CTABLES /TABLE=(x + y) > z. -]) +CTABLES /TABLE=(x[c] + y[c]) > z. +CTABLES /TABLE=(x + y) > z[c]. +CTABLES /TABLE=x BY y BY z. +CTABLES /TABLE=x[c] [ROWPCT.COUNT] > y[c]. +CTABLES /TABLE=x[c] > y[c] [ROWPCT.COUNT]. +]]) AT_CHECK([pspp ctables.sps]) AT_CLEANUP \ No newline at end of file -- 2.30.2