pxd: initial work
[pspp] / src / data / value-labels.c
index 0fe829cebe1781c4ccc2d791756d534b88153e0c..c1dee054ac8318d9c94dc1387b88e5af46aa9ad9 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2009 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012 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
 
 #include <config.h>
 
-#include "value-labels.h"
+#include "data/value-labels.h"
 
 #include <stdlib.h>
 
-#include <data/data-out.h>
-#include <data/value.h>
-#include <data/variable.h>
-#include <libpspp/array.h>
-#include <libpspp/cast.h>
-#include <libpspp/compiler.h>
-#include <libpspp/hash-functions.h>
-#include <libpspp/hmap.h>
-#include <libpspp/message.h>
-#include <libpspp/str.h>
-
-#include "xalloc.h"
-
-static struct atom *atom_create (const char *string);
-static void atom_destroy (struct atom *);
-static const char *atom_to_string (const struct atom *);
-
-/* Returns the label in VL.  The caller must not modify or free
-   the returned value. */
-const char *
-val_lab_get_label (const struct val_lab *vl)
-{
-  return atom_to_string (vl->label);
-}
+#include "data/data-out.h"
+#include "data/value.h"
+#include "data/variable.h"
+#include "libpspp/array.h"
+#include "libpspp/cast.h"
+#include "libpspp/compiler.h"
+#include "libpspp/hash-functions.h"
+#include "libpspp/hmap.h"
+#include "libpspp/intern.h"
+#include "libpspp/message.h"
+#include "libpspp/pxd.h"
+#include "libpspp/str.h"
+
+#include "gl/xalloc.h"
+
+static void val_labs_uncache__ (struct val_labs *);
+static void val_lab_uncache__ (struct val_lab *);
 
 /* Creates and returns a new, empty set of value labels with the
    given WIDTH. */
@@ -69,7 +62,7 @@ val_labs_clone (const struct val_labs *vls)
 
   copy = val_labs_create (vls->width);
   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
-    val_labs_add (copy, &label->value, atom_to_string (label->label));
+    val_labs_add (copy, &label->value, label->escaped_label);
   return copy;
 }
 
@@ -99,7 +92,11 @@ val_labs_set_width (struct val_labs *vls, int new_width)
       HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
         value_resize (&label->value, vls->width, new_width);
     }
-  vls->width = new_width;
+  if (vls->width != new_width)
+    {
+      vls->width = new_width;
+      val_labs_uncache__ (vls);
+    }
 }
 
 /* Destroys VLS. */
@@ -108,12 +105,21 @@ val_labs_destroy (struct val_labs *vls)
 {
   if (vls != NULL)
     {
+      val_labs_uncache__ (vls);
       val_labs_clear (vls);
       hmap_destroy (&vls->labels);
       free (vls);
     }
 }
 
+static void
+val_lab_clear (struct val_lab *lab, int width)
+{
+  val_lab_uncache__ (lab);
+  value_destroy (&lab->value, width);
+  intern_unref (lab->label);
+}
+
 /* Removes all the value labels from VLS. */
 void
 val_labs_clear (struct val_labs *vls)
@@ -122,13 +128,19 @@ val_labs_clear (struct val_labs *vls)
 
   HMAP_FOR_EACH_SAFE (label, next, struct val_lab, node, &vls->labels)
     {
+      val_lab_clear (label, vls->width);
       hmap_delete (&vls->labels, &label->node);
-      value_destroy (&label->value, vls->width);
-      atom_destroy (label->label);
       free (label);
     }
 }
 
+/* Returns the width of VLS. */
+int
+val_labs_get_width (const struct val_labs *vls)
+{
+  return vls->width;
+}
+
 /* Returns the number of value labels in VLS.
    Returns 0 if VLS is null. */
 size_t
@@ -138,18 +150,127 @@ val_labs_count (const struct val_labs *vls)
 }
 \f
 static void
