cfad47cfa3/tads2/vocab.c

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#ifdef RCSID
2
static char RCSid[] =
3
"$Header: d:/cvsroot/tads/TADS2/vocab.c,v 1.5 1999/07/11 00:46:35 MJRoberts Exp $";
4
#endif
5
6
/* 
7
 *   Copyright (c) 1987-1992 by 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
  vocab  - TADS run-time player command parser
15
Function
16
  Player command parser
17
Notes
18
  This version of the parser is for TADS 2.0.
19
Returns
20
  0 for success, 1 for failure
21
Modified
22
  04/11/99 CNebel        - Fix C++ errors.
23
  03/11/92 MJRoberts     - TADS 2.0
24
  11/20/91 MJRoberts     - fix isVisible operation
25
  11/02/91 MJRoberts     - fix strObj.value problem
26
  08/13/91 MJRoberts     - add him/her support
27
  08/12/91 MJRoberts     - make strObj.value an RSTRING (preprsfn arg, too)
28
  08/08/91 MJRoberts     - add preprsfn (preparse function)
29
  04/12/91 MJRoberts     - check abortcmd: if true, skip rest of cmd line
30
  03/10/91 MJRoberts     - moved getstring() to getstr.c for modularity,
31
                           pick up John's qa-scripter mods
32
  06/28/89 MJRoberts     - call rtreset() before pardonfn invocations
33
  11/04/88 MJRoberts     - fix "it" and "them"
34
  10/30/88 MJRoberts     - new "version 6" game/parser interface
35
  12/27/87 MJRoberts     - created 
36
*/
37
38
#include <stdio.h>
39
#include <ctype.h>
40
#include <string.h>
41
#include <stdlib.h>
42
#include <stdarg.h>
43
44
#include "os.h"
45
#include "err.h"
46
#include "voc.h"
47
#include "tio.h"
48
#include "mcm.h"
49
#include "obj.h"
50
#include "prp.h"
51
#include "run.h"
52
#include "lst.h"
53
54
55
static char *type_names[] =
56
{
57
    "article", "adj", "noun", "prep", "verb", "special", "plural",
58
    "unknown"
59
};
60
61
/* array of flag values for words by part of speech */
62
static int voctype[] =
63
{ 0, 0, VOCT_VERB, VOCT_NOUN, VOCT_ADJ, VOCT_PREP, VOCT_ARTICLE };
64
65
/* ------------------------------------------------------------------------ */
66
/*
67
 *   Allocate and push a list, given the number of bytes needed for the
68
 *   elements of the list.  
69
 */
70
uchar *voc_push_list_siz(voccxdef *ctx, uint lstsiz)
71
{
72
    runcxdef *rcx = ctx->voccxrun;
73
    runsdef val;
74
    uchar *lstp;
75
76
    /* add in the size needed for the list's length prefix */
77
    lstsiz += 2;
78
79
    /* allocate space in the heap */
80
    runhres(rcx, lstsiz, 0);
81
82
    /* set up a stack value to push */
83
    val.runstyp = DAT_LIST;
84
    val.runsv.runsvstr = lstp = ctx->voccxrun->runcxhp;
85
86
    /* set up the list's length prefix */
87
    oswp2(lstp, lstsiz);
88
    lstp += 2;
89
90
    /* commit the space in the heap */
91
    rcx->runcxhp += lstsiz;
92
93
    /* push the list value (repush, since we can use the original copy) */
94
    runrepush(rcx, &val);
95
96
    /* return the list element pointer */
97
    return lstp;
98
}
99
100
/*
101
 *   Allocate and push a list.  Returns a pointer to the space for the
102
 *   list's first element in the heap.  
103
 */
104
static uchar *voc_push_list(voccxdef *ctx, int ele_count, int ele_size)
105
{
106
    uint lstsiz;
107
108
    /* 
109
     *   Figure the list size - we need space for the given number of
110
     *   elements of the given size; in addition, each element requires
111
     *   one byte of overhead for its type prefix.  
112
     */
113
    lstsiz = (uint)(ele_count * (1 + ele_size));
114
115
    /* allocate and return the list */
116
    return voc_push_list_siz(ctx, lstsiz);
117
}
118
119
/*
120
 *   Push a list of numbers 
121
 */
122
static void voc_push_numlist(voccxdef *ctx, uint numlist[], int cnt)
123
{
124
    int i;
125
    uchar *lstp;
126
127
    /* allocate space for the list of numbers */
128
    lstp = voc_push_list(ctx, cnt, 4);
129
130
    /* enter the list elements */
131
    for (i = 0 ; i < cnt ; ++i)
132
    {
133
        /* add the type prefix */
134
        *lstp++ = DAT_NUMBER;
135
136
        /* add the value */
137
        oswp4(lstp, numlist[i]);
138
        lstp += 4;
139
    }
140
}
141
142
/*
143
 *   Push a list of object ID's obtained from a vocoldef array 
144
 */
145
void voc_push_vocoldef_list(voccxdef *ctx, vocoldef *objlist, int cnt)
146
{
147
    int i;
148
    uchar *lstp;
149
    uint lstsiz;
150
151
    /* 
152
     *   count the size - we need 3 bytes per object (1 for type plus 2
153
     *   for the value), and 1 byte per nil 
154
     */
155
    for (lstsiz = 0, i = 0 ; i < cnt ; ++i)
156
        lstsiz += (objlist[i].vocolobj == MCMONINV ? 1 : 3);
157
158
    /* allocate space for the list */
159
    lstp = voc_push_list_siz(ctx, lstsiz);
160
161
    /* enter the list elements */
162
    for (i = 0 ; i < cnt ; ++i)
163
    {
164
        if (objlist[i].vocolobj == MCMONINV)
165
        {
166
            /* store the nil */
167
            *lstp++ = DAT_NIL;
168
        }
169
        else
170
        {
171
            /* add the type prefix */
172
            *lstp++ = DAT_OBJECT;
173
174
            /* add the value */
175
            oswp2(lstp, objlist[i].vocolobj);
176
            lstp += 2;
177
        }
178
    }
179
}
180
181
/*
182
 *   Push a list of object ID's 
183
 */
184
void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt)
185
{
186
    int i;
187
    uchar *lstp;
188
    uint lstsiz;
189
190
    /* 
191
     *   count the size - we need 3 bytes per object (1 for type plus 2
192
     *   for the value), and 1 byte per nil 
193
     */
194
    for (lstsiz = 0, i = 0 ; i < cnt ; ++i)
195
        lstsiz += (objlist[i] == MCMONINV ? 1 : 3);
196
197
    /* allocate space for the list */
198
    lstp = voc_push_list_siz(ctx, lstsiz);
199
200
    /* enter the list elements */
201
    for (i = 0 ; i < cnt ; ++i)
202
    {
203
        if (objlist[i] == MCMONINV)
204
        {
205
            /* store the nil */
206
            *lstp++ = DAT_NIL;
207
        }
208
        else
209
        {
210
            /* add the type prefix */
211
            *lstp++ = DAT_OBJECT;
212
            
213
            /* add the value */
214
            oswp2(lstp, objlist[i]);
215
            lstp += 2;
216
        }
217
    }
218
}
219
220
/*
221
 *   Push a list of strings, where the strings are stored in memory, one
222
 *   after the other, each string separated from the next with a null
223
 *   byte.  The list is bounded by firstwrd and lastwrd, inclusive of
224
 *   both.  
225
 */
226
static void voc_push_strlist(voccxdef *ctx, char *firstwrd, char *lastwrd)
227
{
228
    size_t curlen;
229
    char *p;
230
    uint lstsiz;
231
    uchar *lstp;
232
233
    /*
234
     *   Determine how much space we need for the word list.  For each
235
     *   entry, we need one byte for the type prefix, two bytes for the
236
     *   length prefix, and the bytes of the string itself.  
237
     */
238
    for (lstsiz = 0, p = firstwrd ; p != 0 && p <= lastwrd ; p += curlen + 1)
239
    {
240
        curlen = strlen(p);
241
        lstsiz += curlen + (1+2);
242
    }
243
244
    /* allocate space for the word list */
245
    lstp = voc_push_list_siz(ctx, lstsiz);
246
247
    /* enter the list elements */
248
    for (p = firstwrd ; p != 0 && p <= lastwrd ; p += curlen + 1)
249
    {
250
        /* add the type prefix */
251
        *lstp++ = DAT_SSTRING;
252
253
        /* add the length prefix for this string */
254
        curlen = strlen(p);
255
        oswp2(lstp, curlen + 2);
256
        lstp += 2;
257
258
        /* add this string */
259
        memcpy(lstp, p, curlen);
260
        lstp += curlen;
261
    }
262
}
263
264
/*
265
 *   Push a list of strings, taking the strings from an array.  
266
 */
267
static void voc_push_strlist_arr(voccxdef *ctx, char *wordlist[], int cnt)
268
{
269
    int i;
270
    char **p;
271
    uint lstsiz;
272
    uchar *lstp;
273
274
    /* 
275
     *   Add up the lengths of the strings in the array.  For each
276
     *   element, we need space for the string's bytes, plus two bytes for
277
     *   the length prefix, plus one byte for the type prefix.  
278
     */
279
    for (lstsiz = 0, p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
280
        lstsiz += strlen(*p) + 3;
281
282
    /* allocate space for the list */
283
    lstp = voc_push_list_siz(ctx, lstsiz);
284
285
    /* enter the list elements */
286
    for (p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
287
    {
288
        size_t curlen;
289
290
        /* add the type prefix */
291
        *lstp++ = DAT_SSTRING;
292
293
        /* add the length prefix for this string */
294
        curlen = strlen(*p);
295
        oswp2(lstp, curlen + 2);
296
        lstp += 2;
297
298
        /* add this string */
299
        memcpy(lstp, *p, curlen);
300
        lstp += curlen;
301
    }
302
}
303
304
/*
305
 *   Push a list of strings, taking the strings from an array that was
306
 *   prepared by the parser tokenizer.  This is almost the same as pushing
307
 *   a regular string array, with the difference that we must recognize
308
 *   the special format that the tokenizer uses to store string tokens.  
309
 */
310
static void voc_push_toklist(voccxdef *ctx, char *wordlist[], int cnt)
311
{
312
    int i;
313
    char **p;
314
    uint lstsiz;
315
    uchar *lstp;
316
    size_t cur_len;
317
318
    /* 
319
     *   Add up the lengths of the strings in the array.  For each
320
     *   element, we need space for the string's bytes, plus two bytes for
321
     *   the length prefix, plus one byte for the type prefix.  
322
     */
323
    for (lstsiz = 0, p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
324
    {
325
        /* 
326
         *   get the length of the current token - check what kind of
327
         *   token we have, since we must sense the length of different
328
         *   token types in different ways 
329
         */
330
        if (**p == '"')
331
        {
332
            /* 
333
             *   It's a string token - the string follows with a two-byte
334
             *   length prefix; add two bytes for the open and close quote
335
             *   characters that we'll add to the output string.  Note
336
             *   that we must deduct two bytes from the prefix length,
337
             *   because the prefix includes the size of the prefix
338
             *   itself, which we're not copying and will account for
339
             *   separately in the result string.
340
             */
341
            cur_len = osrp2(*p + 1) - 2 + 2;
342
        }
343
        else
344
        {
345
            /* for anything else, it's just a null-terminated string */
346
            cur_len = strlen(*p);
347
        }
348
349
        /* add the current length to the total so far */
350
        lstsiz += cur_len + 3;
351
    }
352
353
    /* allocate space for the list */
354
    lstp = voc_push_list_siz(ctx, lstsiz);
355
356
    /* enter the list elements */
357
    for (p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
358
    {
359
        char *cur_ptr;
360
        size_t copy_len;
361
        
362
        /* add the type prefix */
363
        *lstp++ = DAT_SSTRING;
364
365
        /* get the information for the string based on the type */
366
        if (**p == '"')
367
        {
368
            /* 
369
             *   it's a string - use the length prefix (deducting two
370
             *   bytes for the prefix itself, which we're not copying) 
371
             */
372
            copy_len = osrp2(*p + 1) - 2;
373
374
            /* add space in the result for the open and close quotes */
375
            cur_len = copy_len + 2;
376
377
            /* the string itself follows the length prefix and '"' flag */
378
            cur_ptr = *p + 3;
379
        }
380
        else
381
        {
382
            /* for anything else, it's just a null-terminated string */
383
            cur_len = copy_len = strlen(*p);
384
            cur_ptr = *p;
385
        }
386
387
        /* write the length prefix for this string */
388
        oswp2(lstp, cur_len + 2);
389
        lstp += 2;
390
391
        /* add the open quote if this is a quoted string */
392
        if (**p == '"')
393
            *lstp++ = '"';
394
395
        /* add this string */
396
        memcpy(lstp, cur_ptr, copy_len);
397
        lstp += copy_len;
398
399
        /* add the close quote if it's a quoted string */
400
        if (**p == '"')
401
            *lstp++ = '"';
402
    }
403
}
404
405
/* ------------------------------------------------------------------------ */
406
/*
407
 *   Read a command from the keyboard, doing all necessary output flushing
408
 *   and prompting.
409
 */
410
int vocread(voccxdef *ctx, objnum actor, objnum verb,
411
            char *buf, int bufl, int type)
412
{
413
    char *prompt;
414
    int ret;
415
416
    /* presume we'll return success */
417
    ret = VOCREAD_OK;
418
419
    /* make sure output capturing is off */
420
    tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE);
421
    tioclrcapture(ctx->voccxtio);
422
423
    /* 
424
     *   Clear out the command buffer.  This is important for the
425
     *   timeout-based command reader, since it will take what's in the
426
     *   buffer as the initial contents of the command line; this lets us
427
     *   remember any partial line that the player entered before a
428
     *   timeout interrupted their typing and redisplay the original
429
     *   partial line on the next command line.  Initially, there's no
430
     *   partial line, so clear it out.  
431
     */
432
    buf[0] = '\0';
433
434
    /* show the game-defined prompt, if appropriate */
435
    if (ctx->voccxprom != MCMONINV)
436
    {
437
        runpnum(ctx->voccxrun, (long)type);
438
        runfn(ctx->voccxrun, ctx->voccxprom, 1);
439
        tioflushn(ctx->voccxtio, 0);
440
        prompt = "";
441
    }
442
    else
443
    {
444
        /* there's no game-defined prompt - use our default */
445
        tioblank(tio);
446
        prompt = ">";
447
    }
448
    
449
    /* get a line of input */
450
    if (tiogets(ctx->voccxtio, prompt, buf, bufl))
451
        errsig(ctx->voccxerr, ERR_RUNQUIT);
452
    
453
    /* abort immediately if we see the special panic command */
454
    if (!strcmp(buf, "$$ABEND"))
455
    {
456
        /* make sure any script file is closed */
457
        qasclose();
458
        
459
        /* use the OS-level termination */
460
        os_term(OSEXFAIL);
461
462
        /* if that returned, signal a quit */
463
        errsig(ctx->voccxerr, ERR_RUNQUIT);
464
    }
465
    
466
    /* call the post-prompt function if defined */
467
    if (ctx->voccxpostprom != MCMONINV)
468
    {
469
        runpnum(ctx->voccxrun, (long)type);
470
        runfn(ctx->voccxrun, ctx->voccxpostprom, 1);
471
    }
472
473
    /* 
474
     *   If this isn't a type "0" input, and preparseExt() is defined, call
475
     *   it.  Don't call preparseExt() for type "0" inputs, since these will
476
     *   be handled via the conventional preparse().  
477
     */
478
    if (ctx->voccxpre2 != MCMONINV && type != 0)
479
    {
480
        uchar *s;
481
        size_t len;
482
483
        /* push the arguments - actor, verb, str, type */
484
        runpnum(ctx->voccxrun, (long)type);
485
        runpstr(ctx->voccxrun, buf, (int)strlen(buf), 0);
486
        runpobj(ctx->voccxrun, verb);
487
        runpobj(ctx->voccxrun, actor);
488
489
        /* call preparseExt() */
490
        runfn(ctx->voccxrun, ctx->voccxpre2, 4);
491
492
        /* check the result */
493
        switch(runtostyp(ctx->voccxrun))
494
        {
495
        case DAT_SSTRING:
496
            /* 
497
             *   They returned a string.  Replace the input buffer we read
498
             *   with the new string.  Pop the new string and scan its length
499
             *   prefix.  
500
             */
501
            s = runpopstr(ctx->voccxrun);
502
            len = osrp2(s) - 2;
503
            s += 2;
504
505
            /* 
506
             *   limit the size we copy to our buffer length (leaving space
507
             *   for null termination) 
508
             */
509
            if (len > (size_t)bufl - 1)
510
                len = bufl - 1;
511
512
            /* copy the new command string into our buffer */
513
            memcpy(buf, s, len);
514
515
            /* null-terminate the result */
516
            buf[len] = '\0';
517
518
            /* proceed as normal with the new string */
519
            break;
520
521
        case DAT_TRUE:
522
            /* 
523
             *   they simply want to keep the current string as it is -
524
             *   proceed as normal 
525
             */
526
            break;
527
528
        case DAT_NIL:
529
            /* 
530
             *   They want to skip the special interpretation of the input
531
             *   and proceed directly to treating the input as a brand new
532
             *   command.  The caller will have to take care of the details;
533
             *   we need only indicate this to the caller through our "redo"
534
             *   result code.  
535
             */
536
            ret = VOCREAD_REDO;
537
            break;
538
        }
539
    }
540
541
    /* return our result */
542
    return ret;
543
}
544
545
/*
546
 *   Compare a pair of words, truncated to six characters or the
547
 *   length of the first word, whichever is longer.  (The first word is
548
 *   the user's entry, the second is the reference word in the dictionary.)
549
 *   Returns TRUE if the words match, FALSE otherwise.
550
 */
551
static int voceq(uchar *s1, uint l1, uchar *s2, uint l2)
552
{
553
    uint i;
554
555
    if (l1 == 0 && l2 == 0)  return(TRUE);           /* both NULL - a match */
556
    if (l1 == 0 || l2 == 0)  return(FALSE);  /* one NULL only - not a match */
557
    if (l1 >= 6 && l2 >= l1) l2 = l1;
558
    if (l1 != l2)            return(FALSE);                /* ==> not equal */
559
    for (i = 0 ; i < l1 ; i++)
560
        if (*s1++ != *s2++)  return(FALSE);
561
    return(TRUE);                                          /* strings match */
562
}
563
564
/* find the next word in a search */
565
vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx)
566
{
567
    vocdef  *v, *vf;
568
    vocwdef *vw, *vwf;
569
    vocdef  *c = search_ctx->v;
570
    int      first;
571
572
    /* continue with current word's vocwdef list if anything is left */
573
    first = TRUE;
574
    vw = vocwget(voccx, search_ctx->vw->vocwnxt);
575
576
    /* keep going until we run out of hash chain entries or find a match */
577
    for (v = c, vf = 0 ; v != 0 && vf == 0 ; v = v->vocnxt, first = FALSE)
578
    {
579
        /* if this word matches, look at the objects in its list */
580
        if (first
581
            || (voceq(search_ctx->wrd1, search_ctx->len1,
582
                      v->voctxt, v->voclen)
583
                && voceq(search_ctx->wrd2, search_ctx->len2,
584
                         v->voctxt + v->voclen, v->vocln2)))
585
        {
586
            /*
587
             *   on the first time through, vw has already been set up
588
             *   with the next vocwdef in the current list; on subsequent
589
             *   times through the loop, start at the head of the current
590
             *   word's list 
591
             */
592
            if (!first)
593
                vw = vocwget(voccx, v->vocwlst);
594
595
            /* search the list from vw forward */
596
            for ( ; vw ; vw = vocwget(voccx, vw->vocwnxt))
597
            {
598
                if (search_ctx->vw->vocwtyp == vw->vocwtyp
599
                    && !(vw->vocwflg & VOCFCLASS)
600
                    && !(vw->vocwflg & VOCFDEL))
601
                {
602
                    /*
603
                     *   remember the first vocdef that we found, and
604
                     *   remember this, the first matching vocwdef, then
605
                     *   stop scanning 
606
                     */
607
                    vf = v;
608
                    vwf = vw;
609
                    break;
610
                }
611
            }
612
        }
613
    }
614
615
    /* return the first vocwdef in this word's list */
616
    search_ctx->v = vf;
617
    search_ctx->vw = (vf ? vwf : 0);
618
    return(search_ctx->vw);
619
}
620
621
/* find the first vocdef matching a set of words */
622
vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
623
                int p, vocseadef *search_ctx)
624
{
625
    uint     hshval;
626
    vocdef  *v, *vf;
627
    vocwdef *vw, *vwf;
628
629
    /* get the word's hash value */
630
    hshval = vochsh((uchar *)wrd, len);
631
632
    /* scan the hash list until we run out of entries, or find a match */
633
    for (v = ctx->voccxhsh[hshval], vf = 0 ; v != 0 && vf == 0 ;
634
         v = v->vocnxt)
635
    {
636
        /* if this word matches, look at the objects in its list */
637
        if (voceq((uchar *)wrd, len, v->voctxt, v->voclen)
638
            && voceq((uchar *)wrd2, len2, v->voctxt + v->voclen, v->vocln2))
639
        {
640
            /* look for a suitable object in the vocwdef list */
641
            for (vw = vocwget(ctx, v->vocwlst) ; vw ;
642
                 vw = vocwget(ctx, vw->vocwnxt))
643
            {
644
                if (vw->vocwtyp == p && !(vw->vocwflg & VOCFCLASS)
645
                    && !(vw->vocwflg & VOCFDEL))
646
                {
647
                    /*
648
                     *   remember the first vocdef that we found, and
649
                     *   remember this, the first matching vocwdef; then
650
                     *   stop scanning, since we have a match 
651
                     */
652
                    vf = v;
653
                    vwf = vw;
654
                    break;
655
                }
656
            }
657
        }
658
    }
659
660
    /* set up the caller-provided search structure for next time */
661
    vw = (vf != 0 ? vwf : 0);
662
    if (search_ctx)
663
    {
664
        /* save the search position */
665
        search_ctx->v = vf;
666
        search_ctx->vw = vw;
667
668
        /* save the search criteria */
669
        search_ctx->wrd1 = (uchar *)wrd;
670
        search_ctx->len1 = len;
671
        search_ctx->wrd2 = (uchar *)wrd2;
672
        search_ctx->len2 = len2;
673
    }
674
675
    /* return the match */
676
    return vw;
677
}
678
679
/* ------------------------------------------------------------------------ */
680
/* 
681
 *   vocerr_va information structure.  This is initialized in the call to
682
 *   vocerr_va_prep(), and must then be passed to vocerr_va().  
683
 */
684
struct vocerr_va_info
685
{
686
    /* parseError/parseErrorParam result */
687
    char user_msg[400];
688
689
    /* the sprintf-style format string to display */
690
    char *fmt;
691
692
    /* 
693
     *   Pointer to the output buffer to use to format the string 'fmt' with
694
     *   its arguments, using vsprintf.  The prep function will set this up
695
     *   to point to user_msg[].  
696
     */
697
    char *outp;
698
};
699
700
/*
701
 *   General parser error formatter - preparation.  This must be called to
702
 *   initialize the context before the message can be displayed with
703
 *   vocerr_va().  
704
 */
705
static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
706
                           int err, char *f, va_list argptr)
707
{
708
    /* 
709
     *   presume that we'll use the given format string, instead of one
710
     *   provided by the program 
711
     */
712
    info->fmt = f;
713
714
    /* use the output buffer from the info structure */
715
    info->outp = info->user_msg;
716
717
    /* 
718
     *   if the user has a parseError or parseErrorParam function, see if it
719
     *   provides a msg 
720
     */
721
    if (ctx->voccxper != MCMONINV || ctx->voccxperp != MCMONINV)
722
    {
723
        runcxdef *rcx = ctx->voccxrun;
724
        dattyp    typ;
725
        size_t    len;
726
        int       argc;
727
728
        /* start off with the two arguments that are always present */
729
        argc = 2;
730
731
        /* 
732
         *   if we're calling parseErrorParam, and we have additional
733
         *   arguments, push them as well 
734
         */
735
        if (ctx->voccxperp != MCMONINV)
736
        {
737
            enum typ_t
738
            {
739
                ARGBUF_STR, ARGBUF_INT, ARGBUF_CHAR
740
            };
741
            struct argbuf_t
742
            {
743
                enum typ_t typ;
744
                union
745
                {
746
                    char *strval;
747
                    int   intval;
748
                    char  charval;
749
                } val;
750
            };
751
            struct argbuf_t  args[5];
752
            struct argbuf_t *argp;
753
            char  *p;
754
755
            /* 
756
             *   Retrieve the arguments by examining the format string.  We
757
             *   must buffer up the arguments before pushing them, because
758
             *   we need to push them in reverse order (last to first); so,
759
             *   we must scan all arguments before we push the first one.  
760
             */
761
            for (p = f, argp = args ; *p != '\0' ; ++p)
762
            {
763
                /* check if this is a parameter */
764
                if (*p == '%')
765
                {
766
                    /* find out what type it is */
767
                    switch(*++p)
768
                    {
769
                    case 's':
770
                        /* string - save the char pointer */
771
                        argp->val.strval = va_arg(argptr, char *);
772
                        argp->typ = ARGBUF_STR;
773
774
                        /* consume an argument slot */
775
                        ++argp;
776
                        break;
777
778
                    case 'd':
779
                        /* integer - save the integer */
780
                        argp->val.intval = va_arg(argptr, int);
781
                        argp->typ = ARGBUF_INT;
782
783
                        /* consume an argument slot */
784
                        ++argp;
785
                        break;
786
787
                    case 'c':
788
                        /* character */
789
                        argp->val.charval = (char)va_arg(argptr, int);
790
                        argp->typ = ARGBUF_CHAR;
791
792
                        /* consume an argument slot */
793
                        ++argp;
794
                        break;
795
796
                    default:
797
                        /* 
798
                         *   ignore other types (there shouldn't be any
799
                         *   other types anyway) 
800
                         */
801
                        break;
802
                    }
803
                }
804
            }
805
806
            /*
807
             *   Push the arguments - keep looping until we get back to the
808
             *   first argument slot 
809
             */
810
            while (argp != args)
811
            {
812
                /* move to the next argument, working backwards */
813
                --argp;
814
815
                /* push this argument */
816
                switch(argp->typ)
817
                {
818
                case ARGBUF_STR:
819
                    /* push the string value */
820
                    runpstr(rcx, argp->val.strval,
821
                            (int)strlen(argp->val.strval), 0);
822
                    break;
823
824
                case ARGBUF_INT:
825
                    /* push the number value */
826
                    runpnum(rcx, argp->val.intval);
827
                    break;
828
829
                case ARGBUF_CHAR:
830
                    /* push the character as a one-character string */
831
                    runpstr(rcx, &argp->val.charval, 1, 0);
832
                    break;
833
                }
834
835
                /* count the argument */
836
                ++argc;
837
            }
838
        }
839
840
        /* push standard arguments: error code and default message */
841
        runpstr(rcx, f, (int)strlen(f), 0);         /* 2nd arg: default msg */
842
        runpnum(rcx, (long)err);                   /* 1st arg: error number */
843
844
        /* invoke parseErrorParam if it's defined, otherwise parseError */
845
        runfn(rcx, (objnum)(ctx->voccxperp == MCMONINV
846
                            ? ctx->voccxper : ctx->voccxperp), argc);
847
848
        /* see what the function returned */
849
        typ = runtostyp(rcx);
850
        if (typ == DAT_SSTRING)
851
        {
852
            char *p;
853
            
854
            /* 
855
             *   they returned a string - use it as the error message
856
             *   instead of the default message 
857
             */
858
            p = (char *)runpopstr(rcx);
859
            len = osrp2(p) - 2;
860
            p += 2;
861
            if (len > sizeof(info->user_msg) - 1)
862
                len = sizeof(info->user_msg) - 1;
863
            memcpy(info->user_msg, p, len);
864
            info->user_msg[len] = '\0';
865
866
            /* use the returned string as the message to display */
867
            info->fmt = info->user_msg;
868
869
            /* use the remainder of the buffer for the final formatting */
870
            info->outp = info->user_msg + len + 1;
871
        }
872
        else
873
        {
874
            /* ignore other return values */
875
            rundisc(rcx);
876
        }
877
    }
878
879
}
880
881
/* 
882
 *   General parser error formatter.
883
 *   
884
 *   Before calling this routine, callers MUST invoke vocerr_va_prep() to
885
 *   prepare the information structure.  Because both this routine and the
886
 *   prep routine need to look at the varargs list ('argptr'), the caller
887
 *   must call va_start/va_end around the prep call, and then AGAIN on this
888
 *   call.  va_start/va_end must be used twice to ensure that the argptr is
889
 *   property re-initialized for the call to this routine.  
890
 */
891
static void vocerr_va(voccxdef *ctx, struct vocerr_va_info *info,
892
                      int err, char *f, va_list argptr)
893
{
894
    /* turn on output */
895
    (void)tioshow(ctx->voccxtio);
896
897
    /* build the string to display */
898
    vsprintf(info->outp, info->fmt, argptr);
899
900
    /* display it */
901
    tioputs(ctx->voccxtio, info->outp);
902
}
903
904
/* ------------------------------------------------------------------------ */
905
/* 
906
 *   display a parser informational message 
907
 */
908
void vocerr_info(voccxdef *ctx, int err, char *f, ...)
909
{
910
    va_list argptr;
911
    struct vocerr_va_info info;
912
913
    /* prepare to format the message */
914
    va_start(argptr, f);
915
    vocerr_va_prep(ctx, &info, err, f, argptr);
916
    va_end(argptr);
917
918
   /* call the general vocerr formatter */
919
    va_start(argptr, f);
920
    vocerr_va(ctx, &info, err, f, argptr);
921
    va_end(argptr);
922
}
923
924
/* 
925
 *   display a parser error 
926
 */
927
void vocerr(voccxdef *ctx, int err, char *f, ...)
928
{
929
    va_list argptr;
930
    struct vocerr_va_info info;
931
932
    /*
933
     *   If the unknown word flag is set, suppress this error, because
934
     *   we're going to be trying the whole parsing from the beginning
935
     *   again anyway.  
936
     */
937
    if (ctx->voccxunknown > 0)
938
        return;
939
940
    /* prepare to format the message */
941
    va_start(argptr, f);
942
    vocerr_va_prep(ctx, &info, err, f, argptr);
943
    va_end(argptr);
944
945
    /* call the general vocerr formatter */
946
    va_start(argptr, f);
947
    vocerr_va(ctx, &info, err, f, argptr);
948
    va_end(argptr);
949
}
950
951
/*
952
 *   Handle an unknown verb or sentence structure.  We'll call this when
953
 *   we encounter a sentence where we don't know the verb word, or we
954
 *   don't know the combination of verb and verb preposition, or we don't
955
 *   recognize the sentence structure (for example, an indirect object is
956
 *   present, but we don't have a template defined using an indirect
957
 *   object for the verb).
958
 *   
959
 *   This function calls the game-defined function parseUnknownVerb, if it
960
 *   exists.  If the function doesn't exist, we'll simply display the
961
 *   given error message, using the normal parseError mechanism.  The
962
 *   function should use "abort" or "exit" if it wants to cancel further
963
 *   processing of the command.
964
 *   
965
 *   We'll return true if the function exists, in which case normal
966
 *   processing should continue with any remaining command on the command
967
 *   line.  We'll return false if the function doesn't exist, in which
968
 *   case the remainder of the command should be aborted.  
969
 *   
970
 *   'wrdcnt' is the number of words in the cmd[] array.  If wrdcnt is
971
 *   zero, we'll automatically count the array entries, with the end of
972
 *   the array indicated by a null pointer entry.
973
 *   
974
 *   'next_start' is a variable that we may fill in with the index of the
975
 *   next word in the command to be parsed.  If the user function
976
 *   indicates the number of words it consumes, we'll use 'next_start' to
977
 *   communicate this back to the caller, so that the caller can resume
978
 *   parsing after the part parsed by the function.
979
 */
980
int try_unknown_verb(voccxdef *ctx, objnum actor,
981
                     char **cmd, int *typelist, int wrdcnt, int *next_start,
982
                     int do_fuses, int vocerr_err, char *vocerr_msg, ...)
983
{
984
    int  show_msg;
985
    va_list  argptr;
986
987
    /* presume we won't show the message */
988
    show_msg = FALSE;
989
990
    /* determine the word count if the caller passed in zero */
991
    if (wrdcnt == 0)
992
    {
993
        /* count the words before the terminating null entry */
994
        for ( ; cmd[wrdcnt] != 0 ; ++wrdcnt) ;
995
    }
996
997
    /* if parseUnknownVerb exists, call it */
998
    if (ctx->voccxpuv != MCMONINV)
999
    {
1000
        int  err;
1001
        int  i;
1002
        int  do_fuses;
1003
1004
        /* no error has occurred yet */
1005
        err = 0;
1006
1007
        /* presume we will run the fuses */
1008
        do_fuses = TRUE;
1009
1010
        /* push the error number argument */
1011
        runpnum(ctx->voccxrun, (long)vocerr_err);
1012
1013
        /* push a list of the word types */
1014
        voc_push_numlist(ctx, (uint *)typelist, wrdcnt);
1015
1016
        /* push a list of the words */
1017
        voc_push_toklist(ctx, cmd, wrdcnt);
1018
1019
        /* use "Me" as the default actor */
1020
        if (actor == MCMONINV)
1021
            actor = ctx->voccxme;
1022
1023
        /* push the actor argument */
1024
        runpobj(ctx->voccxrun, actor);
1025
1026
        /* catch any errors that occur while calling the function */
1027
        ERRBEGIN(ctx->voccxerr)
1028
        {
1029
            /* invoke the function */
1030
            runfn(ctx->voccxrun, ctx->voccxpuv, 4);
1031
1032
            /* get the return value */
1033
            switch(runtostyp(ctx->voccxrun))
1034
            {
1035
            case DAT_TRUE:
1036
                /* the command was handled */
1037
                rundisc(ctx->voccxrun);
1038
1039
                /* consume the entire command */
1040
                *next_start = wrdcnt;
1041
1042
                /* 
1043
                 *   since the command has now been handled, forget about
1044
                 *   any unknown words 
1045
                 */
1046
                ctx->voccxunknown = 0;
1047
                break;
1048
1049
            case DAT_NUMBER:
1050
                /* 
1051
                 *   The command was handled, and the function indicated
1052
                 *   the number of words it wants to skip.  Communicate
1053
                 *   this information back to the caller in *next_start.
1054
                 *   Since the routine returns the 1-based index of the
1055
                 *   next entry, we must subtract one to get the number of
1056
                 *   words actually consumed.  
1057
                 */
1058
                *next_start = runpopnum(ctx->voccxrun);
1059
                if (*next_start > 0)
1060
                    --(*next_start);
1061
1062
                /* make sure the value is in range */
1063
                if (*next_start < 0)
1064
                    *next_start = 0;
1065
                else if (*next_start > wrdcnt)
1066
                    *next_start = wrdcnt;
1067
1068
                /* 
1069
                 *   forget about any unknown words in the list up to the
1070
                 *   next word 
1071
                 */
1072
                for (i = 0 ; i < *next_start ; ++i)
1073
                {
1074
                    /* if this word was unknown, forget about that now */
1075
                    if ((typelist[i] & VOCT_UNKNOWN) != 0
1076
                        && ctx->voccxunknown > 0)
1077
                        --(ctx->voccxunknown);
1078
                }
1079
                break;
1080
1081
            default:
1082
                /* treat anything else like nil */
1083
1084
            case DAT_NIL:
1085
                /* nil - command not handled; show the message */
1086
                rundisc(ctx->voccxrun);
1087
                show_msg = TRUE;
1088
                break;
1089
            }
1090
        }
1091
        ERRCATCH(ctx->voccxerr, err)
1092
        {
1093
            /* check the error */
1094
            switch(err)
1095
            {
1096
            case ERR_RUNEXIT:
1097
            case ERR_RUNEXITOBJ:
1098
                /* 
1099
                 *   Exit or exitobj was executed - skip to the fuses.
1100
                 *   Forget about any unknown words, since we've finished
1101
                 *   processing this command and we don't want to allow
1102
                 *   "oops" processing.  
1103
                 */
1104
                ctx->voccxunknown = 0;
1105
                break;
1106
1107
            case ERR_RUNABRT:
1108
                /* 
1109
                 *   abort was executed - skip to the end of the command,
1110
                 *   but do not execute the fuses 
1111
                 */
1112
                do_fuses = FALSE;
1113
1114
                /*
1115
                 *   Since we're aborting the command, ignore any
1116
                 *   remaining unknown words - we're skipping out of the
1117
                 *   command entirely, so we don't care that there were
1118
                 *   unknown words in the command.  
1119
                 */
1120
                ctx->voccxunknown = 0;
1121
                break;
1122
1123
            default:
1124
                /* re-throw any other errors */
1125
                errrse(ctx->voccxerr);
1126
            }
1127
        }
1128
        ERREND(ctx->voccxerr);
1129
1130
        /* if we're not showing the message, process fuses and daemons */
1131
        if (!show_msg)
1132
        {
1133
            /* execute fuses and daemons */
1134
            if (exe_fuses_and_daemons(ctx, err, do_fuses,
1135
                                      actor, MCMONINV, 0, 0,
1136
                                      MCMONINV, MCMONINV) != 0)
1137
            {
1138
                /* 
1139
                 *   aborted from fuses and daemons - return false to tell
1140
                 *   the caller not to execute anything left on the
1141
                 *   command line 
1142
                 */
1143
                return FALSE;
1144
            }
1145
1146
            /* indicate that the game code successfully handled the command */
1147
            return TRUE;
1148
        }
1149
    }
1150
1151
    /* 
1152
     *   If we made it here, it means we're showing the default message.
1153
     *   If we have unknown words, suppress the message so that we show
1154
     *   the unknown word error instead after returning.
1155
     */
1156
    if (ctx->voccxunknown == 0)
1157
    {
1158
        struct vocerr_va_info info;
1159
1160
        /* prepare to format the message */
1161
        va_start(argptr, vocerr_msg);
1162
        vocerr_va_prep(ctx, &info, vocerr_err, vocerr_msg, argptr);
1163
        va_end(argptr);
1164
1165
        /* format the mesage */
1166
        va_start(argptr, vocerr_msg);
1167
        vocerr_va(ctx, &info, vocerr_err, vocerr_msg, argptr);
1168
        va_end(argptr);
1169
    }
1170
1171
    /* indicate that the remainder of the command should be aborted */
1172
    return FALSE;
1173
}
1174
1175
1176
/* determine if a tokenized word is a special internal word flag */
1177
/* int vocisspec(char *wrd); */
1178
#define vocisspec(wrd) \
1179
   (vocisupper(*wrd) || (!vocisalpha(*wrd) && *wrd != '\'' && *wrd != '-'))
