1 #ifdef RCSID
2 static char RCSid[] =
3 "$Header$";
4 #endif
5 
6 /*
7  *   Copyright (c) 2000, 2002 Michael J. Roberts.  All Rights Reserved.
8  *
9  *   Please see the accompanying license file, LICENSE.TXT, for information
10  *   on using and copying this software.
11  */
12 /*
13 Name
14   vmdict.cpp - dictionary metaclass
15 Function
16 
17 Notes
18 
19 Modified
20   01/24/00 MJRoberts  - Creation
21 */
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "t3std.h"
27 #include "vmdict.h"
28 #include "vmerr.h"
29 #include "vmerrnum.h"
30 #include "vmtype.h"
31 #include "vmglob.h"
32 #include "vmobj.h"
33 #include "vmundo.h"
34 #include "vmmcreg.h"
35 #include "vmfile.h"
36 #include "vmhash.h"
37 #include "vmbif.h"
38 #include "vmmeta.h"
39 #include "vmstack.h"
40 #include "vmstr.h"
41 #include "vmlst.h"
42 #include "vmrun.h"
43 #include "vmstrcmp.h"
44 #include "vmpredef.h"
45 
46 
47 /* ------------------------------------------------------------------------ */
48 /*
49  *   Dictionary undo record.  Each time we change the dictionary, we track
50  *   the change with one of these records - we attach this record to the
51  *   standard undo record via the 'ptrval' field.
52  */
53 struct dict_undo_rec
54 {
55     /* action type */
56     enum dict_undo_action action;
57 
58     /* object value stored in dictionary record */
59     vm_obj_id_t obj;
60 
61     /* vocabulary property for word association */
62     vm_prop_id_t prop;
63 
64     /* length of the word */
65     size_t len;
66 
67     /* text of the word */
68     char txt[1];
69 };
70 
71 /* ------------------------------------------------------------------------ */
72 /*
73  *   Comparator-based hash
74  */
75 class CVmHashFuncComparator: public CVmHashFunc
76 {
77 public:
CVmHashFuncComparator(VMG_ vm_obj_id_t obj)78     CVmHashFuncComparator(VMG_ vm_obj_id_t obj)
79     {
80         globals_ = VMGLOB_ADDR;
81         comparator_ = obj;
82     }
83 
84     /* compute the hash value */
compute_hash(const char * str,size_t len) const85     unsigned int compute_hash(const char *str, size_t len) const
86     {
87         CVmObject *cobj;
88         vm_val_t val;
89         vm_obj_id_t srcobj;
90         uint argc;
91         VMGLOB_PTR(globals_);
92 
93         /* get my comparator object */
94         cobj = vm_objp(vmg_ comparator_);
95 
96         /* create a string object to represent the argument, and push it */
97         G_stk->push()->set_obj(CVmObjString::create(vmg_ FALSE, str, len));
98 
99         /* invoke the calcHash method */
100         argc = 1;
101         if (cobj->get_prop(vmg_ G_predef->calc_hash_prop, &val,
102                            comparator_, &srcobj, &argc)
103             && val.typ == VM_INT)
104         {
105             /* return the hash value from the comparator */
106             return val.val.intval;
107         }
108         else
109         {
110             /* no or invalid hash value - throw an error */
111             err_throw(VMERR_BAD_TYPE_BIF);
112             return FALSE;
113         }
114     }
115 
116 private:
117     /* my comparator object */
118     vm_obj_id_t comparator_;
119 
120     /* system globals */
121     vm_globals *globals_;
122 };
123 
124 /*
125  *   StringComparator-based hash
126  */
127 class CVmHashFuncStrComp: public CVmHashFunc
128 {
129 public:
CVmHashFuncStrComp(CVmObjStrComp * comp)130     CVmHashFuncStrComp(CVmObjStrComp *comp) { comparator_ = comp; }
131 
132     /* compute the hash value */
compute_hash(const char * str,size_t len) const133     unsigned int compute_hash(const char *str, size_t len) const
134     {
135         /* ask the StringComparator to do the work */
136         return comparator_->calc_hash(str, len);
137     }
138 
139 private:
140     /* my StringComparator object */
141     CVmObjStrComp *comparator_;
142 };
143 
144 
145 /* ------------------------------------------------------------------------ */
146 /*
147  *   dictionary object statics
148  */
149 
150 /* metaclass registration object */
151 static CVmMetaclassDict metaclass_reg_obj;
152 CVmMetaclass *CVmObjDict::metaclass_reg_ = &metaclass_reg_obj;
153 
154 /* function table */
155 int (CVmObjDict::
156      *CVmObjDict::func_table_[])(VMG_ vm_obj_id_t self,
157                                  vm_val_t *retval, uint *argc) =
158 {
159     &CVmObjDict::getp_undef,
160     &CVmObjDict::getp_set_comparator,
161     &CVmObjDict::getp_find,
162     &CVmObjDict::getp_add,
163     &CVmObjDict::getp_del,
164     &CVmObjDict::getp_is_defined,
165     &CVmObjDict::getp_for_each_word
166 };
167 
168 /* ------------------------------------------------------------------------ */
169 /*
170  *   Dictionary object implementation
171  */
172 
173 /*
174  *   create dynamically using stack arguments
175  */
create_from_stack(VMG_ const uchar ** pc_ptr,uint argc)176 vm_obj_id_t CVmObjDict::create_from_stack(VMG_ const uchar **pc_ptr,
177                                           uint argc)
178 {
179     vm_obj_id_t id;
180     vm_obj_id_t comp;
181     CVmObjDict *obj;
182 
183     /* check arguments */
184     if (argc != 0 && argc != 1)
185         err_throw(VMERR_WRONG_NUM_OF_ARGS);
186 
187     /* get the Comparator, but leave it on the stack as gc protection */
188     if (argc == 0 || G_stk->get(0)->typ == VM_NIL)
189         comp = VM_INVALID_OBJ;
190     else if (G_stk->get(0)->typ == VM_OBJ)
191         comp = G_stk->get(0)->val.obj;
192     else
193         err_throw(VMERR_BAD_TYPE_BIF);
194 
195     /*
196      *   allocate the object ID - this type of construction never creates
197      *   a root object
198      */
199     id = vm_new_id(vmg_ FALSE, TRUE, TRUE);
200 
201     /* create the object */
202     obj = new (vmg_ id) CVmObjDict(vmg0_);
203 
204     /* set the comparator */
205     obj->set_comparator(vmg_ comp);
206 
207     /* build an empty initial hash table */
208     obj->create_hash_table(vmg0_);
209 
210     /*
211      *   mark the object as modified since image load, since it doesn't
212      *   come from the image file at all
213      */
214     obj->get_ext()->modified_ = TRUE;
215 
216     /* discard our gc protection */
217     G_stk->discard();
218 
219     /* return the new ID */
220     return id;
221 }
222 
223 /* ------------------------------------------------------------------------ */
224 /*
225  *   constructor
226  */
CVmObjDict(VMG0_)227 CVmObjDict::CVmObjDict(VMG0_)
228 {
229     /* allocate our extension structure from the variable heap */
230     ext_ = (char *)G_mem->get_var_heap()
231            ->alloc_mem(sizeof(vm_dict_ext), this);
232 
233     /* we have no image data yet */
234     get_ext()->image_data_ = 0;
235     get_ext()->image_data_size_ = 0;
236 
237     /* no hash table yet */
238     get_ext()->hashtab_ = 0;
239 
240     /* no non-image entries yet */
241     get_ext()->modified_ = FALSE;
242 
243     /* no comparator yet */
244     get_ext()->comparator_ = VM_INVALID_OBJ;
245     set_comparator_type(vmg_ VM_INVALID_OBJ);
246 }
247 
248 /* ------------------------------------------------------------------------ */
249 /*
250  *   notify of deletion
251  */
notify_delete(VMG_ int)252 void CVmObjDict::notify_delete(VMG_ int /*in_root_set*/)
253 {
254     /* free our additional data */
255     if (ext_ != 0)
256     {
257         /* free our hash table */
258         if (get_ext()->hashtab_ != 0)
259             delete get_ext()->hashtab_;
260 
261         /* free the extension */
262         G_mem->get_var_heap()->free_mem(ext_);
263     }
264 }
265 
266 /* ------------------------------------------------------------------------ */
267 /*
268  *   Set the comparator object
269  */
set_comparator(VMG_ vm_obj_id_t obj)270 void CVmObjDict::set_comparator(VMG_ vm_obj_id_t obj)
271 {
272     /* if this is the same as the current comparator, there's nothing to do */
273     if (obj == get_ext()->comparator_)
274         return;
275 
276     /* remember the new comparator */
277     get_ext()->comparator_ = obj;
278 
279     /* figure out what kind of compare functions to use */
280     set_comparator_type(vmg_ obj);
281 
282     /* rebuild the hash tale */
283     if (get_ext()->hashtab_ != 0)
284         create_hash_table(vmg0_);
285 }
286 
287 /*
288  *   Set the string-compare function based on the given comparator.
289  */
set_comparator_type(VMG_ vm_obj_id_t obj)290 void CVmObjDict::set_comparator_type(VMG_ vm_obj_id_t obj)
291 {
292     /* check what kind of object we have */
293     if (obj == VM_INVALID_OBJ)
294     {
295         /* there's no comparator at all, so simply use an exact comparison */
296         get_ext()->comparator_type_ = VMDICT_COMP_NONE;
297     }
298     else if (CVmObjStrComp::is_strcmp_obj(vmg_ obj))
299     {
300         /* it's a StringComparator */
301         get_ext()->comparator_type_ = VMDICT_COMP_STRCOMP;
302     }
303     else
304     {
305         /* it's a generic comparator object */
306         get_ext()->comparator_type_ = VMDICT_COMP_GENERIC;
307     }
308 
309     /* remember the object pointer, if there is one */
310     get_ext()->comparator_obj_ = (obj == VM_INVALID_OBJ
311                                   ? 0 : vm_objp(vmg_ obj));
312 }
313 
314 /* ------------------------------------------------------------------------ */
315 /*
316  *   Rebuild the hash table.  If there's an existing hash table, we'll move
317  *   the entries from the old table to the new table, and delete the old
318  *   table.
319  */
create_hash_table(VMG0_)320 void CVmObjDict::create_hash_table(VMG0_)
321 {
322     CVmHashTable *new_tab;
323     CVmHashFunc *hash_func;
324 
325     /*
326      *   Create our hash function.  If we have a comparator object, base the
327      *   hash function on the comparator; use a special hash function if we
328      *   specifically have a StringComparator, since we can call these
329      *   directly for better efficiency.  If we have no comparator, create a
330      *   generic exact string match hash function.
331      */
332     if (get_ext()->comparator_ != VM_INVALID_OBJ)
333     {
334         /*
335          *   use our special StringComparator hash function if possible;
336          *   otherwise, use a generic comparator hash function
337          */
338         if (CVmObjStrComp::is_strcmp_obj(vmg_ get_ext()->comparator_))
339         {
340             /* create a StringComparator hash function */
341             hash_func = new CVmHashFuncStrComp(
342                 (CVmObjStrComp *)vm_objp(vmg_ get_ext()->comparator_));
343         }
344         else
345         {
346             /* create a generic comparator hash function */
347             hash_func = new CVmHashFuncComparator(
348                 vmg_ get_ext()->comparator_);
349         }
350     }
351     else
352     {
353         /* create a simple exact-match hash */
354         hash_func = new CVmHashFuncCS();
355     }
356 
357     /* create the hash table */
358     new_tab = new CVmHashTable(256, hash_func, TRUE);
359 
360     /* if we had a previous hash table, move its contents to the new table */
361     if (get_ext()->hashtab_ != 0)
362     {
363         /* copy the old hash table to the new one */
364         get_ext()->hashtab_->move_entries_to(new_tab);
365 
366         /* delete the old hash table */
367         delete get_ext()->hashtab_;
368     }
369 
370     /* store the new hash table in the extension */
371     get_ext()->hashtab_ = new_tab;
372 }
373 
374 /* ------------------------------------------------------------------------ */
375 /*
376  *   Check for a match between two strings, using the current comparator.
377  *   Returns true if the strings match, false if not; fills in *result_val
378  *   with the actual result value from the comparator's matchValues()
379  *   function.
380  */
match_strings(VMG_ const vm_val_t * valstrval,const char * valstr,size_t vallen,const char * refstr,size_t reflen,vm_val_t * result_val)381 int CVmObjDict::match_strings(VMG_ const vm_val_t *valstrval,
382                               const char *valstr, size_t vallen,
383                               const char *refstr, size_t reflen,
384                               vm_val_t *result_val)
385 {
386     uint argc;
387     vm_obj_id_t srcobj;
388 
389     /* check what kind of comparator we have */
390     switch(get_ext()->comparator_type_)
391     {
392     case VMDICT_COMP_NONE:
393         /* no comparator - compare the strings byte-for-byte */
394         if (vallen == reflen && memcmp(valstr, refstr, vallen) == 0)
395         {
396             /* match - return true */
397             result_val->set_int(1);
398             return TRUE;
399         }
400         else
401         {
402             /* no match - return false */
403             result_val->set_int(0);
404             return FALSE;
405         }
406 
407     case VMDICT_COMP_STRCOMP:
408         /* match the value directly with the StringComparator */
409         result_val->set_int(
410             ((CVmObjStrComp *)get_ext()->comparator_obj_)->match_strings(
411                 valstr, vallen, refstr, reflen));
412 
413         /* we matched if the result was non-zero */
414         return result_val->val.intval;
415 
416     case VMDICT_COMP_GENERIC:
417         /* 2nd param: push the reference string, as a string object */
418         G_stk->push()->set_obj(
419             CVmObjString::create(vmg_ FALSE, refstr, reflen));
420 
421         /* 1st param: push the value string */
422         if (valstrval != 0)
423         {
424             /* push the value string as given */
425             G_stk->push(valstrval);
426         }
427         else
428         {
429             /* no value string was given - create one and push it */
430             G_stk->push()->set_obj(
431                 CVmObjString::create(vmg_ FALSE, valstr, vallen));
432         }
433 
434         /* call the comparator's matchValues method */
435         argc = 2;
436         if (get_ext()->comparator_obj_->get_prop(
437             vmg_ G_predef->match_values_prop, result_val,
438             get_ext()->comparator_, &srcobj, &argc))
439         {
440             /* got it - we matched if the result isn't 0 or nil */
441             return !(result_val->typ == VM_NIL
442                      || (result_val->typ == VM_INT
443                          && result_val->val.intval == 0));
444         }
445         else
446         {
447             /* didn't even find the method - return no match */
448             return FALSE;
449         }
450     }
451 
452     /* we should never get here, but just in case... */
453     return FALSE;
454 }
455 
456 /* ------------------------------------------------------------------------ */
457 /*
458  *   Calculate a hash value with the current comparator
459  */
calc_hash(VMG_ const vm_val_t * valstrval,const char * valstr,size_t vallen)460 unsigned int CVmObjDict::calc_hash(VMG_ const vm_val_t *valstrval,
461                                    const char *valstr, size_t vallen)
462 {
463     uint argc;
464     vm_val_t result;
465     vm_obj_id_t srcobj;
466 
467     /* check what kind of comparator we have */
468     switch(get_ext()->comparator_type_)
469     {
470     case VMDICT_COMP_NONE:
471         /* no comparator - use the hash table's basic hash calculation */
472         return get_ext()->hashtab_->compute_hash(valstr, vallen);
473 
474     case VMDICT_COMP_STRCOMP:
475         /* calculate the hash directly with the StringComparator */
476         return ((CVmObjStrComp *)get_ext()->comparator_obj_)->calc_hash(
477             valstr, vallen);
478 
479     case VMDICT_COMP_GENERIC:
480         /* push the value string */
481         if (valstrval != 0)
482         {
483             /* push the value string as given */
484             G_stk->push(valstrval);
485         }
486         else
487         {
488             /* no value string was given - create one and push it */
489             G_stk->push()->set_obj(
490                 CVmObjString::create(vmg_ FALSE, valstr, vallen));
491         }
492 
493         /* call the comparator object's calcHash method */
494         argc = 1;
495         if (get_ext()->comparator_obj_->get_prop(
496             vmg_ G_predef->calc_hash_prop, &result,
497             get_ext()->comparator_, &srcobj, &argc))
498         {
499             /* got it - make sure it's an integer */
500             if (result.typ != VM_INT)
501                 err_throw(VMERR_BAD_TYPE_BIF);
502 
503             /* return the result */
504             return (unsigned int)result.val.intval;
505         }
506         else
507         {
508             /* didn't even find the method - it's an error */
509             err_throw(VMERR_BAD_TYPE_BIF);
510             return 0;
511         }
512     }
513 
514     /* we should never get here, but just in case... */
515     return 0;
516 }
517 
518 /* ------------------------------------------------------------------------ */
519 /*
520  *   Set a property.  We have no settable properties, so simply signal an
521  *   error indicating that the set-prop call is invalid.
522  */
set_prop(VMG_ CVmUndo *,vm_obj_id_t,vm_prop_id_t,const vm_val_t *)523 void CVmObjDict::set_prop(VMG_ CVmUndo *, vm_obj_id_t,
524                           vm_prop_id_t, const vm_val_t *)
525 {
526     /* no settable properties - throw an error */
527     err_throw(VMERR_INVALID_SETPROP);
528 }
529 
530 /* ------------------------------------------------------------------------ */
531 /*
532  *   get a property
533  */
get_prop(VMG_ vm_prop_id_t prop,vm_val_t * retval,vm_obj_id_t self,vm_obj_id_t * source_obj,uint * argc)534 int CVmObjDict::get_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval,
535                          vm_obj_id_t self, vm_obj_id_t *source_obj,
536                          uint *argc)
537 {
538     ushort func_idx;
539 
540     /* translate the property into a function vector index */
541     func_idx = G_meta_table
542                ->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop);
543 
544     /* call the appropriate function */
545     if ((this->*func_table_[func_idx])(vmg_ self, retval, argc))
546     {
547         *source_obj = metaclass_reg_->get_class_obj(vmg0_);
548         return TRUE;
549     }
550 
551     /* inherit default handling from the base object class */
552     return CVmObject::get_prop(vmg_ prop, retval, self, source_obj, argc);
553 }
554 
555 /* ------------------------------------------------------------------------ */
556 /*
557  *   evaluate property: set the comparator
558  */
getp_set_comparator(VMG_ vm_obj_id_t self,vm_val_t * val,uint * argc)559 int CVmObjDict::getp_set_comparator(VMG_ vm_obj_id_t self, vm_val_t *val,
560                                     uint *argc)
561 {
562     static CVmNativeCodeDesc desc(1);
563     vm_obj_id_t comp;
564 
565     /* check arguments */
566     if (get_prop_check_argc(val, argc, &desc))
567         return TRUE;
568 
569     /* get the comparator object */
570     switch(G_stk->get(0)->typ)
571     {
572     case VM_NIL:
573         comp = VM_INVALID_OBJ;
574         break;
575 
576     case VM_OBJ:
577         comp = G_stk->get(0)->val.obj;
578         break;
579 
580     default:
581         err_throw(VMERR_BAD_TYPE_BIF);
582     }
583 
584     /* if there's a global undo object, add undo for the change */
585     if (G_undo != 0)
586     {
587         dict_undo_rec *undo_rec;
588 
589         /* create an undo record with the original comparator */
590         undo_rec = alloc_undo_rec(DICT_UNDO_COMPARATOR, 0, 0);
591         undo_rec->obj = get_ext()->comparator_;
592 
593         /* add the undo record */
594         add_undo_rec(vmg_ self, undo_rec);
595     }
596 
597     /* set our new comparator object */
598     set_comparator(vmg_ comp);
599 
600     /* mark the object as modified since load */
601     get_ext()->modified_ = TRUE;
602 
603     /* discard arguments */
604     G_stk->discard();
605 
606     /* no return value - just return nil */
607     val->set_nil();
608 
609     /* handled */
610     return TRUE;
611 }
612 
613 /* ------------------------------------------------------------------------ */
614 /*
615  *   result entry for find_ctx
616  */
617 struct find_entry
618 {
619     /* matching object */
620     vm_obj_id_t obj;
621 
622     /* match result code */
623     vm_val_t match_result;
624 };
625 
626 /*
627  *   page of results for find_ctx
628  */
629 const int FIND_ENTRIES_PER_PAGE = 32;
630 struct find_entry_page
631 {
632     /* next in list of pages */
633     find_entry_page *nxt;
634 
635     /* array of entries */
636     find_entry entry[FIND_ENTRIES_PER_PAGE];
637 };
638 
639 /*
640  *   enumeration callback context for getp_find
641  */
642 struct find_ctx
643 {
644     /* set up a context that adds results to our internal list */
find_ctxfind_ctx645     find_ctx(VMG_ CVmObjDict *dict_obj, const vm_val_t *strval,
646              const char *strp, size_t strl, vm_prop_id_t prop)
647     {
648         /* remember the dictionary object we're searching */
649         dict = dict_obj;
650 
651         /* remember the globals */
652         globals = VMGLOB_ADDR;
653 
654         /* keep a pointer to the search string value */
655         this->strval = strval;
656         this->strp = strp;
657         this->strl = strl;
658 
659         /* remember the property */
660         voc_prop = prop;
661 
662         /* no results yet */
663         results_head = 0;
664         results_tail = 0;
665         result_cnt = 0;
666 
667         /*
668          *   set the free pointer to the end of the non-existent current
669          *   page, so we know we have to allocate a new page before adding
670          *   the first entry
671          */
672         result_free = FIND_ENTRIES_PER_PAGE;
673     }
674 
~find_ctxfind_ctx675     ~find_ctx()
676     {
677         /* free our list of results pages */
678         while (results_head != 0)
679         {
680             /* remember the current page */
681             find_entry_page *cur = results_head;
682 
683             /* move on to the next page */
684             results_head = results_head->nxt;
685 
686             /* delete the current page */
687             t3free(cur);
688         }
689     }
690 
691     /* add a result, expanding the result array if necessary */
add_resultfind_ctx692     void add_result(vm_obj_id_t obj, const vm_val_t *match_result)
693     {
694         /*
695          *   if we have no pages, or we're out of room on the current page,
696          *   allocate a new page
697          */
698         if (result_free == FIND_ENTRIES_PER_PAGE)
699         {
700             find_entry_page *pg;
701 
702             /* allocate a new page */
703             pg = (find_entry_page *)t3malloc(sizeof(find_entry_page));
704 
705             /* link it in at the end of the list */
706             pg->nxt = 0;
707             if (results_tail != 0)
708                 results_tail->nxt = pg;
709             else
710                 results_head = pg;
711             results_tail = pg;
712 
713             /* reset the free pointer to the start of the new page */
714             result_free = 0;
715         }
716 
717         /* add this result */
718         results_tail->entry[result_free].obj = obj;
719         results_tail->entry[result_free].match_result = *match_result;
720         ++result_free;
721         ++result_cnt;
722     }
723 
724     /* make our results into a list */
results_to_listfind_ctx725     vm_obj_id_t results_to_list(VMG0_)
726     {
727         vm_obj_id_t lst_id;
728         CVmObjList *lst;
729         find_entry_page *pg;
730         int idx;
731 
732         /*
733          *   Allocate a list of the appropriate size.  We need two list
734          *   entries per result, since we need to store the object and the
735          *   match result code for each result.
736          */
737         lst_id = CVmObjList::create(vmg_ FALSE, result_cnt * 2);
738         lst = (CVmObjList *)vm_objp(vmg_ lst_id);
739 
740         /* add all entries */
741         for (idx = 0, pg = results_head ; idx < result_cnt ; pg = pg->nxt)
742         {
743             find_entry *ep;
744             int pg_idx;
745 
746             /* add each entry on this page */
747             for (ep = pg->entry, pg_idx = 0 ;
748                  idx < result_cnt && pg_idx < FIND_ENTRIES_PER_PAGE ;
749                  ++idx, ++pg_idx, ++ep)
750             {
751                 vm_val_t ele;
752 
753                 /* add this entry to the list */
754                 ele.set_obj(ep->obj);
755                 lst->cons_set_element(idx*2, &ele);
756                 lst->cons_set_element(idx*2 + 1, &ep->match_result);
757             }
758         }
759 
760         /* return the list */
761         return lst_id;
762     }
763 
764     /* head/tail of list of result pages */
765     find_entry_page *results_head;
766     find_entry_page *results_tail;
767 
768     /* next available entry on current results page */
769     int result_free;
770 
771     /* total number of result */
772     int result_cnt;
773 
774     /* the string to find */
775     const vm_val_t *strval;
776     const char *strp;
777     size_t strl;
778 
779     /* vocabulary property value to match */
780     vm_prop_id_t voc_prop;
781 
782     /* VM globals */
783     vm_globals *globals;
784 
785     /* dictionary object we're searching */
786     CVmObjDict *dict;
787 };
788 
789 /*
790  *   Callback for getp_find hash match enumeration
791  */
find_cb(void * ctx0,CVmHashEntry * entry0)792 void CVmObjDict::find_cb(void *ctx0, CVmHashEntry *entry0)
793 {
794     find_ctx *ctx = (find_ctx *)ctx0;
795     CVmHashEntryDict *entry = (CVmHashEntryDict *)entry0;
796     vm_val_t match_val;
797     VMGLOB_PTR(ctx->globals);
798 
799     /* if this entry matches the search string, process it */
800     if (ctx->dict->match_strings(vmg_ ctx->strval, ctx->strp, ctx->strl,
801                                  entry->getstr(), entry->getlen(),
802                                  &match_val))
803     {
804         vm_dict_entry *cur;
805 
806         /* process the items under this entry */
807         for (cur = entry->get_head() ; cur != 0 ; cur = cur->nxt_)
808         {
809             /*
810              *   If it matches the target property, it's a hit.  If we're
811              *   searching for any property at all (indicated by the
812              *   context property being set to VM_INVALID_PROP), count
813              *   everything.
814              */
815             if (cur->prop_ == ctx->voc_prop
816                 || ctx->voc_prop == VM_INVALID_PROP)
817             {
818                 /* process the result */
819                 ctx->add_result(cur->obj_, &match_val);
820             }
821         }
822     }
823 }
824 
825 /* ------------------------------------------------------------------------ */
826 /*
827  *   property evaluation - look up a word
828  */
getp_find(VMG_ vm_obj_id_t,vm_val_t * val,uint * argc)829 int CVmObjDict::getp_find(VMG_ vm_obj_id_t, vm_val_t *val, uint *argc)
830 {
831     static CVmNativeCodeDesc desc(1, 1);
832     uint orig_argc = (argc != 0 ? *argc : 0);
833     vm_val_t *arg0;
834     vm_val_t *arg1;
835     const char *strp;
836     size_t strl;
837     vm_prop_id_t voc_prop;
838     unsigned int hash;
839 
840     /* check arguments */
841     if (get_prop_check_argc(val, argc, &desc))
842         return TRUE;
843 
844     /*
845      *   make sure the first argument is a string, but leave it on the stack
846      *   for gc protection
847      */
848     arg0 = G_stk->get(0);
849     if ((strp = arg0->get_as_string(vmg0_)) == 0)
850         err_throw(VMERR_STRING_VAL_REQD);
851 
852     /* scan and skip the string's length prefix */
853     strl = vmb_get_len(strp);
854     strp += VMB_LEN;
855 
856     /*
857      *   get the property ID, which can be omitted or specified as nil to
858      *   indicate any property
859      */
860     if (orig_argc < 2)
861         voc_prop = VM_INVALID_PROP;
862     else if ((arg1 = G_stk->get(1))->typ == VM_NIL)
863         voc_prop = VM_INVALID_PROP;
864     else if (arg1->typ == VM_PROP)
865         voc_prop = arg1->val.prop;
866     else
867         err_throw(VMERR_PROPPTR_VAL_REQD);
868 
869     /* calculate the hash value */
870     hash = calc_hash(vmg_ arg0, strp, strl);
871 
872     /* enumerate everything that matches the string's hash code */
873     find_ctx ctx(vmg_ this, arg0, strp, strl, voc_prop);
874     get_ext()->hashtab_->enum_hash_matches(hash, &find_cb, &ctx);
875 
876     /* build a list out of the results, and return the list */
877     val->set_obj(ctx.results_to_list(vmg0_));
878 
879     /* discard arguments */
880     G_stk->discard(orig_argc);
881 
882     /* success */
883     return TRUE;
884 }
885 
886 /* ------------------------------------------------------------------------ */
887 /*
888  *   property evaluation - add a word
889  */
getp_add(VMG_ vm_obj_id_t self,vm_val_t * val,uint * argc)890 int CVmObjDict::getp_add(VMG_ vm_obj_id_t self, vm_val_t *val, uint *argc)
891 {
892     vm_obj_id_t obj;
893     vm_val_t str_val;
894     vm_prop_id_t voc_prop;
895     CVmObject *objp;
896     const char *lst;
897     const char *str;
898     static CVmNativeCodeDesc desc(3);
899 
900     /* check arguments */
901     if (get_prop_check_argc(val, argc, &desc))
902         return TRUE;
903 
904     /* pop the object, word string, and property ID arguments */
905     obj = CVmBif::pop_obj_val(vmg0_);
906     G_stk->pop(&str_val);
907     voc_prop = CVmBif::pop_propid_val(vmg0_);
908 
909     /* ensure that the object value isn't a string or list */
910     objp = vm_objp(vmg_ obj);
911     if (objp->get_as_list() != 0 || objp->get_as_string(vmg0_) != 0)
912         err_throw(VMERR_OBJ_VAL_REQD);
913 
914     /* if the string argument is a list, add each word from the list */
915     if ((lst = str_val.get_as_list(vmg0_)) != 0)
916     {
917         size_t cnt;
918         size_t idx;
919         vm_val_t ele_val;
920 
921         /* get and skip the list count prefix */
922         cnt = vmb_get_len(lst);
923 
924         /* iterate over the list, using 1-based indices */
925         for (idx = 1 ; idx <= cnt ; ++idx)
926         {
927             /* get the next value from the list */
928             CVmObjList::index_list(vmg_ &ele_val, lst, idx);
929 
930             /* get the next string value */
931             if ((str = ele_val.get_as_string(vmg0_)) == 0)
932                 err_throw(VMERR_STRING_VAL_REQD);
933 
934             /* set this string */
935             getp_add_string(vmg_ self, str, obj, voc_prop);
936 
937             /*
938              *   refresh the list pointer, in case it was const pool data
939              *   and got swapped out by resolving the string pointer
940              */
941             lst = str_val.get_as_list(vmg0_);
942         }
943     }
944     else if ((str = str_val.get_as_string(vmg0_)) != 0)
945     {
946         /* add the string */
947         getp_add_string(vmg_ self, str, obj, voc_prop);
948     }
949     else
950     {
951         /* string value required */
952         err_throw(VMERR_STRING_VAL_REQD);
953     }
954 
955     /* there's no return value */
956     val->set_nil();
957 
958     /* success */
959     return TRUE;
960 }
961 
962 /*
963  *   Service routine for getp_add() - add a single string
964  */
getp_add_string(VMG_ vm_obj_id_t self,const char * str,vm_obj_id_t obj,vm_prop_id_t voc_prop)965 void CVmObjDict::getp_add_string(VMG_ vm_obj_id_t self,
966                                  const char *str, vm_obj_id_t obj,
967                                  vm_prop_id_t voc_prop)
968 {
969     size_t len;
970     int added;
971 
972     /* get the string's length prefix */
973     len = vmb_get_len(str);
974     str += 2;
975 
976     /* add the entry */
977     added = add_hash_entry(vmg_ str, len, TRUE, obj, voc_prop, FALSE);
978 
979     /*
980      *   if there's a global undo object, and we actually added a new
981      *   entry, add undo for the change
982      */
983     if (added && G_undo != 0)
984     {
985         dict_undo_rec *undo_rec;
986 
987         /* create the undo record */
988         undo_rec = alloc_undo_rec(DICT_UNDO_ADD, str, len);
989         undo_rec->obj = obj;
990         undo_rec->prop = voc_prop;
991 
992         /* add the undo record */
993         add_undo_rec(vmg_ self, undo_rec);
994     }
995 
996     /* mark the object as modified since load */
997     get_ext()->modified_ = TRUE;
998 }
999 
1000 /* ------------------------------------------------------------------------ */
1001 /*
1002  *   property evaluation - delete a word
1003  */
getp_del(VMG_ vm_obj_id_t self,vm_val_t * val,uint * argc)1004 int CVmObjDict::getp_del(VMG_ vm_obj_id_t self, vm_val_t *val, uint *argc)
1005 {
1006     vm_obj_id_t obj;
1007     vm_val_t str_val;
1008     vm_prop_id_t voc_prop;
1009     const char *lst;
1010     const char *str;
1011     static CVmNativeCodeDesc desc(3);
1012 
1013     /* check arguments */
1014     if (get_prop_check_argc(val, argc, &desc))
1015         return TRUE;
1016 
1017     /* pop the object, word string, and property ID arguments */
1018     obj = CVmBif::pop_obj_val(vmg0_);
1019     G_stk->pop(&str_val);
1020     voc_prop = CVmBif::pop_propid_val(vmg0_);
1021 
1022     /* if the string argument is a list, add each word from the list */
1023     if ((lst = str_val.get_as_list(vmg0_)) != 0)
1024     {
1025         size_t cnt;
1026         size_t idx;
1027         vm_val_t ele_val;
1028 
1029         /* get and skip the list count prefix */
1030         cnt = vmb_get_len(lst);
1031 
1032         /* iterate over the list, using 1-based indices */
1033         for (idx = 1 ; idx <= cnt ; ++idx)
1034         {
1035             /* get the next value from the list */
1036             CVmObjList::index_list(vmg_ &ele_val, lst, idx);
1037 
1038             /* get the next string value */
1039             if ((str = ele_val.get_as_string(vmg0_)) == 0)
1040                 err_throw(VMERR_STRING_VAL_REQD);
1041 
1042             /* remove this string */
1043             getp_del_string(vmg_ self, str, obj, voc_prop);
1044 
1045             /*
1046              *   refresh the list pointer, in case it was const pool data
1047              *   and got swapped out by resolving the string pointer
1048              */
1049             lst = str_val.get_as_list(vmg0_);
1050         }
1051     }
1052     else if ((str = str_val.get_as_string(vmg0_)) != 0)
1053     {
1054         /* remove the string */
1055         getp_del_string(vmg_ self, str, obj, voc_prop);
1056     }
1057     else
1058     {
1059         /* string value required */
1060         err_throw(VMERR_STRING_VAL_REQD);
1061     }
1062 
1063     /* there's no return value */
1064     val->set_nil();
1065 
1066     /* success */
1067     return TRUE;
1068 }
1069 
1070 /*
1071  *   Service routine for getp_del - delete a string value
1072  */
getp_del_string(VMG_ vm_obj_id_t self,const char * str,vm_obj_id_t obj,vm_prop_id_t voc_prop)1073 void CVmObjDict::getp_del_string(VMG_ vm_obj_id_t self,
1074                                  const char *str, vm_obj_id_t obj,
1075                                  vm_prop_id_t voc_prop)
1076 {
1077     size_t len;
1078     int deleted;
1079 
1080     /* get the string's length prefix */
1081     len = vmb_get_len(str);
1082     str += 2;
1083 
1084     /* delete this entry */
1085     deleted = del_hash_entry(vmg_ str, len, obj, voc_prop);
1086 
1087     /*
1088      *   if there's a global undo object, and we actually deleted an
1089      *   entry, add undo for the change
1090      */
1091     if (deleted && G_undo != 0)
1092     {
1093         dict_undo_rec *undo_rec;
1094 
1095         /* create the undo record */
1096         undo_rec = alloc_undo_rec(DICT_UNDO_DEL, str, len);
1097         undo_rec->obj = obj;
1098         undo_rec->prop = voc_prop;
1099 
1100         /* add the undo record */
1101         add_undo_rec(vmg_ self, undo_rec);
1102     }
1103 
1104     /* mark the object as modified since load */
1105     get_ext()->modified_ = TRUE;
1106 }
1107 
1108 /* ------------------------------------------------------------------------ */
1109 /*
1110  *   callback context for is_defined enumeration
1111  */
1112 struct isdef_ctx
1113 {
isdef_ctxisdef_ctx1114     isdef_ctx(VMG_ CVmObjDict *dict_obj, vm_val_t *filter,
1115               const vm_val_t *strval, const char *strp, size_t strl)
1116     {
1117         /* remember the VM globals and dictionary object */
1118         globals = VMGLOB_ADDR;
1119         dict = dict_obj;
1120 
1121         /* we haven't yet found a match */
1122         found = FALSE;
1123 
1124         /* remember the string */
1125         this->strval = *strval;
1126         this->strp = strp;
1127         this->strl = strl;
1128 
1129         /* remember the filter function */
1130         filter_func = filter;
1131     }
1132 
1133     /* flag: we've found a match */
1134     int found;
1135 
1136     /* string value we're matching */
1137     vm_val_t strval;
1138     const char *strp;
1139     size_t strl;
1140 
1141     /* our filter callback function, if any */
1142     const vm_val_t *filter_func;
1143 
1144     /* VM globals */
1145     vm_globals *globals;
1146 
1147     /* dictionary we're searching */
1148     CVmObjDict *dict;
1149 };
1150 
1151 /*
1152  *   Callback for getp_is_defined hash match enumeration
1153  */
isdef_cb(void * ctx0,CVmHashEntry * entry0)1154 void CVmObjDict::isdef_cb(void *ctx0, CVmHashEntry *entry0)
1155 {
1156     isdef_ctx *ctx = (isdef_ctx *)ctx0;
1157     CVmHashEntryDict *entry = (CVmHashEntryDict *)entry0;
1158     vm_val_t match_val;
1159     VMGLOB_PTR(ctx->globals);
1160 
1161     /*
1162      *   if we've already found a match, don't bother checking anything else
1163      *   - it could be non-trivial to do the string match, so we can save
1164      *   some work by skipping that if we've already found a match
1165      *   previously
1166      */
1167     if (ctx->found)
1168         return;
1169 
1170     /*
1171      *   if this entry matches the search string, and it has at least one
1172      *   defined object/property associated with it, count it as a match
1173      */
1174     if (entry->get_head() != 0
1175         && ctx->dict->match_strings(vmg_ &ctx->strval, ctx->strp, ctx->strl,
1176                                     entry->getstr(), entry->getlen(),
1177                                     &match_val))
1178     {
1179         /* if there's a filter function to invoke, check with it */
1180         if (ctx->filter_func->typ != VM_NIL)
1181         {
1182             vm_val_t *valp;
1183 
1184             /* push the match value parameter */
1185             G_stk->push(&match_val);
1186 
1187             /* call the filter callback */
1188             G_interpreter->call_func_ptr(vmg_ ctx->filter_func, 1,
1189                                          "Dictionary.isDefined", 0);
1190 
1191             /* get the result */
1192             valp = G_interpreter->get_r0();
1193 
1194             /* if it's 0 or nil, it's a rejection of the match */
1195             if (valp->typ == VM_NIL
1196                 || (valp->typ == VM_INT && valp->val.intval == 0))
1197             {
1198                 /* it's a rejection of the value - don't count it */
1199             }
1200             else
1201             {
1202                 /* it's an acceptance of the value - count it as found */
1203                 ctx->found = TRUE;
1204             }
1205         }
1206         else
1207         {
1208             /* there's no filter, so everything matches */
1209             ctx->found = TRUE;
1210         }
1211     }
1212 }
1213 
1214 /*
1215  *   property evaluation - check to see if a word is defined
1216  */
getp_is_defined(VMG_ vm_obj_id_t self,vm_val_t * val,uint * argc)1217 int CVmObjDict::getp_is_defined(VMG_ vm_obj_id_t self, vm_val_t *val,
1218                                 uint *argc)
1219 {
1220     static CVmNativeCodeDesc desc(1, 1);
1221     uint orig_argc = (argc == 0 ? 0 : *argc);
1222     vm_val_t *arg0;
1223     const char *strp;
1224     size_t strl;
1225     vm_val_t filter;
1226     unsigned int hash;
1227 
1228     /* check arguments */
1229     if (get_prop_check_argc(val, argc, &desc))
1230         return TRUE;
1231 
1232     /*
1233      *   make sure the argument is a string, but leave it on the stack for
1234      *   gc protection
1235      */
1236     arg0 = G_stk->get(0);
1237     if ((strp = arg0->get_as_string(vmg0_)) == 0)
1238         err_throw(VMERR_STRING_VAL_REQD);
1239 
1240     /* read and skip the string's length prefix */
1241     strl = vmb_get_len(strp);
1242     strp += VMB_LEN;
1243 
1244     /* if there's a second argument, it's the filter function */
1245     if (orig_argc >= 2)
1246     {
1247         /* retrieve the filter */
1248         filter = *G_stk->get(1);
1249     }
1250     else
1251     {
1252         /* there's no filter */
1253         filter.set_nil();
1254     }
1255 
1256     /* calculate the hash code */
1257     hash = calc_hash(vmg_ arg0, strp, strl);
1258 
1259     /* enumerate matches for the string */
1260     isdef_ctx ctx(vmg_ this, &filter, arg0, strp, strl);
1261     get_ext()->hashtab_->enum_hash_matches(hash, &isdef_cb, &ctx);
1262 
1263     /* if we found any matches, return true; otherwise, return nil */
1264     val->set_logical(ctx.found);
1265 
1266     /* discard arguments */
1267     G_stk->discard(orig_argc);
1268 
1269     /* success */
1270     return TRUE;
1271 }
1272 
1273 /* ------------------------------------------------------------------------ */
1274 /*
1275  *   callback context for forEachWord enumerator
1276  */
1277 struct for_each_word_enum_cb_ctx
1278 {
1279     /* callback function value */
1280     vm_val_t *cb_val;
1281 
1282     /* globals */
1283     vm_globals *vmg;
1284 };
1285 
1286 /*
1287  *   callback for forEachWord enumerator
1288  */
for_each_word_enum_cb(void * ctx0,CVmHashEntry * entry0)1289 static void for_each_word_enum_cb(void *ctx0, CVmHashEntry *entry0)
1290 {
1291     for_each_word_enum_cb_ctx *ctx = (for_each_word_enum_cb_ctx *)ctx0;
1292     CVmHashEntryDict *entry = (CVmHashEntryDict *)entry0;
1293     size_t list_idx;
1294     vm_obj_id_t str_obj;
1295 
1296     /* set up access to the VM globals through my stashed context */
1297     VMGLOB_PTR(ctx->vmg);
1298 
1299     /* create a string object for this entry's word string */
1300     str_obj = CVmObjString::create(vmg_ FALSE,
1301                                    entry->getstr(), entry->getlen());
1302 
1303     /* scan the entry's list of word associations */
1304     for (list_idx = 0 ; ; ++list_idx)
1305     {
1306         vm_dict_entry *p;
1307         size_t i;
1308 
1309         /*
1310          *   Find the list entry at the current list index.  Note that we
1311          *   scan the list on each iteration for the current index because
1312          *   we don't want to keep track of any pointers between iterations;
1313          *   this insulates us from any changes to the underlying list made
1314          *   in the callback.
1315          */
1316         for (i = 0, p = entry->get_head() ; i < list_idx && p != 0 ;
1317              p = p->nxt_, ++i) ;
1318 
1319         /*
1320          *   if we didn't find a list entry at this index, we're done with
1321          *   this hashtable entry
1322          */
1323         if (p == 0)
1324             break;
1325 
1326         /*
1327          *   Invoke the callback on this list entry.  The arguments to the
1328          *   callback are the object, the string, and the vocabulary
1329          *   property of the association:
1330          *
1331          *   func(obj, str, prop)
1332          *
1333          *   Note that the order of the arguments is the same as for
1334          *   addWord() and removeWord().
1335          */
1336 
1337         /* push the vocabulary property of the association */
1338         G_stk->push()->set_propid(p->prop_);
1339 
1340         /* push the string of the entry */
1341         G_stk->push()->set_obj(str_obj);
1342 
1343         /* push the object of the association */
1344         G_stk->push()->set_obj(p->obj_);
1345 
1346         /* invoke the callback */
1347         G_interpreter->call_func_ptr(vmg_ ctx->cb_val, 3,
1348                                      "Dictionary.forEachWord", 0);
1349     }
1350 }
1351 
1352 /*
1353  *   property evaluation - enumerate each word association
1354  */
getp_for_each_word(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)1355 int CVmObjDict::getp_for_each_word(VMG_ vm_obj_id_t self, vm_val_t *retval,
1356                                    uint *argc)
1357 {
1358     static CVmNativeCodeDesc desc(1);
1359     vm_val_t *cb_val;
1360     for_each_word_enum_cb_ctx ctx;
1361 
1362     /* check arguments */
1363     if (get_prop_check_argc(retval, argc, &desc))
1364         return TRUE;
1365 
1366     /* get a pointer to the callback value, but leave it on the stack */
1367     cb_val = G_stk->get(0);
1368 
1369     /* push a self-reference for gc protection while we're working */
1370     G_stk->push()->set_obj(self);
1371 
1372     /*
1373      *   enumerate the entries in our underlying hashtable - use the "safe"
1374      *   enumerator to ensure that we're insulated from any changes to the
1375      *   hashtable that the callback wants to make
1376      */
1377     ctx.vmg = VMGLOB_ADDR;
1378     ctx.cb_val = cb_val;
1379     get_ext()->hashtab_->safe_enum_entries(for_each_word_enum_cb, &ctx);
1380 
1381     /* discard arguments and gc protection */
1382     G_stk->discard(2);
1383 
1384     /* there's no return value */
1385     retval->set_nil();
1386 
1387     /* handled */
1388     return TRUE;
1389 }
1390 
1391 /* ------------------------------------------------------------------------ */
1392 /*
1393  *   Create an undo record
1394  */
alloc_undo_rec(dict_undo_action action,const char * txt,size_t len)1395 dict_undo_rec *CVmObjDict::alloc_undo_rec(dict_undo_action action,
1396                                           const char *txt, size_t len)
1397 {
1398     size_t alloc_size;
1399     dict_undo_rec *rec;
1400 
1401     /*
1402      *   compute the allocation size - start with the structure size, and
1403      *   add in the length we need to store the string if one is present
1404      */
1405     alloc_size = sizeof(dict_undo_rec) - 1;
1406     if (txt != 0)
1407         alloc_size += len;
1408 
1409     /* allocate the record */
1410     rec = (dict_undo_rec *)t3malloc(alloc_size);
1411 
1412     /* set the action */
1413     rec->action = action;
1414 
1415     /* if there's a word, copy the text */
1416     if (txt != 0)
1417         memcpy(rec->txt, txt, len);
1418 
1419     /* save the word length */
1420     rec->len = len;
1421 
1422     /* presume we won't store an object or property */
1423     rec->obj = VM_INVALID_OBJ;
1424     rec->prop = VM_INVALID_PROP;
1425 
1426     /* return the new record */
1427     return rec;
1428 }
1429 
1430 /*
1431  *   add an undo record
1432  */
add_undo_rec(VMG_ vm_obj_id_t self,dict_undo_rec * rec)1433 void CVmObjDict::add_undo_rec(VMG_ vm_obj_id_t self, dict_undo_rec *rec)
1434 {
1435     vm_val_t val;
1436 
1437     /* add the record with an empty value */
1438     val.set_empty();
1439     if (!G_undo->add_new_record_ptr_key(vmg_ self, rec, &val))
1440     {
1441         /*
1442          *   we didn't add an undo record, so our extra undo information
1443          *   isn't actually going to be stored in the undo system - hence
1444          *   we must delete our extra information
1445          */
1446         t3free(rec);
1447     }
1448 }
1449 
1450 /*
1451  *   apply undo
1452  */
apply_undo(VMG_ CVmUndoRecord * undo_rec)1453 void CVmObjDict::apply_undo(VMG_ CVmUndoRecord *undo_rec)
1454 {
1455     if (undo_rec->id.ptrval != 0)
1456     {
1457         dict_undo_rec *rec;
1458 
1459         /* get my undo record */
1460         rec = (dict_undo_rec *)undo_rec->id.ptrval;
1461 
1462         /* take the appropriate action */
1463         switch(rec->action)
1464         {
1465         case DICT_UNDO_ADD:
1466             /* we added the word, so we must now delete it */
1467             del_hash_entry(vmg_ rec->txt, rec->len, rec->obj, rec->prop);
1468             break;
1469 
1470         case DICT_UNDO_DEL:
1471             /* we deleted the word, so now we must add it */
1472             add_hash_entry(vmg_ rec->txt, rec->len, TRUE,
1473                            rec->obj, rec->prop, FALSE);
1474             break;
1475 
1476         case DICT_UNDO_COMPARATOR:
1477             /* we changed the comparator object, so change it back */
1478             set_comparator(vmg_ rec->obj);
1479             break;
1480         }
1481 
1482         /* discard my record */
1483         t3free(rec);
1484 
1485         /* clear the pointer in the undo record so we know it's gone */
1486         undo_rec->id.ptrval = 0;
1487     }
1488 }
1489 
1490 /*
1491  *   discard extra undo information
1492  */
discard_undo(VMG_ CVmUndoRecord * rec)1493 void CVmObjDict::discard_undo(VMG_ CVmUndoRecord *rec)
1494 {
1495     /* delete our extra information record */
1496     if (rec->id.ptrval != 0)
1497     {
1498         /* free the record */
1499         t3free((dict_undo_rec *)rec->id.ptrval);
1500 
1501         /* clear the pointer so we know it's gone */
1502         rec->id.ptrval = 0;
1503     }
1504 }
1505 
1506 /*
1507  *   mark references in an undo record
1508  */
mark_undo_ref(VMG_ CVmUndoRecord * undo_rec)1509 void CVmObjDict::mark_undo_ref(VMG_ CVmUndoRecord *undo_rec)
1510 {
1511     /* check our private record if we have one */
1512     if (undo_rec->id.ptrval != 0)
1513     {
1514         dict_undo_rec *rec;
1515 
1516         /* get my undo record */
1517         rec = (dict_undo_rec *)undo_rec->id.ptrval;
1518 
1519         /* take the appropriate action */
1520         switch(rec->action)
1521         {
1522         case DICT_UNDO_COMPARATOR:
1523             /* the 'obj' entry is the old comparator object */
1524             G_obj_table->mark_all_refs(rec->obj, VMOBJ_REACHABLE);
1525             break;
1526 
1527         case DICT_UNDO_ADD:
1528         case DICT_UNDO_DEL:
1529             /*
1530              *   these actions use only weak references, so there's nothing
1531              *   to do here
1532              */
1533             break;
1534         }
1535     }
1536 }
1537 
1538 /*
1539  *   remove stale weak references from an undo record
1540  */
remove_stale_undo_weak_ref(VMG_ CVmUndoRecord * undo_rec)1541 void CVmObjDict::remove_stale_undo_weak_ref(VMG_ CVmUndoRecord *undo_rec)
1542 {
1543     /* check our private record if we have one */
1544     if (undo_rec->id.ptrval != 0)
1545     {
1546         dict_undo_rec *rec;
1547 
1548         /* get my undo record */
1549         rec = (dict_undo_rec *)undo_rec->id.ptrval;
1550 
1551         /* take the appropriate action */
1552         switch(rec->action)
1553         {
1554         case DICT_UNDO_ADD:
1555         case DICT_UNDO_DEL:
1556             /* check to see if our object is being deleted */
1557             if (rec->obj != VM_INVALID_OBJ
1558                 && G_obj_table->is_obj_deletable(rec->obj))
1559             {
1560                 /*
1561                  *   the object is being deleted - since we only weakly
1562                  *   reference our objects, we must forget about this
1563                  *   object now, which means we can forget this entire
1564                  *   record
1565                  */
1566                 t3free(rec);
1567                 undo_rec->id.ptrval = 0;
1568             }
1569             break;
1570 
1571         case DICT_UNDO_COMPARATOR:
1572             /* this action does not use weak references */
1573             break;
1574         }
1575     }
1576 }
1577 
1578 /* ------------------------------------------------------------------------ */
1579 /*
1580  *   callback context for weak reference function
1581  */
1582 struct enum_hashtab_ctx
1583 {
1584     /* global context pointer */
1585     vm_globals *vmg;
1586 
1587     /* dictionary object */
1588     CVmObjDict *dict;
1589 };
1590 
1591 /*
1592  *   Mark references
1593  */
mark_refs(VMG_ uint state)1594 void CVmObjDict::mark_refs(VMG_ uint state)
1595 {
1596     /* if I have a comparator object, mark it as referenced */
1597     if (get_ext()->comparator_ != VM_INVALID_OBJ)
1598         G_obj_table->mark_all_refs(get_ext()->comparator_, state);
1599 }
1600 
1601 /*
1602  *   Remove weak references that have become stale.  For each object for
1603  *   which we have a weak reference, check to see if the object is marked
1604  *   for deletion; if so, remove our reference to the object.
1605  */
remove_stale_weak_refs(VMG0_)1606 void CVmObjDict::remove_stale_weak_refs(VMG0_)
1607 {
1608     enum_hashtab_ctx ctx;
1609 
1610     /* iterate over our hash table */
1611     ctx.vmg = VMGLOB_ADDR;
1612     ctx.dict = this;
1613     get_ext()->hashtab_->enum_entries(&remove_weak_ref_cb, &ctx);
1614 }
1615 
1616 /*
1617  *   enumeration callback to eliminate stale weak references
1618  */
remove_weak_ref_cb(void * ctx0,CVmHashEntry * entry0)1619 void CVmObjDict::remove_weak_ref_cb(void *ctx0, CVmHashEntry *entry0)
1620 {
1621     enum_hashtab_ctx *ctx = (enum_hashtab_ctx *)ctx0;
1622     CVmHashEntryDict *entry = (CVmHashEntryDict *)entry0;
1623     vm_dict_entry *cur;
1624     vm_dict_entry *nxt;
1625 
1626     /* set up for global access through the context */
1627     VMGLOB_PTR(ctx->vmg);
1628 
1629     /* scan the entry's list */
1630     for (cur = entry->get_head() ; cur != 0 ; cur = nxt)
1631     {
1632         /* remember the next entry in case we delete this one */
1633         nxt = cur->nxt_;
1634 
1635         /* check to see if this object is free - if so, remove this record */
1636         if (G_obj_table->is_obj_deletable(cur->obj_))
1637         {
1638             /* delete the entry */
1639             entry->del_entry(ctx->dict->get_ext()->hashtab_,
1640                              cur->obj_, cur->prop_);
1641         }
1642     }
1643 }
1644 
1645 
1646 /* ------------------------------------------------------------------------ */
1647 /*
1648  *   load from an image file
1649  */
load_from_image(VMG_ vm_obj_id_t self,const char * ptr,size_t siz)1650 void CVmObjDict::load_from_image(VMG_ vm_obj_id_t self,
1651                                  const char *ptr, size_t siz)
1652 {
1653     /* remember where our image data comes from */
1654     get_ext()->image_data_ = ptr;
1655     get_ext()->image_data_size_ = siz;
1656 
1657     /* build the hash table from the image data */
1658     build_hash_from_image(vmg0_);
1659 
1660     /*
1661      *   register for post-load initialization, as we might need to rebuild
1662      *   our hash table once we have access to the comparator object
1663      */
1664     G_obj_table->request_post_load_init(self);
1665 }
1666 
1667 /*
1668  *   Post-load initialization
1669  */
post_load_init(VMG_ vm_obj_id_t self)1670 void CVmObjDict::post_load_init(VMG_ vm_obj_id_t self)
1671 {
1672     vm_obj_id_t comp;
1673 
1674     /*
1675      *   Rebuild the hash table with the comparator, if we have one.  When
1676      *   we load, reset, or restore, we build a tentative hash table with no
1677      *   comparator (i.e., the default exact literal comparator), because we
1678      *   can't assume we have access to the comparator object (as it might
1679      *   be loaded after us).  So, we register for post-load initialization,
1680      *   and then we go back and re-install the comparator for real,
1681      *   rebuilding the hash table with the actual comparator.
1682      */
1683     if ((comp = get_ext()->comparator_) != VM_INVALID_OBJ)
1684     {
1685         /* ensure the comparator object is initialized */
1686         G_obj_table->ensure_post_load_init(vmg_ comp);
1687 
1688         /* set the comparator object type, now that it's loaded */
1689         set_comparator_type(vmg_ comp);
1690 
1691         /*
1692          *   force a rebuild the hash table, so that we build it with the
1693          *   comparator properly installed
1694          */
1695         create_hash_table(vmg0_);
1696     }
1697 }
1698 
1699 /*
1700  *   Build the hash table from the image data
1701  */
build_hash_from_image(VMG0_)1702 void CVmObjDict::build_hash_from_image(VMG0_)
1703 {
1704     uint cnt;
1705     uint i;
1706     const char *p;
1707     const char *endp;
1708     vm_obj_id_t comp;
1709 
1710     /* start reading at the start of the image data */
1711     p = get_ext()->image_data_;
1712     endp = p + get_ext()->image_data_size_;
1713 
1714     /* read the comparator object ID */
1715     comp = (vm_obj_id_t)osrp4(p);
1716     p += 4;
1717 
1718     /*
1719      *   Do NOT install the actual comparator object at this point, but
1720      *   simply build the table tentatively with a nil comparator.  We can't
1721      *   assume that the comparator object has been loaded yet (as it might
1722      *   be loaded after us), so we cannot use it to build the hash table.
1723      *   We have to do something with the data, though, so build the hash
1724      *   table tentatively with no comparator; we'll rebuild it again at
1725      *   post-load-init time with the real comparator.
1726      *
1727      *   (This isn't as inefficient as it sounds, as we retain all of the
1728      *   original hash table entries when we rebuild the table.  All we end
1729      *   up doing is scanning the entries again to recompute their new hash
1730      *   values, and reallocating the hash table object itself.)
1731      */
1732     get_ext()->comparator_ = VM_INVALID_OBJ;
1733     set_comparator_type(vmg_ VM_INVALID_OBJ);
1734 
1735     /* create the new hash table */
1736     create_hash_table(vmg0_);
1737 
1738     /* read the entry count */
1739     cnt = osrp2(p);
1740     p += 2;
1741 
1742     /* scan the entries */
1743     for (i = 0 ; p < endp && i < cnt ; ++i)
1744     {
1745         vm_obj_id_t obj;
1746         vm_prop_id_t prop;
1747         uint key_len;
1748         uint item_cnt;
1749         const char *key;
1750         CVmHashEntryDict *entry;
1751         char key_buf[256];
1752         char *keyp;
1753         size_t key_rem;
1754 
1755         /* read the key length */
1756         key_len = *(unsigned char *)p++;
1757 
1758         /* remember the key pointer */
1759         key = p;
1760         p += key_len;
1761 
1762         /* copy the key to our buffer for decoding */
1763         memcpy(key_buf, key, key_len);
1764 
1765         /* decode the key */
1766         for (keyp = key_buf, key_rem = key_len ; key_rem != 0 ;
1767              --key_rem, ++keyp)
1768             *keyp ^= 0xBD;
1769 
1770         /* read the key from the decoded buffer now */
1771         key = key_buf;
1772 
1773         /* read the item count */
1774         item_cnt = osrp2(p);
1775         p += 2;
1776 
1777         /* find an existing entry for this key */
1778         entry = (CVmHashEntryDict *)get_ext()->hashtab_->find(key, key_len);
1779 
1780         /* if there's no existing entry, add one */
1781         if (entry == 0)
1782         {
1783             /* create a new entry */
1784             entry = new CVmHashEntryDict(key, key_len, TRUE, TRUE);
1785 
1786             /* add it to the table */
1787             get_ext()->hashtab_->add(entry);
1788         }
1789 
1790         /* read the items */
1791         for ( ; item_cnt != 0 ; --item_cnt)
1792         {
1793             /* read the object ID and property ID */
1794             obj = (vm_obj_id_t)osrp4(p);
1795             prop = (vm_prop_id_t)osrp2(p + 4);
1796 
1797             /*
1798              *   add the entry - this entry is from the image file, so
1799              *   mark it as such
1800              */
1801             entry->add_entry(obj, prop, TRUE);
1802 
1803             /* move on to the next item */
1804             p += 6;
1805         }
1806     }
1807 
1808     /*
1809      *   Now that we're done building the table, remember the comparator.  We
1810      *   can't set the type yet, because the object might not be loaded yet -
1811      *   defer this until post-load initialization time.
1812      */
1813     get_ext()->comparator_ = comp;
1814 }
1815 
1816 /* ------------------------------------------------------------------------ */
1817 /*
1818  *   Add an entry
1819  */
add_hash_entry(VMG_ const char * p,size_t len,int copy,vm_obj_id_t obj,vm_prop_id_t prop,int from_image)1820 int CVmObjDict::add_hash_entry(VMG_ const char *p, size_t len, int copy,
1821                                vm_obj_id_t obj, vm_prop_id_t prop,
1822                                int from_image)
1823 {
1824     CVmHashEntryDict *entry;
1825 
1826     /* find an existing entry for this key */
1827     entry = (CVmHashEntryDict *)get_ext()->hashtab_->find(p, len);
1828 
1829     /* if we didn't find an entry, add one */
1830     if (entry == 0)
1831     {
1832         /* create an entry */
1833         entry = new CVmHashEntryDict(p, len, copy, from_image);
1834 
1835         /* add it to the table */
1836         get_ext()->hashtab_->add(entry);
1837     }
1838 
1839     /* add the obj/prop to the entry's item list */
1840     return entry->add_entry(obj, prop, from_image);
1841 }
1842 
1843 /* ------------------------------------------------------------------------ */
1844 /*
1845  *   delete an entry
1846  */
del_hash_entry(VMG_ const char * str,size_t len,vm_obj_id_t obj,vm_prop_id_t voc_prop)1847 int CVmObjDict::del_hash_entry(VMG_ const char *str, size_t len,
1848                                vm_obj_id_t obj, vm_prop_id_t voc_prop)
1849 {
1850     CVmHashEntryDict *entry;
1851 
1852     /* find the hash table entry for the word */
1853     entry = (CVmHashEntryDict *)get_ext()->hashtab_->find(str, len);
1854 
1855     /* if we found it, delete the obj/prop entry */
1856     if (entry != 0)
1857     {
1858         /* delete the entry */
1859         return entry->del_entry(get_ext()->hashtab_, obj, voc_prop);
1860     }
1861 
1862     /* we didn't find anything to delete */
1863     return FALSE;
1864 }
1865 
1866 /* ------------------------------------------------------------------------ */
1867 /*
1868  *   restore to image file state
1869  */
reset_to_image(VMG_ vm_obj_id_t self)1870 void CVmObjDict::reset_to_image(VMG_ vm_obj_id_t self)
1871 {
1872     /* delete the hash table */
1873     if (get_ext()->hashtab_ != 0)
1874     {
1875         /* delete it */
1876         delete get_ext()->hashtab_;
1877 
1878         /* forget it */
1879         get_ext()->hashtab_ = 0;
1880     }
1881 
1882     /* rebuild the hash table from the image file data */
1883     build_hash_from_image(vmg0_);
1884 
1885     /*
1886      *   register for post-load initialization, as we might need to rebuild
1887      *   our hash table once we have access to the comparator object
1888      */
1889     G_obj_table->request_post_load_init(self);
1890 }
1891 
1892 /* ------------------------------------------------------------------------ */
1893 /*
1894  *   determine if the object has been changed since it was loaded
1895  */
is_changed_since_load() const1896 int CVmObjDict::is_changed_since_load() const
1897 {
1898     /*
1899      *   return true if our 'modified' flag is true - if it is, we must have
1900      *   been changed since we were loaded
1901      */
1902     return (get_ext()->modified_ != 0);
1903 }
1904 
1905 /* ------------------------------------------------------------------------ */
1906 /*
1907  *   save-file enumeration context
1908  */
1909 struct save_file_ctx
1910 {
1911     /* file object */
1912     CVmFile *fp;
1913 
1914     /* entry count */
1915     ulong cnt;
1916 
1917     /* globals */
1918     vm_globals *vmg;
1919 };
1920 
1921 /*
1922  *   save to a file
1923  */
save_to_file(VMG_ CVmFile * fp)1924 void CVmObjDict::save_to_file(VMG_ CVmFile *fp)
1925 {
1926     long cnt_pos;
1927     long end_pos;
1928     save_file_ctx ctx;
1929 
1930     /* write the current comparator object */
1931     fp->write_int4(get_ext()->comparator_);
1932 
1933     /* remember the starting seek position and write a placeholder count */
1934     cnt_pos = fp->get_pos();
1935     fp->write_int4(0);
1936 
1937     /* enumerate the entries to write out each one */
1938     ctx.fp = fp;
1939     ctx.cnt = 0;
1940     ctx.vmg = VMGLOB_ADDR;
1941     get_ext()->hashtab_->enum_entries(&save_file_cb, &ctx);
1942 
1943     /* remember our position for a moment */
1944     end_pos = fp->get_pos();
1945 
1946     /* go back and write out the symbol count prefix */
1947     fp->set_pos(cnt_pos);
1948     fp->write_int4(ctx.cnt);
1949 
1950     /* seek back to the end */
1951     fp->set_pos(end_pos);
1952 }
1953 
1954 /*
1955  *   save file enumeration callback
1956  */
save_file_cb(void * ctx0,CVmHashEntry * entry0)1957 void CVmObjDict::save_file_cb(void *ctx0, CVmHashEntry *entry0)
1958 {
1959     save_file_ctx *ctx = (save_file_ctx *)ctx0;
1960     CVmHashEntryDict *entry = (CVmHashEntryDict *)entry0;
1961     vm_dict_entry *cur;
1962     uint cnt;
1963 
1964     /* set up global access */
1965     VMGLOB_PTR(ctx->vmg);
1966 
1967     /* write out this entry's name */
1968     ctx->fp->write_int2(entry->getlen());
1969     ctx->fp->write_bytes(entry->getstr(), entry->getlen());
1970 
1971     /* count the items in this entry's list */
1972     for (cnt = 0, cur = entry->get_head() ; cur != 0 ; cur = cur->nxt_)
1973     {
1974         /*
1975          *   if the referenced object is persistent, count this item;
1976          *   don't count items that refer to non-persistent items, because
1977          *   these objects will not be saved to the file and hence will
1978          *   not be present when the saved state is restored
1979          */
1980         if (G_obj_table->is_obj_persistent(cur->obj_))
1981             ++cnt;
1982     }
1983 
1984     /*
1985      *   if there are no saveable items for this entry, do not write out
1986      *   the entry at all
1987      */
1988     if (cnt == 0)
1989         return;
1990 
1991     /* count this entry */
1992     ++ctx->cnt;
1993 
1994     /* write the item count */
1995     ctx->fp->write_int2(cnt);
1996 
1997     /* write out each item in this entry's list */
1998     for (cur = entry->get_head() ; cur != 0 ; cur = cur->nxt_)
1999     {
2000         /* if this item's object is persistent, write out the item */
2001         if (G_obj_table->is_obj_persistent(cur->obj_))
2002         {
2003             /* write out the object and property for this entry */
2004             ctx->fp->write_int4((long)cur->obj_);
2005             ctx->fp->write_int2((int)cur->prop_);
2006         }
2007     }
2008 }
2009 
2010 /* ------------------------------------------------------------------------ */
2011 /*
2012  *   restore from a file
2013  */
restore_from_file(VMG_ vm_obj_id_t self,CVmFile * fp,CVmObjFixup * fixups)2014 void CVmObjDict::restore_from_file(VMG_ vm_obj_id_t self,
2015                                    CVmFile *fp, CVmObjFixup *fixups)
2016 {
2017     ulong cnt;
2018     vm_obj_id_t comp;
2019 
2020     /* delete the old hash table if we have one */
2021     if (get_ext()->hashtab_ != 0)
2022     {
2023         delete get_ext()->hashtab_;
2024         get_ext()->hashtab_ = 0;
2025     }
2026 
2027     /*
2028      *   Read the comparator and fix it up to the new object numbering
2029      *   scheme, but do not install it (as it might not be loaded yet).
2030      */
2031     comp = fixups->get_new_id(vmg_ (vm_obj_id_t)fp->read_int4());
2032 
2033     /* create the new, empty hash table */
2034     create_hash_table(vmg0_);
2035 
2036     /* read the number of symbols */
2037     cnt = fp->read_int4();
2038 
2039     /* read the symbols */
2040     for ( ; cnt != 0 ; --cnt)
2041     {
2042         uint len;
2043         uint read_len;
2044         char buf[256];
2045         uint item_cnt;
2046 
2047         /* read the symbol length */
2048         len = fp->read_uint2();
2049 
2050         /* limit the reading to our buffer size */
2051         read_len = len;
2052         if (read_len > sizeof(buf))
2053             read_len = sizeof(buf);
2054 
2055         /* read the string */
2056         fp->read_bytes(buf, read_len);
2057 
2058         /* skip any extra data */
2059         if (len > read_len)
2060             fp->set_pos(fp->get_pos() + len - read_len);
2061 
2062         /* read the item count */
2063         item_cnt = fp->read_uint2();
2064 
2065         /* read the items */
2066         for ( ; item_cnt != 0 ; --item_cnt)
2067         {
2068             vm_obj_id_t obj;
2069             vm_prop_id_t prop;
2070 
2071             /* read the object and property ID's */
2072             obj = (vm_obj_id_t)fp->read_int4();
2073             prop = (vm_prop_id_t)fp->read_uint2();
2074 
2075             /* translate the object ID through the fixup table */
2076             obj = fixups->get_new_id(vmg_ obj);
2077 
2078             /* add the entry, if it refers to a valid object */
2079             if (obj != VM_INVALID_OBJ)
2080                 add_hash_entry(vmg_ buf, len, TRUE, obj, prop, FALSE);
2081         }
2082     }
2083 
2084     /*
2085      *   install the comparator (we can't set its type yet, though, because
2086      *   the object might not be loaded yet)
2087      */
2088     get_ext()->comparator_ = comp;
2089     set_comparator_type(vmg_ VM_INVALID_OBJ);
2090 
2091     /*
2092      *   If we had to restore it, we'll have to save it if the current state
2093      *   is later saved, even if we don't modify it again.  The fact that
2094      *   this version was saved before means this version has to be saved
2095      *   from now on.
2096      */
2097     get_ext()->modified_ = TRUE;
2098 
2099     /*
2100      *   register for post-load initialization, so that we can rebuild the
2101      *   hash table with the actual comparator if necessary
2102      */
2103     G_obj_table->request_post_load_init(self);
2104 }
2105 
2106 /* ------------------------------------------------------------------------ */
2107 /*
2108  *   impedence-matcher callback context
2109  */
2110 struct enum_word_props_ctx
2111 {
2112     /* client callback to invoke */
2113     void (*cb_func)(VMG_ void *ctx, vm_prop_id_t prop,
2114                     const vm_val_t *match_val);
2115 
2116     /* string value to match */
2117     const vm_val_t *strval;
2118     const char *strp;
2119     size_t strl;
2120 
2121     /* client callback context */
2122     void *cb_ctx;
2123 
2124     /* globals */
2125     vm_globals *globals;
2126 
2127     /* the dictionary object we're searching */
2128     CVmObjDict *dict;
2129 };
2130 
2131 /*
2132  *   enum_word_props hash table enumeration callback
2133  */
enum_word_props_cb(void * ctx0,CVmHashEntry * entry0)2134 void CVmObjDict::enum_word_props_cb(void *ctx0, CVmHashEntry *entry0)
2135 {
2136     enum_word_props_ctx *ctx = (enum_word_props_ctx *)ctx0;
2137     CVmHashEntryDict *entry = (CVmHashEntryDict *)entry0;
2138     vm_val_t match_val;
2139     VMGLOB_PTR(ctx->globals);
2140 
2141     /* if this entry matches the search string, process it */
2142     if (ctx->dict->match_strings(vmg_ ctx->strval, ctx->strp, ctx->strl,
2143                                  entry->getstr(), entry->getlen(),
2144                                  &match_val))
2145     {
2146         vm_dict_entry *cur;
2147 
2148         /* process the items under this entry */
2149         for (cur = entry->get_head() ; cur != 0 ; cur = cur->nxt_)
2150         {
2151             /* invoke the callback */
2152             (*ctx->cb_func)(vmg_ ctx->cb_ctx, cur->prop_, &match_val);
2153         }
2154     }
2155 }
2156 
2157 /*
2158  *   Enumerate the properties for which a word is defined
2159  */
enum_word_props(VMG_ void (* cb_func)(VMG_ void *,vm_prop_id_t,const vm_val_t *),void * cb_ctx,const vm_val_t * strval,const char * strp,size_t strl)2160 void CVmObjDict::enum_word_props(VMG_
2161                                  void (*cb_func)(VMG_ void *, vm_prop_id_t,
2162                                                  const vm_val_t *),
2163                                  void *cb_ctx, const vm_val_t *strval,
2164                                  const char *strp, size_t strl)
2165 {
2166     enum_word_props_ctx ctx;
2167     unsigned int hash;
2168 
2169     /* set up the enumeration callback context */
2170     ctx.strval = strval;
2171     ctx.strp = strp;
2172     ctx.strl = strl;
2173     ctx.cb_func = cb_func;
2174     ctx.cb_ctx = cb_ctx;
2175     ctx.globals = VMGLOB_ADDR;
2176     ctx.dict = this;
2177 
2178     /* calculate the hash code */
2179     hash = calc_hash(vmg_ strval, strp, strl);
2180 
2181     /* enumerate the matches */
2182     get_ext()->hashtab_->enum_hash_matches(hash, &enum_word_props_cb, &ctx);
2183 }
2184 
2185