+set_label (struct val_lab *lab, const char *escaped_label)
+{
+  lab->escaped_label = intern_new (escaped_label);
+  if (strstr (escaped_label, "\\n") == NULL)
+    lab->label = intern_ref (lab->escaped_label);
+  else
+    {
+      struct string s;
+      const char *p;
+
+      ds_init_empty (&s);
+      ds_extend (&s, intern_strlen (lab->escaped_label));
+      for (p = escaped_label; *p != '\0'; p++)
+        {
+          char c = *p;
+          if (c == '\\' && p[1] == 'n')
+            {
+              c = '\n';
+              p++;
+            }
+          ds_put_byte (&s, c);
+        }
+      lab->label = intern_new (ds_cstr (&s));
+      ds_destroy (&s);
+    }
+}
+
+static void
+val_labs_uncache__ (struct val_labs *vls UNUSED)
+{
+}
+
+static void
+val_lab_uncache__ (struct val_lab *vl UNUSED)
+{
+}
+
+static struct pxd_object *
+val_lab_save (const struct val_lab *vl, int width, struct pxd *pxd)
+{
+  struct pxd_builder b;
+
+  pxd_builder_init (&b, pxd);
+
+  pxd_builder_put_value (&b, &vl->value, width);
+  pxd_builder_put_interned_string (&b, vl->label);
+
+  return pxd_builder_commit (&b);
+}
+
+struct pxd_object *
+val_labs_save (const struct val_labs *vls, struct pxd *pxd)
+{
+  struct val_lab *label;
+  struct pxd_builder b;
+
+  pxd_builder_init (&b, pxd);
+
+  pxd_builder_put_u32 (&b, vls->width);
+  HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
+    pxd_builder_put_link (&b, val_lab_save (label, vls->width, pxd));
+
+  return pxd_builder_commit (&b);
+}
+
+static struct val_lab *
+val_lab_load (struct pxd_object *object, const struct pxd *pxd, int width)
+{
+  struct pxd_parser p;
+  struct val_lab *vl;
+
+  pxd_parser_init (&p, object, pxd);
+
+  vl = xmalloc (sizeof *vl);
+  pxd_parser_get_value (&p, &vl->value, width);
+  vl->label = pxd_parser_get_interned_string (&p);
+
+  pxd_parser_destroy (&p);
+
+  return vl;
+}
+
+struct val_labs *
+val_labs_load (struct pxd_object *object, const struct pxd *pxd)
+{
+  struct val_labs *vls;
+  struct pxd_parser p;
+  unsigned int i;
+  int width;
+
+  pxd_parser_init (&p, object, pxd);
+  width = pxd_parser_get_u32 (&p);
+
+  vls = val_labs_create (width);
+  for (i = 0; i < pxd_object_get_n_links (object); i++)
+    {
+      struct pxd_object *lab_obj = pxd_object_get_link (object, i, pxd);
+      struct val_lab *lab = val_lab_load (lab_obj, pxd, width);
+      hmap_insert (&vls->labels, &lab->node,
+                   value_hash (&lab->value, width, 0));
+    }
+
+  pxd_parser_destroy (&p);
+
+  return vls;
+}
+\f
+static void
 do_add_val_lab (struct val_labs *vls, const union value *value,
-                const char *label)
+                const char *escaped_label)
 {
   struct val_lab *lab = xmalloc (sizeof *lab);
-  value_init (&lab->value, vls->width);
-  value_copy (&lab->value, value, vls->width);
-  lab->label = atom_create (label);
+  value_clone (&lab->value, value, vls->width);
+  set_label (lab, escaped_label);
   hmap_insert (&vls->labels, &lab->node, value_hash (value, vls->width, 0));
 }
 
-/* If VLS does not already contain a value label for VALUE, adds
-   LABEL for it and returns true.  Otherwise, returns false. */
+/* If VLS does not already contain a value label for VALUE, adds the UTF-8
+   encoded LABEL for it and returns true.  Otherwise, returns false.
+
+   In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */
 bool
 val_labs_add (struct val_labs *vls, const union value *value,
               const char *label)
@@ -165,7 +286,9 @@ val_labs_add (struct val_labs *vls, const union value *value,
 }
 
 /* Sets LABEL as the value label for VALUE in VLS, replacing any
-   existing label for VALUE. */
+   existing label for VALUE.
+
+   In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */
 void
 val_labs_replace (struct val_labs *vls, const union value *value,
                   const char *label)