1180
1181
static vocspdef vocsptab[] =
1182
{
1183
    { "of",     VOCW_OF   },
1184
    { "and",    VOCW_AND  },
1185
    { "then",   VOCW_THEN },
1186
    { "all",    VOCW_ALL  },
1187
    { "everyt", VOCW_ALL  },
1188
    { "both",   VOCW_BOTH },
1189
    { "but",    VOCW_BUT  },
1190
    { "except", VOCW_BUT  },
1191
    { "one",    VOCW_ONE  },
1192
    { "ones",   VOCW_ONES },
1193
    { "it",     VOCW_IT   },
1194
    { "them",   VOCW_THEM },
1195
    { "him",    VOCW_HIM  },
1196
    { "her",    VOCW_HER  },
1197
    { "any",    VOCW_ANY  },
1198
    { "either", VOCW_ANY  },
1199
    { 0,        0         }
1200
};
1201
1202
/* test a word to see if it's a particular special word */
1203
static int voc_check_special(voccxdef *ctx, char *wrd, int checktyp)
1204
{
1205
    /* search the user or built-in special table, as appropriate */
1206
    if (ctx->voccxspp)
1207
    {
1208
        char  *p;
1209
        char  *endp;
1210
        char   typ;
1211
        int    len;
1212
        int    wrdlen = strlen((char *)wrd);
1213
        
1214
        for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
1215
             p < endp ; )
1216
        {
1217
            typ = *p++;
1218
            len = *p++;
1219
1220
            /* if this word matches in type and text, we have a match */
1221
            if (typ == checktyp
1222
                && len == wrdlen && !memcmp(p, wrd, (size_t)len))
1223
                return TRUE;
1224
1225
            /* no match - keep going */
1226
            p += len;
1227
        }
1228
    }
1229
    else
1230
    {
1231
        vocspdef *x;
1232
        
1233
        for (x = vocsptab ; x->vocspin ; ++x)
1234
        {
1235
            /* if it matches in type and text, we have a match */
1236
            if (x->vocspout == checktyp
1237
                && !strncmp((char *)wrd, x->vocspin, (size_t)6))
1238
                return TRUE;
1239
        }
1240
    }
1241
1242
    /* didn't find a match for the text and type */
1243
    return FALSE;
1244
}
1245
1246
1247
/* tokenize a command line - returns number of words in command */
1248
int voctok(voccxdef *ctx, char *cmd, char *outbuf, char **wrd,
1249
           int lower, int cvt_ones, int show_errors)
1250
{
1251
    int       i;
1252
    vocspdef *x;
1253
    int       l;
1254
    char     *p;
1255
    char     *w;
1256
    uint      len;
1257
1258
    for (i = 0 ;; )
1259
    {
1260
        while (vocisspace(*cmd)) cmd++;
1261
        if (!*cmd)
1262
        {
1263
            wrd[i] = outbuf;
1264
            *outbuf = '\0';
1265
            return(i);
1266
        }
1267
1268
        wrd[i++] = outbuf;
1269
        if (vocisalpha(*cmd) || *cmd == '-')
1270
        {
1271
            while(vocisalpha(*cmd) || vocisdigit(*cmd) ||
1272
                  *cmd=='\'' || *cmd=='-')
1273
            {
1274
                *outbuf++ = (vocisupper(*cmd) && lower) ? tolower(*cmd) : *cmd;
1275
                ++cmd;
1276
            }
1277
            
1278
            /*
1279
             *   Check for a special case:  abbreviations that end in a
1280
             *   period.  For example, "Mr. Patrick J. Wayne."  We wish
1281
             *   to absorb the period after "Mr" and the one after "J"
1282
             *   into the respective words; we detect this condition by
1283
             *   actually trying to find a word in the dictionary that
1284
             *   has the period.
1285
             */
1286
            w = wrd[i-1];
1287
            len = outbuf - w;
1288
            if (*cmd == '.')
1289
            {
1290
                *outbuf++ = *cmd++;           /* add the period to the word */
1291
                *outbuf = '\0';                        /* null-terminate it */
1292
                ++len;
1293
                if (!vocffw(ctx, (char *)w, len, 0, 0, PRP_NOUN,
1294
                            (vocseadef *)0)
1295
                    && !vocffw(ctx, (char *)w, len, 0, 0, PRP_ADJ,
1296
                               (vocseadef *)0))
1297
                {
1298
                    /* no word with period in dictionary - remove period */
1299
                    --outbuf;
1300
                    --cmd;
1301
                    --len;
1302
                }
1303
            }
1304
1305
            /* null-terminate the buffer */
1306
            *outbuf = '\0';
1307
1308
            /* find compound words and glue them together */
1309
            for (p = ctx->voccxcpp, l = ctx->voccxcpl ; l ; )
1310
            {
1311
                uint   l1 = osrp2(p);
1312
                char  *p2 = p + l1;                      /* get second word */
1313
                uint   l2 = osrp2(p2);
1314
                char  *p3 = p2 + l2;                   /* get compound word */
1315
                uint   l3 = osrp2(p3);
1316
                
1317
                if (i > 1 && len == (l2 - 2)
1318
                    && !memcmp(w, p2 + 2, (size_t)len)
1319
                    && strlen((char *)wrd[i-2]) == (l1 - 2)
1320
                    && !memcmp(wrd[i-2], p + 2, (size_t)(l1 - 2)))
1321
                {
1322
                    memcpy(wrd[i-2], p3 + 2, (size_t)(l3 - 2));
1323
                    *(wrd[i-2] + l3 - 2) = '\0';
1324
                    --i;
1325
                    break;
1326
                }
1327
1328
                /* move on to the next word */
1329
                l -= l1 + l2 + l3;
1330
                p = p3 + l3;
1331
            }
1332
1333
            /*
1334
             *   Find any special keywords, and set to appropriate flag
1335
             *   char.  Note that we no longer convert "of" in this
1336
             *   fashion; "of" is now handled separately in order to
1337
             *   facilitate its use as an ordinary preposition. 
1338
             */
1339
            if (ctx->voccxspp)
1340
            {
1341
                char  *p;
1342
                char  *endp;
1343
                char   typ;
1344
                int    len;
1345
                int    wrdlen = strlen((char *)wrd[i-1]);
1346
                
1347
                for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
1348
                     p < endp ; )
1349
                {
1350
                    typ = *p++;
1351
                    len = *p++;
1352
                    if (len == wrdlen && !memcmp(p, wrd[i-1], (size_t)len)
1353
                        && (cvt_ones || (typ != VOCW_ONE && typ != VOCW_ONES))
1354
                        && typ != VOCW_OF)
1355
                    {
1356
                        *wrd[i-1] = typ;
1357
                        *(wrd[i-1] + 1) = '\0';
1358
                        break;
1359
                    }
1360
                    p += len;
1361
                }
1362
            }
1363
            else
1364
            {
1365
                for (x = vocsptab ; x->vocspin ; ++x)
1366
                {
1367
                    if (!strncmp((char *)wrd[i-1], (char *)x->vocspin,
1368
                                 (size_t)6)
1369
                        && (cvt_ones ||
1370
                            (x->vocspout != VOCW_ONE
1371
                             && x->vocspout != VOCW_ONES))
1372
                        && x->vocspout != VOCW_OF)
1373
                    {
1374
                        *wrd[i-1] = x->vocspout;
1375
                        *(wrd[i-1] + 1) = '\0';
1376
                        break;
1377
                    }
1378
                }
1379
            }
1380
1381
            /* make sure the output pointer is fixed up to the right spot */
1382
            outbuf = wrd[i-1];
1383
            outbuf += strlen((char *)outbuf);
1384
        }
1385
        else if (vocisdigit( *cmd ))
1386
        {
1387
            while(vocisdigit(*cmd) || vocisalpha(*cmd)
1388
                  || *cmd == '\'' || *cmd == '-')
1389
                *outbuf++ = *cmd++;
1390
        }
1391
        else switch( *cmd )
1392
        {
1393
        case '.':
1394
        case '!':
1395
        case '?':
1396
        case ';':
1397
            *outbuf++ = VOCW_THEN;
1398
            ++cmd;
1399
            break;
1400
1401
        case ',':
1402
        case ':':
1403
            *outbuf++ = VOCW_AND;
1404
            ++cmd;
1405
            break;
1406
1407
        case '"':
1408
        case '\'':
1409
            {
1410
                char  *lenptr;
1411
                char   quote = *cmd++;
1412
1413
                /* 
1414
                 *   remember that this is a quoted string (it doesn't
1415
                 *   matter whether they're actually using single or
1416
                 *   double quotes - in either case, we use '"' as the
1417
                 *   flag to indicate that it's a quote string)
1418
                 */
1419
                *outbuf++ = '"';
1420
1421
                /* make room for the length prefix */
1422
                lenptr = outbuf;
1423
                outbuf += 2;
1424
1425
                /* copy up to the matching close quote */
1426
                while (*cmd && *cmd != quote)
1427
                {
1428
                    char c;
1429
1430
                    /* get this character */
1431
                    c = *cmd++;
1432
                    
1433
                    /* escape the character if necessary */
1434
                    switch(c)
1435
                    {
1436
                    case '\\':
1437
                        *outbuf++ = '\\';
1438
                        break;
1439
                    }
1440
1441
                    /* copy this character */
1442
                    *outbuf++ = c;
1443
                }
1444
                
1445
                oswp2(lenptr, ((int)(outbuf - lenptr)));
1446
                if (*cmd == quote) cmd++;
1447
                break;
1448
            }
1449
1450
        default:
1451
            /* display an error if appropriate */
1452
            if (show_errors)
1453
            {
1454
                int hmode = tio_is_html_mode();
1455
1456
                /* 
1457
                 *   if we're in HTML mode, switch out momentarily, so that
1458
                 *   we show the character literally, even if it's a
1459
                 *   markup-significant character (such as '<' or '&') 
1460
                 */
1461
                if (hmode)
1462
                    tioputs(ctx->voccxtio, "\\H-");
1463
                
1464
                /* show the message */
1465
                vocerr(ctx, VOCERR(1),
1466
                       "I don't understand the punctuation \"%c\".", *cmd);
1467
1468
                /* restore HTML mode if appropriate */
1469
                if (hmode)
1470
                    tioputs(ctx->voccxtio, "\\H+");
1471
            }
1472
1473
            /* return failure */
1474
            return -1;
1475
        }
1476
1477
        /* null-terminate the result */
1478
        *outbuf++ = '\0';
1479
    }
1480
}
1481
1482
1483
/* ------------------------------------------------------------------------ */
1484
/*
1485
 *   Look up a word's type.  If 'of_is_spec' is true, we'll treat OF as
1486
 *   being of type special if it's not otherwise defined.  
1487
 */
1488
static int voc_lookup_type(voccxdef *ctx, char *p, int len, int of_is_spec)
1489
{
1490
    int t;
1491
    
1492
    /* check for a special word */
1493
    if (vocisspec(p))
1494
    {
1495
        /* it's a special word - this is its type */
1496
        t = VOCT_SPEC;
1497
    }
1498
    else
1499
    {
1500
        vocwdef *vw;
1501
        vocdef  *v;
1502
1503
        /*
1504
         *   Now check the various entries of this word to get the word
1505
         *   type flag bits.  The Noun and Adjective flags can be set for
1506
         *   any word which matches this word in the first six letters (or
1507
         *   more if more were provided by the player), but the Plural
1508
         *   flag can only be set if the plural word matches exactly.
1509
         *   Note that this pass only matches the first word in two-word
1510
         *   verbs; the second word is considered later during the
1511
         *   semantic analysis.  
1512
         */
1513
        for (t = 0, v = ctx->voccxhsh[vochsh((uchar *)p, len)] ; v != 0 ;
1514
             v = v->vocnxt)
1515
        {
1516
            /* if this hash chain entry matches, add it to our types */
1517
            if (voceq((uchar *)p, len, v->voctxt, v->voclen))
1518
            {
1519
                /* we have a match - look through relation list for word */
1520
                for (vw = vocwget(ctx, v->vocwlst) ; vw != 0 ;
1521
                     vw = vocwget(ctx, vw->vocwnxt))
1522
                {
1523
                    /* skip this word if it's been deleted */
1524
                    if (vw->vocwflg & VOCFDEL)
1525
                        continue;
1526
1527
                    /* we need a special check for plurals */
1528
                    if (vw->vocwtyp == PRP_PLURAL)
1529
                    {
1530
                        /* plurals must be exact (non-truncated) match */
1531
                        if (len == v->voclen)
1532
                        {
1533
                            /* plurals also count as nouns */
1534
                            t |= (VOCT_NOUN | VOCT_PLURAL);
1535
                        }
1536
                    }
1537
                    else
1538
                    {
1539
                        /* add this type bit to our type value */
1540
                        t |= voctype[vw->vocwtyp];
1541
                    }
1542
                }
1543
            }
1544
        }
1545
    }
1546
1547
    /* check for "of" if the caller wants us to */
1548
    if (of_is_spec && t == 0 && voc_check_special(ctx, p, VOCW_OF))
1549
        t = VOCT_SPEC;
1550
1551
    /* return the type */
1552
    return t;
1553
}
1554
1555
1556
/* ------------------------------------------------------------------------ */
1557
/*
1558
 *   Display an unknown word error, and read a new command, allowing the
1559
 *   user to respond with the special OOPS command to correct the unknown
1560
 *   word.  Returns a pointer to the start of the replacement text if the
1561
 *   player entered a correction via OOPS, or a null pointer if the player
1562
 *   simply entered a new command.  
1563
 */
1564
static char *voc_read_oops(voccxdef *ctx, char *oopsbuf, size_t oopsbuflen,
1565
                           const char *unknown_word)
1566
{
1567
    char *p;
1568
    
1569
    /* display the error */
1570
    vocerr(ctx, VOCERR(2), "I don't know the word \"%s\".", unknown_word);
1571
1572
    /* read a new command */
1573
    if (vocread(ctx, MCMONINV, MCMONINV,
1574
                oopsbuf, (int)oopsbuflen, 1) == VOCREAD_REDO)
1575
    {
1576
        /* 
1577
         *   we've already decided it's not an OOPS input - return null to
1578
         *   indicate to the caller that we have a new command 
1579
         */
1580
        return 0;
1581
    }
1582
1583
    /* lower-case the string */
1584
    for (p = oopsbuf ; *p != '\0' ; ++p)
1585
        *p = (vocisupper(*p) ? tolower(*p) : *p);
1586
1587
    /* skip leading spaces */
1588
    for (p = oopsbuf ; vocisspace(*p) ; ++p) ;
1589
1590
    /* 
1591
     *   See if they are saying "oops".  Allow "oops" or simply "o",
1592
     *   followed by either a space or a comma.  
1593
     */
1594
    if ((strlen(p) > 5 && memcmp(p, "oops ", 5) == 0)
1595
        || (strlen(p) > 5 && memcmp(p, "oops,", 5) == 0))
1596
    {
1597
        /* we found "OOPS" - move to the next character */
1598
        p += 5;
1599
    }
1600
    else if ((strlen(p) > 2 && memcmp(p, "o ", 2) == 0)
1601
             || (strlen(p) > 2 && memcmp(p, "o,", 2) == 0))
1602
    {
1603
        /* we found "O" - move to the next character */
1604
        p += 2;
1605
    }
1606
    else
1607
    {
1608
        /* 
1609
         *   we didn't find any form of "OOPS" response - return null to
1610
         *   indicate to the caller that the player entered a new command 
1611
         */
1612
        return 0;
1613
    }
1614
1615
    /* skip spaces before the replacement text */
1616
    for ( ; vocisspace(*p) ; ++p) ;
1617
1618
    /* return a pointer to the start of the replacement text */
1619
    return p;
1620
}
1621
1622
/* ------------------------------------------------------------------------ */
1623
/*
1624
 *   figure out what parts of speech are associated with each
1625
 *   word in a tokenized command list
1626
 */
1627
int vocgtyp(voccxdef *ctx, char *cmd[], int types[], char *orgbuf)
1628
{
1629
    int      cur;
1630
    int      t;
1631
    char    *p;
1632
    int      len;
1633
    int      unknown_count = 0;
1634
    
1635
startover:
1636
    if (ctx->voccxflg & VOCCXFDBG)
1637
        tioputs(ctx->vocxtio, ". Checking words:\\n");
1638
1639
    for (cur = 0 ; cmd[cur] ; ++cur)
1640
    {
1641
        /* get the word */
1642
        p = cmd[cur];
1643
        len = strlen(p);
1644
1645
        /* look it up */
1646
        t = voc_lookup_type(ctx, p, len, FALSE);
1647
        
1648
        /* see if the word was found */
1649
        if (t == 0 && !voc_check_special(ctx, p, VOCW_OF))
1650
        {
1651
            /* 
1652
             *   We didn't find the word.  For now, set its type to
1653
             *   "unknown".
1654
             */
1655
            t = VOCT_UNKNOWN;
1656
1657
            /*
1658
             *   If the unknown word count is already non-zero, it means
1659
             *   that we've tried to let the game resolve this word using
1660
             *   the parseUnknownDobj/Iobj mechanism, but it wasn't able
1661
             *   to do so, thus we've come back here to use the normal
1662
             *   "oops" processing instead.
1663
             *   
1664
             *   Don't generate a message until we get to the first
1665
             *   unknown word from the original list that we weren't able
1666
             *   to resolve.  We may have been able to handle one or more
1667
             *   of the original list of unknown words (through
1668
             *   parseNounPhrase or other means), so we don't want to
1669
             *   generate a message for any words we ended up handling.
1670
             *   The number we resolved is the last full unknown count
1671
             *   minus the remaining unknown count.  
1672
             */
1673
            if (ctx->voccxunknown != 0
1674
                && unknown_count >= ctx->voccxlastunk - ctx->voccxunknown)
1675
            {
1676
                char  oopsbuf[VOCBUFSIZ];
1677
                char *p1;
1678
                
1679
                /* 
1680
                 *   we can try using the parseUnknownDobj/Iobj again
1681
                 *   after this, so clear the unknown word count for now
1682
                 */
1683
                ctx->voccxunknown = 0;
1684
1685
                /* display an error, and ask for a new command */
1686
                p1 = voc_read_oops(ctx, oopsbuf, sizeof(oopsbuf), p);
1687
1688
                /* if they responded with replacement text, apply it */
1689
                if (p1 != 0)
1690
                {
1691
                    char   redobuf[200];
1692
                    char  *q;
1693
                    int    i;
1694
                    int    wc;
1695
                    char **w;
1696
                    char  *outp;
1697
                    
1698
                    /* 
1699
                     *   copy words from the original string, replacing
1700
                     *   the unknown word with what follows the "oops" in
1701
                     *   the new command 
1702
                     */
1703
                    for (outp = redobuf, i = 0, w = cmd ; *w != 0 ; ++i, ++w)
1704
                    {
1705
                        
1706
                        /* see what we have */
1707
                        if (i == cur)
1708
                        {
1709
                            /* 
1710
                             *   We've reached the word to be replaced.
1711
                             *   Ignore the original token, and replace it
1712
                             *   with the word or words from the OOPS
1713
                             *   command 
1714
                             */
1715
                            for (q = p1, len = 0 ;
1716
                                 *q != '\0' && *q != '.' && *q != ','
1717
                                     && *q != '?' && *q != '!' ; ++q, ++len) ;
1718
                            memcpy(outp, p1, (size_t)len);
1719
                            outp += len;
1720
                        }
1721
                        else if (**w == '"')
1722
                        {
1723
                            char *strp;
1724
                            char *p2;
1725
                            char qu;
1726
                            int rem;
1727
1728
                            /* 
1729
                             *   It's a string - add a quote mark, then
1730
                             *   copy the string as indicated by the
1731
                             *   length prefix, then add another quote
1732
                             *   mark.  Get the length by reading the
1733
                             *   length prefix following the quote mark,
1734
                             *   and get a pointer to the text of the
1735
                             *   string, which immediately follows the
1736
                             *   length prefix.  
1737
                             */
1738
                            len = osrp2(*w + 1) - 2;
1739
                            strp = *w + 3;
1740
1741
                            /*
1742
                             *   We need to figure out what kind of quote
1743
                             *   mark to use.  If the string contains any
1744
                             *   embedded double quotes, use single quotes
1745
                             *   to delimit the string; otherwise, use
1746
                             *   double quotes.  Presume we'll use double
1747
                             *   quotes as the delimiter, then scan the
1748
                             *   string for embedded double quotes.  
1749
                             */
1750
                            for (qu = '"', p2 = strp, rem = len ; rem != 0 ;
1751
                                 --rem, ++p2)
1752
                            {
1753
                                /* 
1754
                                 *   if this is an embedded double quote,
1755
                                 *   use single quotes to delimite the
1756
                                 *   string 
1757
                                 */
1758
                                if (*p2 == '"')
1759
                                {
1760
                                    /* use single quotes as delimiters */
1761
                                    qu = '\'';
1762
1763
                                    /* no need to look any further */
1764
                                    break;
1765
                                }
1766
                            }
1767
1768
                            /* add the open quote */
1769
                            *outp++ = qu;
1770
1771
                            /* copy the string */
1772
                            memcpy(outp, strp, len);
1773
                            outp += len;
1774
1775
                            /* add the close quote */
1776
                            *outp++ = qu;
1777
                        }
1778
                        else
1779
                        {
1780
                            /* 
1781
                             *   it's an ordinary token - copy the
1782
                             *   null-terminated string for the token from
1783
                             *   the original command list 
1784
                             */
1785
                            len = strlen(*w);
1786
                            memcpy(outp, *w, (size_t)len);
1787
                            outp += len;
1788
                        }
1789
1790
                        /* add a space between words */
1791
                        *outp++ = ' ';
1792
                    }
1793
                    
1794
                    /* terminate the new string */
1795
                    *outp = '\0';
1796
                    
1797
                    /* try tokenizing the string */
1798
                    *(cmd[0]) = '\0';
1799
                    if ((wc = voctok(ctx, redobuf, cmd[0],
1800
                                     cmd, FALSE, FALSE, TRUE)) <= 0)
1801
                        return 1;
1802
                    cmd[wc] = 0;
1803
1804
                    /* start over with the typing */
1805
                    goto startover;
1806
                }
1807
                else
1808
                {
1809
                    /* 
1810
                     *   They didn't start the command with "oops", so
1811
                     *   this must be a brand new command.  Replace the
1812
                     *   original command with the new command.  
1813
                     */
1814
                    strcpy(orgbuf, oopsbuf);
1815
1816
                    /* 
1817
                     *   forget we had an unknown word so that we're sure
1818
                     *   to start over with a new command 
1819
                     */
1820
                    ctx->voccxunknown = 0;
1821
                    
1822
                    /* 
1823
                     *   set the "redo" flag to start over with the new
1824
                     *   command 
1825
                     */
1826
                    ctx->voccxredo = 1;
1827
                    
1828
                    /* 
1829
                     *   return an error to indicate the current command
1830
                     *   has been aborted 
1831
                     */
1832
                    return 1;
1833
                }
1834
            }
1835
            else
1836
            {
1837
                /*
1838
                 *   We've now encountered an unknown word, and we're
1839
                 *   going to defer resolution.  Remember this; we'll
1840
                 *   count the unknown word in the context when we return
1841
                 *   (do so only locally for now, since we may encounter
1842
                 *   more unknown words before we return, in which case we
1843
                 *   want to know that this is still the first pass).  
1844
                 */
1845
                ++unknown_count;
1846
            }
1847
        }
1848
1849
        /* display if in debug mode */
1850
        if (ctx->voccxflg & VOCCXFDBG)
1851
        {
1852
            char    buf[128];
1853
            size_t  i;
1854
            char   *p;
1855
            int     cnt;
1856
            
1857
            (void)tioshow(ctx->voccxtio);
1858
            sprintf(buf, "... %s (", cmd[cur]);
1859
            p = buf + strlen(buf);
1860
            cnt = 0;
1861
            for (i = 0 ; i < sizeof(type_names)/sizeof(type_names[0]) ; ++i)
1862
            {
1863
                if (t & (1 << i))
1864
                {
1865
                    if (cnt) *p++ = ',';
1866
                    strcpy(p, type_names[i]);
1867
                    p += strlen(p);
1868
                    ++cnt;
1869
                }
1870
            }
1871
            *p++ = ')';
1872
            *p++ = '\\';
1873
            *p++ = 'n';
1874
            *p = '\0';
1875
            tioputs(ctx->voccxtio, buf);
1876
        }
1877
        
1878
        types[cur] = t;                         /* record type of this word */
1879
    }
1880
1881
    /* if we found any unknown words, note this in our context */
1882
    ctx->voccxunknown = unknown_count;
1883
    ctx->voccxlastunk = unknown_count;
1884
    
1885
    /* successful acquisition of types */
1886
    return 0;
1887
}
1888
1889
/*
1890
 *   intersect - takes two lists and puts the intersection of them into
1891
 *   the first list.
1892
 */
1893
static int vocisect(objnum *list1, objnum *list2)
1894
{
1895
    int i, j, k;
1896
1897
    for (i = k = 0 ; list1[i] != MCMONINV ; ++i)
1898
    {
1899
        for (j = 0 ; list2[j] != MCMONINV ; ++j)
1900
        {
1901
            if (list1[i] == list2[j])
1902
            {
1903
                list1[k++] = list1[i];
1904
                break;
1905
            }
1906
        }
1907
    }
1908
    list1[k] = MCMONINV;
1909
    return(k);
1910
}
1911
1912
/*
1913
 *   Intersect lists, including parallel flags lists.  The flags from the
1914
 *   two lists for any matching object are OR'd together. 
1915
 */
1916
static int vocisect_flags(objnum *list1, uint *flags1,
1917
                          objnum *list2, uint *flags2)
1918
{
1919
    int i, j, k;
1920
1921
    for (i = k = 0 ; list1[i] != MCMONINV ; ++i)
1922
    {
1923
        for (j = 0 ; list2[j] != MCMONINV ; ++j)
1924
        {
1925
            if (list1[i] == list2[j])
1926
            {
1927
                list1[k] = list1[i];
1928
                flags1[k] = flags1[i] | flags2[j];
1929
                ++k;
1930
                break;
1931
            }
1932
        }
1933
    }
1934
    list1[k] = MCMONINV;
1935
    return(k);
1936
}
1937
1938
/*
1939
 *   get obj list: build a list of the objects that are associated with a
1940
 *   given word of player input.
1941
 */
1942
static int vocgol(voccxdef *ctx, objnum *list, uint *flags, char **wrdlst,
1943
                  int *typlst, int first, int cur, int last, int ofword)
1944
{
1945
    char      *wrd;
1946
    int        typ;
1947
    vocwdef   *v;
1948
    int        cnt;
1949
    int        len;
1950
    vocseadef  search_ctx;
1951
    int        try_plural;
1952
    int        try_noun_before_num;
1953
    int        try_endadj;
1954
    int        trying_endadj;
1955
    int        wrdtyp;
1956
1957
    /* get the current word and its type */
1958
    wrd = wrdlst[cur];
1959
    typ = typlst[cur];
1960
1961
    /* get the length of the word */
1962
    len = strlen(wrd);
1963
1964
    /*
1965
     *   Get word type: figure out the correct part of speech, given by
1966
     *   context, for a given word.  If it could count as only a
1967
     *   noun/plural or only an adjective, we use that.  If it could count
1968
     *   as either a noun/plural or an adjective, we will treat it as a
1969
     *   noun/plural if it is the last word in the name or the last word
1970
     *   before "of", otherwise as an adjective.
1971
     *   
1972
     *   If the word is unknown, treat it as a noun or adjective - treat
1973
     *   it as part of the current noun phrase.  One unknown word renders
1974
     *   the whole noun phrase unknown.  
1975
     */
1976
    try_plural = (typ & VOCT_PLURAL);
1977
1978
    /* presume we won't retry this word as an adjective */
1979
    try_endadj = FALSE;
1980
1981
    /* presume we won't retry this as a noun before a number */
1982
    try_noun_before_num = FALSE;
1983
1984
    /* we're not yet trying with adjective-at-end */
1985
    trying_endadj = FALSE;
1986
1987
    /* check to see what parts of speech are defined for this word */
1988
    if ((typ & (VOCT_NOUN | VOCT_PLURAL)) && (typ & VOCT_ADJ))
1989
    {
1990
        /*
1991
         *   This can be either an adjective or a plural/noun.  If this is
1992
         *   the last word in the noun phrase, treat it as a noun/plural if
1993
         *   possible.  Otherwise, treat it as an adjective.  
1994
         */
1995
        if (cur + 1 == last || cur == ofword - 1)
1996
        {
1997
            /* 
1998
             *   This is the last word in the entire phrase, or the last word
1999
             *   before an 'of' (which makes it the last word of its
2000
             *   subphrase).  Treat it as a noun if possible, otherwise as a
2001
             *   plural 
2002
             */
2003
            wrdtyp = ((typ & VOCT_NOUN) ? PRP_NOUN : PRP_PLURAL);
2004
2005
            /* 
2006
             *   If this can be an adjective, too, make a note to come back
2007
             *   and try it again as an adjective.  We prefer not to end a
2008
             *   noun phrase with an adjective, but we allow it, since it's
2009
             *   often convenient to abbreviate a noun phrase to just the
2010
             *   adjectives (as in TAKE RED, where there's only one object
2011
             *   nearby to which RED applies).  
2012
             */
2013
            if ((typ & VOCT_ADJ) != 0)
2014
                try_endadj = TRUE;
2015
        }
2016
        else if ((cur + 2 == last || cur == ofword - 2)
2017
                 && vocisdigit(wrdlst[cur+1][0]))
2018
        {
2019
            /*
2020
             *   This is the second-to-last word, and the last word is
2021
             *   numeric.  In this case, try this word as BOTH a noun and an
2022
             *   adjective.  Try it as an adjective first, but make a note to
2023
             *   go back and try it again as a noun. 
2024
             */
2025
            wrdtyp = PRP_ADJ;
2026
            try_noun_before_num = TRUE;
2027
        }
2028
        else
2029
        {
2030
            /* 
2031
             *   This isn't the last word, so it can only be an adjective.
2032
             *   Look at it only as an adjective.  
2033
             */
2034
            wrdtyp = PRP_ADJ;
2035
        }
2036
    }
2037
    else if (typ & VOCT_NOUN)
2038
        wrdtyp = PRP_NOUN;
2039
    else if (typ & VOCT_UNKNOWN)
2040
        wrdtyp = PRP_UNKNOWN;
2041
    else
2042
    {
2043
        /* it's just an adjective */
2044
        wrdtyp = PRP_ADJ;
2045
2046
        /* 
2047
         *   if this is the last word in the phrase, flag it as an ending
2048
         *   adjective 
2049
         */
2050
        if (cur + 1 == last || cur == ofword - 1)
2051
            trying_endadj = TRUE;
2052
    }
2053
2054
    /* display debugger information if appropriate */
2055
    if (ctx->voccxflg & VOCCXFDBG)
2056
    {
2057
        char buf[128];
2058
2059
        sprintf(buf, "... %s (treating as %s%s)\\n", wrd,
2060
                (wrdtyp == PRP_ADJ ? "adjective" :
2061
                 wrdtyp == PRP_NOUN ? "noun" :
2062
                 wrdtyp == PRP_INVALID ? "unknown" : "plural"),
2063
                (wrdtyp == PRP_NOUN && try_plural ? " + plural" : ""));
2064
        tioputs(ctx->vocxtio, buf);
2065
    }
2066
2067
    /* if this is an unknown word, it doesn't have any objects */
2068
    if (wrdtyp == PRP_UNKNOWN)
2069
    {
2070
        list[0] = MCMONINV;
2071
        return 0;
2072
    }
2073
2074
    /* we have nothing in the list yet */
2075
    cnt = 0;
2076
2077
add_words:
2078
    for (v = vocffw(ctx, wrd, len, (char *)0, 0, wrdtyp, &search_ctx)
2079
         ; v != 0 ; v = vocfnw(ctx, &search_ctx))
2080
    {
2081
        int i;
2082
2083
        /* add the matching object to the output list */
2084
        list[cnt] = v->vocwobj;
2085
2086
        /* clear the flags */
2087
        flags[cnt] = 0;
2088
2089
        /* set the PLURAL flag if this is the plural vocabulary usage */
2090
        if (wrdtyp == PRP_PLURAL)
2091
            flags[cnt] |= VOCS_PLURAL;
2092
2093
        /* set the ADJECTIVE AT END flag if appropriate */
2094
        if (wrdtyp == PRP_ADJ && trying_endadj)
2095
            flags[cnt] |= VOCS_ENDADJ;
2096
2097
        /* 
2098
         *   if this is not an exact match for the word, but is merely a
2099
         *   long-enough leading substring, flag it as truncated 
2100
         */
2101
        if (len < search_ctx.v->voclen)
2102
            flags[cnt] |= VOCS_TRUNC;
2103
2104
        /* count the additional word in the list */
2105
        ++cnt;
2106
2107
        /* 
2108
         *   if this object is already in the list with the same flags,
2109
         *   don't add it again 
2110
         */
2111
        for (i = 0 ; i < cnt - 1 ; ++i)
2112
        {
2113
            /* check for an identical entry */
2114
            if (list[i] == list[cnt-1] && flags[i] == flags[cnt-1])
2115
            {
2116
                /* take it back out of the list */
2117
                --cnt;
2118
2119
                /* no need to continue looking for the duplicate */
2120
                break;
2121
            }
2122
        }
2123
2124
        /* make sure we haven't overflowed the list */
2125
        if (cnt >= VOCMAXAMBIG)
2126
        {
2127
            vocerr(ctx, VOCERR(3),
2128
                   "The word \"%s\" refers to too many objects.", wrd);
2129
            list[0] = MCMONINV;
2130
            return -1;
2131
        }
2132
    }
2133
2134
    /*
2135
     *   if we want to go back and try the word again as a noun before a
2136
     *   number (as in "button 5"), do so now 
2137
     */
2138
    if (try_noun_before_num && wrdtyp == PRP_ADJ)
2139
    {
2140
        /* change the word type to noun */
2141
        wrdtyp = PRP_NOUN;
2142
2143
        /* don't try this again */
2144
        try_noun_before_num = FALSE;
2145
2146
        /* add the words for the noun usage */
2147
        goto add_words;
2148
    }
2149
2150
    /*
2151
     *   if we're interpreting the word as a noun, and the word can be a
2152
     *   plural, add in the plural interpretation as well 
2153
     */
2154
    if (try_plural && wrdtyp != PRP_PLURAL)
2155
    {
2156
        /* change the word type to plural */
2157
        wrdtyp = PRP_PLURAL;
2158
2159
        /* don't try plurals again */
2160
        try_plural = FALSE;
2161
2162
        /* add the words for the plural usage */
2163
        goto add_words;
2164
    }
2165
2166
    /* 
2167
     *   if this was the last word in the phrase, and it could have been
2168
     *   an adjective, try it again as an adjective 
2169
     */
2170
    if (try_endadj && wrdtyp != PRP_ADJ)
2171
    {
2172
        /* change the word type to adjective */
2173
        wrdtyp = PRP_ADJ;
2174
2175
        /* note that we're retrying as an adjective */
2176
        trying_endadj = TRUE;
2177
2178
        /* don't try this again */
2179
        try_endadj = FALSE;
2180
2181
        /* add the words for the adjective usage */
2182
        goto add_words;
2183
    }
2184
2185
    /*
2186
     *   If we're interpreting the word as an adjective, and it's
2187
     *   numeric, include objects with "#" in their adjective list --
2188
     *   these objects allow arbitrary numbers as adjectives.  Don't do
2189
     *   this if there's only the one word.  
2190
     */
2191
    if (vocisdigit(wrd[0]) && wrdtyp == PRP_ADJ && first + 1 != last)
2192
    {
2193
        wrd = "#";
2194
        len = 1;
2195
        goto add_words;
2196
    }
2197
2198
    list[cnt] = MCMONINV;
2199
    return cnt;
2200
}
2201
2202
/*
2203
 *   Add the user-defined word for "of" to a buffer.  If no such word is
2204
 *   defined by the user (with the specialWords construct), add "of".  
2205
 */
