diff options
author | Paul Gilbert | 2019-11-24 18:15:56 -0800 |
---|---|---|
committer | Paul Gilbert | 2019-11-27 21:10:29 -0800 |
commit | 28c3584148c49c1c6f118c5cbf13d0bf53d56726 (patch) | |
tree | 03102c6aeccebf3aec189deaf19cfd641673245e /engines/glk/agt/metacommand.cpp | |
parent | f9921a7a177dd506640211dd05751e046100a57b (diff) | |
download | scummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.tar.gz scummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.tar.bz2 scummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.zip |
GLK: AGT: Added subengine files
Diffstat (limited to 'engines/glk/agt/metacommand.cpp')
-rw-r--r-- | engines/glk/agt/metacommand.cpp | 1154 |
1 files changed, 1154 insertions, 0 deletions
diff --git a/engines/glk/agt/metacommand.cpp b/engines/glk/agt/metacommand.cpp new file mode 100644 index 0000000000..9324a0b054 --- /dev/null +++ b/engines/glk/agt/metacommand.cpp @@ -0,0 +1,1154 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/agt/agility.h" +#include "glk/agt/interp.h" +#include "glk/agt/exec.h" + +namespace Glk { +namespace AGT { + +/* This contains the code for scanning and running metacommands. + Note that while the code here deals with all of the flow-of-execution + details, the code for executing individual tokens is actually + in token.c (With a few exceptions for commands that impact + the order of execution). */ + +#define DEBUG_SCAN 1 + +#define MAX_REDIR 50000L /* Maximum number of redirects, to catch + infinite loops. If this is 0, allow infinitely + many */ + +#define MAX_SUBCALL 2047 /* Maximum number of subroutine calls. + If this is 0, no limit (except for the + program's stack size). */ + + +/* + +scan_metacommand + -2=end of cycle, something happened (disambiguation only) + 0=end of this cycle (disambig: end of cycle, nothing happened) + 1=end of all commands (disambig: end of cycle, nothing happened) + 2=end of turn (disambig: nothing happened) + +run_metacommand + 0 to go on to next metacommand, + 1 to stop running metacommands, and + 2 to end the turn. + 3 indicates that redirection has just occured + 4 indicates a subcall has just occured. + 5 to go on to next metacommand after a return has occured. + -2 means we're doing disambiguation and just hit an action token. + +*/ + + +/* ====================================================================*/ +/* RUN METACOMMAND: The following are the routines used to execute */ +/* a single metacommand block. run_metacommand is invoked by */ +/* scan_metacommand, which is further down in this file. */ +/* ====================================================================*/ + +/* ------------------------------------------------------------------- */ +/* TYPE CHECKING ROUTINES */ +/* Routines used to do type checking for metacommands. */ +/* ------------------------------------------------------------------- */ + + +rbool argvalid(int argtype, int arg) { + if (argtype & AGT_VAR) { /* We have a variable */ + /* First, verify that arg actually indexes a variable */ + if (arg < 0 || arg > VAR_NUM) return 0; /* Nope */ + + if (argtype == AGT_VAR) return 1; /* Pure variable; contents don't matter */ + + /* Next, verify its contents, using the rest of this routine */ + arg = agt_var[arg]; + argtype &= ~AGT_VAR; /* Mask off AGT_VAR */ + } + + if (argtype < 128) { + if (tnoun(arg)) return (argtype & AGT_ITEM) != 0; + if (troom(arg)) return (argtype & AGT_ROOM) != 0; + if (arg == 0) return (argtype & AGT_NONE) != 0; + if (arg == 1) return (argtype & AGT_SELF) != 0; + if (tcreat(arg)) return (argtype & AGT_CREAT) != 0; + if (arg == 1000) return (argtype & AGT_WORN) != 0; + return 0; + } else switch (argtype) { + case AGT_NUM: + return 1; + case AGT_DIR: + return (arg >= 1 && arg <= 12); + case AGT_FLAG: + return (arg >= 0 && arg <= FLAG_NUM); + case AGT_CNT: + return (arg >= 0 && arg <= CNT_NUM); + case AGT_QUEST: + return (arg >= 1 && arg <= MaxQuestion); + case AGT_MSG: + return (arg >= 1 && arg <= last_message); + case AGT_ERR: + return (arg >= 1 && arg <= NUM_ERR); + case AGT_STR: + return (arg >= 1 && arg <= MAX_USTR); + case AGT_SUB: + return (arg >= 1 && arg <= MAX_SUB); + case AGT_PIC: + return (arg >= 1 && arg <= maxpict); + case AGT_PIX: + return (arg >= 1 && arg <= maxpix); + case AGT_FONT: + return (arg >= 1 && arg <= maxfont); + case AGT_SONG: + return (arg >= 1 && arg <= maxsong); + case AGT_ROOMFLAG: + return (arg >= 1 && arg <= 32); + case AGT_EXIT: + return (argvalid(AGT_ROOM | AGT_NONE, arg) + || argvalid(AGT_MSG, arg - exitmsg_base) + || (arg < 0 && aver >= AGX00)); /* Treat as verb */ + case AGT_OBJFLAG: + return (arg >= 0 && arg < oflag_cnt); + case AGT_OBJPROP: + return (arg >= 0 && arg < oprop_cnt); + case AGT_ATTR: /* ATTR and PROP are type-checked elsewhere */ + case AGT_PROP: + return 1; + default: + writeln("INTERNAL ERROR:Unrecognized type specifier."); + } + return 0; +} + +/* <special> is set true for NOUN, OBJECT, NAME variables that are 0 */ +/* (In this case, some error handling is suppressed) */ + +static rbool argfix(int argtype, int *arg, int optype, rbool *special) { + *special = 0; + switch (optype) { + case 0: + break; /* Direct: The easy case */ + case 1: /* Variable */ + if (*arg == -1) { /* Top-of-stack */ + *arg = pop_expr_stack(); + break; + } + if (!argvalid(AGT_VAR, *arg)) return 0; + *arg = (int)agt_var[*arg]; + break; + case 2: + *arg = dobj; + *special = (dobj == 0); + break; /* NOUN */ + case 3: + *arg = iobj; + *special = (iobj == 0); + break; /* OBJECT */ + default: + rprintf("Internal error: Invalid optype."); + return 0; + } + if (!(optype & 2)) { + /* i.e. we have direct or variable type */ + /* The noun and object types below are useless for direct use, + but may be useful when used as values of variables. */ + if (argtype < 64) { + if (*arg == -1) { /* NAME */ + *arg = actor; + *special = (actor == 0); + } else if (*arg == -2) { /* NOUN */ + *arg = dobj; + *special = (dobj == 0); + } else if (*arg == -3) { /* OBJECT */ + *arg = iobj; + *special = (iobj == 0); + } + } + } + return argvalid(argtype, *arg); +} + + +/* These are handled in the order ARG2 then ARG1 so that + top-of-stack references will pop the stack in that order + (so that the push-order will corrospond to the argument order) */ +/* <grammer_arg> is true if "bad" argument is NOUN/OBJECT/etc. and + is 0. */ +static int argok(const opdef *opdata, int *arg1, int *arg2, int optype, + rbool *grammer_arg) { + if ((opdata->argnum) > 1 && !argfix(opdata->arg2, arg2, optype % 4, grammer_arg)) + return 0; + if ((opdata->argnum) > 0 && !argfix(opdata->arg1, arg1, optype / 4, grammer_arg)) + return 0; + return 1; +} + +/* ------------------------------------------------------------------- */ +/* INSTRUCTION DECODING ROUTINES */ +/* Routines for decoding opcodes and their arguments */ +/* ------------------------------------------------------------------- */ + +static int decode_instr(op_rec *oprec, const integer *data, int maxleng) { + integer op_; + int optype; + int leng; + rbool special_arg1; /* Is the first argument a special 0-length argument? */ + + oprec->negate = oprec->failmsg = oprec->disambig = 0; + oprec->errmsg = NULL; + oprec->op = -1; + oprec->opdata = &illegal_def; + oprec->argcnt = 0; + oprec->endor = 1; + + special_arg1 = 0; + + if (maxleng <= 0) { + oprec->errmsg = "GAME ERROR: Unexpected end of token sequence."; + return 1; + } + op_ = data[0]; + if (op_ < 0) { + oprec->errmsg = "GAME ERROR: Negative token found."; + return 1; + } + oprec->optype = optype = op_ / 2048; /* Split op_ into operand proper and optype */ + oprec->op = op_ = op_ % 2048; + oprec->opdata = get_opdef(op_); + + if (oprec->opdata == &illegal_def) { + if (op_ < START_ACT) + oprec->errmsg = "GAME ERROR: Illegal condition token encountered."; + else + oprec->errmsg = "GAME ERROR: Illegal action token encountered."; + return 1; + } + + if (op_ < 1000) oprec->endor = 0; /* Conditional tokens don't end OR block */ + + /* Recall that oprec->disambig is initialized to 0 */ + switch (op_) { + case 89: + case 95: + case 96: + case 97: + oprec->disambig = 1; + break; /* YesNo and Chance */ + case WIN_ACT: + case WIN_ACT+1: + oprec->disambig = 1; + break; /* WinGame, EndGame */ + + case 1037: + case 1038: /* DoSubroutine, Return */ + case 1062: + case 1115: /* RedirectTo, SetDisambigPriority */ + case 1132: /* AND */ + case 1149: + case 1150: /* Goto and OnFailGoto */ + case 1151: /* EndDisambig */ + case 1152: /* XRedirect */ + break; /* Accept default of 0: these tokens don' trigger disambig */ + + case 1135: + case 1137: + case 1138: + case 1139: + case 1140: + case 1141: + case 1142: + case 1143: + case 1147: + case 1159: + oprec->endor = 0; + break; /* Operations that only affect the stack don't + stop disambiguation, either. They also + don't mark the end of an OR block */ + + default: + /* Aside from the above exceptions, all actions will stop + disambiguation (with success) and all conditions will let it + continue. */ + oprec->disambig = (op_ >= START_ACT && op_ < WIN_ACT); + } + + if (op_ >= 1128 && op_ <= 1131) /* FailMessage group */ + oprec->failmsg = 1; + + leng = oprec->opdata->argnum + 1; + if (optype != 0) { /* Correct leng for NOUN and OBJECT args */ + special_arg1 = ((optype & 8) == 8); + leng -= special_arg1 + ((optype & 2) == 2); + if (leng < 1) { + oprec->errmsg = "GAME ERROR: Token list corrupted."; + return 1; + } + } + if (leng > maxleng) { + oprec->errmsg = "GAME ERROR: Unexpected end of token sequence"; + return 1; + } + + if (op_ == 108) { /* NOT */ + leng = 1 + decode_instr(oprec, data + 1, maxleng - 1); + oprec->negate = !oprec->negate; + return leng; + } + oprec->argcnt = leng - 1; + oprec->arg1 = oprec->arg2 = 0; + if (leng >= 2) { + if (special_arg1) { + assert(leng == 2); + oprec->arg2 = data[1]; + oprec->arg1 = 0; + } else oprec->arg1 = data[1]; + } + if (leng >= 3) oprec->arg2 = data[2]; + if (leng >= 4) writeln("INTERNAL ERROR: Too many token arguments."); + return leng; +} + + +/* decode_args checks and decodes the arguments to metacommand tokens */ +/* Returns false on an error */ +static rbool decode_args(int ip_, op_rec *oprec) { + rbool grammer_arg; /* Have NOUN/OBJECT that is 0 and so failed argok tests */ + + if (oprec->errmsg != NULL) { + if (!PURE_ERROR) + writeln(oprec->errmsg); + return 0; + } + if (DEBUG_AGT_CMD && !supress_debug) { + if (oprec->negate) { /* Output NOT */ + debug_cmd_out(ip_, 108, 0, 0, 0); + ip_++; + } + } + + if (DEBUG_AGT_CMD && !supress_debug) + debug_cmd_out(ip_, oprec->op, oprec->arg1, oprec->arg2, oprec->optype); + + /* This checks and translates the arguments */ + if (!argok(oprec->opdata, &(oprec->arg1), &(oprec->arg2), + oprec->optype, &grammer_arg)) { + /* Don't report errors for null NOUN/OBJECT/ACTOR arguments + used in conditional tokens */ + if (grammer_arg && oprec->op <= MAX_COND) + return 0; + if (!PURE_ERROR) { + if (DEBUG_AGT_CMD && !supress_debug) debugout("\n"); + writeln("GAME ERROR: Invalid argument to metacommand token."); + } + return 0; + } + return 1; +} + + + + +/* ------------------------------------------------------------------- */ +/* Subroutine Call Stack routines */ +/* ------------------------------------------------------------------- */ +/* Note: run_metacommand() passes subroutine calls up to it's parent, + but it processes Returns on its own (and is the routine responsible + for maintaining the subcall stack-- scan_metacommand treats + a subroutine call just like RedirecTo) */ +/* The progression for subroutine calls goes like this: + run_metacommand hits a DoSubroutine token; + the subroutine id is saved in subcall_arg by exec_token. + run_metacommand does push_subcall, saving cnum and ip, + and then returns 4 to scan_metacommand. + scan_metacommand saves grammar state to the new stack entry + with push_subcall and then starts scanning SUBROUTINEnn + + Many tokens are executed. + + run_metacommand hits Return. It sets restart_state and + returns 5 to its parent. + scan_metacommand then runs pop_subcall_grammar and restores + the original scanning grammer. It subtracts one from cnum + so the original cnum will be rerun. + run_metacommand sees that restart_state is set and pops the + rest of the information (cnum and ip) off of the stack. + Things continue as usual. + */ + + + +typedef struct { + /* run_metacommand state */ + short cnum, ip, failaddr; + /* scan_metacommand state */ + integer mactor, mdobj, miobj; + word mprep; + short vcode; + /* Global state (is this really saved?) */ + short vb; + word prep; +} subcall_rec; + + +static subcall_rec *substack = NULL; +static short subcnt = 0; +static short subsize = 0; + + +static rbool push_subcall(int cnum, int ip_, int failaddr) { + subcall_rec *savestack; /* In case something goes wrong. */ + + if (MAX_SUBCALL != 0 && ++subcnt > MAX_SUBCALL) + return 0; + if (subcnt > subsize) { + subsize += 5; + savestack = substack; + rm_trap = 0; + substack = (subcall_rec *)rrealloc(substack, subsize * sizeof(subcall_rec)); + rm_trap = 1; + if (substack == NULL) { /* out of memory */ + substack = savestack; + return 0; + } + } + substack[subcnt - 1].cnum = cnum; + substack[subcnt - 1].ip = ip_; + substack[subcnt - 1].failaddr = failaddr; + return 1; +} + + +/* pop_subcall_grammar is called before this */ +static void pop_subcall(int *rcnum, int *rip, int *rfailaddr) { + assert(*rcnum == substack[subcnt - 1].cnum); + /* *rcnum=substack[subcnt-1].cnum; */ + *rip = substack[subcnt - 1].ip; + *rfailaddr = substack[subcnt - 1].failaddr; + subcnt--; +} + +/* This is called after push_subcall */ +static void push_subcall_grammar(int m_actor, int vcode, int m_dobj, word m_prep, + int m_iobj, int cnum) { + /* run_metacommand should already have pushed cnum on the stack */ + substack[subcnt - 1].vb = vb; + substack[subcnt - 1].prep = prep; + substack[subcnt - 1].mactor = m_actor; + substack[subcnt - 1].vcode = vcode; + substack[subcnt - 1].mdobj = m_dobj; + substack[subcnt - 1].mprep = m_prep; + substack[subcnt - 1].miobj = m_iobj; +} + +/* Return false if something goes wrong-- such as stack underflow. */ +/* This is called *before* pop_subcall */ +static rbool pop_subcall_grammar(integer *m_actor, int *vcode, + integer *m_dobj, word *m_prep, integer *m_iobj, + int *cnum) { + if (subcnt == 0) return 0; + vb = substack[subcnt - 1].vb; + prep = substack[subcnt - 1].prep; + *cnum = substack[subcnt - 1].cnum; + *m_actor = substack[subcnt - 1].mactor; + *vcode = substack[subcnt - 1].vcode; + *m_dobj = substack[subcnt - 1].mdobj; + *m_prep = substack[subcnt - 1].mprep; + *m_iobj = substack[subcnt - 1].miobj; + return 1; +} + + + + +/* ------------------------------------------------------------------- */ +/* Run Metacommand */ +/* ------------------------------------------------------------------- */ + +static int run_metacommand(int cnum, int *redir_offset) +/* cnum=command number to run. */ +/* *redir_offset=offset of redirect header, if we exit with redirection. */ +/* Return + 0 to go on to next metacommand, + 1 to stop running metacommands, and + 2 to end the turn. + 3 indicates that redirection has just occured + 4 indicates a subcall has just occured. + 5 Is used to go on to the next metacommand after a Return. + -2 means we're doing disambiguation and just hit an action token. */ +{ + int ip_, oip; /* ip_=Instruction pointer, oip=Old instruction pointer */ + int r; /* Used to hold return value from token execution */ + int fail_addr; /* What address to jump to on failure */ + rbool fail; /* Last token was a conditional token that failed */ + rbool ortrue, blocktrue, orflag; /* OR stuff + orflag: Are we in an OR group? + ortrue: Is current OR group true? + blocktrue: Is current block w/in OR true? + */ + static rbool restart = 0; /* Restarting after subroutine? */ + op_rec currop; /* Information on the current token and its args */ + + fail_addr = 32000; /* Fall off the end when we fail */ + fail = 0; + ip_ = 0; + orflag = blocktrue = ortrue = 0; + *redir_offset = 1; /* Default: This is what RedirectTo does. + Only XRedirect can send a different value */ + + + if (restart) /* finish up Return from subroutine */ + pop_subcall(&cnum, &ip_, &fail_addr); + + if (DEBUG_AGT_CMD && !supress_debug) { + debug_head(cnum); + if (restart) debugout(" (Resuming after subroutine)\n"); + } + + restart = 0; + + + /* ========== Main Loop ================= */ + while (ip_ < command[cnum].cmdsize) { + + oip = ip_; + ip_ += decode_instr(&currop, command[cnum].data + ip_, command[cnum].cmdsize - ip_); + + /* ------- OR Logic --------------- */ + if (currop.op == 109) { /* OR */ + if (!orflag) { /* First OR; set things up */ + orflag = 1; + ortrue = 0; + blocktrue = 1; + } + blocktrue = blocktrue && !fail; /* Was the previous token true? */ + fail = 0; + ortrue = ortrue || blocktrue; /* OR in last block */ + blocktrue = 1; /* New block starts out true. */ + } else if (orflag) { /* we're in the middle of a block */ + blocktrue = blocktrue && !fail; /* Add in previous token */ + fail = 0; + if (currop.endor) { /* i.e. not a conditional token */ + orflag = 0; /* End of OR block */ + ortrue = ortrue || blocktrue; /* OR in last block */ + fail = !ortrue; /* Success of whole group */ + } + } + + /* ------------ FAILMESSAGE handling ------------- */ + if (currop.failmsg) { /* Is the current token a Fail... token? */ + if (!fail) continue; /* Skip it; look at next instruction */ + /* ErrMessage and ErrStdMessage: set disambiguation score */ + if (do_disambig) { + if (currop.op == 1130 || currop.op == 1131) { + if (!decode_args(oip, &currop)) return 2; + disambig_score = currop.arg1; + return 2; + } else return -2; /* FailMessage counts as an action token */ + } + /* Then run the failmessage, skipping the following step... */ + } + /* -------- Failure routines -------------------- */ + else if (fail) { /* ... and not failmessage */ + /* consequences of failure */ + fail = 0; /* In case fail_addr doesn't point off the edge of the world */ + ip_ = fail_addr; + fail_addr = 32000; /* Reset fail_addr */ + continue; /* Usually fail_addr will fall off the end, causing this to + return 0 */ + } + + /* - Finish decoding arguments and print out debugging message - */ + if (!decode_args(oip, &currop)) { + if (currop.op < 1000) fail = currop.negate ? 0 : 1; + continue; + /* return 2;*/ + } + + /* -------- Commands that need to be handled specially -------------- */ + if (currop.op == 109) { /* OR */ + if (DEBUG_AGT_CMD && !supress_debug) debug_newline(op, 0); + continue; /* OR: skip further processing */ + } + + if (currop.op == 1037) { /* DoSubroutine */ + if (!push_subcall(cnum, ip_, fail_addr)) { + writeln("GAME ERROR: Subroutine stack overflow."); + return 2; + } + subcall_arg = currop.arg1; + if (DEBUG_AGT_CMD && !supress_debug) debugout("--> Call\n"); + return 4; + } + + if (currop.op == 1038) { /* Return */ + restart = 1; + if (DEBUG_AGT_CMD && !supress_debug) debugout("--> Return\n"); + return 5; + } + + if (currop.op == 1149) { /* Goto */ + ip_ = currop.arg1; + if (DEBUG_AGT_CMD && !supress_debug) debugout("\n"); + continue; + } + + if (currop.op == 1150) { /* OnFailGoto */ + fail_addr = currop.arg1; + if (DEBUG_AGT_CMD && !supress_debug) debugout("\n"); + continue; + } + + if (currop.op == 1152) /* XRedirect */ + *redir_offset = currop.arg1; + + /* ---------- Disambiguation Success -------------- */ + if (do_disambig && currop.disambig) { + if (DEBUG_AGT_CMD && !supress_debug) debugout("==> ACTION\n"); + return -2; + } + + /* ---------- Run normal metacommands -------------- */ + switch (r = exec_instr(&currop)) { + case 0: /* Normal action token or successful conditional token */ + if (DEBUG_AGT_CMD && !supress_debug) debug_newline(op, 0); + continue; + case 1: /* Conditional token: fail */ + if (DEBUG_AGT_CMD && !supress_debug) { + if (orflag) debugout(" (-->FAIL)\n"); + else debugout("--->FAIL\n"); + } + fail = 1; + continue; + default: /* Return explicit value */ + if (DEBUG_AGT_CMD && !supress_debug) { + if (r == 103) debugout("-->Redirect\n"); + else debugout("==> END\n"); + } + return r - 100; + } + } + return 0; +} + + + +/* ====================================================================*/ +/* SCAN METACOMMAND: These are the routines that scan through the */ +/* metacommand headers and find the appropriate ones to execute */ +/* Redirection is also handled at this level */ +/* ====================================================================*/ + + +/* ------------------------------------------------------------------- */ +/* Support routines for extracting object information */ +/* ------------------------------------------------------------------- */ + +/* For $ strings. Returns object number if there is one, or negative + the dictionary index. + This is used by the metacommand redirection routines */ + +static integer expand_redirect(word w) { + assert(w != -1); /* <*NONE*> object shouldn't make it this far */ + if (w == 0 || aver < AGTME10) return -w; + if (w == ext_code[wdverb]) return -syntbl[auxsyn[vb]]; + if (w == ext_code[wdnoun]) return dobj; + if (w == ext_code[wdobject]) return iobj; + if (w == ext_code[wdname]) return actor; + if (w == ext_code[wdadjective]) return -it_adj(dobj); + if (w == ext_code[wdprep]) return -prep; + return -w; +} + + +static int extract_actor(int actnum) { + if (actnum < 0) actnum = -actnum; /* Erase redirection stuff */ + if (tcreat(actnum)) return actnum; + else return 0; +} + +/* Basically, we need to find an object with a matching noun + and adj to our choice. */ +static int extract_obj(word name, word adj) { + int i, obj; + + /* We just take the first one. We split this into separate noun and + creature loops for performance reaons */ + + if (name == -1) /* <*NONE*> */ + return 0; + + obj = expand_redirect(name); + adj = it_name(expand_redirect(adj)); + + if (obj > 0) { /* $noun$, $object$, or $name$ */ + if (adj == 0 || adj == it_adj(obj)) + return obj; /* We're done */ + name = it_name(obj); + } else + name = -obj; + + if (adj == 0) return -name; /* Adjectives required for CLASS redirect */ + nounloop(i) + if (noun[i].name == name && noun[i].adj == adj) return i + first_noun; + creatloop(i) + if (creature[i].name == name && creature[i].adj == adj) + return i + first_creat; + /* Hmm... just hope it's an internal noun. */ + writeln("GAME ERROR: Redirect statement with bad object name."); + return -name; +} + + +/* ------------------------------------------------------------------- */ +/* Redirection Routines */ +/* ------------------------------------------------------------------- */ + + +#define wordcode_fix(w) it_name(expand_redirect(w)); + +/* 'real_obj' below is the dobj_obj/iobj_obj field; it takes + precedence over anything else if it is nonzero. + It represents an *explicitly* declared object in + the header */ + +static void fix_objnum(integer *objnum, word match, + int real_obj, + int actor_, int dobj_, int iobj_) { + if (real_obj) *objnum = real_obj; + else if (match == ext_code[wdobject]) *objnum = iobj_; + else if (match == ext_code[wdnoun]) *objnum = dobj_; + else if (match == ext_code[wdname]) *objnum = actor_; +} + +/* Returns TRUE if we changed *objrec, FALSE otherwise */ +/* (This is needed for memory allocation purposes) */ +static rbool fix_objrec(parse_rec **objrec, word match, + int real_obj, + parse_rec *actrec, parse_rec *dobjrec, + parse_rec *iobjrec) { + if (real_obj) *objrec = make_parserec(real_obj, NULL); + else if (match == ext_code[wdobject]) *objrec = copy_parserec(iobjrec); + else if (match == ext_code[wdnoun]) *objrec = copy_parserec(dobjrec); + else if (match == ext_code[wdname]) *objrec = copy_parserec(actrec); + else return 0; /* *objrec unchanged */ + + return 1; /* *objrec changed */ +} + +static void objcode_fix(cmd_rec *cmd) +/* For $ strings. Fixes object redirection if neccessary */ +{ + int actorword; + word nounword, objword; + int dobj_obj, iobj_obj; + int savedobj, saveactor; + parse_rec *savedrec, *saveactrec, *saveirec; + rbool achange, dchange, ichange; /* Did the given _rec ptr change? */ + + /* dobj_obj/iobj_obj take precedence over anything else */ + actorword = cmd->actor; + nounword = cmd->nouncmd; + objword = cmd->objcmd; + dobj_obj = cmd->noun_obj; + iobj_obj = cmd->obj_obj; + + /* Make temporary copies of things for when more than one thing is + being shuffled around; we don't need to save iobj since + it's processed last */ + saveactor = actor; + saveactrec = actor_rec; + savedobj = dobj; + savedrec = dobj_rec; + saveirec = iobj_rec; /* Saved only so it can be freed */ + + /* Fix object numbers... */ + fix_objnum(&actor, actorword, 0, saveactor, savedobj, iobj); + fix_objnum(&dobj, nounword, dobj_obj, saveactor, savedobj, iobj); + fix_objnum(&iobj, objword, iobj_obj, saveactor, savedobj, iobj); + + /* ... and records */ + achange = fix_objrec(&actor_rec, actorword, 0, saveactrec, savedrec, iobj_rec); + dchange = fix_objrec(&dobj_rec, nounword, dobj_obj, saveactrec, savedrec, iobj_rec); + ichange = fix_objrec(&iobj_rec, objword, iobj_obj, saveactrec, savedrec, iobj_rec); + + /* Free up whatever needs freeing */ + if (achange) rfree(saveactrec); + if (dchange) rfree(savedrec); + if (ichange) rfree(saveirec); +} + + +/* Redirection is very superficial-- normally all it does is */ +/* change the matching pattern, not the underlying objects */ +/* The one exception is when we use the special redirection tokens */ +/* NOUN or OBJECT */ + +void redirect_exec(cmd_rec *cmd, word *m_actor, int *vcode, + word *m_dobj, word *m_prep, word *m_iobj) { + *m_actor = extract_actor(cmd->actor); + vb = *vcode = verb_code(it_name(expand_redirect(cmd->verbcmd))); + *m_dobj = extract_obj(cmd->nouncmd, cmd->noun_adj); + if (cmd->prep == -1) + *m_prep = 0; + else + *m_prep = it_name(expand_redirect(cmd->prep)); + *m_iobj = extract_obj(cmd->objcmd, cmd->obj_adj); + + /* This shuffles the _real_ objects if $noun$ forms are being + used */ + objcode_fix(cmd); +} + + + + +/* ------------------------------------------------------------------- */ +/* Scan Metacommand and the matching function it uses */ +/* ------------------------------------------------------------------- */ + +/* This is used to match the elements of metacommand trigger patterns */ +/* Sees if w2 matches COMMMAND pattern word w1; w1==0 corresponds to ANY */ +#define cmatch(w1,w2) ((w1)==0 || (w1)==(w2) || ((w1)==-1 && (w2)==0)) + +static int cm_actor(int actnum, int actor_) +/* cmd: actnum, player entry: actor_ */ +{ + if (aver < AGX00) return 1; /* Bit of AGT brain-deadness. */ + if (actnum == 1) return actor_ == 0; /* No actor_: just the player */ + if (tcreat(actnum)) + return (creat_fix[actor_ - first_creat] == creat_fix[actnum - first_creat]); + if (actnum == 2) return (actor_ != 0); /* ANYBODY? */ + return (actor_ == 0); +} + + +/* Check that the explicit object matches */ +static rbool cm_x_obj(int x_obj, int real_obj) { + if (x_obj == 0) return 1; /* No explicit object; automatically match. */ + /* Explicit object case */ + /* In this case, we match against the _real_ object */ + /* However, we also require a "normal" match */ + do { + if (x_obj == real_obj) return 1; + real_obj = it_class(real_obj); + } while (real_obj != 0); + return 0; +} + +/* Does [obj] match <adj> <noun> [x_obj]? */ +/* --[obj] must match up with <adj> <noun> */ +/* --If x_obj(the explicit object) is defined, it must match with + the "real" object-- that is, the global dobj or iobj value. */ +static rbool cm_obj(word name, word adj, int x_obj, int obj, int real_obj) { + if (name == -1) return (obj == 0); /* <NONE> */ + + if (x_obj && !cm_x_obj(x_obj, real_obj)) return 0; + + /* (Note that ANY does not match ALL) */ + if (obj == -ext_code[wall]) + return (name == ext_code[wall] && adj == 0); + + do { /* Work our way up the class hierarchy */ + if (cmatch(name, it_name(obj)) && cmatch(adj, it_adj(obj))) + return 1; + obj = it_class(obj); + } while (obj != 0); + + return 0; +} + + + +static void scan_dbg(int vcode) { + char buff[220]; + word w; + + if (vcode >= BASE_VERB && vcode < BASE_VERB + DUMB_VERB + && syntbl[synlist[vcode]] != 0) + w = syntbl[synlist[vcode]]; + else w = syntbl[auxsyn[vcode]]; + + if (strlen(dict[w]) > 200) return; /* Just in case... */ + sprintf(buff, "+++++Scanning %s\n", dict[w]); + debugout(buff); +} + +#define not_any(n,a) (n!=0 || a!=0) + +/* This returns true if we redirect from VERB OBJ {PREP OBJ} + to something that has fewer objects or no (explicit) preposition. + This is less perfect than I would like since there is currently + no way of distinguishing between ANY and an empty slot unless + the new "NOMATCH" extension is used. */ + +static rbool redir_narrows_grammar(cmd_rec *cmd1, cmd_rec *cmd2) { + /* Check inward from obj to prep to noun; if in any of these + fields cmd2 has ANY and cmd1 doesn't, return 1. + Stop as soon as we find a non-ANY field in either one. */ + + /* If we *are* using the new extension, we can just use that info */ + if (cmd2->objcmd == -1) { + if (cmd1->objcmd != -1) return 1; + if (cmd1->prep == -1) { + if (cmd1->prep != -1) return 1; + if (cmd2->nouncmd == -1 && cmd1->objcmd != -1) return 1; + } + } + if (nomatch_aware) return 0; /* If we are using nomatch, don't need + to go through the rest of this nonsense. */ + + if (not_any(cmd2->objcmd, cmd2->obj_adj)) return 0; + if (not_any(cmd1->objcmd, cmd1->obj_adj)) return 1; + + if (cmd2->prep != 0) return 0; + if (cmd1->prep != 0) return 1; + + if (not_any(cmd2->nouncmd, cmd2->noun_adj)) return 0; + if (not_any(cmd1->nouncmd, cmd1->noun_adj)) return 1; + + return 0; /* They are both all ANY. */ +} + + + +static rbool cm_command(cmd_rec *cmd, + integer m_actor, int m_verb, + integer m_dobj, word m_prep, integer m_iobj) { + if (cmd->verbcmd == 0) { /* ANY */ + if (cmd->actor == 0 && aver >= AGX00) + return (m_verb == 0); /* ANY command: rest of line ignored */ + /* Else ANY matchs; go on to test other things. */ + } else if (cmd->verbcmd != m_verb) return 0; + + return + cm_actor(cmd->actor, m_actor) + && cm_obj(cmd->nouncmd, cmd->noun_adj, cmd->noun_obj, m_dobj, dobj) + && cmatch(cmd->prep, m_prep) + && cm_obj(cmd->objcmd, cmd->obj_adj, cmd->obj_obj, m_iobj, iobj); +} + + + +static void scan_for_actor(integer m_actor, int *start, int *end) { + int i; + + assert(m_actor != 0); + + if (aver >= AGX00) { + if (start != NULL) *start = verbptr[DIR_ADDR_CODE]; + *end = verbend[DIR_ADDR_CODE]; + return; + } + for (i = verbend[DIR_ADDR_CODE]; i > verbptr[DIR_ADDR_CODE]; i--) + if (creat_fix[command[i].actor - first_creat] + == creat_fix[m_actor - first_creat]) { + i++; + break; + } + *end = i; + + if (start == NULL) return; + + for (i = verbptr[DIR_ADDR_CODE]; i <= *end; i++) + if (creat_fix[command[i].actor - first_creat] + == creat_fix[m_actor - first_creat]) + break; + *start = i; +} + + +/* m_<word> are the matching criterion; they have no *neccessary* + connection to dobj, iobj, etc. */ + +int scan_metacommand(integer m_actor, int vcode, + integer m_dobj, word m_prep, integer m_iobj, + int *redir_flag) +/* Return codes: 0=end of this cycle, 1=end of all commands + 2=end of turn */ +/* If doing disambiguation, then -2=end of cycle, something happened; + 0 or 1=end of cycle; nothing happened; 2=end of turn, nothing happened. */ +/* If redir_flag is non-NULL, it is set when redirection occurs: + 1+=Redirection occured + 2=Grammar-changing redirection occured. */ +{ + int i, oldi; + word m_verb; + int scanend; + int redir_offset; /* Used for multiple redirects in the same + metacommand (which can occur in AGATE-style + commands)-- this is used to hold the offset + of the given redirect. */ + long redirect_count; /* This is a safety measure: this keeps track of how + many redirections have occured on a single turn, and + if there are "too many" it will issue an error message + and stop. This is to prevent the system from getting + into a redirection loop. The number should be set + high enough not to prevent deliberate loops, + however. */ + + rfree(substack); + subcnt = 0; + subsize = 0; + redirect_count = 0; + + if (mars_fix) + if (vcode == 0 || m_actor == 2) return 0; + /* Don't explicity scan ANY metacommands if MARS fix is active. */ + if (m_actor == -ext_code[weverybody]) m_actor = 2; + + + if (DEBUG_AGT_CMD && DEBUG_SCAN && !supress_debug) scan_dbg(vcode); + + m_verb = syntbl[auxsyn[vcode]]; + if (m_actor == 0) { + i = verbptr[vcode]; + scanend = verbend[vcode]; + } else + scan_for_actor(m_actor, &i, &scanend); + for (; i < scanend; i++) + if (command[i].actor < 0) { + /* REDIRECT data; skip over it */; + } else if (cm_command(&command[i], m_actor, m_verb, m_dobj, m_prep, m_iobj)) + switch (run_metacommand(i, &redir_offset)) { + case -2: + rfree(substack); + return -2; + /* We are doing disambiguation and reached + an action token */ + case 0: + break; /* Go onto next metacommand */ + case 1: + rfree(substack); + return 1; /* Done with metacommands */ + case 2: + rfree(substack); + return 2; /* Done with turn */ + + + /* -------- REDIRECTION ------------ */ + /* This handles RedirectTo tokens */ + case 3: + oldi = i; + i += redir_offset; + if (i == last_cmd || command[i].actor > 0) { + if (!PURE_ERROR) writeln("GAME ERROR: Invalid REDIRECT token."); + rfree(substack); + return 2; + } + if (MAX_REDIR != 0 && ++redirect_count > MAX_REDIR) { + if (!PURE_ERROR) writeln("GAME ERROR: Infinite REDIRECT loop."); + rfree(substack); + return 2; + } + if (DEBUG_AGT_CMD && !supress_debug) { + debugout(" ==>"); + debug_head(i); + } + + /* REDIRECT :If we do a redirect from a broader grammar to a + narrower grammer, it will be noted so that certain types + of grammer checking can be disabled. */ + if (redir_flag != NULL) { + if (*redir_flag < 2 + && redir_narrows_grammar(&command[oldi], &command[i])) + *redir_flag = 2; + + /* Set *redir_flag to at least 1 if we do *any* redirection. */ + if (!*redir_flag) *redir_flag = 1; + } + + /* REDIRECT: Do the actual redirection, building the new command + header and shuffling around nouns and verbs as + neccessary */ + redirect_exec(&command[i], &m_actor, &vcode, + &m_dobj, &m_prep, &m_iobj); + + /* REDIRECT: Start scanning again from the beginning */ + if (!mars_fix) {/* In MARS, we *don't* go back to the top */ + if (m_actor != 0) + scan_for_actor(m_actor, &i, &scanend); + else { + i = verbptr[vcode]; + scanend = verbend[vcode]; + } + i--; /* Back up one so that the following i++ we'll + be at the right location */ + } + + /* So when i is incremented, we start back at the correct start: i.e. + we start scanning again from the beginning. It's even possible + to use REDIRECT to run verb commands from an AFTER command, + although it precludes other AFTER commands from running. */ + m_verb = syntbl[auxsyn[vcode]]; + break; + + + + /* -------- SUBROUTINE CALL ------------ */ + case 4: /* Subroutine Call -- same idea as RedirectTo, + but less complicated */ + push_subcall_grammar(m_actor, vcode, m_dobj, m_prep, m_iobj, i); + vcode = verb_code(sub_name[subcall_arg - 1]); + m_actor = m_dobj = m_iobj = 0; + m_prep = 0; + + if (!mars_fix) /* In MARS, we *don't* go back to the top */ + i = verbptr[vcode] - 1; + scanend = verbend[vcode]; + m_verb = syntbl[auxsyn[vcode]]; + break; + + + /* -------- RETURN ------------ */ + case 5: /* Return: pop grammar state, then ... ? */ + if (!pop_subcall_grammar(&m_actor, &vcode, + &m_dobj, &m_prep, &m_iobj, &i)) { + writeln("GAME ERROR: Return without DoSubroutine."); + rfree(substack); + return 2; + } + + if (m_actor == 0) + scanend = verbend[vcode]; + else + scan_for_actor(m_actor, NULL, &scanend); + m_verb = syntbl[auxsyn[vcode]]; + + i--; /* Cause the last command to restart, + at which point run_command will pop the rest of the + stack. */ + + break; + } + rfree(substack); + return 0; /* Done with this cycle of metacommands */ +} + +/* ====================================================================*/ + +#undef cm + +} // End of namespace AGT +} // End of namespace Glk |