chiark / gitweb /
Fix nasty bugs in `dstr_vputf'.
[mLib] / sym.c
1 /* -*-c-*-
2  *
3  * $Id: sym.c,v 1.6 1999/05/26 21:08:31 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.6  1999/05/26 21:08:31  mdw
34  * Rename symbols in line with newer conventions.
35  *
36  * Revision 1.5  1999/05/13 22:48:37  mdw
37  * Twiddle the extension threshold.  Change `-ise' to `-ize' throughout.
38  *
39  * Revision 1.4  1999/05/06 19:51:35  mdw
40  * Reformatted the LGPL notice a little bit.
41  *
42  * Revision 1.3  1999/05/05 18:50:31  mdw
43  * Change licensing conditions to LGPL.
44  *
45  * Revision 1.2  1998/11/26 19:27:33  mdw
46  * Move SYM_NAME into the header file.  Fix bugs.
47  *
48  * Revision 1.1.1.1  1998/06/17 23:44:42  mdw
49  * Initial version of mLib
50  *
51  */
52
53 /*----- Header files ------------------------------------------------------*/
54
55 /* --- ANSI headers --- */
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60
61 /* --- Local headers --- */
62
63 #include "alloc.h"
64 #include "crc32.h"
65 #include "exc.h"
66 #include "sub.h"
67 #include "sym.h"
68 #include "track.h"
69
70 /*----- Tuning parameters -------------------------------------------------*/
71
72 /* --- Initial hash table size --- *
73  *
74  * This is the initial @mask@ value.  It must be of the form %$2^n - 1$%,
75  * so that it can be used to mask of the bottom bits of a hash value.
76  */
77
78 #define SYM_INITSZ 255                  /* Size of a new hash table */
79
80 /* --- Maximum load factor --- *
81  *
82  * This parameter controls how much the table has to be loaded before the
83  * table is extended.  The number of elements %$n$%, the number of bins %$b$%
84  * and the limit %$l$% satisfy the relation %$n < bl$%; if a new item is
85  * added to the table and this relation is found to be false, the table is
86  * doubled in size.
87  */
88
89 #define SYM_LIMIT(n) ((n) * 4)          /* Load factor for growing table */
90
91 /*----- Main code ---------------------------------------------------------*/
92
93 /* --- @sym_create@ --- *
94  *
95  * Arguments:   @sym_table *t@ = symbol table to initialize
96  *
97  * Returns:     ---
98  *
99  * Use:         Initializes the given symbol table.  Raises @EXC_NOMEM@ if
100  *              there isn't enough memory.
101  */
102
103 void sym_create(sym_table *t)
104 {
105   size_t i;
106
107   TRACK_CTX("symbol table creation");
108   TRACK_PUSH;
109
110   t->mask = SYM_INITSZ;
111   t->c = SYM_LIMIT(SYM_INITSZ);
112   t->a = xmalloc((t->mask + 1) * sizeof(sym_base *));
113
114   for (i = 0; i < SYM_INITSZ + 1; i++)
115     t->a[i] = 0;
116
117   TRACK_POP;
118 }
119
120 /* --- @sym_destroy@ --- *
121  *
122  * Arguments:   @sym_table *t@ = pointer to symbol table in question
123  *
124  * Returns:     ---
125  *
126  * Use:         Destroys a symbol table, freeing all the memory it used to
127  *              occupy.
128  */
129
130 void sym_destroy(sym_table *t)
131 {
132   size_t i;
133   sym_base *p, *q;
134
135   TRACK_CTX("symbol table deletion");
136   TRACK_PUSH;
137
138   for (i = 0; i <= t->mask; i++) {
139     p = t->a[i];
140     while (p) {
141       q = p->next;
142       if (p->len > SYM_BUFSZ)
143         sub_free(p->name.p, p->len);
144       free(p);
145       p = q;
146     }
147   }
148   free(t->a);
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   unsigned long hash;                   /* Hash value for user's name */
187   size_t len = l < 0 ? strlen(n) + 1 : l; /* Find length of user's name */
188   sym_base *bin;                        /* Bin containing our item */
189   sym_base *p, *q;                      /* Pointer wandering through list */
190
191   /* --- Find the correct bin --- */
192
193   CRC32(hash, 0, n, len);               /* Find hash value for this name */
194   bin = p = (sym_base *)(t->a + (hash & t->mask));
195
196   /* --- Search the bin list --- */
197
198   while (p->next) {
199     if (hash == p->next->hash &&        /* Check full hash values first */
200         len == p->next->len &&          /* Then compare string lengths */
201         !memcmp(n, SYM_NAME(p->next), len)) /* And finally compare names */
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       q = p->next;                      /* Find the actual symbol block */
210       p->next = q->next;                /* Extract block from bin list */
211       q->next = bin->next;              /* Set up symbol's next pointer */
212       bin->next = q;                    /* And reinsert the block */
213
214       /* --- Return the block --- */
215
216       if (f) *f = 1;                    /* Didn't fail to find the item */
217       return (q);                       /* And return the block */
218     }
219
220     p = p->next;                        /* Move onto the next item */
221   }
222
223   /* --- Couldn't find the item there --- */
224
225   if (f) *f = 0;                        /* Failed to find the block */
226   if (!sz) return (0);                  /* Return zero if not creating */
227
228   /* --- Create a new symbol block and initialize it --- */
229
230   {
231     TRACK_CTX("new symbol creation");
232     TRACK_PUSH;
233
234     p = xmalloc(sz);                    /* Create a new symbol block */
235     p->next = bin->next;                /* Set up the next pointer */
236     p->hash = hash;                     /* Set up the hash value too */
237     p->len = len;                       /* And set up the string length */
238     if (len <= SYM_BUFSZ)
239       memcpy(p->name.b, n, len);        /* And copy the string over */
240     else {
241       TRY {
242         p->name.p = sub_alloc(len);     /* Allocate a block for the name */
243         memcpy(p->name.p, n, len);      /* And copy the string over */
244       } CATCH {
245         free(p);
246         TRACK_POP;
247         RETHROW;
248       } END_TRY;
249     }
250
251     TRACK_POP;
252   }
253
254   bin->next = p;                        /* Put the pointer into the bin */
255
256   /* --- Consider growing the array --- */
257
258   if (t->c)
259     t->c--;
260   if (!t->c) {
261     unsigned long m = t->mask + 1;      /* Next set bit in has word */
262     sym_base *p, *q, *r;                /* More useful pointers */
263     size_t i, lim;                      /* Loop counter and limit */
264
265     TRACK_CTX("symbol table extension");
266     TRACK_PUSH;
267
268     /* --- Update values in the anchor block --- */
269
270     TRY {
271       t->a = xrealloc(t->a, (t->mask + 1) * 2 * sizeof(sym_base *));
272     } CATCH switch (exc_type) {
273       case EXC_NOMEM:
274         TRACK_POP;
275         return (bin->next);
276       default:
277         TRACK_POP;
278         RETHROW;
279     } END_TRY;
280
281     t->c = SYM_LIMIT(t->mask + 1);      /* Set load value */
282     t->mask = (t->mask + 1) * 2 - 1;    /* Set the new mask value */
283
284     /* --- Now wander through the table rehashing things --- *
285      *
286      * This loop is very careful to avoid problems with aliasing.  The items
287      * are dealt with from the end backwards to avoid overwriting bins before
288      * they've been processed.
289      */
290
291     lim = (t->mask + 1) >> 1;
292     for (i = 0; i < lim; i++) {
293
294       /* --- Some initialisation --- */
295
296       r = t->a[i];                      /* Find the list we're dissecting */
297       p = (sym_base *)(t->a + i);       /* Find bit-clear list */
298       q = (sym_base *)(t->a + i + lim); /* And the bit-set lsit */
299
300       /* --- Now go through the @r@ list --- */
301
302       while (r) {
303         if (r->hash & m)                /* Is the next bit set? */
304           q = q->next = r;              /* Yes, so fit up previous link */
305         else
306           p = p->next = r;              /* No, so fit up previous link */
307         r = r->next;                    /* Move onto the next item */
308       }
309       p->next = q->next = 0;            /* Null terminate the new lists */
310     }
311
312     TRACK_POP;
313   }
314
315   /* --- Finished that, so return the new symbol block --- */
316
317   return (p);
318 }
319
320 /* --- @sym_remove@ --- *
321  *
322  * Arguments:   @sym_table *i@ = pointer to a symbol table object
323  *              @void *b@ = pointer to symbol table entry
324  *
325  * Returns:     ---
326  *
327  * Use:         Removes the object from the symbol table.  The space occupied
328  *              by the object and its name is freed; anything else attached
329  *              to the entry should already be gone by this point.
330  */
331
332 void sym_remove(sym_table *t, void *b)
333 {
334   /* --- A quick comment --- *
335    *
336    * Since the @sym_base@ block contains the hash, finding the element in the
337    * bin list is really quick -- it's not worth bothering with things like
338    * doubly linked lists.
339    */
340
341   sym_base *p = b;
342   sym_base *bin = (sym_base *)(t->a + (p->hash & t->mask));
343
344   /* --- Find the item in the bin list --- */
345
346   while (bin->next) {
347     if (bin->next == p)
348       break;
349     bin = bin->next;
350   }
351   if (!bin->next)
352     return;
353
354   /* --- Now just remove the item from the list and free it --- *
355    *
356    * Oh, and bump the load counter.
357    */
358
359   bin->next = p->next;
360   if (p->len > SYM_BUFSZ)
361     sub_free(p->name.p, p->len);
362   free(p);
363   t->c++;
364 }
365
366 /* --- @sym_mkiter@ --- *
367  *
368  * Arguments:   @sym_iter *i@ = pointer to an iterator object
369  *              @sym_table *t@ = pointer to a symbol table object
370  *
371  * Returns:     ---
372  *
373  * Use:         Creates a new symbol table iterator which may be used to
374  *              iterate through a symbol table.
375  */
376
377 void sym_mkiter(sym_iter *i, sym_table *t)
378 {
379   i->t = t;
380   i->i = 0;
381   i->n = 0;
382 }
383
384 /* --- @sym_next@ --- *
385  *
386  * Arguments:   @sym_iter *i@ = pointer to iterator object
387  *
388  * Returns:     Pointer to the next symbol found, or null when finished.
389  *
390  * Use:         Returns the next symbol from the table.  Symbols are not
391  *              returned in any particular order.
392  */
393
394 void *sym_next(sym_iter *i)
395 {
396   sym_base *p;
397
398   /* --- Find the next item --- */
399
400   while (!i->n) {
401     if (i->i > i->t->mask)
402       return (0);
403     i->n = i->t->a[i->i++];
404   }
405
406   /* --- Update the iterator block --- */
407
408   p = i->n;
409   i->n = p->next;
410
411   /* --- Done --- */
412
413   return (p);
414 }
415
416 /*----- Symbol table test code --------------------------------------------*/
417
418 #ifdef TEST_RIG
419
420 #include <errno.h>
421 #include <time.h>
422
423 typedef struct sym_word {
424   sym_base base;
425   size_t i;
426 } sym_word;
427
428
429 /* --- What it does --- *
430  *
431  * Reads the file /usr/dict/words (change to some other file full of
432  * interesting and appropriate bits of text to taste) into a big buffer and
433  * picks apart into lines.  Then picks lines at random and enters them into
434  * the symbol table.
435  */
436
437 int main(void)
438 {
439   char *buff, *p, *lim;
440   size_t sz, done;
441   FILE *fp;
442   int i;
443   char **line;
444   sym_word **flag;
445   sym_table tbl;
446   int entries;
447
448   /* --- Initialize for reading the file --- */
449
450   sz = BUFSIZ;
451   buff = xmalloc(sz + 1);
452   done = 0;
453   sub_init();
454
455   if ((fp = fopen("/usr/dict/words", "r")) == 0)
456     fprintf(stderr, "buggered ;-( (%s)\n", strerror(errno));
457
458   /* --- Read buffers of text --- *
459    *
460    * Read a buffer.  If more to come, double the buffer size and try again.
461    * This is the method I recommended to comp.lang.c, so I may as well try
462    * it.
463    */
464
465   for (;;) {
466     i = fread(buff + done, 1, sz - done, fp);
467     done += i;
468     if (done != sz)
469       break;
470     sz <<= 1;
471     buff = xrealloc(buff, sz + 1);
472   }
473
474   /* --- Count the lines --- */
475
476   lim = buff + done;
477
478   sz = 1;
479   for (p = buff; p < lim; p++)
480     if (*p == '\n') sz++;
481
482   /* --- Build a table of line starts --- */
483
484   line = xmalloc(sz * sizeof(char *));
485   i = 0;
486   line[i++] = buff;
487   for (p = buff; p < lim; p++)
488     if (*p == '\n') *p = 0, line[i++] = p + 1;
489   *lim = 0;
490
491   /* --- Build a table of lines --- *
492    *
493    * This reverses the mapping which the symbol table performs, so that its
494    * accuracy can be tested.
495    */
496
497   flag = xmalloc(sz * sizeof(sym_word *));
498   for (i = 0; i < sz; i++)
499     flag[i] = 0;
500   entries = 0;
501
502   sym_create(&tbl);
503
504   for (;;) {
505     i = (unsigned)rand() % sz;
506
507     switch (rand() % 5)
508     {
509       case 0: {
510         sym_word *w;
511
512         printf("find `%s'\n", line[i]);
513         if ((rand() & 1023) == 0) {
514           putchar('.'); fflush(stdout);
515         }
516
517         w = sym_find(&tbl, line[i], -1, 0, 0);
518         if (w != flag[i])
519           printf("*** error: find `%s' gave %p not %p\n",
520                  line[i], (void *)w, (void *)flag[i]);
521         else if (w && w->i != i)
522           printf("*** error: find sym for `%s' gives index %i not %i\n",
523                  line[i], w->i, i);     
524       } break;
525
526       case 1: {
527         unsigned f;
528         sym_word *w;
529
530         printf("create `%s'\n", line[i]);
531         if ((rand() & 1023) == 0) {
532           putchar('+'); fflush(stdout);
533         }
534
535         w = sym_find(&tbl, line[i], -1, sizeof(sym_word), &f);
536         if (f)
537         {
538           if (w != flag[i])
539             printf("*** error: create `%s' gave %p not %p\n",
540                    line[i], (void *)w, (void *)flag[i]);
541           else if (w && w->i != i)
542             printf("*** error: create sym for `%s' gives index %i not %i\n",
543                    line[i], w->i, i);
544         }
545         else
546         {
547           if (flag[i])
548             printf("*** error: create `%s' gave new block, should be %p\n",
549                    line[i], (void *)flag[i]);
550           else {
551             flag[i] = w;
552             w->i = i;
553             entries++;
554           }
555         }
556       } break;
557
558       case 2: {
559         sym_iter it;
560         sym_word *w, **ntbl;
561         int v;
562
563         if (!entries)
564           break;
565         v = (rand() % entries) == 0;
566         if (!v)
567           break;
568         printf("\niterated %i entries\n", entries);
569         break;
570
571         printf("iterate\n");
572
573         ntbl = xmalloc(sz * sizeof(sym_word *));
574         memcpy(ntbl, flag, sz * sizeof(sym_word *));
575         sym_mkiter(&it, &tbl);
576
577         while ((w = sym_next(&it)) != 0) {
578           if (ntbl[w->i] == 0)
579             printf("*** error: iterate returned duff item %i\n", w->i);
580           else
581             ntbl[w->i] = 0;
582         }
583
584         for (i = 0; i < sz; i++)
585           if (ntbl[i]) printf("*** error: iterate didn't return item %i\n",
586                               i);
587         free(ntbl);
588       } break;
589
590       case 3: {
591         sym_base *b;
592         int v = rand() & 255 ? 0 : 1;
593         break;
594
595         printf("dump\n");
596
597         for (i = 0; i <= tbl.mask; i++) {
598           if (!tbl.a[i]) continue;
599           if (v) printf("  %i: ", i);
600           b = tbl.a[i];
601           while (b) {
602             if ((b->hash & tbl.mask) != i)
603               printf("*** error: bad hash value found");
604             if (v) printf("`%s'(%08lx:%lu) ",
605                           line[((sym_word *)b)->i],
606                           b->hash,
607                           b->hash & tbl.mask);
608             b = b->next;
609           }
610           if (v) putchar('\n');
611         }
612       } break;
613
614       case 4: {
615         if (flag[i]) {
616           printf("remove `%s'\n", SYM_NAME(&flag[i]->base));
617           if ((rand() & 1023) == 0) {
618             putchar('-'); fflush(stdout);
619           }
620           sym_remove(&tbl, flag[i]);
621           flag[i] = 0;
622           entries--;
623         }
624       } break;
625     }
626
627   }
628
629   return (0);
630 }
631
632 #endif
633
634 /*----- That's all, folks -------------------------------------------------*/