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