26 |
#include "dictionary.h" |
#include "dictionary.h" |
27 |
#include "error.h" |
#include "error.h" |
28 |
#include "lexer.h" |
#include "lexer.h" |
29 |
|
#include "pool.h" |
30 |
|
#include "range-prs.h" |
31 |
#include "str.h" |
#include "str.h" |
32 |
#include "var.h" |
#include "var.h" |
33 |
|
|
34 |
#include "gettext.h" |
#include "gettext.h" |
35 |
#define _(msgid) gettext (msgid) |
#define _(msgid) gettext (msgid) |
36 |
|
|
37 |
/* Implementation details: |
/* Value or range? */ |
38 |
|
enum value_type |
|
The S?SS manuals do not specify the order that COUNT subcommands are |
|
|
performed in. Experiments, however, have shown that they are performed |
|
|
in the order that they are specified in, rather than simultaneously. |
|
|
So, with the two variables A and B, and the two cases, |
|
|
|
|
|
A B |
|
|
1 2 |
|
|
2 1 |
|
|
|
|
|
the command COUNT A=A B (1) / B=A B (2) will produce the following |
|
|
results, |
|
|
|
|
|
A B |
|
|
1 1 |
|
|
1 0 |
|
|
|
|
|
rather than the results that would be produced if subcommands were |
|
|
simultaneous: |
|
|
|
|
|
A B |
|
|
1 1 |
|
|
1 1 |
|
|
|
|
|
Perhaps simultaneity could be implemented as an option. On the |
|
|
other hand, what good are the above commands? */ |
|
|
|
|
|
/* Definitions. */ |
|
|
|
|
|
enum |
|
39 |
{ |
{ |
|
CNT_ERROR, /* Invalid value. */ |
|
40 |
CNT_SINGLE, /* Single value. */ |
CNT_SINGLE, /* Single value. */ |
41 |
CNT_HIGH, /* x >= a. */ |
CNT_RANGE /* a <= x <= b. */ |
|
CNT_LOW, /* x <= a. */ |
|
|
CNT_RANGE, /* a <= x <= b. */ |
|
|
CNT_ANY, /* Count any. */ |
|
|
CNT_SENTINEL /* List terminator. */ |
|
42 |
}; |
}; |
43 |
|
|
44 |
struct cnt_num |
/* Numeric count criteria. */ |
45 |
|
struct num_value |
46 |
{ |
{ |
47 |
int type; |
enum value_type type; /* How to interpret a, b. */ |
48 |
double a, b; |
double a, b; /* Values to count. */ |
49 |
}; |
}; |
50 |
|
|
51 |
struct cnt_str |
struct criteria |
52 |
{ |
{ |
53 |
int type; |
struct criteria *next; |
|
char *s; |
|
|
}; |
|
|
|
|
|
struct counting |
|
|
{ |
|
|
struct counting *next; |
|
54 |
|
|
55 |
/* variables to count */ |
/* Variables to count. */ |
56 |
struct variable **v; |
struct variable **vars; |
57 |
size_t n; |
size_t var_cnt; |
58 |
|
|
59 |
/* values to count */ |
/* Count special values?. */ |
60 |
int missing; /* (numeric only) |
bool count_system_missing; /* Count system missing? */ |
61 |
0=don't count missing, |
bool count_user_missing; /* Count user missing? */ |
62 |
1=count SYSMIS, |
|
63 |
2=count system- and user-missing */ |
/* Criterion values. */ |
64 |
union /* Criterion values. */ |
size_t value_cnt; |
65 |
|
union |
66 |
{ |
{ |
67 |
struct cnt_num *n; |
struct num_value *num; |
68 |
struct cnt_str *s; |
char **str; |
69 |
} |
} |
70 |
crit; |
values; |
71 |
}; |
}; |
72 |
|
|
73 |
struct cnt_var_info |
struct dst_var |
74 |
{ |
{ |
75 |
struct cnt_var_info *next; |
struct dst_var *next; |
76 |
|
struct variable *var; /* Destination variable. */ |
77 |
struct variable *d; /* Destination variable. */ |
char *name; /* Name of dest var. */ |
78 |
char n[LONG_NAME_LEN + 1]; /* Name of dest var. */ |
struct criteria *crit; /* The criteria specifications. */ |
|
|
|
|
struct counting *c; /* The counting specifications. */ |
|
79 |
}; |
}; |
80 |
|
|
81 |
struct count_trns |
struct count_trns |
82 |
{ |
{ |
83 |
struct trns_header h; |
struct trns_header h; |
84 |
struct cnt_var_info *specs; |
struct dst_var *dst_vars; |
85 |
|
struct pool *pool; |
86 |
}; |
}; |
87 |
|
|
88 |
/* Parser. */ |
/* Parser. */ |
90 |
static trns_proc_func count_trns_proc; |
static trns_proc_func count_trns_proc; |
91 |
static trns_free_func count_trns_free; |
static trns_free_func count_trns_free; |
92 |
|
|
93 |
static int parse_numeric_criteria (struct counting *); |
static bool parse_numeric_criteria (struct pool *, struct criteria *); |
94 |
static int parse_string_criteria (struct counting *); |
static bool parse_string_criteria (struct pool *, struct criteria *); |
95 |
|
|
96 |
int |
int |
97 |
cmd_count (void) |
cmd_count (void) |
98 |
{ |
{ |
99 |
struct cnt_var_info *cnt; /* Specification currently being parsed. */ |
struct dst_var *dv; /* Destination var being parsed. */ |
|
struct counting *c; /* Counting currently being parsed. */ |
|
|
int ret; /* Return value from parsing function. */ |
|
100 |
struct count_trns *trns; /* Transformation. */ |
struct count_trns *trns; /* Transformation. */ |
|
struct cnt_var_info *head; /* First counting in chain. */ |
|
101 |
|
|
102 |
/* Parses each slash-delimited specification. */ |
/* Parses each slash-delimited specification. */ |
103 |
head = cnt = xmalloc (sizeof *cnt); |
trns = xmalloc (sizeof *trns); |
104 |
|
trns->h.proc = count_trns_proc; |
105 |
|
trns->h.free = count_trns_free; |
106 |
|
trns->pool = pool_create (); |
107 |
|
trns->dst_vars = dv = pool_alloc (trns->pool, sizeof *dv); |
108 |
for (;;) |
for (;;) |
109 |
{ |
{ |
110 |
/* Initialize this struct cnt_var_info to ensure proper cleanup. */ |
struct criteria *crit; |
111 |
cnt->next = NULL; |
|
112 |
cnt->d = NULL; |
/* Initialize this struct dst_var to ensure proper cleanup. */ |
113 |
cnt->c = NULL; |
dv->next = NULL; |
114 |
|
dv->var = NULL; |
115 |
|
dv->crit = NULL; |
116 |
|
|
117 |
/* Get destination variable, or at least its name. */ |
/* Get destination variable, or at least its name. */ |
118 |
if (!lex_force_id ()) |
if (!lex_force_id ()) |
119 |
goto fail; |
goto fail; |
120 |
cnt->d = dict_lookup_var (default_dict, tokid); |
dv->var = dict_lookup_var (default_dict, tokid); |
121 |
if (cnt->d) |
if (dv->var != NULL) |
122 |
{ |
{ |
123 |
if (cnt->d->type == ALPHA) |
if (dv->var->type == ALPHA) |
124 |
{ |
{ |
125 |
msg (SE, _("Destination cannot be a string variable.")); |
msg (SE, _("Destination cannot be a string variable.")); |
126 |
goto fail; |
goto fail; |
127 |
} |
} |
128 |
} |
} |
129 |
else |
else |
130 |
str_copy_trunc (cnt->n, sizeof cnt->n, tokid); |
dv->name = pool_strdup (trns->pool, tokid); |
131 |
|
|
132 |
lex_get (); |
lex_get (); |
133 |
if (!lex_force_match ('=')) |
if (!lex_force_match ('=')) |
134 |
goto fail; |
goto fail; |
135 |
|
|
136 |
c = cnt->c = xmalloc (sizeof *c); |
crit = dv->crit = pool_alloc (trns->pool, sizeof *crit); |
137 |
for (;;) |
for (;;) |
138 |
{ |
{ |
139 |
c->next = NULL; |
bool ok; |
140 |
c->v = NULL; |
|
141 |
if (!parse_variables (default_dict, &c->v, &c->n, |
crit->next = NULL; |
142 |
|
crit->vars = NULL; |
143 |
|
if (!parse_variables (default_dict, &crit->vars, &crit->var_cnt, |
144 |
PV_DUPLICATE | PV_SAME_TYPE)) |
PV_DUPLICATE | PV_SAME_TYPE)) |
145 |
goto fail; |
goto fail; |
146 |
|
pool_register (trns->pool, free, crit->vars); |
147 |
|
|
148 |
if (!lex_force_match ('(')) |
if (!lex_force_match ('(')) |
149 |
goto fail; |
goto fail; |
150 |
|
|
151 |
ret = (c->v[0]->type == NUMERIC |
crit->value_cnt = 0; |
152 |
? parse_numeric_criteria |
if (crit->vars[0]->type == NUMERIC) |
153 |
: parse_string_criteria) (c); |
ok = parse_numeric_criteria (trns->pool, crit); |
154 |
if (!ret) |
else |
155 |
|
ok = parse_string_criteria (trns->pool, crit); |
156 |
|
if (!ok) |
157 |
goto fail; |
goto fail; |
158 |
|
|
159 |
if (token == '/' || token == '.') |
if (token == '/' || token == '.') |
160 |
break; |
break; |
161 |
|
|
162 |
c = c->next = xmalloc (sizeof *c); |
crit = crit->next = pool_alloc (trns->pool, sizeof *crit); |
163 |
} |
} |
164 |
|
|
165 |
if (token == '.') |
if (token == '.') |
167 |
|
|
168 |
if (!lex_force_match ('/')) |
if (!lex_force_match ('/')) |
169 |
goto fail; |
goto fail; |
170 |
cnt = cnt->next = xmalloc (sizeof *cnt); |
dv = dv->next = pool_alloc (trns->pool, sizeof *dv); |
171 |
} |
} |
172 |
|
|
173 |
/* Create all the nonexistent destination variables. */ |
/* Create all the nonexistent destination variables. */ |
174 |
for (cnt = head; cnt; cnt = cnt->next) |
for (dv = trns->dst_vars; dv; dv = dv->next) |
175 |
if (!cnt->d) |
if (dv->var == NULL) |
176 |
{ |
{ |
177 |
/* It's valid, though motivationally questionable, to count to |
/* It's valid, though motivationally questionable, to count to |
178 |
the same dest var more than once. */ |
the same dest var more than once. */ |
179 |
cnt->d = dict_lookup_var (default_dict, cnt->n); |
dv->var = dict_lookup_var (default_dict, dv->name); |
180 |
|
|
181 |
if (cnt->d == NULL) |
if (dv->var == NULL) |
182 |
cnt->d = dict_create_var_assert (default_dict, cnt->n, 0); |
dv->var = dict_create_var_assert (default_dict, dv->name, 0); |
183 |
} |
} |
184 |
|
|
185 |
trns = xmalloc (sizeof *trns); |
add_transformation (&trns->h); |
|
trns->h.proc = count_trns_proc; |
|
|
trns->h.free = count_trns_free; |
|
|
trns->specs = head; |
|
|
add_transformation ((struct trns_header *) trns); |
|
|
|
|
186 |
return CMD_SUCCESS; |
return CMD_SUCCESS; |
187 |
|
|
188 |
fail: |
fail: |
189 |
{ |
count_trns_free (&trns->h); |
190 |
struct count_trns t; |
return CMD_FAILURE; |
|
t.specs = head; |
|
|
count_trns_free ((struct trns_header *) & t); |
|
|
return CMD_FAILURE; |
|
|
} |
|
191 |
} |
} |
192 |
|
|
193 |
/* Parses a set of numeric criterion values. */ |
/* Parses a set of numeric criterion values. Returns success. */ |
194 |
static int |
static bool |
195 |
parse_numeric_criteria (struct counting *c) |
parse_numeric_criteria (struct pool *pool, struct criteria *crit) |
196 |
{ |
{ |
197 |
size_t n = 0; |
size_t allocated = 0; |
|
size_t m = 0; |
|
198 |
|
|
199 |
c->crit.n = 0; |
crit->values.num = NULL; |
200 |
c->missing = 0; |
crit->count_system_missing = false; |
201 |
|
crit->count_user_missing = false; |
202 |
for (;;) |
for (;;) |
203 |
{ |
{ |
204 |
struct cnt_num *cur; |
double low, high; |
205 |
if (n + 1 >= m) |
|
206 |
{ |
if (lex_match_id ("SYSMIS")) |
207 |
m += 16; |
crit->count_system_missing = true; |
|
c->crit.n = xnrealloc (c->crit.n, m, sizeof *c->crit.n); |
|
|
} |
|
|
|
|
|
cur = &c->crit.n[n++]; |
|
|
if (lex_is_number ()) |
|
|
{ |
|
|
cur->a = tokval; |
|
|
lex_get (); |
|
|
if (lex_match_id ("THRU")) |
|
|
{ |
|
|
if (lex_is_number ()) |
|
|
{ |
|
|
if (!lex_force_num ()) |
|
|
return 0; |
|
|
cur->b = tokval; |
|
|
cur->type = CNT_RANGE; |
|
|
lex_get (); |
|
|
|
|
|
if (cur->a > cur->b) |
|
|
{ |
|
|
msg (SE, _("%g THRU %g is not a valid range. The " |
|
|
"number following THRU must be at least " |
|
|
"as big as the number preceding THRU."), |
|
|
cur->a, cur->b); |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
else if (lex_match_id ("HI") || lex_match_id ("HIGHEST")) |
|
|
cur->type = CNT_HIGH; |
|
|
else |
|
|
{ |
|
|
lex_error (NULL); |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
else |
|
|
cur->type = CNT_SINGLE; |
|
|
} |
|
|
else if (lex_match_id ("LO") || lex_match_id ("LOWEST")) |
|
|
{ |
|
|
if (!lex_force_match_id ("THRU")) |
|
|
return 0; |
|
|
if (lex_is_number ()) |
|
|
{ |
|
|
cur->type = CNT_LOW; |
|
|
cur->a = tokval; |
|
|
lex_get (); |
|
|
} |
|
|
else if (lex_match_id ("HI") || lex_match_id ("HIGHEST")) |
|
|
cur->type = CNT_ANY; |
|
|
else |
|
|
{ |
|
|
lex_error (NULL); |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
else if (lex_match_id ("SYSMIS")) |
|
|
{ |
|
|
if (c->missing < 1) |
|
|
c->missing = 1; |
|
|
} |
|
208 |
else if (lex_match_id ("MISSING")) |
else if (lex_match_id ("MISSING")) |
209 |
c->missing = 2; |
crit->count_user_missing = true; |
210 |
|
else if (parse_num_range (&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 |
else |
224 |
{ |
return false; |
|
lex_error (NULL); |
|
|
return 0; |
|
|
} |
|
225 |
|
|
226 |
lex_match (','); |
lex_match (','); |
227 |
if (lex_match (')')) |
if (lex_match (')')) |
228 |
break; |
break; |
229 |
} |
} |
230 |
|
return true; |
|
c->crit.n[n].type = CNT_SENTINEL; |
|
|
return 1; |
|
231 |
} |
} |
232 |
|
|
233 |
/* Parses a set of string criteria values. The skeleton is the same |
/* Parses a set of string criteria values. Returns success. */ |
234 |
as parse_numeric_criteria(). */ |
static bool |
235 |
static int |
parse_string_criteria (struct pool *pool, struct criteria *crit) |
|
parse_string_criteria (struct counting *c) |
|
236 |
{ |
{ |
237 |
int len = 0; |
int len = 0; |
238 |
|
size_t allocated = 0; |
|
size_t n = 0; |
|
|
size_t m = 0; |
|
|
|
|
239 |
size_t i; |
size_t i; |
240 |
|
|
241 |
for (i = 0; i < c->n; i++) |
for (i = 0; i < crit->var_cnt; i++) |
242 |
if (c->v[i]->width > len) |
if (crit->vars[i]->width > len) |
243 |
len = c->v[i]->width; |
len = crit->vars[i]->width; |
244 |
|
|
245 |
c->crit.n = 0; |
crit->values.str = NULL; |
246 |
for (;;) |
for (;;) |
247 |
{ |
{ |
248 |
struct cnt_str *cur; |
char **cur; |
249 |
if (n + 1 >= m) |
if (crit->value_cnt >= allocated) |
250 |
{ |
crit->values.str = pool_2nrealloc (pool, crit->values.str, |
251 |
m += 16; |
&allocated, |
252 |
c->crit.s = xnrealloc (c->crit.s, m, sizeof *c->crit.s); |
sizeof *crit->values.str); |
|
} |
|
253 |
|
|
254 |
if (!lex_force_string ()) |
if (!lex_force_string ()) |
255 |
return 0; |
return false; |
256 |
cur = &c->crit.s[n++]; |
cur = &crit->values.str[crit->value_cnt++]; |
257 |
cur->type = CNT_SINGLE; |
*cur = pool_alloc (pool, len + 1); |
258 |
cur->s = malloc (len + 1); |
str_copy_rpad (*cur, len + 1, ds_c_str (&tokstr)); |
|
str_copy_rpad (cur->s, len + 1, ds_c_str (&tokstr)); |
|
259 |
lex_get (); |
lex_get (); |
260 |
|
|
261 |
lex_match (','); |
lex_match (','); |
263 |
break; |
break; |
264 |
} |
} |
265 |
|
|
266 |
c->crit.s[n].type = CNT_SENTINEL; |
return true; |
|
return 1; |
|
267 |
} |
} |
268 |
|
|
269 |
/* Transformation. */ |
/* Transformation. */ |
270 |
|
|
271 |
/* Counts the number of values in case C matching counting CNT. */ |
/* Counts the number of values in case C matching CRIT. */ |
272 |
static inline int |
static inline int |
273 |
count_numeric (struct counting *cnt, struct ccase *c) |
count_numeric (struct criteria *crit, struct ccase *c) |
274 |
{ |
{ |
275 |
int counter = 0; |
int counter = 0; |
276 |
size_t i; |
size_t i; |
277 |
|
|
278 |
for (i = 0; i < cnt->n; i++) |
for (i = 0; i < crit->var_cnt; i++) |
279 |
{ |
{ |
280 |
struct cnt_num *num; |
double x = case_num (c, crit->vars[i]->fv); |
281 |
|
if (x == SYSMIS) |
282 |
/* Extract the variable value and eliminate missing values. */ |
counter += crit->count_system_missing; |
283 |
double cmp = case_num (c, cnt->v[i]->fv); |
else if (crit->count_user_missing |
284 |
if (cmp == SYSMIS) |
&& mv_is_num_user_missing (&crit->vars[i]->miss, x)) |
285 |
{ |
counter++; |
286 |
if (cnt->missing >= 1) |
else |
287 |
counter++; |
{ |
288 |
continue; |
struct num_value *v; |
289 |
} |
|
290 |
if (cnt->missing >= 2 && mv_is_num_user_missing (&cnt->v[i]->miss, cmp)) |
for (v = crit->values.num; v < crit->values.num + crit->value_cnt; |
291 |
{ |
v++) |
292 |
counter++; |
if (v->type == CNT_SINGLE ? x == v->a : x >= v->a && x <= v->b) |
293 |
continue; |
{ |
294 |
} |
counter++; |
295 |
|
break; |
296 |
/* Try to find the value in the list. */ |
} |
297 |
for (num = cnt->crit.n;; num++) |
} |
|
switch (num->type) |
|
|
{ |
|
|
case CNT_ERROR: |
|
|
assert (0); |
|
|
break; |
|
|
case CNT_SINGLE: |
|
|
if (cmp != num->a) |
|
|
break; |
|
|
counter++; |
|
|
goto done; |
|
|
case CNT_HIGH: |
|
|
if (cmp < num->a) |
|
|
break; |
|
|
counter++; |
|
|
goto done; |
|
|
case CNT_LOW: |
|
|
if (cmp > num->a) |
|
|
break; |
|
|
counter++; |
|
|
goto done; |
|
|
case CNT_RANGE: |
|
|
if (cmp < num->a || cmp > num->b) |
|
|
break; |
|
|
counter++; |
|
|
goto done; |
|
|
case CNT_ANY: |
|
|
counter++; |
|
|
goto done; |
|
|
case CNT_SENTINEL: |
|
|
goto done; |
|
|
default: |
|
|
assert (0); |
|
|
} |
|
|
done: ; |
|
298 |
} |
} |
299 |
|
|
300 |
return counter; |
return counter; |
301 |
} |
} |
302 |
|
|
303 |
/* Counts the number of values in case C matching counting CNT. */ |
/* Counts the number of values in case C matching CRIT. */ |
304 |
static inline int |
static inline int |
305 |
count_string (struct counting *cnt, struct ccase *c) |
count_string (struct criteria *crit, struct ccase *c) |
306 |
{ |
{ |
307 |
int counter = 0; |
int counter = 0; |
308 |
size_t i; |
size_t i; |
309 |
|
|
310 |
for (i = 0; i < cnt->n; i++) |
for (i = 0; i < crit->var_cnt; i++) |
311 |
{ |
{ |
312 |
struct cnt_str *str; |
char **v; |
313 |
|
for (v = crit->values.str; v < crit->values.str + crit->value_cnt; v++) |
314 |
/* Extract the variable value, variable width. */ |
if (!memcmp (case_str (c, crit->vars[i]->fv), *v, |
315 |
for (str = cnt->crit.s;; str++) |
crit->vars[i]->width)) |
316 |
switch (str->type) |
{ |
|
{ |
|
|
case CNT_ERROR: |
|
|
assert (0); |
|
|
case CNT_SINGLE: |
|
|
if (memcmp (case_str (c, cnt->v[i]->fv), str->s, |
|
|
cnt->v[i]->width)) |
|
|
break; |
|
317 |
counter++; |
counter++; |
318 |
goto done; |
break; |
319 |
case CNT_SENTINEL: |
} |
|
goto done; |
|
|
default: |
|
|
assert (0); |
|
|
} |
|
|
done: ; |
|
320 |
} |
} |
321 |
|
|
322 |
return counter; |
return counter; |
323 |
} |
} |
324 |
|
|
325 |
/* Performs the COUNT transformation T on case C. */ |
/* Performs the COUNT transformation T on case C. */ |
326 |
static int |
static int |
327 |
count_trns_proc (struct trns_header *trns, struct ccase *c, |
count_trns_proc (struct trns_header *trns_, struct ccase *c, |
328 |
int case_num UNUSED) |
int case_num UNUSED) |
329 |
{ |
{ |
330 |
struct cnt_var_info *info; |
struct count_trns *trns = (struct count_trns *) trns_; |
331 |
struct counting *cnt; |
struct dst_var *dv; |
|
int counter; |
|
332 |
|
|
333 |
for (info = ((struct count_trns *) trns)->specs; info; info = info->next) |
for (dv = trns->dst_vars; dv; dv = dv->next) |
334 |
{ |
{ |
335 |
|
struct criteria *crit; |
336 |
|
int counter; |
337 |
|
|
338 |
counter = 0; |
counter = 0; |
339 |
for (cnt = info->c; cnt; cnt = cnt->next) |
for (crit = dv->crit; crit; crit = crit->next) |
340 |
if (cnt->v[0]->type == NUMERIC) |
if (crit->vars[0]->type == NUMERIC) |
341 |
counter += count_numeric (cnt, c); |
counter += count_numeric (crit, c); |
342 |
else |
else |
343 |
counter += count_string (cnt, c); |
counter += count_string (crit, c); |
344 |
case_data_rw (c, info->d->fv)->f = counter; |
case_data_rw (c, dv->var->fv)->f = counter; |
345 |
} |
} |
346 |
return -1; |
return -1; |
347 |
} |
} |
348 |
|
|
349 |
/* Destroys all dynamic data structures associated with T. */ |
/* Destroys all dynamic data structures associated with TRNS. */ |
350 |
static void |
static void |
351 |
count_trns_free (struct trns_header *t) |
count_trns_free (struct trns_header *trns_) |
352 |
{ |
{ |
353 |
struct cnt_var_info *iter, *next; |
struct count_trns *trns = (struct count_trns *) trns_; |
354 |
|
pool_destroy (trns->pool); |
|
for (iter = ((struct count_trns *) t)->specs; iter; iter = next) |
|
|
{ |
|
|
struct counting *i, *n; |
|
|
|
|
|
for (i = iter->c; i; i = n) |
|
|
{ |
|
|
if (i->n && i->v) |
|
|
{ |
|
|
if (i->v[0]->type == NUMERIC) |
|
|
free (i->crit.n); |
|
|
else |
|
|
{ |
|
|
struct cnt_str *s; |
|
|
|
|
|
for (s = i->crit.s; s->type != CNT_SENTINEL; s++) |
|
|
free (s->s); |
|
|
free (i->crit.s); |
|
|
} |
|
|
} |
|
|
free (i->v); |
|
|
|
|
|
n = i->next; |
|
|
free (i); |
|
|
} |
|
|
|
|
|
next = iter->next; |
|
|
free (iter); |
|
|
} |
|
355 |
} |
} |