message: Intern file names in msg_location to make them cheaper to copy.
[pspp] / src / libpspp / intern.c
index 0bb9277dc9172ae3aad1b801df4fba36cc606dac..b8eb5198b4d9ac902ef2ce67c2fd1c994b5f0de2 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2010 Free Software Foundation, Inc.
+   Copyright (C) 2010, 2011 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
@@ -22,6 +22,7 @@
 #include <string.h>
 
 #include "libpspp/assertion.h"
+#include "libpspp/cast.h"
 #include "libpspp/hash-functions.h"
 #include "libpspp/hmap.h"
 
@@ -32,20 +33,22 @@ struct interned_string
   {
     struct hmap_node node;      /* Node in hash table. */
     size_t ref_cnt;             /* Reference count. */
+    size_t length;              /* strlen(string).  */
     char string[1];             /* Null-terminated string. */
   };
 
 /* All interned strings. */
 static struct hmap interns = HMAP_INITIALIZER (interns);
 
-/* Searches the table of interned string for  */
+/* Searches the table of interned strings for one equal to S, which has length
+   LENGTH and hash value HASH. */
 static struct interned_string *
 intern_lookup__ (const char *s, size_t length, unsigned int hash)
 {
   struct interned_string *is;
 
   HMAP_FOR_EACH_WITH_HASH (is, struct interned_string, node, hash, &interns)
-    if (!memcmp (s, is->string, length + 1))
+    if (is->length == length && !memcmp (s, is->string, length))
       return is;
 
   return NULL;
@@ -68,16 +71,24 @@ intern_new (const char *s)
       is = xmalloc (length + sizeof *is);
       hmap_insert (&interns, &is->node, hash);
       is->ref_cnt = 1;
+      is->length = length;
       memcpy (is->string, s, length + 1);
     }
   return is->string;
 }
 
+const char *
+intern_new_if_nonnull (const char *s)
+{
+  return s ? intern_new (s) : NULL;
+}
+
+
 static struct interned_string *
-interned_string_from_string (const char *s)
+interned_string_from_string (const char *s_)
 {
-  const size_t ofs = offsetof (struct interned_string, string);
-  struct interned_string *is = (struct interned_string *) (s - ofs);
+  char (*s)[1] = (char (*)[1]) s_;
+  struct interned_string *is = UP_CAST (s, struct interned_string, string);
   assert (is->ref_cnt > 0);
   return is;
 }
@@ -92,17 +103,26 @@ intern_ref (const char *s)
   return s;
 }
 
+const char *
+intern_ref_if_nonnull (const char *s)
+{
+  return s ? intern_ref (s) : NULL;
+}
+
 /* Decreases the reference count on S, which must be an interned string
    returned by intern_new().  If the reference count reaches 0, frees the
    interned string. */
 void
 intern_unref (const char *s)
 {
-  struct interned_string *is = interned_string_from_string (s);
-  if (--is->ref_cnt == 0)
+  if (s)
     {
-      hmap_delete (&interns, &is->node);
-      free (is);
+      struct interned_string *is = interned_string_from_string (s);
+      if (--is->ref_cnt == 0)
+        {
+          hmap_delete (&interns, &is->node);
+          free (is);
+        }
     }
 }
 
@@ -119,3 +139,11 @@ is_interned_string (const char *s)
   unsigned int hash = hash_bytes (s, length, 0);
   return intern_lookup__ (s, length, hash) != NULL;
 }
+
+/* Returns the length of S, which must be an interned string returned by
+   intern_new(). */
+size_t
+intern_strlen (const char *s)
+{
+  return interned_string_from_string (s)->length;
+}