| | 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 | |