/* 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 3 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, see .
*
*/
#include "glk/hugo/hugo.h"
namespace Glk {
namespace Hugo {
int Hugo::EvalExpr(int p) {
int n1, n2;
int oper;
short result = 0; /* must be 16 bits */
/* for precedence stacking/unstacking */
int next_prec, this_prec, temp_lp;
if (!evalcount) return 0; /* no expression */
do
{
if (eval[p]==1)
{
if (eval[p+1]==OPEN_BRACKET_T ||
eval[p+1]==OPEN_SQUARE_T)
{
eval[p] = 0;
eval[p+1] = EvalExpr(p+2);
TrimExpr(p+2);
}
else if (eval[p+1]==MINUS_T)
{
TrimExpr(p);
eval[p+1] = -eval[p+1];
}
}
if (evalcount<=p+2)
{
result = eval[p+1];
TrimExpr(p);
eval[p] = 0;
eval[p+1] = result;
goto ReturnResult;
}
n1 = eval[p+1];
oper = eval[p+3];
/* At this point, holds the first value, and holds
the token number of the operator.
*/
if (eval[p+4]==1 && (eval[p+5]==OPEN_BRACKET_T
|| eval[p+5]==OPEN_SQUARE_T))
{
eval[p+4] = 0;
eval[p+5] = EvalExpr(p+6);
TrimExpr(p+6);
}
n2 = eval[p+5];
if (evalcount > p+7)
{
if (eval[p+3]==CLOSE_BRACKET_T && eval[p+2]==1)
{
TrimExpr(p+2);
return eval[p+1];
}
/* eval[p+7] holds the next operator, i.e., the "*"
in: "x + y * z"
This way, we can check if the upcoming operator
takes precedence over the current one.
*/
if ((next_prec = Precedence(eval[p+7]))
< (this_prec = Precedence(oper)))
{
if (next_prec >= last_precedence)
{
#if defined (DEBUG_PRECEDENCE)
sprintf(line, "Not preferring %s to %s because of previous level %d", token[eval[p+7]], token[oper], last_precedence);
Printout(line);
#endif
goto ReturnResult;
}
#if defined (DEBUG_PRECEDENCE)
sprintf(line, "Preferring %s to %s", token[eval[p+7]], token[oper]);
Printout(line);
#endif
temp_lp = last_precedence;
last_precedence = this_prec;
n2 = EvalExpr(p+4);
last_precedence = temp_lp;
}
}
else if (Precedence(oper)>=last_precedence)
{
goto ReturnResult;
}
#if defined (DEBUG_PRECEDENCE)
sprintf(line, "Solving %d %s %d", n1, token[oper], n2);
Printout(line);
#endif
switch (oper)
{
case DECIMAL_T:
{
result = GetProp(n1, n2, 1, 0);
break;
}
case EQUALS_T:
{
result = (n1==n2);
break;
}
case MINUS_T:
{
result = n1 - n2;
break;
}
case PLUS_T:
{
result = n1 + n2;
break;
}
case ASTERISK_T:
{
result = n1 * n2;
break;
}
case FORWARD_SLASH_T:
{
if (n2==0)
#if defined (DEBUGGER)
{
RuntimeWarning("Division by zero: invalid result");
result = 0;
}
#else
FatalError(DIVIDE_E);
#endif
result = n1 / n2;
break;
}
case PIPE_T:
{
result = n1 | n2;
break;
}
case GREATER_EQUAL_T:
{
result = (n1>=n2);
break;
}
case LESS_EQUAL_T:
{
result = (n1<=n2);
break;
}
case NOT_EQUAL_T:
{
result = (n1!=n2);
break;
}
case AMPERSAND_T:
{
result = n1 & n2;
break;
}
case GREATER_T:
{
result = (n1 > n2);
break;
}
case LESS_T:
{
result = (n1 < n2);
break;
}
case AND_T:
{
result = (n1 && n2);
break;
}
case OR_T:
{
result = (n1 || n2);
break;
}
default:
{
result = n1;
}
}
#if defined (DEBUGGER)
if ((debug_eval) && debug_eval_error) return 0;
#endif
TrimExpr(p+4); /* second value */
TrimExpr(p+2); /* operator */
eval[p] = 0;
eval[p+1] = result;
/* Keep looping while there are expression elements, or until there
is a ")", "]", or end of line
*/
} while ((evalcount>p+2) && !(eval[p+2]==1 &&
(eval[p+3]==CLOSE_BRACKET_T || eval[p+3]==CLOSE_SQUARE_T ||
eval[p+3]==255)));
result = eval[p+1];
TrimExpr(p); /* first value */
ReturnResult:
#if defined (DEBUG_EXPR_EVAL)
if (p==0 && exprt)
{
Common::sprintf_s(line, " = %d", result);
AP(line);
}
#endif
return result;
}
int Hugo::GetVal() {
char a = 0;
char tempinexpr, tempgetaddress, tempinobj;
int i, j;
int tempret;
unsigned short routineaddr, arrayaddr; /* must be 16 bits */
short val = 0;
char inctype = 0;
int preincdec; /* pre-increment/decrement */
defseg = gameseg;
tempret = ret;
tempinexpr = inexpr;
inexpr = 0;
preincdec = incdec;
incdec = 0;
switch (MEM(codeptr))
{
case AMPERSAND_T: /* an address */
{codeptr++;
getaddress = true;
val = GetValue();
getaddress = false;
break;}
case ROUTINE_T:
case CALL_T:
{
if (MEM(codeptr)==ROUTINE_T)
{
if (tail_recursion==0 && MEM(codeptr-1)==RETURN_T)
{
/* We may be able to tail-recurse this return
statement if it's simply 'return Routine(...)'
*/
tail_recursion = TAIL_RECURSION_ROUTINE;
}
routineaddr = PeekWord(++codeptr);
codeptr += 2;
if (getaddress)
{val = routineaddr;
getaddress = false;
break;}
}
else
{
codeptr++;
routineaddr = GetValue();
}
#if defined (DEBUGGER)
if (debug_eval)
{
debug_eval_error = true;
val = 0;
break;
}
#endif
val = CallRoutine(routineaddr);
break;
}
case OPEN_BRACKET_T:
{
codeptr++;
inexpr = 1;
tempgetaddress = getaddress;
getaddress = false;
SetupExpr();
inexpr = 0;
val = EvalExpr(0);
getaddress = tempgetaddress;
break;
}
case MINUS_T:
{
codeptr++;
j = inexpr; /* don't reuse tempinexpr */
inexpr = 1;
val = -GetValue();
inexpr = (char)j;
break;
}
case VALUE_T: /* integer 0 - 65535 */
case OBJECTNUM_T:
case DICTENTRY_T:
{
val = PeekWord(++codeptr);
codeptr += 2;
break;
}
case ATTR_T:
case PROP_T:
{
val = MEM(++codeptr);
codeptr++;
break;
}
case VAR_T: /* variable */
{
val = var[(i=MEM(++codeptr))];
if (game_version >= 22)
{
/* Pre-v2.4 included linelength and pagelength as
global variables after objectcount
*/
if (i <= ((game_version>=24)?objectcount:objectcount+2))
{
if (i==wordcount) val = words;
else if (i==objectcount) val = objects;
/* i.e., pre-v2.4 only */
else if (i==objectcount+1)
{
#if defined (ACTUAL_LINELENGTH)
val = ACTUAL_LINELENGTH();
#else
val = SCREENWIDTH/charwidth;
#endif
}
else if (i==objectcount+2)
val = SCREENHEIGHT/lineheight;
}
}
codeptr++;
if (!inobj) inctype = IsIncrement(codeptr);
/* don't operate on, e.g., ++variable.property as
(++variable).property
*/
if ((incdec || preincdec) && MEM(codeptr)!=DECIMAL_T)
{
if (i < MAXGLOBALS) SaveUndo(VAR_T, i, val, 0, 0);
if (inctype) val = Increment(val, inctype);
/* still a post-increment hanging around */
var[i] = (val+=preincdec) + incdec;
incdec = preincdec = 0;
}
break;
}
case TRUE_T:
val = 1;
codeptr++;
break;
case FALSE_T:
val = 0;
codeptr++;
break;
case TILDE_T:
codeptr++;
val = ~GetValue();
break;
case NOT_T:
codeptr++;
val = !GetValue();
break;
case ARRAYDATA_T:
case ARRAY_T:
{
unsigned int element;
if (MEM(codeptr)==ARRAY_T)
{
codeptr++;
arrayaddr = GetValue();
}
else
{
arrayaddr = PeekWord(++codeptr);
codeptr += 2;
}
if (MEM(codeptr)!=OPEN_SQUARE_T)
{val = arrayaddr;
break;}
if (game_version>=22)
{
/* convert to word value */
arrayaddr*=2;
if (game_version>=23)
/* space for array length */
a = 2;
}
/* check if this is array[] (i.e., array length) */
if (MEM(++codeptr)==CLOSE_SQUARE_T)
{
defseg = arraytable;
val = PeekWord(arrayaddr);
codeptr++;
break;
}
tempinobj = inobj;
inobj = 0;
j = GetValue();
inobj = tempinobj;
/* The array element we're after: */
element = arrayaddr+a + j*2;
defseg = arraytable;
#if defined (DEBUGGER)
CheckinRange(element, debug_workspace, "array data");
#endif
/* Check to make sure we've got a sane element number */
if ((element>0) && (element < (unsigned int)(dicttable-arraytable)*16))
val = PeekWord(element);
else
val = 0;
codeptr++;
if (!inobj) inctype = IsIncrement(codeptr);
/* Don't operate on the array on:
++a[n].property
*/
if ((incdec || preincdec) && MEM(codeptr)!=DECIMAL_T)
{
/* Same sanity check for element number */
if ((element>0) && (element < (unsigned)(dicttable-arraytable)*16))
{
if (inctype) val = Increment(val, inctype);
/* still a post-increment hanging around */
SaveUndo(ARRAYDATA_T, arrayaddr+a, j, val, 0);
PokeWord(element, (val+=preincdec) + incdec);
incdec = preincdec = 0;
}
}
break;
}
case RANDOM_T:
{
codeptr += 2; /* skip the "(" */
val = GetValue();
if (val!=0)
#if !defined (RANDOM)
val = (hugo_rand() % val)+1;
#else
val = (RANDOM() % val)+1;
#endif
if (MEM(codeptr)==2) codeptr++;
break;
}
case WORD_T:
{
codeptr += 2; /* skip the "[" */
if (MEM(codeptr)==CLOSE_SQUARE_T) /* words[] */
{
val = words;
break;
}
val = wd[GetValue()];
if (MEM(codeptr)==CLOSE_SQUARE_T) codeptr++;
break;
}
case CHILDREN_T:
{
codeptr += 2; /* skip the "(" */
val = GetValue();
if (MEM(codeptr)==CLOSE_BRACKET_T) codeptr++;
val = Children(val);
break;
}
case PARENT_T:
case SIBLING_T:
case CHILD_T:
case YOUNGEST_T:
case ELDEST_T:
case YOUNGER_T:
case ELDER_T:
{
i = MEM(codeptr);
codeptr += 2; /* skip the "(" */
val = GetValue();
if (MEM(codeptr)==CLOSE_BRACKET_T) codeptr++;
switch (i)
{
case PARENT_T:
val = Parent(val);
break;
case SIBLING_T:
case YOUNGER_T:
val = Sibling(val);
break;
case CHILD_T:
case ELDEST_T:
val = Child(val);
break;
case YOUNGEST_T:
val = Youngest(val);
break;
case ELDER_T:
val = Elder(val);
break;
}
break;
}
case SAVE_T:
val = RunSave();
codeptr++;
break;
case RESTORE_T:
val = RunRestore();
codeptr++;
break;
case SCRIPTON_T:
case SCRIPTOFF_T:
val = RunScriptSet();
codeptr++;
break;
case RESTART_T:
val = RunRestart();
codeptr++;
break;
case STRING_T:
val = RunString();
break;
case UNDO_T:
val = Undo();
codeptr++;
break;
case DICT_T:
val = Dict();
if (MEM(codeptr)==CLOSE_BRACKET_T) codeptr++;
break;
case RECORDON_T:
case RECORDOFF_T:
case PLAYBACK_T:
val = RecordCommands();
codeptr++;
break;
case READVAL_T:
{
val = 0;
if (ioblock)
{
#ifdef TODO
int low, high;
if ((ioblock==1)
|| (low = hugo_fgetc(io))==EOF
|| (high = hugo_fgetc(io))==EOF)
{
ioerror = true;
retflag = true;
}
else val = low + high*256;
#else
error("TODO: file io");
#endif
}
codeptr++;
break;
}
case PARSE_T:
{
val = (short)PARSE_STRING_VAL;
codeptr++;
break;
}
case SERIAL_T:
{
val = (short)SERIAL_STRING_VAL;
codeptr++;
break;
}
case SYSTEM_T:
{
val = RunSystem();
codeptr++;
break;
}
default:
{
#if defined (DEBUGGER)
if (debug_eval)
debug_eval_error = true;
else
#endif
FatalError(EXPECT_VAL_E);
#if defined (DEBUGGER)
runtime_error = true;
codeptr++;
return 0;
#endif
}
}
defseg = gameseg;
ret = tempret;
inexpr = tempinexpr;
incdec = preincdec;
return val;
}
int Hugo::GetValue() {
char noself = 0;
int p, n;
char inctype; int preincdec;
int nattr = 0, attr;
unsigned int pa, val;
long tempptr;
short g; /* must be 16 bits */
int potential_tail_recursion = 0;
/* Check to see if this may be a valid tail-recursion */
if (tail_recursion==0 && MEM(codeptr-1)==RETURN_T)
{
/* We may be able to tail-recurse this return statement if
it's simply 'return object.property[.property...]'
*/
potential_tail_recursion = TAIL_RECURSION_PROPERTY;
}
IsIncrement(codeptr); /* check for ++, -- */
tempptr = codeptr;
g = GetVal();
preincdec = incdec;
incdec = 0;
if (inobj==0)
{
switch (MEM(codeptr))
{
case DECIMAL_T: /* object.property */
{
DetermineProperty:
if (MEM(++codeptr)==DECIMAL_T) /* object..property */
{
noself = true;
codeptr++;
}
if (MEM(codeptr)==POUND_T) /* object.#property */
{
codeptr++;
inobj = true;
p = GetValue();
inobj = false;
pa = PropAddr(g, p, 0);
if (pa)
{
defseg = proptable;
g = Peek(pa + 1);
if (g==PROP_ROUTINE) g = 1;
defseg = gameseg;
}
else
g = 0;
}
else
{
inobj = true;
p = GetValue();
inobj = false;
if (MEM(codeptr) != POUND_T)
n = 1;
else /* object.property #x */
{
codeptr++;
/* Not GetValue(), since that might
botch "obj.property #n is attr"
*/
n = GetVal();
}
/* We checked this at the start of the function, but
GetValue() for the property would've cleared it
*/
tail_recursion = potential_tail_recursion;
val = GetProp(g, p, n, noself);
inctype = IsIncrement(codeptr);
/* Increment/decrement an object.property, although
only if this is the last property in, e.g.,
object.property.property...
*/
if ((incdec || preincdec) && MEM(codeptr)!=DECIMAL_T)
{
SaveUndo(PROP_T, g, p, n, val);
if (inctype) val = Increment(val, inctype);
/* Still a post-increment hanging around */
pa = PropAddr(g, p, 0);
defseg = proptable;
/* Only change it if not a routine */
if (Peek(pa+1)!=PROP_ROUTINE)
PokeWord(pa+n*2, (val+=preincdec)+incdec);
defseg = gameseg;
incdec = preincdec = 0;
}
g = val;
}
if (MEM(codeptr)==IS_T) goto CheckAttribute;
break;
}
case IS_T:
{
CheckAttribute:
if (!inobj)
{
codeptr++;
if (MEM(codeptr)==NOT_T)
{
nattr = 1;
codeptr++;
}
attr = GetValue();
#if defined (DEBUGGER)
CheckinRange((unsigned)attr, (unsigned)attributes, "attribute");
#endif
g = TestAttribute(g, attr, nattr);
break;
}
}
}
switch (MEM(codeptr))
{
/* This comes here (again) in order to process
object.property1.property2...
*/
case DECIMAL_T: goto DetermineProperty;
case NOT_T:
if (!inobj)
{nattr = 1;
codeptr++;}
// fall through
case IN_T:
{
if (!inobj)
{
codeptr++;
p = GetValue(); /* testing parent */
g = (p==Parent(g));
if (nattr)
g = !g;
}
}
}
} /* end of "if (inobj==0)" */
n = MEM(codeptr);
/* See if we have an implicit expression that needs to be
taken as a single value, i.e., "n + 1" where we've just
read n
*/
if (((n>=MINUS_T && n<=PIPE_T) || n==AMPERSAND_T) &&
((!inexpr)) && !inobj)
/*
#if !defined (DEBUGGER)
((!inexpr)) && !inobj)
#else
((!inexpr)) && !inobj && !debug_eval)
#endif
*/
{
inexpr = 2;
codeptr = tempptr;
SetupExpr();
g = EvalExpr(0);
inexpr = 0;
}
/* Not a tail-recursive 'return object.property' */
if (tail_recursion_addr==0)
tail_recursion = 0;
return g;
}
int Hugo::Increment(int a, char inctype) {
short v; /* must be 16 bits */
v = a;
switch (inctype)
{
case MINUS_T: {v -= incdec; break;}
case PLUS_T: {v += incdec; break;}
case ASTERISK_T: {v *= incdec; break;}
case AMPERSAND_T: {v &= incdec; break;}
case PIPE_T: {v |= incdec; break;}
case FORWARD_SLASH_T:
{
#if defined (DEBUGGER)
if (incdec==0)
{
RuntimeWarning("Division by zero: invalid result");
v = 0;
}
else
#endif
v /= incdec;
break;
}
}
if (inctype!=1) incdec = 0;
return v;
}
char Hugo::IsIncrement(long addr) {
unsigned char a, t = 0;
incdec = 0;
switch (a = MEM(addr))
{
case MINUS_T:
case PLUS_T:
case ASTERISK_T:
case FORWARD_SLASH_T:
case AMPERSAND_T:
case PIPE_T:
{
/* ++, -- */
if ((a==MINUS_T || a==PLUS_T) && MEM(addr+1)==a)
{
codeptr = addr + 2;
if (a==PLUS_T) incdec = 1;
else incdec = -1;
t = 1;
break;
}
/* +=, -=, etc. */
else if (MEM(addr+1)==EQUALS_T)
{
codeptr = addr + 2;
incdec = GetValue();
t = a;
}
}
}
#if defined (DEBUGGER)
if (t && debug_eval)
{
debug_eval_error = true;
Common::sprintf_s(debug_line, "'%s%s' illegal in watch/assignment", token[a], token[MEM(addr+1)]);
DebugMessageBox("Expression Error", debug_line);
t = 0;
}
#endif
return t;
}
int Hugo::Precedence(int t) {
switch (t)
{
case DECIMAL_T:
return 1;
case ASTERISK_T:
case FORWARD_SLASH_T:
return 2;
case MINUS_T:
case PLUS_T:
return 3;
case PIPE_T:
case TILDE_T:
case AMPERSAND_T:
return 4;
case EQUALS_T:
case GREATER_EQUAL_T:
case LESS_EQUAL_T:
case NOT_EQUAL_T:
case GREATER_T:
case LESS_T:
return 5;
default:
return 6;
}
}
#if defined (DEBUG_EXPR_EVAL)
/* PRINTEXPR
Prints the current expression during expression tracing.
*/
void PrintExpr(void)
{
char e[261];
int i, bracket = 0;
if (!evalcount) return;
strcpy(e, "( ");
for (i=0; i<=evalcount; i+=2)
{
switch (eval[i])
{
case 0:
{
Common::sprintf_s(line, "%d ", eval[i + 1]);
strcat(e, line);
break;
}
case 1:
{
if (eval[i+1]==OPEN_BRACKET_T) bracket++;
if (eval[i+1]==CLOSE_BRACKET_T)
{bracket--;
if (bracket<0) goto ExitPrintExpr;}
if (token[eval[i+1]][0]=='~')
strcat(e, "\\");
if (eval[i+1] != 255)
{sprintf(line, "%s ", token[eval[i+1]]);
strcat(e, line);}
break;
}
}
}
ExitPrintExpr:
strcat(e, ")\\;");
AP(e);
}
#endif
void Hugo::SetupExpr() {
char justgotvalue = 1;
int j, t, bracket = 0;
int tempret;
int tempeval[MAX_EVAL_ELEMENTS];
int tempevalcount;
last_precedence = 10;
tempret = ret;
tempevalcount = 0;
inobj = false;
if (!inexpr) inexpr = 1;
do
{
justgotvalue++;
switch (t = MEM(codeptr))
{
/* Various indications that we've hit the
end of the expression:
*/
case EOL_T:
arrexpr = false;
// fall through
case COMMA_T:
multiprop = false;
// fall through
case SEMICOLON_T:
case CLOSE_SQUARE_T:
case JUMP_T:
{
if (t==EOL_T || t==COMMA_T || t==JUMP_T)
codeptr++;
LeaveSetupExpr:
for (j=0; j MAX_EVAL_ELEMENTS-2)
FatalError(OVERFLOW_E);
justgotvalue = 0;
break;
}
/* Logical constants */
case TRUE_T:
case FALSE_T:
{
tempeval[tempevalcount] = 0;
if (Peek(codeptr)==TRUE_T)
tempeval[tempevalcount + 1] = 1;
else
tempeval[tempevalcount + 1] = 0;
codeptr++;
tempevalcount += 2;
if (tempevalcount > MAX_EVAL_ELEMENTS-2)
FatalError(OVERFLOW_E);
break;
}
/* Some symbol or token */
default:
{
SomeSymbolorToken:
tempeval[tempevalcount] = 1;
tempeval[tempevalcount + 1] = MEM(codeptr++);
tempevalcount += 2;
if (tempevalcount > MAX_EVAL_ELEMENTS-2)
FatalError(OVERFLOW_E);
switch (MEM(codeptr-1))
{
case OPEN_BRACKET_T:
{bracket++;
break;}
case CLOSE_BRACKET_T:
{bracket--;
justgotvalue = 0;
if (inexpr==2)
codeptr--;}
}
if (bracket < 0) goto LeaveSetupExpr;
break;
}
}
}
while (true); /* endless loop */
}
void Hugo::TrimExpr(int ptr) {
int i;
for (i=ptr; i<=evalcount; i+=2)
{
eval[i] = eval[i+2];
eval[i+1] = eval[i+3];
}
evalcount -= 2;
}
} // End of namespace Hugo
} // End of namespace Glk