message: Intern file names in msg_location to make them cheaper to copy.
[pspp] / src / libpspp / intern.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010, 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 "libpspp/intern.h"
20
21 #include <assert.h>
22 #include <string.h>
23
24 #include "libpspp/assertion.h"
25 #include "libpspp/cast.h"
26 #include "libpspp/hash-functions.h"
27 #include "libpspp/hmap.h"
28
29 #include "gl/xalloc.h"
30
31 /* A single interned string. */
32 struct interned_string
33   {
34     struct hmap_node node;      /* Node in hash table. */
35     size_t ref_cnt;             /* Reference count. */
36     size_t length;              /* strlen(string).  */
37     char string[1];             /* Null-terminated string. */
38   };
39
40 /* All interned strings. */
41 static struct hmap interns = HMAP_INITIALIZER (interns);
42
43 /* Searches the table of interned strings for one equal to S, which has length
44    LENGTH and hash value HASH. */
45 static struct interned_string *
46 intern_lookup__ (const char *s, size_t length, unsigned int hash)
47 {
48   struct interned_string *is;
49
50   HMAP_FOR_EACH_WITH_HASH (is, struct interned_string, node, hash, &interns)
51     if (is->length == length && !memcmp (s, is->string, length))
52       return is;
53
54   return NULL;
55 }
56
57 /* Returns an interned version of string S.  Pass the returned string to
58    intern_unref() to release it. */
59 const char *
60 intern_new (const char *s)
61 {
62   size_t length = strlen (s);
63   unsigned int hash = hash_bytes (s, length, 0);
64   struct interned_string *is;
65
66   is = intern_lookup__ (s, length, hash);
67   if (is != NULL)
68     is->ref_cnt++;
69   else
70     {
71       is = xmalloc (length + sizeof *is);
72       hmap_insert (&interns, &is->node, hash);
73       is->ref_cnt = 1;
74       is->length = length;
75       memcpy (is->string, s, length + 1);
76     }
77   return is->string;
78 }
79
80 const char *
81 intern_new_if_nonnull (const char *s)
82 {
83   return s ? intern_new (s) : NULL;
84 }
85
86
87 static struct interned_string *
88 interned_string_from_string (const char *s_)
89 {
90   char (*s)[1] = (char (*)[1]) s_;
91   struct interned_string *is = UP_CAST (s, struct interned_string, string);
92   assert (is->ref_cnt > 0);
93   return is;
94 }
95
96 /* Increases the reference count on S, which must be an interned string
97    returned by intern_new(). */
98 const char *
99 intern_ref (const char *s)
100 {
101   struct interned_string *is = interned_string_from_string (s);
102   is->ref_cnt++;
103   return s;
104 }
105
106 const char *
107 intern_ref_if_nonnull (const char *s)
108 {
109   return s ? intern_ref (s) : NULL;
110 }
111
112 /* Decreases the reference count on S, which must be an interned string
113    returned by intern_new().  If the reference count reaches 0, frees the
114    interned string. */
115 void
116 intern_unref (const char *s)
117 {
118   if (s)
119     {
120       struct interned_string *is = interned_string_from_string (s);
121       if (--is->ref_cnt == 0)
122         {
123           hmap_delete (&interns, &is->node);
124           free (is);
125         }
126     }
127 }
128
129 /* Given null-terminated string S, returns true if S is an interned string
130    returned by intern_string_new(), false otherwise.
131
132    This is appropriate for use in debug assertions, e.g.:
133        assert (is_interned_string (s));
134 */
135 bool
136 is_interned_string (const char *s)
137 {
138   size_t length = strlen (s);
139   unsigned int hash = hash_bytes (s, length, 0);
140   return intern_lookup__ (s, length, hash) != NULL;
141 }
142
143 /* Returns the length of S, which must be an interned string returned by
144    intern_new(). */
145 size_t
146 intern_strlen (const char *s)
147 {
148   return interned_string_from_string (s)->length;
149 }