c47b6ae98221930d7a201c946704c1a78cb33f75
[pspp] / src / data / caseproto.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU 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, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "data/caseproto.h"
20
21 #include "data/val-type.h"
22 #include "data/value.h"
23 #include "libpspp/array.h"
24 #include "libpspp/assertion.h"
25 #include "libpspp/pool.h"
26
27 #include "gl/minmax.h"
28
29 static struct caseproto *caseproto_unshare (struct caseproto *);
30 static bool try_init_strings (const struct caseproto *,
31                                    size_t first, size_t last, union value[]);
32 static void init_strings (const struct caseproto *,
33                                size_t first, size_t last, union value[]);
34 static void destroy_strings (const struct caseproto *,
35                                   size_t first, size_t last, union value[]);
36 static size_t count_strings (const struct caseproto *,
37                                   size_t idx, size_t count);
38
39 /* Returns the number of bytes to allocate for a struct caseproto
40    with room for N_WIDTHS elements in its widths[] array. */
41 static inline size_t
42 caseproto_size (size_t n_widths)
43 {
44   return (offsetof (struct caseproto, widths)
45           + n_widths * sizeof (((struct caseproto *) NULL)->widths[0]));
46 }
47
48 /* Creates and returns a case prototype that initially has no
49    widths. */
50 struct caseproto *
51 caseproto_create (void)
52 {
53   enum { N_ALLOCATE = 4 };
54   struct caseproto *proto = xmalloc (caseproto_size (N_ALLOCATE));
55   proto->ref_cnt = 1;
56   proto->strings = NULL;
57   proto->n_strings = 0;
58   proto->n_widths = 0;
59   proto->allocated_widths = N_ALLOCATE;
60   return proto;
61 }
62
63 static void
64 do_unref (void *proto_)
65 {
66   struct caseproto *proto = proto_;
67   caseproto_unref (proto);
68 }
69
70 /* Creates and returns a new reference to PROTO.  When POOL is
71    destroyed, the new reference will be destroyed (unrefed).  */
72 struct caseproto *
73 caseproto_ref_pool (const struct caseproto *proto_, struct pool *pool)
74 {
75   struct caseproto *proto = caseproto_ref (proto_);
76   pool_register (pool, do_unref, proto);
77   return proto;
78 }
79
80 /* Returns a replacement for PROTO that is unshared and has
81    enough room for at least N_WIDTHS widths before additional
82    memory is needed.  */
83 struct caseproto *
84 caseproto_reserve (struct caseproto *proto, size_t n_widths)
85 {
86   proto = caseproto_unshare (proto);
87   if (n_widths > proto->allocated_widths)
88     {
89       proto->allocated_widths = MAX (proto->allocated_widths * 2, n_widths);
90       proto = xrealloc (proto, caseproto_size (proto->allocated_widths));
91     }
92   return proto;
93 }
94
95 /* Returns a replacement for PROTO with WIDTH appended.  */
96 struct caseproto *
97 caseproto_add_width (struct caseproto *proto, int width)
98 {
99   assert (width >= -1 && width <= MAX_STRING);
100
101   proto = caseproto_reserve (proto, proto->n_widths + 1);
102   proto->widths[proto->n_widths++] = width;
103   proto->n_strings += count_strings (proto, proto->n_widths - 1, 1);
104
105   return proto;
106 }
107
108 /* Returns a replacement for PROTO with the width at index IDX
109    replaced by WIDTH.  IDX may be greater than the current number
110    of widths in PROTO, in which case any gap is filled in by
111    widths of -1. */
112 struct caseproto *
113 caseproto_set_width (struct caseproto *proto, size_t idx, int width)
114 {
115   assert (width >= -1 && width <= MAX_STRING);
116
117   proto = caseproto_reserve (proto, idx + 1);
118   while (idx >= proto->n_widths)
119     proto->widths[proto->n_widths++] = -1;
120   proto->n_strings -= count_strings (proto, idx, 1);
121   proto->widths[idx] = width;
122   proto->n_strings += count_strings (proto, idx, 1);
123
124   return proto;
125 }
126
127 /* Returns a replacement for PROTO with WIDTH inserted just
128    before index BEFORE, or just after the last element if BEFORE
129    is the number of widths in PROTO. */
130 struct caseproto *
131 caseproto_insert_width (struct caseproto *proto, size_t before, int width)
132 {
133   assert (before <= proto->n_widths);
134
135   proto = caseproto_reserve (proto, proto->n_widths + 1);
136   proto->n_strings += value_needs_init (width);
137   insert_element (proto->widths, proto->n_widths, sizeof *proto->widths,
138                   before);
139   proto->widths[before] = width;
140   proto->n_widths++;
141
142   return proto;
143 }
144
145 /* Returns a replacement for PROTO with N widths removed
146    starting at index IDX. */
147 struct caseproto *
148 caseproto_remove_widths (struct caseproto *proto, size_t idx, size_t n)
149 {
150   assert (caseproto_range_is_valid (proto, idx, n));
151
152   proto = caseproto_unshare (proto);
153   proto->n_strings -= count_strings (proto, idx, n);
154   remove_range (proto->widths, proto->n_widths, sizeof *proto->widths,
155                 idx, n);
156   proto->n_widths -= n;
157   return proto;
158 }
159
160 /* Returns a replacement for PROTO in which the N widths
161    starting at index OLD_WIDTH now start at index NEW_WIDTH, with
162    other widths shifting out of the way to make room. */
163 struct caseproto *
164 caseproto_move_widths (struct caseproto *proto,
165                        size_t old_start, size_t new_start,
166                        size_t n)
167 {
168   assert (caseproto_range_is_valid (proto, old_start, n));
169   assert (caseproto_range_is_valid (proto, new_start, n));
170
171   proto = caseproto_unshare (proto);
172   move_range (proto->widths, proto->n_widths, sizeof *proto->widths,
173               old_start, new_start, n);
174   return proto;
175 }
176
177 /* Returns true if PROTO contains COUNT widths starting at index
178    OFS, false if any of those widths are out of range for
179    PROTO. */
180 bool
181 caseproto_range_is_valid (const struct caseproto *proto,
182                           size_t ofs, size_t count)
183 {
184   return (count <= proto->n_widths
185           && ofs <= proto->n_widths
186           && ofs + count <= proto->n_widths);
187 }
188
189 /* Returns true if A and B have the same widths along their
190    common length.  (When this is so, a case with prototype A may
191    be extended or truncated to have prototype B without having to
192    change any existing values, and vice versa.) */
193 bool
194 caseproto_is_conformable (const struct caseproto *a, const struct caseproto *b)
195 {
196   size_t min;
197   size_t i;
198
199   min = MIN (a->n_widths, b->n_widths);
200   for (i = 0; i < min; i++)
201     if (a->widths[i] != b->widths[i])
202       return false;
203   return true;
204 }
205
206 /* Returns true if the N widths starting at A_START in A are the
207    same as the N widths starting at B_START in B, false if any of
208    the corresponding widths differ. */
209 bool
210 caseproto_range_equal (const struct caseproto *a, size_t a_start,
211                        const struct caseproto *b, size_t b_start,
212                        size_t n)
213 {
214   size_t i;
215
216   assert (caseproto_range_is_valid (a, a_start, n));
217   assert (caseproto_range_is_valid (b, b_start, n));
218   for (i = 0; i < n; i++)
219     if (a->widths[a_start + i] != b->widths[b_start + i])
220       return false;
221   return true;
222 }
223
224 /* Returns true if A and B have the same widths, false otherwise. */
225 bool
226 caseproto_equal (const struct caseproto *a, const struct caseproto *b)
227 {
228   return (a == b ? true
229           : a->n_widths != b->n_widths ? false
230           : caseproto_range_equal (a, 0, b, 0, a->n_widths));
231 }
232
233 /* Returns true if an array of values that is to be used for
234    data of the format specified in PROTO needs to be initialized
235    by calling caseproto_init_values, false if that step may be
236    skipped because such an initialization would be a no-op anyhow.
237
238    This optimization is useful only when a large number of
239    initializations of such arrays may be skipped as a group. */
240 bool
241 caseproto_needs_init_values (const struct caseproto *proto)
242 {
243   return proto->n_strings > 0;
244 }
245
246 /* Initializes the values in VALUES as required by PROTO, by
247    calling value_init() on each value for which this is required.
248    The data in VALUES have indeterminate contents until
249    explicitly written.
250
251    VALUES must have at least caseproto_get_n_widths(PROTO)
252    elements; only that many elements of VALUES are initialized.
253
254    The caller retains ownership of PROTO. */
255 void
256 caseproto_init_values (const struct caseproto *proto, union value values[])
257 {
258   init_strings (proto, 0, proto->n_strings, values);
259 }
260
261 /* Like caseproto_init_values, but returns false instead of
262    terminating if memory cannot be obtained. */
263 bool
264 caseproto_try_init_values (const struct caseproto *proto, union value values[])
265 {
266   return try_init_strings (proto, 0, proto->n_strings, values);
267 }
268
269 /* Initializes the data in VALUES that are in NEW but not in OLD,
270    destroys the data in VALUES that are in OLD but not NEW, and
271    does not modify the data in VALUES that are in both OLD and
272    NEW.  VALUES must previously have been initialized as required
273    by OLD using e.g. caseproto_init_values.  The data in VALUES
274    that are in NEW but not in OLD will have indeterminate
275    contents until explicitly written.
276
277    OLD and NEW must be conformable for this operation, as
278    reported by caseproto_is_conformable.
279
280    The caller retains ownership of OLD and NEW. */
281 void
282 caseproto_reinit_values (const struct caseproto *old,
283                          const struct caseproto *new, union value values[])
284 {
285   size_t old_n_long = old->n_strings;
286   size_t new_n_long = new->n_strings;
287
288   expensive_assert (caseproto_is_conformable (old, new));
289
290   if (new_n_long > old_n_long)
291     init_strings (new, old_n_long, new_n_long, values);
292   else if (new_n_long < old_n_long)
293     destroy_strings (old, new_n_long, old_n_long, values);
294 }
295
296 /* Frees the values in VALUES as required by PROTO, by calling
297    value_destroy() on each value for which this is required.  The
298    values must previously have been initialized using
299    e.g. caseproto_init_values.
300
301    The caller retains ownership of PROTO. */
302 void
303 caseproto_destroy_values (const struct caseproto *proto, union value values[])
304 {
305   destroy_strings (proto, 0, proto->n_strings, values);
306 }
307
308 /* Copies COUNT values, whose widths are given by widths in PROTO
309    starting with index IDX, from SRC to DST.  The caller must
310    ensure that the values in SRC and DST were appropriately
311    initialized using e.g. caseproto_init_values. */
312 void
313 caseproto_copy (const struct caseproto *proto, size_t idx, size_t count,
314                 union value *dst, const union value *src)
315 {
316   size_t i;
317
318   assert (caseproto_range_is_valid (proto, idx, count));
319   for (i = 0; i < count; i++)
320     value_copy (&dst[idx + i], &src[idx + i], proto->widths[idx + i]);
321 }
322 \f
323 void
324 caseproto_free__ (struct caseproto *proto)
325 {
326   free (proto->strings);
327   free (proto);
328 }
329
330 void
331 caseproto_refresh_string_cache__ (const struct caseproto *proto_)
332 {
333   struct caseproto *proto = CONST_CAST (struct caseproto *, proto_);
334   size_t n, i;
335
336   assert (proto->strings == NULL);
337   assert (proto->n_strings > 0);
338
339   proto->strings = xmalloc (proto->n_strings * sizeof *proto->strings);
340   n = 0;
341   for (i = 0; i < proto->n_widths; i++)
342     if (proto->widths[i] > 0)
343       proto->strings[n++] = i;
344   assert (n == proto->n_strings);
345 }
346
347 static struct caseproto *
348 caseproto_unshare (struct caseproto *old)
349 {
350   struct caseproto *new;
351   if (old->ref_cnt > 1)
352     {
353       new = xmemdup (old, caseproto_size (old->allocated_widths));
354       new->ref_cnt = 1;
355       --old->ref_cnt;
356     }
357   else
358     {
359       new = old;
360       free (new->strings);
361     }
362   new->strings = NULL;
363   return new;
364 }
365
366 static bool
367 try_init_strings (const struct caseproto *proto,
368                        size_t first, size_t last, union value values[])
369 {
370   size_t i;
371
372   if (last > 0 && proto->strings == NULL)
373     caseproto_refresh_string_cache__ (proto);
374
375   for (i = first; i < last; i++)
376     {
377       size_t idx = proto->strings[i];
378       if (!value_try_init (&values[idx], proto->widths[idx]))
379         {
380           destroy_strings (proto, first, i, values);
381           return false;
382         }
383     }
384   return true;
385 }
386
387 static void
388 init_strings (const struct caseproto *proto,
389                    size_t first, size_t last, union value values[])
390 {
391   if (!try_init_strings (proto, first, last, values))
392     xalloc_die ();
393 }
394
395 static void
396 destroy_strings (const struct caseproto *proto, size_t first, size_t last,
397                       union value values[])
398 {
399   size_t i;
400
401   if (last > 0 && proto->strings == NULL)
402     caseproto_refresh_string_cache__ (proto);
403
404   for (i = first; i < last; i++)
405     {
406       size_t idx = proto->strings[i];
407       value_destroy (&values[idx], proto->widths[idx]);
408     }
409 }
410
411 static size_t
412 count_strings (const struct caseproto *proto, size_t idx, size_t count)
413 {
414   size_t n, i;
415
416   n = 0;
417   for (i = 0; i < count; i++)
418     n += proto->widths[idx + i] > 0;
419   return n;
420 }