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