cfad47cfa3/tads3/vmdict.cpp

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