chiark / gitweb /
Version bump.
[mLib] / sym.c
1 /* -*-c-*-
2  *
3  * $Id: sym.c,v 1.8 1999/08/02 14:45:48 mdw Exp $
4  *
5  * Symbol table management
6  *
7  * (c) 1998 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of the mLib utilities library.
13  *
14  * mLib is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU Library General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  * 
19  * mLib is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  * 
24  * You should have received a copy of the GNU Library General Public
25  * License along with mLib; if not, write to the Free
26  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27  * MA 02111-1307, USA.
28  */
29
30 /*----- Revision history --------------------------------------------------*
31  *
32  * $Log: sym.c,v $
33  * Revision 1.8  1999/08/02 14:45:48  mdw
34  * Break low-level hashtable code out from sym.
35  *
36  * Revision 1.7  1999/06/01 09:49:08  mdw
37  * Allow things to be looked up by just their caller-supplied hashes.  This
38  * actually needs to be thought through better.
39  *
40  * Revision 1.6  1999/05/26 21:08:31  mdw
41  * Rename symbols in line with newer conventions.
42  *
43  * Revision 1.5  1999/05/13 22:48:37  mdw
44  * Twiddle the extension threshold.  Change `-ise' to `-ize' throughout.
45  *
46  * Revision 1.4  1999/05/06 19:51:35  mdw
47  * Reformatted the LGPL notice a little bit.
48  *
49  * Revision 1.3  1999/05/05 18:50:31  mdw
50  * Change licensing conditions to LGPL.
51  *
52  * Revision 1.2  1998/11/26 19:27:33  mdw
53  * Move SYM_NAME into the header file.  Fix bugs.
54  *
55  * Revision 1.1.1.1  1998/06/17 23:44:42  mdw
56  * Initial version of mLib
57  *
58  */
59
60 /*----- Header files ------------------------------------------------------*/
61
62 /* --- ANSI headers --- */
63
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67
68 /* --- Local headers --- */
69
70 #include "alloc.h"
71 #include "bits.h"
72 #include "crc32.h"
73 #include "exc.h"
74 #include "hash.h"
75 #include "sub.h"
76 #include "sym.h"
77 #include "track.h"
78
79 /*----- Tuning parameters -------------------------------------------------*/
80
81 /* --- Initial hash table size --- *
82  *
83  * This is the initial @mask@ value.  It must be of the form %$2^n - 1$%,
84  * so that it can be used to mask of the bottom bits of a hash value.
85  */
86
87 #define SYM_INITSZ 64                   /* Size of a new hash table */
88
89 /* --- Maximum load factor --- *
90  *
91  * This parameter controls how much the table has to be loaded before the
92  * table is extended.  The number of elements %$n$%, the number of bins %$b$%
93  * and the limit %$l$% satisfy the relation %$n < bl$%; if a new item is
94  * added to the table and this relation is found to be false, the table is
95  * doubled in size.
96  */
97
98 #define SYM_LIMIT(n) ((n) * 2)          /* Load factor for growing table */
99
100 /*----- Main code ---------------------------------------------------------*/
101
102 /* --- @sym_create@ --- *
103  *
104  * Arguments:   @sym_table *t@ = symbol table to initialize
105  *
106  * Returns:     ---
107  *
108  * Use:         Initializes the given symbol table.  Raises @EXC_NOMEM@ if
109  *              there isn't enough memory.
110  */
111
112 void sym_create(sym_table *t)
113 {
114   TRACK_CTX("symbol table creation");
115   TRACK_PUSH;
116   hash_create(&t->t, SYM_INITSZ);
117   t->load = SYM_LIMIT(SYM_INITSZ);
118   TRACK_POP;
119 }
120
121 /* --- @sym_destroy@ --- *
122  *
123  * Arguments:   @sym_table *t@ = pointer to symbol table in question
124  *
125  * Returns:     ---
126  *
127  * Use:         Destroys a symbol table, freeing all the memory it used to
128  *              occupy.
129  */
130
131 void sym_destroy(sym_table *t)
132 {
133   sym_iter i;
134
135   TRACK_CTX("symbol table destruction");
136   TRACK_PUSH;
137
138   SYM_MKITER(&i, t);
139   for (;;) {
140     sym_base *p;
141     SYM_NEXT(&i, p);
142     if (!p)
143       break;
144     if (p->len > SYM_BUFSZ)
145       sub_free(p->name.p, p->len);
146     free(p);
147   }
148   hash_destroy(&t->t);
149
150   TRACK_POP;
151 }
152
153 /* --- @sym_find@ --- *
154  *
155  * Arguments:   @sym_table *t@ = pointer to symbol table in question
156  *              @const char *n@ = pointer to symbol table to look up
157  *              @long l@ = length of the name string or negative to measure
158  *              @size_t sz@ = size of desired symbol object, or zero
159  *              @unsigned *f@ = pointer to a flag, or null.
160  *
161  * Returns:     The address of a @sym_base@ structure, or null if not found
162  *              and @sz@ is zero.
163  *
164  * Use:         Looks up a symbol in a given symbol table.  The name is
165  *              passed by the address of its first character.  The length
166  *              may be given, in which case the name may contain arbitrary
167  *              binary data, or it may be given as a negative number, in
168  *              which case the length of the name is calculated as
169  *              @strlen(n) + 1@.
170  *
171  *              The return value is the address of a pointer to a @sym_base@
172  *              block (which may have other things on the end, as above).  If
173  *              the symbol could be found, the return value points to the
174  *              symbol block.  If the symbol wasn't there, then if @sz@ is
175  *              nonzero, a new symbol is created and its address is returned;
176  *              otherwise a null pointer is returned.  The exception
177  *              @EXC_NOMEM@ is raised if the block can't be allocated.
178  *
179  *              The value of @*f@ indicates whether a new symbol entry was
180  *              created: a nonzero value indicates that an old value was
181  *              found.
182  */
183
184 void *sym_find(sym_table *t, const char *n, long l, size_t sz, unsigned *f)
185 {
186   uint32 hash;
187   size_t len = 0;
188   hash_base **bin, **p;
189   sym_base *q;
190
191   /* --- Find the correct bin --- */
192
193   len = l < 0 ? strlen(n) + 1 : l;
194   CRC32(hash, 0, n, len);
195   bin = HASH_BIN(&t->t, hash);
196
197   /* --- Search the bin list --- */
198
199   for (p = bin; *p; p = &(*p)->next) {
200     q = (sym_base *)*p;
201     if (hash == q->b.hash && len == q->len && !memcmp(n, SYM_NAME(q), len)) {
202
203       /* --- Found a match --- *
204        *
205        * As a minor, and probably pointless, tweak, move the item to the
206        * front of its bin list.
207        */
208
209       (*p) = q->b.next;
210       q->b.next = *bin;
211       *bin = &q->b;
212
213       /* --- Return the block --- */
214
215       if (f) *f = 1;
216       return (q);
217     }
218   }
219
220   /* --- Couldn't find the item there --- */
221
222   if (f) *f = 0;
223   if (!sz) return (0);
224
225   /* --- Create a new symbol block and initialize it --- */
226
227   {
228     TRACK_CTX("new symbol creation");
229     TRACK_PUSH;
230
231     q = xmalloc(sz);
232     q->b.next = *bin;
233     q->b.hash = hash;
234     q->len = len;
235     if (n) {
236       if (len <= SYM_BUFSZ)
237         memcpy(q->name.b, n, len);
238       else {
239         TRY {
240           q->name.p = sub_alloc(len);
241           memcpy(q->name.p, n, len);
242         } CATCH {
243           free(q);
244           TRACK_POP;
245           RETHROW;
246         } END_TRY;
247       }
248     }
249
250     TRACK_POP;
251   }
252
253   *bin = &q->b;
254
255   /* --- Consider growing the array --- */
256
257   if (t->load)
258     t->load--;
259   if (!t->load && hash_extend(&t->t))
260     t->load = SYM_LIMIT(t->t.mask / 2 + 1);
261
262   /* --- Finished that, so return the new symbol block --- */
263
264   return (q);
265 }
266
267 /* --- @sym_remove@ --- *
268  *
269  * Arguments:   @sym_table *i@ = pointer to a symbol table object
270  *              @void *p@ = pointer to symbol table entry
271  *
272  * Returns:     ---
273  *
274  * Use:         Removes the object from the symbol table.  The space occupied
275  *              by the object and its name is freed; anything else attached
276  *              to the entry should already be gone by this point.
277  */
278
279 void sym_remove(sym_table *t, void *p)
280 {
281   sym_base *q = p;
282   hash_remove(&t->t, &q->b);
283   if (q->len > SYM_BUFSZ)
284     sub_free(q->name.p, q->len);
285   free(q);
286   t->load++;
287 }
288
289 /* --- @sym_mkiter@ --- *
290  *
291  * Arguments:   @sym_iter *i@ = pointer to an iterator object
292  *              @sym_table *t@ = pointer to a symbol table object
293  *
294  * Returns:     ---
295  *
296  * Use:         Creates a new symbol table iterator which may be used to
297  *              iterate through a symbol table.
298  */
299
300 void sym_mkiter(sym_iter *i, sym_table *t) { SYM_MKITER(i, t); }
301
302 /* --- @sym_next@ --- *
303  *
304  * Arguments:   @sym_iter *i@ = pointer to iterator object
305  *
306  * Returns:     Pointer to the next symbol found, or null when finished.
307  *
308  * Use:         Returns the next symbol from the table.  Symbols are not
309  *              returned in any particular order.
310  */
311
312 void *sym_next(sym_iter *i)
313 {
314   void *p;
315   SYM_NEXT(i, p);
316   return (p);
317 }
318
319 /*----- Symbol table test code --------------------------------------------*/
320
321 #ifdef TEST_RIG
322
323 #include <errno.h>
324 #include <time.h>
325
326 typedef struct sym_word {
327   sym_base base;
328   size_t i;
329 } sym_word;
330
331
332 /* --- What it does --- *
333  *
334  * Reads the file /usr/dict/words (change to some other file full of
335  * interesting and appropriate bits of text to taste) into a big buffer and
336  * picks apart into lines.  Then picks lines at random and enters them into
337  * the symbol table.
338  */
339
340 int main(void)
341 {
342   char *buff, *p, *lim;
343   size_t sz, done;
344   FILE *fp;
345   int i;
346   char **line;
347   sym_word **flag;
348   sym_table tbl;
349   int entries;
350
351   /* --- Initialize for reading the file --- */
352
353   sz = BUFSIZ;
354   buff = xmalloc(sz + 1);
355   done = 0;
356   sub_init();
357
358   if ((fp = fopen("/usr/dict/words", "r")) == 0)
359     fprintf(stderr, "buggered ;-( (%s)\n", strerror(errno));
360
361   /* --- Read buffers of text --- *
362    *
363    * Read a buffer.  If more to come, double the buffer size and try again.
364    * This is the method I recommended to comp.lang.c, so I may as well try
365    * it.
366    */
367
368   for (;;) {
369     i = fread(buff + done, 1, sz - done, fp);
370     done += i;
371     if (done != sz)
372       break;
373     sz <<= 1;
374     buff = xrealloc(buff, sz + 1);
375   }
376
377   /* --- Count the lines --- */
378
379   lim = buff + done;
380
381   sz = 1;
382   for (p = buff; p < lim; p++)
383     if (*p == '\n') sz++;
384
385   /* --- Build a table of line starts --- */
386
387   line = xmalloc(sz * sizeof(char *));
388   i = 0;
389   line[i++] = buff;
390   for (p = buff; p < lim; p++)
391     if (*p == '\n') *p = 0, line[i++] = p + 1;
392   *lim = 0;
393
394   /* --- Build a table of lines --- *
395    *
396    * This reverses the mapping which the symbol table performs, so that its
397    * accuracy can be tested.
398    */
399
400   flag = xmalloc(sz * sizeof(sym_word *));
401   for (i = 0; i < sz; i++)
402     flag[i] = 0;
403   entries = 0;
404
405   sym_create(&tbl);
406
407   for (;;) {
408     i = (unsigned)rand() % sz;
409
410     switch (rand() % 5)
411     {
412       case 0: {
413         sym_word *w;
414
415         printf("? %s\n", line[i]);
416
417         w = sym_find(&tbl, line[i], -1, 0, 0);
418         if (w != flag[i])
419           printf("*** error: find `%s' gave %p not %p\n",
420                  line[i], (void *)w, (void *)flag[i]);
421         else if (w && w->i != i)
422           printf("*** error: find sym for `%s' gives index %i not %i\n",
423                  line[i], w->i, i);     
424       } break;
425
426       case 1: {
427         unsigned f;
428         sym_word *w;
429
430         printf("+ %s\n", line[i]);
431
432         w = sym_find(&tbl, line[i], -1, sizeof(sym_word), &f);
433         if (f)
434         {
435           if (w != flag[i])
436             printf("*** error: create `%s' gave %p not %p\n",
437                    line[i], (void *)w, (void *)flag[i]);
438           else if (w && w->i != i)
439             printf("*** error: create sym for `%s' gives index %i not %i\n",
440                    line[i], w->i, i);
441         }
442         else
443         {
444           if (flag[i])
445             printf("*** error: create `%s' gave new block, should be %p\n",
446                    line[i], (void *)flag[i]);
447           else {
448             flag[i] = w;
449             w->i = i;
450             entries++;
451           }
452         }
453       } break;
454
455       case 2: {
456         sym_iter it;
457         sym_word *w, **ntbl;
458         int v;
459
460         if (!entries)
461           break;
462         v = (rand() % entries) == 0;
463         if (!v)
464           break;
465
466         printf(".\n");
467
468         ntbl = xmalloc(sz * sizeof(sym_word *));
469         memcpy(ntbl, flag, sz * sizeof(sym_word *));
470         sym_mkiter(&it, &tbl);
471
472         while ((w = sym_next(&it)) != 0) {
473           if (ntbl[w->i] == 0)
474             printf("*** error: iterate returned duff item %s\n", SYM_NAME(w));
475           else {
476             printf(": %s\n", SYM_NAME(w));
477             ntbl[w->i] = 0;
478           }
479         }
480
481         for (i = 0; i < sz; i++)
482           if (ntbl[i]) printf("*** error: iterate didn't return item %s\n",
483                               SYM_NAME(ntbl[i]));
484         free(ntbl);
485       } break;
486
487       case 3: {
488         sym_base *b;
489         int v = rand() & 255 ? 0 : 1;
490         break;
491
492         printf("dump\n");
493
494         for (i = 0; i <= tbl.b.mask; i++) {
495           if (!tbl.b.v[i]) continue;
496           if (v) printf("  %i: ", i);
497           b = (sym_base *)tbl.b.v[i];
498           while (b) {
499             if ((b->b.hash & tbl.b.mask) != i)
500               printf("*** error: bad hash value found");
501             if (v) printf("`%s'(%08lx:%lu) ",
502                           line[((sym_word *)b)->i],
503                           b->b.hash,
504                           b->b.hash & tbl.b.mask);
505             b = (sym_base *)b->b.next;
506           }
507           if (v) putchar('\n');
508         }
509       } break;
510
511       case 4: {
512         if (flag[i]) {
513           printf("- %s\n", SYM_NAME(&flag[i]->base));
514           if ((rand() & 1023) == 0) {
515             putchar('-'); fflush(stdout);
516           }
517           sym_remove(&tbl, flag[i]);
518           flag[i] = 0;
519           entries--;
520         }
521       } break;
522     }
523
524   }
525
526   return (0);
527 }
528
529 #endif
530
531 /*----- That's all, folks -------------------------------------------------*/