Change "union value" to dynamically allocate long strings.
[pspp-builds.git] / src / data / caseproto.c
diff --git a/src/data/caseproto.c b/src/data/caseproto.c
new file mode 100644 (file)
index 0000000..1b6827d
--- /dev/null
@@ -0,0 +1,411 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <data/caseproto.h>
+#include <data/val-type.h>
+#include <data/value.h>
+#include <libpspp/array.h>
+#include <libpspp/assertion.h>
+#include <libpspp/pool.h>
+
+#include "minmax.h"
+
+static struct caseproto *caseproto_unshare (struct caseproto *);
+static bool try_init_long_strings (const struct caseproto *,
+                                   size_t first, size_t last, union value[]);
+static void init_long_strings (const struct caseproto *,
+                               size_t first, size_t last, union value[]);
+static void destroy_long_strings (const struct caseproto *,
+                                  size_t first, size_t last, union value[]);
+static size_t count_long_strings (const struct caseproto *,
+                                  size_t idx, size_t count);
+
+/* Returns the number of bytes to allocate for a struct caseproto
+   with room for N_WIDTHS elements in its widths[] array. */
+static inline size_t
+caseproto_size (size_t n_widths)
+{
+  return (offsetof (struct caseproto, widths)
+          + n_widths * sizeof (((struct caseproto *) NULL)->widths[0]));
+}
+
+/* Creates and returns a case prototype that initially has no
+   widths. */
+struct caseproto *
+caseproto_create (void)
+{
+  enum { N_ALLOCATE = 4 };
+  struct caseproto *proto = xmalloc (caseproto_size (N_ALLOCATE));
+  proto->ref_cnt = 1;
+  proto->long_strings = NULL;
+  proto->n_long_strings = 0;
+  proto->n_widths = 0;
+  proto->allocated_widths = N_ALLOCATE;
+  return proto;
+}
+
+static void
+do_unref (void *proto_)
+{
+  struct caseproto *proto = proto_;
+  caseproto_unref (proto);
+}
+
+/* Creates and returns a new reference to PROTO.  When POOL is
+   destroyed, the new reference will be destroyed (unrefed).  */
+struct caseproto *
+caseproto_ref_pool (const struct caseproto *proto_, struct pool *pool)
+{
+  struct caseproto *proto = caseproto_ref (proto_);
+  pool_register (pool, do_unref, proto);
+  return proto;
+}
+
+/* Returns a replacement for PROTO that is unshared and has
+   enough room for at least N_WIDTHS widths before additional
+   memory is needed.  */
+struct caseproto *
+caseproto_reserve (struct caseproto *proto, size_t n_widths)
+{
+  proto = caseproto_unshare (proto);
+  if (n_widths > proto->allocated_widths)
+    {
+      proto->allocated_widths *= MAX (proto->allocated_widths * 2, n_widths);
+      proto = xrealloc (proto, caseproto_size (proto->allocated_widths));
+    }
+  return proto;
+}
+
+/* Returns a replacement for PROTO with WIDTH appended.  */
+struct caseproto *
+caseproto_add_width (struct caseproto *proto, int width)
+{
+  assert (width >= -1 && width <= MAX_STRING);
+
+  proto = caseproto_reserve (proto, proto->n_widths + 1);
+  proto->widths[proto->n_widths++] = width;
+  proto->n_long_strings += count_long_strings (proto, proto->n_widths - 1, 1);
+
+  return proto;
+}
+
+/* Returns a replacement for PROTO with the width at index IDX
+   replaced by WIDTH.  IDX may be greater than the current number
+   of widths in PROTO, in which case any gap is filled in by
+   widths of -1. */
+struct caseproto *
+caseproto_set_width (struct caseproto *proto, size_t idx, int width)
+{
+  assert (width >= -1 && width <= MAX_STRING);
+
+  proto = caseproto_reserve (proto, idx + 1);
+  while (idx >= proto->n_widths)
+    proto->widths[proto->n_widths++] = -1;
+  proto->n_long_strings -= count_long_strings (proto, idx, 1);
+  proto->widths[idx] = width;
+  proto->n_long_strings += count_long_strings (proto, idx, 1);
+
+  return proto;
+}
+
+/* Returns a replacement for PROTO with WIDTH inserted just
+   before index BEFORE, or just after the last element if BEFORE
+   is the number of widths in PROTO. */
+struct caseproto *
+caseproto_insert_width (struct caseproto *proto, size_t before, int width)
+{
+  assert (before <= proto->n_widths);
+
+  proto = caseproto_reserve (proto, proto->n_widths + 1);
+  proto->n_long_strings += value_needs_init (width);
+  insert_element (proto->widths, proto->n_widths, sizeof *proto->widths,
+                  before);
+  proto->widths[before] = width;
+  proto->n_widths++;
+
+  return proto;
+}
+
+/* Returns a replacement for PROTO with CNT widths removed
+   starting at index IDX. */
+struct caseproto *
+caseproto_remove_widths (struct caseproto *proto, size_t idx, size_t cnt)
+{
+  assert (caseproto_range_is_valid (proto, idx, cnt));
+
+  proto = caseproto_unshare (proto);
+  proto->n_long_strings -= count_long_strings (proto, idx, cnt);
+  remove_range (proto->widths, proto->n_widths, sizeof *proto->widths,
+                idx, cnt);
+  proto->n_widths -= cnt;
+  return proto;
+}
+
+/* Returns a replacement for PROTO in which the CNT widths
+   starting at index OLD_WIDTH now start at index NEW_WIDTH, with
+   other widths shifting out of the way to make room. */
+struct caseproto *
+caseproto_move_widths (struct caseproto *proto,
+                       size_t old_start, size_t new_start,
+                       size_t cnt)
+{
+  assert (caseproto_range_is_valid (proto, old_start, cnt));
+  assert (caseproto_range_is_valid (proto, new_start, cnt));
+
+  proto = caseproto_unshare (proto);
+  move_range (proto->widths, proto->n_widths, sizeof *proto->widths,
+              old_start, new_start, cnt);
+  return proto;
+}
+
+/* Returns true if PROTO contains COUNT widths starting at index
+   OFS, false if any of those widths are out of range for
+   PROTO. */
+bool
+caseproto_range_is_valid (const struct caseproto *proto,
+                          size_t ofs, size_t count)
+{
+  return (count <= proto->n_widths
+          && ofs <= proto->n_widths
+          && ofs + count <= proto->n_widths);
+}
+
+/* Returns true if A and B have the same widths along their
+   common length.  (When this is so, a case with prototype A may
+   be extended or truncated to have prototype B without having to
+   change any existing values, and vice versa.) */
+bool
+caseproto_is_conformable (const struct caseproto *a, const struct caseproto *b)
+{
+  size_t min;
+  size_t i;
+
+  min = MIN (a->n_widths, b->n_widths);
+  for (i = 0; i < min; i++)
+    if (a->widths[i] != b->widths[i])
+      return false;
+  return true;
+}
+
+/* Returns true if the N widths starting at A_START in A are the
+   same as the N widths starting at B_START in B, false if any of
+   the corresponding widths differ. */
+bool
+caseproto_equal (const struct caseproto *a, size_t a_start,
+                 const struct caseproto *b, size_t b_start,
+                 size_t n)
+{
+  size_t i;
+
+  assert (caseproto_range_is_valid (a, a_start, n));
+  assert (caseproto_range_is_valid (b, b_start, n));
+  for (i = 0; i < n; i++)
+    if (a->widths[a_start + i] != b->widths[b_start + i])
+      return false;
+  return true;
+}
+
+/* Returns true if an array of values that is to be used for
+   data of the format specified in PROTO needs to be initialized
+   by calling caseproto_init_values, false if that step may be
+   skipped because such an initialization would be a no-op anyhow.
+
+   This optimization is useful only when a large number of
+   initializations of such arrays may be skipped as a group. */
+bool
+caseproto_needs_init_values (const struct caseproto *proto)
+{
+  return proto->n_long_strings > 0;
+}
+
+/* Initializes the values in VALUES as required by PROTO, by
+   calling value_init() on each value for which this is required.
+   The data in VALUES have indeterminate contents until
+   explicitly written.
+
+   VALUES must have at least caseproto_get_n_widths(PROTO)
+   elements; only that many elements of VALUES are initialized.
+
+   The caller retains ownership of PROTO. */
+void
+caseproto_init_values (const struct caseproto *proto, union value values[])
+{
+  init_long_strings (proto, 0, proto->n_long_strings, values);
+}
+
+/* Like caseproto_init_values, but returns false instead of
+   terminating if memory cannot be obtained. */
+bool
+caseproto_try_init_values (const struct caseproto *proto, union value values[])
+{
+  return try_init_long_strings (proto, 0, proto->n_long_strings, values);
+}
+
+/* Initializes the data in VALUES that are in NEW but not in OLD,
+   destroys the data in VALUES that are in OLD but not NEW, and
+   does not modify the data in VALUES that are in both OLD and
+   NEW.  VALUES must previously have been initialized as required
+   by OLD using e.g. caseproto_init_values.  The data in VALUES
+   that are in NEW but not in OLD will have indeterminate
+   contents until explicitly written.
+
+   OLD and NEW must be conformable for this operation, as
+   reported by caseproto_is_conformable.
+
+   The caller retains ownership of OLD and NEW. */
+void
+caseproto_reinit_values (const struct caseproto *old,
+                         const struct caseproto *new, union value values[])
+{
+  size_t old_n_long = old->n_long_strings;
+  size_t new_n_long = new->n_long_strings;
+
+  expensive_assert (caseproto_is_conformable (old, new));
+
+  if (new_n_long > old_n_long)
+    init_long_strings (new, old_n_long, new_n_long, values);
+  else if (new_n_long < old_n_long)
+    destroy_long_strings (old, new_n_long, old_n_long, values);
+}
+
+/* Frees the values in VALUES as required by PROTO, by calling
+   value_destroy() on each value for which this is required.  The
+   values must previously have been initialized using
+   e.g. caseproto_init_values.
+
+   The caller retains ownership of PROTO. */
+void
+caseproto_destroy_values (const struct caseproto *proto, union value values[])
+{
+  destroy_long_strings (proto, 0, proto->n_long_strings, values);
+}
+
+/* Copies COUNT values, whose widths are given by widths in PROTO
+   starting with index IDX, from SRC to DST.  The caller must
+   ensure that the values in SRC and DST were appropriately
+   initialized using e.g. caseproto_init_values. */
+void
+caseproto_copy (const struct caseproto *proto, size_t idx, size_t count,
+                union value *dst, const union value *src)
+{
+  size_t i;
+
+  assert (caseproto_range_is_valid (proto, idx, count));
+  for (i = 0; i < count; i++)
+    value_copy (&dst[idx + i], &src[idx + i], proto->widths[idx + i]);
+}
+\f
+void
+caseproto_free__ (struct caseproto *proto)
+{
+  free (proto->long_strings);
+  free (proto);
+}
+
+void
+caseproto_refresh_long_string_cache__ (const struct caseproto *proto_)
+{
+  struct caseproto *proto = (struct caseproto *) proto_;
+  size_t n, i;
+
+  assert (proto->long_strings == NULL);
+  assert (proto->n_long_strings > 0);
+
+  proto->long_strings = xmalloc (proto->n_long_strings
+                                 * sizeof *proto->long_strings);
+  n = 0;
+  for (i = 0; i < proto->n_widths; i++)
+    if (proto->widths[i] >= MIN_LONG_STRING)
+      proto->long_strings[n++] = i;
+  assert (n == proto->n_long_strings);
+}
+
+static struct caseproto *
+caseproto_unshare (struct caseproto *old)
+{
+  struct caseproto *new;
+  if (old->ref_cnt > 1)
+    {
+      new = xmemdup (old, caseproto_size (old->allocated_widths));
+      new->ref_cnt = 1;
+      --old->ref_cnt;
+    }
+  else
+    {
+      new = old;
+      free (new->long_strings);
+    }
+  new->long_strings = NULL;
+  return new;
+}
+
+static bool
+try_init_long_strings (const struct caseproto *proto,
+                       size_t first, size_t last, union value values[])
+{
+  size_t i;
+
+  if (last > 0 && proto->long_strings == NULL)
+    caseproto_refresh_long_string_cache__ (proto);
+
+  for (i = first; i < last; i++)
+    {
+      size_t idx = proto->long_strings[i];
+      if (!value_try_init (&values[idx], proto->widths[idx]))
+        {
+          destroy_long_strings (proto, first, i, values);
+          return false;
+        }
+    }
+  return true;
+}
+
+static void
+init_long_strings (const struct caseproto *proto,
+                   size_t first, size_t last, union value values[])
+{
+  if (!try_init_long_strings (proto, first, last, values))
+    xalloc_die ();
+}
+
+static void
+destroy_long_strings (const struct caseproto *proto, size_t first, size_t last,
+                      union value values[])
+{
+  size_t i;
+
+  if (last > 0 && proto->long_strings == NULL)
+    caseproto_refresh_long_string_cache__ (proto);
+
+  for (i = first; i < last; i++)
+    {
+      size_t idx = proto->long_strings[i];
+      value_destroy (&values[idx], proto->widths[idx]);
+    }
+}
+
+static size_t
+count_long_strings (const struct caseproto *proto, size_t idx, size_t count)
+{
+  size_t n, i;
+
+  n = 0;
+  for (i = 0; i < count; i++)
+    n += proto->widths[idx + i] >= MIN_LONG_STRING;
+  return n;
+}