2206
static void vocaddof(voccxdef *ctx, char *buf)
2207
{
2208
    if (ctx->voccxspp)
2209
    {
2210
        size_t len = ctx->voccxspp[1];
2211
        size_t oldlen = strlen(buf);
2212
        memcpy(buf + oldlen, ctx->voccxspp + 2, len);
2213
        buf[len + oldlen] = '\0';
2214
    }
2215
    else
2216
        strcat(buf, "of");
2217
}
2218
2219
/* ------------------------------------------------------------------------ */
2220
/*
2221
 *   Call the parseNounPhrase user function, if defined, to attempt to
2222
 *   parse a noun phrase.
2223
 *   
2224
 *   Returns VOC_PNP_ERROR if the hook function indicates that an error
2225
 *   occurred; PNP_DEFAULT if the hook function told us to use the default
2226
 *   list; or PNP_SUCCESS to indicate that the hook function provided a
2227
 *   list to use.  
2228
 */
2229
static int voc_pnp_hook(voccxdef *ctx, char *cmd[], int typelist[],
2230
                        int cur, int *next, int complain,
2231
                        vocoldef *out_nounlist, int *out_nouncount,
2232
                        int chkact, int *no_match)
2233
{
2234
    runcxdef *rcx = ctx->voccxrun;
2235
    runsdef val;
2236
    int wordcnt;
2237
    char **cmdp;
2238
    int outcnt;
2239
    vocoldef *outp;
2240
    int i;
2241
2242
    /* if parseNounPhrase isn't defined, use the default handling */
2243
    if (ctx->voccxpnp == MCMONINV)
2244
        return VOC_PNP_DEFAULT;
2245
2246
    /* push the actor-check flag */
2247
    val.runstyp = (chkact ? DAT_TRUE : DAT_NIL);
2248
    runpush(rcx, val.runstyp, &val);
2249
2250
    /* push the complain flag */
2251
    val.runstyp = (complain ? DAT_TRUE : DAT_NIL);
2252
    runpush(rcx, val.runstyp, &val);
2253
2254
    /* push the current index (adjusted to 1-based user convention) */
2255
    runpnum(rcx, cur + 1);
2256
2257
    /* count the entries in the command list */
2258
    for (wordcnt = 0, cmdp = cmd ; *cmdp != 0 && **cmdp != '\0' ;
2259
         ++wordcnt, ++cmdp) ;
2260
2261
    /* push the type list */
2262
    voc_push_numlist(ctx, (uint *)typelist, wordcnt);
2263
2264
    /* push the command word list */
2265
    voc_push_strlist_arr(ctx, cmd, wordcnt);
2266
2267
    /* call the method */
2268
    runfn(rcx, ctx->voccxpnp, 5);
2269
2270
    /* check the return value */
2271
    if (runtostyp(rcx) == DAT_NUMBER)
2272
    {
2273
        /* return the status code directly from the hook function */
2274
        return (int)runpopnum(rcx);
2275
    }
2276
    else if (runtostyp(rcx) == DAT_LIST)
2277
    {
2278
        uchar *lstp;
2279
        uint lstsiz;
2280
        
2281
        /* pop the list */
2282
        lstp = runpoplst(rcx);
2283
2284
        /* read and skip the size prefix */
2285
        lstsiz = osrp2(lstp);
2286
        lstsiz -= 2;
2287
        lstp += 2;
2288
2289
        /* the first element should be the next index */
2290
        if (lstsiz > 1 && *lstp == DAT_NUMBER)
2291
        {
2292
            /* set the 'next' pointer, adjusting to 0-based indexing */
2293
            *next = osrp4(lstp+1) - 1;
2294
2295
            /* 
2296
             *   If 'next' is out of range, force it into range.  We can't
2297
             *   go backwards (so 'next' must always be at least 'cur'),
2298
             *   and we can't go past the null element at the end of the
2299
             *   list. 
2300
             */
2301
            if (*next < cur)
2302
                *next = cur;
2303
            else if (*next > wordcnt)
2304
                *next = wordcnt;
2305
2306
            /* skip the list entry */
2307
            lstadv(&lstp, &lstsiz);
2308
        }
2309
        else
2310
        {
2311
            /* ignore the list and use the default parsing */
2312
            return VOC_PNP_DEFAULT;
2313
        }
2314
2315
        /* read the list entries and store them in the output array */
2316
        for (outcnt = 0, outp = out_nounlist ; lstsiz > 0 ; )
2317
        {
2318
            /* make sure we have room for another entry */
2319
            if (outcnt >= VOCMAXAMBIG - 1)
2320
                break;
2321
            
2322
            /* get the next list entry, and store it in the output array */
2323
            if (*lstp == DAT_NIL)
2324
            {
2325
                /* set the list entry */
2326
                outp->vocolobj = MCMONINV;
2327
2328
                /* skip the entry */
2329
                lstadv(&lstp, &lstsiz);
2330
            }
2331
            else if (*lstp == DAT_OBJECT)
2332
            {
2333
                /* set the list entry */
2334
                outp->vocolobj = osrp2(lstp+1);
2335
2336
                /* skip the list entry */
2337
                lstadv(&lstp, &lstsiz);
2338
            }
2339
            else
2340
            {
2341
                /* ignore other types in the list */
2342
                lstadv(&lstp, &lstsiz);
2343
                continue;
2344
            }
2345
2346
            /* check for a flag entry */
2347
            if (lstsiz > 0 && *lstp == DAT_NUMBER)
2348
            {
2349
                /* set the flags */
2350
                outp->vocolflg = (int)osrp4(lstp+1);
2351
                
2352
                /* skip the number */
2353
                lstadv(&lstp, &lstsiz);
2354
            }
2355
            else
2356
            {
2357
                /* no flags were specified - use the default */
2358
                outp->vocolflg = 0;
2359
            }
2360
2361
            /* set the word list boundaries */
2362
            outp->vocolfst = cmd[cur];
2363
            outp->vocollst = cmd[*next - 1];
2364
2365
            /* count the entry */
2366
            ++outp;
2367
            ++outcnt;
2368
        }
2369
2370
        /* terminate the list */
2371
        outp->vocolobj = MCMONINV;
2372
        outp->vocolflg = 0;
2373
2374
        /* set the output count */
2375
        *out_nouncount = outcnt;
2376
2377
        /* 
2378
         *   set "no_match" appropriately -- set "no_match" true if we're
2379
         *   returning an empty list and we parsed one or more words 
2380
         */
2381
        if (no_match != 0)
2382
            *no_match = (outcnt == 0 && *next > cur);
2383
2384
        /*
2385
         *   Adjust the unknown word count in the context.  If the routine
2386
         *   parsed any unknown words, decrement the unknown word count in
2387
         *   the context by the number of unknown words parsed, since
2388
         *   these have now been dealt with.  If the return list contains
2389
         *   any objects flagged as having unknown words, add the count of
2390
         *   such objects back into the context, since we must still
2391
         *   resolve these at disambiguation time. 
2392
         */
2393
        for (i = cur ; i < *next ; ++i)
2394
        {
2395
            /* if this parsed word was unknown, remove it from the count */
2396
            if ((typelist[i] & VOCT_UNKNOWN) != 0)
2397
                --(ctx->voccxunknown);
2398
        }
2399
        for (i = 0, outp = out_nounlist ; i < outcnt ; ++i)
2400
        {
2401
            /* if this object has the unknown flag, count it */
2402
            if ((outp->vocolflg & VOCS_UNKNOWN) != 0)
2403
                ++(ctx->voccxunknown);
2404
        }
2405
2406
        /* indicate that the hook provided a list */
2407
        return VOC_PNP_SUCCESS;
2408
    }
2409
    else
2410
    {
2411
        /* 
2412
         *   ignore any other return value - consider others equivalent to
2413
         *   DEFAULT 
2414
         */
2415
        rundisc(rcx);
2416
        return VOC_PNP_DEFAULT;
2417
    }
2418
}
2419
2420
/* ------------------------------------------------------------------------ */
2421
/*
2422
 *   Build an object name from the words in a command 
2423
 */
2424
void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[],
2425
                       int firstwrd, int lastwrd)
2426
{
2427
    int i;
2428
    
2429
    /* run through the range of words, and add them to the buffer */
2430
    for (i = firstwrd, namebuf[0] = '\0' ; i < lastwrd ; ++i)
2431
    {
2432
        if (voc_check_special(ctx, cmd[i], VOCW_OF))
2433
            vocaddof(ctx, namebuf);
2434
        else
2435
            strcat(namebuf, cmd[i]);
2436
        
2437
        if (cmd[i][strlen(cmd[i])-1] == '.' && i + 1 < lastwrd)
2438
            strcat(namebuf, "\\");
2439
2440
        if (i + 1 < lastwrd)
2441
            strcat(namebuf, " ");
2442
    }
2443
}
2444
2445
/*
2446
 *   Make an object name from a list entry 
2447
 */
2448
void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf,
2449
                                 char *cmd[], char *firstwrd, char *lastwrd)
2450
{
2451
    int i, i1, i2;
2452
    
2453
    /* find the cmd indices */
2454
    for (i = i1 = i2 = 0 ; cmd[i] != 0 && *cmd[i] != 0 ; ++i)
2455
    {
2456
        if (cmd[i] == firstwrd)
2457
            i1 = i;
2458
        if (cmd[i] == lastwrd)
2459
            i2 = i + 1;
2460
    }
2461
2462
    /* build the name */
2463
    voc_make_obj_name(ctx, namebuf, cmd, i1, i2);
2464
}
2465
2466
2467
/* ------------------------------------------------------------------------ */
2468
/*
2469
 *   get 1 obj - attempts to figure out the limits of a single noun
2470
 *   phrase.  Aside from dealing with special words here ("all", "it",
2471
 *   "them", string objects, numeric objects), we will accept a basic noun
2472
 *   phrase of the form [article][adjective*][noun]["of" [noun-phrase]].
2473
 *   (Note that this is not actually recursive; only one "of" can occur in
2474
 *   a noun phrase.)  If successful, we will construct a list of all
2475
 *   objects that have all the adjectives and nouns in the noun phrase.
2476
 *   Note that plurals are treated basically like nouns, except that we
2477
 *   will flag them so that the disambiguator knows to include all objects
2478
 *   that work with the plural.
2479
 *   
2480
 *   Note that we also allow the special constructs "all [of]
2481
 *   <noun-phrase>" and "both [of] <noun-phrase>"; these are treated
2482
 *   identically to normal plurals.
2483
 *   
2484
 *   If no_match is not null, we'll set it to true if we found valid
2485
 *   syntax but no matching objects, false otherwise.  
2486
 */
2487
static int vocg1o(voccxdef *ctx, char *cmd[], int typelist[],
2488
                  int cur, int *next, int complain, vocoldef *nounlist,
2489
                  int chkact, int *no_match)
2490
{
2491
    int     l1;
2492
    int     firstwrd;
2493
    int     i;
2494
    int     ofword = -1;
2495
    int     hypothetical_last = -1;
2496
    int     trim_flags = 0;
2497
    int     outcnt = 0;
2498
    objnum *list1;
2499
    uint   *flags1;
2500
    objnum *list2;
2501
    uint   *flags2;
2502
    char   *namebuf;
2503
    int     has_any = FALSE;
2504
    uchar  *save_sp;
2505
    int     found_plural;
2506
    int     unknown_count;
2507
    int     trying_count = FALSE;
2508
    int     retry_with_count;
2509
2510
    voc_enter(ctx, &save_sp);
2511
    VOC_MAX_ARRAY(ctx, objnum, list1);
2512
    VOC_MAX_ARRAY(ctx, uint,   flags1);
2513
    VOC_MAX_ARRAY(ctx, objnum, list2);
2514
    VOC_MAX_ARRAY(ctx, uint,   flags2);
2515
    VOC_STK_ARRAY(ctx, char,   namebuf, VOCBUFSIZ);
2516
2517
    /* presume we'll find a match */
2518
    if (no_match != 0)
2519
        *no_match = FALSE;
2520
2521
    /* start at the first word */
2522
    *next = cur;
2523
2524
    /* if we're at the end of the command, return no objects in list */
2525
    if (cur == -1 || !cmd[cur]) { VOC_RETVAL(ctx, save_sp, 0); }
2526
2527
    /* show trace message if in debug mode */
2528
    if (ctx->voccxflg & VOCCXFDBG)
2529
        tioputs(ctx->vocxtio,
2530
                chkact ? ". Checking for actor\\n"
2531
                : ". Reading noun phrase\\n");
2532
2533
    /* try the user parseNounPhrase hook */
2534
    switch(voc_pnp_hook(ctx, cmd, typelist, cur, next, complain,
2535
                        nounlist, &outcnt, chkact, no_match))
2536
    {
2537
    case VOC_PNP_DEFAULT:
2538
        /* continue on to the default processing */
2539
        break;
2540
2541
    case VOC_PNP_ERROR:
2542
    default:
2543
        /* return an error */
2544
        VOC_RETVAL(ctx, save_sp, -1);
2545
2546
    case VOC_PNP_SUCCESS:
2547
        /* use their list */
2548
        VOC_RETVAL(ctx, save_sp, outcnt);
2549
    }
2550
2551
    /* check for a quoted string */
2552
    if (*cmd[cur] == '"')
2553
    {
2554
        /* can't use a quoted string as an actor */
2555
        if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
2556
        
2557
        if (ctx->voccxflg & VOCCXFDBG)
2558
            tioputs(ctx->vocxtio, "... found quoted string\\n");
2559
2560
        nounlist[outcnt].vocolobj = MCMONINV;
2561
        nounlist[outcnt].vocolflg = VOCS_STR;
2562
        nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
2563
        *next = ++cur;
2564
        ++outcnt;
2565
        VOC_RETVAL(ctx, save_sp, outcnt);
2566
    }
2567
2568
    /* check for ALL/ANY/BOTH/EITHER [OF] <plural> contruction */
2569
    if ((vocspec(cmd[cur], VOCW_ALL)
2570
         || vocspec(cmd[cur], VOCW_BOTH)
2571
         || vocspec(cmd[cur], VOCW_ANY)) &&
2572
        cmd[cur+1] != (char *)0)
2573
    {
2574
        int nxt;
2575
        int n = cur+1;
2576
        int has_of;
2577
2578
        /* can't use ALL as an actor */
2579
        if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
2580
2581
        /* remember whether we have "any" or "either" */
2582
        has_any = vocspec(cmd[cur], VOCW_ANY);
2583
2584
        /* check for optional 'of' */
2585
        if (voc_check_special(ctx, cmd[n], VOCW_OF))
2586
        {
2587
            if (ctx->voccxflg & VOCCXFDBG)
2588
                tioputs(ctx->vocxtio, "... found ALL/ANY/BOTH/EITHER OF\\n");
2589
2590
            has_of = TRUE;
2591
            n++;
2592
            if (!cmd[n])
2593
            {
2594
                char *p;
2595
                int   ver;
2596
                
2597
                if (vocspec(cmd[cur], VOCW_ALL))
2598
                {
2599
                    ver = VOCERR(4);
2600
                    p = "I think you left something out after \"all of\".";
2601
                }
2602
                else if (vocspec(cmd[cur], VOCW_ANY))
2603
                {
2604
                    ver = VOCERR(29);
2605
                    p = "I think you left something out after \"any of\".";
2606
                }
2607
                else
2608
                {
2609
                    ver = VOCERR(5);
2610
                    p = "There's something missing after \"both of\".";
2611
                }
2612
                vocerr(ctx, ver, p);
2613
                VOC_RETVAL(ctx, save_sp, -1);
2614
            }
2615
        }
2616
        else
2617
            has_of = FALSE;
2618
2619
        nxt = n;
2620
        if (typelist[n] & VOCT_ARTICLE) ++n;        /* skip leading article */
2621
        for ( ;; )
2622
        {
2623
            if (!cmd[n])
2624
                break;
2625
2626
            if (voc_check_special(ctx, cmd[n], VOCW_OF))
2627
            {
2628
                ++n;
2629
                if (!cmd[n])
2630
                {
2631
                    vocerr(ctx, VOCERR(6), "I expected a noun after \"of\".");
2632
                    VOC_RETVAL(ctx, save_sp, -1);
2633
                }
2634
                if (*cmd[n] & VOCT_ARTICLE) ++n;
2635
            }
2636
            else if (typelist[n] & (VOCT_ADJ | VOCT_NOUN))
2637
                ++n;
2638
            else
2639
                break;
2640
        }
2641
2642
        /*
2643
         *   Accept the ALL if the last word is a plural.  Accept the ANY
2644
         *   if either we don't have an OF (ANY NOUN is okay even without
2645
         *   a plural), or if we have OF and a plural.  (More simply put,
2646
         *   accept the ALL or ANY if the last word is a plural, or if we
2647
         *   have ANY but not OF).  
2648
         */
2649
        if (n > cur && ((typelist[n-1] & VOCT_PLURAL)
2650
                        || (has_any && !has_of)))
2651
        {
2652
            if (ctx->voccxflg & VOCCXFDBG)
2653
                tioputs(ctx->vocxtio,
2654
                        "... found ALL/ANY/BOTH/EITHER + noun phrase\\n");
2655
2656
            cur = nxt;
2657
        }
2658
    }
2659
    
2660
    if (vocspec(cmd[cur], VOCW_ALL) && !has_any)
2661
    {
2662
        /* can't use ALL as an actor */
2663
        if (chkact)
2664
        {
2665
            VOC_RETVAL(ctx, save_sp, 0);
2666
        }
2667
        
2668
        if (ctx->voccxflg & VOCCXFDBG)
2669
            tioputs(ctx->vocxtio, "... found ALL\\n");
2670
2671
        nounlist[outcnt].vocolobj = MCMONINV;
2672
        nounlist[outcnt].vocolflg = VOCS_ALL;
2673
        nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
2674
        ++outcnt;
2675
        ++cur;
2676
2677
        if (cmd[cur] && vocspec(cmd[cur], VOCW_BUT))
2678
        {
2679
            int       cnt;
2680
            int       i;
2681
            vocoldef *xlist;
2682
            uchar    *inner_save_sp;
2683
2684
            if (ctx->voccxflg & VOCCXFDBG)
2685
                tioputs(ctx->vocxtio, "... found ALL EXCEPT\\n");
2686
2687
            voc_enter(ctx, &inner_save_sp);
2688
            VOC_MAX_ARRAY(ctx, vocoldef, xlist);
2689
2690
            cur++;
2691
            cnt = vocgobj(ctx, cmd, typelist, cur, next, complain, xlist, 1, 
2692
                          chkact, 0);
2693
            if (cnt < 0)
2694
            {
2695
                /* 
2696
                 *   An error occurred - return it.  Note that, since
2697
                 *   we're returning from the entire function, we're
2698
                 *   popping the save_sp frame, NOT the inner_save_sp
2699
                 *   frame -- the inner frame is nested within the save_sp
2700
                 *   frame, and we want to pop the entire way out since
2701
                 *   we're exiting the entire function. 
2702
                 */
2703
                VOC_RETVAL(ctx, save_sp, cnt);
2704
            }
2705
            cur = *next;
2706
            for (i = 0 ; i < cnt ; ++i)
2707
            {
2708
                OSCPYSTRUCT(nounlist[outcnt], xlist[i]);
2709
                nounlist[outcnt].vocolflg |= VOCS_EXCEPT;
2710
                ++outcnt;
2711
            }
2712
2713
            voc_leave(ctx, inner_save_sp);
2714
        }
2715
        *next = cur;
2716
        nounlist[outcnt].vocolobj = MCMONINV;
2717
        nounlist[outcnt].vocolflg = 0;
2718
        VOC_RETVAL(ctx, save_sp, outcnt);
2719
    }
2720
    
2721
    switch(*cmd[cur])
2722
    {
2723
    case VOCW_IT:
2724
        nounlist[outcnt].vocolflg = VOCS_IT;
2725
        goto do_special;
2726
    case VOCW_THEM:
2727
        nounlist[outcnt].vocolflg = VOCS_THEM;
2728
        goto do_special;
2729
    case VOCW_HIM:
2730
        nounlist[outcnt].vocolflg = VOCS_HIM;
2731
        goto do_special;
2732
    case VOCW_HER:
2733
        nounlist[outcnt].vocolflg = VOCS_HER;
2734
        /* FALLTHRU */
2735
    do_special:
2736
        if (ctx->voccxflg & VOCCXFDBG)
2737
            tioputs(ctx->vocxtio, "... found pronoun\\n");
2738
2739
        *next = cur + 1;
2740
        nounlist[outcnt].vocolobj = MCMONINV;
2741
        nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
2742
        ++outcnt;
2743
        VOC_RETVAL(ctx, save_sp, outcnt);
2744
    default:
2745
        break;
2746
    }
2747
2748
    if (((typelist[cur]
2749
          & (VOCT_ARTICLE | VOCT_ADJ | VOCT_NOUN | VOCT_UNKNOWN)) == 0)
2750
        && !vocisdigit(*cmd[cur]))
2751
    {
2752
        VOC_RETVAL(ctx, save_sp, 0);
2753
    }
2754
2755
    if (typelist[cur] & VOCT_ARTICLE)
2756
    {
2757
        ++cur;
2758
        if (cmd[cur] == (char *)0
2759
            || ((typelist[cur] & (VOCT_ADJ | VOCT_NOUN | VOCT_UNKNOWN)) == 0
2760
                && !vocisdigit(*cmd[cur])))
2761
        {
2762
            vocerr(ctx, VOCERR(7), "An article must be followed by a noun.");
2763
            *next = cur;
2764
            VOC_RETVAL(ctx, save_sp, -1);
2765
        }
2766
    }
2767
2768
    /* start at the current word */
2769
    firstwrd = cur;
2770
2771
    /* scan words for inclusion in this noun phrase */
2772
    for (found_plural = FALSE, unknown_count = 0, l1 = 0 ; ; )
2773
    {
2774
        if (cmd[cur] == (char *)0)
2775
            break;
2776
2777
        if (typelist[cur] & VOCT_ADJ)
2778
            ++cur;
2779
        else if (typelist[cur] & VOCT_UNKNOWN)
2780
        {
2781
            /* 
2782
             *   Remember that we found an unknown word, but include it in
2783
             *   the noun phrase - this will render the entire noun phrase
2784
             *   unknown, but we'll resolve that later.
2785
             */
2786
            ++unknown_count;
2787
            ++cur;
2788
        }
2789
        else if (typelist[cur] & VOCT_NOUN)
2790
        {
2791
            ++cur;
2792
            if (cmd[cur] == (char *)0) break;
2793
            if (vocisdigit(*cmd[cur])) ++cur;
2794
            if (cmd[cur] == (char *)0) break;
2795
            if (!voc_check_special(ctx, cmd[cur], VOCW_OF)) break;
2796
        }
2797
        else if (vocisdigit(*cmd[cur]))
2798
            ++cur;
2799
        else if (voc_check_special(ctx, cmd[cur], VOCW_OF))
2800
        {
2801
            ++cur;
2802
            if (ofword != -1)
2803
            {
2804
                /* there's already one 'of' - we must be done */
2805
                --cur;
2806
                break;
2807
            }
2808
            ofword = cur-1;
2809
            if (typelist[cur] & VOCT_ARTICLE)   /* allow article after "of" */
2810
                ++cur;
2811
        }
2812
        else
2813
            break;
2814
2815
        /* note whether we found anything that might be a plural */
2816
        if (cmd[cur] != 0 && (typelist[cur] & VOCT_PLURAL) != 0)
2817
            found_plural = TRUE;
2818
    }
2819
2820
try_again:
2821
    /* 
2822
     *   build a printable string consisting of the words in the noun
2823
     *   phrase, for displaying error messages 
2824
     */
2825
    voc_make_obj_name(ctx, namebuf, cmd, firstwrd, cur);
2826
2827
    /* remember the next word after this noun phrase */
2828
    *next = cur;
2829
2830
    /*
2831
     *   If we have any unknown words, we won't be able to match any
2832
     *   objects for the noun phrase.  Return with one entry in the list,
2833
     *   but use an invalid object and mark the object as containing
2834
     *   unknown words.  
2835
     */
2836
    if (unknown_count > 0)
2837
    {
2838
        /*
2839
         *   Add one unknown object for each unknown word.  This lets us
2840
         *   communicate the number of unknown words that we found to the
2841
         *   disambiguator, which will later attempt to resolve the
2842
         *   reference.  Each object we add is the same; they're here only
2843
         *   for the word count.  
2844
         */
2845
        for ( ; unknown_count > 0 ; --unknown_count)
2846
        {
2847
            nounlist[outcnt].vocolobj = MCMONINV;
2848
            nounlist[outcnt].vocolflg = VOCS_UNKNOWN;
2849
            nounlist[outcnt].vocolfst = cmd[firstwrd];
2850
            nounlist[outcnt].vocollst = cmd[cur-1];
2851
            ++outcnt;
2852
        }
2853
        nounlist[outcnt].vocolobj = MCMONINV;
2854
        nounlist[outcnt].vocolflg = 0;
2855
        VOC_RETVAL(ctx, save_sp, outcnt);
2856
    }
2857
2858
    /* get the list of objects that match the first word */
2859
    l1 = vocgol(ctx, list1, flags1, cmd, typelist,
2860
                firstwrd, firstwrd, cur, ofword);
2861
2862
    /*
2863
     *   Allow retrying with a count plus a plural if the first word is a
2864
     *   number, and we have something plural in the list.  Only treat "1"
2865
     *   this way if more words follow in the noun phrase.  
2866
     */
2867
    retry_with_count = ((vocisdigit(*cmd[firstwrd]) && found_plural)
2868
                        || (vocisdigit(*cmd[firstwrd])
2869
                            && cur != firstwrd+1
2870
                            && atoi(cmd[firstwrd]) == 1));
2871
2872
    /* see if we found anything on the first word */
2873
    if (l1 <= 0)
2874
    {
2875
        if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
2876
2877
        if (vocisdigit(*cmd[firstwrd]))
2878
        {
2879
            if (retry_with_count)
2880
            {
2881
                /* interpret it as a count plus a plural */
2882
                trying_count = TRUE;
2883
2884
                /* don't try this again */
2885
                retry_with_count = FALSE;
2886
            }
2887
            else
2888
            {
2889
                /* not a plural - take the number as the entire noun phrase */
2890
                nounlist[outcnt].vocolobj = MCMONINV;
2891
                nounlist[outcnt].vocolflg = VOCS_NUM;
2892
                nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst =
2893
                    cmd[firstwrd];
2894
                *next = firstwrd + 1;
2895
                ++outcnt;
2896
                nounlist[outcnt].vocolobj = MCMONINV;
2897
                nounlist[outcnt].vocolflg = 0;
2898
                VOC_RETVAL(ctx, save_sp, outcnt);
2899
            }
2900
        }
2901
        else
2902
        {
2903
            /* 
2904
             *   display a message if we didn't already (if vocgol
2905
             *   returned less than zero, it already displayed its own
2906
             *   error message) 
2907
             */
2908
            if (l1 == 0)
2909
                vocerr(ctx, VOCERR(9), "I don't see any %s here.", namebuf);
2910
2911
            /* return failure */
2912
            VOC_RETVAL(ctx, save_sp, -1);
2913
        }
2914
    }
2915
2916
retry_exclude_first:
2917
    for (i = firstwrd + 1 ; i < cur ; ++i)
2918
    {
2919
        int l2;
2920
        
2921
        if (voc_check_special(ctx, cmd[i], VOCW_OF)
2922
            || (typelist[i] & VOCT_ARTICLE))
2923
            continue;
2924
2925
        /* get the list for this new object */
2926
        l2 = vocgol(ctx, list2, flags2, cmd, typelist,
2927
                    firstwrd, i, cur, ofword);
2928
2929
        /* if that failed and displayed an error, return failure */
2930
        if (l2 < 0)
2931
        {
2932
            /* return failure */
2933
            VOC_RETVAL(ctx, save_sp, -1);
2934
        }
2935
2936
        /*
2937
         *   Intersect the last list with the new list.  If the previous
2938
         *   list didn't have anything in it, it must mean that the word
2939
         *   list started with a number, in which case we're trying to
2940
         *   interpret this as a count plus a plural.  So, don't intersect
2941
         *   the list if there was nothing in the first list. 
2942
         */
2943
        if (l1 == 0)
2944
        {
2945
            /* just copy the new list */
2946
            l1 = l2;
2947
            memcpy(list1, list2, (size_t)((l1+1) * sizeof(list1[0])));
2948
            memcpy(flags1, flags2, (size_t)(l1 * sizeof(flags1[0])));
2949
        }       
2950
        else
2951
        {
2952
            /* intersect the two lists */
2953
            l1 = vocisect_flags(list1, flags1, list2, flags2);
2954
        }
2955
2956
        /*
2957
         *   If there's nothing in the list, it means that there's no
2958
         *   object that defines all of these words.  
2959
         */
2960
        if (l1 == 0)
2961
        {
2962
            if (ctx->voccxflg & VOCCXFDBG)
2963
                tioputs(ctx->vocxtio,
2964
                        "... can't find any objects matching these words\\n");
2965
            /*
2966
             *   If there's an "of", remove the "of" and everything that
2967
             *   follows, and go back and reprocess the part up to the
2968
             *   "of" -- treat it as a sentence that has two objects, with
2969
             *   "of" as the preposition introducing the indirect object.
2970
             */
2971
            if (ofword != -1)
2972
            {
2973
                if (ctx->voccxflg & VOCCXFDBG)
2974
                    tioputs(ctx->vocxtio,
2975
                            "... dropping the part after OF and retrying\\n");
2976
2977
                /* 
2978
                 *   drop the part from 'of' on - scan only from firstwrd
2979
                 *   to the word before 'of' 
2980
                 */
2981
                hypothetical_last = cur;
2982
                trim_flags |= VOCS_TRIMPREP;
2983
                cur = ofword;
2984
2985
                /* forget that we have an 'of' phrase at all */
2986
                ofword = -1;
2987
2988
                /* retry with the new, shortened phrase */
2989
                goto try_again;
2990
            }
2991
2992
            /*
2993
             *   Try again with the count + plural interpretation, if
2994
             *   possible 
2995
             */
2996
            if (retry_with_count)
2997
            {
2998
                if (ctx->voccxflg & VOCCXFDBG)
2999
                    tioputs(ctx->vocxtio,
3000
                         "... treating the number as a count and retrying\\n");
3001
3002
                /* we've exhausted our retries */
3003
                retry_with_count = FALSE;
3004
                trying_count = TRUE;
3005
3006
                /* go try it */
3007
                goto retry_exclude_first;
3008
            }
3009
3010
            /*
3011
             *   If one of the words will work as a preposition, and we
3012
             *   took it as an adjective, go back and try the word again
3013
             *   as a preposition.  
3014
             */
3015
            for (i = cur - 1; i > firstwrd ; --i)
3016
            {
3017
                if (typelist[i] & VOCT_PREP)
3018
                {
3019
                    if (ctx->voccxflg & VOCCXFDBG)
3020
                        tioputs(ctx->vocxtio,
3021
                                "... changing word to prep and retrying\\n");
3022
3023
                    hypothetical_last = cur;
3024
                    trim_flags |= VOCS_TRIMPREP;
3025
                    cur = i;
3026
                    goto try_again;
3027
                }
3028
            }
3029
3030
            /* if just checking actor, don't display an error */
3031
            if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
3032
3033
            /* 
3034
             *   tell the player about it unless supressing complaints,
3035
             *   and return an error 
3036
             */
3037
            if (complain)
3038
                vocerr(ctx, VOCERR(9), "I don't see any %s here.", namebuf);
3039
            if (no_match != 0)
3040
                *no_match = TRUE;
3041
            VOC_RETVAL(ctx, save_sp, 0);
3042
        }
3043
    }
3044
3045
    /*
3046
     *   We have one or more objects, so make a note of how we found
3047
     *   them.
3048
     */
3049
    if (ctx->voccxflg & VOCCXFDBG)
3050
        tioputs(ctx->vocxtio, "... found objects matching vocabulary:\\n");
3051
3052
    /* store the list of objects that match all of our words */
3053
    for (i = 0 ; i < l1 ; ++i)
3054
    {
3055
        if (ctx->voccxflg & VOCCXFDBG)
3056
        {
3057
            tioputs(ctx->voccxtio, "..... ");
3058
            runppr(ctx->voccxrun, list1[i], PRP_SDESC, 0);
3059
            tioflushn(ctx->voccxtio, 1);
3060
        }
3061
3062
        nounlist[outcnt].vocolfst = cmd[firstwrd];
3063
        nounlist[outcnt].vocollst = cmd[cur-1];
3064
        nounlist[outcnt].vocolflg =
3065
            flags1[i] | (trying_count ? VOCS_COUNT : 0) | trim_flags;
3066
        if (trim_flags)
3067
            nounlist[outcnt].vocolhlst = cmd[hypothetical_last - 1];
3068
        if (has_any)
3069
            nounlist[outcnt].vocolflg |= VOCS_ANY;
3070
        nounlist[outcnt++].vocolobj = list1[i];
3071
        if (outcnt > VOCMAXAMBIG)
3072
        {
3073
            vocerr(ctx, VOCERR(10),
3074
                   "You're referring to too many objects with \"%s\".",
3075
                   namebuf);
3076
            VOC_RETVAL(ctx, save_sp, -2);
3077
        }
3078
    }
3079
3080
    /*
3081
     *   If we have a one-word noun phrase, and the word is a number, add
3082
     *   the number object into the list of objects we're considering,
3083
     *   even though we found an object that matches.  We'll probably
3084
     *   easily disambiguate this later, but we need to keep open the
3085
     *   possibility that they're just referring to a number rather than a
3086
     *   numbered adjective for now.
3087
     */
3088
    if (firstwrd + 1 == cur && vocisdigit(*cmd[firstwrd]))
3089
    {
3090
        /* add just the number as an object */
3091
        nounlist[outcnt].vocolobj = ctx->voccxnum;
3092
        nounlist[outcnt].vocolflg = VOCS_NUM;
3093
        nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst =
3094
            cmd[firstwrd];
3095
        ++outcnt;
3096
    }
3097
3098
    /* terminate the list */
3099
    nounlist[outcnt].vocolobj = MCMONINV;
3100
    nounlist[outcnt].vocolflg = 0;
3101
3102
    /* return the number of objects in our match list */
3103
    VOC_RETVAL(ctx, save_sp, outcnt);
3104
}
3105
3106
/*
3107
 *   get obj - gets one or more noun lists (a flag, "multi", says whether
3108
 *   we should allow multiple lists).  We use vocg1o() to read noun lists
3109
 *   one at a time, and keep going (if "multi" is true) as long as there
3110
 *   are more "and <noun-phrase>" clauses.
3111
 *   
3112
 *   If no_match is not null, we'll set it to true if the syntax was okay
3113
 *   but we didn't find any match for the list of words, false otherwise.  
3114
 */
3115
int vocgobj(voccxdef *ctx, char *cmd[], int typelist[],
3116
            int cur, int *next, int complain, vocoldef *nounlist,
3117
            int multi, int chkact, int *no_match)
