cfad47cfa3/tads3/vmbiftad.cpp

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