77595c8f80d2b90b77f7ee8e925ad8e3b3065b19
[pspp-builds.git] / src / language / xforms / count.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17    02110-1301, USA. */
18
19 #include <config.h>
20
21 #include <stdlib.h>
22
23 #include <data/case.h>
24 #include <data/dictionary.h>
25 #include <data/procedure.h>
26 #include <data/transformations.h>
27 #include <data/variable.h>
28 #include <language/command.h>
29 #include <language/lexer/lexer.h>
30 #include <language/lexer/range-parser.h>
31 #include <language/lexer/variable-parser.h>
32 #include <libpspp/alloc.h>
33 #include <libpspp/compiler.h>
34 #include <libpspp/message.h>
35 #include <libpspp/message.h>
36 #include <libpspp/pool.h>
37 #include <libpspp/str.h>
38
39 #include "gettext.h"
40 #define _(msgid) gettext (msgid)
41
42 /* Value or range? */
43 enum value_type
44   {
45     CNT_SINGLE,                 /* Single value. */
46     CNT_RANGE                   /* a <= x <= b. */
47   };
48
49 /* Numeric count criteria. */
50 struct num_value
51   {
52     enum value_type type;       /* How to interpret a, b. */
53     double a, b;                /* Values to count. */
54   };
55
56 struct criteria
57   {
58     struct criteria *next;
59
60     /* Variables to count. */
61     const struct variable **vars;
62     size_t var_cnt;
63
64     /* Count special values? */
65     bool count_system_missing;  /* Count system missing? */
66     bool count_user_missing;    /* Count user missing? */
67
68     /* Criterion values. */
69     size_t value_cnt;
70     union
71       {
72         struct num_value *num;
73         char **str;
74       }
75     values;
76   };
77
78 struct dst_var
79   {
80     struct dst_var *next;
81     struct variable *var;       /* Destination variable. */
82     char *name;                 /* Name of dest var. */
83     struct criteria *crit;      /* The criteria specifications. */
84   };
85
86 struct count_trns
87   {
88     struct dst_var *dst_vars;
89     struct pool *pool;
90   };
91
92 static trns_proc_func count_trns_proc;
93 static trns_free_func count_trns_free;
94
95 static bool parse_numeric_criteria (struct lexer *, struct pool *, struct criteria *);
96 static bool parse_string_criteria (struct lexer *, struct pool *, struct criteria *);
97 \f
98 int
99 cmd_count (struct lexer *lexer, struct dataset *ds)
100 {
101   struct dst_var *dv;           /* Destination var being parsed. */
102   struct count_trns *trns;      /* Transformation. */
103
104   /* Parses each slash-delimited specification. */
105   trns = pool_create_container (struct count_trns, pool);
106   trns->dst_vars = dv = pool_alloc (trns->pool, sizeof *dv);
107   for (;;)
108     {
109       struct criteria *crit;
110
111       /* Initialize this struct dst_var to ensure proper cleanup. */
112       dv->next = NULL;
113       dv->var = NULL;
114       dv->crit = NULL;
115
116       /* Get destination variable, or at least its name. */
117       if (!lex_force_id (lexer))
118         goto fail;
119       dv->var = dict_lookup_var (dataset_dict (ds), lex_tokid (lexer));
120       if (dv->var != NULL)
121         {
122           if (var_is_alpha (dv->var))
123             {
124               msg (SE, _("Destination cannot be a string variable."));
125               goto fail;
126             }
127         }
128       else
129         dv->name = pool_strdup (trns->pool, lex_tokid (lexer));
130
131       lex_get (lexer);
132       if (!lex_force_match (lexer, '='))
133         goto fail;
134
135       crit = dv->crit = pool_alloc (trns->pool, sizeof *crit);
136       for (;;)
137         {
138           bool ok;
139
140           crit->next = NULL;
141           crit->vars = NULL;
142           if (!parse_variables_const (lexer, dataset_dict (ds), &crit->vars,
143                                       &crit->var_cnt,
144                                 PV_DUPLICATE | PV_SAME_TYPE))
145             goto fail;
146           pool_register (trns->pool, free, crit->vars);
147
148           if (!lex_force_match (lexer, '('))
149             goto fail;
150
151           crit->value_cnt = 0;
152           if (var_is_numeric (crit->vars[0]))
153             ok = parse_numeric_criteria (lexer, trns->pool, crit);
154           else
155             ok = parse_string_criteria (lexer, trns->pool, crit);
156           if (!ok)
157             goto fail;
158
159           if (lex_token (lexer) == '/' || lex_token (lexer) == '.')
160             break;
161
162           crit = crit->next = pool_alloc (trns->pool, sizeof *crit);
163         }
164
165       if (lex_token (lexer) == '.')
166         break;
167
168       if (!lex_force_match (lexer, '/'))
169         goto fail;
170       dv = dv->next = pool_alloc (trns->pool, sizeof *dv);
171     }
172
173   /* Create all the nonexistent destination variables. */
174   for (dv = trns->dst_vars; dv; dv = dv->next)
175     if (dv->var == NULL)
176       {
177         /* It's valid, though motivationally questionable, to count to
178            the same dest var more than once. */
179         dv->var = dict_lookup_var (dataset_dict (ds), dv->name);
180
181         if (dv->var == NULL)
182           dv->var = dict_create_var_assert (dataset_dict (ds), dv->name, 0);
183       }
184
185   add_transformation (ds, count_trns_proc, count_trns_free, trns);
186   return CMD_SUCCESS;
187
188 fail:
189   count_trns_free (trns);
190   return CMD_FAILURE;
191 }
192
193 /* Parses a set of numeric criterion values.  Returns success. */
194 static bool
195 parse_numeric_criteria (struct lexer *lexer, struct pool *pool, struct criteria *crit)
196 {
197   size_t allocated = 0;
198
199   crit->values.num = NULL;
200   crit->count_system_missing = false;
201   crit->count_user_missing = false;
202   for (;;)
203     {
204       double low, high;
205
206       if (lex_match_id (lexer, "SYSMIS"))
207         crit->count_system_missing = true;
208       else if (lex_match_id (lexer, "MISSING"))
209         crit->count_user_missing = true;
210       else if (parse_num_range (lexer, &low, &high, NULL))
211         {
212           struct num_value *cur;
213
214           if (crit->value_cnt >= allocated)
215             crit->values.num = pool_2nrealloc (pool, crit->values.num,
216                                                &allocated,
217                                                sizeof *crit->values.num);
218           cur = &crit->values.num[crit->value_cnt++];
219           cur->type = low == high ? CNT_SINGLE : CNT_RANGE;
220           cur->a = low;
221           cur->b = high;
222         }
223       else
224         return false;
225
226       lex_match (lexer, ',');
227       if (lex_match (lexer, ')'))
228         break;
229     }
230   return true;
231 }
232
233 /* Parses a set of string criteria values.  Returns success. */
234 static bool
235 parse_string_criteria (struct lexer *lexer, struct pool *pool, struct criteria *crit)
236 {
237   int len = 0;
238   size_t allocated = 0;
239   size_t i;
240
241   for (i = 0; i < crit->var_cnt; i++)
242     if (var_get_width (crit->vars[i]) > len)
243       len = var_get_width (crit->vars[i]);
244
245   crit->values.str = NULL;
246   for (;;)
247     {
248       char **cur;
249       if (crit->value_cnt >= allocated)
250         crit->values.str = pool_2nrealloc (pool, crit->values.str,
251                                            &allocated,
252                                            sizeof *crit->values.str);
253
254       if (!lex_force_string (lexer))
255         return false;
256       cur = &crit->values.str[crit->value_cnt++];
257       *cur = pool_alloc (pool, len + 1);
258       str_copy_rpad (*cur, len + 1, ds_cstr (lex_tokstr (lexer)));
259       lex_get (lexer);
260
261       lex_match (lexer, ',');
262       if (lex_match (lexer, ')'))
263         break;
264     }
265
266   return true;
267 }
268 \f
269 /* Transformation. */
270
271 /* Counts the number of values in case C matching CRIT. */
272 static inline int
273 count_numeric (struct criteria *crit, struct ccase *c)
274 {
275   int counter = 0;
276   size_t i;
277
278   for (i = 0; i < crit->var_cnt; i++)
279     {
280       double x = case_num (c, crit->vars[i]);
281       if (var_is_num_missing (crit->vars[i], x, MV_ANY))
282         {
283           if (x == SYSMIS
284               ? crit->count_system_missing
285               : crit->count_user_missing)
286             counter++;
287         }
288       else
289         {
290           struct num_value *v;
291
292           for (v = crit->values.num; v < crit->values.num + crit->value_cnt;
293                v++)
294             if (v->type == CNT_SINGLE ? x == v->a : x >= v->a && x <= v->b)
295               {
296                 counter++;
297                 break;
298               }
299         }
300     }
301
302   return counter;
303 }
304
305 /* Counts the number of values in case C matching CRIT. */
306 static inline int
307 count_string (struct criteria *crit, struct ccase *c)
308 {
309   int counter = 0;
310   size_t i;
311
312   for (i = 0; i < crit->var_cnt; i++)
313     {
314       char **v;
315       for (v = crit->values.str; v < crit->values.str + crit->value_cnt; v++)
316         if (!memcmp (case_str (c, crit->vars[i]), *v,
317                      var_get_width (crit->vars[i])))
318           {
319             counter++;
320             break;
321           }
322     }
323
324   return counter;
325 }
326
327 /* Performs the COUNT transformation T on case C. */
328 static int
329 count_trns_proc (void *trns_, struct ccase *c,
330                  casenumber case_num UNUSED)
331 {
332   struct count_trns *trns = trns_;
333   struct dst_var *dv;
334
335   for (dv = trns->dst_vars; dv; dv = dv->next)
336     {
337       struct criteria *crit;
338       int counter;
339
340       counter = 0;
341       for (crit = dv->crit; crit; crit = crit->next)
342         if (var_is_numeric (crit->vars[0]))
343           counter += count_numeric (crit, c);
344         else
345           counter += count_string (crit, c);
346       case_data_rw (c, dv->var)->f = counter;
347     }
348   return TRNS_CONTINUE;
349 }
350
351 /* Destroys all dynamic data structures associated with TRNS. */
352 static bool
353 count_trns_free (void *trns_)
354 {
355   struct count_trns *trns = (struct count_trns *) trns_;
356   pool_destroy (trns->pool);
357   return true;
358 }