| | 1 | #ifdef RCSID |
| | 2 | static char RCSid[] = |
| | 3 | "$Header: d:/cvsroot/tads/tads3/VMBIFTAD.CPP,v 1.3 1999/07/11 00:46:58 MJRoberts Exp $"; |
| | 4 | #endif |
| | 5 | |
| | 6 | /* |
| | 7 | * Copyright (c) 1999, 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 | vmbiftad.cpp - TADS built-in function set for T3 VM |
| | 15 | Function |
| | 16 | |
| | 17 | Notes |
| | 18 | |
| | 19 | Modified |
| | 20 | 04/05/99 MJRoberts - Creation |
| | 21 | */ |
| | 22 | |
| | 23 | #include <stdio.h> |
| | 24 | #include <string.h> |
| | 25 | #include <time.h> |
| | 26 | |
| | 27 | #include "t3std.h" |
| | 28 | #include "os.h" |
| | 29 | #include "utf8.h" |
| | 30 | #include "vmuni.h" |
| | 31 | #include "vmbiftad.h" |
| | 32 | #include "vmstack.h" |
| | 33 | #include "vmerr.h" |
| | 34 | #include "vmerrnum.h" |
| | 35 | #include "vmglob.h" |
| | 36 | #include "vmpool.h" |
| | 37 | #include "vmobj.h" |
| | 38 | #include "vmstr.h" |
| | 39 | #include "vmlst.h" |
| | 40 | #include "vmrun.h" |
| | 41 | #include "vmregex.h" |
| | 42 | #include "vmundo.h" |
| | 43 | #include "vmfile.h" |
| | 44 | #include "vmsave.h" |
| | 45 | #include "vmbignum.h" |
| | 46 | #include "vmfunc.h" |
| | 47 | #include "vmpat.h" |
| | 48 | #include "vmtobj.h" |
| | 49 | #include "vmvec.h" |
| | 50 | #include "vmpredef.h" |
| | 51 | |
| | 52 | |
| | 53 | /* ------------------------------------------------------------------------ */ |
| | 54 | /* |
| | 55 | * forward statics |
| | 56 | */ |
| | 57 | |
| | 58 | #ifdef VMBIFTADS_RNG_ISAAC |
| | 59 | static void isaac_init(isaacctx *ctx, int flag); |
| | 60 | #endif /* VMBIFTADS_RNG_ISAAC */ |
| | 61 | |
| | 62 | |
| | 63 | /* ------------------------------------------------------------------------ */ |
| | 64 | /* |
| | 65 | * Initialize the TADS intrinsics global state |
| | 66 | */ |
| | 67 | CVmBifTADSGlobals::CVmBifTADSGlobals(VMG0_) |
| | 68 | { |
| | 69 | /* allocate our regular expression parser */ |
| | 70 | rex_parser = new CRegexParser(); |
| | 71 | rex_searcher = new CRegexSearcherSimple(rex_parser); |
| | 72 | |
| | 73 | /* |
| | 74 | * Allocate a global variable to hold the most recent regular |
| | 75 | * expression search string. We need this in a global so that the last |
| | 76 | * search string is always protected from garbage collection; we must |
| | 77 | * keep the string because we might need it to extract a group-match |
| | 78 | * substring. |
| | 79 | */ |
| | 80 | last_rex_str = G_obj_table->create_global_var(); |
| | 81 | |
| | 82 | #ifdef VMBIFTADS_RNG_LCG |
| | 83 | /* |
| | 84 | * Set the random number seed to a fixed starting value (this value |
| | 85 | * is arbitrary; we chose it by throwing dice). If the program |
| | 86 | * wants another sequence, it can manually change this by calling |
| | 87 | * the randomize() intrinsic in our function set, which seeds the |
| | 88 | * generator with an OS-dependent starting value (usually based on |
| | 89 | * the system's real-time clock, to ensure that each run will use a |
| | 90 | * different starting value). |
| | 91 | */ |
| | 92 | rand_seed = 024136543305; |
| | 93 | #endif |
| | 94 | |
| | 95 | #ifdef VMBIFTADS_RNG_ISAAC |
| | 96 | /* create the ISAAC context structure */ |
| | 97 | isaac_ctx = (struct isaacctx *)t3malloc(sizeof(struct isaacctx)); |
| | 98 | |
| | 99 | /* initialize with a fixed seed vector */ |
| | 100 | isaac_init(isaac_ctx, FALSE); |
| | 101 | #endif |
| | 102 | } |
| | 103 | |
| | 104 | /* |
| | 105 | * delete the TADS intrinsics global state |
| | 106 | */ |
| | 107 | CVmBifTADSGlobals::~CVmBifTADSGlobals() |
| | 108 | { |
| | 109 | /* delete our regular expression searcher and parser */ |
| | 110 | delete rex_searcher; |
| | 111 | delete rex_parser; |
| | 112 | |
| | 113 | /* |
| | 114 | * note that we leave our last_rex_str global variable undeleted here, |
| | 115 | * as we don't have access to G_obj_table (as there's no VMG_ to a |
| | 116 | * destructor); this is okay, since the object table will take care of |
| | 117 | * deleting the variable for us when the object table itself is deleted |
| | 118 | */ |
| | 119 | |
| | 120 | #ifdef VMBIFTADS_RNG_ISAAC |
| | 121 | /* delete the ISAAC context */ |
| | 122 | t3free(isaac_ctx); |
| | 123 | #endif |
| | 124 | } |
| | 125 | |
| | 126 | /* ------------------------------------------------------------------------ */ |
| | 127 | /* |
| | 128 | * datatype - get the datatype of a given value |
| | 129 | */ |
| | 130 | void CVmBifTADS::datatype(VMG_ uint argc) |
| | 131 | { |
| | 132 | vm_val_t val; |
| | 133 | vm_val_t retval; |
| | 134 | |
| | 135 | /* check arguments */ |
| | 136 | check_argc(vmg_ argc, 1); |
| | 137 | |
| | 138 | /* pop the value */ |
| | 139 | G_stk->pop(&val); |
| | 140 | |
| | 141 | /* return the appropriate value for this type */ |
| | 142 | retval.set_datatype(vmg_ &val); |
| | 143 | retval_int(vmg_ retval.val.intval); |
| | 144 | } |
| | 145 | |
| | 146 | /* ------------------------------------------------------------------------ */ |
| | 147 | /* |
| | 148 | * getarg - get the given argument to the current procedure |
| | 149 | */ |
| | 150 | void CVmBifTADS::getarg(VMG_ uint argc) |
| | 151 | { |
| | 152 | int idx; |
| | 153 | |
| | 154 | /* check arguments */ |
| | 155 | check_argc(vmg_ argc, 1); |
| | 156 | |
| | 157 | /* get the argument index value */ |
| | 158 | idx = pop_int_val(vmg0_); |
| | 159 | |
| | 160 | /* if the argument index is out of range, throw an error */ |
| | 161 | if (idx < 1 || idx > G_interpreter->get_cur_argc(vmg0_)) |
| | 162 | err_throw(VMERR_BAD_VAL_BIF); |
| | 163 | |
| | 164 | /* push the parameter value */ |
| | 165 | *G_interpreter->get_r0() = *G_interpreter->get_param(vmg_ idx - 1); |
| | 166 | } |
| | 167 | |
| | 168 | /* ------------------------------------------------------------------------ */ |
| | 169 | /* |
| | 170 | * firstobj - get the first object instance |
| | 171 | */ |
| | 172 | void CVmBifTADS::firstobj(VMG_ uint argc) |
| | 173 | { |
| | 174 | /* check arguments */ |
| | 175 | check_argc_range(vmg_ argc, 0, 2); |
| | 176 | |
| | 177 | /* enumerate objects starting with object 1 in the master object table */ |
| | 178 | enum_objects(vmg_ argc, (vm_obj_id_t)1); |
| | 179 | } |
| | 180 | |
| | 181 | /* |
| | 182 | * nextobj - get the next object instance after a given object |
| | 183 | */ |
| | 184 | void CVmBifTADS::nextobj(VMG_ uint argc) |
| | 185 | { |
| | 186 | vm_val_t val; |
| | 187 | vm_obj_id_t prv_obj; |
| | 188 | |
| | 189 | /* check arguments */ |
| | 190 | check_argc_range(vmg_ argc, 1, 3); |
| | 191 | |
| | 192 | /* get the previous object */ |
| | 193 | G_interpreter->pop_obj(vmg_ &val); |
| | 194 | prv_obj = val.val.obj; |
| | 195 | |
| | 196 | /* |
| | 197 | * Enumerate objects starting with the next object in the master |
| | 198 | * object table after the given object. Reduce the argument count by |
| | 199 | * one, since we've removed the preceding object. |
| | 200 | */ |
| | 201 | enum_objects(vmg_ argc - 1, prv_obj + 1); |
| | 202 | } |
| | 203 | |
| | 204 | /* enum_objects flags */ |
| | 205 | #define VMBIFTADS_ENUM_INSTANCES 0x0001 |
| | 206 | #define VMBIFTADS_ENUM_CLASSES 0x0002 |
| | 207 | |
| | 208 | /* |
| | 209 | * Common handler for firstobj/nextobj object iteration |
| | 210 | */ |
| | 211 | void CVmBifTADS::enum_objects(VMG_ uint argc, vm_obj_id_t start_obj) |
| | 212 | { |
| | 213 | vm_val_t val; |
| | 214 | vm_obj_id_t sc; |
| | 215 | vm_obj_id_t obj; |
| | 216 | unsigned long flags; |
| | 217 | |
| | 218 | /* presume no superclass filter will be specified */ |
| | 219 | sc = VM_INVALID_OBJ; |
| | 220 | |
| | 221 | /* presume we're enumerating instances only */ |
| | 222 | flags = VMBIFTADS_ENUM_INSTANCES; |
| | 223 | |
| | 224 | /* |
| | 225 | * check arguments - we can optionally have two more arguments: a |
| | 226 | * superclass whose instances/subclasses we are to enumerate, and an |
| | 227 | * integer giving flag bits |
| | 228 | */ |
| | 229 | if (argc == 2) |
| | 230 | { |
| | 231 | /* pop the object */ |
| | 232 | G_interpreter->pop_obj(vmg_ &val); |
| | 233 | sc = val.val.obj; |
| | 234 | |
| | 235 | /* pop the flags */ |
| | 236 | flags = pop_long_val(vmg0_); |
| | 237 | } |
| | 238 | else if (argc == 1) |
| | 239 | { |
| | 240 | /* check to see if it's an object or the flags integer */ |
| | 241 | switch (G_stk->get(0)->typ) |
| | 242 | { |
| | 243 | case VM_INT: |
| | 244 | /* it's the flags */ |
| | 245 | flags = pop_long_val(vmg0_); |
| | 246 | break; |
| | 247 | |
| | 248 | case VM_OBJ: |
| | 249 | /* it's the superclass filter */ |
| | 250 | G_interpreter->pop_obj(vmg_ &val); |
| | 251 | sc = val.val.obj; |
| | 252 | break; |
| | 253 | |
| | 254 | default: |
| | 255 | /* invalid argument type */ |
| | 256 | err_throw(VMERR_BAD_TYPE_BIF); |
| | 257 | } |
| | 258 | } |
| | 259 | |
| | 260 | /* presume we won't find anything */ |
| | 261 | retval_nil(vmg0_); |
| | 262 | |
| | 263 | /* |
| | 264 | * starting with the given object, scan objects until we find one |
| | 265 | * that's valid and matches our superclass, if one was provided |
| | 266 | */ |
| | 267 | for (obj = start_obj ; obj < G_obj_table->get_max_used_obj_id() ; ++obj) |
| | 268 | { |
| | 269 | /* |
| | 270 | * If it's valid, and it's not an intrinsic class modifier object, |
| | 271 | * consider it further. Skip intrinsic class modifiers, since |
| | 272 | * they're not really separate objects; they're really part of the |
| | 273 | * intrinsic class they modify, and all of the properties and |
| | 274 | * methods of a modifier object are reachable through the base |
| | 275 | * intrinsic class. |
| | 276 | */ |
| | 277 | if (G_obj_table->is_obj_id_valid(obj) |
| | 278 | && !CVmObjIntClsMod::is_intcls_mod_obj(vmg_ obj)) |
| | 279 | { |
| | 280 | /* |
| | 281 | * if it's a class, skip it if the flags indicate classes are |
| | 282 | * not wanted; if it's an instance, skip it if the flags |
| | 283 | * indicate that instances are not wanted |
| | 284 | */ |
| | 285 | if (vm_objp(vmg_ obj)->is_class_object(vmg_ obj)) |
| | 286 | { |
| | 287 | /* it's a class - skip it if classes are not wanted */ |
| | 288 | if ((flags & VMBIFTADS_ENUM_CLASSES) == 0) |
| | 289 | continue; |
| | 290 | } |
| | 291 | else |
| | 292 | { |
| | 293 | /* it's an instance - skip it if instances are not wanted */ |
| | 294 | if ((flags & VMBIFTADS_ENUM_INSTANCES) == 0) |
| | 295 | continue; |
| | 296 | } |
| | 297 | |
| | 298 | /* |
| | 299 | * if a superclass was specified, and it matches, we have a |
| | 300 | * winner |
| | 301 | */ |
| | 302 | if (sc != VM_INVALID_OBJ) |
| | 303 | { |
| | 304 | /* if the object matches, return it */ |
| | 305 | if (vm_objp(vmg_ obj)->is_instance_of(vmg_ sc)) |
| | 306 | { |
| | 307 | retval_obj(vmg_ obj); |
| | 308 | break; |
| | 309 | } |
| | 310 | } |
| | 311 | else |
| | 312 | { |
| | 313 | /* |
| | 314 | * We're enumerating all objects - but skip List and String |
| | 315 | * object, as we expose these are special types. |
| | 316 | */ |
| | 317 | if (vm_objp(vmg_ obj)->get_as_list() == 0 |
| | 318 | && vm_objp(vmg_ obj)->get_as_string(vmg0_) == 0) |
| | 319 | { |
| | 320 | retval_obj(vmg_ obj); |
| | 321 | break; |
| | 322 | } |
| | 323 | } |
| | 324 | } |
| | 325 | } |
| | 326 | } |
| | 327 | |
| | 328 | /* ------------------------------------------------------------------------ */ |
| | 329 | /* |
| | 330 | * Random number generators. Define one of the following configuration |
| | 331 | * variables to select a random number generation algorithm: |
| | 332 | * |
| | 333 | * VMBIFTADS_RNG_LCG - linear congruential generator |
| | 334 | *. VMBIFTADS_RNG_ISAAC - ISAAC (cryptographic hash generator) |
| | 335 | */ |
| | 336 | |
| | 337 | /* ------------------------------------------------------------------------ */ |
| | 338 | /* |
| | 339 | * Linear Congruential Random-Number Generator. This generator uses an |
| | 340 | * algorithm from Knuth, The Art of Computer Programming, Volume 2, p. |
| | 341 | * 170, with parameters chosen from the same book for their good |
| | 342 | * statistical properties and efficiency on 32-bit hardware. |
| | 343 | */ |
| | 344 | #ifdef VMBIFTADS_RNG_LCG |
| | 345 | /* |
| | 346 | * randomize - seed the random-number generator |
| | 347 | */ |
| | 348 | void CVmBifTADS::randomize(VMG_ uint argc) |
| | 349 | { |
| | 350 | /* check arguments */ |
| | 351 | check_argc(vmg_ argc, 0); |
| | 352 | |
| | 353 | /* seed the generator */ |
| | 354 | os_rand(&G_bif_tads_globals->rand_seed); |
| | 355 | } |
| | 356 | |
| | 357 | /* |
| | 358 | * generate the next random number - linear congruential generator |
| | 359 | */ |
| | 360 | static ulong rng_next(VMG0_) |
| | 361 | { |
| | 362 | const ulong a = 1664525L; |
| | 363 | const ulong c = 1; |
| | 364 | |
| | 365 | /* |
| | 366 | * Generate the next random value using the linear congruential |
| | 367 | * method described in Knuth, The Art of Computer Programming, |
| | 368 | * volume 2, p170. |
| | 369 | * |
| | 370 | * Use 2^32 as m, hence (n mod m) == (n & 0xFFFFFFFF). This is |
| | 371 | * efficient and is well-suited to 32-bit machines, works fine on |
| | 372 | * larger machines, and will even work on 16-bit machines as long as |
| | 373 | * the compiler can provide us with 32-bit arithmetic (which we |
| | 374 | * assume extensively elsewhere anyway). |
| | 375 | * |
| | 376 | * We use a = 1664525, a multiplier which has very good results with |
| | 377 | * the Spectral Test (see Knuth p102) with our choice of m. |
| | 378 | * |
| | 379 | * Use c = 1, since this trivially satisfies Knuth's requirements |
| | 380 | * about common factors. |
| | 381 | * |
| | 382 | * Note that the result of the multiplication might overflow a |
| | 383 | * 32-bit ulong for values of rand_seed that are not small. This |
| | 384 | * doesn't matter, since if it does, the machine will naturally |
| | 385 | * truncate high-order bits to yield the result mod 2^32. So, on a |
| | 386 | * 32-bit machine, the (&0xFFFFFFFF) part is superfluous, but it's |
| | 387 | * harmless and is needed for machines with a larger word size. |
| | 388 | */ |
| | 389 | G_bif_tads_globals->rand_seed = |
| | 390 | (long)(((a * (ulong)G_bif_tads_globals->rand_seed) + 1) & 0xFFFFFFFF); |
| | 391 | return (ulong)G_bif_tads_globals->rand_seed; |
| | 392 | } |
| | 393 | #endif /* VMBIFTADS_RNG_LCG */ |
| | 394 | |
| | 395 | /* ------------------------------------------------------------------------ */ |
| | 396 | /* |
| | 397 | * ISAAC random number generator. |
| | 398 | */ |
| | 399 | |
| | 400 | #ifdef VMBIFTADS_RNG_ISAAC |
| | 401 | |
| | 402 | /* service macros for ISAAC random number generator */ |
| | 403 | #define isaac_ind(mm,x) ((mm)[(x>>2)&(ISAAC_RANDSIZ-1)]) |
| | 404 | #define isaac_step(mix,a,b,mm,m,m2,r,x) \ |
| | 405 | { \ |
| | 406 | x = *m; \ |
| | 407 | a = ((a^(mix)) + *(m2++)) & 0xffffffff; \ |
| | 408 | *(m++) = y = (isaac_ind(mm,x) + a + b) & 0xffffffff; \ |
| | 409 | *(r++) = b = (isaac_ind(mm,y>>ISAAC_RANDSIZL) + x) & 0xffffffff; \ |
| | 410 | } |
| | 411 | #define isaac_rand(r) \ |
| | 412 | ((r)->cnt-- == 0 ? \ |
| | 413 | (isaac_gen_group(r), (r)->cnt=ISAAC_RANDSIZ-1, (r)->rsl[(r)->cnt]) : \ |
| | 414 | (r)->rsl[(r)->cnt]) |
| | 415 | |
| | 416 | #define isaac_mix(a,b,c,d,e,f,g,h) \ |
| | 417 | { \ |
| | 418 | a^=b<<11; d+=a; b+=c; \ |
| | 419 | b^=c>>2; e+=b; c+=d; \ |
| | 420 | c^=d<<8; f+=c; d+=e; \ |
| | 421 | d^=e>>16; g+=d; e+=f; \ |
| | 422 | e^=f<<10; h+=e; f+=g; \ |
| | 423 | f^=g>>4; a+=f; g+=h; \ |
| | 424 | g^=h<<8; b+=g; h+=a; \ |
| | 425 | h^=a>>9; c+=h; a+=b; \ |
| | 426 | } |
| | 427 | |
| | 428 | /* generate the group of numbers */ |
| | 429 | static void isaac_gen_group(isaacctx *ctx) |
| | 430 | { |
| | 431 | ulong a; |
| | 432 | ulong b; |
| | 433 | ulong x; |
| | 434 | ulong y; |
| | 435 | ulong *m; |
| | 436 | ulong *mm; |
| | 437 | ulong *m2; |
| | 438 | ulong *r; |
| | 439 | ulong *mend; |
| | 440 | |
| | 441 | mm = ctx->mem; |
| | 442 | r = ctx->rsl; |
| | 443 | a = ctx->a; |
| | 444 | b = (ctx->b + (++ctx->c)) & 0xffffffff; |
| | 445 | for (m = mm, mend = m2 = m + (ISAAC_RANDSIZ/2) ; m<mend ; ) |
| | 446 | { |
| | 447 | isaac_step(a<<13, a, b, mm, m, m2, r, x); |
| | 448 | isaac_step(a>>6, a, b, mm, m, m2, r, x); |
| | 449 | isaac_step(a<<2, a, b, mm, m, m2, r, x); |
| | 450 | isaac_step(a>>16, a, b, mm, m, m2, r, x); |
| | 451 | } |
| | 452 | for (m2 = mm; m2<mend; ) |
| | 453 | { |
| | 454 | isaac_step(a<<13, a, b, mm, m, m2, r, x); |
| | 455 | isaac_step(a>>6, a, b, mm, m, m2, r, x); |
| | 456 | isaac_step(a<<2, a, b, mm, m, m2, r, x); |
| | 457 | isaac_step(a>>16, a, b, mm, m, m2, r, x); |
| | 458 | } |
| | 459 | ctx->b = b; |
| | 460 | ctx->a = a; |
| | 461 | } |
| | 462 | |
| | 463 | /* |
| | 464 | * Initialize. If flag is true, then use the contents of ctx->rsl[] to |
| | 465 | * initialize ctx->mm[]; otherwise, we'll use a fixed starting |
| | 466 | * configuration. |
| | 467 | */ |
| | 468 | static void isaac_init(isaacctx *ctx, int flag) |
| | 469 | { |
| | 470 | int i; |
| | 471 | ulong a; |
| | 472 | ulong b; |
| | 473 | ulong c; |
| | 474 | ulong d; |
| | 475 | ulong e; |
| | 476 | ulong f; |
| | 477 | ulong g; |
| | 478 | ulong h; |
| | 479 | ulong *m; |
| | 480 | ulong *r; |
| | 481 | |
| | 482 | ctx->a = ctx->b = ctx->c = 0; |
| | 483 | m = ctx->mem; |
| | 484 | r = ctx->rsl; |
| | 485 | a = b = c = d = e = f = g = h = 0x9e3779b9; /* the golden ratio */ |
| | 486 | |
| | 487 | /* scramble the initial settings */ |
| | 488 | for (i = 0 ; i < 4 ; ++i) |
| | 489 | { |
| | 490 | isaac_mix(a, b, c, d, e, f, g, h); |
| | 491 | } |
| | 492 | |
| | 493 | if (flag) |
| | 494 | { |
| | 495 | /* initialize using the contents of ctx->rsl[] as the seed */ |
| | 496 | for (i = 0 ; i < ISAAC_RANDSIZ ; i += 8) |
| | 497 | { |
| | 498 | a += r[i]; b += r[i+1]; c += r[i+2]; d += r[i+3]; |
| | 499 | e += r[i+4]; f += r[i+5]; g += r[i+6]; h += r[i+7]; |
| | 500 | isaac_mix(a, b, c, d, e, f, g, h); |
| | 501 | m[i] = a; m[i+1] = b; m[i+2] = c; m[i+3] = d; |
| | 502 | m[i+4] = e; m[i+5] = f; m[i+6] = g; m[i+7] = h; |
| | 503 | } |
| | 504 | |
| | 505 | /* do a second pass to make all of the seed affect all of m */ |
| | 506 | for (i = 0 ; i < ISAAC_RANDSIZ ; i += 8) |
| | 507 | { |
| | 508 | a += m[i]; b += m[i+1]; c += m[i+2]; d += m[i+3]; |
| | 509 | e += m[i+4]; f += m[i+5]; g += m[i+6]; h += m[i+7]; |
| | 510 | isaac_mix(a, b, c, d, e, f, g, h); |
| | 511 | m[i] = a; m[i+1] = b; m[i+2] = c; m[i+3] = d; |
| | 512 | m[i+4] = e; m[i+5] = f; m[i+6] = g; m[i+7] = h; |
| | 513 | } |
| | 514 | } |
| | 515 | else |
| | 516 | { |
| | 517 | /* initialize using fixed initial settings */ |
| | 518 | for (i = 0 ; i < ISAAC_RANDSIZ ; i += 8) |
| | 519 | { |
| | 520 | isaac_mix(a, b, c, d, e, f, g, h); |
| | 521 | m[i] = a; m[i+1] = b; m[i+2] = c; m[i+3] = d; |
| | 522 | m[i+4] = e; m[i+5] = f; m[i+6] = g; m[i+7] = h; |
| | 523 | } |
| | 524 | } |
| | 525 | |
| | 526 | /* fill in the first set of results */ |
| | 527 | isaac_gen_group(ctx); |
| | 528 | |
| | 529 | /* prepare to use the first set of results */ |
| | 530 | ctx->cnt = ISAAC_RANDSIZ; |
| | 531 | } |
| | 532 | |
| | 533 | /* |
| | 534 | * seed the rng |
| | 535 | */ |
| | 536 | void CVmBifTADS::randomize(VMG_ uint argc) |
| | 537 | { |
| | 538 | int i; |
| | 539 | long seed; |
| | 540 | |
| | 541 | /* check arguments */ |
| | 542 | check_argc(vmg_ argc, 0); |
| | 543 | |
| | 544 | /* seed the generator */ |
| | 545 | os_rand(&seed); |
| | 546 | |
| | 547 | /* |
| | 548 | * Fill in rsl[] with the seed. It doesn't do a lot of good to call |
| | 549 | * os_rand() repeatedly, since this function might simply return the |
| | 550 | * real-time clock value. So, use the os_rand() seed value as the |
| | 551 | * first rsl[] value, then use a simple linear congruential |
| | 552 | * generator to fill in the rest of rsl[]. |
| | 553 | */ |
| | 554 | for (i = 0 ; i < ISAAC_RANDSIZ ; ++i) |
| | 555 | { |
| | 556 | const ulong a = 1664525L; |
| | 557 | |
| | 558 | /* fill in this value from the previous seed value */ |
| | 559 | G_bif_tads_globals->isaac_ctx->rsl[i] = (ulong)seed; |
| | 560 | |
| | 561 | /* generate the next lcg value */ |
| | 562 | seed = (long)(((a * (ulong)seed) + 1) & 0xFFFFFFFF); |
| | 563 | } |
| | 564 | |
| | 565 | /* initialize with this rsl[] array */ |
| | 566 | isaac_init(G_bif_tads_globals->isaac_ctx, TRUE); |
| | 567 | } |
| | 568 | |
| | 569 | |
| | 570 | /* |
| | 571 | * generate the next random number - ISAAC (by Bob Jenkins, |
| | 572 | * http://ourworld.compuserve.com/homepages/bob_jenkins/isaacafa.htm) |
| | 573 | */ |
| | 574 | static ulong rng_next(VMG0_) |
| | 575 | { |
| | 576 | /* return the next number */ |
| | 577 | return isaac_rand(G_bif_tads_globals->isaac_ctx); |
| | 578 | } |
| | 579 | #endif /* VMBIFTADS_RNG_ISAAC */ |
| | 580 | |
| | 581 | /* ------------------------------------------------------------------------ */ |
| | 582 | /* |
| | 583 | * rand - generate a random number, or choose an element randomly from a |
| | 584 | * list of values or from our list of arguments. |
| | 585 | * |
| | 586 | * With one integer argument N, we choose a random number from 0 to N-1. |
| | 587 | * |
| | 588 | * With one list argument, we choose a random element of the list. |
| | 589 | * |
| | 590 | * With multiple arguments, we choose one argument at random and return |
| | 591 | * its value. Note that, because this is an ordinary built-in function, |
| | 592 | * all of our arguments will be fully evaluated. |
| | 593 | */ |
| | 594 | void CVmBifTADS::rand(VMG_ uint argc) |
| | 595 | { |
| | 596 | ulong range; |
| | 597 | int use_range; |
| | 598 | int choose_an_arg; |
| | 599 | const char *listp; |
| | 600 | ulong rand_val; |
| | 601 | CVmObjVector *vec = 0; |
| | 602 | vm_obj_id_t vecid = VM_INVALID_OBJ; |
| | 603 | |
| | 604 | /* presume we're not going to choose from our arguments or from a list */ |
| | 605 | choose_an_arg = FALSE; |
| | 606 | listp = 0; |
| | 607 | |
| | 608 | /* determine the desired range of values based on the arguments */ |
| | 609 | if (argc == 0) |
| | 610 | { |
| | 611 | /* |
| | 612 | * if no argument is given, produce a random number in our full |
| | 613 | * range - clear the 'use_range' flag to so indicate |
| | 614 | */ |
| | 615 | use_range = FALSE; |
| | 616 | } |
| | 617 | else if (argc == 1 && G_stk->get(0)->typ == VM_INT) |
| | 618 | { |
| | 619 | /* we're returning a number in the range 0..(arg-1) */ |
| | 620 | range = G_stk->get(0)->val.intval; |
| | 621 | use_range = TRUE; |
| | 622 | |
| | 623 | /* discard the argument */ |
| | 624 | G_stk->discard(); |
| | 625 | } |
| | 626 | else if (argc == 1) |
| | 627 | { |
| | 628 | /* check for a vector or a list */ |
| | 629 | if (G_stk->get(0)->typ == VM_OBJ |
| | 630 | && CVmObjVector::is_vector_obj(vmg_ G_stk->get(0)->val.obj)) |
| | 631 | { |
| | 632 | /* |
| | 633 | * it's a vector - get the object pointer, but leave it on the |
| | 634 | * stack for GC protection for now |
| | 635 | */ |
| | 636 | vecid = G_stk->get(0)->val.obj; |
| | 637 | vec = (CVmObjVector *)vm_objp(vmg_ vecid); |
| | 638 | |
| | 639 | /* the range is 0..(vector_length-1) */ |
| | 640 | range = vec->get_element_count(); |
| | 641 | use_range = TRUE; |
| | 642 | } |
| | 643 | else |
| | 644 | { |
| | 645 | /* it must be a list - pop the list value */ |
| | 646 | listp = pop_list_val(vmg0_); |
| | 647 | |
| | 648 | /* our range is 0..(list_element_count-1) */ |
| | 649 | range = vmb_get_len(listp); |
| | 650 | use_range = TRUE; |
| | 651 | } |
| | 652 | } |
| | 653 | else |
| | 654 | { |
| | 655 | /* |
| | 656 | * produce a random number in the range 0..(argc-1) so that we |
| | 657 | * can select one of our arguments |
| | 658 | */ |
| | 659 | range = argc; |
| | 660 | use_range = TRUE; |
| | 661 | |
| | 662 | /* note that we should choose an argument value */ |
| | 663 | choose_an_arg = TRUE; |
| | 664 | } |
| | 665 | |
| | 666 | /* get the next random number */ |
| | 667 | rand_val = rng_next(vmg0_); |
| | 668 | |
| | 669 | /* |
| | 670 | * Calculate our random value in the range 0..(range-1). If range |
| | 671 | * == 0, simply choose a value across our full range. |
| | 672 | */ |
| | 673 | if (use_range) |
| | 674 | { |
| | 675 | unsigned long hi; |
| | 676 | unsigned long lo; |
| | 677 | |
| | 678 | /* |
| | 679 | * A range was specified, so choose in our range. As Knuth |
| | 680 | * suggests, don't simply take the low-order bits from the value, |
| | 681 | * since these are the least random part. Instead, use the method |
| | 682 | * Knuth describes in TAOCP Vol 2 section 3.4.2. |
| | 683 | * |
| | 684 | * Avoid floating point arithmetic - use an integer calculation |
| | 685 | * instead. This code performs a 64-bit fixed-point calculation |
| | 686 | * using 32-bit values. |
| | 687 | * |
| | 688 | * The calculation we're really performing is this: |
| | 689 | * |
| | 690 | * rand_val = (ulong)((((double)rand_val) / 4294967296.0) |
| | 691 | *. * (double)range); |
| | 692 | */ |
| | 693 | |
| | 694 | /* calculate the high-order 32 bits of (rand_val / 2^32 * range) */ |
| | 695 | hi = (((rand_val >> 16) & 0xffff) * ((range >> 16) & 0xffff)) |
| | 696 | + ((((rand_val >> 16) & 0xffff) * (range & 0xffff)) >> 16) |
| | 697 | + (((rand_val & 0xffff) * ((range >> 16) & 0xffff)) >> 16); |
| | 698 | |
| | 699 | /* calculate the low-order 32 bits */ |
| | 700 | lo = ((((rand_val >> 16) & 0xffff) * (range & 0xffff)) & 0xffff) |
| | 701 | + (((rand_val & 0xffff) * ((range >> 16) & 0xffff)) & 0xffff) |
| | 702 | + ((((rand_val & 0xffff) * (range & 0xffff)) >> 16) & 0xffff); |
| | 703 | |
| | 704 | /* |
| | 705 | * add the carry from the low part into the high part to get the |
| | 706 | * result |
| | 707 | */ |
| | 708 | rand_val = hi + (lo >> 16); |
| | 709 | } |
| | 710 | |
| | 711 | /* |
| | 712 | * Return the appropriate value, depending on our argument list |
| | 713 | */ |
| | 714 | if (choose_an_arg) |
| | 715 | { |
| | 716 | /* return the selected argument */ |
| | 717 | retval(vmg_ G_stk->get((int)rand_val)); |
| | 718 | |
| | 719 | /* discard all of the arguments */ |
| | 720 | G_stk->discard(argc); |
| | 721 | } |
| | 722 | else if (vec != 0) |
| | 723 | { |
| | 724 | vm_val_t val; |
| | 725 | |
| | 726 | /* get the selected element */ |
| | 727 | if (range == 0) |
| | 728 | { |
| | 729 | /* there are no elements to choose from, so return nil */ |
| | 730 | val.set_nil(); |
| | 731 | } |
| | 732 | else |
| | 733 | { |
| | 734 | vm_val_t idxval; |
| | 735 | |
| | 736 | /* get the selected vector element */ |
| | 737 | idxval.set_int(rand_val + 1); |
| | 738 | vec->index_val(vmg_ &val, vecid, &idxval); |
| | 739 | } |
| | 740 | |
| | 741 | /* set the result */ |
| | 742 | retval(vmg_ &val); |
| | 743 | |
| | 744 | /* discard our gc protection */ |
| | 745 | G_stk->discard(); |
| | 746 | } |
| | 747 | else if (listp != 0) |
| | 748 | { |
| | 749 | vm_val_t val; |
| | 750 | |
| | 751 | /* as a special case, if the list has zero elements, return nil */ |
| | 752 | if (vmb_get_len(listp) == 0) |
| | 753 | { |
| | 754 | /* there are no elements to choose from, so return nil */ |
| | 755 | val.set_nil(); |
| | 756 | } |
| | 757 | else |
| | 758 | { |
| | 759 | /* get the selected list element */ |
| | 760 | vmb_get_dh(listp + VMB_LEN |
| | 761 | + (size_t)((rand_val * VMB_DATAHOLDER)), &val); |
| | 762 | } |
| | 763 | |
| | 764 | /* set the result */ |
| | 765 | retval(vmg_ &val); |
| | 766 | } |
| | 767 | else |
| | 768 | { |
| | 769 | /* simply return the random number */ |
| | 770 | retval_int(vmg_ (long)rand_val); |
| | 771 | } |
| | 772 | } |
| | 773 | |
| | 774 | /* ------------------------------------------------------------------------ */ |
| | 775 | /* |
| | 776 | * Bit-shift generator. This is from Knuth, The Art of Computer |
| | 777 | * Programming, volume 2. This generator is designed to produce random |
| | 778 | * strings of bits and is not suitable for use as a general-purpose RNG. |
| | 779 | * |
| | 780 | * Linear congruential generators are not ideal for generating random |
| | 781 | * bits; their statistical properties seem better suited for generating |
| | 782 | * values over a wider range. This generator is specially designed to |
| | 783 | * produce random bits, so it could be a useful complement to an LCG RNG. |
| | 784 | * |
| | 785 | * This code should not be enabled in its present state; it's retained |
| | 786 | * in case we want in the future to implement a generator exclusively |
| | 787 | * for random bits. The ISAAC generator seems to be a good source of |
| | 788 | * random bits as well as random numbers, so it seems unlikely that |
| | 789 | * we'll need a separate random bit generator. |
| | 790 | */ |
| | 791 | |
| | 792 | #ifdef VMBIFTADS_RNG_BITSHIFT |
| | 793 | void CVmBifTADS::randbit(VMG_ uint argc) |
| | 794 | { |
| | 795 | int top_bit; |
| | 796 | |
| | 797 | /* check arguments */ |
| | 798 | check_argc(vmg_ argc, 0); |
| | 799 | |
| | 800 | top_bit = (G_bif_tads_globals->rand_seed & 0x8000000); |
| | 801 | G_bif_tads_globals->rand_seed <<= 1; |
| | 802 | if (top_bit) |
| | 803 | G_bif_tads_globals->rand_seed ^= 035604231625; |
| | 804 | |
| | 805 | retval_int(vmg_ (long)(G_bif_tads_globals->rand_seed & 1)); |
| | 806 | } |
| | 807 | #endif /* VMBIFTADS_RNG_BITSHIFT */ |
| | 808 | |
| | 809 | |
| | 810 | /* ------------------------------------------------------------------------ */ |
| | 811 | /* |
| | 812 | * cvtstr (toString) - convert to string |
| | 813 | */ |
| | 814 | void CVmBifTADS::cvtstr(VMG_ uint argc) |
| | 815 | { |
| | 816 | const char *p; |
| | 817 | char buf[50]; |
| | 818 | vm_val_t val; |
| | 819 | int radix; |
| | 820 | vm_val_t new_str; |
| | 821 | |
| | 822 | /* check arguments */ |
| | 823 | check_argc_range(vmg_ argc, 1, 2); |
| | 824 | |
| | 825 | /* pop the argument */ |
| | 826 | G_stk->pop(&val); |
| | 827 | |
| | 828 | /* if there's a radix specified, pop it as well */ |
| | 829 | if (argc == 2) |
| | 830 | { |
| | 831 | /* get the radix from the stack */ |
| | 832 | radix = pop_int_val(vmg0_); |
| | 833 | } |
| | 834 | else |
| | 835 | { |
| | 836 | /* use decimal by default */ |
| | 837 | radix = 10; |
| | 838 | } |
| | 839 | |
| | 840 | /* convert the value */ |
| | 841 | p = CVmObjString::cvt_to_str(vmg_ &new_str, |
| | 842 | buf, sizeof(buf), &val, radix); |
| | 843 | |
| | 844 | /* save the new string on the stack to protect from garbage collection */ |
| | 845 | G_stk->push(&new_str); |
| | 846 | |
| | 847 | /* create and return a string from our new value */ |
| | 848 | retval_obj(vmg_ CVmObjString::create(vmg_ FALSE, |
| | 849 | p + VMB_LEN, vmb_get_len(p))); |
| | 850 | |
| | 851 | /* done with the new string */ |
| | 852 | G_stk->discard(); |
| | 853 | } |
| | 854 | |
| | 855 | /* |
| | 856 | * cvtnum (toInteger) - convert to an integer |
| | 857 | */ |
| | 858 | void CVmBifTADS::cvtnum(VMG_ uint argc) |
| | 859 | { |
| | 860 | const char *strp; |
| | 861 | size_t len; |
| | 862 | int radix; |
| | 863 | vm_val_t *valp; |
| | 864 | |
| | 865 | /* check arguments */ |
| | 866 | check_argc_range(vmg_ argc, 1, 2); |
| | 867 | |
| | 868 | /* |
| | 869 | * check for a BigNumber and convert it (not very object-oriented, |
| | 870 | * but this is a type-conversion routine, so special awareness of |
| | 871 | * individual types isn't that weird) |
| | 872 | */ |
| | 873 | valp = G_stk->get(0); |
| | 874 | if (valp->typ == VM_OBJ |
| | 875 | && CVmObjBigNum::is_bignum_obj(vmg_ valp->val.obj)) |
| | 876 | { |
| | 877 | long intval; |
| | 878 | |
| | 879 | /* convert it as a BigNumber */ |
| | 880 | intval = ((CVmObjBigNum *)vm_objp(vmg_ valp->val.obj)) |
| | 881 | ->convert_to_int(); |
| | 882 | |
| | 883 | /* discard arguments (ignore the radix in this case) */ |
| | 884 | G_stk->discard(argc); |
| | 885 | |
| | 886 | /* return the integer value */ |
| | 887 | retval_int(vmg_ intval); |
| | 888 | return; |
| | 889 | } |
| | 890 | |
| | 891 | /* if it's already an integer, just return the same value */ |
| | 892 | if (valp->typ == VM_INT) |
| | 893 | { |
| | 894 | /* just return the argument value */ |
| | 895 | retval_int(vmg_ valp->val.intval); |
| | 896 | |
| | 897 | /* discard arguments (ignore the radix in this case) */ |
| | 898 | G_stk->discard(argc); |
| | 899 | |
| | 900 | /* done */ |
| | 901 | return; |
| | 902 | } |
| | 903 | |
| | 904 | /* otherwise, it must be a string */ |
| | 905 | strp = pop_str_val(vmg0_); |
| | 906 | len = vmb_get_len(strp); |
| | 907 | |
| | 908 | /* if there's a radix specified, pop it as well */ |
| | 909 | if (argc == 2) |
| | 910 | { |
| | 911 | /* get the radix from the stack */ |
| | 912 | radix = pop_int_val(vmg0_); |
| | 913 | |
| | 914 | /* make sure the radix is valid */ |
| | 915 | switch(radix) |
| | 916 | { |
| | 917 | case 2: |
| | 918 | case 8: |
| | 919 | case 10: |
| | 920 | case 16: |
| | 921 | /* it's okay - proceed */ |
| | 922 | break; |
| | 923 | |
| | 924 | default: |
| | 925 | /* other radix values are invalid */ |
| | 926 | err_throw(VMERR_BAD_VAL_BIF); |
| | 927 | } |
| | 928 | } |
| | 929 | else |
| | 930 | { |
| | 931 | /* the default radix is decimal */ |
| | 932 | radix = 10; |
| | 933 | } |
| | 934 | |
| | 935 | /* parse the value */ |
| | 936 | if (len == 3 && memcmp(strp + VMB_LEN, "nil", 3) == 0) |
| | 937 | { |
| | 938 | /* the value is the constant 'nil' */ |
| | 939 | retval_nil(vmg0_); |
| | 940 | } |
| | 941 | else if (len == 4 && memcmp(strp + VMB_LEN, "true", 3) == 0) |
| | 942 | { |
| | 943 | /* the value is the constant 'true' */ |
| | 944 | retval_true(vmg0_); |
| | 945 | } |
| | 946 | else |
| | 947 | { |
| | 948 | utf8_ptr p; |
| | 949 | size_t rem; |
| | 950 | int is_neg; |
| | 951 | ulong acc; |
| | 952 | |
| | 953 | /* scan past leading spaces */ |
| | 954 | for (p.set((char *)strp + VMB_LEN), rem = len ; |
| | 955 | rem != 0 && is_space(p.getch()) ; p.inc(&rem)) ; |
| | 956 | |
| | 957 | /* presume it's positive */ |
| | 958 | is_neg = FALSE; |
| | 959 | |
| | 960 | /* if the radix is 10, check for a leading + or - */ |
| | 961 | if (radix == 10 && rem != 0) |
| | 962 | { |
| | 963 | if (p.getch() == '-') |
| | 964 | { |
| | 965 | /* note the sign and skip the character */ |
| | 966 | is_neg = TRUE; |
| | 967 | p.inc(&rem); |
| | 968 | } |
| | 969 | else if (p.getch() == '+') |
| | 970 | { |
| | 971 | /* skip the character */ |
| | 972 | p.inc(&rem); |
| | 973 | } |
| | 974 | } |
| | 975 | |
| | 976 | /* clear the accumulator */ |
| | 977 | acc = 0; |
| | 978 | |
| | 979 | /* scan the digits */ |
| | 980 | switch (radix) |
| | 981 | { |
| | 982 | case 2: |
| | 983 | for ( ; rem != 0 && (p.getch() == '0' || p.getch() == '1') ; |
| | 984 | p.inc(&rem)) |
| | 985 | { |
| | 986 | acc <<= 1; |
| | 987 | if (p.getch() == '1') |
| | 988 | acc += 1; |
| | 989 | } |
| | 990 | break; |
| | 991 | |
| | 992 | case 8: |
| | 993 | for ( ; rem != 0 && is_odigit(p.getch()) ; p.inc(&rem)) |
| | 994 | { |
| | 995 | acc <<= 3; |
| | 996 | acc += value_of_odigit(p.getch()); |
| | 997 | } |
| | 998 | break; |
| | 999 | |
| | 1000 | case 10: |
| | 1001 | for ( ; rem != 0 && is_digit(p.getch()) ; p.inc(&rem)) |
| | 1002 | { |
| | 1003 | acc *= 10; |
| | 1004 | acc += value_of_digit(p.getch()); |
| | 1005 | } |
| | 1006 | break; |
| | 1007 | |
| | 1008 | case 16: |
| | 1009 | for ( ; rem != 0 && is_xdigit(p.getch()) ; p.inc(&rem)) |
| | 1010 | { |
| | 1011 | acc <<= 4; |
| | 1012 | acc += value_of_xdigit(p.getch()); |
| | 1013 | } |
| | 1014 | break; |
| | 1015 | } |
| | 1016 | |
| | 1017 | /* apply the sign, if appropriate, and set the return value */ |
| | 1018 | if (is_neg) |
| | 1019 | retval_int(vmg_ -(long)acc); |
| | 1020 | else |
| | 1021 | retval_int(vmg_ (long)acc); |
| | 1022 | } |
| | 1023 | } |
| | 1024 | |
| | 1025 | /* ------------------------------------------------------------------------ */ |
| | 1026 | /* |
| | 1027 | * put an integer value in a constant list, advancing the list write |
| | 1028 | * pointer |
| | 1029 | */ |
| | 1030 | static void put_list_int(char **dstp, long intval) |
| | 1031 | { |
| | 1032 | vm_val_t val; |
| | 1033 | |
| | 1034 | /* set up the integer value */ |
| | 1035 | val.set_int(intval); |
| | 1036 | |
| | 1037 | /* write it to the list */ |
| | 1038 | vmb_put_dh(*dstp, &val); |
| | 1039 | |
| | 1040 | /* advance the output pointer */ |
| | 1041 | *dstp += VMB_DATAHOLDER; |
| | 1042 | } |
| | 1043 | |
| | 1044 | /* |
| | 1045 | * put an object value in a constant list, advancing the list write |
| | 1046 | * pointer |
| | 1047 | */ |
| | 1048 | static void put_list_obj(char **dstp, vm_obj_id_t objval) |
| | 1049 | { |
| | 1050 | vm_val_t val; |
| | 1051 | |
| | 1052 | /* set up the integer value */ |
| | 1053 | val.set_obj(objval); |
| | 1054 | |
| | 1055 | /* write it to the list */ |
| | 1056 | vmb_put_dh(*dstp, &val); |
| | 1057 | |
| | 1058 | /* advance the output pointer */ |
| | 1059 | *dstp += VMB_DATAHOLDER; |
| | 1060 | } |
| | 1061 | |
| | 1062 | |
| | 1063 | /* |
| | 1064 | * get the current time |
| | 1065 | */ |
| | 1066 | void CVmBifTADS::gettime(VMG_ uint argc) |
| | 1067 | { |
| | 1068 | int typ; |
| | 1069 | time_t timer; |
| | 1070 | struct tm *tblock; |
| | 1071 | char buf[80]; |
| | 1072 | char *dst; |
| | 1073 | |
| | 1074 | /* check arguments */ |
| | 1075 | check_argc_range(vmg_ argc, 0, 1); |
| | 1076 | |
| | 1077 | /* if there's an argument, get the type of time value to return */ |
| | 1078 | if (argc == 1) |
| | 1079 | { |
| | 1080 | /* get the time type code */ |
| | 1081 | typ = pop_int_val(vmg0_); |
| | 1082 | } |
| | 1083 | else |
| | 1084 | { |
| | 1085 | /* use the default type */ |
| | 1086 | typ = 1; |
| | 1087 | } |
| | 1088 | |
| | 1089 | /* check the type */ |
| | 1090 | switch(typ) |
| | 1091 | { |
| | 1092 | case 1: |
| | 1093 | /* |
| | 1094 | * default information - return the current time and date |
| | 1095 | */ |
| | 1096 | |
| | 1097 | /* make sure the time zone is set up properly */ |
| | 1098 | os_tzset(); |
| | 1099 | |
| | 1100 | /* get the local time information */ |
| | 1101 | timer = time(NULL); |
| | 1102 | tblock = localtime(&timer); |
| | 1103 | |
| | 1104 | /* adjust values for return format */ |
| | 1105 | tblock->tm_year += 1900; |
| | 1106 | tblock->tm_mon++; |
| | 1107 | tblock->tm_wday++; |
| | 1108 | tblock->tm_yday++; |
| | 1109 | |
| | 1110 | /* |
| | 1111 | * build the return list: [year, month, day, day-of-week, |
| | 1112 | * day-of-year, hour, minute, second, seconds-since-1970] |
| | 1113 | */ |
| | 1114 | vmb_put_len(buf, 9); |
| | 1115 | dst = buf + VMB_LEN; |
| | 1116 | |
| | 1117 | /* build return list value */ |
| | 1118 | put_list_int(&dst, tblock->tm_year); |
| | 1119 | put_list_int(&dst, tblock->tm_mon); |
| | 1120 | put_list_int(&dst, tblock->tm_mday); |
| | 1121 | put_list_int(&dst, tblock->tm_wday); |
| | 1122 | put_list_int(&dst, tblock->tm_yday); |
| | 1123 | put_list_int(&dst, tblock->tm_hour); |
| | 1124 | put_list_int(&dst, tblock->tm_min); |
| | 1125 | put_list_int(&dst, tblock->tm_sec); |
| | 1126 | put_list_int(&dst, (long)timer); |
| | 1127 | |
| | 1128 | /* allocate and return the list value */ |
| | 1129 | retval_obj(vmg_ CVmObjList::create(vmg_ FALSE, buf)); |
| | 1130 | |
| | 1131 | /* done */ |
| | 1132 | break; |
| | 1133 | |
| | 1134 | case 2: |
| | 1135 | /* |
| | 1136 | * They want the high-precision system timer value, which returns |
| | 1137 | * the time in milliseconds from an arbitrary zero point. |
| | 1138 | */ |
| | 1139 | { |
| | 1140 | unsigned long t; |
| | 1141 | static unsigned long t_zero; |
| | 1142 | static int t_zero_set = FALSE; |
| | 1143 | |
| | 1144 | /* retrieve the raw time from the operating system */ |
| | 1145 | t = os_get_sys_clock_ms(); |
| | 1146 | |
| | 1147 | /* |
| | 1148 | * We only have 31 bits of precision in our result (since we |
| | 1149 | * must fit the value into a signed integer), so we can only |
| | 1150 | * represent time differences of about 23 days. Now, the |
| | 1151 | * value from the OS could be at any arbitrary point in our |
| | 1152 | * 23-day range, so there's a nontrivial probability that the |
| | 1153 | * raw OS value is near enough to the wrapping point that a |
| | 1154 | * future call to this same function during the current |
| | 1155 | * session could encounter the wrap condition. The caller is |
| | 1156 | * likely to be confused by this, because the time difference |
| | 1157 | * from this call to that future call would appear to be |
| | 1158 | * negative. |
| | 1159 | * |
| | 1160 | * There's obviously no way we can eliminate the possibility |
| | 1161 | * of a negative time difference if the current program |
| | 1162 | * session lasts more than 23 days of continuous execution. |
| | 1163 | * Fortunately, it seems unlikely that most sessions will be |
| | 1164 | * so long, which gives us a way to reduce the likelihood that |
| | 1165 | * the program will encounter a wrapped timer: we can adjust |
| | 1166 | * the zero point of the timer to the time of the first call |
| | 1167 | * to this function. That way, the timer will wrap only if |
| | 1168 | * the program session runs continuously until the timer's |
| | 1169 | * range is exhausted. |
| | 1170 | */ |
| | 1171 | if (!t_zero_set) |
| | 1172 | { |
| | 1173 | /* this is the first call - remember the zero point */ |
| | 1174 | t_zero = t; |
| | 1175 | t_zero_set = TRUE; |
| | 1176 | } |
| | 1177 | |
| | 1178 | /* |
| | 1179 | * Adjust the time by subtracting the zero point from the raw |
| | 1180 | * OS timer. This will give us the number of milliseconds |
| | 1181 | * from our zero point. |
| | 1182 | * |
| | 1183 | * If the system timer has wrapped since our zero point, we'll |
| | 1184 | * get what looks like a negative number; but what we really |
| | 1185 | * have is a large positive number with a borrow from an |
| | 1186 | * unrepresented higher-precision portion, so the fact that |
| | 1187 | * this value is negative doesn't matter - it will still be |
| | 1188 | * sequential when treated as unsigned. |
| | 1189 | */ |
| | 1190 | t -= t_zero; |
| | 1191 | |
| | 1192 | /* |
| | 1193 | * whatever we got, keep only the low-order 31 bits, since we |
| | 1194 | * only have 31 bits in which to represent an unsigned value |
| | 1195 | */ |
| | 1196 | t &= 0x7fffffff; |
| | 1197 | |
| | 1198 | /* return the value we've calculated */ |
| | 1199 | retval_int(vmg_ t); |
| | 1200 | } |
| | 1201 | break; |
| | 1202 | |
| | 1203 | default: |
| | 1204 | err_throw(VMERR_BAD_VAL_BIF); |
| | 1205 | } |
| | 1206 | } |
| | 1207 | |
| | 1208 | /* ------------------------------------------------------------------------ */ |
| | 1209 | /* |
| | 1210 | * re_match - match a regular expression to a string |
| | 1211 | */ |
| | 1212 | void CVmBifTADS::re_match(VMG_ uint argc) |
| | 1213 | { |
| | 1214 | const char *str; |
| | 1215 | utf8_ptr p; |
| | 1216 | size_t len; |
| | 1217 | int match_len; |
| | 1218 | vm_val_t *v1, *v2, *v3; |
| | 1219 | int start_idx; |
| | 1220 | CVmObjPattern *pat_obj = 0; |
| | 1221 | const char *pat_str = 0; |
| | 1222 | |
| | 1223 | /* check arguments */ |
| | 1224 | check_argc_range(vmg_ argc, 2, 3); |
| | 1225 | |
| | 1226 | /* |
| | 1227 | * make copies of the arguments, so we can pop the values without |
| | 1228 | * actually removing them from the stack - leave the originals on the |
| | 1229 | * stack for gc protection |
| | 1230 | */ |
| | 1231 | v1 = G_stk->get(0); |
| | 1232 | v2 = G_stk->get(1); |
| | 1233 | v3 = (argc >= 3 ? G_stk->get(2) : 0); |
| | 1234 | G_stk->push(v2); |
| | 1235 | G_stk->push(v1); |
| | 1236 | |
| | 1237 | /* note the starting index, if given */ |
| | 1238 | start_idx = 0; |
| | 1239 | if (v3 != 0) |
| | 1240 | { |
| | 1241 | /* check the type */ |
| | 1242 | if (v3->typ != VM_INT) |
| | 1243 | err_throw(VMERR_BAD_TYPE_BIF); |
| | 1244 | |
| | 1245 | /* get the value */ |
| | 1246 | start_idx = (int)v3->val.intval - 1; |
| | 1247 | |
| | 1248 | /* make sure it's in range */ |
| | 1249 | if (start_idx < 0) |
| | 1250 | start_idx = 0; |
| | 1251 | } |
| | 1252 | |
| | 1253 | /* remember the last search string (the second argument) */ |
| | 1254 | G_bif_tads_globals->last_rex_str->val = *v2; |
| | 1255 | |
| | 1256 | /* |
| | 1257 | * check what we have for the pattern - we could have either a string |
| | 1258 | * giving the regular expression, or a RexPattern object with the |
| | 1259 | * compiled pattern |
| | 1260 | */ |
| | 1261 | if (G_stk->get(0)->typ == VM_OBJ |
| | 1262 | && CVmObjPattern::is_pattern_obj(vmg_ G_stk->get(0)->val.obj)) |
| | 1263 | { |
| | 1264 | vm_val_t pat_val; |
| | 1265 | |
| | 1266 | /* get the pattern object */ |
| | 1267 | G_stk->pop(&pat_val); |
| | 1268 | pat_obj = (CVmObjPattern *)vm_objp(vmg_ pat_val.val.obj); |
| | 1269 | } |
| | 1270 | else |
| | 1271 | { |
| | 1272 | /* get the pattern string */ |
| | 1273 | pat_str = pop_str_val(vmg0_); |
| | 1274 | } |
| | 1275 | |
| | 1276 | /* get the string to match */ |
| | 1277 | str = pop_str_val(vmg0_); |
| | 1278 | len = vmb_get_len(str); |
| | 1279 | p.set((char *)str + VMB_LEN); |
| | 1280 | |
| | 1281 | /* skip to the starting index */ |
| | 1282 | for ( ; start_idx > 0 && len != 0 ; --start_idx, p.inc(&len)) ; |
| | 1283 | |
| | 1284 | /* match the pattern */ |
| | 1285 | if (pat_obj != 0) |
| | 1286 | { |
| | 1287 | /* match the compiled pattern object */ |
| | 1288 | match_len = G_bif_tads_globals->rex_searcher-> |
| | 1289 | match_pattern(pat_obj->get_pattern(vmg0_), |
| | 1290 | str + VMB_LEN, p.getptr(), len); |
| | 1291 | } |
| | 1292 | else |
| | 1293 | { |
| | 1294 | /* match the pattern to the regular expression string */ |
| | 1295 | match_len = G_bif_tads_globals->rex_searcher-> |
| | 1296 | compile_and_match(pat_str + VMB_LEN, vmb_get_len(pat_str), |
| | 1297 | str + VMB_LEN, p.getptr(), len); |
| | 1298 | } |
| | 1299 | |
| | 1300 | /* check for a match */ |
| | 1301 | if (match_len >= 0) |
| | 1302 | { |
| | 1303 | /* we got a match - calculate the character length of the match */ |
| | 1304 | retval_int(vmg_ (long)p.len(match_len)); |
| | 1305 | } |
| | 1306 | else |
| | 1307 | { |
| | 1308 | /* no match - return nil */ |
| | 1309 | retval_nil(vmg0_); |
| | 1310 | } |
| | 1311 | |
| | 1312 | /* discard the arguments */ |
| | 1313 | G_stk->discard(argc); |
| | 1314 | } |
| | 1315 | |
| | 1316 | /* |
| | 1317 | * re_search - search for a substring matching a regular expression |
| | 1318 | * within a string |
| | 1319 | */ |
| | 1320 | void CVmBifTADS::re_search(VMG_ uint argc) |
| | 1321 | { |
| | 1322 | const char *str; |
| | 1323 | utf8_ptr p; |
| | 1324 | size_t len; |
| | 1325 | int match_idx; |
| | 1326 | int match_len; |
| | 1327 | vm_val_t *v1, *v2, *v3; |
| | 1328 | int start_idx; |
| | 1329 | int i; |
| | 1330 | CVmObjPattern *pat_obj = 0; |
| | 1331 | const char *pat_str = 0; |
| | 1332 | |
| | 1333 | /* check arguments */ |
| | 1334 | check_argc_range(vmg_ argc, 2, 3); |
| | 1335 | |
| | 1336 | /* |
| | 1337 | * make copies of the arguments, so we can pop the values without |
| | 1338 | * actually removing them from the stack - leave the originals on the |
| | 1339 | * stack for gc protection |
| | 1340 | */ |
| | 1341 | v1 = G_stk->get(0); |
| | 1342 | v2 = G_stk->get(1); |
| | 1343 | v3 = (argc >= 3 ? G_stk->get(2) : 0); |
| | 1344 | G_stk->push(v2); |
| | 1345 | G_stk->push(v1); |
| | 1346 | |
| | 1347 | /* note the starting index, if given */ |
| | 1348 | start_idx = 0; |
| | 1349 | if (v3 != 0) |
| | 1350 | { |
| | 1351 | /* check the type */ |
| | 1352 | if (v3->typ != VM_INT) |
| | 1353 | err_throw(VMERR_BAD_TYPE_BIF); |
| | 1354 | |
| | 1355 | /* get the value */ |
| | 1356 | start_idx = (int)v3->val.intval - 1; |
| | 1357 | |
| | 1358 | /* make sure it's in range */ |
| | 1359 | if (start_idx < 0) |
| | 1360 | start_idx = 0; |
| | 1361 | } |
| | 1362 | |
| | 1363 | /* remember the last search string (the second argument) */ |
| | 1364 | G_bif_tads_globals->last_rex_str->val = *v2; |
| | 1365 | |
| | 1366 | /* check to see if we have a RexPattern object or an uncompiled string */ |
| | 1367 | if (G_stk->get(0)->typ == VM_OBJ |
| | 1368 | && CVmObjPattern::is_pattern_obj(vmg_ G_stk->get(0)->val.obj)) |
| | 1369 | { |
| | 1370 | vm_val_t pat_val; |
| | 1371 | |
| | 1372 | /* get the pattern object */ |
| | 1373 | G_stk->pop(&pat_val); |
| | 1374 | pat_obj = (CVmObjPattern *)vm_objp(vmg_ pat_val.val.obj); |
| | 1375 | } |
| | 1376 | else |
| | 1377 | { |
| | 1378 | /* get the pattern string */ |
| | 1379 | pat_str = pop_str_val(vmg0_); |
| | 1380 | } |
| | 1381 | |
| | 1382 | /* get the string to search for the pattern */ |
| | 1383 | str = pop_str_val(vmg0_); |
| | 1384 | p.set((char *)str + VMB_LEN); |
| | 1385 | len = vmb_get_len(str); |
| | 1386 | |
| | 1387 | /* skip to the starting index */ |
| | 1388 | for (i = start_idx ; i > 0 && len != 0 ; --i, p.inc(&len)) ; |
| | 1389 | |
| | 1390 | /* search for the pattern */ |
| | 1391 | if (pat_obj != 0) |
| | 1392 | { |
| | 1393 | /* try finding the compiled pattern */ |
| | 1394 | match_idx = G_bif_tads_globals->rex_searcher->search_for_pattern( |
| | 1395 | pat_obj->get_pattern(vmg0_), |
| | 1396 | str + VMB_LEN, p.getptr(), len, &match_len); |
| | 1397 | } |
| | 1398 | else |
| | 1399 | { |
| | 1400 | /* try finding the regular expression string pattern */ |
| | 1401 | match_idx = G_bif_tads_globals->rex_searcher->compile_and_search( |
| | 1402 | pat_str + VMB_LEN, vmb_get_len(pat_str), |
| | 1403 | str + VMB_LEN, p.getptr(), len, &match_len); |
| | 1404 | } |
| | 1405 | |
| | 1406 | /* check for a match */ |
| | 1407 | if (match_idx >= 0) |
| | 1408 | { |
| | 1409 | utf8_ptr matchp; |
| | 1410 | size_t char_idx; |
| | 1411 | size_t char_len; |
| | 1412 | vm_obj_id_t match_str_obj; |
| | 1413 | char *dst; |
| | 1414 | char buf[VMB_LEN + VMB_DATAHOLDER * 3]; |
| | 1415 | |
| | 1416 | /* |
| | 1417 | * We got a match - calculate the character index of the match |
| | 1418 | * offset, adjusted to a 1-base. The character index is simply the |
| | 1419 | * number of characters in the part of the string up to the match |
| | 1420 | * index. Note that we have to add the starting index to get the |
| | 1421 | * actual index in the overall string, since 'p' points to the |
| | 1422 | * character at the starting index. |
| | 1423 | */ |
| | 1424 | char_idx = p.len(match_idx) + start_idx + 1; |
| | 1425 | |
| | 1426 | /* calculate the character length of the match */ |
| | 1427 | matchp.set(p.getptr() + match_idx); |
| | 1428 | char_len = matchp.len(match_len); |
| | 1429 | |
| | 1430 | /* allocate a string containing the match */ |
| | 1431 | match_str_obj = |
| | 1432 | CVmObjString::create(vmg_ FALSE, matchp.getptr(), match_len); |
| | 1433 | |
| | 1434 | /* push it momentarily as protection against garbage collection */ |
| | 1435 | G_stk->push()->set_obj(match_str_obj); |
| | 1436 | |
| | 1437 | /* |
| | 1438 | * set up a 3-element list to contain the return value: |
| | 1439 | * [match_start_index, match_length, match_string] |
| | 1440 | */ |
| | 1441 | vmb_put_len(buf, 3); |
| | 1442 | dst = buf + VMB_LEN; |
| | 1443 | put_list_int(&dst, (long)char_idx); |
| | 1444 | put_list_int(&dst, (long)char_len); |
| | 1445 | put_list_obj(&dst, match_str_obj); |
| | 1446 | |
| | 1447 | /* allocate and return the list */ |
| | 1448 | retval_obj(vmg_ CVmObjList::create(vmg_ FALSE, buf)); |
| | 1449 | |
| | 1450 | /* we no longer need the garbage collection protection */ |
| | 1451 | G_stk->discard(); |
| | 1452 | } |
| | 1453 | else |
| | 1454 | { |
| | 1455 | /* no match - return nil */ |
| | 1456 | retval_nil(vmg0_); |
| | 1457 | } |
| | 1458 | |
| | 1459 | /* discard the arguments */ |
| | 1460 | G_stk->discard(argc); |
| | 1461 | } |
| | 1462 | |
| | 1463 | /* |
| | 1464 | * re_group - get the string matching a group in the most recent regular |
| | 1465 | * expression search or match |
| | 1466 | */ |
| | 1467 | void CVmBifTADS::re_group(VMG_ uint argc) |
| | 1468 | { |
| | 1469 | int groupno; |
| | 1470 | const re_group_register *reg; |
| | 1471 | char buf[VMB_LEN + 3*VMB_DATAHOLDER]; |
| | 1472 | char *dst; |
| | 1473 | utf8_ptr p; |
| | 1474 | vm_obj_id_t strobj; |
| | 1475 | const char *last_str; |
| | 1476 | int start_byte_ofs; |
| | 1477 | |
| | 1478 | /* check arguments */ |
| | 1479 | check_argc(vmg_ argc, 1); |
| | 1480 | |
| | 1481 | /* get the group number to retrieve */ |
| | 1482 | groupno = pop_int_val(vmg0_); |
| | 1483 | |
| | 1484 | /* make sure it's in range */ |
| | 1485 | if (groupno < 1 || groupno > RE_GROUP_REG_CNT) |
| | 1486 | err_throw(VMERR_BAD_VAL_BIF); |
| | 1487 | |
| | 1488 | /* adjust from a 1-base to a 0-base */ |
| | 1489 | --groupno; |
| | 1490 | |
| | 1491 | /* if the group doesn't exist in the pattern, return nil */ |
| | 1492 | if (groupno >= G_bif_tads_globals->rex_searcher->get_group_cnt()) |
| | 1493 | { |
| | 1494 | retval_nil(vmg0_); |
| | 1495 | return; |
| | 1496 | } |
| | 1497 | |
| | 1498 | /* |
| | 1499 | * get the previous search string - get a pointer directly to the |
| | 1500 | * contents of the string |
| | 1501 | */ |
| | 1502 | last_str = G_bif_tads_globals->last_rex_str->val.get_as_string(vmg0_); |
| | 1503 | |
| | 1504 | /* get the register */ |
| | 1505 | reg = G_bif_tads_globals->rex_searcher->get_group_reg(groupno); |
| | 1506 | |
| | 1507 | /* if the group wasn't set, or there's no last string, return nil */ |
| | 1508 | if (last_str == 0 || reg->start_ofs == -1 || reg->end_ofs == -1) |
| | 1509 | { |
| | 1510 | retval_nil(vmg0_); |
| | 1511 | return; |
| | 1512 | } |
| | 1513 | |
| | 1514 | /* set up for a list with three elements */ |
| | 1515 | vmb_put_len(buf, 3); |
| | 1516 | dst = buf + VMB_LEN; |
| | 1517 | |
| | 1518 | /* get the starting offset from the group register */ |
| | 1519 | start_byte_ofs = reg->start_ofs; |
| | 1520 | |
| | 1521 | /* |
| | 1522 | * The first element is the character index of the group text in the |
| | 1523 | * source string. Calculate the character index by adding 1 to the |
| | 1524 | * character length of the text preceding the group; calculate the |
| | 1525 | * character length from the byte length of that string. Note that the |
| | 1526 | * starting in the group register is stored from the starting point of |
| | 1527 | * the search, not the start of the string, so we need to add in the |
| | 1528 | * starting point in the search. |
| | 1529 | */ |
| | 1530 | p.set((char *)last_str + VMB_LEN); |
| | 1531 | put_list_int(&dst, p.len(start_byte_ofs) + 1); |
| | 1532 | |
| | 1533 | /* |
| | 1534 | * The second element is the character length of the group text. |
| | 1535 | * Calculate the character length from the byte length. |
| | 1536 | */ |
| | 1537 | p.set(p.getptr() + start_byte_ofs); |
| | 1538 | put_list_int(&dst, p.len(reg->end_ofs - reg->start_ofs)); |
| | 1539 | |
| | 1540 | /* |
| | 1541 | * The third element is the string itself. Create a new string |
| | 1542 | * containing the matching substring. |
| | 1543 | */ |
| | 1544 | strobj = CVmObjString::create(vmg_ FALSE, p.getptr(), |
| | 1545 | reg->end_ofs - reg->start_ofs); |
| | 1546 | put_list_obj(&dst, strobj); |
| | 1547 | |
| | 1548 | /* save the string on the stack momentarily to protect against GC */ |
| | 1549 | G_stk->push()->set_obj(strobj); |
| | 1550 | |
| | 1551 | /* create and return the list value */ |
| | 1552 | retval_obj(vmg_ CVmObjList::create(vmg_ FALSE, buf)); |
| | 1553 | |
| | 1554 | /* we no longer need the garbage collector protection */ |
| | 1555 | G_stk->discard(); |
| | 1556 | } |
| | 1557 | |
| | 1558 | /* |
| | 1559 | * re_replace flags |
| | 1560 | */ |
| | 1561 | #define VMBIFTADS_REPLACE_ALL 0x0001 |
| | 1562 | |
| | 1563 | /* |
| | 1564 | * re_replace - search for a pattern in a string, and apply a |
| | 1565 | * replacement pattern |
| | 1566 | */ |
| | 1567 | void CVmBifTADS::re_replace(VMG_ uint argc) |
| | 1568 | { |
| | 1569 | vm_val_t patval, rplval; |
| | 1570 | const char *str; |
| | 1571 | const char *rpl; |
| | 1572 | ulong flags; |
| | 1573 | vm_val_t search_val; |
| | 1574 | int match_idx; |
| | 1575 | int match_len; |
| | 1576 | size_t new_len; |
| | 1577 | utf8_ptr p; |
| | 1578 | size_t rem; |
| | 1579 | int groupno; |
| | 1580 | const re_group_register *reg; |
| | 1581 | vm_obj_id_t ret_obj; |
| | 1582 | utf8_ptr dstp; |
| | 1583 | int match_cnt; |
| | 1584 | int start_idx; |
| | 1585 | re_compiled_pattern *cpat; |
| | 1586 | int cpat_is_ours; |
| | 1587 | int group_cnt; |
| | 1588 | int start_char_idx; |
| | 1589 | int skip_bytes; |
| | 1590 | |
| | 1591 | /* check arguments */ |
| | 1592 | check_argc_range(vmg_ argc, 4, 5); |
| | 1593 | |
| | 1594 | /* remember the pattern and replacement string values */ |
| | 1595 | patval = *G_stk->get(0); |
| | 1596 | rplval = *G_stk->get(2); |
| | 1597 | |
| | 1598 | /* retrieve the compiled RexPattern or uncompiled pattern string */ |
| | 1599 | if (G_stk->get(0)->typ == VM_OBJ |
| | 1600 | && CVmObjPattern::is_pattern_obj(vmg_ G_stk->get(0)->val.obj)) |
| | 1601 | { |
| | 1602 | vm_val_t pat_val; |
| | 1603 | CVmObjPattern *pat; |
| | 1604 | |
| | 1605 | /* get the pattern object */ |
| | 1606 | G_stk->pop(&pat_val); |
| | 1607 | pat = (CVmObjPattern *)vm_objp(vmg_ pat_val.val.obj); |
| | 1608 | |
| | 1609 | /* get the compiled pattern structure */ |
| | 1610 | cpat = pat->get_pattern(vmg0_); |
| | 1611 | |
| | 1612 | /* the pattern isn't ours, so we don't need to delete it */ |
| | 1613 | cpat_is_ours = FALSE; |
| | 1614 | } |
| | 1615 | else |
| | 1616 | { |
| | 1617 | re_status_t stat; |
| | 1618 | const char *pat_str; |
| | 1619 | |
| | 1620 | /* pop the pattern string */ |
| | 1621 | pat_str = pop_str_val(vmg0_); |
| | 1622 | |
| | 1623 | /* since we'll need it multiple times, compile it */ |
| | 1624 | stat = G_bif_tads_globals->rex_parser->compile_pattern( |
| | 1625 | pat_str + VMB_LEN, vmb_get_len(pat_str), &cpat); |
| | 1626 | |
| | 1627 | /* if that failed, we don't have a pattern */ |
| | 1628 | if (stat != RE_STATUS_SUCCESS) |
| | 1629 | cpat = 0; |
| | 1630 | |
| | 1631 | /* note that we allocated the pattern, so we have to delete it */ |
| | 1632 | cpat_is_ours = TRUE; |
| | 1633 | } |
| | 1634 | |
| | 1635 | /* |
| | 1636 | * Pop the search string and the replacement string. Note that we want |
| | 1637 | * to retain the original value information for the search string, |
| | 1638 | * since we'll end up returning it unchanged if we don't find the |
| | 1639 | * pattern. |
| | 1640 | */ |
| | 1641 | G_stk->pop(&search_val); |
| | 1642 | rpl = pop_str_val(vmg0_); |
| | 1643 | |
| | 1644 | /* remember the last search string */ |
| | 1645 | G_bif_tads_globals->last_rex_str->val = search_val; |
| | 1646 | |
| | 1647 | /* pop the flags */ |
| | 1648 | flags = pop_long_val(vmg0_); |
| | 1649 | |
| | 1650 | /* pop the starting index if given */ |
| | 1651 | start_char_idx = (argc >= 5 ? pop_int_val(vmg0_) - 1 : 0); |
| | 1652 | |
| | 1653 | /* make sure it's in range */ |
| | 1654 | if (start_char_idx < 0) |
| | 1655 | start_char_idx = 0; |
| | 1656 | |
| | 1657 | /* |
| | 1658 | * put the pattern, replacement string, and search string values back |
| | 1659 | * on the stack as protection against garbage collection |
| | 1660 | */ |
| | 1661 | G_stk->push(&patval); |
| | 1662 | G_stk->push(&rplval); |
| | 1663 | G_stk->push(&search_val); |
| | 1664 | |
| | 1665 | /* make sure the search string is indeed a string */ |
| | 1666 | str = search_val.get_as_string(vmg0_); |
| | 1667 | if (str == 0) |
| | 1668 | err_throw(VMERR_STRING_VAL_REQD); |
| | 1669 | |
| | 1670 | /* |
| | 1671 | * figure out how many bytes at the start of the string to skip before |
| | 1672 | * our first replacement |
| | 1673 | */ |
| | 1674 | for (p.set((char *)str + VMB_LEN), rem = vmb_get_len(str) ; |
| | 1675 | start_char_idx > 0 && rem != 0 ; --start_char_idx, p.inc(&rem)) ; |
| | 1676 | |
| | 1677 | /* the current offset in the string is the byte skip offset */ |
| | 1678 | skip_bytes = p.getptr() - (str + VMB_LEN); |
| | 1679 | |
| | 1680 | /* |
| | 1681 | * if we don't have a compiled pattern at this point, we're not going |
| | 1682 | * to be able to match anything, so we can just stop now and return the |
| | 1683 | * original string unchanged |
| | 1684 | */ |
| | 1685 | if (cpat == 0) |
| | 1686 | { |
| | 1687 | /* return the original search string */ |
| | 1688 | retval(vmg_ &search_val); |
| | 1689 | goto done; |
| | 1690 | } |
| | 1691 | |
| | 1692 | /* note the group count in the compiled pattern */ |
| | 1693 | group_cnt = cpat->group_cnt; |
| | 1694 | |
| | 1695 | /* |
| | 1696 | * First, determine how long the result string will be. Search |
| | 1697 | * repeatedly if the REPLACE_ALL flag (0x0001) is set. |
| | 1698 | */ |
| | 1699 | for (new_len = skip_bytes, match_cnt = 0, start_idx = skip_bytes ; |
| | 1700 | (size_t)start_idx < vmb_get_len(str) ; ++match_cnt) |
| | 1701 | { |
| | 1702 | const char *last_str; |
| | 1703 | |
| | 1704 | /* figure out where the next search starts */ |
| | 1705 | last_str = str + VMB_LEN + start_idx; |
| | 1706 | |
| | 1707 | /* search for the pattern in the search string */ |
| | 1708 | match_idx = G_bif_tads_globals->rex_searcher->search_for_pattern( |
| | 1709 | cpat, str + VMB_LEN, last_str, vmb_get_len(str) - start_idx, |
| | 1710 | &match_len); |
| | 1711 | |
| | 1712 | /* if there was no match, there is no more replacing to do */ |
| | 1713 | if (match_idx == -1) |
| | 1714 | { |
| | 1715 | /* |
| | 1716 | * if we haven't found a match before, there's no |
| | 1717 | * replacement at all to do -- just return the original |
| | 1718 | * string unchanged |
| | 1719 | */ |
| | 1720 | if (match_cnt == 0) |
| | 1721 | { |
| | 1722 | /* no replacement - return the original search string */ |
| | 1723 | retval(vmg_ &search_val); |
| | 1724 | goto done; |
| | 1725 | } |
| | 1726 | else |
| | 1727 | { |
| | 1728 | /* we've found all of our matches - stop searching */ |
| | 1729 | break; |
| | 1730 | } |
| | 1731 | } |
| | 1732 | |
| | 1733 | /* |
| | 1734 | * We've found a match to replace. Determine how much space we |
| | 1735 | * need for the replacement pattern with its substitution |
| | 1736 | * parameters replaced with the original string's matching text. |
| | 1737 | * |
| | 1738 | * First, add in the length of the part from the start of this |
| | 1739 | * segment of the search to the matched substring. |
| | 1740 | */ |
| | 1741 | new_len += match_idx; |
| | 1742 | |
| | 1743 | /* |
| | 1744 | * now, scan the replacement string and add in its length and |
| | 1745 | * the lengths of substitution parameters |
| | 1746 | */ |
| | 1747 | for (p.set((char *)rpl + VMB_LEN), rem = vmb_get_len(rpl) ; |
| | 1748 | rem != 0 ; p.inc(&rem)) |
| | 1749 | { |
| | 1750 | /* check for '%' sequences */ |
| | 1751 | if (p.getch() == '%') |
| | 1752 | { |
| | 1753 | /* skip the '%' */ |
| | 1754 | p.inc(&rem); |
| | 1755 | |
| | 1756 | /* if there's anything left, see what we have */ |
| | 1757 | if (rem != 0) |
| | 1758 | { |
| | 1759 | switch(p.getch()) |
| | 1760 | { |
| | 1761 | case '1': |
| | 1762 | case '2': |
| | 1763 | case '3': |
| | 1764 | case '4': |
| | 1765 | case '5': |
| | 1766 | case '6': |
| | 1767 | case '7': |
| | 1768 | case '8': |
| | 1769 | case '9': |
| | 1770 | /* get the group number */ |
| | 1771 | groupno = value_of_digit(p.getch()) - 1; |
| | 1772 | |
| | 1773 | /* if this group is valid, add its length */ |
| | 1774 | if (groupno < group_cnt) |
| | 1775 | { |
| | 1776 | /* get the register */ |
| | 1777 | reg = G_bif_tads_globals->rex_searcher |
| | 1778 | ->get_group_reg(groupno); |
| | 1779 | |
| | 1780 | /* if it's been set, add its length */ |
| | 1781 | if (reg->start_ofs != -1 && reg->end_ofs != -1) |
| | 1782 | new_len += reg->end_ofs - reg->start_ofs; |
| | 1783 | } |
| | 1784 | break; |
| | 1785 | |
| | 1786 | case '*': |
| | 1787 | /* add the entire match size */ |
| | 1788 | new_len += match_len; |
| | 1789 | break; |
| | 1790 | |
| | 1791 | case '%': |
| | 1792 | /* add a single '%' */ |
| | 1793 | ++new_len; |
| | 1794 | break; |
| | 1795 | |
| | 1796 | default: |
| | 1797 | /* add the entire sequence unchanged */ |
| | 1798 | new_len += 2; |
| | 1799 | break; |
| | 1800 | } |
| | 1801 | } |
| | 1802 | } |
| | 1803 | else |
| | 1804 | { |
| | 1805 | /* count this character literally */ |
| | 1806 | new_len += p.charsize(); |
| | 1807 | } |
| | 1808 | } |
| | 1809 | |
| | 1810 | /* start the next search after the end of this match */ |
| | 1811 | start_idx += match_idx + match_len; |
| | 1812 | |
| | 1813 | /* |
| | 1814 | * if the match length was zero, skip one more character - a zero |
| | 1815 | * length match will just match again at the same spot forever, so |
| | 1816 | * once we replace it once we need to move on to avoid an infinite |
| | 1817 | * loop |
| | 1818 | */ |
| | 1819 | if (match_len == 0) |
| | 1820 | { |
| | 1821 | /* move past the input */ |
| | 1822 | start_idx += 1; |
| | 1823 | |
| | 1824 | /* we'll copy this character to the output, so make room for it */ |
| | 1825 | new_len += 1; |
| | 1826 | } |
| | 1827 | |
| | 1828 | /* |
| | 1829 | * if we're only replacing a single match, stop now; otherwise, |
| | 1830 | * continue looking |
| | 1831 | */ |
| | 1832 | if (!(flags & VMBIFTADS_REPLACE_ALL)) |
| | 1833 | break; |
| | 1834 | } |
| | 1835 | |
| | 1836 | /* add in the size of the remainder of the string after the last match */ |
| | 1837 | new_len += vmb_get_len(str) - start_idx; |
| | 1838 | |
| | 1839 | /* allocate the result string */ |
| | 1840 | ret_obj = CVmObjString::create(vmg_ FALSE, new_len); |
| | 1841 | |
| | 1842 | /* get a pointer to the result buffer */ |
| | 1843 | dstp.set(((CVmObjString *)vm_objp(vmg_ ret_obj))->cons_get_buf()); |
| | 1844 | |
| | 1845 | /* copy the initial part that we're skipping */ |
| | 1846 | if (skip_bytes != 0) |
| | 1847 | { |
| | 1848 | memcpy(dstp.getptr(), str + VMB_LEN, skip_bytes); |
| | 1849 | dstp.set(dstp.getptr() + skip_bytes); |
| | 1850 | } |
| | 1851 | |
| | 1852 | /* |
| | 1853 | * Once again, start searching from the beginning of the string. |
| | 1854 | * This time, build the result string as we go. |
| | 1855 | */ |
| | 1856 | for (start_idx = skip_bytes ; (size_t)start_idx < vmb_get_len(str) ; ) |
| | 1857 | { |
| | 1858 | const char *last_str; |
| | 1859 | |
| | 1860 | /* figure out where the next search starts */ |
| | 1861 | last_str = str + VMB_LEN + start_idx; |
| | 1862 | |
| | 1863 | /* search for the pattern */ |
| | 1864 | match_idx = G_bif_tads_globals->rex_searcher->search_for_pattern( |
| | 1865 | cpat, str + VMB_LEN, last_str, vmb_get_len(str) - start_idx, |
| | 1866 | &match_len); |
| | 1867 | |
| | 1868 | /* stop if we can't find another match */ |
| | 1869 | if (match_idx < 0) |
| | 1870 | break; |
| | 1871 | |
| | 1872 | /* copy the part up to the start of the matched text, if any */ |
| | 1873 | if (match_idx > 0) |
| | 1874 | { |
| | 1875 | /* copy the part from the last match to this match */ |
| | 1876 | memcpy(dstp.getptr(), last_str, match_idx); |
| | 1877 | |
| | 1878 | /* advance the output pointer */ |
| | 1879 | dstp.set(dstp.getptr() + match_idx); |
| | 1880 | } |
| | 1881 | |
| | 1882 | /* |
| | 1883 | * Scan the replacement string again, and this time actually |
| | 1884 | * build the result. |
| | 1885 | */ |
| | 1886 | for (p.set((char *)rpl + VMB_LEN), rem = vmb_get_len(rpl) ; |
| | 1887 | rem != 0 ; p.inc(&rem)) |
| | 1888 | { |
| | 1889 | /* check for '%' sequences */ |
| | 1890 | if (p.getch() == '%') |
| | 1891 | { |
| | 1892 | /* skip the '%' */ |
| | 1893 | p.inc(&rem); |
| | 1894 | |
| | 1895 | /* if there's anything left, see what we have */ |
| | 1896 | if (rem != 0) |
| | 1897 | { |
| | 1898 | switch(p.getch()) |
| | 1899 | { |
| | 1900 | case '1': |
| | 1901 | case '2': |
| | 1902 | case '3': |
| | 1903 | case '4': |
| | 1904 | case '5': |
| | 1905 | case '6': |
| | 1906 | case '7': |
| | 1907 | case '8': |
| | 1908 | case '9': |
| | 1909 | /* get the group number */ |
| | 1910 | groupno = value_of_digit(p.getch()) - 1; |
| | 1911 | |
| | 1912 | /* if this group is valid, add its length */ |
| | 1913 | if (groupno < group_cnt) |
| | 1914 | { |
| | 1915 | /* get the register */ |
| | 1916 | reg = G_bif_tads_globals->rex_searcher |
| | 1917 | ->get_group_reg(groupno); |
| | 1918 | |
| | 1919 | /* if it's been set, add its text */ |
| | 1920 | if (reg->start_ofs != -1 && reg->end_ofs != -1) |
| | 1921 | { |
| | 1922 | size_t glen; |
| | 1923 | |
| | 1924 | /* get the group length */ |
| | 1925 | glen = reg->end_ofs - reg->start_ofs; |
| | 1926 | |
| | 1927 | /* copy the data */ |
| | 1928 | memcpy(dstp.getptr(), |
| | 1929 | str + VMB_LEN + reg->start_ofs, glen); |
| | 1930 | |
| | 1931 | /* advance past it */ |
| | 1932 | dstp.set(dstp.getptr() + glen); |
| | 1933 | } |
| | 1934 | } |
| | 1935 | break; |
| | 1936 | |
| | 1937 | case '*': |
| | 1938 | /* add the entire matched string */ |
| | 1939 | memcpy(dstp.getptr(), last_str + match_idx, |
| | 1940 | match_len); |
| | 1941 | dstp.set(dstp.getptr() + match_len); |
| | 1942 | break; |
| | 1943 | |
| | 1944 | case '%': |
| | 1945 | /* add a single '%' */ |
| | 1946 | dstp.setch('%'); |
| | 1947 | break; |
| | 1948 | |
| | 1949 | default: |
| | 1950 | /* add the entire sequence unchanged */ |
| | 1951 | dstp.setch('%'); |
| | 1952 | dstp.setch(p.getch()); |
| | 1953 | break; |
| | 1954 | } |
| | 1955 | } |
| | 1956 | } |
| | 1957 | else |
| | 1958 | { |
| | 1959 | /* copy this character literally */ |
| | 1960 | dstp.setch(p.getch()); |
| | 1961 | } |
| | 1962 | } |
| | 1963 | |
| | 1964 | /* advance past this matched string for the next search */ |
| | 1965 | start_idx += match_idx + match_len; |
| | 1966 | |
| | 1967 | /* skip to the next character if it was a zero-length match */ |
| | 1968 | if (match_len == 0) |
| | 1969 | { |
| | 1970 | /* copy the character we're skipping to the output */ |
| | 1971 | p.set((char *)str + VMB_LEN + start_idx); |
| | 1972 | dstp.setch(p.getch()); |
| | 1973 | |
| | 1974 | /* move on to the next character */ |
| | 1975 | start_idx += 1; |
| | 1976 | } |
| | 1977 | |
| | 1978 | /* if we're only performing a single replacement, stop now */ |
| | 1979 | if (!(flags & VMBIFTADS_REPLACE_ALL)) |
| | 1980 | break; |
| | 1981 | } |
| | 1982 | |
| | 1983 | /* add the part after the end of the matched text */ |
| | 1984 | if ((size_t)start_idx < vmb_get_len(str)) |
| | 1985 | memcpy(dstp.getptr(), str + VMB_LEN + start_idx, |
| | 1986 | vmb_get_len(str) - start_idx); |
| | 1987 | |
| | 1988 | /* return the string */ |
| | 1989 | retval_obj(vmg_ ret_obj); |
| | 1990 | |
| | 1991 | done: |
| | 1992 | /* discard the garbage collection protection references */ |
| | 1993 | G_stk->discard(3); |
| | 1994 | |
| | 1995 | /* if we created the pattern string, delete it */ |
| | 1996 | if (cpat != 0 && cpat_is_ours) |
| | 1997 | CRegexParser::free_pattern(cpat); |
| | 1998 | } |
| | 1999 | |
| | 2000 | /* ------------------------------------------------------------------------ */ |
| | 2001 | /* |
| | 2002 | * savepoint - establish an undo savepoint |
| | 2003 | */ |
| | 2004 | void CVmBifTADS::savepoint(VMG_ uint argc) |
| | 2005 | { |
| | 2006 | /* check arguments */ |
| | 2007 | check_argc(vmg_ argc, 0); |
| | 2008 | |
| | 2009 | /* establish the savepoint */ |
| | 2010 | G_undo->create_savept(vmg0_); |
| | 2011 | } |
| | 2012 | |
| | 2013 | /* |
| | 2014 | * undo - undo changes to most recent savepoint |
| | 2015 | */ |
| | 2016 | void CVmBifTADS::undo(VMG_ uint argc) |
| | 2017 | { |
| | 2018 | /* check arguments */ |
| | 2019 | check_argc(vmg_ argc, 0); |
| | 2020 | |
| | 2021 | /* if no undo is available, return nil to indicate that we can't undo */ |
| | 2022 | if (G_undo->get_savept_cnt() == 0) |
| | 2023 | { |
| | 2024 | /* we can't undo */ |
| | 2025 | retval_nil(vmg0_); |
| | 2026 | } |
| | 2027 | else |
| | 2028 | { |
| | 2029 | /* undo to the savepoint */ |
| | 2030 | G_undo->undo_to_savept(vmg0_); |
| | 2031 | |
| | 2032 | /* tell the caller that we succeeded */ |
| | 2033 | retval_true(vmg0_); |
| | 2034 | } |
| | 2035 | } |
| | 2036 | |
| | 2037 | /* ------------------------------------------------------------------------ */ |
| | 2038 | /* |
| | 2039 | * save |
| | 2040 | */ |
| | 2041 | void CVmBifTADS::save(VMG_ uint argc) |
| | 2042 | { |
| | 2043 | char fname[OSFNMAX]; |
| | 2044 | CVmFile *file; |
| | 2045 | osfildef *fp; |
| | 2046 | |
| | 2047 | /* check arguments */ |
| | 2048 | check_argc(vmg_ argc, 1); |
| | 2049 | |
| | 2050 | /* get the filename as a null-terminated string */ |
| | 2051 | pop_str_val_fname(vmg_ fname, sizeof(fname)); |
| | 2052 | |
| | 2053 | /* open the file */ |
| | 2054 | fp = osfoprwtb(fname, OSFTT3SAV); |
| | 2055 | if (fp == 0) |
| | 2056 | err_throw(VMERR_CREATE_FILE); |
| | 2057 | |
| | 2058 | /* set up the file writer */ |
| | 2059 | file = new CVmFile(); |
| | 2060 | file->set_file(fp, 0); |
| | 2061 | |
| | 2062 | err_try |
| | 2063 | { |
| | 2064 | /* save the state */ |
| | 2065 | CVmSaveFile::save(vmg_ file); |
| | 2066 | } |
| | 2067 | err_finally |
| | 2068 | { |
| | 2069 | /* close the file */ |
| | 2070 | delete file; |
| | 2071 | } |
| | 2072 | err_end; |
| | 2073 | } |
| | 2074 | |
| | 2075 | /* |
| | 2076 | * restore |
| | 2077 | */ |
| | 2078 | void CVmBifTADS::restore(VMG_ uint argc) |
| | 2079 | { |
| | 2080 | char fname[OSFNMAX]; |
| | 2081 | CVmFile *file; |
| | 2082 | osfildef *fp; |
| | 2083 | int err; |
| | 2084 | |
| | 2085 | /* check arguments */ |
| | 2086 | check_argc(vmg_ argc, 1); |
| | 2087 | |
| | 2088 | /* get the filename as a null-terminated string */ |
| | 2089 | pop_str_val_fname(vmg_ fname, sizeof(fname)); |
| | 2090 | |
| | 2091 | /* open the file */ |
| | 2092 | fp = osfoprb(fname, OSFTT3SAV); |
| | 2093 | if (fp == 0) |
| | 2094 | err_throw(VMERR_FILE_NOT_FOUND); |
| | 2095 | |
| | 2096 | /* set up the file reader */ |
| | 2097 | file = new CVmFile(); |
| | 2098 | file->set_file(fp, 0); |
| | 2099 | |
| | 2100 | err_try |
| | 2101 | { |
| | 2102 | /* restore the state */ |
| | 2103 | err = CVmSaveFile::restore(vmg_ file); |
| | 2104 | } |
| | 2105 | err_finally |
| | 2106 | { |
| | 2107 | /* close the file */ |
| | 2108 | delete file; |
| | 2109 | } |
| | 2110 | err_end; |
| | 2111 | |
| | 2112 | /* if an error occurred, throw an exception */ |
| | 2113 | if (err != 0) |
| | 2114 | err_throw(err); |
| | 2115 | } |
| | 2116 | |
| | 2117 | /* |
| | 2118 | * restart |
| | 2119 | */ |
| | 2120 | void CVmBifTADS::restart(VMG_ uint argc) |
| | 2121 | { |
| | 2122 | /* check arguments */ |
| | 2123 | check_argc(vmg_ argc, 0); |
| | 2124 | |
| | 2125 | /* reset the VM to the image file's initial state */ |
| | 2126 | CVmSaveFile::reset(vmg0_); |
| | 2127 | } |
| | 2128 | |
| | 2129 | |
| | 2130 | /* ------------------------------------------------------------------------ */ |
| | 2131 | /* |
| | 2132 | * Get the maximum value from a set of argument |
| | 2133 | */ |
| | 2134 | void CVmBifTADS::get_max(VMG_ uint argc) |
| | 2135 | { |
| | 2136 | uint i; |
| | 2137 | vm_val_t cur_max; |
| | 2138 | |
| | 2139 | /* make sure we have at least one argument */ |
| | 2140 | if (argc < 1) |
| | 2141 | err_throw(VMERR_WRONG_NUM_OF_ARGS); |
| | 2142 | |
| | 2143 | /* start with the first argument as the presumptive maximum */ |
| | 2144 | cur_max = *G_stk->get(0); |
| | 2145 | |
| | 2146 | /* compare each argument in turn */ |
| | 2147 | for (i = 1 ; i < argc ; ++i) |
| | 2148 | { |
| | 2149 | /* |
| | 2150 | * compare this value to the maximum so far; if this value is |
| | 2151 | * greater, it becomes the new maximum so far |
| | 2152 | */ |
| | 2153 | if (G_stk->get(i)->compare_to(vmg_ &cur_max) > 0) |
| | 2154 | cur_max = *G_stk->get(i); |
| | 2155 | } |
| | 2156 | |
| | 2157 | /* discard the arguments */ |
| | 2158 | G_stk->discard(argc); |
| | 2159 | |
| | 2160 | /* return the maximum value */ |
| | 2161 | retval(vmg_ &cur_max); |
| | 2162 | } |
| | 2163 | |
| | 2164 | /* |
| | 2165 | * Get the minimum value from a set of argument |
| | 2166 | */ |
| | 2167 | void CVmBifTADS::get_min(VMG_ uint argc) |
| | 2168 | { |
| | 2169 | uint i; |
| | 2170 | vm_val_t cur_min; |
| | 2171 | |
| | 2172 | /* make sure we have at least one argument */ |
| | 2173 | if (argc < 1) |
| | 2174 | err_throw(VMERR_WRONG_NUM_OF_ARGS); |
| | 2175 | |
| | 2176 | /* start with the first argument as the presumptive minimum */ |
| | 2177 | cur_min = *G_stk->get(0); |
| | 2178 | |
| | 2179 | /* compare each argument in turn */ |
| | 2180 | for (i = 1 ; i < argc ; ++i) |
| | 2181 | { |
| | 2182 | /* |
| | 2183 | * compare this value to the minimum so far; if this value is |
| | 2184 | * less, it becomes the new minimum so far |
| | 2185 | */ |
| | 2186 | if (G_stk->get(i)->compare_to(vmg_ &cur_min) < 0) |
| | 2187 | cur_min = *G_stk->get(i); |
| | 2188 | } |
| | 2189 | |
| | 2190 | /* discard the arguments */ |
| | 2191 | G_stk->discard(argc); |
| | 2192 | |
| | 2193 | /* return the minimum value */ |
| | 2194 | retval(vmg_ &cur_min); |
| | 2195 | } |
| | 2196 | |
| | 2197 | /* ------------------------------------------------------------------------ */ |
| | 2198 | /* |
| | 2199 | * makeString - construct a string by repeating a character; by |
| | 2200 | * converting a unicode code point to a string; or by converting a list |
| | 2201 | * of unicode code points to a string |
| | 2202 | */ |
| | 2203 | void CVmBifTADS::make_string(VMG_ uint argc) |
| | 2204 | { |
| | 2205 | vm_val_t val; |
| | 2206 | long rpt; |
| | 2207 | vm_obj_id_t new_str_obj; |
| | 2208 | CVmObjString *new_str; |
| | 2209 | size_t new_str_len; |
| | 2210 | char *new_strp; |
| | 2211 | const char *lstp = 0; |
| | 2212 | const char *strp = 0; |
| | 2213 | size_t len; |
| | 2214 | size_t i; |
| | 2215 | utf8_ptr dst; |
| | 2216 | |
| | 2217 | /* check arguments */ |
| | 2218 | check_argc_range(vmg_ argc, 1, 2); |
| | 2219 | |
| | 2220 | /* get the base value */ |
| | 2221 | G_stk->pop(&val); |
| | 2222 | |
| | 2223 | /* if there's a repeat count, get it */ |
| | 2224 | rpt = (argc >= 2 ? pop_long_val(vmg0_) : 1); |
| | 2225 | |
| | 2226 | /* if the repeat count is less than or equal to zero, make it 1 */ |
| | 2227 | if (rpt < 1) |
| | 2228 | rpt = 1; |
| | 2229 | |
| | 2230 | /* leave the original value on the stack to protect it from GC */ |
| | 2231 | G_stk->push(&val); |
| | 2232 | |
| | 2233 | /* |
| | 2234 | * see what we have, and calculate how much space we'll need for the |
| | 2235 | * result string |
| | 2236 | */ |
| | 2237 | switch(val.typ) |
| | 2238 | { |
| | 2239 | case VM_LIST: |
| | 2240 | /* it's a list of integers giving unicode character values */ |
| | 2241 | lstp = G_const_pool->get_ptr(val.val.ofs); |
| | 2242 | |
| | 2243 | do_list: |
| | 2244 | /* get the list count */ |
| | 2245 | len = vmb_get_len(lstp); |
| | 2246 | |
| | 2247 | /* |
| | 2248 | * Run through the list and get the size of each character, so |
| | 2249 | * we can determine how long the string will have to be. |
| | 2250 | */ |
| | 2251 | for (new_str_len = 0, i = 1 ; i <= len ; ++i) |
| | 2252 | { |
| | 2253 | vm_val_t ele_val; |
| | 2254 | |
| | 2255 | /* get this element */ |
| | 2256 | CVmObjList::index_list(vmg_ &ele_val, lstp, i); |
| | 2257 | |
| | 2258 | /* if it's not an integer, it's an error */ |
| | 2259 | if (ele_val.typ != VM_INT) |
| | 2260 | err_throw(VMERR_INT_VAL_REQD); |
| | 2261 | |
| | 2262 | /* add this character's byte size to the string size */ |
| | 2263 | new_str_len += |
| | 2264 | utf8_ptr::s_wchar_size((wchar_t)ele_val.val.intval); |
| | 2265 | } |
| | 2266 | break; |
| | 2267 | |
| | 2268 | case VM_SSTRING: |
| | 2269 | /* get the string pointer */ |
| | 2270 | strp = G_const_pool->get_ptr(val.val.ofs); |
| | 2271 | |
| | 2272 | do_string: |
| | 2273 | /* |
| | 2274 | * it's a string - the output length is the same as the input |
| | 2275 | * length |
| | 2276 | */ |
| | 2277 | new_str_len = vmb_get_len(strp); |
| | 2278 | break; |
| | 2279 | |
| | 2280 | case VM_INT: |
| | 2281 | /* |
| | 2282 | * it's an integer giving a unicode character value - we just |
| | 2283 | * need enough space to store this particular character |
| | 2284 | */ |
| | 2285 | new_str_len = utf8_ptr::s_wchar_size((wchar_t)val.val.intval); |
| | 2286 | break; |
| | 2287 | |
| | 2288 | case VM_OBJ: |
| | 2289 | /* check to see if it's a string */ |
| | 2290 | if ((strp = val.get_as_string(vmg0_)) != 0) |
| | 2291 | goto do_string; |
| | 2292 | |
| | 2293 | /* check to see if it's a list */ |
| | 2294 | if ((lstp = val.get_as_list(vmg0_)) != 0) |
| | 2295 | goto do_list; |
| | 2296 | |
| | 2297 | /* it's invalid */ |
| | 2298 | err_throw(VMERR_BAD_TYPE_BIF); |
| | 2299 | break; |
| | 2300 | |
| | 2301 | default: |
| | 2302 | /* other types are invalid */ |
| | 2303 | err_throw(VMERR_BAD_TYPE_BIF); |
| | 2304 | break; |
| | 2305 | } |
| | 2306 | |
| | 2307 | /* |
| | 2308 | * if the length times the repeat count would be over the maximum |
| | 2309 | * 16-bit string length, it's an error |
| | 2310 | */ |
| | 2311 | if (new_str_len * rpt > 0xffffL - VMB_LEN) |
| | 2312 | err_throw(VMERR_BAD_VAL_BIF); |
| | 2313 | |
| | 2314 | /* multiply the length by the repeat count */ |
| | 2315 | new_str_len *= rpt; |
| | 2316 | |
| | 2317 | /* allocate the string and gets its buffer */ |
| | 2318 | new_str_obj = CVmObjString::create(vmg_ FALSE, new_str_len); |
| | 2319 | new_str = (CVmObjString *)vm_objp(vmg_ new_str_obj); |
| | 2320 | new_strp = new_str->cons_get_buf(); |
| | 2321 | |
| | 2322 | /* set up the destination pointer */ |
| | 2323 | dst.set(new_strp); |
| | 2324 | |
| | 2325 | /* run through the number of iterations requested */ |
| | 2326 | for ( ; rpt != 0 ; --rpt) |
| | 2327 | { |
| | 2328 | /* build one iteration of the string, according to the type */ |
| | 2329 | if (lstp != 0) |
| | 2330 | { |
| | 2331 | /* run through the list */ |
| | 2332 | for (i = 1 ; i <= len ; ++i) |
| | 2333 | { |
| | 2334 | vm_val_t ele_val; |
| | 2335 | |
| | 2336 | /* get this element */ |
| | 2337 | CVmObjList::index_list(vmg_ &ele_val, lstp, i); |
| | 2338 | |
| | 2339 | /* add this character to the string */ |
| | 2340 | dst.setch((wchar_t)ele_val.val.intval); |
| | 2341 | } |
| | 2342 | } |
| | 2343 | else if (strp != 0) |
| | 2344 | { |
| | 2345 | /* copy the string's contents into the output string */ |
| | 2346 | memcpy(dst.getptr(), strp + VMB_LEN, vmb_get_len(strp)); |
| | 2347 | |
| | 2348 | /* advance past the bytes we copied */ |
| | 2349 | dst.set(dst.getptr() + vmb_get_len(strp)); |
| | 2350 | } |
| | 2351 | else |
| | 2352 | { |
| | 2353 | /* set this int value */ |
| | 2354 | dst.setch((wchar_t)val.val.intval); |
| | 2355 | } |
| | 2356 | } |
| | 2357 | |
| | 2358 | /* return the new string */ |
| | 2359 | retval_obj(vmg_ new_str_obj); |
| | 2360 | |
| | 2361 | /* discard the GC protection */ |
| | 2362 | G_stk->discard(); |
| | 2363 | } |
| | 2364 | |
| | 2365 | /* ------------------------------------------------------------------------ */ |
| | 2366 | /* |
| | 2367 | * getFuncParams |
| | 2368 | */ |
| | 2369 | void CVmBifTADS::get_func_params(VMG_ uint argc) |
| | 2370 | { |
| | 2371 | vm_val_t val; |
| | 2372 | vm_val_t func; |
| | 2373 | CVmFuncPtr hdr; |
| | 2374 | vm_obj_id_t lst_obj; |
| | 2375 | CVmObjList *lst; |
| | 2376 | |
| | 2377 | /* check arguments */ |
| | 2378 | check_argc(vmg_ argc, 1); |
| | 2379 | |
| | 2380 | /* the argument can be an anonymous function object or function pointer */ |
| | 2381 | if (G_stk->get(0)->typ == VM_OBJ |
| | 2382 | && G_predef->obj_call_prop != VM_INVALID_PROP) |
| | 2383 | { |
| | 2384 | uint argc = 0; |
| | 2385 | vm_obj_id_t srcobj; |
| | 2386 | |
| | 2387 | /* it's an anonymous function - get the object */ |
| | 2388 | G_interpreter->pop_obj(vmg_ &func); |
| | 2389 | |
| | 2390 | /* retrieve its ObjectCallProp value, and make sure it's a function */ |
| | 2391 | if (!vm_objp(vmg_ func.val.obj)->get_prop( |
| | 2392 | vmg_ G_predef->obj_call_prop, &func, func.val.obj, &srcobj, &argc) |
| | 2393 | || func.typ != VM_FUNCPTR) |
| | 2394 | err_throw(VMERR_FUNCPTR_VAL_REQD); |
| | 2395 | } |
| | 2396 | else |
| | 2397 | { |
| | 2398 | /* it's a simple function pointer - retrieve it */ |
| | 2399 | G_interpreter->pop_funcptr(vmg_ &func); |
| | 2400 | } |
| | 2401 | |
| | 2402 | /* set up a pointer to the function header */ |
| | 2403 | hdr.set((const uchar *)G_code_pool->get_ptr(func.val.ofs)); |
| | 2404 | |
| | 2405 | /* |
| | 2406 | * Allocate our return list. We need three elements: [minArgs, |
| | 2407 | * optionalArgs, isVarargs]. |
| | 2408 | */ |
| | 2409 | lst_obj = CVmObjList::create(vmg_ FALSE, 3); |
| | 2410 | |
| | 2411 | /* get the list object, properly cast */ |
| | 2412 | lst = (CVmObjList *)vm_objp(vmg_ lst_obj); |
| | 2413 | |
| | 2414 | /* set the minimum argument count */ |
| | 2415 | val.set_int(hdr.get_min_argc()); |
| | 2416 | lst->cons_set_element(0, &val); |
| | 2417 | |
| | 2418 | /* |
| | 2419 | * set the optional argument count (which is always zero for a |
| | 2420 | * function, since there is no way to specify named optional arguments |
| | 2421 | * for a function) |
| | 2422 | */ |
| | 2423 | val.set_int(0); |
| | 2424 | lst->cons_set_element(1, &val); |
| | 2425 | |
| | 2426 | /* set the varargs flag */ |
| | 2427 | val.set_logical(hdr.is_varargs()); |
| | 2428 | lst->cons_set_element(2, &val); |
| | 2429 | |
| | 2430 | /* return the list */ |
| | 2431 | retval_obj(vmg_ lst_obj); |
| | 2432 | |
| | 2433 | /* |
| | 2434 | * re-touch the currently executing method's code page to make sure |
| | 2435 | * it's the most recently used item in the cache, to avoid swapping it |
| | 2436 | * out |
| | 2437 | */ |
| | 2438 | G_interpreter->touch_entry_ptr_page(vmg0_); |
| | 2439 | } |
| | 2440 | |