scummvm/engines/glk/agt/metacommand.cpp
Thunderforge 35c3668275 COMMON: Fixing misspellings "occured" -> "occurred"
Fixing this misspelled word across multiple parts of the codebase.
2021-09-25 21:36:42 +03:00

1155 lines
35 KiB
C++

/* 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 occurred
4 indicates a subcall has just occurred.
5 to go on to next metacommand after a return has occurred.
-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 occurred
4 indicates a subcall has just occurred.
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 occurred
2=Grammar-changing redirection occurred. */
{
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 occurred 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:
default:
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