3118
{
3119
    int       cnt;
3120
    int       outcnt = 0;
3121
    int       i;
3122
    int       again = FALSE;
3123
    int       lastcur;
3124
    vocoldef *tmplist;
3125
    uchar    *save_sp;
3126
3127
    voc_enter(ctx, &save_sp);
3128
    VOC_MAX_ARRAY(ctx, vocoldef, tmplist);
3129
3130
    for ( ;; )
3131
    {
3132
        /* try getting a single object */
3133
        cnt = vocg1o(ctx, cmd, typelist, cur, next, complain,
3134
                     tmplist, chkact, no_match);
3135
3136
        /* if we encountered a syntax error, return failure */
3137
        if (cnt < 0)
3138
        {
3139
            VOC_RETVAL(ctx, save_sp, cnt);
3140
        }
3141
3142
        /* if we got any objects, store them in our output list */
3143
        if (cnt > 0)
3144
        {
3145
            for (i = 0 ; i < cnt ; ++i)
3146
            {
3147
                OSCPYSTRUCT(nounlist[outcnt], tmplist[i]);
3148
                if (++outcnt > VOCMAXAMBIG)
3149
                {
3150
                    vocerr(ctx, VOCERR(11),
3151
                           "You're referring to too many objects.");
3152
                    VOC_RETVAL(ctx, save_sp, -1);
3153
                }
3154
            }
3155
        }
3156
3157
        /* if we didn't find any objects, stop looking */
3158
        if (cnt == 0)
3159
        {
3160
            if (again)
3161
                *next = lastcur;
3162
            break;
3163
        }
3164
3165
        /* 
3166
         *   if the caller only wanted a single object (or is getting an
3167
         *   actor, in which case they implicitly want only a single
3168
         *   object), stop looking for additional noun phrases 
3169
         */
3170
        if (!multi || chkact)
3171
            break;
3172
3173
        /* skip past the previous noun phrase */
3174
        cur = *next;
3175
3176
        /* 
3177
         *   if we're looking at a noun phrase separator ("and" or a
3178
         *   comma), get the next noun phrase; otherwise, we're done 
3179
         */
3180
        if (cur != -1 && cmd[cur] != 0 && vocspec(cmd[cur], VOCW_AND))
3181
        {
3182
            lastcur = cur;
3183
            while (cmd[cur] && vocspec(cmd[cur], VOCW_AND)) ++cur;
3184
            again = TRUE;
3185
            if (complain) complain = 2;
3186
        }
3187
        else
3188
        {
3189
            /* end of line, or not at a separator - we're done */
3190
            break;
3191
        }
3192
    }
3193
3194
    /* terminate the list and return the number of objects we found */
3195
    nounlist[outcnt].vocolobj = MCMONINV;
3196
    nounlist[outcnt].vocolflg = 0;
3197
    VOC_RETVAL(ctx, save_sp, outcnt);
3198
}
3199
3200
3201
/* ------------------------------------------------------------------------ */
3202
/*
3203
 *   TADS program code interface - tokenize a string.  Returns a list of
3204
 *   strings, with each string giving a token in the command. 
3205
 */
3206
void voc_parse_tok(voccxdef *ctx)
3207
{
3208
    uchar  *save_sp;
3209
    runcxdef *rcx = ctx->voccxrun;
3210
    char **cmd;
3211
    char *inbuf;
3212
    char *outbuf;
3213
    uchar *p;
3214
    uint len;
3215
    int cnt;
3216
3217
    /* enter our stack frame */
3218
    voc_enter(ctx, &save_sp);
3219
3220
    /* get the string argument */
3221
    p = runpopstr(rcx);
3222
3223
    /* get and skip the length prefix */
3224
    len = osrp2(p) - 2;
3225
    p += 2;
3226
3227
    /* 
3228
     *   Allocate space for the original string, and space for the token
3229
     *   pointers and the tokenized string buffer.  We could potentially
3230
     *   have one token per character in the original string, and we could
3231
     *   potentially need one extra null terminator for each character in
3232
     *   the original string; allocate accordingly.  
3233
     */
3234
    VOC_STK_ARRAY(ctx, char,   inbuf,  len + 1);
3235
    VOC_STK_ARRAY(ctx, char,   outbuf, len*2);
3236
    VOC_STK_ARRAY(ctx, char *, cmd,    len*2);
3237
3238
    /* copy the string into our buffer, and null-terminate it */
3239
    memcpy(inbuf, p, len);
3240
    inbuf[len] = '\0';
3241
3242
    /* tokenize the string */
3243
    cnt = voctok(ctx, inbuf, outbuf, cmd, TRUE, FALSE, FALSE);
3244
3245
    /* check the result */
3246
    if (cnt < 0)
3247
    {
3248
        /* negative count - it's an error, so return nil */
3249
        runpnil(rcx);
3250
    }
3251
    else
3252
    {
3253
        /* push the return list */
3254
        voc_push_toklist(ctx, cmd, cnt);
3255
    }
3256
3257
    /* leave our stack frame */
3258
    voc_leave(ctx, save_sp);
3259
}
3260
3261
/* ------------------------------------------------------------------------ */
3262
/*
3263
 *   TADS program code interface - get the list of types for a list words.
3264
 *   The words are simply strings of the type returned from the tokenizer.
3265
 *   The return value is a list of types, with each entry in the return
3266
 *   list giving the types of the corresponding entry in the word list. 
3267
 */
3268
void voc_parse_types(voccxdef *ctx)
3269
{
3270
    runcxdef *rcx = ctx->voccxrun;
3271
    uchar *wrdp;
3272
    uint wrdsiz;
3273
    uchar *typp;
3274
    uchar *lstp;
3275
    uint lstsiz;
3276
    int wrdcnt;
3277
3278
    /* get the list of words from the stack */
3279
    wrdp = runpoplst(rcx);
3280
3281
    /* read and skip the size prefix */
3282
    wrdsiz = osrp2(wrdp) - 2;
3283
    wrdp += 2;
3284
3285
    /* scan the list and count the number of string entries */
3286
    for (wrdcnt = 0, lstp = wrdp, lstsiz = wrdsiz ; lstsiz != 0 ;
3287
         lstadv(&lstp, &lstsiz))
3288
    {
3289
        /* if this is a string, count it */
3290
        if (*lstp == DAT_SSTRING)
3291
            ++wrdcnt;
3292
    }
3293
3294
    /* allocate the return list - one number entry per word */
3295
    typp = voc_push_list(ctx, wrdcnt, 4);
3296
3297
    /* look up each word and set the corresponding element in the type list */
3298
    for (lstp = wrdp, lstsiz = wrdsiz ; lstsiz != 0 ; lstadv(&lstp, &lstsiz))
3299
    {
3300
        /* if this is a string, look it up in the dictionary */
3301
        if (*lstp == DAT_SSTRING)
3302
        {
3303
            char buf[256];
3304
            int curtyp;
3305
            uint len;
3306
3307
            /* make sure it fits in our buffer */
3308
            len = osrp2(lstp+1) - 2;
3309
            if (len < sizeof(buf))
3310
            {
3311
                /* extract the word into our buffer */
3312
                memcpy(buf, lstp+3, len);
3313
3314
                /* null-terminate it */
3315
                buf[len] = '\0';
3316
3317
                /* get the type */
3318
                curtyp = voc_lookup_type(ctx, buf, len, TRUE);
3319
3320
                /* if they didn't find a type at all, set it to UNKNOWN */
3321
                if (curtyp == 0)
3322
                    curtyp = VOCT_UNKNOWN;
3323
            }
3324
            else
3325
            {
3326
                /* the string is too big - just mark it as unknown */
3327
                curtyp = VOCT_UNKNOWN;
3328
            }
3329
3330
            /* add this type to the return list */
3331
            *typp++ = DAT_NUMBER;
3332
            oswp4(typp, curtyp);
3333
            typp += 4;
3334
        }
3335
    }
3336
}
3337
3338
3339
/* ------------------------------------------------------------------------ */
3340
/*
3341
 *   Parse a noun phrase from TADS program code.  Takes a list of words
3342
 *   and a list of types from the stack, uses the standard noun phrase
3343
 *   parser, and returns a list of matching objects.  The object list is
3344
 *   not disambiguated, but merely reflects all matching objects.  The
3345
 *   entire standard parsing algorithm applies, including parseNounPhrase
3346
 *   invocation if appropriate.  
3347
 */
3348
void voc_parse_np(voccxdef *ctx)
3349
{
3350
    runcxdef *rcx = ctx->voccxrun;
3351
    vocoldef *objlist;
3352
    uchar *save_sp;
3353
    uchar *wordp;
3354
    uint wordsiz;
3355
    uchar *typp;
3356
    uint typsiz;
3357
    int cnt;
3358
    char **wordarr;
3359
    int wordcnt;
3360
    char *wordchararr;
3361
    uint wordcharsiz;
3362
    int *typarr;
3363
    int complain;
3364
    int chkact;
3365
    int multi;
3366
    int no_match;
3367
    int next;
3368
    int cur;
3369
    uchar *lstp;
3370
    uint lstsiz;
3371
    char *p;
3372
    int i;
3373
    int old_unknown, old_lastunk;
3374
3375
    /* allocate stack arrays */
3376
    voc_enter(ctx, &save_sp);
3377
    VOC_MAX_ARRAY(ctx, vocoldef, objlist);
3378
3379
    /* 
3380
     *   Save the original context unknown values, since we don't want to
3381
     *   affect the context information in this game-initiated call, then
3382
     *   clear the unknown word count for the duration of the call.  
3383
     */
3384
    old_unknown = ctx->voccxunknown;
3385
    old_lastunk = ctx->voccxlastunk;
3386
    ctx->voccxunknown = ctx->voccxlastunk = 0;
3387
3388
    /* get the list of words, and read its length prefix */
3389
    wordp = runpoplst(rcx);
3390
    wordsiz = osrp2(wordp) - 2;
3391
    wordp += 2;
3392
3393
    /* get the list of types, and read its length prefix */
3394
    typp = runpoplst(rcx);
3395
    typsiz = osrp2(typp) - 2;
3396
    typp += 2;
3397
3398
    /* get the starting index (adjusting for zero-based indexing) */
3399
    cur = runpopnum(rcx) - 1;
3400
    next = cur;
3401
3402
    /* get the flag arguments */
3403
    complain = runpoplog(rcx);
3404
    multi = runpoplog(rcx);
3405
    chkact = runpoplog(rcx);
3406
3407
    /* count the words in the word list */
3408
    for (wordcnt = 0, lstp = wordp, wordcharsiz = 0, lstsiz = wordsiz ;
3409
         lstsiz != 0 ; lstadv(&lstp, &lstsiz))
3410
    {
3411
        /* count the word */
3412
        ++wordcnt;
3413
3414
        /* 
3415
         *   count the space needed for the word - count the bytes of the
3416
         *   string plus a null terminator 
3417
         */
3418
        if (*lstp == DAT_SSTRING)
3419
            wordcharsiz += osrp2(lstp+1) + 1;
3420
    }
3421
3422
    /* allocate space for the word arrays */
3423
    VOC_STK_ARRAY(ctx, char,   wordchararr, wordcharsiz);
3424
    VOC_STK_ARRAY(ctx, char *, wordarr,     wordcnt+1);
3425
    VOC_STK_ARRAY(ctx, int,    typarr,      wordcnt+1);
3426
3427
    /* build the word array */
3428
    for (i = 0, p = wordchararr, lstp = wordp, lstsiz = wordsiz ;
3429
         lstsiz != 0 ; lstadv(&lstp, &lstsiz))
3430
    {
3431
        /* add the word to the word array */
3432
        if (*lstp == DAT_SSTRING)
3433
        {
3434
            uint len;
3435
            
3436
            /* add this entry to the word array */
3437
            wordarr[i] = p;
3438
3439
            /* copy the word to the character array and null-terminate it */
3440
            len = osrp2(lstp + 1) - 2;
3441
            memcpy(p, lstp + 3, len);
3442
            p[len] = '\0';
3443
3444
            /* move the write pointer past this entry */
3445
            p += len + 1;
3446
3447
            /* bump the index */
3448
            ++i;
3449
        }
3450
    }
3451
3452
    /* store an empty last entry */
3453
    wordarr[i] = 0;
3454
3455
    /* build the type array */
3456
    for (i = 0, lstp = typp, lstsiz = typsiz ;
3457
         lstsiz != 0 && i < wordcnt ; lstadv(&lstp, &lstsiz))
3458
    {
3459
        /* add this entry to the type array */
3460
        if (*lstp == DAT_NUMBER)
3461
        {
3462
            /* set the entry */
3463
            typarr[i] = (int)osrp4(lstp + 1);
3464
3465
            /* bump the index */
3466
            ++i;
3467
        }
3468
    }
3469
3470
    /* parse the noun phrase */
3471
    cnt = vocgobj(ctx, wordarr, typarr, cur, &next, complain, objlist,
3472
                  multi, chkact, &no_match);
3473
3474
    /* restore context unknown values */
3475
    ctx->voccxunknown = old_unknown;
3476
    ctx->voccxlastunk = old_lastunk;
3477
3478
    /* check the return value */
3479
    if (cnt < 0)
3480
    {
3481
        /* syntax error; return nil to indicate an error */
3482
        runpnil(rcx);
3483
    }
3484
    else if (cnt == 0)
3485
    {
3486
        /* 
3487
         *   No objects found.  Return a list consisting only of the next
3488
         *   index.  If the next index is equal to the starting index,
3489
         *   this will tell the caller that no noun phrase is
3490
         *   syntactically present; otherwise, it will tell the caller
3491
         *   that a noun phrase is present but there are no matching
3492
         *   objects.
3493
         *   
3494
         *   Note that we must increment the returned element index to
3495
         *   conform with the 1-based index values that the game function
3496
         *   uses.  
3497
         */
3498
        ++next;
3499
        voc_push_numlist(ctx, (uint *)&next, 1);
3500
    }
3501
    else
3502
    {
3503
        /*
3504
         *   We found one or more objects.  Return a list whose first
3505
         *   element is the index of the next word to be parsed, and whose
3506
         *   remaining elements are sublists.  Each sublist contains a
3507
         *   match for one noun phrase; for each AND adding another noun
3508
         *   phrase, there's another sublist.  Each sublist contains the
3509
         *   index of the first word of its noun phrase, the index of the
3510
         *   last word of its noun phrase, and then the objects.  For each
3511
         *   object, there is a pair of entries: the object itself, and
3512
         *   the flags for the object.
3513
         *   
3514
         *   First, figure out how much space we need by scanning the
3515
         *   return list.  
3516
         */
3517
        for (lstsiz = 0, i = 0 ; i < cnt ; )
3518
        {
3519
            int j;
3520
3521
            /* 
3522
             *   count the entries in this sublist by looking for the next
3523
             *   entry whose starting word is different 
3524
             */
3525
            for (j = i ;
3526
                 j < cnt && objlist[j].vocolfst == objlist[i].vocolfst ;
3527
                 ++j)
3528
            {
3529
                /* 
3530
                 *   for this entry, we need space for the object (1 + 2
3531
                 *   for an object, or just 1 for nil) and flags (1 + 4) 
3532
                 */
3533
                if (objlist[j].vocolobj == MCMONINV)
3534
                    lstsiz += 1;
3535
                else
3536
                    lstsiz += 3;
3537
                lstsiz += 5;
3538
            }
3539
3540
            /* 
3541
             *   For this sublist, we need space for the first index (type
3542
             *   prefix + number = 1 + 4 = 5) and the last index (5).
3543
             *   We've already counted space for the objects in the list.
3544
             *   Finally, we need space for the list type and length
3545
             *   prefixes (1 + 2) for the sublist itself.  
3546
             */
3547
            lstsiz += (5 + 5) + 3;
3548
3549
            /* skip to the next element */
3550
            i = j;
3551
        }
3552
3553
        /* 
3554
         *   finally, we need space for the first element of the list,
3555
         *   which is the index of the next word to be parsed (1+4)
3556
         */
3557
        lstsiz += 5;
3558
3559
        /* allocate space for the list */
3560
        lstp = voc_push_list_siz(ctx, lstsiz);
3561
3562
        /* 
3563
         *   store the first element - the index of the next word to parse
3564
         *   (adjusting to 1-based indexing) 
3565
         */
3566
        *lstp++ = DAT_NUMBER;
3567
        oswp4(lstp, next + 1);
3568
        lstp += 4;
3569
3570
        /* build the list */
3571
        for (i = 0 ; i < cnt ; )
3572
        {
3573
            uchar *sublstp;
3574
            int j;
3575
            int firstidx;
3576
            int lastidx;
3577
3578
            /* store the list type prefix */
3579
            *lstp++ = DAT_LIST;
3580
3581
            /* 
3582
             *   remember where the length prefix is, then skip it - we'll
3583
             *   come back and fill it in when we're done with the sublist 
3584
             */
3585
            sublstp = lstp;
3586
            lstp += 2;
3587
3588
            /* search the array to find the indices of the boundary words */
3589
            for (j = 0 ; j < wordcnt ; ++j)
3590
            {
3591
                /* if this is the first word, remember the index */
3592
                if (wordarr[j] == objlist[i].vocolfst)
3593
                    firstidx = j;
3594
3595
                /* if this is the last word, remember the index */
3596
                if (wordarr[j] == objlist[i].vocollst)
3597
                {
3598
                    /* remember the index */
3599
                    lastidx = j;
3600
3601
                    /* 
3602
                     *   we can stop looking now, since the words are
3603
                     *   always in order (so the first index will never be
3604
                     *   after the last index) 
3605
                     */
3606
                    break;
3607
                }
3608
            }
3609
3610
            /* add the first and last index, adjusting to 1-based indexing */
3611
            *lstp++ = DAT_NUMBER;
3612
            oswp4(lstp, firstidx + 1);
3613
            lstp += 4;
3614
            *lstp++ = DAT_NUMBER;
3615
            oswp4(lstp, lastidx + 1);
3616
            lstp += 4;
3617
3618
            /* add the objects */
3619
            for (j = i ; 
3620
                 j < cnt && objlist[j].vocolfst == objlist[i].vocolfst ;
3621
                 ++j)
3622
            {
3623
                /* add this object */
3624
                if (objlist[j].vocolobj == MCMONINV)
3625
                {
3626
                    /* just store a nil */
3627
                    *lstp++ = DAT_NIL;
3628
                }
3629
                else
3630
                {
3631
                    /* store the object */
3632
                    *lstp++ = DAT_OBJECT;
3633
                    oswp2(lstp, objlist[j].vocolobj);
3634
                    lstp += 2;
3635
                }
3636
3637
                /* add the flags */
3638
                *lstp++ = DAT_NUMBER;
3639
                oswp4(lstp, objlist[i].vocolflg);
3640
                lstp += 4;
3641
            }
3642
3643
            /* fix up the sublist's length prefix */
3644
            oswp2(sublstp, lstp - sublstp);
3645
3646
            /* move on to the next sublist */
3647
            i = j;
3648
        }
3649
    }
3650
3651
    /* exit the stack frame */
3652
    voc_leave(ctx, save_sp);
3653
}
3654
3655
3656
/* ------------------------------------------------------------------------ */
3657
/*
3658
 *   TADS program code interface - given a list of words and a list of
3659
 *   their types, produce a list of objects that match all of the words.  
3660
 */
3661
void voc_parse_dict_lookup(voccxdef *ctx)
3662
{
3663
    uchar *save_sp;
3664
    runcxdef *rcx = ctx->voccxrun;
3665
    uchar *wrdp;
3666
    uint wrdsiz;
3667
    uchar *typp;
3668
    uint typsiz;
3669
    objnum *list1;
3670
    objnum *list2;
3671
    int cnt1;
3672
    int cnt2;
3673
3674
    /* enter our stack frame and allocate stack arrays */
3675
    voc_enter(ctx, &save_sp);
3676
    VOC_MAX_ARRAY(ctx, objnum, list1);
3677
    VOC_MAX_ARRAY(ctx, objnum, list2);
3678
    
3679
    /* get the word list, and read and skip its size prefix */
3680
    wrdp = runpoplst(rcx);
3681
    wrdsiz = osrp2(wrdp) - 2;
3682
    wrdp += 2;
3683
3684
    /* get the type list, and read and skip its size prefix */
3685
    typp = runpoplst(rcx);
3686
    typsiz = osrp2(typp) - 2;
3687
    typp += 2;
3688
3689
    /* nothing in the main list yet */
3690
    cnt1 = 0;
3691
3692
    /* go through the word list */
3693
    while (wrdsiz > 0)
3694
    {
3695
        int curtyp;
3696
        int type_prop;
3697
        char *curword;
3698
        uint curwordlen;
3699
        char *curword2;
3700
        uint curwordlen2;
3701
        vocwdef *v;
3702
        char *p;
3703
        uint len;
3704
        vocseadef  search_ctx;
3705
        
3706
        /* if this entry is a string, consider it */
3707
        if (*wrdp == DAT_SSTRING)
3708
        {
3709
            /* get the current word's text string */
3710
            curword = (char *)(wrdp + 3);
3711
            curwordlen = osrp2(wrdp+1) - 2;
3712
3713
            /* check for an embedded space */
3714
            for (p = curword, len = curwordlen ; len != 0 && !t_isspace(*p) ;
3715
                 ++p, --len) ;
3716
            if (len != 0)
3717
            {
3718
                /* get the second word */
3719
                curword2 = p + 1;
3720
                curwordlen2 = len - 1;
3721
3722
                /* truncate the first word accordingly */
3723
                curwordlen -= len;
3724
            }
3725
            else
3726
            {
3727
                /* no embedded space -> no second word */
3728
                curword2 = 0;
3729
                curwordlen2 = 0;
3730
            }
3731
3732
            /* presume we won't find a valid type property */
3733
            type_prop = PRP_INVALID;
3734
3735
            /* 
3736
             *   get this type entry, if there's another entry in the
3737
             *   list, and it's of the appropriate type 
3738
             */
3739
            if (typsiz > 0 && *typp == DAT_NUMBER)
3740
            {
3741
                /*
3742
                 *   Figure out what type property we'll be using.  We'll
3743
                 *   consider only one meaning for each word, and we'll
3744
                 *   arbitrarily pick one if the type code has more than
3745
                 *   one type, because we expect the caller to provide
3746
                 *   exactly one type per word.  
3747
                 */
3748
                int i;
3749
                struct typemap_t
3750
                {
3751
                    int flag;
3752
                    int prop;
3753
                };
3754
                static struct typemap_t typemap[] =
3755
                {
3756
                    { VOCT_ARTICLE, PRP_ARTICLE },
3757
                    { VOCT_ADJ,     PRP_ADJ },
3758
                    { VOCT_NOUN,    PRP_NOUN },
3759
                    { VOCT_PREP,    PRP_PREP },
3760
                    { VOCT_VERB,    PRP_VERB },
3761
                    { VOCT_PLURAL,  PRP_PLURAL }
3762
                };
3763
                struct typemap_t *mapp;
3764
3765
                /* get the type */
3766
                curtyp = (int)osrp4(typp+1);
3767
3768
                /* search for a type */
3769
                for (mapp = typemap, i = sizeof(typemap)/sizeof(typemap[0]) ;
3770
                     i != 0 ; ++mapp, --i)
3771
                {
3772
                    /* if this flag is set, use this type property */
3773
                    if ((curtyp & mapp->flag) != 0)
3774
                    {
3775
                        /* use this one */
3776
                        type_prop = mapp->prop;
3777
                        break;
3778
                    }
3779
                }
3780
            }
3781
3782
            /* nothing in the new list yet */
3783
            cnt2 = 0;
3784
3785
            /* scan for matching words */
3786
            for (v = vocffw(ctx, curword, curwordlen, curword2, curwordlen2,
3787
                            type_prop, &search_ctx) ;
3788
                 v != 0 ;
3789
                 v = vocfnw(ctx, &search_ctx))
3790
            {
3791
                int i;
3792
                
3793
                /* make sure we have room in our list */
3794
                if (cnt2 >= VOCMAXAMBIG - 1)
3795
                    break;
3796
3797
                /* make sure that this entry isn't already in our list */
3798
                for (i = 0 ; i < cnt2 ; ++i)
3799
                {
3800
                    /* if this entry matches, stop looking */
3801
                    if (list2[i] == v->vocwobj)
3802
                        break;
3803
                }
3804
3805
                /* if it's not already in the list, add it now */
3806
                if (i == cnt2)
3807
                {
3808
                    /* add it to our list */
3809
                    list2[cnt2++] = v->vocwobj;
3810
                }
3811
            }
3812
3813
            /* terminate the list */
3814
            list2[cnt2] = MCMONINV;
3815
3816
            /* 
3817
             *   if there's nothing in the first list, simply copy this
3818
             *   into the first list; otherwise, intersect the two lists 
3819
             */
3820
            if (cnt1 == 0)
3821
            {
3822
                /* this is the first list -> copy it into the main list */
3823
                memcpy(list1, list2, (cnt2+1)*sizeof(list2[0]));
3824
                cnt1 = cnt2;
3825
            }
3826
            else
3827
            {
3828
                /* intersect the two lists */
3829
                cnt1 = vocisect(list1, list2);
3830
            }
3831
3832
            /* 
3833
             *   if there's nothing in the result list now, there's no
3834
             *   need to look any further, because further intersection
3835
             *   will yield nothing 
3836
             */
3837
            if (cnt1 == 0)
3838
                break;
3839
        }
3840
        
3841
        /* advance the word list */
3842
        lstadv(&wrdp, &wrdsiz);
3843
3844
        /* if there's anything left in the type list, advance it as well */
3845
        if (typsiz > 0)
3846
            lstadv(&typp, &typsiz);
3847
    }
3848
3849
    /* push the result list */
3850
    voc_push_objlist(ctx, list1, cnt1);
3851
3852
    /* exit our stack frame */
3853
    voc_leave(ctx, save_sp);
3854
}
3855
3856
/* ------------------------------------------------------------------------ */
3857
/*
3858
 *   TADS program code interface - disambiguate a noun list.  We take the
3859
 *   kind of complex object list returned by voc_parse_np(), and produce a
3860
 *   fully-resolved list of objects.  
3861
 */
3862
void voc_parse_disambig(voccxdef *ctx)
3863
{
3864
    voccxdef ctx_copy;
3865
    uchar *save_sp;
3866
    runcxdef *rcx = ctx->voccxrun;
3867
    vocoldef *inlist;
3868
    vocoldef *outlist;
3869
    int objcnt;
3870
    char **cmd;
3871
    char *cmdbuf;
3872
    char *oopsbuf;
3873
    objnum actor;
3874
    objnum verb;
3875
    objnum prep;
3876
    objnum otherobj;
3877
    prpnum defprop;
3878
    prpnum accprop;
3879
    prpnum verprop;
3880
    uchar *tokp;
3881
    uint toksiz;
3882
    uchar *objp;
3883
    uint objsiz;
3884
    uchar *lstp;
3885
    uint lstsiz;
3886
    int tokcnt;
3887
    char *p;
3888
    int i;
3889
    int silent;
3890
    int err;
3891
    int unknown_count;
3892
3893
    /* allocate our stack arrays */
3894
    voc_enter(ctx, &save_sp);
3895
    VOC_MAX_ARRAY(ctx, vocoldef, outlist);
3896
    VOC_STK_ARRAY(ctx, char,     cmdbuf, VOCBUFSIZ);
3897
    VOC_STK_ARRAY(ctx, char,     oopsbuf, VOCBUFSIZ);
3898
3899
    /* get the actor, verb, prep, and otherobj arguments */
3900
    actor = runpopobj(rcx);
3901
    verb = runpopobj(rcx);
3902
    prep = runpopobjnil(rcx);
3903
    otherobj = runpopobjnil(rcx);
3904
3905
    /* 
3906
     *   get the usage parameter, and use it to select the appropriate
3907
     *   defprop and accprop 
3908
     */
3909
    switch(runpopnum(rcx))
3910
    {
3911
    case VOC_PRO_RESOLVE_DOBJ:
3912
    default:
3913
        defprop = PRP_DODEFAULT;
3914
        accprop = PRP_VALIDDO;
3915
        break;
3916
3917
    case VOC_PRO_RESOLVE_IOBJ:
3918
        defprop = PRP_IODEFAULT;
3919
        accprop = PRP_VALIDIO;
3920
        break;
3921
3922
    case VOC_PRO_RESOLVE_ACTOR:
3923
        defprop = PRP_DODEFAULT;
3924
        accprop = PRP_VALIDACTOR;
3925
        break;
3926
    }
3927
    
3928
    /* get the verprop argument */
3929
    verprop = runpopprp(rcx);
3930
3931
    /* pop the token list, and read and skip the length prefix */
3932
    tokp = runpoplst(rcx);
3933
    toksiz = osrp2(tokp) - 2;
3934
    tokp += 2;
3935
3936
    /* pop the object list, and read and skip the length prefix */
3937
    objp = runpoplst(rcx);
3938
    objsiz = osrp2(objp) - 2;
3939
    objp += 2;
3940
3941
    /* pop the "silent" flag */
3942
    silent = runpoplog(rcx);
3943
3944
    /* count the tokens */
3945
    for (lstp = tokp, lstsiz = toksiz, tokcnt = 0 ;
3946
         lstsiz != 0 ; lstadv(&lstp, &lstsiz))
3947
    {
3948
        /* if this is a string, count it */
3949
        if (*lstp == DAT_SSTRING)
3950
            ++tokcnt;
3951
    }
3952
3953
    /* allocate stack space for the command pointers and buffer */
3954
    VOC_STK_ARRAY(ctx, char *, cmd, tokcnt + 1);
3955
3956
    /* extract the tokens into our pointer buffer */
3957
    for (lstp = tokp, lstsiz = toksiz, i = 0, p = cmdbuf ;
3958
         lstsiz != 0 ; lstadv(&lstp, &lstsiz))
3959
    {
3960
        /* if this is a string, count and size it */
3961
        if (*lstp == DAT_SSTRING)
3962
        {
3963
            uint len;
3964
            
3965
            /* store a pointer to the word in the command buffer */
3966
            cmd[i++] = p;
3967
3968
            /* copy the token into the command buffer and null-terminate it */
3969
            len = osrp2(lstp + 1) - 2;
3970
            memcpy(p, lstp + 3, len);
3971
            p[len] = '\0';
3972
3973
            /* move the buffer pointer past this word */
3974
            p += len + 1;
3975
        }
3976
    }
3977
3978
    /* store a null at the end of the command pointer list */
3979
    cmd[i] = 0;
3980
3981
    /*
3982
     *   The object list is provided in the same format as the list
3983
     *   returned by voc_parse_np(), but the leading index number is
3984
     *   optional.  We don't need the leading index for anything, so if
3985
     *   it's there, simply skip it so that we can start with the first
3986
     *   sublist.  
3987
     */
3988
    if (objsiz > 0 && *objp == DAT_NUMBER)
3989
        lstadv(&objp, &objsiz);
3990
3991
    /*
3992
     *   Count the objects in the object list, so that we can figure out
3993
     *   how much we need to allocate for the input object list.  
3994
     */
3995
    for (lstp = objp, lstsiz = objsiz, objcnt = 0 ; lstsiz != 0 ;
3996
         lstadv(&lstp, &lstsiz))
3997
    {
3998
        /* if this is a sublist, parse it */
3999
        if (*lstp == DAT_LIST)
4000
        {
4001
            uchar *subp;
4002
            uint subsiz;
4003
4004
            /* get the sublist pointer, and read and skip the size prefix */
4005
            subp = lstp + 1;
4006
            subsiz = osrp2(subp) - 2;
4007
            subp += 2;
4008
4009
            /* scan the sublist */
4010
            while (subsiz > 0)
4011
            {
4012
                /* if this is an object, count it */
4013
                if (*subp == DAT_OBJECT || *subp == DAT_NIL)
4014
                    ++objcnt;
4015
4016
                /* skip this element */
4017
                lstadv(&subp, &subsiz);
4018
            }
4019
        }
4020
    }
4021
4022
    /* allocate space for the input list */
4023
    VOC_STK_ARRAY(ctx, vocoldef, inlist, objcnt + 1);
4024
4025
    /* we don't have any unknown words yet */
4026
    unknown_count = 0;
4027
4028
    /* parse the list, filling in the input array */
4029
    for (lstp = objp, lstsiz = objsiz, i = 0 ; lstsiz != 0 ;
4030
         lstadv(&lstp, &lstsiz))
4031
    {
4032
        /* if this is a sublist, parse it */
4033
        if (*lstp == DAT_LIST)
4034
        {
4035
            uchar *subp;
4036
            uint subsiz;
4037
            int firstwrd, lastwrd;
4038
4039
            /* get the sublist pointer, and read and skip the size prefix */
4040
            subp = lstp + 1;
4041
            subsiz = osrp2(subp) - 2;
4042
            subp += 2;
4043
4044
            /* in case we don't find token indices, clear them */
4045
            firstwrd = 0;
4046
            lastwrd = 0;
4047
4048
            /* 
4049
             *   the first two elements of the list are the token indices
4050
             *   of the first and last words of this object's noun phrase 
4051
             */
4052
            if (subsiz > 0 && *subp == DAT_NUMBER)
4053
            {
4054
                /* read the first index, adjusting to zero-based indexing */
4055
                firstwrd = (int)osrp4(subp+1) - 1;
4056
4057
                /* make sure it's in range */
4058
                if (firstwrd < 0)
4059
                    firstwrd = 0;
4060
                else if (firstwrd > tokcnt)
4061
                    firstwrd = tokcnt;
4062
4063
                /* skip it */
4064
                lstadv(&subp, &subsiz);
4065
            }
4066
            if (subsiz > 0 && *subp == DAT_NUMBER)
4067
            {
4068
                /* read the last index, adjusting to zero-based indexing */
4069
                lastwrd = (int)osrp4(subp+1) - 1;
4070
4071
                /* make sure it's in range */
4072
                if (lastwrd < firstwrd)
4073
                    lastwrd = firstwrd;
4074
                else if (lastwrd > tokcnt)
4075
                    lastwrd = tokcnt;
4076
4077
                /* skip it */
4078
                lstadv(&subp, &subsiz);
4079
            }
4080
4081
            /* scan the sublist */
4082
            while (subsiz > 0)
4083
            {
4084
                /* if this is an object, store it */
4085
                if (*subp == DAT_OBJECT || *subp == DAT_NIL)
4086
                {
4087
                    /* store the object */
4088
                    if (*subp == DAT_OBJECT)
4089
                        inlist[i].vocolobj = osrp2(subp+1);
4090
                    else
4091
                        inlist[i].vocolobj = MCMONINV;
4092
4093
                    /* set the first and last word pointers */
4094
                    inlist[i].vocolfst = cmd[firstwrd];
4095
                    inlist[i].vocollst = cmd[lastwrd];
4096
4097
                    /* skip the object */
4098
                    lstadv(&subp, &subsiz);
4099
4100
                    /* check for flags */
4101
                    if (subsiz > 0 && *subp == DAT_NUMBER)
4102
                    {
4103
                        /* get the flags value */
4104
                        inlist[i].vocolflg = (int)osrp4(subp+1);
4105
4106
                        /* skip the number in the list */
4107
                        lstadv(&subp, &subsiz);
4108
                    }
4109
                    else
4110
                    {
4111
                        /* clear the flags */
4112
                        inlist[i].vocolflg = 0;
4113
                    }
4114
4115
                    /* if an unknown word was involved, note it */
4116
                    if ((inlist[i].vocolflg & VOCS_UNKNOWN) != 0)
4117
                        ++unknown_count;
4118
4119
                    /* consume the element */
4120
                    ++i;
4121
                }
4122
                else
4123
                {
4124
                    /* skip this element */
4125
                    lstadv(&subp, &subsiz);
4126
                }
4127
            }
4128
        }
4129
    }
4130
4131
    /* terminate the list */
4132
    inlist[i].vocolobj = MCMONINV;
4133
    inlist[i].vocolflg = 0;
4134
4135
    /* 
4136
     *   make a copy of our context, so the disambiguation can't make any
4137
     *   global changes 
4138
     */
4139
    memcpy(&ctx_copy, ctx, sizeof(ctx_copy));
4140
4141
    /*
4142
     *   Count the unknown words and set the count in the context.  This
4143
     *   will allow us to determine after we call the resolver whether the
4144
     *   resolution process cleared up the unknown words (via
4145
     *   parseUnknownDobj/Iobj). 
4146
     */
4147
    ctx_copy.voccxunknown = ctx_copy.voccxlastunk = unknown_count;
4148
4149
    /* disambiguate the noun list */
4150
    err = vocdisambig(&ctx_copy, outlist, inlist,
4151
                      defprop, accprop, verprop, cmd,
4152
                      otherobj, actor, verb, prep, cmdbuf, silent);
4153
4154
    /*
4155
     *   If the error was VOCERR(2) - unknown word - check the input list
4156
     *   to see if it contained any unknown words.  If it does, and we're
4157
     *   not in "silent" mode, flag the error and then give the user a
4158
     *   chance to use "oops" to correct the problem.  If we're in silent
4159
     *   mode, don't display an error and don't allow interactive
4160
     *   correction via "oops."
4161
     *   
4162
     *   It is possible that the unknown word is not in the input list,
4163
     *   but in the user's response to an interactive disambiguation
4164
     *   query.  This is why we must check to see if the unknown word is
4165
     *   in the original input list or not.
4166
     */
4167
    if (err == VOCERR(2) && ctx_copy.voccxunknown != 0 && !silent)
4168
    {
4169
        char *unk;
4170
        int unk_idx;
4171
        char *rpl_text;
4172
4173
        /* 
4174
         *   forget we have unknown words, since we're going to handle
4175
         *   them now
4176
         */
4177
        ctx_copy.voccxunknown = 0;
4178
4179
        /* 
4180
         *   find the unknown word - look up each word until we find one
4181
         *   that's not in the dictionary 
4182
         */
4183
        for (i = 0, unk = 0 ; cmd[i] != 0 ; ++i)
4184
        {
4185
            int t;
4186
            
4187
            /* 
4188
             *   get this word's type - if the word has no type, it's an
4189
             *   unknown word 
4190
             */
4191
            t = voc_lookup_type(ctx, cmd[i], strlen(cmd[i]), TRUE);
4192
            if (t == 0)
4193
            {
4194
                /* this is it - note it and stop searching */
4195
                unk_idx = i;
4196
                unk = cmd[i];
4197
                break;
4198
            }
4199
        }
4200
4201
        /* 
4202
         *   if we didn't find any unknown words, assume the first word
4203
         *   was unknown 
4204
         */
4205
        if (unk == 0)
4206
        {
4207
            unk_idx = 0;
4208
            unk = cmd[0];
4209
        }
4210
4211
        /* display an error, and read a new command */
4212
        rpl_text = voc_read_oops(&ctx_copy, oopsbuf, VOCBUFSIZ, unk);
4213
4214
        /* 
4215
         *   if they didn't respond with "oops," treat the response as a
4216
         *   brand new command to replace the current command 
4217
         */
4218
        if (rpl_text == 0)
4219
        {
4220
            /* 
4221
             *   it's a replacement command - set the redo flag to
4222
             *   indicate that we should process the replacement command 
4223
             */
4224
            ctx_copy.voccxredo = TRUE;
4225
4226
            /* copy the response into the command buffer */
4227
            strcpy(cmdbuf, oopsbuf);
4228
        }
4229
        else
4230
        {
4231
            /* indicate the correction via the result code */
4232
            err = VOCERR(45);
4233
4234
            /* 
4235
             *   Build the new command string.  The new command string
4236
             *   consists of all of the tokens up to the unknown token,
4237
             *   then the replacement text, then all of the remaining
4238
             *   tokens. 
4239
             */
4240
            for (p = cmdbuf, i = 0 ; cmd[i] != 0 ; ++i)
4241
            {
4242
                size_t needed;
4243
                
4244
                /* figure the size needs for this token */
4245
                if (i == unk_idx)
4246
                {
4247
                    /* we need to insert the replacement text */
4248
                    needed = strlen(rpl_text);
4249
                }
4250
                else
4251
                {
4252
                    /* we need to insert this token string */
4253
                    needed = strlen(cmd[i]);
4254
                }
4255
                
4256
                /* 
4257
                 *   if more tokens follow, we need a space after the
4258
                 *   replacement text to separate it from what follows 
4259
                 */
4260
                if (cmd[i+1] != 0 && needed != 0)
4261
                    needed += 1;
4262
4263
                /* leave room for null termination */
4264
                needed += 1;
4265
4266
                /* if we don't have room for this token, stop now */
4267
                if (needed > (size_t)(VOCBUFSIZ - (p - cmdbuf)))
4268
                    break;
4269
4270
                /* 
4271
                 *   if we've reached the unknown token, insert the
4272
                 *   replacement text; otherwise, insert this token
4273
                 */
4274
                if (i == unk_idx)
4275
                {
4276
                    /* insert the replacement text */
4277
                    strcpy(p, rpl_text);
4278
                }
4279
                else if (*cmd[i] == '"')
4280
                {
4281
                    char *p1;
4282
                    char qu;
4283
                    
4284
                    /* 
4285
                     *   Scan the quoted string for embedded double quotes
4286
                     *   - if it has any, use single quotes as the
4287
                     *   delimiter; otherwise, use double quotes as the
4288
                     *   delimiter.  Note that we ignore the first and
4289
                     *   last characters in the string, since these are
4290
                     *   always the delimiting double quotes in the
4291
                     *   original token text.  
4292
                     */
4293
                    for (qu = '"', p1 = cmd[i] + 1 ;
4294
                         *p1 != '\0' && *(p1 + 1) != '\0' ; ++p1)
4295
                    {
4296
                        /* check for an embedded double quote */
4297
                        if (*p1 == '"')
4298
                        {
4299
                            /* switch to single quotes as delimiters */
4300
                            qu = '\'';
4301
4302
                            /* no need to look any further */
4303
                            break;
4304
                        }
4305
                    }
4306
4307
                    /* add the open quote */
4308
                    *p++ = '"';
4309
4310
                    /* 
4311
                     *   add the text, leaving out the first and last
4312
                     *   characters (which are the original quotes) 
4313
                     */
4314
                    if (strlen(cmd[i]) > 2)
4315
                    {
4316
                        memcpy(p, cmd[i] + 1, strlen(cmd[i]) - 2);
4317
                        p += strlen(cmd[i]) - 2;
4318
                    }
4319
4320
                    /* add the closing quote */
4321
                    *p++ = '"';
4322
4323
                    /* null-terminate here so we don't skip any further */
4324
                    *p = '\0';
4325
                }
4326
                else
4327
                {
4328
                    /* copy this word */
4329
                    strcpy(p, cmd[i]);
4330
                }
4331
4332
                /* move past this token */
4333
                p += strlen(p);
4334
4335
                /* add a space if another token follows */
4336
                if (cmd[i+1] != 0)
4337
                    *p++ = ' ';
4338
            }
4339
4340
            /* null-terminate the replacement text */
4341
            *p = '\0';
4342
        }
4343
    }
4344
4345
    /* 
4346
     *   Count the objects.  An object list is returned only on success or
4347
     *   VOCERR(44), which indicates that the list is still ambiguous. 
4348
     */
4349
    if (err == 0 || err == VOCERR(44))
4350
    {
4351
        /* count the objects in the list */
4352
        for (i = 0 ; outlist[i].vocolobj != MCMONINV ; ++i) ;
4353
        objcnt = i;
4354
    }
4355
    else
4356
    {
4357
        /* there's nothing in the list */
4358
        objcnt = 0;
4359
    }
4360
4361
    /* figure out how much space we need for the objects */
4362
    lstsiz = (1+2) * objcnt;
4363
        
4364
    /* add space for the first element, which contains the status code */
4365
    lstsiz += (1 + 4);
4366
4367
    /* if there's a new command string, we'll store it, so make room */
4368
    if (ctx_copy.voccxredo || err == VOCERR(45))
4369
    {
4370
        /* 
4371
         *   add space for the type prefix (1), length prefix (2), and the
4372
         *   string bytes (with no null terminator, of course) 
4373
         */
4374
        lstsiz += (1 + 2 + strlen(cmdbuf));
4375
4376
        /* 
4377
         *   if we're retrying due to the redo flag, always return the
4378
         *   RETRY error code, regardless of what caused us to retry the
4379
         *   command 
4380
         */
4381
        if (ctx_copy.voccxredo)
4382
            err = VOCERR(43);
4383
    }
4384
4385
    /* push a list with space for the objects */
4386
    lstp = voc_push_list_siz(ctx, lstsiz);
4387
4388
    /* store the status code in the first element */
4389
    *lstp++ = DAT_NUMBER;
4390
    oswp4(lstp, err);
4391
    lstp += 4;
4392
4393
    /* store the remainder of the list */
4394
    if (err == 0 || err == VOCERR(44))
4395
    {
4396
        /* fill in the list with the objects */
4397
        for (i = 0 ; i < objcnt ; ++i)
4398
        {
4399
            /* set this element */
4400
            *lstp++ = DAT_OBJECT;
4401
            oswp2(lstp, outlist[i].vocolobj);
4402
            lstp += 2;
4403
        }
4404
    }
4405
    else if (ctx_copy.voccxredo || err == VOCERR(45))
4406
    {
4407
        uint len;
4408
        
4409
        /* there's a new command - return it as the second element */
4410
        *lstp++ = DAT_SSTRING;
4411
4412
        /* store the length */
4413
        len = strlen(cmdbuf);
4414
        oswp2(lstp, len + 2);
4415
        lstp += 2;
4416
4417
        /* store the string */
4418
        memcpy(lstp, cmdbuf, len);
4419
    }
4420
4421
    /* leave the stack frame */
4422
    voc_leave(ctx, save_sp);
4423
}
4424
4425
4426
/* ------------------------------------------------------------------------ */
4427
/*
4428
 *   TADS program code interface - replace the current command line with a
4429
 *   new string, aborting the current command. 
4430
 */
