1 /******************************************************************************
2 Copyright (c) 1995, 1996 Xerox Corporation. All rights reserved.
3 Portions of this code were written by Stephen White, aka ghond.
4 Use and copying of this software and preparation of derivative works based
5 upon this software are permitted. Any distribution of this software or
6 derivative works must comply with all applicable United States export
7 control laws. This software is made available AS IS, and Xerox Corporation
8 makes no warranty about the software, its performance or its conformity to
9 any specification. Any person obtaining a copy of this software is requested
10 to send their name and post office or electronic mail address to:
11 Pavel Curtis
12 Xerox PARC
13 3333 Coyote Hill Rd.
14 Palo Alto, CA 94304
15 Pavel@Xerox.Com
16 *****************************************************************************/
17
18 /*****************************************************************************
19 * Routines for manipulating verbs on DB objects
20 *****************************************************************************/
21
22 #include <ctype.h>
23 #include "my-stdlib.h"
24 #include "my-string.h"
25
26 #include "config.h"
27 #include "db.h"
28 #include "db_private.h"
29 #include "db_tune.h"
30 #include "list.h"
31 #include "log.h"
32 #include "parse_cmd.h"
33 #include "program.h"
34 #include "storage.h"
35 #include "utils.h"
36
37
38 /*********** Prepositions ***********/
39
40 #define MAXPPHRASE 3 /* max. number of words in a prepositional phrase */
41
42 /* NOTE: New prepositional phrases should only be added to this list at the
43 * end, never in the middle, and no entries should every be removed from this
44 * list; the list indices are stored raw in the DB file.
45 */
46 static const char *prep_list[] =
47 {
48 "with/using",
49 "at/to",
50 "in front of",
51 "in/inside/into",
52 "on top of/on/onto/upon",
53 "out of/from inside/from",
54 "over",
55 "through",
56 "under/underneath/beneath",
57 "behind",
58 "beside",
59 "for/about",
60 "is",
61 "as",
62 "off/off of",
63 };
64
65 #define NPREPS Arraysize(prep_list)
66
67 typedef struct pt_entry {
68 int nwords;
69 char *words[MAXPPHRASE];
70 struct pt_entry *next;
71 } pt_entry;
72
73 struct pt_entry *prep_table[NPREPS];
74
75 void
dbpriv_build_prep_table(void)76 dbpriv_build_prep_table(void)
77 {
78 int i, j;
79 int nwords;
80 char **words;
81 char cprep[100];
82 const char *p;
83 char *t;
84 pt_entry *current_alias, **prev;
85
86 for (i = 0; i < NPREPS; i++) {
87 p = prep_list[i];
88 prev = &prep_table[i];
89 while (*p) {
90 t = cprep;
91 if (*p == '/')
92 p++;
93 while (*p && *p != '/')
94 *t++ = *p++;
95 *t = '\0';
96
97 /* This call to PARSE_INTO_WORDS() is the reason that this function
98 * is called from DB_INITIALIZE() instead of from the first call to
99 * DB_FIND_PREP(), below. You see, PARSE_INTO_WORDS() isn't
100 * re-entrant, and DB_FIND_PREP() is called from code that's still
101 * using the results of a previous call to PARSE_INTO_WORDS()...
102 */
103 words = parse_into_words(cprep, &nwords);
104
105 current_alias = mymalloc(sizeof(struct pt_entry), M_PREP);
106 current_alias->nwords = nwords;
107 current_alias->next = 0;
108 for (j = 0; j < nwords; j++)
109 current_alias->words[j] = str_dup(words[j]);
110 *prev = current_alias;
111 prev = ¤t_alias->next;
112 }
113 }
114 }
115
116 db_prep_spec
db_find_prep(int argc,char * argv[],int * first,int * last)117 db_find_prep(int argc, char *argv[], int *first, int *last)
118 {
119 pt_entry *alias;
120 int i, j, k;
121 int exact_match = (first == 0 || last == 0);
122
123 for (i = 0; i < argc; i++) {
124 for (j = 0; j < NPREPS; j++) {
125 for (alias = prep_table[j]; alias; alias = alias->next) {
126 if (i + alias->nwords <= argc) {
127 for (k = 0; k < alias->nwords; k++) {
128 if (mystrcasecmp(argv[i + k], alias->words[k]))
129 break;
130 }
131 if (k == alias->nwords
132 && (!exact_match || i + k == argc)) {
133 if (!exact_match) {
134 *first = i;
135 *last = i + alias->nwords - 1;
136 }
137 return (db_prep_spec) j;
138 }
139 }
140 }
141 }
142 if (exact_match)
143 break;
144 }
145 return PREP_NONE;
146 }
147
148 db_prep_spec
db_match_prep(const char * prepname)149 db_match_prep(const char *prepname)
150 {
151 db_prep_spec prep;
152 int argc;
153 char *ptr;
154 char **argv;
155 char *s, first;
156
157 s = str_dup(prepname);
158 first = s[0];
159 if (first == '#')
160 first = (++s)[0];
161 prep = strtol(s, &ptr, 10);
162 if (*ptr == '\0') {
163 free_str(s);
164 if (!isdigit(first) || prep >= NPREPS)
165 return PREP_NONE;
166 else
167 return prep;
168 }
169 if ((ptr = strchr(s, '/')) != '\0')
170 *ptr = '\0';
171
172 argv = parse_into_words(s, &argc);
173 prep = db_find_prep(argc, argv, 0, 0);
174 free_str(s);
175 return prep;
176 }
177
178 const char *
db_unparse_prep(db_prep_spec prep)179 db_unparse_prep(db_prep_spec prep)
180 {
181 if (prep == PREP_NONE)
182 return "none";
183 else if (prep == PREP_ANY)
184 return "any";
185 else
186 return prep_list[prep];
187 }
188
189
190 /*********** Verbs ***********/
191
192 #define DOBJSHIFT 4
193 #define IOBJSHIFT 6
194 #define OBJMASK 0x3
195 #define PERMMASK 0xF
196
197 void
db_add_verb(Objid oid,const char * vnames,Objid owner,unsigned flags,db_arg_spec dobj,db_prep_spec prep,db_arg_spec iobj)198 db_add_verb(Objid oid, const char *vnames, Objid owner, unsigned flags,
199 db_arg_spec dobj, db_prep_spec prep, db_arg_spec iobj)
200 {
201 Object *o = dbpriv_find_object(oid);
202 Verbdef *v, *newv;
203
204 db_priv_affected_callable_verb_lookup();
205
206 newv = mymalloc(sizeof(Verbdef), M_VERBDEF);
207 newv->name = vnames;
208 newv->owner = owner;
209 newv->perms = flags | (dobj << DOBJSHIFT) | (iobj << IOBJSHIFT);
210 newv->prep = prep;
211 newv->next = 0;
212 newv->program = 0;
213 if (o->verbdefs) {
214 for (v = o->verbdefs; v->next; v = v->next);
215 v->next = newv;
216 } else
217 o->verbdefs = newv;
218 }
219
220 static Verbdef *
find_verbdef_by_name(Object * o,const char * vname,int check_x_bit)221 find_verbdef_by_name(Object * o, const char *vname, int check_x_bit)
222 {
223 Verbdef *v;
224
225 for (v = o->verbdefs; v; v = v->next)
226 if (verbcasecmp(v->name, vname)
227 && (!check_x_bit || (v->perms & VF_EXEC)))
228 break;
229
230 return v;
231 }
232
233 int
db_count_verbs(Objid oid)234 db_count_verbs(Objid oid)
235 {
236 int count = 0;
237 Object *o = dbpriv_find_object(oid);
238 Verbdef *v;
239
240 for (v = o->verbdefs; v; v = v->next)
241 count++;
242
243 return count;
244 }
245
246 int
db_for_all_verbs(Objid oid,int (* func)(void * data,const char * vname),void * data)247 db_for_all_verbs(Objid oid,
248 int (*func) (void *data, const char *vname),
249 void *data)
250 {
251 Object *o = dbpriv_find_object(oid);
252 Verbdef *v;
253
254 for (v = o->verbdefs; v; v = v->next)
255 if ((*func) (data, v->name))
256 return 1;
257
258 return 0;
259 }
260
261 typedef struct { /* Non-null db_verb_handles point to these */
262 Objid definer;
263 Verbdef *verbdef;
264 } handle;
265
266 void
db_delete_verb(db_verb_handle vh)267 db_delete_verb(db_verb_handle vh)
268 {
269 handle *h = (handle *) vh.ptr;
270 Objid oid = h->definer;
271 Verbdef *v = h->verbdef;
272 Object *o = dbpriv_find_object(oid);
273 Verbdef *vv;
274
275 db_priv_affected_callable_verb_lookup();
276
277 vv = o->verbdefs;
278 if (vv == v)
279 o->verbdefs = v->next;
280 else {
281 while (vv->next != v)
282 vv = vv->next;
283 vv->next = v->next;
284 }
285
286 if (v->program)
287 free_program(v->program);
288 if (v->name)
289 free_str(v->name);
290 myfree(v, M_VERBDEF);
291 }
292
293 db_verb_handle
db_find_command_verb(Objid oid,const char * verb,db_arg_spec dobj,unsigned prep,db_arg_spec iobj)294 db_find_command_verb(Objid oid, const char *verb,
295 db_arg_spec dobj, unsigned prep, db_arg_spec iobj)
296 {
297 Object *o;
298 Verbdef *v;
299 static handle h;
300 db_verb_handle vh;
301
302 for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent))
303 for (v = o->verbdefs; v; v = v->next) {
304 db_arg_spec vdobj = (v->perms >> DOBJSHIFT) & OBJMASK;
305 db_arg_spec viobj = (v->perms >> IOBJSHIFT) & OBJMASK;
306
307 if (verbcasecmp(v->name, verb)
308 && (vdobj == ASPEC_ANY || vdobj == dobj)
309 && (v->prep == PREP_ANY || v->prep == prep)
310 && (viobj == ASPEC_ANY || viobj == iobj)) {
311 h.definer = o->id;
312 h.verbdef = v;
313 vh.ptr = &h;
314
315 return vh;
316 }
317 }
318
319 vh.ptr = 0;
320
321 return vh;
322 }
323
324 #ifdef VERB_CACHE
325 int db_verb_generation = 0;
326
327 int verbcache_hit = 0;
328 int verbcache_neg_hit = 0;
329 int verbcache_miss = 0;
330
331 typedef struct vc_entry vc_entry;
332
333 struct vc_entry {
334 unsigned int hash;
335 #ifdef RONG
336 int generation;
337 #endif
338 Objid oid_key; /* Note that we proceed up the parent tree
339 until we hit an object with verbs on it */
340 char *verbname;
341 handle h;
342 struct vc_entry *next;
343 };
344
345 static vc_entry **vc_table = NULL;
346 static int vc_size = 0;
347
348 #define DEFAULT_VC_SIZE 7507
349
350 void
db_priv_affected_callable_verb_lookup(void)351 db_priv_affected_callable_verb_lookup(void)
352 {
353 int i;
354 vc_entry *vc, *vc_next;
355
356 if (vc_table == NULL)
357 return;
358
359 db_verb_generation++;
360
361 for (i = 0; i < vc_size; i++) {
362 vc = vc_table[i];
363 while (vc) {
364 vc_next = vc->next;
365 free_str(vc->verbname);
366 myfree(vc, M_VC_ENTRY);
367 vc = vc_next;
368 }
369 vc_table[i] = NULL;
370 }
371 }
372
373 static void
make_vc_table(int size)374 make_vc_table(int size)
375 {
376 int i;
377
378 vc_size = size;
379 vc_table = mymalloc(size * sizeof(vc_entry *), M_VC_TABLE);
380 for (i = 0; i < size; i++) {
381 vc_table[i] = NULL;
382 }
383 }
384
385 #define VC_CACHE_STATS_MAX 16
386
387 Var
db_verb_cache_stats(void)388 db_verb_cache_stats(void)
389 {
390 int i, depth, histogram[VC_CACHE_STATS_MAX + 1];
391 vc_entry *vc;
392 Var v, vv;
393
394 for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++) {
395 histogram[i] = 0;
396 }
397
398 for (i = 0; i < vc_size; i++) {
399 depth = 0;
400 for (vc = vc_table[i]; vc; vc = vc->next)
401 depth++;
402 if (depth > VC_CACHE_STATS_MAX)
403 depth = VC_CACHE_STATS_MAX;
404 histogram[depth]++;
405 }
406
407 v = new_list(5);
408 v.v.list[1].type = TYPE_INT;
409 v.v.list[1].v.num = verbcache_hit;
410 v.v.list[2].type = TYPE_INT;
411 v.v.list[2].v.num = verbcache_neg_hit;
412 v.v.list[3].type = TYPE_INT;
413 v.v.list[3].v.num = verbcache_miss;
414 v.v.list[4].type = TYPE_INT;
415 v.v.list[4].v.num = db_verb_generation;
416 vv = (v.v.list[5] = new_list(VC_CACHE_STATS_MAX + 1));
417 for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++) {
418 vv.v.list[i + 1].type = TYPE_INT;
419 vv.v.list[i + 1].v.num = histogram[i];
420 }
421 return v;
422 }
423
424 void
db_log_cache_stats(void)425 db_log_cache_stats(void)
426 {
427 int i, depth, histogram[VC_CACHE_STATS_MAX + 1];
428 vc_entry *vc;
429
430 for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++) {
431 histogram[i] = 0;
432 }
433
434 for (i = 0; i < vc_size; i++) {
435 depth = 0;
436 for (vc = vc_table[i]; vc; vc = vc->next)
437 depth++;
438 if (depth > VC_CACHE_STATS_MAX)
439 depth = VC_CACHE_STATS_MAX;
440 histogram[depth]++;
441 }
442
443 oklog("Verb cache stat summary: %d hits, %d misses, %d generations\n",
444 verbcache_hit, verbcache_miss, db_verb_generation);
445 oklog("Depth Count\n");
446 for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++)
447 oklog("%-5d %-5d\n", i, histogram[i]);
448 oklog("---\n");
449 }
450
451 #endif
452
453 db_verb_handle
db_find_callable_verb(Objid oid,const char * verb)454 db_find_callable_verb(Objid oid, const char *verb)
455 {
456 Object *o;
457 Verbdef *v;
458 #ifdef VERB_CACHE
459 vc_entry *new_vc;
460 #else
461 static handle h;
462 #endif
463 db_verb_handle vh;
464
465 #ifdef VERB_CACHE
466 unsigned int hash, bucket;
467 Objid first_parent_with_verbs = oid;
468 vc_entry *vc;
469
470 if (vc_table == NULL)
471 make_vc_table(DEFAULT_VC_SIZE);
472
473 for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent)) {
474 if (o->verbdefs != NULL)
475 break;
476 }
477
478 if (o) {
479 first_parent_with_verbs = o->id;
480 } else {
481 first_parent_with_verbs = NOTHING;
482 }
483
484 hash = str_hash(verb) ^ (~first_parent_with_verbs); /* ewww, but who cares */
485 bucket = hash % vc_size;
486
487 for (vc = vc_table[bucket]; vc; vc = vc->next) {
488 if (hash == vc->hash
489 && first_parent_with_verbs == vc->oid_key
490 && !mystrcasecmp(verb, vc->verbname)) {
491 /* we haaave a winnaaah */
492 if (vc->h.verbdef) {
493 verbcache_hit++;
494 vh.ptr = &vc->h;
495 } else {
496 verbcache_neg_hit++;
497 vh.ptr = 0;
498 }
499 return vh;
500 }
501 }
502
503 /* A swing and a miss. */
504 verbcache_miss++;
505 #else
506 o = dbpriv_find_object(oid);
507 #endif
508
509 #ifdef VERB_CACHE
510 /*
511 * Add the entry to the verbcache whether we find it or not. This means
512 * we do "negative caching", keeping track of failed lookups so that
513 * repeated failures hit the cache instead of going through a lookup.
514 */
515 new_vc = mymalloc(sizeof(vc_entry), M_VC_ENTRY);
516
517 new_vc->hash = hash;
518 new_vc->oid_key = first_parent_with_verbs;
519 new_vc->verbname = str_dup(verb);
520 new_vc->h.verbdef = NULL;
521 new_vc->next = vc_table[bucket];
522 vc_table[bucket] = new_vc;
523 #endif
524
525 for ( /* from above */ ; o; o = dbpriv_find_object(o->parent))
526 if ((v = find_verbdef_by_name(o, verb, 1)) != 0) {
527 #ifdef VERB_CACHE
528 new_vc->h.definer = o->id;
529 new_vc->h.verbdef = v;
530 vh.ptr = &new_vc->h;
531 #else
532 h.definer = o->id;
533 h.verbdef = v;
534 vh.ptr = &h;
535 #endif
536 return vh;
537 }
538 /*
539 * note that the verbcache has cleared h.verbdef, so it defaults to a
540 * "miss" cache if the for loop doesn't win
541 */
542 vh.ptr = 0;
543
544 return vh;
545 }
546
547 db_verb_handle
db_find_defined_verb(Objid oid,const char * vname,int allow_numbers)548 db_find_defined_verb(Objid oid, const char *vname, int allow_numbers)
549 {
550 Object *o = dbpriv_find_object(oid);
551 Verbdef *v;
552 char *p;
553 int num, i;
554 static handle h;
555 db_verb_handle vh;
556
557 if (!allow_numbers ||
558 (num = strtol(vname, &p, 10),
559 (isspace(*vname) || *p != '\0')))
560 num = -1;
561
562 for (i = 0, v = o->verbdefs; v; v = v->next, i++)
563 if (i == num || verbcasecmp(v->name, vname))
564 break;
565
566 if (v) {
567 h.definer = o->id;
568 h.verbdef = v;
569 vh.ptr = &h;
570
571 return vh;
572 }
573 vh.ptr = 0;
574
575 return vh;
576 }
577
578 db_verb_handle
db_find_indexed_verb(Objid oid,unsigned index)579 db_find_indexed_verb(Objid oid, unsigned index)
580 {
581 Object *o = dbpriv_find_object(oid);
582 Verbdef *v;
583 unsigned i;
584 static handle h;
585 db_verb_handle vh;
586
587 for (v = o->verbdefs, i = 0; v; v = v->next)
588 if (++i == index) {
589 h.definer = o->id;
590 h.verbdef = v;
591 vh.ptr = &h;
592
593 return vh;
594 }
595 vh.ptr = 0;
596
597 return vh;
598 }
599
600 Objid
db_verb_definer(db_verb_handle vh)601 db_verb_definer(db_verb_handle vh)
602 {
603 handle *h = (handle *) vh.ptr;
604
605 if (h)
606 return h->definer;
607
608 panic("DB_VERB_DEFINER: Null handle!");
609 return 0;
610 }
611
612 const char *
db_verb_names(db_verb_handle vh)613 db_verb_names(db_verb_handle vh)
614 {
615 handle *h = (handle *) vh.ptr;
616
617 if (h)
618 return h->verbdef->name;
619
620 panic("DB_VERB_NAMES: Null handle!");
621 return 0;
622 }
623
624 void
db_set_verb_names(db_verb_handle vh,const char * names)625 db_set_verb_names(db_verb_handle vh, const char *names)
626 {
627 handle *h = (handle *) vh.ptr;
628
629 db_priv_affected_callable_verb_lookup();
630
631 if (h) {
632 if (h->verbdef->name)
633 free_str(h->verbdef->name);
634 h->verbdef->name = names;
635 } else
636 panic("DB_SET_VERB_NAMES: Null handle!");
637 }
638
639 Objid
db_verb_owner(db_verb_handle vh)640 db_verb_owner(db_verb_handle vh)
641 {
642 handle *h = (handle *) vh.ptr;
643
644 if (h)
645 return h->verbdef->owner;
646
647 panic("DB_VERB_OWNER: Null handle!");
648 return 0;
649 }
650
651 void
db_set_verb_owner(db_verb_handle vh,Objid owner)652 db_set_verb_owner(db_verb_handle vh, Objid owner)
653 {
654 handle *h = (handle *) vh.ptr;
655
656 if (h)
657 h->verbdef->owner = owner;
658 else
659 panic("DB_SET_VERB_OWNER: Null handle!");
660 }
661
662 unsigned
db_verb_flags(db_verb_handle vh)663 db_verb_flags(db_verb_handle vh)
664 {
665 handle *h = (handle *) vh.ptr;
666
667 if (h)
668 return h->verbdef->perms & PERMMASK;
669
670 panic("DB_VERB_FLAGS: Null handle!");
671 return 0;
672 }
673
674 void
db_set_verb_flags(db_verb_handle vh,unsigned flags)675 db_set_verb_flags(db_verb_handle vh, unsigned flags)
676 {
677 handle *h = (handle *) vh.ptr;
678
679 db_priv_affected_callable_verb_lookup();
680
681 if (h) {
682 h->verbdef->perms &= ~PERMMASK;
683 h->verbdef->perms |= flags;
684 } else
685 panic("DB_SET_VERB_FLAGS: Null handle!");
686 }
687
688 Program *
db_verb_program(db_verb_handle vh)689 db_verb_program(db_verb_handle vh)
690 {
691 handle *h = (handle *) vh.ptr;
692
693 if (h) {
694 Program *p = h->verbdef->program;
695
696 return p ? p : null_program();
697 }
698 panic("DB_VERB_PROGRAM: Null handle!");
699 return 0;
700 }
701
702 void
db_set_verb_program(db_verb_handle vh,Program * program)703 db_set_verb_program(db_verb_handle vh, Program * program)
704 {
705 handle *h = (handle *) vh.ptr;
706
707 /* Not necessary, since this was only here to cope with nonprogrammed verbs, and that turns out to be handled properly in modern servers. */
708
709 /* db_priv_affected_callable_verb_lookup(); */
710
711 if (h) {
712 if (h->verbdef->program)
713 free_program(h->verbdef->program);
714 h->verbdef->program = program;
715 } else
716 panic("DB_SET_VERB_PROGRAM: Null handle!");
717 }
718
719 void
db_verb_arg_specs(db_verb_handle vh,db_arg_spec * dobj,db_prep_spec * prep,db_arg_spec * iobj)720 db_verb_arg_specs(db_verb_handle vh,
721 db_arg_spec * dobj, db_prep_spec * prep, db_arg_spec * iobj)
722 {
723 handle *h = (handle *) vh.ptr;
724
725 if (h) {
726 *dobj = (h->verbdef->perms >> DOBJSHIFT) & OBJMASK;
727 *prep = h->verbdef->prep;
728 *iobj = (h->verbdef->perms >> IOBJSHIFT) & OBJMASK;
729 } else
730 panic("DB_VERB_ARG_SPECS: Null handle!");
731 }
732
733 void
db_set_verb_arg_specs(db_verb_handle vh,db_arg_spec dobj,db_prep_spec prep,db_arg_spec iobj)734 db_set_verb_arg_specs(db_verb_handle vh,
735 db_arg_spec dobj, db_prep_spec prep, db_arg_spec iobj)
736 {
737 handle *h = (handle *) vh.ptr;
738
739 db_priv_affected_callable_verb_lookup();
740
741 if (h) {
742 h->verbdef->perms = ((h->verbdef->perms & PERMMASK)
743 | (dobj << DOBJSHIFT)
744 | (iobj << IOBJSHIFT));
745 h->verbdef->prep = prep;
746 } else
747 panic("DB_SET_VERB_ARG_SPECS: Null handle!");
748 }
749
750 int
db_verb_allows(db_verb_handle h,Objid progr,db_verb_flag flag)751 db_verb_allows(db_verb_handle h, Objid progr, db_verb_flag flag)
752 {
753 return ((db_verb_flags(h) & flag)
754 || progr == db_verb_owner(h)
755 || is_wizard(progr));
756 }
757
758
759 char rcsid_db_verbs[] = "$Id: db_verbs.c,v 1.5 1998/12/14 13:17:39 nop Exp $";
760
761 /*
762 * $Log: db_verbs.c,v $
763 * Revision 1.5 1998/12/14 13:17:39 nop
764 * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims
765 *
766 * Revision 1.4 1997/09/07 23:58:37 nop
767 * Bump up cache size to 7507, since lambdamoo has been running with that
768 * for months.
769 *
770 * Revision 1.3 1997/07/07 03:24:53 nop
771 * Merge UNSAFE_OPTS (r5) after extensive testing.
772 *
773 * Revision 1.2.2.5 1997/07/07 01:41:20 nop
774 * set_verb_code() doesn't really need a verb generation bump.
775 *
776 * Revision 1.2.2.4 1997/06/05 08:38:37 bjj
777 * Tweak nop's verbcache by moving an actual handle into vc_entry to avoid the
778 * copy after the lookup. Also, keep verbs that aren't found in the cache so
779 * repeated calls to nonexistant verbs benefit from caching as well (need to
780 * watch the new field in verb_cache_stats()[2] to see how often this is useful).
781 *
782 * Revision 1.2.2.3 1997/05/29 11:56:21 nop
783 * Added Jason Maltzen's builtin to return a list version of cache stats.
784 *
785 * Revision 1.2.2.2 1997/03/22 22:54:36 bjj
786 * Tiny tweak to db_find_callable_verb to avoid recomputing the first parent
787 * with verbdefs. Also hit it with indent, which made these diffs bigger than
788 * they should have been...
789 *
790 * Revision 1.2.2.1 1997/03/20 07:26:03 nop
791 * First pass at the new verb cache. Some ugly code inside.
792 *
793 * Revision 1.2 1997/03/03 04:18:31 nop
794 * GNU Indent normalization
795 *
796 * Revision 1.1.1.1 1997/03/03 03:44:59 nop
797 * LambdaMOO 1.8.0p5
798 *
799 * Revision 2.4 1996/05/12 21:32:23 pavel
800 * Changed db_add_verb() not to bump the reference count of the given
801 * verb-names string. Release 1.8.0p5.
802 *
803 * Revision 2.3 1996/02/08 07:17:40 pavel
804 * Updated copyright notice for 1996. Release 1.8.0beta1.
805 *
806 * Revision 2.2 1995/12/28 00:29:03 pavel
807 * Broke out the building of the preposition table into a separate function,
808 * called during initialization, to avoid an storage-overwriting bug. Changed
809 * db_delete_verb() to unbundle how the verb is found. Changed
810 * db_find_defined_verb() to allow suppressing old numeric-string behavior.
811 * Release 1.8.0alpha3.
812 *
813 * Revision 2.1 1995/12/11 08:11:07 pavel
814 * Made verb programs never be NULL any more. Release 1.8.0alpha2.
815 *
816 * Revision 2.0 1995/11/30 04:21:33 pavel
817 * New baseline version, corresponding to release 1.8.0alpha1.
818 *
819 * Revision 1.1 1995/11/30 04:21:20 pavel
820 * Initial revision
821 */
822