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