@@ -173,8 +296,10 @@ val_labs_replace (struct val_labs *vls, const union value *value,
   struct val_lab *vl = val_labs_lookup (vls, value);
   if (vl != NULL)
     {
-      atom_destroy (vl->label);
-      vl->label = atom_create (label);
+      val_lab_uncache__ (vl);
+      intern_unref (vl->label);
+      intern_unref (vl->escaped_label);
+      set_label (vl, label);
     }
   else
     do_add_val_lab (vls, value, label);
@@ -184,20 +309,36 @@ val_labs_replace (struct val_labs *vls, const union value *value,
 void
 val_labs_remove (struct val_labs *vls, struct val_lab *label)
 {
+  val_lab_clear (label, vls->width);
   hmap_delete (&vls->labels, &label->node);
-  value_destroy (&label->value, vls->width);
-  atom_destroy (label->label);
   free (label);
 }
 
-/* Searches VLS for a value label for VALUE.  If successful,
-   returns the string used as the label; otherwise, returns a
-   null pointer.  Returns a null pointer if VLS is null. */
+/* Searches VLS for a value label for VALUE.  If successful, returns the string
+   used as the label, as a UTF-8 encoded string in a format suitable for
+   output.  Otherwise, returns a null pointer.  Returns a null pointer if VLS
+   is null. */
 const char *
 val_labs_find (const struct val_labs *vls, const union value *value)
 {
   const struct val_lab *label = val_labs_lookup (vls, value);
-  return label ? atom_to_string (label->label) : NULL;
+  return label ? label->label : NULL;
+}
+
+/* Searches VLS for a value label for VALUE.  If successful,
+   returns the value label; otherwise, returns a null pointer.
+   Returns a null pointer if VLS is null. */
+static struct val_lab *
+val_labs_lookup__ (const struct val_labs *vls, const union value *value,
+                   unsigned int hash)
+{
+  struct val_lab *label;
+
+  HMAP_FOR_EACH_WITH_HASH (label, struct val_lab, node, hash, &vls->labels)
+    if (value_equal (&label->value, value, vls->width))
+      return label;
+
+  return NULL;
 }
 
 /* Searches VLS for a value label for VALUE.  If successful,
@@ -206,15 +347,37 @@ val_labs_find (const struct val_labs *vls, const union value *value)
 struct val_lab *
 val_labs_lookup (const struct val_labs *vls, const union value *value)
 {
+  return (vls == NULL ? NULL
+          : val_labs_lookup__ (vls, value, value_hash (value, vls->width, 0)));
+}
+
+/* Searches VLS for a value label whose label is exactly LABEL.  If successful,
+   returns the corresponding value.  Otherwise, returns a null pointer.
+
+   Returns a null pointer if VLS is null.
+
+   This function is O(n) in the number of labels in VLS. */
+const union value *
+val_labs_find_value (const struct val_labs *vls, const char *label_)
+{
+  const union value *value = NULL;
+
   if (vls != NULL)
     {
-      struct val_lab *label;
-      HMAP_FOR_EACH_WITH_HASH (label, struct val_lab, node,
-                               value_hash (value, vls->width, 0), &vls->labels)
-        if (value_equal (&label->value, value, vls->width))
-          return label;
+      const struct val_lab *vl;
+      const char *label;
+
+      label = intern_new (label_);
+      HMAP_FOR_EACH (vl, struct val_lab, node, &vls->labels)
+        if (vl->label == label)
+          {
+            value = &vl->value;
+            break;
+          }
+      intern_unref (label);
     }
-  return NULL;
+
+  return value;
 }
 \f
 /* Returns the first value label in VLS, in arbitrary order, or a
@@ -270,89 +433,45 @@ val_labs_sorted (const struct val_labs *vls)
   else
     return NULL;
 }
-\f
-/* Atoms: reference-counted constant strings. */
-
-/* An atom. */
-struct atom
-  {
-    struct hmap_node node;      /* Hash map node. */
-    char *string;               /* String value. */
-    unsigned ref_count;         /* Number of references. */
-  };
-
-/* Hash table of atoms. */
-static struct hmap atoms = HMAP_INITIALIZER (atoms);
-
-static void free_atom (struct atom *atom);
-static void free_all_atoms (void);
 
-/* Creates and returns an atom for STRING. */
-static struct atom *
-atom_create (const char *string)
+/* Returns a hash value that represents all of the labels in VLS, starting from
+   BASIS. */
+unsigned int
+val_labs_hash (const struct val_labs *vls, unsigned int basis)
 {
-  static bool initialized;
-  struct atom *atom;
-  size_t hash;
-
-  assert (string != NULL);
-
-  if (!initialized)
-    {
-      initialized = true;
-      atexit (free_all_atoms);
-    }
+  const struct val_lab *label;
+  unsigned int hash;
 
-  hash = hash_string (string, 0);
-  HMAP_FOR_EACH_WITH_HASH (atom, struct atom, node, hash, &atoms)
-    if (!strcmp (atom->string, string))
-      {
-        atom->ref_count++;
-        return atom;
-      }
-
-  atom = xmalloc (sizeof *atom);
-  atom->string = xstrdup (string);
-  atom->ref_count = 1;
-  hmap_insert (&atoms, &atom->node, hash);
-  return atom;
+  hash = hash_int (val_labs_count (vls), basis);
+  HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
+    hash ^= value_hash (&label->value, vls->width,
+                        hash_string (label->label, basis));
+  return hash;
 }
 
-/* Destroys ATOM. */
-static void
-atom_destroy (struct atom *atom)
+/* Returns true if A and B contain the same values with the same labels,
+   false if they differ in some way. */
+bool
+val_labs_equal (const struct val_labs *a, const struct val_labs *b)
 {
-  if (atom != NULL)
-    {
-      assert (atom->ref_count > 0);
-      atom->ref_count--;
-      if (atom->ref_count == 0)
-        {
-          hmap_delete (&atoms, &atom->node);
-          free_atom (atom);
-        }
-    }
-}
+  const struct val_lab *label;
 
-/* Returns the string associated with ATOM. */
-static const char *
-atom_to_string (const struct atom *atom)
-{
-  return atom->string;
-}
+  if (val_labs_count (a) != val_labs_count (b))
+    return false;
+  
+  if (a == NULL || b == NULL)
+    return true;
 
-static void
-free_atom (struct atom *atom)
-{
-  free (atom->string);
-  free (atom);
-}
+  if (a->width != b->width)
+    return false;
 
-static void
-free_all_atoms (void)
-{
-  struct atom *atom, *next;
+  HMAP_FOR_EACH (label, struct val_lab, node, &a->labels)
+    {
+      struct val_lab *label2 = val_labs_lookup__ (b, &label->value,
+                                                  label->node.hash);
+      if (!label2 || label->label != label2->label)
+        return false;
+    }
 
-  HMAP_FOR_EACH_SAFE (atom, next, struct atom, node, &atoms)
-    free_atom (atom);
+  return true;
 }