4431
void voc_parse_replace_cmd(voccxdef *ctx)
4432
{
4433
    runcxdef *rcx = ctx->voccxrun;
4434
    uchar *p;
4435
    uint len;
4436
4437
    /* get the string */
4438
    p = runpopstr(rcx);
4439
4440
    /* read and skip the length prefix */
4441
    len = osrp2(p) - 2;
4442
    p += 2;
4443
4444
    /* make sure it fits in the redo buffer - truncate it if necessary */
4445
    if (len + 1 > VOCBUFSIZ)
4446
        len = VOCBUFSIZ - 1;
4447
4448
    /* copy the string and null-terminate it */
4449
    memcpy(ctx->voccxredobuf, p, len);
4450
    ctx->voccxredobuf[len] = '\0';
4451
4452
    /* set the "redo" flag so that we execute what's in the buffer */
4453
    ctx->voccxredo = TRUE;
4454
4455
    /* abort the current command so that we start anew with the replacement */
4456
    errsig(ctx->voccxerr, ERR_RUNABRT);
4457
}
4458
4459
4460
/* ------------------------------------------------------------------------ */
4461
/*
4462
 *   This routine gets an actor, which is just a single object reference at
4463
 *   the beginning of a sentence.  We return 0 if we fail to find an actor;
4464
 *   since this can be either a harmless or troublesome condition, we must
4465
 *   return additional information.  The method used to return back ERROR/OK
4466
 *   is to set *next != cur if there is an error, *next == cur if not.  So,
4467
 *   getting back (objdef*)0 means that you should check *next.  If the return
4468
 *   value is nonzero, then that object is the actor.
4469
 */
4470
static objnum vocgetactor(voccxdef *ctx, char *cmd[], int typelist[],
4471
                          int cur, int *next, char *cmdbuf)
4472
{
4473
    int       l;
4474
    vocoldef *nounlist;
4475
    vocoldef *actlist;
4476
    int       cnt;
4477
    uchar    *save_sp;
4478
    prpnum    valprop, verprop;
4479
4480
    voc_enter(ctx, &save_sp);
4481
    VOC_MAX_ARRAY(ctx, vocoldef, nounlist);
4482
    VOC_MAX_ARRAY(ctx, vocoldef, actlist);
4483
    
4484
    *next = cur;                              /* assume no error will occur */
4485
    cnt = vocchknoun(ctx, cmd, typelist, cur, next, nounlist, TRUE);
4486
    if (cnt > 0 && *next != -1 && cmd[*next] && vocspec(cmd[*next], VOCW_AND))
4487
    {
4488
        int  have_unknown;
4489
4490
        /* make a note as to whether the list contains an unknown word */
4491
        have_unknown = ((nounlist[0].vocolflg & VOCS_UNKNOWN) != 0);
4492
        
4493
        /*
4494
         *   If validActor is defined for any of the actors, use it;
4495
         *   otherwise, for compatibility with past versions, use the
4496
         *   takeVerb disambiguation mechanism.  If we have a pronoun, we
4497
         *   can't decide yet how to do this, so presume that we'll use
4498
         *   the new mechanism and switch later if necessary.
4499
         *   
4500
         *   If we have don't have a valid object (which will be the case
4501
         *   for a pronoun), we can't decide until we get into the
4502
         *   disambiguation process, so presume we'll use validActor for
4503
         *   now.  
4504
         */
4505
        verprop = PRP_VERACTOR;
4506
        if (nounlist[0].vocolobj == MCMONINV
4507
            || objgetap(ctx->voccxmem, nounlist[0].vocolobj, PRP_VALIDACTOR,
4508
                        (objnum *)0, FALSE))
4509
            valprop = PRP_VALIDACTOR;
4510
        else
4511
            valprop = PRP_VALIDDO;
4512
        
4513
        /* disambiguate it using the selected properties */
4514
        if (vocdisambig(ctx, actlist, nounlist, PRP_DODEFAULT, valprop,
4515
                        verprop, cmd, MCMONINV, ctx->voccxme,
4516
                        ctx->voccxvtk, MCMONINV, cmdbuf, FALSE))
4517
        {
4518
            /* 
4519
             *   if we have an unknown word in the list, assume for the
4520
             *   moment that this isn't an actor phrase after all, but a
4521
             *   verb 
4522
             */
4523
            if (have_unknown)
4524
            {
4525
                /* indicate that we didn't find a noun phrase syntactically */
4526
                *next = cur;
4527
            }
4528
4529
            /* return no actor */
4530
            VOC_RETVAL(ctx, save_sp, MCMONINV);
4531
        }
4532
4533
        if ((l = voclistlen(actlist)) > 1)
4534
        {
4535
            vocerr(ctx, VOCERR(12),
4536
                   "You can only speak to one person at a time.");
4537
            *next = cur + 1;   /* error flag - return invalid but next!=cur */
4538
            VOC_RETVAL(ctx, save_sp, MCMONINV);
4539
        }
4540
        else if (l == 0)
4541
            return(MCMONINV);
4542
4543
        if (cmd[*next] && vocspec(cmd[*next], VOCW_AND))
4544
        {
4545
            ++(*next);
4546
            VOC_RETVAL(ctx, save_sp, actlist[0].vocolobj);
4547
        }
4548
    }
4549
    if (cnt < 0)
4550
    {
4551
        /* error - make *next != cur */
4552
        *next = cur + 1;
4553
    }
4554
    else
4555
        *next = cur;               /* no error condition, but nothing found */
4556
4557
    VOC_RETVAL(ctx, save_sp, MCMONINV);    /* so return invalid and *next == cur */
4558
}
4559
4560
/* figure out how many objects are in an object list */
4561
int voclistlen(vocoldef *lst)
4562
{
4563
    int i;
4564
    
4565
    for (i = 0 ; lst->vocolobj != MCMONINV || lst->vocolflg != 0 ;
4566
         ++lst, ++i) ;
4567
    return(i);
4568
}
4569
4570
/*
4571
 *   check access - evaluates cmdVerb.verprop(actor, obj, seqno), and
4572
 *   returns whatever it returns.  The seqno parameter is used for special
4573
 *   cases, such as "ask", when the validation routine wishes to return
4574
 *   "true" on the first object and "nil" on all subsequent objects which
4575
 *   correspond to a particular noun phrase.  We expect to be called with
4576
 *   seqno==0 on the first object, non-zero on others; we will pass
4577
 *   seqno==1 on the first object to the validation property, higher on
4578
 *   subsequent objects, to maintain consistency with the TADS language
4579
 *   convention of indexing from 1 up (as seen by the user in indexing
4580
 *   functions).  Note that if we're checking an actor, we'll just call
4581
 *   obj.validActor() for the object itself (not the verb).
4582
 */
4583
int vocchkaccess(voccxdef *ctx, objnum obj, prpnum verprop,
4584
                 int seqno, objnum cmdActor, objnum cmdVerb)
4585
{
4586
    /* 
4587
     *   special case: the special "string" and "number" objects are
4588
     *   always accessible 
4589
     */
4590
    if (obj == ctx->voccxstr || obj == ctx->voccxnum)
4591
        return TRUE;
4592
4593
    /*
4594
     *   If the access method is validActor, make sure the object in fact
4595
     *   has a validActor method defined; if it doesn't, we must be
4596
     *   running a game from before validActor's invention, so use the old
4597
     *   ValidXo mechanism instead. 
4598
     */
4599
    if (verprop == PRP_VALIDACTOR)
4600
    {
4601
        /* checking an actor - check to see if ValidActor is defined */
4602
        if (objgetap(ctx->voccxmem, obj, PRP_VALIDACTOR, (objnum *)0, FALSE))
4603
        {
4604
            /* ValidActor is present - call ValidActor in the object itself */
4605
            runppr(ctx->voccxrun, obj, verprop, 0);
4606
4607
            /* return the result */
4608
            return runpoplog(ctx->voccxrun);
4609
        }
4610
        else
4611
        {
4612
            /* there's no ValidActor - call ValidXo in the "take" verb */
4613
            cmdVerb = ctx->voccxvtk;
4614
            verprop = PRP_VALIDDO;
4615
        }
4616
    }
4617
4618
    /* call ValidXo in the verb */
4619
    runpnum(ctx->voccxrun, (long)(seqno + 1));
4620
    runpobj(ctx->voccxrun, obj);
4621
    runpobj(ctx->voccxrun,
4622
            (objnum)(cmdActor == MCMONINV ? ctx->voccxme : cmdActor));
4623
    runppr(ctx->voccxrun, cmdVerb, verprop, 3);
4624
4625
    /* return the result */
4626
    return runpoplog(ctx->voccxrun);
4627
}
4628
4629
/* ask game if object is visible to the actor */
4630
int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor)
4631
{
4632
    runpobj(ctx->voccxrun,
4633
            (objnum)(cmdActor == MCMONINV ? ctx->voccxme : cmdActor));
4634
    runppr(ctx->voccxrun, obj, (prpnum)PRP_ISVIS, 1);
4635
    return(runpoplog(ctx->voccxrun));
4636
}
4637
4638
/* set {numObj | strObj}.value, as appropriate */
4639
void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
4640
               vocoldef *inobj, vocoldef *outobj)
4641
{
4642
    *outobj = *inobj;
4643
    outobj->vocolobj = obj;
4644
    objsetp(ctx->voccxmem, obj, PRP_VALUE, typ, val, ctx->voccxundo);
4645
}
4646
4647
/* set up a vocoldef */
4648
static void vocout(vocoldef *outobj, objnum obj, int flg,
4649
                   char *fst, char *lst)
4650
{
4651
    outobj->vocolobj = obj;
4652
    outobj->vocolflg = flg;
4653
    outobj->vocolfst = fst;
4654
    outobj->vocollst = lst;
4655
}
4656
4657
/*
4658
 *   Generate an appropriate error message saying that the objects in the
4659
 *   command are visible, but can't be used with the command for some
4660
 *   reason.  Use the cantReach method of the verb (the new way), or if
4661
 *   there is no cantReach in the verb, of each object in the list. 
4662
 */
4663
void vocnoreach(voccxdef *ctx, objnum *list1, int cnt,
4664
                objnum actor, objnum verb, objnum prep, prpnum defprop,
4665
                int show_multi_prefix,
4666
                int multi_flags, int multi_base_index, int multi_total_count)
