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