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