4667
{
4668
    /* see if the verb has a cantReach method - use it if so */
4669
    if (objgetap(ctx->voccxmem, verb, PRP_NOREACH, (objnum *)0, FALSE))
4670
    {
4671
        /* push arguments:  (actor, dolist, iolist, prep) */
4672
        runpobj(ctx->voccxrun, prep);
4673
        if (defprop == PRP_DODEFAULT)
4674
        {
4675
            runpnil(ctx->voccxrun);
4676
            voc_push_objlist(ctx, list1, cnt);
4677
        }
4678
        else
4679
        {
4680
            voc_push_objlist(ctx, list1, cnt);
4681
            runpnil(ctx->voccxrun);
4682
        }
4683
        runpobj(ctx->voccxrun, actor);
4684
4685
        /* invoke the method in the verb */
4686
        runppr(ctx->voccxrun, verb, PRP_NOREACH, 4);
4687
    }
4688
    else
4689
    {
4690
        int  i;
4691
4692
        /* use the old way - call obj.cantReach() for each object */
4693
        for (i = 0 ; i < cnt ; ++i)
4694
        {
4695
            /* 
4696
             *   display this object's name if there's more than one, so
4697
             *   that the player can tell to which object this message
4698
             *   applies 
4699
             */
4700
            voc_multi_prefix(ctx, list1[i], show_multi_prefix, multi_flags,
4701
                             multi_base_index + i, multi_total_count);
4702
4703
            /* call cantReach on the object */
4704
            runpobj(ctx->voccxrun,
4705
                    (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
4706
            runppr(ctx->voccxrun, list1[i], (prpnum)PRP_NOREACH, 1);
4707
            tioflush(ctx->voccxtio);
4708
        }
4709
    }
4710
}
4711
4712
/*
4713
 *   Get the specialWords string for a given special word entry.  Returns
4714
 *   the first string if multiple strings are defined for the entry.  
4715
 */
4716
static void voc_get_spec_str(voccxdef *ctx, char vocw_id,
4717
                             char *buf, size_t buflen,
4718
                             const char *default_name)
4719
{
4720
    int found;
4721
4722
    /* presume we won't find it */
4723
    found = FALSE;
4724
4725
    /* if there's a special word list, search it for this entry */
4726
    if (ctx->voccxspp != 0)
4727
    {
4728
        char   *p;
4729
        char   *endp;
4730
        size_t  len;
4731
4732
        /* find appropriate user-defined word in specialWords list */
4733
        for (p = ctx->voccxspp, endp = p + ctx->voccxspl ; p < endp ; )
4734
        {
4735
            /* if this one matches, get its first word */
4736
            if (*p++ == vocw_id)
4737
            {
4738
                /* note that we found it */
4739
                found = TRUE;
4740
4741
                /* 
4742
                 *   get the length, and limit it to the buffer size,
4743
                 *   leaving room for null termination 
4744
                 */
4745
                len = *p++;
4746
                if (len + 1 > buflen)
4747
                    len = buflen - 1;
4748
4749
                /* copy it and null-terminate the string */
4750
                memcpy(buf, p, len);
4751
                buf[len] = '\0';
4752
4753
                /* we found it - no need to look any further */
4754
                break;
4755
            }
4756
4757
            /* 
4758
             *   move on to the next one - skip the length prefix plus the
4759
             *   length 
4760
             */
4761
            p += *p + 1;
4762
        }
4763
    }
4764
4765
    /* if we didn't find it, use the default */
4766
    if (!found)
4767
    {
4768
        strncpy(buf, default_name, (size_t)buflen);
4769
        buf[buflen - 1] = '\0';
4770
    }
4771
}
4772
4773
/* set it/him/her */
4774
static int vocsetit(voccxdef *ctx, objnum obj, int accprop,
4775
                    objnum actor, objnum verb, objnum prep,
4776
                    vocoldef *outobj, char *default_name, char vocw_id,
4777
                    prpnum defprop, int silent)
4778
{
4779
    if (obj == MCMONINV || !vocchkaccess(ctx, obj, (prpnum)accprop,
4780
                                         0, actor, verb))
4781
    {
4782
        char nambuf[40];
4783
4784
        /* get the display name for this specialWords entry */
4785
        voc_get_spec_str(ctx, vocw_id, nambuf, sizeof(nambuf), default_name);
4786
        
4787
        /* show the error if appropriate */
4788
        if (!silent)
4789
        {
4790
            /* use 'noreach' if possible, otherwise use a default message */
4791
            if (obj == MCMONINV)
4792
                vocerr(ctx, VOCERR(13),
4793
                       "I don't know what you're referring to with '%s'.",
4794
                       nambuf);
4795
            else
4796
                vocnoreach(ctx, &obj, 1, actor, verb, prep,
4797
                           defprop, FALSE, 0, 0, 1);
4798
        }
4799
4800
        /* indicate that the antecedent is inaccessible */
4801
        return VOCERR(13);
4802
    }
4803
4804
    /* set the object */
4805
    vocout(outobj, obj, 0, default_name, default_name);
4806
    return 0;
4807
}
4808
4809
/*
4810
 *   Get a new numbered object, given a number.  This is used for objects
4811
 *   that define '#' as one of their adjectives; we call the object,
4812
 *   asking it to create an object with a particular number.  The object
4813
 *   can return nil, in which case we'll reject the command.  
4814
 */
4815
static objnum voc_new_num_obj(voccxdef *ctx, objnum objn,
4816
                              objnum actor, objnum verb,
4817
                              long num, int plural)
4818
{
4819
    /* push the number - if we need a plural object, use nil instead */
4820
    if (plural)
4821
        runpnil(ctx->voccxrun);
4822
    else
4823
        runpnum(ctx->voccxrun, num);
4824
4825
    /* push the other arguments and call the method */
4826
    runpobj(ctx->voccxrun, verb);
4827
    runpobj(ctx->voccxrun, actor);
4828
    runppr(ctx->voccxrun, objn, PRP_NEWNUMOBJ, 3);
4829
4830
    /* if it was rejected, return an invalid object, else return the object */
4831
    if (runtostyp(ctx->voccxrun) == DAT_NIL)
4832
    {
4833
        rundisc(ctx->voccxrun);
4834
        return MCMONINV;
4835
    }
4836
    else
4837
        return runpopobj(ctx->voccxrun);
4838
}
4839
4840
/* check if an object defines the special adjective '#' */
4841
static int has_gen_num_adj(voccxdef *ctx, objnum objn)
4842
{
4843
    vocwdef   *v;
4844
    vocseadef  search_ctx;
4845
4846
    /* scan the list of objects defined '#' as an adjective */
4847
    for (v = vocffw(ctx, "#", 1, (char *)0, 0, PRP_ADJ, &search_ctx) ;
4848
         v ; v = vocfnw(ctx, &search_ctx))
4849
    {
4850
        /* if this is the object, return positive indication */
4851
        if (v->vocwobj == objn)
4852
            return TRUE;
4853
    }
4854
4855
    /* didn't find it */
4856
    return FALSE;
4857
}
4858
4859
4860
/* ------------------------------------------------------------------------ */
4861
/*
4862
 *   Call the deepverb's disambigDobj or disambigIobj method to perform
4863
 *   game-controlled disambiguation.  
4864
 */
4865
static int voc_disambig_hook(voccxdef *ctx, objnum verb, objnum actor,
4866
                             objnum prep, objnum otherobj,
4867
                             prpnum accprop, prpnum verprop,
4868
                             objnum *objlist, uint *flags, int *objcount,
4869
                             char *firstwrd, char *lastwrd,
4870
                             int num_wanted, int is_ambig, char *resp,
4871
                             int silent)
4872
{
4873
    runcxdef *rcx = ctx->voccxrun;
4874
    prpnum call_prop;
4875
    runsdef val;
4876
    uchar *lstp;
4877
    uint lstsiz;
4878
    int ret;
4879
    int i;
4880
    
4881
    /* check for actor disambiguation */
4882
    if (verprop == PRP_VERACTOR)
4883
    {
4884
        /* do nothing on actor disambiguation */
4885
        return VOC_DISAMBIG_CONT;
4886
    }
4887
4888
    /* figure out whether this is a dobj method or an iobj method */
4889
    call_prop = (accprop == PRP_VALIDDO ? PRP_DISAMBIGDO : PRP_DISAMBIGIO);
4890
4891
    /* if the method isn't defined, we can skip this entirely */
4892
    if (objgetap(ctx->voccxmem, verb, call_prop, (objnum *)0, FALSE) == 0)
4893
        return VOC_DISAMBIG_CONT;
4894
4895
    /* push the "silent" flag */
4896
    val.runstyp = (silent ? DAT_TRUE : DAT_NIL);
4897
    runpush(rcx, val.runstyp, &val);
4898
4899
    /* push the "is_ambiguous" flag */
4900
    val.runstyp = (is_ambig ? DAT_TRUE : DAT_NIL);
4901
    runpush(rcx, val.runstyp, &val);
4902
4903
    /* push the "numWanted" count */
4904
    runpnum(rcx, num_wanted);
4905
4906
    /* push the flag list */
4907
    voc_push_numlist(ctx, flags, *objcount);
4908
4909
    /* push the object list */
4910
    voc_push_objlist(ctx, objlist, *objcount);
4911
4912
    /* push the word list */
4913
    voc_push_strlist(ctx, firstwrd, lastwrd);
4914
4915
    /* push the verification property */
4916
    val.runstyp = DAT_PROPNUM;
4917
    val.runsv.runsvprp = verprop;
4918
    runpush(rcx, DAT_PROPNUM, &val);
4919
4920
    /* push the other object */
4921
    runpobj(rcx, otherobj);
4922
4923
    /* push the preposition and the actor objects */
4924
    runpobj(rcx, prep);
4925
    runpobj(rcx, actor);
4926
4927
    /* call the method */
4928
    runppr(rcx, verb, call_prop, 10);
4929
4930
    /* check the return value */
4931
    switch(runtostyp(rcx))
4932
    {
4933
    case DAT_LIST:
4934
        /* get the list */
4935
        lstp = runpoplst(rcx);
4936
4937
        /* read the list size prefix */
4938
        lstsiz = osrp2(lstp) - 2;
4939
        lstp += 2;
4940
4941
        /* check for the status code */
4942
        if (lstsiz > 0 && *lstp == DAT_NUMBER)
4943
        {
4944
            /* get the status code */
4945
            ret = osrp4(lstp+1);
4946
4947
            /* skip the element */
4948
            lstadv(&lstp, &lstsiz);
4949
        }
4950
        else
4951
        {
4952
            /* there's no status code - assume CONTINUE */
4953
            ret = VOC_DISAMBIG_CONT;
4954
        }
4955
4956
        /* check for a PARSE_RESP return */
4957
        if (ret == VOC_DISAMBIG_PARSE_RESP)
4958
        {
4959
            /* the second element is the string */
4960
            if (*lstp == DAT_SSTRING)
4961
            {
4962
                uint len;
4963
                
4964
                /* get the length, and limit it to our buffer size */
4965
                len = osrp2(lstp+1) - 2;
4966
                if (len > VOCBUFSIZ - 1)
4967
                    len = VOCBUFSIZ - 1;
4968
4969
                /* copy the string into the caller's buffer */
4970
                memcpy(resp, lstp+3, len);
4971
                resp[len] = '\0';
4972
            }
4973
            else
4974
            {
4975
                /* there's no string - ignore it */
4976
                ret = VOC_DISAMBIG_CONT;
4977
            }
4978
        }
4979
        else
4980
        {
4981
            /* store the object list in the caller's list */
4982
            for (i = 0 ; lstsiz > 0 && i < VOCMAXAMBIG-1 ; ++i)
4983
            {
4984
                /* get this object */
4985
                if (*lstp == DAT_OBJECT)
4986
                    objlist[i] = osrp2(lstp+1);
4987
                else
4988
                    objlist[i] = MCMONINV;
4989
                
4990
                /* skip the list entry */
4991
                lstadv(&lstp, &lstsiz);
4992
                
4993
                /* check for flags */
4994
                if (lstsiz > 0 && *lstp == DAT_NUMBER)
4995
                {
4996
                    /* store the flags */
4997
                    flags[i] = (int)osrp4(lstp+1);
4998
                    
4999
                    /* skip the flags elements */
5000
                    lstadv(&lstp, &lstsiz);
5001
                }
5002
                else
5003
                {
5004
                    /* no flags - use zero by default */
5005
                    flags[i] = 0;
5006
                }
5007
            }
5008
5009
            /* store a terminator at the end of the list */
5010
            objlist[i] = MCMONINV;
5011
            flags[i] = 0;
5012
            
5013
            /* store the output count for the caller */
5014
            *objcount = i;
5015
        }
5016
5017
        /* return the result */
5018
        return ret;
5019
5020
    case DAT_NUMBER:
5021
        /* get the status code */
5022
        ret = runpopnum(rcx);
5023
5024
        /* ignore raw PARSE_RESP codes, since they need to return a string */
5025
        if (ret == VOC_DISAMBIG_PARSE_RESP)
5026
            ret = VOC_DISAMBIG_CONT;
5027
5028
        /* return the status */
5029
        return ret;
5030
5031
    default:
5032
        /* treat anything else as CONTINUE */
5033
        rundisc(rcx);
5034
        return VOC_DISAMBIG_CONT;
5035
    }
5036
}
5037
5038
5039
/* ------------------------------------------------------------------------ */
5040
/*
5041
 *   Prune a list of matches by keeping only the matches without the given
5042
 *   flag value, if we have a mix of entries with and without the flag.
5043
 *   This is a service routine for voc_prune_matches.
5044
 *   
5045
 *   The flag indicates a lower quality of matching, so this routine can
5046
 *   be used to reduce ambiguity by keeping only the best-quality matches
5047
 *   when matches of mixed quality are present.  
5048
 */
5049
static int voc_remove_objs_with_flag(voccxdef *ctx,
5050
                                     objnum *list, uint *flags, int cnt,
5051
                                     int flag_to_remove)
5052
{
5053
    int i;
5054
    int flag_cnt;
5055
    int special_cnt;
5056
5057
    /* first, count the number of objects with the flag */
5058
    for (i = 0, flag_cnt = special_cnt = 0 ; i < cnt ; ++i)
5059
    {
5060
        /* if this object exhibits the flag, count it */
5061
        if ((flags[i] & flag_to_remove) != 0)
5062
            ++flag_cnt;
5063
5064
        /* if it's numObj or strObj, count it separately */
5065
        if (list[i] == ctx->voccxnum || list[i] == ctx->voccxstr)
5066
            ++special_cnt;
5067
    }
5068
5069
    /* 
5070
     *   If all of the objects didn't have the flag, omit the ones that
5071
     *   did, so that we reduce the ambiguity to those without the flag.
5072
     *   Don't include the special objects (numObj and strObj) in the
5073
     *   count, since they will never have any of these flags set.  
5074
     */
5075
    if (flag_cnt != 0 && flag_cnt < cnt - special_cnt)
5076
    {
5077
        int dst;
5078
5079
        /* 
5080
         *   Remove the flagged objects.  Note that we can make this
5081
         *   adjustment to the arrays in place, because they can only
5082
         *   shrink - there's no need to make an extra temporary copy.  
5083
         */
5084
        for (i = 0, dst = 0 ; i < cnt ; ++i)
5085
        {
5086
            /* 
5087
             *   If this one doesn't have the flag, keep it.  Always keep
5088
             *   the special objects (numObj and strObj). 
5089
             */
5090
            if ((flags[i] & flag_to_remove) == 0
5091
                || list[i] == ctx->voccxnum
5092
                || list[i] == ctx->voccxstr)
5093
            {
5094
                /* copy this one to the output location */
5095
                list[dst] = list[i];
5096
                flags[dst] = flags[i];
5097
5098
                /* count the new element of the output */
5099
                ++dst;
5100
            }
5101
        }
5102
5103
        /* set the updated count */
5104
        cnt = dst;
5105
        list[cnt] = MCMONINV;
5106
    }
5107
5108
    /* return the new count */
5109
    return cnt;
5110
}
5111
5112
/*
5113
 *   Prune a list of matches by keeping only the best matches when matches
5114
 *   of different qualities are present.
5115
 *   
5116
 *   If we have a mix of objects matching noun phrases that end in
5117
 *   adjectives and phrases ending in nouns with the same words, remove
5118
 *   those elements that end in adjectives, keeping only the better
5119
 *   matches that end in nouns.
5120
 *   
5121
 *   If we have a mix of objects where the words match exactly, and others
5122
 *   where the words are only leading substrings of longer dictionary
5123
 *   words, keep only the exact matches.
5124
 *   
5125
 *   Returns the number of elements in the result list.  
5126
 */
5127
static int voc_prune_matches(voccxdef *ctx,
5128
                             objnum *list, uint *flags, int cnt)
5129
{
5130
    /* remove matches that end with an adjective */
5131
    cnt = voc_remove_objs_with_flag(ctx, list, flags, cnt, VOCS_ENDADJ);
5132
5133
    /* remove matches that use truncated words */
5134
    cnt = voc_remove_objs_with_flag(ctx, list, flags, cnt, VOCS_TRUNC);
5135
5136
    /* return the new list size */
5137
    return cnt;
5138
}
5139
5140
/* ------------------------------------------------------------------------ */
5141
/*
5142
 *   Count indistinguishable items.
5143
 *   
5144
 *   If 'keep_all' is true, we'll keep all of the items, whether or not
5145
 *   some are indistinguishable from one another.  If 'keep_all' is false,
5146
 *   we'll keep only one item from each set of indistinguishable items. 
5147
 */
5148
static int voc_count_diff(voccxdef *ctx, objnum *list, uint *flags, int *cnt,
5149
                          int keep_all)
5150
{
5151
    int i;
5152
    int diff_cnt;
5153
    
5154
    /* 
5155
     *   Presume all items will be distinguishable from one another.  As
5156
     *   we scan the list for indistinguishable items, we'll decrement
5157
     *   this count each time we find an item that can't be distinguished
5158
     *   from another item.  
5159
     */
5160
    diff_cnt = *cnt;
5161
5162
    /* 
5163
     *   Look for indistinguishable items.
5164
     *   
5165
     *   An object is distinguishable if it doesn't have the special
5166
     *   property marking it as one of a group of equivalent objects
5167
     *   (PRP_ISEQUIV), or if it has the property but there is no object
5168
     *   following it in the list which has the same immediate superclass.
5169
     *   
5170
     *   Note that we want to keep the duplicates if we're looking for
5171
     *   plurals, because the player is explicitly referring to all
5172
     *   matching objects.  
5173
     */
5174
    for (i = 0 ; i < *cnt ; ++i)
5175
    {
5176
        /* 
5177
         *   check to see if this object might have indistinguishable
5178
         *   duplicates - it must be marked with isEquiv for this to be
5179
         *   possible 
5180
         */
5181
        runppr(ctx->voccxrun, list[i], PRP_ISEQUIV, 0);
5182
        if (runpoplog(ctx->voccxrun))
5183
        {
5184
            int     j;
5185
            int     dst;
5186
            objnum  sc;
5187
            
5188
            /* get the superclass, if possible */
5189
            sc = objget1sc(ctx->voccxmem, list[i]);
5190
            if (sc == MCMONINV)
5191
                continue;
5192
            
5193
            /* 
5194
             *   run through the remainder of the list, and remove any
5195
             *   duplicates of this item 
5196
             */
5197
            for (j = i + 1, dst = i + 1 ; j < *cnt ; ++j)
5198
            {
5199
                /* 
5200
                 *   see if it matches our object - if not, keep it in the
5201
                 *   list by copying it to our destination position 
5202
                 */
5203
                if (objget1sc(ctx->voccxmem, list[j]) != sc)
5204
                {
5205
                    /* it's distinguishable - keep it */
5206
                    list[dst] = list[j];
5207
                    flags[dst++] = flags[j];
5208
                }
5209
                else
5210
                {
5211
                    /*
5212
                     *   This item is indistinguishable from the list[i].
5213
                     *   First, reduce the count of different items.  
5214
                     */
5215
                    --diff_cnt;
5216
                    
5217
                    /*
5218
                     *   Keep this object only if we're keeping all
5219
                     *   redundant indistinguishable items. 
5220
                     */
5221
                    if (keep_all)
5222
                    {
5223
                        /* keep all items -> keep this item */
5224
                        list[dst] = list[j];
5225
                        flags[dst++] = flags[j];
5226
                    }
5227
                }
5228
            }
5229
            
5230
            /* adjust the count to reflect the updated list */
5231
            *cnt = dst;
5232
5233
            /* add a terminator */
5234
            list[dst] = MCMONINV;
5235
            flags[dst] = 0;
5236
        }
5237
    }
5238
5239
    /* return the number of distinguishable items */
5240
    return diff_cnt;
5241
}
5242
5243
/* ------------------------------------------------------------------------ */
5244
/*
5245
 *   vocdisambig - determines which nouns in a noun list apply.  When this
5246
 *   is called, we must know the verb that we are processing, so we delay
5247
 *   disambiguation until quite late in the parsing of a sentence, opting
5248
 *   to keep all relevant information around until such time as we can
5249
 *   meaningfully disambiguate.
5250
 *
5251
 *   This routine resolves any "all [except...]", "it", and "them"
5252
 *   references.  We determine if all of the objects listed are accessible
5253
 *   (via verb.validDo, verb.validIo).  We finally try to determine which
5254
 *   nouns apply when there are ambiguous nouns by using do.verDo<Verb>
5255
 *   and io.verIo<Verb>.
5256
 */
5257
int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
5258
                prpnum defprop, prpnum accprop, prpnum verprop,
5259
                char *cmd[], objnum otherobj, objnum cmdActor,
5260
                objnum cmdVerb, objnum cmdPrep, char *cmdbuf,
5261
                int silent)
5262
{
5263
    int       inpos;
5264
    int       outpos;
5265
    int       listlen = voclistlen(inlist);
5266
    int       noreach = FALSE;
5267
    prpnum    listprop;
5268
    uchar    *save_sp;
5269
    int       old_unknown, old_lastunk;
5270
    int       err;
5271
    int       still_ambig;
5272
5273
    voc_enter(ctx, &save_sp);
5274
5275
    ERRBEGIN(ctx->voccxerr)
5276
5277
    /* presume we will not leave any ambiguity in the result */
5278
    still_ambig = FALSE;
5279
5280
    /* loop through all of the objects in the input list */
5281
    for (inpos = outpos = 0 ; inpos < listlen ; ++inpos)
5282
    {
5283
        /* 
5284
         *   reset the stack to our entrypoint value, since our stack
5285
         *   variables are all temporary for a single iteration 
5286
         */
5287
        voc_leave(ctx, save_sp);
5288
        voc_enter(ctx, &save_sp);
5289
5290
        if (inlist[inpos].vocolflg == VOCS_STR)
5291
        {
5292
            vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING,
5293
                      inlist[inpos].vocolfst + 1,
5294
                      &inlist[inpos], &outlist[outpos]);
5295
            ++outpos;
5296
        }
5297
        else if (inlist[inpos].vocolflg == VOCS_NUM)
5298
        {
5299
            long v1;
5300
            char vbuf[4];
5301
            
5302
            v1 = atol(inlist[inpos].vocolfst);
5303
            oswp4(vbuf, v1);
5304
            vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, vbuf,
5305
                      &inlist[inpos], &outlist[outpos]);
5306
            ++outpos;
5307
        }
5308
        else if (inlist[inpos].vocolflg == VOCS_IT ||
5309
                 (inlist[inpos].vocolflg == VOCS_THEM && ctx->voccxthc == 0))
5310
        {
5311
            err = vocsetit(ctx, ctx->voccxit, accprop, cmdActor,
5312
                           cmdVerb, cmdPrep, &outlist[outpos],
5313
                           inlist[inpos].vocolflg == VOCS_IT ? "it" : "them",
5314
                           (char)(inlist[inpos].vocolflg == VOCS_IT
5315
                                  ? VOCW_IT : VOCW_THEM), defprop, silent);
5316
            if (err != 0)
5317
                goto done;
5318
            ++outpos;
5319
        }
5320
        else if (inlist[inpos].vocolflg == VOCS_HIM)
5321
        {
5322
            err = vocsetit(ctx, ctx->voccxhim, accprop, cmdActor, cmdVerb,
5323
                           cmdPrep, &outlist[outpos], "him", VOCW_HIM,
5324
                           defprop, silent);
5325
            if (err != 0)
5326
                goto done;
5327
            ++outpos;
5328
        }
5329
        else if (inlist[inpos].vocolflg == VOCS_HER)
5330
        {
5331
            err = vocsetit(ctx, ctx->voccxher, accprop, cmdActor, cmdVerb,
5332
                           cmdPrep, &outlist[outpos], "her", VOCW_HER,
5333
                           defprop, silent);
5334
            if (err != 0)
5335
                goto done;
5336
            ++outpos;
5337
        }
5338
        else if (inlist[inpos].vocolflg == VOCS_THEM)
5339
        {
5340
            int i;
5341
            int thempos = outpos;
5342
            static char them_name[] = "them";
5343
5344
            for (i = 0 ; i < ctx->voccxthc ; ++i)
5345
            {
5346
                if (outpos >= VOCMAXAMBIG)
5347
                {
5348
                    if (!silent)
5349
                        vocerr(ctx, VOCERR(11),
5350
                               "You're referring to too many objects.");
5351
                    err = VOCERR(11);
5352
                    goto done;
5353
                }
5354
                
5355
                /* add object only if it's still accessible */
5356
                if (vocchkaccess(ctx, ctx->voccxthm[i], accprop, 0,
5357
                                 cmdActor, cmdVerb))
5358
                {
5359
                    /* it's still accessible - add it to the list */
5360
                    vocout(&outlist[outpos++], ctx->voccxthm[i], VOCS_THEM,
5361
                           them_name, them_name);
5362
                }
5363
                else
5364
                {
5365
                    /* it's not accessible - complain about it */
5366
                    vocnoreach(ctx, &ctx->voccxthm[i], 1,
5367
                               cmdActor, cmdVerb, cmdPrep,
5368
                               defprop, TRUE, VOCS_THEM, i, ctx->voccxthc);
5369
                    tioflush(ctx->voccxtio);
5370
                }
5371
            }
5372
            
5373
            /* make sure we found at least one acceptable object  */
5374
            if (outpos == thempos)
5375
            {
5376
                if (!silent)
5377
                    vocerr(ctx, VOCERR(14),
5378
                           "I don't know what you're referring to.");
5379
                err = VOCERR(14);
5380
                goto done;
5381
            }
5382
        }
5383
        else if (inlist[inpos].vocolflg == VOCS_ALL)
5384
        {
5385
            uchar    *l;
5386
            int       exccnt = 0;
5387
            int       allpos = outpos;
5388
            int       k;
5389
            uint      len;
5390
            static    char all_name[] = "all";
5391
            vocoldef *exclist;
5392
            vocoldef *exclist2;
5393
5394
            VOC_MAX_ARRAY(ctx, vocoldef, exclist);
5395
            VOC_MAX_ARRAY(ctx, vocoldef, exclist2);
5396
5397
            if (defprop != PRP_IODEFAULT)
5398
                runpobj(ctx->voccxrun, otherobj);
5399
            runpobj(ctx->voccxrun, cmdPrep);
5400
            runpobj(ctx->voccxrun, cmdActor);
5401
            runppr(ctx->voccxrun, cmdVerb, defprop,
5402
                   defprop == PRP_DODEFAULT ? 3 : 2);
5403
            
5404
            if (runtostyp(ctx->voccxrun) == DAT_LIST)
5405
            {
5406
                l = runpoplst(ctx->voccxrun);
5407
                len = osrp2(l) - 2;
5408
                l += 2;
5409
                
5410
                while (len)
5411
                {
5412
                    /* add list element to output if it's an object */
5413
                    if (*l == DAT_OBJECT)
5414
                        vocout(&outlist[outpos++], (objnum)osrp2(l+1), 0,
5415
                               all_name, all_name);
5416
5417
                    /* move on to next list element */
5418
                    lstadv(&l, &len);
5419
                }
5420
                
5421
                vocout(&outlist[outpos], MCMONINV, 0, (char *)0, (char *)0);
5422
            }
5423
            else
5424
                rundisc(ctx->voccxrun);           /* discard non-list value */
5425
5426
            /* if we didn't get anything, complain about it and quit */
5427
            if (outpos <= allpos)
5428
            {
5429
                if (!silent)
5430
                    vocerr(ctx, VOCERR(15),
5431
                           "I don't see what you're referring to.");
5432
                err = VOCERR(15);
5433
                goto done;
5434
            }
5435
5436
            /* remove any items in "except" list */
5437
            while (inlist[inpos + 1].vocolflg & VOCS_EXCEPT)
5438
            {
5439
                OSCPYSTRUCT(exclist[exccnt], inlist[++inpos]);
5440
                exclist[exccnt++].vocolflg &= ~VOCS_EXCEPT;
5441
            }
5442
            exclist[exccnt].vocolobj = MCMONINV;
5443
            exclist[exccnt].vocolflg = 0;
5444
5445
            /* disambiguate "except" list */
5446
            if (exccnt)
5447
            {
5448
                err = vocdisambig(ctx, exclist2, exclist, defprop, accprop,
5449
                                  verprop, cmd, otherobj, cmdActor,
5450
                                  cmdVerb, cmdPrep, cmdbuf, silent);
5451
                if (err != 0)
5452
                    goto done;
5453
5454
                exccnt = voclistlen(exclist2);
5455
                for (k = 0 ; k < exccnt ; ++k)
5456
                {
5457
                    int i;
5458
                    for (i = allpos ; i < outpos ; ++i)
5459
                    {
5460
                        if (outlist[i].vocolobj == exclist2[k].vocolobj)
5461
                        {
5462
                            int j;
5463
                            for (j = i ; j < outpos ; ++j)
5464
                                outlist[j].vocolobj = outlist[j+1].vocolobj;
5465
                            --i;
5466
                            --outpos;
5467
                            if (outpos <= allpos)
5468
                            {
5469
                                if (!silent)
5470
                                    vocerr(ctx,  VOCERR(15),
5471
                                      "I don't see what you're referring to.");
5472
                                err = VOCERR(15);
5473
                                goto done;
5474
                            }
5475
                        }
5476
                    }
5477
                }
5478
            }
5479
        }
5480
        else                         /* we have a (possibly ambiguous) noun */
5481
        {
5482
            int       lpos = inpos;
5483
            int       i = 0;
5484
            int       cnt;
5485
            char     *p;
5486
            int       cnt2, cnt3;
5487
            int       trying_again;
5488
            int       user_count = 0;
5489
            objnum   *cantreach_list;
5490
            int       unknown_count;
5491
            int       use_all_objs;
5492
            objnum   *list1;
5493
            uint     *flags1;
5494
            objnum   *list2;
5495
            uint     *flags2;
5496
            objnum   *list3;
5497
            uint     *flags3;
5498
            char     *usrobj;
5499
            uchar    *lstbuf;
5500
            char     *newobj;
5501
            char     *disnewbuf;
5502
            char     *disbuffer;
5503
            char    **diswordlist;
5504
            int      *distypelist;
5505
            vocoldef *disnounlist;
5506
            int       dst;
5507
5508
            VOC_MAX_ARRAY(ctx, objnum,   list1);
5509
            VOC_MAX_ARRAY(ctx, objnum,   list2);
5510
            VOC_MAX_ARRAY(ctx, objnum,   list3);
5511
            VOC_MAX_ARRAY(ctx, uint,     flags1);
5512
            VOC_MAX_ARRAY(ctx, uint,     flags2);
5513
            VOC_MAX_ARRAY(ctx, uint,     flags3);
5514
            VOC_MAX_ARRAY(ctx, vocoldef, disnounlist);
5515
            VOC_STK_ARRAY(ctx, char,     disnewbuf,   VOCBUFSIZ);
5516
            VOC_STK_ARRAY(ctx, char,     disbuffer,   2*VOCBUFSIZ);
5517
            VOC_STK_ARRAY(ctx, char *,   diswordlist, VOCBUFSIZ);
5518
            VOC_STK_ARRAY(ctx, int,      distypelist, VOCBUFSIZ);
5519
            VOC_STK_ARRAY(ctx, char,     usrobj,      VOCBUFSIZ);
5520
            VOC_STK_ARRAY(ctx, char,     newobj,      VOCBUFSIZ);
5521
            VOC_STK_ARRAY(ctx, uchar,    lstbuf,      2 + VOCMAXAMBIG*3);
5522
5523
            /* presume we won't resolve any unknown words */
5524
            unknown_count = 0;
5525
5526
            /*
5527
             *   Presume that we won't use all the objects that match
5528
             *   these words, since we normally want to try to find a
5529
             *   single, unambiguous match for a given singular noun
5530
             *   phrase.  Under certain circumstances, we'll want to keep
5531
             *   all of the words that match the noun phrase, in which
5532
             *   case we'll set this flag accordingly. 
5533
             */
5534
            use_all_objs = FALSE;
5535
5536
            /* 
5537
             *   go through the objects matching the current noun phrase
5538
             *   and add them into our list 
5539
             */
5540
            while (inlist[lpos].vocolfst == inlist[inpos].vocolfst
5541
                   && lpos < listlen)
5542
            {
5543
                /* add this object to the list of nouns */
5544
                list1[i] = inlist[lpos].vocolobj;
5545
5546
                /* 
5547
                 *   note whether this object matched a plural, whether it
5548
                 *   matched adjective-at-end usage, and whether it
5549
                 *   matched a truncated dictionary word 
5550
                 */
5551
                flags1[i] = inlist[lpos].vocolflg
5552
                    & (VOCS_PLURAL | VOCS_ANY | VOCS_COUNT
5553
                       | VOCS_ENDADJ | VOCS_TRUNC);
5554
5555
                /* if this is a valid object, count it */
5556
                if (list1[i] != MCMONINV)
5557
                    ++i;
5558
5559
                /* if there's a user count, note it */
5560
                if ((inlist[lpos].vocolflg & VOCS_COUNT) != 0)
5561
                    user_count = atoi(inlist[lpos].vocolfst);
5562
5563
                /* if an unknown word was involved, note it */
5564
                if ((inlist[lpos].vocolflg & VOCS_UNKNOWN) != 0)
5565
                    ++unknown_count;
5566
5567
                /* move on to the next entry */
5568
                ++lpos;
5569
            }
5570
5571
            /* terminate the list */
5572
            list1[i] = MCMONINV;
5573
            cnt = i;
5574
5575
            /*
5576
             *   If this noun phrase contained an unknown word, check to
5577
             *   see if the verb defines the parseUnknownXobj() method.
5578
             *   If so, call the method and check the result. 
5579
             */
5580
            if (unknown_count > 0)
5581
            {
5582
                prpnum prp;
5583
5584
                /* 
5585
                 *   figure out which method to call - use
5586
                 *   parseUnknownDobj if we're disambiguating the direct
5587
                 *   object, parseUnknownIobj for the indirect object 
5588
                 */
5589
                prp = (defprop == PRP_DODEFAULT
5590
                       ? PRP_PARSEUNKNOWNDOBJ : PRP_PARSEUNKNOWNIOBJ);
5591
5592
                /* check if the verb defines this method */
5593
                if (objgetap(ctx->voccxmem, cmdVerb, prp, (objnum *)0, FALSE))
5594
                {
5595
                    uchar *lstp;
5596
                    uint lstlen;
5597
5598
                    /* trace the event for debugging */
5599
                    if (ctx->voccxflg & VOCCXFDBG)
5600
                        tioputs(ctx->voccxtio,
5601
                                "... unknown word: calling "
5602
                                "parseUnknownXobj\\n");
5603
5604
                    /* push the list of words in the noun phrase */
5605
                    voc_push_strlist(ctx, inlist[inpos].vocolfst,
5606
                                     inlist[inpos].vocollst);
5607
5608
                    /* push the other arguments */
5609
                    runpobj(ctx->voccxrun, otherobj);
5610
                    runpobj(ctx->voccxrun, cmdPrep);
5611
                    runpobj(ctx->voccxrun, cmdActor);
5612
5613
                    /* call the method */
5614
                    runppr(ctx->voccxrun, cmdVerb, prp, 4);
5615
5616
                    /* see what they returned */
5617
                    switch(runtostyp(ctx->voccxrun))
5618
                    {
5619
                    case DAT_OBJECT:
5620
                        /* 
5621
                         *   use the object they returned as the match for
5622
                         *   the noun phrase 
5623
                         */
5624
                        list1[cnt++] = runpopobj(ctx->voccxrun);
5625
5626
                        /* terminate the new list */
5627
                        list1[cnt] = MCMONINV;
5628
                        break;
5629
5630
                    case DAT_LIST:
5631
                        /*
5632
                         *   use the list of objects they returned as the
5633
                         *   match for the noun phrase 
5634
                         */
5635
                        lstp = runpoplst(ctx->voccxrun);
5636
5637
                        /* get the length of the list */
5638
                        lstlen = osrp2(lstp) - 2;
5639
                        lstp += 2;
5640
5641
                        /* run through the list's elements */
5642
                        while (lstlen != 0)
5643
                        {
5644
                            /* if this is an object, add it */
5645
                            if (*lstp == DAT_OBJECT
5646
                                && i < VOCMAXAMBIG)
5647
                                list1[cnt++] = osrp2(lstp+1);
5648
                            
5649
                            /* move on to the next element */
5650
                            lstadv(&lstp, &lstlen);
5651
                        }
5652
5653
                        /* 
5654
                         *   Note that we want to use all of these objects
5655
                         *   without disambiguation, since the game code
5656
                         *   has explicitly said that this is the list
5657
                         *   that matches the given noun phrase. 
5658
                         */
5659
                        use_all_objs = TRUE;
5660
                        
5661
                        /* terminate the new list */
5662
                        list1[cnt] = MCMONINV;
5663
                        break;
5664
5665
                    case DAT_TRUE:
5666
                        /*
5667
                         *   A 'true' return value indicates that the
5668
                         *   parseUnknownXobj routine has fully handled
5669
                         *   the command.  They don't want anything more
5670
                         *   to be done with these words.  Simply remove
5671
                         *   the unknown words and continue with any other
5672
                         *   words in the list.  
5673
                         */
5674
                        rundisc(ctx->voccxrun);
5675
5676
                        /* we're done with this input phrase */
5677
                        continue;
5678
5679
                    default:
5680
                        /*
5681
                         *   For anything else, use the default mechanism.
5682
                         *   Simply return an error; since the "unknown
5683
                         *   word" flag is set, we'll reparse the
5684
                         *   sentence, this time rejecting unknown words
5685
                         *   from the outset.
5686
                         *   
5687
                         *   Return error 2, since that's the generic "I
5688
                         *   don't know the word..." error code.  
5689
                         */
5690
                        rundisc(ctx->voccxrun);
5691
                        err = VOCERR(2);
5692
                        goto done;
5693
                    }
5694
5695
                    /*
5696
                     *   If we made it this far, it means that they've
5697
                     *   resolved the object for us, so we can consider
5698
                     *   the previously unknown words to be known now. 
5699
                     */
5700
                    ctx->voccxunknown -= unknown_count;
5701
                }
5702
                else
5703
                {
5704
                    /* trace the event for debugging */
5705
                    if (ctx->voccxflg & VOCCXFDBG)
5706
                        tioputs(ctx->voccxtio,
5707
                                "... unknown word: no parseUnknownXobj - "
5708
                                "restarting parsing\\n");
5709
5710
                    /*
5711
                     *   The verb doesn't define this method, so we should
5712
                     *   use the traditional method; simply return
5713
                     *   failure, and we'll reparse the sentence to reject
5714
                     *   the unknown word in the usual fashion.  Return
5715
                     *   error 2, since that's the generic "I don't know
5716
                     *   the word..." error code.  
5717
                     */
5718
                    err = VOCERR(2);
5719
                    goto done;
5720
                }
5721
            }
5722
            
5723
            /*
5724
             *   Use a new method to cut down on the time it will take to
5725
             *   iterate through the verprop's on all of those words.
5726
             *   We'll call the verb's validXoList method - it should
5727
             *   return a list containing all of the valid objects for the
5728
             *   verb (it's sort of a Fourier transform of validDo).
5729
             *   We'll intersect that list with the list we're about to
5730
             *   disambiguate, which should provide a list of objects that
5731
             *   are already qualified, in that validDo should return true
5732
             *   for every one of them.  
5733
             * 
5734
             *   The calling sequence is:
5735
             *       verb.validXoList(actor, prep, otherobj)
5736
             * 
5737
             *   For reverse compatibility, if the return value is nil,
5738
             *   we use the old algorithm and consider all objects
5739
             *   that match the vocabulary.  The return value must be
5740
             *   a list to be considered.
5741
             *
5742
             *   If disambiguating the actor, skip this phase, since
5743
             *   we don't have a verb yet.
5744
             */
5745
            if (accprop != PRP_VALIDACTOR && cnt != 0)
5746
            {
5747
                if (defprop == PRP_DODEFAULT)
5748
                    listprop = PRP_VALDOLIST;
5749
                else
5750
                    listprop = PRP_VALIOLIST;
5751
                
5752
                /* push the arguments:  the actor, prep, and other object */
5753
                runpobj(ctx->voccxrun, otherobj);
5754
                runpobj(ctx->voccxrun, cmdPrep);
5755
                runpobj(ctx->voccxrun, cmdActor);
5756
                runppr(ctx->voccxrun, cmdVerb, listprop, 3);
5757
                if (runtostyp(ctx->voccxrun) == DAT_LIST)
5758
                {
5759
                    uchar *l;
5760
                    uint   len;
5761
                    int    kept_numobj;
5762
5763
                    /* presume we won't keep numObj */
5764
                    kept_numobj = FALSE;
5765
                    
5766
                    /* read the list length prefix, and skip it */
5767
                    l = runpoplst(ctx->voccxrun);
5768
                    len = osrp2(l) - 2;
5769
                    l += 2;
5770
                    
5771
                    /*
5772
                     *   For each element of the return value, see if
5773
                     *   it's in list1.  If so, copy the object into
5774
                     *   list2, unless it's already in list2.  
5775
                     */
5776
                    for (cnt2 = 0 ; len != 0 ; )
5777
                    {
5778
                        if (*l == DAT_OBJECT)
5779
                        {
5780
                            objnum o = osrp2(l+1);
5781
                            
5782
                            for (i = 0 ; i < cnt ; ++i)
5783
                            {
5784
                                if (list1[i] == o)
5785
                                {
5786
                                    int j;
5787
                                    
5788
                                    /* check to see if o is already in list2 */
5789
                                    for (j = 0 ; j < cnt2 ; ++j)
5790
                                        if (list2[j] == o) break;
5791
                                    
5792
                                    /* if o is not in list2 yet, add it */
5793
                                    if (j == cnt2)
5794
                                    {
5795
                                        /* add it */
5796
                                        list2[cnt2] = o;
5797
                                        flags2[cnt2] = flags1[i];
5798
                                        ++cnt2;
5799
5800
                                        /* 
5801
                                         *   if it's numObj, note that
5802
                                         *   we've already included it in
5803
                                         *   the output list, so that we
5804
                                         *   don't add it again later 
5805
                                         */
5806
                                        if (o == ctx->voccxnum)
5807
                                            kept_numobj = TRUE;
5808
                                    }
5809
                                    break;
5810
                                }
5811
                            }
5812
                        }
5813
                        
5814
                        /* move on to next element */
5815
                        lstadv(&l, &len);
5816
                    }
5817
5818
                    /*
5819
                     *   If the original list included numObj, keep it in
5820
                     *   the accessible list for now - we consider numObj
5821
                     *   to be always accessible.  The noun phrase matcher
5822
                     *   will include numObj whenever the player enters a
5823
                     *   single number as a noun phrase, even when the
5824
                     *   number matches an object.  Note that we can skip
5825
                     *   this special step if we already kept numObj in
5826
                     *   the valid list.  
5827
                     */
5828
                    if (!kept_numobj)
5829
                    {
5830
                        /* search the original list for numObj */
5831
                        for (i = 0 ; i < cnt ; ++i)
5832
                        {
5833
                            /* if this original entry is numObj, keep it */
5834
                            if (list1[i] == ctx->voccxnum)
5835
                            {
5836
                                /* keep it in the accessible list */
5837
                                list2[cnt2++] = ctx->voccxnum;
5838
                                
5839
                                /* no need to look any further */
5840
                                break;
5841
                            }
5842
                        }
5843
                    }
5844
                    
5845
                    /* copy list2 into list1 */
5846
                    memcpy(list1, list2, (size_t)(cnt2 * sizeof(list1[0])));
5847
                    memcpy(flags1, flags2, (size_t)cnt2 * sizeof(flags1[0]));
5848
                    cnt = cnt2;
5849
                    list1[cnt] = MCMONINV;
5850
                }
5851
                else
5852
                    rundisc(ctx->voccxrun);
5853
            }
5854
5855
            /*
5856
             *   Determine accessibility and visibility.  First, limit
5857
             *   list1 to those objects that are visible OR accessible,
5858
             *   and limit list3 to those objects that are visible.  
5859
             */
5860
            for (cnt = cnt3 = i = 0 ; list1[i] != MCMONINV ; ++i)
5861
            {
5862
                int is_vis;
5863
                int is_acc;
5864
5865
                /* determine if the object is visible */
5866
                is_vis = vocchkvis(ctx, list1[i], cmdActor);
5867
5868
                /* determine if it's accessible */
5869
                is_acc = vocchkaccess(ctx, list1[i], accprop, i,
5870
                                      cmdActor, cmdVerb);
5871
5872
                /* keep items that are visible OR accessible in list1 */
5873
                if (is_acc || is_vis)
5874
                {
5875
                    list1[cnt] = list1[i];
5876
                    flags1[cnt] = flags1[i];
5877
                    ++cnt;
5878
                }
5879
                
5880
                /* 
5881
                 *   put items that are visible (regardless of whether or
5882
                 *   not they're accessible) in list3
5883
                 */
5884
                if (is_vis)
5885
                {
5886
                    list3[cnt3] = list1[i];
5887
                    flags3[cnt3] = flags1[i];
5888
                    ++cnt3;
5889
                }
5890
            }
5891
5892
            /*
5893
             *   If some of our accessible objects matched with an
5894
             *   adjective at the end of the noun phrase, and others
5895
             *   didn't (i.e., the others matched with a noun or plural at
5896
             *   the end of the noun phrase), eliminate the ones that
5897
             *   matched with an adjective at the end.  Ending a noun
5898
             *   phrase with an adjective is really a kind of short-hand;
5899
             *   if we have matches for both the full name version (with a
5900
             *   noun at the end) and a short-hand version, we want to
5901
             *   discard the short-hand version so that we don't treat it
5902
             *   as ambiguous with the long-name version.  Likewise, if we
5903
             *   have some exact matches and some truncations, keep only
5904
             *   the exact matches.  
5905
             */
5906
            cnt = voc_prune_matches(ctx, list1, flags1, cnt);
5907
            cnt3 = voc_prune_matches(ctx, list3, flags3, cnt3);
5908
5909
            /*
5910
             *   Now, reduce list1 to objects that are accessible.  The
5911
             *   reason for this multi-step process is to ensure that we
5912
             *   prune the list with respect to every object in scope
5913
             *   (visible or accessible for the verb), so that we get the
5914
             *   most sensible pruning behavior.  This is more sensible
5915
             *   than pruning by accessibility only, because sometimes we
5916
             *   may have objects that are visible but are not accessible;
5917
             *   as far as the player is concerned, the visible objects
5918
             *   are part of the current location, so the player should be
5919
             *   able to refer to them regardless of whether they're
5920
             *   accessible.  
5921
             */
5922
            for (dst = 0, i = 0 ; i < cnt ; ++i)
5923
            {
5924
                /* check this object for accessibility */
5925
                if (vocchkaccess(ctx, list1[i], accprop, i,
5926
                                 cmdActor, cmdVerb))
5927
                {
5928
                    /* keep it in the final list */
5929
                    list1[dst] = list1[i];
5930
                    flags1[dst] = flags1[i];
5931
                    
5932
                    /* count the new list entry */
5933
                    ++dst;
5934
                }
5935
            }
5936
5937
            /* terminate list1 */
5938
            cnt = dst;
5939
            list1[dst] = MCMONINV;
5940
5941
            /*
5942
             *   Go through the list of accessible objects, and perform
5943
             *   the sensible-object (verXoVerb) check on each.  Copy each
5944
             *   sensible object to list2.  
5945
             */
5946
            for (i = 0, cnt2 = 0 ; i < cnt ; ++i)
5947
            {
5948
                /* run it by the appropriate sensible-object check */
5949
                if (accprop == PRP_VALIDACTOR)
5950
                {
5951
                    /* run it through preferredActor */
5952
                    runppr(ctx->voccxrun, list1[i], PRP_PREFACTOR, 0);
5953
                    if (runpoplog(ctx->voccxrun))
5954
                    {
5955
                        list2[cnt2] = list1[i];
5956
                        flags2[cnt2] = flags1[i];
5957
                        ++cnt2;
5958
                    }
5959
                }
5960
                else
5961
                {
5962
                    /* run it through verXoVerb */
5963
                    tiohide(ctx->voccxtio);
5964
                    if (otherobj != MCMONINV)
5965
                        runpobj(ctx->voccxrun, otherobj);
5966
                    runpobj(ctx->voccxrun, cmdActor);
5967
                    runppr(ctx->voccxrun, list1[i], verprop,
5968
                           (otherobj != MCMONINV ? 2 : 1));
5969
                    
5970
                    /*
5971
                     *   If that didn't result in a message, this object
5972
                     *   passed the tougher test of ver?oX, so include it
5973
                     *   in list2.  
5974
                     */
5975
                    if (!tioshow(ctx->voccxtio))
5976
                    {
5977
                        list2[cnt2] = list1[i];
5978
                        flags2[cnt2] = flags1[i];
5979
                        ++cnt2;
5980
                    }
5981
                }
5982
            }
5983
5984
            /*
5985
             *   Construct a string consisting of the words the user typed
5986
             *   to reference this object, in case we need to complain.
5987
             */
5988
            usrobj[0] = '\0';
5989
            if (inlist[inpos].vocolfst != 0 && inlist[inpos].vocollst != 0)
5990
            {
5991
                for (p = inlist[inpos].vocolfst ; p <= inlist[inpos].vocollst
5992
                     ; p += strlen(p) + 1)
5993
                {
5994
                    /* add a space if we have a prior word */
5995
                    if (usrobj[0] != '\0')
5996
                    {
5997
                        /* quote the space if the last word ended with '.' */
5998
                        if (p[strlen(p)-1] == '.')
5999
                            strcat(usrobj, "\\");
6000
6001
                        /* add the space */
6002
                        strcat(usrobj, " ");
6003
                    }
6004
6005
                    /* add the current word, or "of" if it's "of" */
6006
                    if (voc_check_special(ctx, p, VOCW_OF))
6007
                        vocaddof(ctx, usrobj);
6008
                    else
6009
                        strcat(usrobj, p);
6010
                }
6011
            }
6012
6013
            /*
6014
             *   If there's nothing in the YES list, and we have just a
6015
             *   single number as our word, act as though they are talking
6016
             *   about the number itself, rather than one of the objects
6017
             *   that happened to use the number -- none of those objects
6018
             *   make any sense, it seems, so fall back on the number.
6019
             *   
6020
             *   Note that we may also have only numObj in the YES list,
6021
             *   because the noun phrase parser normally adds numObj when
6022
             *   the player types a noun phrase consisting only of a
6023
             *   number.  Do the same thing in this case -- just return
6024
             *   the number object.  
6025
             */
6026
            if ((cnt2 == 0
6027
                 || (cnt2 == 1 && list2[0] == ctx->voccxnum))
6028
                && inlist[inpos].vocolfst != 0
6029
                && inlist[inpos].vocolfst == inlist[inpos].vocollst
6030
                && vocisdigit(*inlist[inpos].vocolfst))
6031
            {
6032
                long  v1;
6033
                char  vbuf[4];
6034
6035
                v1 = atol(inlist[inpos].vocolfst);
6036
                oswp4(vbuf, v1);
6037
                vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, vbuf,
6038
                          &inlist[inpos], &outlist[outpos]);
6039
                outlist[outpos].vocolflg = VOCS_NUM;
6040
                ++outpos;
6041
6042
                /* skip all objects that matched the number */
6043
                for ( ; inlist[inpos+1].vocolobj != MCMONINV
6044
                      && inlist[inpos+1].vocolfst == inlist[inpos].vocolfst
6045
                      ; ++inpos) ;
6046
                continue;
6047
            }
6048
6049
            /*
6050
             *   Check if we found anything in either the YES (list2) or
6051
             *   MAYBE (list1) lists.  If there's nothing in either list,
6052
             *   complain and return.  
6053
             */
6054
            if (cnt2 == 0 && cnt == 0)
6055
            {
6056
                /*
6057
                 *   We have nothing sensible, and nothing even
6058
                 *   accessible.  If there's anything merely visible,
6059
                 *   complain about those items. 
6060
                 */
6061
                if (cnt3 != 0)
6062
                {
6063
                    /* there are visible items - complain about them */
6064
                    cnt = cnt3;
6065
                    cantreach_list = list3;
6066
                    noreach = TRUE;
6067
6068
                    /* give the cantReach message, even for multiple objects */
6069
                    goto noreach1;
6070
                }
6071
                else
6072
                {
6073
                    /* 
6074
                     *   explain that there's nothing visible or
6075
                     *   accessible matching the noun phrase, and abort
6076
                     *   the command with an error 
6077
                     */
6078
                    if (!silent)
6079
                        vocerr(ctx, VOCERR(9),
6080
                               "I don't see any %s here.", usrobj);
6081
                    err = VOCERR(9);
6082
                    goto done;
6083
                }
6084
            }
6085
6086
            /*
6087
             *   If anything passed the stronger test (objects passing are
6088
             *   in list2), use this as our proposed resolution for the
6089
             *   noun phrase.  If nothing passed the stronger test (i.e.,
6090
             *   list2 is empty), simply keep the list of accessible
6091
             *   objects in list1. 
6092
             */
6093
            if (cnt2 != 0)
6094
            {
6095
                /* 
6096
                 *   we have items passing the stronger test -- copy the
6097
                 *   stronger list (list2) to list1 
6098
                 */
6099
                cnt = cnt2;
6100
                memcpy(list1, list2, (size_t)(cnt2 * sizeof(list1[0])));
6101
                memcpy(flags1, flags2, (size_t)(cnt2 * sizeof(flags1[0])));
6102
            }
6103
6104
            /*
6105
             *   Check for redundant objects in the list.  If the same
6106
             *   object appears multiple times in the list, remove the
6107
             *   extra occurrences.  Sometimes, a game can inadvertantly
6108
             *   define the same vocabulary word several times for the
6109
             *   same object, because of the parser's leniency with
6110
             *   matching leading substrings of 6 characters or longer.
6111
             *   To avoid unnecessary "which x do you mean..." errors,
6112
             *   simply discard any duplicates in the list.  
6113
             */
6114
            for (dst = 0, i = 0 ; i < cnt ; ++i)
6115
            {
6116
                int dup;
6117
                int j;
6118
                
6119
                /* presume we won't find a duplicate of this object */
6120
                dup = FALSE;
6121
                
6122
                /* 
6123
                 *   look for duplicates of this object in the remainder
6124
                 *   of the list 
6125
                 */
6126
                for (j = i + 1 ; j < cnt ; ++j)
6127
                {
6128
                    /* check for a duplicate */
6129
                    if (list1[i] == list1[j])
6130
                    {
6131
                        /* note that this object has a duplicate */
6132
                        dup = TRUE;
6133
                        
6134
                        /* we don't need to look any further */
6135
                        break;
6136
                    }
6137
                }
6138
6139
                /* 
6140
                 *   if this object has no duplicate, retain it in the
6141
                 *   output list 
6142
                 */
6143
                if (!dup)
6144
                {
6145
                    /* copy the element to the output */
6146
                    list1[dst] = list1[i];
6147
                    flags1[dst] = flags1[i];
6148
                    
6149
                    /* count the output */
6150
                    ++dst;
6151
                }
6152
            }
6153
            
6154
            /* update the count to the new list's size */
6155
            cnt = dst;
6156
            list1[cnt] = MCMONINV;
6157
6158
            /* 
6159
             *   If we have more than one object in the list, and numObj
6160
             *   is still in the list, remove numObj - we don't want to
6161
             *   consider numObj to be considered ambiguous with another
6162
             *   object when the other object passes access and validation
6163
             *   tests.
6164
             */
6165
            if (cnt > 1)
6166
            {
6167
                /* scan the list for numObj */
6168
                for (i = 0, dst = 0 ; i < cnt ; ++i)
6169
                {
6170
                    /* if this isn't numObj, keep this element */
6171
                    if (list1[i] != ctx->voccxnum)
6172
                        list1[dst++] = list1[i];
6173
                }
6174
6175
                /* update the final count */
6176
                cnt = dst;
6177
                list1[cnt] = MCMONINV;
6178
            }
6179
6180
            /*
6181
             *   Check for a generic numeric adjective ('#' in the
6182
             *   adjective list for the object) in each object.  If we
6183
             *   find it, we need to make sure there's a number in the
6184
             *   name of the object.  
6185
             */
6186
            for (i = 0 ; i < cnt ; ++i)
6187
            {
6188
                if (has_gen_num_adj(ctx, list1[i]))
6189
                {
6190
                    /*
6191
                     *   If they specified a count, create the specified
6192
                     *   number of objects.  Otherwise, if the object is
6193
                     *   plural, they mean to use all of the objects, so a
6194
                     *   numeric adjective isn't required -- set the
6195
                     *   numeric adjective property in the object to nil
6196
                     *   to so indicate.  Otherwise, look for the number,
6197
                     *   and set the numeric adjective property
6198
                     *   accordingly.  
6199
                     */
6200
                    if ((flags1[i] & (VOCS_ANY | VOCS_COUNT)) != 0)
6201
                    {
6202
                        int     n = (user_count ? user_count : 1);
6203
                        int     j;
6204
                        objnum  objn = list1[i];
6205
                        
6206
                        /* 
6207
                         *   They specified a count, so we want to create
6208
                         *   n-1 copies of the numbered object.  Make room
6209
                         *   for the n-1 new copies of this object by
6210
                         *   shifting any elements that follow up n-1
6211
                         *   slots.  
6212
                         */
6213
                        if (i + 1 != cnt && n > 1)
6214
                        {
6215
                            memmove(&list1[i + n - 1], &list1[i],
6216
                                    (cnt - i) * sizeof(list1[i]));
6217
                            memmove(&flags1[i + n - 1], &flags1[i],
6218
                                    (cnt - i) * sizeof(flags1[i]));
6219
                        }
6220
                        
6221
                        /* create n copies of this object */
6222
                        for (j = 0 ; j < n ; ++j)
6223
                        {
6224
                            long l;
6225
                            
6226
                            /* 
6227
                             *   Generate a number for the new object,
6228
                             *   asking the object to tell us what value
6229
                             *   to use for an "any".  
6230
                             */
6231
                            runpnum(ctx->voccxrun, (long)(j + 1));
6232
                            runppr(ctx->voccxrun, objn, PRP_ANYVALUE, 1);
6233
                            l = runpopnum(ctx->voccxrun);
6234
                            
6235
                            /* try creating the new object */
6236
                            list1[i+j] =
6237
                                voc_new_num_obj(ctx, objn,
6238
                                                cmdActor, cmdVerb,
6239
                                                l, FALSE);
6240
                            if (list1[i+j] == MCMONINV)
6241
                            {
6242
                                err = VOCERR(40);
6243
                                goto done;
6244
                            }
6245
                        }
6246
                    }
6247
                    else if ((flags1[i] & VOCS_PLURAL) != 0)
6248
                    {
6249
                        /*
6250
                         *   get the plural object by asking for the
6251
                         *   numbered object with a nil number parameter 
6252
                         */
6253
                        list1[i] =
6254
                            voc_new_num_obj(ctx, list1[i], cmdActor, cmdVerb,
6255
                                            (long)0, TRUE);
6256
                        if (list1[i] == MCMONINV)
6257
                        {
6258
                            err = VOCERR(40);
6259
                            goto done;
6260
                        }
6261
                    }
6262
                    else
6263
                    {
6264
                        char *p;
6265
                        int   found;
6266
                        
6267
                        /* 
6268
                         *   No plural, no "any" - we just want to create
6269
                         *   one numbered object, using the number that
6270
                         *   the player must have specified.  Make sure
6271
                         *   the player did, in fact, specify a number. 
6272
                         */
6273
                        for (found = FALSE, p = inlist[inpos].vocolfst ;
6274
                             p != 0 && p <= inlist[inpos].vocollst ;
6275
                             p += strlen(p) + 1)
6276
                        {
6277
                            /* did we find it? */
6278
                            if (vocisdigit(*p))
6279
                            {
6280
                                long l;
6281
6282
                                /* get the number */
6283
                                l = atol(p);
6284
                                
6285
                                /* create the object with this number */
6286
                                list1[i] = voc_new_num_obj(ctx, list1[i],
6287
                                    cmdActor, cmdVerb,
6288
                                    l, FALSE);
6289
                                if (list1[i] == MCMONINV)
6290
                                {
6291
                                    err = VOCERR(40);
6292
                                    goto done;
6293
                                }
6294
                                
6295
                                /* the command looks to be valid */
6296
                                found = TRUE;
6297
                                break;
6298
                            }
6299
                        }
6300
                        
6301
                        /* if we didn't find it, stop now */
6302
                        if (!found)
6303
                        {
6304
                            if (!silent)
6305
                                vocerr(ctx, VOCERR(160),
6306
                    "You'll have to be more specific about which %s you mean.",
6307
                                       usrobj);
6308
                            err = VOCERR(160);
6309
                            goto done;
6310
                        }
6311
                    }
6312
                }
6313
            }
6314
6315
            /*
6316
             *   We still have an ambiguous word - ask the user which of
6317
             *   the possible objects they meant to use 
6318
             */
6319
            trying_again = FALSE;
6320
            for (;;)
6321
            {
6322
                int    wrdcnt;
6323
                int    next;
6324
                uchar *p;
6325
                int    cleared_noun;
6326
                int    diff_cnt;
6327
                int    stat;
6328
                int    num_wanted;
6329
                int    is_ambig;
6330
                int    all_plural;
6331
                    
6332
                /* 
6333
                 *   check for usage - determine if we have singular
6334
                 *   definite, singular indefinite, counted, or plural
6335
                 *   usage 
6336
                 */
6337
                if ((flags1[0] & (VOCS_PLURAL | VOCS_ANY | VOCS_COUNT)) != 0)
6338
                {
6339
                    int i;
6340
6341
                    /* 
6342
                     *   loop through the objects to AND together the
6343
                     *   flags from all of the objects; we only care about
6344
                     *   the plural flags (PLURAL, ANY, and COUNT), so
6345
                     *   start out with only those, then AND off any that
6346
                     *   aren't in all of the objects 
6347
                     */
6348
                    for (all_plural = VOCS_PLURAL | VOCS_ANY | VOCS_COUNT,
6349
                         i = 0 ; i < cnt ; ++i)
6350
                    {
6351
                        /* AND out this object's flags */
6352
                        all_plural &= flags1[i];
6353
                        
6354
                        /* 
6355
                         *   if we've ANDed down to zero, there's no need
6356
                         *   to look any further 
6357
                         */
6358
                        if (!all_plural)
6359
                            break;
6360
                    }
6361
                }
6362
                else
6363
                {
6364
                    /* 
6365
                     *   it looks like we want just a single object -
6366
                     *   clear the various plural flags 
6367
                     */
6368
                    all_plural = 0;
6369
                }
6370
6371
                /*
6372
                 *   Count the distinguishable items.
6373
                 *   
6374
                 *   If we're looking for a single object, don't keep
6375
                 *   duplicate indistinguishable items (i.e., keep only
6376
                 *   one item from each set of mutually indistinguishable
6377
                 *   items), since we could equally well use any single
6378
                 *   one of those items.  If we're looking for multiple
6379
                 *   objects, keep all of the items, since the user is
6380
                 *   referring to all of them. 
6381
                 */
6382
                diff_cnt = voc_count_diff(ctx, list1, flags1, &cnt,
6383
                                          all_plural != 0 || use_all_objs);
6384
6385
                /*
6386
                 *   Determine how many objects we'd like to find.  If we
6387
                 *   have a count specified, we'd like to find the given
6388
                 *   number of objects.  If we have "ANY" specified, we
6389
                 *   just want to pick one object arbitrarily.  If we have
6390
                 *   all plurals, we can keep all of the objects.  If the
6391
                 *   'use_all_objs' flag is true, it means that we can use
6392
                 *   everything in the list.  
6393
                 */
6394
                if (use_all_objs)
6395
                {
6396
                    /* we want to use all of the objects */
6397
                    num_wanted = cnt;
6398
                    is_ambig = FALSE;
6399
                }
6400
                else if ((all_plural & VOCS_COUNT) != 0)
6401
                {
6402
                    /* 
6403
                     *   we have a count - we want exactly the given
6404
                     *   number of objects, but we can pick an arbitrary
6405
                     *   subset, so it's not ambiguous even if we have too
6406
                     *   many at the moment 
6407
                     */
6408
                    num_wanted = user_count;
6409
                    is_ambig = FALSE;
6410
                }
6411
                else if ((all_plural & VOCS_ANY) != 0)
6412
                {
6413
                    /* 
6414
                     *   they specified "any", so we want exactly one, but
6415
                     *   we can pick one arbitrarily, so there's no
6416
                     *   ambiguity 
6417
                     */
6418
                    num_wanted = 1;
6419
                    is_ambig = FALSE;
6420
                }
6421
                else if (all_plural != 0)
6422
                {
6423
                    /* 
6424
                     *   we have a simple plural, so we can use all of the
6425
                     *   provided objects without ambiguity 
6426
                     */
6427
                    num_wanted = cnt;
6428
                    is_ambig = FALSE;
6429
                }
6430
                else
6431
                {
6432
                    /* 
6433
                     *   it's a singular, definite usage, so we want
6434
                     *   exactly one item; if we have more than one in our
6435
                     *   list, it's ambiguous 
6436
                     */
6437
                    num_wanted = 1;
6438
                    is_ambig = (cnt != 1);
6439
                }
6440
6441
                /* call the disambiguation hook */
6442
                stat = voc_disambig_hook(ctx, cmdVerb, cmdActor, cmdPrep,
6443
                                         otherobj, accprop, verprop,
6444
                                         list1, flags1, &cnt,
6445
                                         inlist[inpos].vocolfst,
6446
                                         inlist[inpos].vocollst,
6447
                                         num_wanted, is_ambig, disnewbuf,
6448
                                         silent);
6449
6450
                /* check the status */
6451
                if (stat == VOC_DISAMBIG_DONE)
6452
                {
6453
                    /* that's it - copy the result */
6454
                    for (i = 0 ; i < cnt ; ++i)
6455
                        vocout(&outlist[outpos++], list1[i], flags1[i],
6456
                               inlist[inpos].vocolfst,
6457
                               inlist[inpos].vocollst);
6458
                    
6459
                    /* we're done */
6460
                    break;
6461
                }
6462
                else if (stat == VOC_DISAMBIG_CONT)
6463
                {
6464
                    /* 
6465
                     *   Continue with the new list (which is the same as
6466
                     *   the old list, if it wasn't actually updated by
6467
                     *   the hook routine) - proceed with remaining
6468
                     *   processing, but using the new list.
6469
                     *   
6470
                     *   Because the list has been updated, we must once
6471
                     *   again count the number of distinguishable items,
6472
                     *   since that may have changed.  
6473
                     */
6474
                    diff_cnt = voc_count_diff(ctx, list1, flags1, &cnt, TRUE);
6475
                }
6476
                else if (stat == VOC_DISAMBIG_PARSE_RESP
6477
                         || stat == VOC_DISAMBIG_PROMPTED)
6478
                {
6479
                    /* 
6480
                     *   The status indicates one of the following:
6481
                     *   
6482
                     *   - the hook prompted for more information and read
6483
                     *   a response from the player, but decided not to
6484
                     *   parse it; we will continue with the current list,
6485
                     *   and parse the player's response as provided by
6486
                     *   the hook.
6487
                     *   
6488
                     *   - the hook prompted for more information, but
6489
                     *   left the reading to us.  We'll proceed with the
6490
                     *   current list and read a response as normal, but
6491
                     *   without displaying another prompt.
6492
                     *   
6493
                     *   In any case, just continue processing; we'll take
6494
                     *   appropriate action on the prompting and reading
6495
                     *   when we reach those steps.  
6496
                     */
6497
                }
6498
                else
6499
                {
6500
                    /* anything else is an error */
6501
                    err = VOCERR(41);
6502
                    goto done;
6503
                }
6504
6505
                /*
6506
                 *   If we found only one word, or a plural/ANY, we are
6507
                 *   finished.  If we found a count, use that count if
6508
                 *   possible.  
6509
                 */
6510
                if (cnt == 1 || all_plural || use_all_objs)
6511
                {
6512
                    int flags;
6513
6514
                    /* keep only one of the objects if ANY was used */
6515
                    if ((all_plural & VOCS_COUNT) != 0)
6516
                    {
6517
                        if (user_count > cnt)
6518
                        {
6519
                            if (!silent)
6520
                                vocerr(ctx, VOCERR(30),
6521
                                       "I only see %d of those.", cnt);
6522
                            err = VOCERR(30);
6523
                            goto done;
6524
                        }
6525
                        cnt = user_count;
6526
                        flags = VOCS_ALL;
6527
                    }
6528
                    else if ((all_plural & VOCS_ANY) != 0)
6529
                    {
6530
                        cnt = 1;
6531
                        flags = VOCS_ALL;
6532
                    }
6533
                    else
6534
                        flags = 0;
6535
6536
                    /* put the list */
6537
                    for (i = 0 ; i < cnt ; ++i)
6538
                        vocout(&outlist[outpos++], list1[i], flags,
6539
                               inlist[inpos].vocolfst,
6540
                               inlist[inpos].vocollst);
6541
6542
                    /* we're done */
6543
                    break;
6544
                }
6545
6546
                /* make sure output capturing is off */
6547
                tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE);
6548
                tioclrcapture(ctx->voccxtio);
6549
6550
                /* 
6551
                 *   if we're in "silent" mode, we can't ask the player
6552
                 *   for help, so return an error 
6553
                 */
6554
                if (silent)
6555
                {
6556
                    /* 
6557
                     *   We can't disambiguate the list.  Fill in the
6558
                     *   return list with what's left, which is still
6559
                     *   ambiguous, and note that we need to return an
6560
                     *   error code indicating that the list remains
6561
                     *   ambiguous.  
6562
                     */
6563
                    for (i = 0 ; i < cnt && outpos < VOCMAXAMBIG ; ++i)
6564
                        vocout(&outlist[outpos++], list1[i], 0,
6565
                               inlist[inpos].vocolfst,
6566
                               inlist[inpos].vocollst);
6567
6568
                    /* note that we have ambiguity remaining */
6569
                    still_ambig = TRUE;
6570
6571
                    /* we're done with this sublist */
6572
                    break;
6573
                }
6574
6575
                /*
6576
                 *   We need to prompt for more information interactively.
6577
                 *   Figure out how we're going to display the prompt.
6578
                 *   
6579
                 *   - If the disambigXobj hook status (stat) indicates
6580
                 *   that the hook already displayed a prompt of its own,
6581
                 *   we don't need to add anything here.
6582
                 *   
6583
                 *   - Otherwise, if there's a parseDisambig function
6584
                 *   defined in the game, call it to display the prompt.
6585
                 *   
6586
                 *   - Otherwise, display our default prompt.  
6587
                 */
6588
                if (stat == VOC_DISAMBIG_PARSE_RESP
6589
                    || stat == VOC_DISAMBIG_PROMPTED)
6590
                {
6591
                    /* 
6592
                     *   the disambigXobj hook already asked for a
6593
                     *   response, so don't display any prompt of our own 
6594
                     */
6595
                }
6596
                else if (ctx->voccxpdis != MCMONINV)
6597
                {
6598
                    uint l;
6599
                    
6600
                    /* 
6601
                     *   There's a parseDisambig function defined in the
6602
                     *   game - call it to display the prompt, passing the
6603
                     *   list of possible objects and the player's
6604
                     *   original noun phrase text as parameters.  
6605
                     */
6606
                    for (i = 0, p = lstbuf+2 ; i < cnt ; ++i, p += 2)
6607
                    {
6608
                        *p++ = DAT_OBJECT;
6609
                        oswp2(p, list1[i]);
6610
                    }
6611
                    l = p - lstbuf;
6612
                    oswp2(lstbuf, l);
6613
                    runpbuf(ctx->voccxrun, DAT_LIST, lstbuf);
6614
                    runpstr(ctx->voccxrun, usrobj, (int)strlen(usrobj), 1);
6615
                    runfn(ctx->voccxrun, ctx->voccxpdis, 2);
6616
                }
6617
                else
6618
                {
6619
                    /* display "again" message, if necessary */
6620
                    if (trying_again)
6621
                        vocerr_info(ctx, VOCERR(100), "Let's try it again: ");
6622
6623
                    /* ask the user about it */
6624
                    vocerr_info(ctx, VOCERR(101),
6625
                                "Which %s do you mean, ", usrobj);
6626
                    for (i = 0 ; i < cnt ; )
6627
                    {
6628
                        int    eqcnt;
6629
                        int    j;
6630
                        objnum sc;
6631
                        
6632
                        /*
6633
                         *   See if we have multiple instances of an
6634
                         *   identical object.  All such instances should
6635
                         *   be grouped together (this was done above), so
6636
                         *   we can just count the number of consecutive
6637
                         *   equivalent objects. 
6638
                         */
6639
                        eqcnt = 1;
6640
                        runppr(ctx->voccxrun, list1[i], PRP_ISEQUIV, 0);
6641
                        if (runpoplog(ctx->voccxrun))
6642
                        {
6643
                            /* get the superclass, if possible */
6644
                            sc = objget1sc(ctx->voccxmem, list1[i]);
6645
                            if (sc != MCMONINV)
6646
                            {
6647
                                /* count equivalent objects that follow */
6648
                                for (j = i + 1 ; j < cnt ; ++j)
6649
                                {
6650
                                    if (objget1sc(ctx->voccxmem, list1[j])
6651
                                        == sc)
6652
                                        ++eqcnt;
6653
                                    else
6654
                                        break;
6655
                                }
6656
                            }
6657
                        }
6658
6659
                        /*
6660
                         *   Display this object's name.  If we have only
6661
                         *   one such object, display its thedesc,
6662
                         *   otherwise display its adesc. 
6663
                         */
6664
                        runppr(ctx->voccxrun, list1[i],
6665
                               (prpnum)(eqcnt == 1 ?
6666
                                        PRP_THEDESC : PRP_ADESC), 0);
6667
6668
                        /* display the separator as appropriate */
6669
                        if (i + 1 < diff_cnt)
6670
                            vocerr_info(ctx, VOCERR(102), ", ");
6671
                        if (i + 2 == diff_cnt)
6672
                            vocerr_info(ctx, VOCERR(103), "or ");
6673
6674
                        /* skip all equivalent items */
6675
                        i += eqcnt;
6676
                    }
6677
                    vocerr_info(ctx, VOCERR(104), "?");
6678
                }
6679
6680
                /* 
6681
                 *   Read the response.  If the disambigXobj hook already
6682
                 *   read the response, we don't need to read anything
6683
                 *   more. 
6684
                 */
6685
                if (stat != VOC_DISAMBIG_PARSE_RESP
6686
                    && vocread(ctx, cmdActor, cmdVerb, disnewbuf,
6687
                               (int)VOCBUFSIZ, 2) == VOCREAD_REDO)
6688
                {
6689
                    /* they want to treat the input as a new command */
6690
                    strcpy(cmdbuf, disnewbuf);
6691
                    ctx->voccxunknown = 0;
6692
                    ctx->voccxredo = TRUE;
6693
                    err = VOCERR(43);
6694
                    goto done;
6695
                }
6696
6697
                /*
6698
                 *   parse the response 
6699
                 */
6700
6701
                /* tokenize the list */
6702
                wrdcnt = voctok(ctx, disnewbuf, disbuffer, diswordlist,
6703
                                TRUE, TRUE, TRUE);
6704
                if (wrdcnt == 0)
6705
                {
6706
                    /* empty response - run pardon() function and abort */
6707
                    runfn(ctx->voccxrun, ctx->voccxprd, 0);
6708
                    err = VOCERR(42);
6709
                    goto done;
6710
                }
6711
                if (wrdcnt < 0)
6712
                {
6713
                    /* return the generic punctuation error */
6714
                    err = VOCERR(1);
6715
                    goto done;
6716
                }
6717
6718
                /*
6719
                 *   Before we tokenize the sentence, remember the current
6720
                 *   unknown word count, then momentarily set the count to
6721
                 *   zero.  This will cause the tokenizer to absorb any
6722
                 *   unknown words; if there are any unknown words, the
6723
                 *   tokenizer will parse them and set the unknown count.
6724
                 *   If we find any unknown words in the input, we'll
6725
                 *   simply treat the input as an entirely new command.  
6726
                 */
6727
                old_unknown = ctx->voccxunknown;
6728
                old_lastunk = ctx->voccxlastunk;
6729
                ctx->voccxunknown = 0;
6730
6731
                /* clear our internal type list */
6732
                memset(distypelist, 0, VOCBUFSIZ * sizeof(distypelist[0]));
6733
6734
                /* tokenize the sentence */
6735
                diswordlist[wrdcnt] = 0;
6736
                if (vocgtyp(ctx, diswordlist, distypelist, cmdbuf)
6737
                    || ctx->voccxunknown != 0)
6738
                {
6739
                    /* 
6740
                     *   there's an unknown word or other problem - retry
6741
                     *   the input as an entirely new command 
6742
                     */
6743
                    strcpy(cmdbuf, disnewbuf);
6744
                    ctx->voccxunknown = 0;
6745
                    ctx->voccxredo = TRUE;
6746
                    err = VOCERR(2);
6747
                    goto done;
6748
                }
6749
6750
                /* restore the original unknown word count */
6751
                ctx->voccxunknown = old_unknown;
6752
                ctx->voccxlastunk = old_lastunk;
6753
6754
                /*
6755
                 *   Find the last word that can be an adj and/or a noun.
6756
                 *   If it can be either (i.e., both bits are set), clear
6757
                 *   the noun bit and make it just an adjective.  This is
6758
                 *   because we're asking for an adjective for clarification,
6759
                 *   and we most likely want it to be an adjective in this
6760
                 *   context; if the noun bit is set, too, the object lister
6761
                 *   will think it must be a noun, being the last word.
6762
                 */
6763
                for (i = 0 ; i < wrdcnt ; ++i)
6764
                {
6765
                    if (!(distypelist[i] &
6766
                          (VOCT_ADJ | VOCT_NOUN | VOCT_ARTICLE)))
6767
                        break;
6768
                }
6769
                
6770
                if (i && (distypelist[i-1] & VOCT_ADJ)
6771
                    && (distypelist[i-1] & VOCT_NOUN))
6772
                {
6773
                    /*
6774
                     *   Note that we're clearing the noun flag.  If
6775
                     *   we're unsuccessful in finding the object with the
6776
                     *   noun flag cleared, we'll put the noun flag back
6777
                     *   in and give it another try (by adding VOCT_NOUN
6778
                     *   back into distypelist[cleared_noun], and coming
6779
                     *   back to the label below). 
6780
                     */
6781
                    cleared_noun = i-1;
6782
                    distypelist[i-1] &= ~VOCT_NOUN;
6783
                }
6784
                else
6785
                    cleared_noun = -1;
6786
6787
            try_current_flags:
6788
                /* start with the first word */
6789
                if (vocspec(diswordlist[0], VOCW_ALL)
6790
                    || vocspec(diswordlist[0], VOCW_BOTH))
6791
                {
6792
                    char *nam;
6793
                    static char all_name[] = "all";
6794
                    static char both_name[] = "both";
6795
6796
                    if (vocspec(diswordlist[0], VOCW_ALL))
6797
                        nam = all_name;
6798
                    else
6799
                        nam = both_name;
6800
                    
6801
                    for (i = 0 ; i < cnt ; ++i)
6802
                        vocout(&outlist[outpos++], list1[i], 0, nam, nam);
6803
                    if (noreach)
6804
                    {
6805
                        cantreach_list = list1;
6806
                        goto noreach1;
6807
                    }
6808
                    break;
6809
                }
6810
                else if (vocspec(diswordlist[0], VOCW_ANY))
6811
                {
6812
                    static char *anynm = "any";
6813
6814
                    /* choose the first object arbitrarily */
6815
                    vocout(&outlist[outpos++], list1[i], VOCS_ALL,
6816
                           anynm, anynm);
6817
                    break;
6818
                }
6819
                else
6820
                {
6821
                    /* check for a word matching the phrase */
6822
                    cnt2 = vocchknoun(ctx, diswordlist, distypelist,
6823
                                      0, &next, disnounlist, FALSE);
6824
                    if (cnt2 > 0)
6825
                    {
6826
                        /* 
6827
                         *   if that didn't consume the entire phrase, or
6828
                         *   at least up to "one" or "ones" or a period,
6829
                         *   disallow it, since they must be entering
6830
                         *   something more complicated 
6831
                         */
6832
                        if (diswordlist[next] != 0
6833
                            && !vocspec(diswordlist[next], VOCW_ONE)
6834
                            && !vocspec(diswordlist[next], VOCW_ONES)
6835
                            && !vocspec(diswordlist[next], VOCW_THEN))
6836
                        {
6837
                            cnt2 = 0;
6838
                        }
6839
                    }
6840
                    else if (cnt2 < 0)
6841
                    {
6842
                        /* 
6843
                         *   There was a syntax error in the phrase.
6844
                         *   vocchknoun() will have displayed a message in
6845
                         *   this case, so we're done parsing this command. 
6846
                         */
6847
                        err = VOCERR(45);
6848
                        goto done;
6849
                    }
6850
6851
                    /* proceed only if we got a valid phrase */
6852
                    if (cnt2 > 0)
6853
                    {
6854
                        int cnt3;
6855
                        int newcnt;
6856
6857
                        /* build the list of matches for the new phrase */
6858
                        for (i = 0, newcnt = 0 ; i < cnt2 ; ++i)
6859
                        {
6860
                            int j;
6861
                            int found;
6862
6863
                            /* 
6864
                             *   make sure this object isn't already in
6865
                             *   our list - we want each object only once 
6866
                             */
6867
                            for (j = 0, found = FALSE ; j < newcnt ; ++j)
6868
                            {
6869
                                /* if this is in the list, note it */
6870
                                if (list2[j] == disnounlist[i].vocolobj)
6871
                                {
6872
                                    found = TRUE;
6873
                                    break;
6874
                                }
6875
                            }
6876
6877
                            /* 
6878
                             *   add it to our list only if it wasn't
6879
                             *   already there 
6880
                             */
6881
                            if (!found)
6882
                            {
6883
                                /* copy the object ID */
6884
                                list2[newcnt] = disnounlist[i].vocolobj;
6885
6886
                                /* copy the flags that we care about */
6887
                                flags2[newcnt] = disnounlist[i].vocolflg
6888
                                    & (VOCS_PLURAL | VOCS_ANY
6889
                                       | VOCS_COUNT);
6890
6891
                                /* count the entry */
6892
                                ++newcnt;
6893
                            }
6894
                        }
6895
6896
                        /* terminate the list */
6897
                        list2[newcnt] = MCMONINV;
6898
6899
                        /* intersect the new list with the old list */
6900
                        newcnt = vocisect(list2, list1);
6901
6902
                        /* count the noun phrases in the new list */
6903
                        for (i = cnt3 = 0 ; i < cnt2 ; ++i)
6904
                        {
6905
                            /* we have one more noun phrase */
6906
                            ++cnt3;
6907
6908
                            /* if we have a noun phrase, skip matching objs */
6909
                            if (disnounlist[i].vocolfst != 0)
6910
                            {
6911
                                int j;
6912
6913
                                /* skip objects matching this noun phrase */
6914
                                for (j = i + 1 ; disnounlist[i].vocolfst ==
6915
                                         disnounlist[j].vocolfst ; ++j) ;
6916
                                i = j - 1;
6917
                            }
6918
                        }
6919
                        
6920
                        /*
6921
                         *   If the count of items in the intersection of
6922
                         *   the original list and the typed-in list is no
6923
                         *   bigger than the number of items specified in
6924
                         *   the typed-in list, we've successfully
6925
                         *   disambiguated the object, because the user's
6926
                         *   new list matches only one object for each set
6927
                         *   of words the user typed.  
6928
                         */
6929
                        if (newcnt
6930
                            && (newcnt <= cnt3
6931
                                || (diswordlist[next]
6932
                                    && vocspec(diswordlist[next],
6933
                                               VOCW_ONES))))
6934
                        {
6935
                            static char one_name[] = "ones";
6936
                            
6937
                            for (i = 0 ; i < cnt ; ++i)
6938
                                vocout(&outlist[outpos++], list2[i], 0,
6939
                                       one_name, one_name);
6940
                            
6941
                            if (noreach)
6942
                            {
6943
                                cnt = newcnt;
6944
                                cantreach_list = list2;
6945
                            noreach1:
6946
                                if (accprop == PRP_VALIDACTOR)
6947
                                {
6948
                                    /* for actors, show a special message */
6949
                                    vocerr(ctx, VOCERR(31),
6950
                                           "You can't talk to that.");
6951
                                }
6952
                                else
6953
                                {
6954
                                    /* use the normal no-reach message */
6955
                                    vocnoreach(ctx, cantreach_list, cnt,
6956
                                               cmdActor, cmdVerb, cmdPrep,
6957
                                               defprop, cnt > 1, 0, 0, cnt);
6958
                                }
6959
                                err = VOCERR(31);
6960
                                goto done;
6961
                            }
6962
                            break;
6963
                        }
6964
                        else if (newcnt == 0)
6965
                        {
6966
                            /*
6967
                             *   If we cleared the noun, maybe we actually
6968
                             *   need to treat the word as a noun, so add
6969
                             *   the noun flag back in and give it another
6970
                             *   go.  If we didn't clear the noun, there's
6971
                             *   nothing left to try, so explain that we
6972
                             *   don't see any such object and give up.  
6973
                             */
6974
                            if (cleared_noun != -1)
6975
                            {
6976
                                distypelist[cleared_noun] |= VOCT_NOUN;
6977
                                cleared_noun = -1;
6978
                                goto try_current_flags;
6979
                            }
6980
6981
                            /* find the first object with a noun phrase */
6982
                            for (i = 0 ; i < cnt2 ; ++i)
6983
                            {
6984
                                /* if we have a noun phrase, stop scanning */
6985
                                if (disnounlist[i].vocolfst != 0)
6986
                                    break;
6987
                            }
6988
6989
                            /* 
6990
                             *   if we found a noun phrase, build a string
6991
                             *   out of the words used; otherwise, just
6992
                             *   use "such" 
6993
                             */
6994
                            if (i != cnt2)
6995
                            {
6996
                                char *p;
6997
                                char *last;
6998
6999
                                /* clear the word buffer */
7000
                                newobj[0] = '\0';
7001
                                
7002
                                /* build a string out of the words */
7003
                                p = disnounlist[i].vocolfst;
7004
                                last = disnounlist[i].vocollst;
7005
                                for ( ; p <= last ; p += strlen(p) + 1)
7006
                                {
7007
                                    /* 
7008
                                     *   If this is a special word, we
7009
                                     *   probably can't construct a
7010
                                     *   sensible sentence - special words
7011
                                     *   are special parts of speech that
7012
                                     *   will look weird if inserted into
7013
                                     *   our constructed noun phrase.  In
7014
                                     *   these cases, turn the entire
7015
                                     *   thing into "I don't see any
7016
                                     *   *such* object" rather than trying
7017
                                     *   to make do with pronouns or other
7018
                                     *   special words.  
7019
                                     */
7020
                                    if (vocisspec(p))
7021
                                    {
7022
                                        /* 
7023
                                         *   replace the entire adjective
7024
                                         *   phrase with "such" 
7025
                                         */
7026
                                        strcpy(newobj, "such");
7027
7028
                                        /* 
7029
                                         *   stop here - don't add any
7030
                                         *   more, since "such" is the
7031
                                         *   whole thing 
7032
                                         */
7033
                                        break;
7034
                                    }
7035
                                    
7036
                                    /* add a space if we have a prior word */
7037
                                    if (newobj[0] != '\0')
7038
                                        strcat(newobj, " ");
7039
7040
                                    /* add this word */
7041
                                    strcat(newobj, p);
7042
                                }
7043
                            }
7044
                            else
7045
                            {
7046
                                /* no noun phrase found */
7047
                                strcpy(newobj, "such");
7048
                            }
7049
7050
                            /* didn't find anything - complain and give up */
7051
                            vocerr(ctx, VOCERR(16),
7052
                                   "You don't see any %s %s here.",
7053
                                   newobj, usrobj);
7054
                            err = VOCERR(16);
7055
                            goto done;
7056
                        }
7057
                        
7058
                        /*
7059
                         *   If we get here, it means that we have still
7060
                         *   more than one object per noun phrase typed in
7061
                         *   the latest sentence.  Limit the list to the
7062
                         *   intersection (by copying list2 to list1), and
7063
                         *   try again.  
7064
                         */
7065
                        memcpy(list1, list2,
7066
                               (size_t)((newcnt + 1) * sizeof(list1[0])));
7067
                        cnt = newcnt;
7068
                        trying_again = TRUE;
7069
                    }
7070
                    else
7071
                    {
7072
                        /* 
7073
                         *   We didn't find a noun phrase, so it's probably a
7074
                         *   new command.  However, check first to see if we
7075
                         *   were making a trial run with the noun flag
7076
                         *   cleared: if so, go back and make another pass
7077
                         *   with the noun flag added back in to see if that
7078
                         *   works any better.  
7079
                         */
7080
                        if (cleared_noun != -1)
7081
                        {
7082
                            distypelist[cleared_noun] |= VOCT_NOUN;
7083
                            cleared_noun = -1;
7084
                            goto try_current_flags;
7085
                        }
7086
                        
7087
                        /* retry as an entire new command */
7088
                        strcpy(cmdbuf, disnewbuf);
7089
                        ctx->voccxunknown = 0;
7090
                        ctx->voccxredo = TRUE;
7091
                        err = VOCERR(43);
7092
                        goto done;
7093
                    }
7094
                }
7095
            }
