From: Ben Pfaff Date: Sun, 7 Nov 2021 19:17:46 +0000 (-0800) Subject: GET is well tested. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bc5132948bdccf849f0cbd7ee27c7a882b364d15;p=pspp GET is well tested. --- diff --git a/src/language/lexer/variable-parser.c b/src/language/lexer/variable-parser.c index e637940351..3b6b96ad0e 100644 --- a/src/language/lexer/variable-parser.c +++ b/src/language/lexer/variable-parser.c @@ -697,6 +697,135 @@ parse_mixed_vars_pool (struct lexer *lexer, const struct dictionary *dict, struc return retval; } +/* Frees the N var_syntax structures in VS, as well as VS itself. */ +void +var_syntax_destroy (struct var_syntax *vs, size_t n) +{ + for (size_t i = 0; i < n; i++) + { + free (vs[i].first); + free (vs[i].last); + } + free (vs); +} + +/* Parses syntax for variables and variable ranges from LEXER. If successful, + initializes *VS to the beginning of an array of var_syntax structs and *N_VS + to the number of elements in the array and returns true. On error, sets *VS + to NULL and *N_VS to 0 and returns false. */ +bool +var_syntax_parse (struct lexer *lexer, struct var_syntax **vs, size_t *n_vs) +{ + *vs = NULL; + *n_vs = 0; + + if (lex_token (lexer) != T_ID) + { + lex_error (lexer, _("expecting variable name")); + goto error; + } + + size_t allocated_vs = 0; + do + { + if (allocated_vs >= *n_vs) + *vs = x2nrealloc (*vs, &allocated_vs, sizeof **vs); + struct var_syntax *new = &(*vs)[(*n_vs)++]; + *new = (struct var_syntax) { .first = ss_xstrdup (lex_tokss (lexer)) }; + lex_get (lexer); + + if (lex_match (lexer, T_TO)) + { + if (lex_token (lexer) != T_ID) + { + lex_error (lexer, _("expecting variable name")); + goto error; + } + + new->last = ss_xstrdup (lex_tokss (lexer)); + lex_get (lexer); + } + } + while (lex_token (lexer) == T_ID); + return true; + +error: + var_syntax_destroy (*vs, *n_vs); + *vs = NULL; + *n_vs = 0; + return false; +} + +/* Looks up the N_VS var syntax structs in VS in DICT, translating them to an + array of variables. If successful, initializes *VARS to the beginning of an + array of pointers to variables and *N_VARS to the length of the array and + returns true. On error, sets *VARS to NULL and *N_VARS to 0. + + For the moment, only honors PV_NUMERIC in OPTS. */ +bool +var_syntax_evaluate (const struct var_syntax *vs, size_t n_vs, + const struct dictionary *dict, + struct variable ***vars, size_t *n_vars, int opts) +{ + assert (!(opts & ~PV_NUMERIC)); + + *vars = NULL; + *n_vars = 0; + + size_t allocated_vars = 0; + for (size_t i = 0; i < n_vs; i++) + { + struct variable *first = dict_lookup_var (dict, vs[i].first); + if (!first) + { + msg (SE, _("%s is not a variable name."), vs[i].first); + goto error; + } + + struct variable *last = (vs[i].last + ? dict_lookup_var (dict, vs[i].last) + : first); + if (!last) + { + msg (SE, _("%s is not a variable name."), vs[i].last); + goto error; + } + + size_t first_idx = var_get_dict_index (first); + size_t last_idx = var_get_dict_index (last); + if (last_idx < first_idx) + { + msg (SE, _("%s TO %s is not valid syntax since %s " + "precedes %s in the dictionary."), + vs[i].first, vs[i].last, + vs[i].first, vs[i].last); + goto error; + } + + for (size_t j = first_idx; j <= last_idx; j++) + { + struct variable *v = dict_get_var (dict, j); + if (opts & PV_NUMERIC && !var_is_numeric (v)) + { + msg (SW, _("%s is not a numeric variable."), var_get_name (v)); + goto error; + } + + if (*n_vars >= allocated_vars) + *vars = x2nrealloc (*vars, &allocated_vars, sizeof **vars); + (*vars)[(*n_vars)++] = v; + } + } + + return true; + +error: + free (*vars); + *vars = NULL; + *n_vars = 0; + return false; +} + /* A set of variables. */ struct var_set { diff --git a/src/language/lexer/variable-parser.h b/src/language/lexer/variable-parser.h index 8a6772d031..5ccb7b3d7f 100644 --- a/src/language/lexer/variable-parser.h +++ b/src/language/lexer/variable-parser.h @@ -71,7 +71,24 @@ bool parse_mixed_vars (struct lexer *, const struct dictionary *dict, bool parse_mixed_vars_pool (struct lexer *, const struct dictionary *dict, struct pool *, char ***names, size_t *cnt, int opts); + +/* This variable parser supports the unusual situation where set of variables + has to be parsed before the associated dictionary is available. Thus, + parsing proceeds in two phases: first, the variables are parsed in a vector + of "struct var_syntax"; second, when the dictionary becomes available, the + structs are turned into "struct variable"s. */ +struct var_syntax + { + char *first; /* Always nonnull. */ + char *last; /* Nonnull for var ranges (e.g. "a TO b"). */ + }; +void var_syntax_destroy (struct var_syntax *, size_t n); + +bool var_syntax_parse (struct lexer *, struct var_syntax **, size_t *); +bool var_syntax_evaluate (const struct var_syntax *, size_t, + const struct dictionary *, + struct variable ***, size_t *, int opts); /* Const wrappers */ diff --git a/src/language/stats/matrix.c b/src/language/stats/matrix.c index 5542c55c67..5214ee3d8b 100644 --- a/src/language/stats/matrix.c +++ b/src/language/stats/matrix.c @@ -3435,7 +3435,8 @@ struct matrix_cmd struct dataset *dataset; struct file_handle *file; char *encoding; - struct string_array variables; + struct var_syntax *vars; + size_t n_vars; struct matrix_var *names; /* Treatment of missing values. */ @@ -5370,11 +5371,6 @@ matrix_parse_get (struct matrix_state *s) { if (lex_match_id (s->lexer, "FILE")) { - if (get->variables.n) - { - lex_error (s->lexer, _("FILE must precede VARIABLES")); - goto error; - } lex_match (s->lexer, T_EQUALS); fh_unref (get->file); @@ -5389,11 +5385,6 @@ matrix_parse_get (struct matrix_state *s) } else if (lex_match_id (s->lexer, "ENCODING")) { - if (get->variables.n) - { - lex_error (s->lexer, _("ENCODING must precede VARIABLES")); - goto error; - } lex_match (s->lexer, T_EQUALS); if (!lex_force_string (s->lexer)) goto error; @@ -5407,40 +5398,14 @@ matrix_parse_get (struct matrix_state *s) { lex_match (s->lexer, T_EQUALS); - struct dictionary *dict = NULL; - if (!get->file) - { - dict = dict_ref (dataset_dict (s->dataset)); - if (dict_get_var_cnt (dict) == 0) - { - lex_error (s->lexer, _("GET cannot read empty active file.")); - goto error; - } - } - else - { - struct casereader *reader = any_reader_open_and_decode ( - get->file, get->encoding, &dict, NULL); - if (!reader) - goto error; - casereader_destroy (reader); - } - - struct variable **vars; - size_t n_vars; - bool ok = parse_variables (s->lexer, dict, &vars, &n_vars, - PV_DUPLICATE | PV_NUMERIC | PV_NO_SCRATCH); - if (!ok) + if (get->n_vars) { - dict_unref (dict); + lex_sbc_only_once ("VARIABLES"); goto error; } - string_array_clear (&get->variables); - for (size_t i = 0; i < n_vars; i++) - string_array_append (&get->variables, var_get_name (vars[i])); - free (vars); - dict_unref (dict); + if (!var_syntax_parse (s->lexer, &get->vars, &get->n_vars)) + goto error; } else if (lex_match_id (s->lexer, "NAMES")) { @@ -5513,38 +5478,22 @@ matrix_cmd_execute_get__ (struct get_command *get, const struct dictionary *dict, struct casereader *reader) { - const struct variable **vars = xnmalloc ( - get->variables.n ? get->variables.n : dict_get_var_cnt (dict), - sizeof *vars); + struct variable **vars; size_t n_vars = 0; - if (get->variables.n) + if (get->n_vars) { - for (size_t i = 0; i < get->variables.n; i++) - { - const char *name = get->variables.strings[i]; - const struct variable *var = dict_lookup_var (dict, name); - if (!var) - { - msg (SE, _("GET: Data file does not contain variable %s."), - name); - free (vars); - return; - } - if (!var_is_numeric (var)) - { - msg (SE, _("GET: Variable %s is not numeric."), name); - free (vars); - return; - } - vars[n_vars++] = var; - } + if (!var_syntax_evaluate (get->vars, get->n_vars, dict, + &vars, &n_vars, PV_NUMERIC)) + return; } else { - for (size_t i = 0; i < dict_get_var_cnt (dict); i++) + n_vars = dict_get_var_cnt (dict); + vars = xnmalloc (n_vars, sizeof *vars); + for (size_t i = 0; i < n_vars; i++) { - const struct variable *var = dict_get_var (dict, i); + struct variable *var = dict_get_var (dict, i); if (!var_is_numeric (var)) { msg (SE, _("GET: Variable %s is not numeric."), @@ -5552,7 +5501,7 @@ matrix_cmd_execute_get__ (struct get_command *get, free (vars); return; } - vars[n_vars++] = var; + vars[i] = var; } } @@ -5654,6 +5603,11 @@ matrix_cmd_execute_get (struct get_command *get) } else { + if (dict_get_var_cnt (dataset_dict (get->dataset)) == 0) + { + msg (ME, _("GET cannot read empty active file.")); + return; + } reader = proc_open (get->dataset); dict = dict_ref (dataset_dict (get->dataset)); } @@ -6694,7 +6648,7 @@ matrix_cmd_destroy (struct matrix_cmd *cmd) matrix_lvalue_destroy (cmd->get.dst); fh_unref (cmd->get.file); free (cmd->get.encoding); - string_array_destroy (&cmd->get.variables); + var_syntax_destroy (cmd->get.vars, cmd->get.n_vars); break; case MCMD_MSAVE: diff --git a/tests/language/stats/matrix.at b/tests/language/stats/matrix.at index aed715319a..306669e271 100644 --- a/tests/language/stats/matrix.at +++ b/tests/language/stats/matrix.at @@ -3025,4 +3025,74 @@ names7 b c ]) -AT_CLEANUP \ No newline at end of file +AT_CLEANUP + +AT_SETUP([MATRIX - GET - negative]) +AT_DATA([matrix.sps], [dnl +DATA LIST LIST NOTABLE /a b c * d(a1). +MISSING VALUES a(1) b(5). +BEGIN DATA. +0 0 0 a +1 2 3 b +4 5 6 b +7 8 . d +END DATA. +SAVE OUTFILE='matrix.sav'. + +MATRIX. +GET !. +GET x/VARIABLES=!. +GET x/FILE=!. +GET x/ENCODING=!. +GET x/NAMES=!. +GET x/MISSING=!. +GET x/SYSMIS=!. +GET x/!. +GET x/VARIABLES=!. +GET x/VARIABLES=x TO !. +GET x/VARIABLES=x. +GET x/VARIABLES=c TO a. +GET x/VARIABLES=d. +GET x. +END MATRIX. + +NEW FILE. +MATRIX. +GET x/VARIABLES=a. +END MATRIX. +]) +AT_CHECK([pspp matrix.sps], [1], [dnl +matrix.sps:12.5: error: GET: Syntax error at `!': expecting identifier. + +matrix.sps:13.17: error: GET: Syntax error at `!': expecting variable name. + +matrix.sps:14.12: error: GET: Syntax error at `!': expecting a file name or +handle name. + +matrix.sps:15.16: error: GET: Syntax error at `!': expecting string. + +matrix.sps:16.13: error: GET: Syntax error at `!': expecting identifier. + +matrix.sps:17.15: error: GET: Syntax error at `!'. + +matrix.sps:18.14: error: GET: Syntax error at `!'. + +matrix.sps:19.7: error: GET: Syntax error at `!': expecting FILE, VARIABLES, +NAMES, MISSING, or SYSMIS. + +matrix.sps:20.17: error: GET: Syntax error at `!': expecting variable name. + +matrix.sps:21.22: error: GET: Syntax error at `!': expecting variable name. + +matrix.sps:22: error: MATRIX: x is not a variable name. + +matrix.sps:23: error: MATRIX: c TO a is not valid syntax since c precedes a in +the dictionary. + +matrix.sps:24: warning: MATRIX: d is not a numeric variable. + +matrix.sps:25: error: MATRIX: GET: Variable d is not numeric. + +error: GET cannot read empty active file. +]) +AT_CLEANUP