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