7096
            inpos = lpos - 1;
7097
        }
7098
    }
7099
7100
    /* terminate the output list */
7101
    vocout(&outlist[outpos], MCMONINV, 0, (char *)0, (char *)0);
7102
7103
    /* 
7104
     *   If we still have ambiguous objects, so indicate.  This can only
7105
     *   happen when we operate in "silent" mode, because only then can we
7106
     *   give up without fully resolving a list. 
7107
     */
7108
    if (still_ambig)
7109
        err = VOCERR(44);
7110
7111
    /* no error */
7112
    err = 0;
7113
7114
done:
7115
    ERRCLEAN(ctx->voccxerr)
7116
    {
7117
        /* 
7118
         *   reset the stack before we return, in case the caller handles
7119
         *   the error without aborting the command 
7120
         */
7121
        voc_leave(ctx, save_sp);
7122
    }
7123
    ERRENDCLN(ctx->voccxerr);
7124
7125
    /* return success */
7126
    VOC_RETVAL(ctx, save_sp, err);
7127
}
7128
7129
/* vocready - see if at end of command, execute & return TRUE if so */
7130
static int vocready(voccxdef *ctx, char *cmd[], int *typelist, int cur,
7131
                    objnum cmdActor, objnum cmdPrep, char *vverb, char *vprep,
7132
                    vocoldef *dolist, vocoldef *iolist, int *errp,
7133
                    char *cmdbuf, int first_word, uchar **preparse_list,
7134
                    int *next_start)
7135
{
7136
    if (cur != -1
7137
        && (cmd[cur] == (char *)0
7138
            || vocspec(cmd[cur], VOCW_AND) || vocspec(cmd[cur], VOCW_THEN)))
7139
    {
7140
        if (ctx->voccxflg & VOCCXFDBG)
7141
        {
7142
            char buf[128];
7143
            
7144
            sprintf(buf, ". executing verb:  %s %s\\n",
7145
                    vverb, vprep ? vprep : "");
7146
            tioputs(ctx->vocxtio, buf);
7147
        }
7148
7149
        *errp = execmd(ctx, cmdActor, cmdPrep, vverb, vprep, dolist, iolist,
7150
                       &cmd[first_word], &typelist[first_word],cmdbuf,
7151
                       cur - first_word, preparse_list, next_start);
7152
        return(TRUE);
7153
    }
7154
    return(FALSE);
7155
}
7156
7157
/* execute a single command */
7158
static int voc1cmd(voccxdef *ctx, char *cmd[], char *cmdbuf,
7159
                   objnum *cmdActorp, int first)
7160
{
7161
    int       cur;
7162
    int       next;
7163
    objnum    o;
7164
    vocwdef  *v;
7165
    char     *vverb;
7166
    int       vvlen;
7167
    char     *vprep;
7168
    int       cnt;
7169
    int       err;
7170
    vocoldef *dolist;
7171
    vocoldef *iolist;
7172
    int      *typelist;
7173
    objnum    cmdActor = *cmdActorp;
7174
    objnum    cmdPrep;
7175
    int       swapObj;                        /* TRUE -> swap dobj and iobj */
7176
    int       again;
7177
    int       first_word;
7178
    uchar    *preparse_list;
7179
    int       next_start;
7180
    struct
7181
    {
7182
        int    active;
7183
        int    cur;
7184
        char **cmd;
7185
        char  *cmdbuf;
7186
    } preparseCmd_stat;
7187
    char    **newcmd;
7188
    char     *origcmdbuf;
7189
    char     *newcmdbuf;
7190
    uchar    *save_sp;
7191
    int       no_match;
7192
    int       retval;
7193
7194
    voc_enter(ctx, &save_sp);
7195
    VOC_MAX_ARRAY(ctx, vocoldef, dolist);
7196
    VOC_MAX_ARRAY(ctx, vocoldef, iolist);
7197
    VOC_STK_ARRAY(ctx, int,      typelist,  VOCBUFSIZ);
7198
    VOC_STK_ARRAY(ctx, char *,   newcmd,    VOCBUFSIZ);
7199
    VOC_STK_ARRAY(ctx, char,     newcmdbuf, VOCBUFSIZ);
7200
7201
    /* save the original command buf in case we need to redo the command */
7202
    origcmdbuf = cmdbuf;
7203
7204
    /* clear out the type list */
7205
    memset(typelist, 0, VOCBUFSIZ*sizeof(typelist[0]));
7206
7207
    /* get the types of the words in the command */
7208
    if (vocgtyp(ctx, cmd, typelist, cmdbuf))
7209
    {
7210
        retval = 1;
7211
        goto done;
7212
    }
7213
7214
    /* start off at the first word */
7215
    cur = next = first_word = 0;
7216
7217
    /* 
7218
     *   Presume we will be in control of the next word - when execmd() or
7219
     *   another routine we call decides where the command ends, it will
7220
     *   fill in a new value here.  When this value is non-zero, it will
7221
     *   tell us where the next sentence start is relative to the previous
7222
     *   sentence start.  
7223
     */
7224
    next_start = 0;
7225
7226
    /* we don't have a preparseCmd result yet */
7227
    preparseCmd_stat.active = FALSE;
7228
7229
    /* keep going until we run out of work to do */
7230
    for (again = FALSE, err = 0 ; ; again = TRUE)
7231
    {
7232
        /*
7233
         *   if preparseCmd sent us back a list, parse that list as a new
7234
         *   command 
7235
         */
7236
        if (err == ERR_PREPRSCMDREDO)
7237
        {
7238
            uchar  *src;
7239
            size_t  len;
7240
            size_t  curlen;
7241
            char   *dst;
7242
            int     cnt;
7243
7244
            /* don't allow a preparseCmd to loop */
7245
            if (preparseCmd_stat.active)
7246
            {
7247
                vocerr(ctx, VOCERR(34),
7248
                       "Internal game error: preparseCmd loop");
7249
                retval = 1;
7250
                goto done;
7251
            }
7252
            
7253
            /* save our status prior to processing the preparseCmd list */
7254
            preparseCmd_stat.active = TRUE;
7255
            preparseCmd_stat.cur = cur;
7256
            preparseCmd_stat.cmd = cmd;
7257
            preparseCmd_stat.cmdbuf = cmdbuf;
7258
7259
            /* set up with the new command */
7260
            cmd = newcmd;
7261
            cmdbuf = newcmdbuf;
7262
            cur = 0;
7263
7264
            /* break up the list into the new command buffer */
7265
            src = preparse_list;
7266
            len = osrp2(src) - 2;
7267
            for (src += 2, dst = cmdbuf, cnt = 0 ; len ; )
7268
            {
7269
                /* make sure the next element is a string */
7270
                if (*src != DAT_SSTRING)
7271
                {
7272
                    vocerr(ctx, VOCERR(32),
7273
                 "Internal game error: preparseCmd returned an invalid list");
7274
                    retval = 1;
7275
                    goto done;
7276
                }
7277
7278
                /* get the string */
7279
                ++src;
7280
                curlen = osrp2(src) - 2;
7281
                src += 2;
7282
7283
                /* make sure it will fit in the buffer */
7284
                if (dst + curlen + 1 >= cmdbuf + VOCBUFSIZ)
7285
                {
7286
                    vocerr(ctx, VOCERR(33),
7287
                          "Internal game error: preparseCmd command too long");
7288
                    retval = 1;
7289
                    goto done;
7290
                }
7291
7292
                /* store the word */
7293
                cmd[cnt++] = dst;
7294
                memcpy(dst, src, curlen);
7295
                dst[curlen] = '\0';
7296
7297
                /* move on to the next word */
7298
                len -= 3 + curlen;
7299
                src += curlen;
7300
                dst += curlen + 1;
7301
            }
7302
7303
            /* enter a null last word */
7304
            cmd[cnt] = 0;
7305
7306
            /* generate the type list for the new list */
7307
            if (vocgtyp(ctx, cmd, typelist, cmdbuf))
7308
            {
7309
                /* return an error */
7310
                retval = 1;
7311
                goto done;
7312
            }
7313
7314
            /*
7315
             *   this is not a new command - it's just further processing
7316
             *   of the current command 
7317
             */
7318
            again = FALSE;
7319
7320
            /* clear the error */
7321
            err = 0;
7322
        }
7323
7324
        /* initialize locals */
7325
        cmdPrep  = MCMONINV;                       /* assume no preposition */
7326
        swapObj  = FALSE;                      /* assume no object swapping */
7327
        dolist[0].vocolobj = iolist[0].vocolobj = MCMONINV;
7328
        dolist[0].vocolflg = iolist[0].vocolflg = 0;
7329
7330
        /* check error return from vocready (which returns from execmd) */
7331
        if (err)
7332
        {
7333
            /* return the error */
7334
            retval = err;
7335
            goto done;
7336
        }
7337
7338
    skip_leading_stuff:
7339
        /* 
7340
         *   If someone updated the sentence start point, jump there.  The
7341
         *   sentence start is relative to the previous sentence start. 
7342
         */
7343
        if (next_start != 0)
7344
            cur = first_word + next_start;
7345
7346
        /* clear next_start, so we can tell if someone updates it */
7347
        next_start = 0;
7348
        
7349
        /* skip any leading THEN's and AND's */
7350
        while (cmd[cur] && (vocspec(cmd[cur], VOCW_THEN)
7351
                            || vocspec(cmd[cur], VOCW_AND)))
7352
            ++cur;
7353
7354
        /* see if there's anything left to parse */
7355
        if (cmd[cur] == 0)
7356
        {
7357
            /*
7358
             *   if we've been off doing preparseCmd work, return to the
7359
             *   original command list 
7360
             */
7361
            if (preparseCmd_stat.active)
7362
            {
7363
                /* restore the original status */
7364
                cur = preparseCmd_stat.cur;
7365
                cmd = preparseCmd_stat.cmd;
7366
                cmdbuf = preparseCmd_stat.cmdbuf;
7367
                preparseCmd_stat.active = FALSE;
7368
                
7369
                /* get the type list for the original list again */
7370
                if (vocgtyp(ctx, cmd, typelist, cmdbuf))
7371
                {
7372
                    /* return the error */
7373
                    retval = 1;
7374
                    goto done;
7375
                }
7376
7377
                /* try again */
7378
                goto skip_leading_stuff;
7379
            }
7380
            else
7381
            {
7382
                /* nothing to pop - we must be done */
7383
                retval = 0;
7384
                goto done;
7385
            }
7386
        }
7387
7388
        /* 
7389
         *   display a blank line if this is not the first command on this
7390
         *   command line, so that we visually separate the results of the
7391
         *   new command from the results of the previous command 
7392
         */
7393
        if (again)
7394
            outformat("\\b");                   /* tioblank(ctx->voccxtio); */
7395
7396
        {
7397
            /* look for an explicit actor in the command */
7398
            if ((o = vocgetactor(ctx, cmd, typelist, cur, &next, cmdbuf))
7399
                != MCMONINV)
7400
            {
7401
                /* skip the actor noun phrase in the input */
7402
                cur = next;
7403
7404
                /* remember the actor internally */
7405
                cmdActor = *cmdActorp = o;
7406
7407
                /* set the actor in the context */
7408
                ctx->voccxactor = o;
7409
            }
7410
7411
            /* if the actor parsing failed, return an error */
7412
            if (cur != next)
7413
            {
7414
                /* error getting actor */
7415
                retval = 1;
7416
                goto done;
7417
            }
7418
        }
7419
7420
        /* remember where the sentence starts */
7421
        first_word = cur;
7422
7423
        /* make sure we have a verb */
7424
        if ((cmd[cur] == (char *)0) || !(typelist[cur] & VOCT_VERB))
7425
        {
7426
            /* unknown verb - handle it with parseUnknownVerb if possible */
7427
            if (!try_unknown_verb(ctx, cmdActor, &cmd[cur], &typelist[cur],
7428
                                  0, &next_start, TRUE, VOCERR(17),
7429
                                  "There's no verb in that sentence!"))
7430
            {
7431
                /* error - abort the command */
7432
                retval = 1;
7433
                goto done;
7434
            }
7435
            else
7436
            {
7437
                /* go back for more */
7438
                continue;
7439
            }
7440
        }
7441
        vverb = cmd[cur++];                             /* this is the verb */
7442
        vvlen = strlen(vverb);                   /* remember length of verb */
7443
        vprep = 0;                            /* assume no verb-preposition */
7444
7445
        /* execute if the command is just a verb */
7446
        if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
7447
                     vverb, vprep, dolist, iolist, &err, cmdbuf,
7448
                     first_word, &preparse_list, &next_start))
7449
            continue;
7450
7451
        /*
7452
         *   If the next word is a preposition, and it makes sense to be
7453
         *   aggregated with this verb, use it as such.
7454
         */
7455
        if (typelist[cur] & VOCT_PREP)
7456
        {
7457
            if (vocffw(ctx, vverb, vvlen, cmd[cur], (int)strlen(cmd[cur]),
7458
                       PRP_VERB, (vocseadef *)0))
7459
            {
7460
                vprep = cmd[cur++];
7461
                if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
7462
                             vverb, vprep, dolist, iolist, &err, cmdbuf,
7463
                             first_word, &preparse_list, &next_start))
7464
                    continue;
7465
            }
7466
            else
7467
            {
7468
                /*
7469
                 *   If we have a preposition which can NOT be aggregated
7470
                 *   with the verb, take command of this form: "verb prep
7471
                 *   iobj dobj".  Note that we do *not* do this if the
7472
                 *   word is also a noun, or it's an adjective and a noun
7473
                 *   (possibly separated by one or more adjectives)
7474
                 *   follows.  
7475
                 */
7476
                if ((v = vocffw(ctx, cmd[cur], (int)strlen(cmd[cur]),
7477
                                (char *)0, 0, PRP_PREP, (vocseadef *)0)) != 0)
7478
                {
7479
                    int swap_ok;
7480
7481
                    /* if it can be an adjective, check further */
7482
                    if (typelist[cur] & VOCT_NOUN)
7483
                    {
7484
                        /* don't allow the swap */
7485
                        swap_ok = FALSE;
7486
                    }
7487
                    else if (typelist[cur] & VOCT_ADJ)
7488
                    {
7489
                        int i;
7490
7491
                        /* look for a noun, possibly preceded by adj's */
7492
                        for (i = cur + 1 ;
7493
                             cmd[i] && (typelist[i] & VOCT_ADJ)
7494
                             && !(typelist[i] & VOCT_NOUN) ; ++i) ;
7495
                        swap_ok = (!cmd[i] || !(typelist[i] & VOCT_NOUN));
7496
                    }
7497
                    else
7498
                    {
7499
                        /* we can definitely allow this swap */
7500
                        swap_ok = TRUE;
7501
                    }
7502
7503
                    if (swap_ok)
7504
                    {
7505
                        cmdPrep = v->vocwobj;
7506
                        swapObj = TRUE;
7507
                        ++cur;
7508
                    }
7509
                }
7510
            }
7511
        }
7512
7513
    retry_swap:
7514
        /* get the direct object if there is one */
7515
        if ((cnt = vocchknoun2(ctx, cmd, typelist, cur, &next, dolist,
7516
                               FALSE, &no_match)) > 0)
7517
        {
7518
            /* we found a noun phrase matching one or more objects */
7519
            cur = next;
7520
        }
7521
        else if (no_match)
7522
        {
7523
            /* 
7524
             *   we found a valid noun phrase, but we didn't find any
7525
             *   objects that matched the words -- get the noun again,
7526
             *   this time showing the error 
7527
             */
7528
            vocgetnoun(ctx, cmd, typelist, cur, &next, dolist);
7529
7530
            /* return the error */
7531
            retval = 1;
7532
            goto done;
7533
        }
7534
        else if (cnt < 0)
7535
        {
7536
            /* invalid syntax - return failure */
7537
            retval = 1;
7538
            goto done;
7539
        }
7540
        else
7541
        {
7542
            /*
7543
             *   If we thought we were going to get a two-object
7544
             *   sentence, and we got a zero-object sentence, and it looks
7545
             *   like the word we took as a preposition is also an
7546
             *   adjective or noun, go back and treat it as such. 
7547
             */
7548
            if (swapObj &&
7549
                ((typelist[cur-1] & VOCT_NOUN)
7550
                 || (typelist[cur-1] & VOCT_ADJ)))
7551
            {
7552
                --cur;
7553
                swapObj = FALSE;
7554
                cmdPrep = MCMONINV;
7555
                goto retry_swap;
7556
            }
7557
7558
        bad_sentence:
7559
            /* find the last word */
7560
            while (cmd[cur]) ++cur;
7561
            
7562
            /* try running the sentence through preparseCmd */
7563
            err = try_preparse_cmd(ctx, &cmd[first_word], cur - first_word,
7564
                                   &preparse_list);
7565
            switch(err)
7566
            {
7567
            case 0:
7568
                /* preparseCmd didn't do anything - the sentence fails */
7569
                if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
7570
                                      &typelist[first_word], 0, &next_start,
7571
                                      TRUE, VOCERR(18),
7572
                                      "I don't understand that sentence."))
7573
                {
7574
                    /* error - abort the command */
7575
                    retval = 1;
7576
                    goto done;
7577
                }
7578
                else
7579
                {
7580
                    /* success - go back for more */
7581
                    continue;
7582
                }
7583
7584
            case ERR_PREPRSCMDCAN:
7585
                /* they cancelled - we're done with the sentence */
7586
                retval = 0;
7587
                goto done;
7588
7589
            case ERR_PREPRSCMDREDO:
7590
                /* reparse with the new sentence */
7591
                continue;
7592
            }
7593
        }
7594
7595
        /* see if we want to execute the command now */
7596
        if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
7597
                     vverb, vprep,
7598
                     swapObj ? iolist : dolist,
7599
                     swapObj ? dolist : iolist,
7600
                     &err, cmdbuf, first_word, &preparse_list,
7601
                     &next_start))
7602
            continue;
7603
        
7604
        /*
7605
         *   Check for an indirect object, which may or may not be preceded
7606
         *   by a preposition.  (Note that the lack of a preposition implies
7607
         *   that the object we already found is the indirect object, and the
7608
         *   next object is the direct object.  It also implies a preposition
7609
         *   of "to.")
7610
         */
7611
        if (cmdPrep == MCMONINV && (typelist[cur] & VOCT_PREP))
7612
        {
7613
            char *p1 = cmd[cur++];
7614
7615
            /* if this is the end of the sentence, add the prep to the verb */
7616
            if (cmd[cur] == (char *)0
7617
                || vocspec(cmd[cur], VOCW_AND)
7618
                || vocspec(cmd[cur], VOCW_THEN))
7619
            {
7620
                if (vocffw(ctx, vverb, vvlen, p1, (int)strlen(p1), PRP_VERB,
7621
                           (vocseadef *)0)
7622
                    && !vprep)
7623
                    vprep = p1;
7624
                else
7625
                {
7626
                    /* call parseUnknownVerb */
7627
                    if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
7628
                                          &typelist[first_word], 0,
7629
                                          &next_start, TRUE, VOCERR(19),
7630
                        "There are words after your command I couldn't use."))
7631
                    {
7632
                        /* error - abandon the command */
7633
                        retval = 1;
7634
                        goto done;
7635
                    }
7636
                    else
7637
                    {
7638
                        /* go back for more */
7639
                        continue;
7640
                    }
7641
                }
7642
                
7643
                if ((err = execmd(ctx, cmdActor, cmdPrep, vverb, vprep,
7644
                                  dolist, iolist,
7645
                                  &cmd[first_word], &typelist[first_word],
7646
                                  cmdbuf, cur - first_word,
7647
                                  &preparse_list, &next_start)) != 0)
7648
                {
7649
                    retval = 1;
7650
                    goto done;
7651
                }
7652
                continue;
7653
            }
7654
7655
            /*
7656
             *   If we have no verb preposition already, and we have
7657
             *   another prep-capable word following this prep-capable
7658
             *   word, and this preposition aggregates with the verb, take
7659
             *   it as a sentence of the form "pry box open with crowbar"
7660
             *   (where the current word is "with").  We also must have at
7661
             *   least one more word after that, since there will have to
7662
             *   be an indirect object.  
7663
             */
7664
            if (cmd[cur] && (typelist[cur] & VOCT_PREP) && cmd[cur+1]
7665
                && vprep == 0
7666
                && vocffw(ctx, vverb, vvlen, p1, (int)strlen(p1), PRP_VERB,
7667
                          (vocseadef *)0))
7668
            {
7669
                /* 
7670
                 *   check to make sure that the next word, which we're
7671
                 *   about to take for a prep (the "with" in "pry box open
7672
                 *   with crowbar") is actually not part of an object name
7673
                 *   - if it is, use it as the object name rather than as
7674
                 *   the prep 
7675
                 */
7676
                if (vocgobj(ctx, cmd, typelist, cur, &next,
7677
                            FALSE, iolist, FALSE, FALSE, 0) <= 0)
7678
                {
7679
                    /* aggregate the first preposition into the verb */
7680
                    vprep = p1;
7681
7682
                    /* use the current word as the object-introducing prep */
7683
                    p1 = cmd[cur++];
7684
                }
7685
            }
7686
7687
            /* try for an indirect object */
7688
            cnt = vocgobj(ctx, cmd, typelist, cur, &next, TRUE, iolist,
7689
                          TRUE, FALSE, &no_match);
7690
            if (cnt > 0)
7691
            {
7692
                cur = next;
7693
                v = vocffw(ctx, p1, (int)strlen(p1), (char *)0, 0, PRP_PREP,
7694
                           (vocseadef *)0);
7695
                if (v == (vocwdef *)0)
7696
                {
7697
                    /* let parseUnknownVerb handle it */
7698
                    if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
7699
                                          &typelist[first_word], 0,
7700
                                          &next_start, TRUE, VOCERR(20),
7701
                   "I don't know how to use the word \"%s\" like that.", p1))
7702
                    {
7703
                        /* error - abort the command */
7704
                        retval = 1;
7705
                        goto done;
7706
                    }
7707
                    else
7708
                    {
7709
                        /* go on to the next command */
7710
                        continue;
7711
                    }
7712
                }
7713
                cmdPrep = v->vocwobj;
7714
7715
                if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
7716
                             vverb, vprep, dolist, iolist, &err, cmdbuf,
7717
                             first_word, &preparse_list, &next_start))
7718
                    continue;
7719
                else if ((typelist[cur] & VOCT_PREP) &&
7720
                         vocffw(ctx, vverb, vvlen, cmd[cur],
7721
                                (int)strlen(cmd[cur]), PRP_VERB,
7722
                                (vocseadef *)0) && !vprep)
7723
                {
7724
                    vprep = cmd[cur++];
7725
                    if (vocready(ctx, cmd, typelist, cur, cmdActor,
7726
                                 cmdPrep, vverb, vprep, dolist, iolist,
7727
                                 &err, cmdbuf, first_word, &preparse_list,
7728
                                 &next_start))
7729
                        continue;
7730
                    else
7731
                    {
7732
                        /* let parseUnknownVerb handle it */
7733
                        if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
7734
                                              &typelist[first_word], 0,
7735
                                              &next_start, TRUE, VOCERR(19),
7736
                         "There are words after your command I couldn't use."))
7737
                        {
7738
                            /* error - abandon the command */
7739
                            retval = 1;
7740
                            goto done;
7741
                        }
7742
                        else
7743
                        {
7744
                            /* go on to the next command */
7745
                            continue;
7746
                        }
7747
                    }
7748
                }
7749
                else
7750
                {
7751
                    /*
7752
                     *   If the latter object phrase is flagged with the
7753
                     *   "trimmed preposition" flag, meaning that we could
7754
                     *   have used the preposition in the noun phrase but
7755
                     *   assumed instead it was part of the verb, reverse
7756
                     *   this assumption now: add the preposition back to the
7757
                     *   noun phrase and explain that there's no such thing
7758
                     *   present.
7759
                     *   
7760
                     *   Otherwise, we simply have an unknown verb phrasing,
7761
                     *   so let parseUnknownVerb handle it.  
7762
                     */
7763
                    vocoldef *np1 =
7764
                        (dolist[0].vocolflg & VOCS_TRIMPREP) != 0
7765
                        ? &dolist[0]
7766
                        : (iolist[0].vocolflg & VOCS_TRIMPREP) != 0
7767
                        ? &iolist[0]
7768
                        : 0;
7769
                    if (np1 != 0)
7770
                    {
7771
                        char namebuf[VOCBUFSIZ];
7772
                        
7773
                        /* show the name, adding the prep back in */
7774
                        voc_make_obj_name_from_list(
7775
                            ctx, namebuf, cmd, np1->vocolfst, np1->vocolhlst);
7776
                        vocerr(ctx, VOCERR(9), "I don't see any %s here.",
7777
                               namebuf);
7778
7779
                        /* error - abort */
7780
                        retval = 1;
7781
                        goto done;
7782
                    }
7783
                    else if (!try_unknown_verb(
7784
                        ctx, cmdActor,
7785
                        &cmd[first_word], &typelist[first_word],
7786
                        0, &next_start, TRUE, VOCERR(19),
7787
                  "There are words after your command that I couldn't use."))
7788
                    {
7789
                        /* error - abort */
7790
                        retval = 1;
7791
                        goto done;
7792
                    }
7793
                    else
7794
                    {
7795
                        /* continue with the next command */
7796
                        continue;
7797
                    }
7798
                }
7799
            }
7800
            else if (cnt < 0)
7801
            {
7802
                /* 
7803
                 *   the noun phrase syntax was invalid - we've already
7804
                 *   displayed an error about it, so simply return failure 
7805
                 */
7806
                retval = 1;
7807
                goto done;
7808
            }
7809
            else if (no_match)
7810
            {
7811
                /* 
7812
                 *   we found a valid noun phrase, but we didn't find any
7813
                 *   matching objects - we've already generated an error,
7814
                 *   so simply return failure 
7815
                 */
7816
                retval = 1;
7817
                goto done;
7818
            }
7819
            else
7820
            {
7821
                goto bad_sentence;
7822
            }
7823
        }
7824
        else if ((cnt = vocchknoun(ctx, cmd, typelist, cur,
7825
                                   &next, iolist, FALSE)) > 0)
7826
        {
7827
            /* look for prep at end of command */
7828
            cur = next;
7829
            if (cmd[cur])
7830
            {
7831
                if ((typelist[cur] & VOCT_PREP) &&
7832
                    vocffw(ctx, vverb, vvlen, cmd[cur],
7833
                           (int)strlen(cmd[cur]), PRP_VERB,
7834
                           (vocseadef *)0) && !vprep)
7835
                {
7836
                    vprep = cmd[cur++];
7837
                }
7838
            }
7839
7840
            /* the command should definitely be done now */
7841
            if (cmd[cur] != 0)
7842
            {
7843
                /* let parseUnknownVerb handle it */
7844
                if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
7845
                                      &typelist[first_word], 0,
7846
                                      &next_start, TRUE, VOCERR(21),
7847
                        "There appear to be extra words after your command."))
7848
                {
7849
                    /* error - stop the command */
7850
                    retval = 1;
7851
                    goto done;
7852
                }
7853
                else
7854
                {
7855
                    /* go on to the next command */
7856
                    continue;
7857
                }
7858
            }
7859
                
7860
            /*
7861
             *   If we don't have a preposition yet, we need to find the
7862
             *   verb's default.  If the verb object has a nilPrep
7863
             *   property defined, use that prep object; otherwise, look
7864
             *   up the word "to" and use that.  
7865
             */
7866
            if (cmdPrep == MCMONINV &&
7867
                (v = vocffw(ctx, vverb, vvlen,
7868
                            vprep, (vprep ? (int)strlen(vprep) : 0),
7869
                            PRP_VERB, (vocseadef *)0)) != 0)
7870
            {
7871
                runppr(ctx->voccxrun, v->vocwobj, PRP_NILPREP, 0);
7872
                if (runtostyp(ctx->voccxrun) == DAT_OBJECT)
7873
                    cmdPrep = runpopobj(ctx->voccxrun);
7874
                else
7875
                    rundisc(ctx->voccxrun);
7876
            }
7877
7878
            /* if we didn't find anything with nilPrep, find "to" */
7879
            if (cmdPrep == MCMONINV)
7880
            {
7881
                v = vocffw(ctx, "to", 2, (char *)0, 0, PRP_PREP,
7882
                           (vocseadef *)0);
7883
                if (v) cmdPrep = v->vocwobj;
7884
            }
7885
7886
            /* execute the command */
7887
            err = execmd(ctx, cmdActor, cmdPrep, vverb, vprep,
7888
                         iolist, dolist,
7889
                         &cmd[first_word], &typelist[first_word], cmdbuf,
7890
                         cur - first_word, &preparse_list, &next_start);
7891
            continue;
7892
        }
7893
        else if (cnt < 0)
7894
        {
7895
            retval = 1;
7896
            goto done;
7897
        }
7898
        else
7899
        {
7900
            goto bad_sentence;
7901
        }
7902
    }
7903
7904
done:
7905
    /* copy back the command if we need to redo */
7906
    if (ctx->voccxredo && cmdbuf != origcmdbuf)
7907
        strcpy(origcmdbuf, cmdbuf);
7908
                    
7909
    /* return the status */
7910
    VOC_RETVAL(ctx, save_sp, retval);
7911
}
7912
7913
/* execute a player command */
7914
int voccmd(voccxdef *ctx, char *cmd, uint cmdlen)
7915
{
7916
    int      wrdcnt;
7917
    int      cur;
7918
    int      next;
7919
    char    *buffer;
7920
    char   **wordlist;
7921
    objnum   cmdActor;
7922
    int      first;
7923
7924
    /* 
7925
     *   Make sure the stack is set up, resetting the stack on entry. Note
7926
     *   that this makes this routine non-reentrant - recursively calling
7927
     *   this routine will wipe out the enclosing caller's stack. 
7928
     */
7929
    voc_stk_ini(ctx, (uint)VOC_STACK_SIZE);
7930
7931
    /* allocate our stack arrays */
7932
    VOC_STK_ARRAY(ctx, char,   buffer,   2*VOCBUFSIZ);
7933
    VOC_STK_ARRAY(ctx, char *, wordlist, VOCBUFSIZ);
7934
    
7935
    /* until further notice, the actor for formatStrings is Me */
7936
    tiosetactor(ctx->voccxtio, ctx->voccxme);
7937
7938
    /* clear the 'ignore' flag */
7939
    ctx->voccxflg &= ~VOCCXFCLEAR;
7940
7941
    /* send to game function 'preparse' */
7942
    if (ctx->voccxpre != MCMONINV)
7943
    {
7944
        int      typ;
7945
        uchar   *s;
7946
        size_t   len;
7947
        int      err;
7948
7949
        /* push the argument string */
7950
        runpstr(ctx->voccxrun, cmd, (int)strlen(cmd), 0);
7951
7952
        /* presume no error will occur */
7953
        err = 0;
7954
7955
        /* catch errors that occur during preparse() */
7956
        ERRBEGIN(ctx->voccxerr)
7957
        {
7958
            /* call preparse() */
7959
            runfn(ctx->voccxrun, ctx->voccxpre, 1);
7960
        }
7961
        ERRCATCH(ctx->voccxerr, err)
7962
        {
7963
            /* see what we have */
7964
            switch(err)
7965
            {
7966
            case ERR_RUNEXIT:
7967
            case ERR_RUNEXITOBJ:
7968
            case ERR_RUNABRT:
7969
                /* 
7970
                 *   if we encountered abort, exit, or exitobj, treat it
7971
                 *   the same as a nil result code - simply cancel the
7972
                 *   entire current command 
7973
                 */
7974
                break;
7975
7976
            default:
7977
                /* re-throw any other error */
7978
                errrse(ctx->voccxerr);
7979
            }
7980
        }
7981
        ERREND(ctx->voccxerr);
7982
7983
        /* if an error occurred, abort the command */
7984
        if (err != 0)
7985
            return 0;
7986
7987
        /* check the return value's type */
7988
        typ = runtostyp(ctx->voccxrun);
7989
        if (typ == DAT_SSTRING)
7990
        {
7991
            /* 
7992
             *   It's a string - replace the command with the new string.
7993
             *   Pop the new string and scan its length prefix. 
7994
             */
7995
            s = runpopstr(ctx->voccxrun);
7996
            len = osrp2(s) - 2;
7997
            s += 2;
7998
7999
            /* 
8000
             *   limit the size of the command to our buffer length,
8001
             *   leaving space for null termination
8002
             */
8003
            if (len > cmdlen - 1)
8004
                len = cmdlen - 1;
8005
8006
            /* copy the new command string into our command buffer */
8007
            memcpy(cmd, s, len);
8008
8009
            /* null-terminate the command buffer */
8010
            cmd[len] = '\0';
8011
        }
8012
        else
8013
        {
8014
            /* the value isn't important for any other type */
8015
            rundisc(ctx->voccxrun);
8016
8017
            /* if they returned nil, we're done processing the command */
8018
            if (typ == DAT_NIL)
8019
                return 0;
8020
        }
8021
    }
8022
8023
    /* break up into individual words */
8024
    if ((wrdcnt = voctok(ctx, cmd, buffer, wordlist, TRUE, FALSE, TRUE)) > 0)
8025
    {
8026
        /* skip any leading "and" and "then" separators */
8027
        for (cur = 0 ; cur < wrdcnt ; ++cur)
8028
        {
8029
            /* if this isn't "and" or "then", we're done scanning */
8030
            if (!vocspec(wordlist[cur], VOCW_THEN)
8031
                && !vocspec(wordlist[cur], VOCW_AND))
8032
                break;
8033
        }
8034
    }
8035
8036
    /* 
8037
     *   if we found no words, or we found only useless leading "and" and
8038
     *   "then" separators, run the "pardon" function to tell the player
8039
     *   that we didn't find any command on the line 
8040
     */
8041
    if (wrdcnt == 0 || (wrdcnt > 0 && cur >= wrdcnt))
8042
    {
8043
        runfn(ctx->voccxrun, ctx->voccxprd, 0);
8044
        return 0;
8045
    }
8046
8047
    /* 
8048
     *   if we got an error tokenizing the word list, return - we've
8049
     *   already displayed an error message, so there's nothing more for
8050
     *   us to do here 
8051
     */
8052
    if (wrdcnt < 0)
8053
        return 0;
8054
8055
    /* process the commands on the line */
8056
    for (first = TRUE, cmdActor = ctx->voccxactor = MCMONINV ; cur < wrdcnt ;
8057
         ++cur, first = FALSE)
8058
    {
8059
        /* presume we won't find an unknown word */
8060
        ctx->voccxunknown = 0;
8061
8062
        /* find the THEN that ends the command, if there is one */
8063
        for (next = cur ; cur < wrdcnt && !vocspec(wordlist[cur], VOCW_THEN)
8064
             ; ++cur) ;
8065
        wordlist[cur] = (char *)0;
8066
8067
        /* process until we run out of work to do */
8068
        for (;;)
8069
        {
8070
            /* try processing the command */
8071
            if (voc1cmd(ctx, &wordlist[next], cmd, &cmdActor, first))
8072
            {
8073
                /* 
8074
                 *   If the unknown word flag is set, try the command
8075
                 *   again from the beginning.  This flag means that we
8076
                 *   tried using parseUnknownDobj/Iobj to resolve the
8077
                 *   unknown word, but that failed and we need to try
8078
                 *   again with the normal "oops" processing.  
8079
                 */
8080
                if (ctx->voccxunknown > 0)
8081
                    continue;
8082
            
8083
                /* return an error */
8084
                return 1;
8085
            }
8086
8087
            /* success - we're done with the command */
8088
            break;
8089
        }
8090
8091
        /* if the rest of the command is to be ignored, we're done */
8092
        if (ctx->voccxflg & VOCCXFCLEAR)
8093
            return 0;
8094
8095
        /* scan past any separating AND's and THEN's */
8096
        while (cur + 1 < wrdcnt
8097
               && (vocspec(wordlist[cur+1], VOCW_THEN)
8098
                   || vocspec(wordlist[cur+1], VOCW_AND)))
8099
            ++cur;
8100
8101
        /* 
8102
         *   if another command follows, add a blank line to separate the
8103
         *   results from the previous command and those from the next
8104
         *   command 
8105
         */
8106
        if (cur + 1 < wrdcnt)
8107
            outformat("\\b");
8108
    }
8109
8110
    /* done */
8111
    return 0;
8112
}
8113
8114
8115
/*
8116
 *   Off-stack stack management 
8117
 */
8118
8119
/* allocate/reset the stack */
8120
void voc_stk_ini(voccxdef *ctx, uint siz)
8121
{
8122
    /* allocate it if it's not already allocated */
8123
    if (ctx->voc_stk_ptr == 0)
8124
    {
8125
        ctx->voc_stk_ptr = mchalo(ctx->voccxerr, (ushort)siz, "voc_stk_ini");
8126
        ctx->voc_stk_end = ctx->voc_stk_ptr + siz;
8127
    }
8128
    
8129
    /* reset the stack */
8130
    ctx->voc_stk_cur = ctx->voc_stk_ptr;
8131
}
8132
8133
/* allocate space from the off-stack stack */
8134
void *voc_stk_alo(voccxdef *ctx, uint siz)
8135
{
8136
    void *ret;
8137
    
8138
    /* round size up to allocation units */
8139
    siz = osrndsz(siz);
8140
8141
    /* if there's not space, signal an error */
8142
    if (ctx->voc_stk_cur + siz > ctx->voc_stk_end)
8143
        errsig(ctx->voccxerr, ERR_VOCSTK);
8144
8145
    /* save the return pointer */
8146
    ret = ctx->voc_stk_cur;
8147
8148
    /* consume the space */
8149
    ctx->voc_stk_cur += siz;
8150
8151
/*#define SHOW_HI*/
8152
#ifdef SHOW_HI
8153
{
8154
    static uint maxsiz;
8155
    if (ctx->voc_stk_cur - ctx->voc_stk_ptr > maxsiz)
8156
    {
8157
        char buf[20];
8158
        
8159
        maxsiz = ctx->voc_stk_cur - ctx->voc_stk_ptr;
8160
        sprintf(buf, "%u\n", maxsiz);
8161
        os_printz(buf);
8162
    }
8163
}
8164
#endif
8165
8166
8167
    /* return the space */
8168
    return ret;
8169
}
8170