Selector rewrite: values other than long, interpret identifiers

according to context, to make `lstat * ?type#spy&spy>1' work.  Closes
bug#825363, #905809, #905814 and #922968.
(nsc_type, packed_nsc_type, nsc_cat, packed_nsc_cat, nsc_flags): New.
(valstr): New.  Old code encoded values in type long, which was
somewhat hard to read and could only support signed integer values.
(nscstr): Redesign.  Use valstr.  Typed operator.
(castr): Split ca_code into ca_type, ca_flags, ca_off.  Tables
changed.
(nstr_comp, nstr_exec): Redesign and rewrite.  Callers changed.  They
used the old design incorrectly, which let players smash the stack
by supplying more than NCOND conditions.
(encode, nstr_comp_val, decode, nstr_exec_val): Rename, redesign, and
rewrite.  Callers changed.
(nstr_coerce_val): New.
(var_ca, sect_ca, ship_ca, land_ca): Checking both var_ca[] and the
object's ca complicates proper recognition of unique abbreviations.
Copy contents of var_ca[] into the ca of objects, remove var_ca[].

(surv): Reject values with category other than NSC_OFF and types that
can't be coerced to NSC_LONG.  Old code happily passed values with
category NSC_VAL to code_char().  The previous version interpreted
them correctly, but earlier versions interpreted them as NSC_OFF, then
logged `bad type in decode: 0' and evaluated them into zero.

(code_char): Used to test category NSC_VAR to decide whether to
display tens or hundreds.  NSC_VAR no longer exists.  Test type
instead.  Makes more sense anyway.
This commit is contained in:
Markus Armbruster 2004-04-02 19:02:12 +00:00
parent 47c8a32ae3
commit 4366c5ac6e
13 changed files with 726 additions and 544 deletions

View file

@ -30,248 +30,443 @@
* Known contributors to this file:
* Dave Pare, 1989
* Steve McClure, 1997
* Markus Armbruster, 2004
*/
#include <ctype.h>
#include "struct.h"
#include "misc.h"
#include "var.h"
#include "xy.h"
#include "sect.h"
#include "nsc.h"
#include "nat.h"
#include "match.h"
#include "file.h"
#include "player.h"
#include "prototypes.h"
#include "match.h"
#include "nsc.h"
static int legal_val(s_char *str, int val);
static int nstr_promote(int valtype);
/*
* Compiles and adds "str" to the list of conditionals.
* type is the EF typename of the item type we're selecting.
* returns amount of "str" used by nstr_comp (i.e. how far
* the pointer was advanced). The last is only meaningful
* if several conditionals are expected in one string.
*/
s_char *
nstr_comp(struct nscstr *np, int *size, int type, s_char *str)
{
register s_char *bp;
register s_char *cp;
register int c;
s_char ident[80];
s_char arg[255];
int val;
strncpy(arg, str, sizeof(arg) - 1);
arg[sizeof(arg) - 1] = 0;
cp = arg;
bp = ident;
while ((c = *cp++) && bp < &ident[sizeof(ident) - 1]) {
if (c == '<' || c == '=' || c == '>' || c == '#')
break;
*bp++ = c;
}
*bp = 0;
if (c == 0) {
pr("'%s'? -- meaningless condition?\n", arg);
return 0;
}
np[*size].oper = c & NSC_OPMASK;
if ((val = encode(ident, &np[*size].fld1, type)) < 0)
return 0;
if (val == 2)
np[*size].oper |= NSC_ISNUM1;
bp = ident;
while ((c = *cp++) && bp < &ident[sizeof(ident) - 1]) {
if (c == '&')
break;
*bp++ = c;
}
*bp = 0;
if ((val = encode(ident, &np[*size].fld2, type)) < 0)
return 0;
if (val == 2)
np[*size].oper |= NSC_ISNUM2;
if (c == 0)
cp--;
(*size)++;
return str + (cp - arg);
}
/*
* return true if the conditions on this item
* are all true.
* Compile conditions into array NP[LEN].
* Return number of conditions, or -1 on error.
* It is an error if there are more than LEN conditions.
* TYPE is the context type, a file type.
* STR is the condition string, in Empire syntax, without the leading
* '?'.
*/
int
nstr_exec(struct nscstr *conds, register int ncond, void *ptr, int type)
nstr_comp(struct nscstr *np, int len, int type, char *str)
{
register struct nscstr *nsc;
register int op;
register int lhs;
register int rhs;
register int oper;
char *cond;
char *tail;
int i;
struct nscstr dummy;
int lft_type, rgt_type;
for (nsc = conds; --ncond >= 0; nsc++) {
oper = nsc->oper;
if (oper & NSC_ISNUM2) {
rhs = nsc->fld2;
} else
rhs = decode(player->cnum, nsc->fld2, ptr, type);
cond = str;
for (i = 0; ; ++i, ++np) {
if (i >= len)
np = &dummy;
if (oper & NSC_ISNUM1) {
lhs = nsc->fld1;
} else
lhs = decode(player->cnum, nsc->fld1, ptr, type);
/* left operand */
tail = nstr_comp_val(cond, &np->lft, type);
if (!tail)
return -1;
op = oper & NSC_OPMASK;
if ((op == '<' && lhs >= rhs)
|| (op == '=' && lhs != rhs)
|| (op == '>' && lhs <= rhs)
|| (op == '#' && lhs == rhs))
return 0;
/* operator */
if (*tail != '<' && *tail != '=' && *tail != '>' && *tail != '#') {
if (*tail)
pr("%s -- expected condition operator\n", cond);
else
pr("%s -- missing condition operator\n", cond);
return -1;
}
np->operator = *tail;
++tail;
/* right operand */
tail = nstr_comp_val(tail, &np->rgt, type);
if (!tail)
return -1;
/* find operator type, coerce operands */
lft_type = nstr_promote(np->lft.val_type);
rgt_type = nstr_promote(np->rgt.val_type);
if (lft_type == NSC_TYPEID) {
if (!nstr_coerce_val(&np->rgt, NSC_TYPEID, str))
np->optype = NSC_TYPEID;
} else if (rgt_type == NSC_TYPEID) {
if (!nstr_coerce_val(&np->lft, NSC_TYPEID, str))
np->optype = NSC_TYPEID;
} else if (lft_type == NSC_STRING) {
if (!nstr_coerce_val(&np->rgt, NSC_STRING, str))
np->optype = NSC_STRING;
} else if (rgt_type == NSC_STRING) {
if (!nstr_coerce_val(&np->lft, NSC_STRING, str))
np->optype = NSC_STRING;
} else if (lft_type == NSC_DOUBLE) {
if (!nstr_coerce_val(&np->rgt, NSC_DOUBLE, str))
np->optype = NSC_DOUBLE;
} else if (rgt_type == NSC_DOUBLE) {
if (!nstr_coerce_val(&np->lft, NSC_DOUBLE, str))
np->optype = NSC_DOUBLE;
} else {
if (!nstr_coerce_val(&np->lft, NSC_LONG, str)
&& !nstr_coerce_val(&np->rgt, NSC_LONG, str))
np->optype = NSC_LONG;
}
/* another condition? */
if (*tail == 0)
break;
if (*tail != '&') {
pr("%s -- expected `&'\n", cond);
return -1;
}
cond = tail + 1;
}
if (i >= len) {
/* could just return I and let caller gripe or enlarge buffer */
pr("%s -- too many conditions\n", str);
return -1;
}
return i + 1;
}
#define EVAL(op, lft, rgt) \
((op) == '<' ? (lft) < (rgt) \
: (op) == '=' ? (lft) == (rgt) \
: (op) == '>' ? (lft) > (rgt) \
: (op) == '#' ? (lft) != (rgt) \
: 0)
/*
* Evaluate compiled conditions in array NP[NCOND].
* Return non-zero iff they are all true.
* PTR points to a context object of the type that was used to compile
* the conditions.
*/
int
nstr_exec(struct nscstr *np, int ncond, void *ptr)
{
int i, op, optype;
struct valstr lft, rgt;
for (i = 0; i < ncond; ++i) {
op = np[i].operator;
optype = np[i].optype;
if (np[i].lft.val_cat == NSC_NOCAT || np[i].rgt.val_cat == NSC_NOCAT)
return 0;
lft = np[i].lft;
nstr_exec_val(&lft, player->cnum, ptr, optype);
rgt = np[i].rgt;
nstr_exec_val(&rgt, player->cnum, ptr, optype);
switch (optype) {
case NSC_TYPEID:
case NSC_LONG:
if (!EVAL(op, lft.val_as.lng, rgt.val_as.lng))
return 0;
break;
case NSC_DOUBLE:
if (!EVAL(op, lft.val_as.dbl, rgt.val_as.dbl))
return 0;
break;
case NSC_STRING:
return 0; /* FIXME */
default:
CANT_HAPPEN("bad OPTYPE");
return 0;
}
}
return 1;
}
int
encode(register s_char *str, long int *val, int type)
/*
* Compile a value in STR into VAL.
* Return a pointer to the first character after the value on success,
* NULL on error.
* TYPE is the context type, a file type.
*/
char *
nstr_comp_val(char *str, struct valstr*val, int type)
{
register int i;
char id[32];
long l;
double d;
char *tail, *tail2;
struct castr *cap;
unsigned i;
int j;
if (str == 0) {
*val = 0;
return 0;
}
if (isdigit(*str) || ((*str == '-') && isdigit(str[1]))) {
*val = atoi(str);
return 2;
}
/*
* FIXME This accepts the first match found, even if there are
* more matches in other tables, i.e. it quietly accepts ambiguous
* matches (matches in multiple tables), and fails to prefer an
* exact match to partial match in an earlier table.
*/
if ((i = typematch(str, type)) >= 0) {
*val = i;
return 1;
}
if ((cap = ef_cadef(type)) != 0) {
i = stmtch(str, (caddr_t)cap, fldoff(castr, ca_name),
sizeof(struct castr));
if (i >= 0) {
*val = cap[i].ca_code;
return legal_val(str, *val);
val->val_type = NSC_NOTYPE;
val->val_cat = NSC_NOCAT;
val->val_as_type = -1;
if (isalpha(str[0])) {
/* identifier */
for (i = 0; isalnum(str[i]) || str[i] == '_'; ++i) {
if (i < sizeof(id) - 1)
id[i] = str[i];
}
if (i == M_NOTUNIQUE) {
pr("%s -- ambiguous type selector\n", str);
return 0;
tail = str + i;
if (i < sizeof(id)) {
id[i] = 0;
val->val_as_type = typematch(id, type);
cap = ef_cadef(type);
if (cap) {
j = stmtch(id, cap, offsetof(struct castr, ca_name),
sizeof(struct castr));
if (j >= 0
&& (!(cap[j].ca_flags & NSC_DEITY) || player->god)) {
val->val_type = cap[j].ca_type;
val->val_cat = NSC_OFF;
val->val_as.off = cap[j].ca_off;
}
} else
j = M_NOTFOUND;
} else
j = M_NOTFOUND;
if (val->val_type == NSC_NOTYPE) {
if (val->val_as_type >= 0) {
val->val_type = NSC_TYPEID;
val->val_cat = NSC_VAL;
val->val_as.lng = val->val_as_type;
} else if (j >= 0)
pr("%s -- selector access denied\n", id);
else if (j == M_NOTUNIQUE)
pr("%s -- ambiguous selector name\n", id);
else
pr("%s -- unknown selector name\n", id);
}
return val->val_type == NSC_NOTYPE ? NULL : tail;
}
/*
* Only check for commodity selectors on objects which
* are allowed to have commodities.
*/
if (ef_flags(type) & EFF_COM) {
i = stmtch(str, (caddr_t)var_ca, fldoff(castr, ca_name),
sizeof(struct castr));
if (i >= 0) {
*val = var_ca[i].ca_code;
return legal_val(str, *val);
}
if (i == M_NOTUNIQUE) {
pr("%s -- ambiguous commodity selector\n", str);
return 0;
}
/* literals */
l = strtol(str, &tail, 0);
d = strtod(str, &tail2);
if (tail2 > tail) {
val->val_type = NSC_DOUBLE;
val->val_cat = NSC_VAL;
val->val_as.dbl = d;
return tail2;
}
pr("%s -- not a valid selector\n", str);
return 0;
if (tail != str) {
val->val_type = NSC_LONG;
val->val_cat = NSC_VAL;
val->val_as.lng = l;
return tail;
}
/* FIXME NSC_STRING */
/* single character type */
id[0] = str[0];
id[1] = 0;
val->val_as_type = typematch(id, type);
if (val->val_as_type >= 0) {
val->val_type = NSC_TYPEID;
val->val_cat = NSC_VAL;
val->val_as.lng = val->val_as_type;
return str + 1;
}
pr("%s -- invalid value for condition\n", str);
return NULL;
}
/*
* Promote VALTYPE.
* If VALTYPE is an integer type, return NSC_LONG.
* If VALTYPE is a floating-point type, return NSC_DOUBLE.
* If VALTYPE is NSC_NOTYPE, NSC_STRING or NSC_TYPEID, return VALTYPE.
*/
static int
nstr_promote(int valtype)
{
switch (valtype) {
case NSC_NOTYPE:
case NSC_LONG:
case NSC_DOUBLE:
case NSC_STRING:
case NSC_TYPEID:
break;
case NSC_CHAR:
case NSC_UCHAR:
case NSC_SHORT:
case NSC_USHORT:
case NSC_INT:
case NSC_XCOORD:
case NSC_YCOORD:
case NSC_TIME:
valtype = NSC_LONG;
break;
case NSC_FLOAT:
valtype = NSC_DOUBLE;
break;
default:
CANT_HAPPEN("bad VALTYPE");
valtype = NSC_NOTYPE;
}
return valtype;
}
static int
legal_val(s_char *str, int val)
cond_type_mismatch(char *str)
{
if (val & NSC_DEITY && !player->god) {
pr("%s -- permission denied\n", str);
return -1;
}
return 1;
if (str)
pr("%s -- condition type mismatch\n", str);
return -1;
}
/*
* Coerce VAL to promoted value type TO.
* Return 0 on success, -1 on error.
* If VAL is evaluated, convert it, else only check.
* STR is the condition text to be used for error messages. Suppress
* messages if it is a null pointer.
*/
int
decode(natid cnum, long int code, void *addr, int type)
nstr_coerce_val(struct valstr *val, nsc_type to, char *str)
{
register int val;
register int nsc_code;
struct natstr *np;
long code_type = (code & NSC_TMASK);
/* FIXME get rid of promotion? */
nsc_type from = nstr_promote(val->val_type);
val = (code & ~NSC_MASK) & 0xffff;
if (from == NSC_NOTYPE)
return 0;
/* handle negative numbers properly */
/* this assumes a binary two's complement number representation */
if (val >= 0x8000)
val -= 0x10000;
nsc_code = code & NSC_CMASK;
if (nsc_code == NSC_VAR) {
u_short *item = ef_items(type, addr);
val = item ? item[val] : 0;
} else if (nsc_code == NSC_OFF) {
/*
* add offset to value
*/
addr = (s_char *)addr + val;
switch (code_type) {
case NSC_TIME:
val = *((time_t *) addr);
if (from != to) {
switch (to) {
case NSC_TYPEID:
if (val->val_as_type >= 0) {
val->val_cat = NSC_VAL;
val->val_as.lng = val->val_as_type;
} else
return cond_type_mismatch(str);
break;
case NSC_CHAR:
val = *((s_char *)addr);
break;
case NSC_UCHAR:
val = (int)*((unsigned char *)addr);
break;
case NSC_SHORT:
val = *((short *)addr);
break;
case NSC_USHORT:
val = *((u_short *)addr);
break;
case NSC_INT:
val = *((int *)addr);
case NSC_STRING:
return cond_type_mismatch(str); /* FIXME */
case NSC_DOUBLE:
if (from == NSC_LONG) {
if (val->val_cat == NSC_VAL)
val->val_as.dbl = val->val_as.lng;
} else
return cond_type_mismatch(str);
break;
case NSC_LONG:
val = *((long *)addr);
break;
case NSC_XCOORD:
val = *((short *)addr);
np = getnatp(cnum);
val = xrel(np, val);
break;
case NSC_YCOORD:
val = *((short *)addr);
np = getnatp(cnum);
val = yrel(np, val);
break;
return cond_type_mismatch(str);
default:
logerror("bad type in decode: %lx!\n", code & NSC_TMASK);
val = 0;
break;
CANT_HAPPEN("bad TO argument");
to = from;
}
}
return val;
if (val->val_cat == NSC_VAL) {
/* coord literals don't occur, conversion not implemented */
CANT_HAPPEN(val->val_type == NSC_XCOORD
|| val->val_type == NSC_YCOORD);
val->val_type = to;
}
return 0;
}
s_char *
decodep(long int code, void *addr)
/*
* Evaluate VAL.
* Use coordinate system of country CNUM.
* PTR points to a context object of the type that was used to compile
* the value.
* If WANT is not zero, coerce the value to promoted value type WANT.
* VAL must be coercible. That's the case if a previous
* nstr_coerce_val(VAL, WANT, STR) succeeded.
*/
void
nstr_exec_val(struct valstr *val, natid cnum, void *ptr, nsc_type want)
{
addr = (char *)addr + ((code & ~NSC_MASK) & 0xffff);
char *memb_ptr;
nsc_type valtype = NSC_LONG;
if ((code & NSC_TMASK) == NSC_CHARP)
return *(s_char **)addr ? *((s_char **)addr) : (s_char *)"";
return addr;
switch (val->val_cat) {
default:
CANT_HAPPEN("Bad VAL category");
/* fall through */
case NSC_VAL:
valtype = val->val_type;
break;
case NSC_OFF:
memb_ptr = ptr;
memb_ptr += val->val_as.off;
switch (val->val_type) {
case NSC_CHAR:
val->val_as.lng = *(signed char *)memb_ptr;
break;
case NSC_UCHAR:
val->val_as.lng = *(unsigned char *)memb_ptr;
break;
case NSC_SHORT:
val->val_as.lng = *(short *)memb_ptr;
break;
case NSC_USHORT:
val->val_as.lng = *(unsigned short *)memb_ptr;
break;
case NSC_INT:
val->val_as.lng = *(int *)memb_ptr;
break;
case NSC_LONG:
val->val_as.lng = *(long *)memb_ptr;
break;
case NSC_XCOORD:
val->val_as.lng = xrel(getnatp(cnum), *(short *)memb_ptr);
break;
case NSC_YCOORD:
val->val_as.lng = yrel(getnatp(cnum), *(short *)memb_ptr);
break;
case NSC_FLOAT:
val->val_as.dbl = *(float *)memb_ptr;
valtype = NSC_DOUBLE;
break;
case NSC_DOUBLE:
val->val_as.dbl = *(double *)memb_ptr;
valtype = NSC_DOUBLE;
break;
case NSC_STRING:
val->val_as.str = *(char **)memb_ptr;
valtype = NSC_STRING;
break;
case NSC_TIME:
val->val_as.lng = *(time_t *)memb_ptr;
break;
case NSC_TYPEID:
val->val_as.lng = *(signed char *)memb_ptr;
valtype = NSC_TYPEID;
break;
default:
CANT_HAPPEN("Bad VAL type");
val->val_as.lng = 0;
}
}
if (want) {
if (valtype == want)
;
else if (want == NSC_DOUBLE) {
if (valtype == NSC_LONG) {
valtype = want;
val->val_as.dbl = val->val_as.lng;
}
} else if (want == NSC_STRING)
; /* FIXME */
if (CANT_HAPPEN(valtype != want)) {
valtype = want;
switch (want) {
case NSC_TYPEID:
case NSC_LONG: val->val_as.lng = 0; break;
case NSC_DOUBLE: val->val_as.dbl = 0.0; break;
case NSC_STRING: val->val_as.str = ""; break;
default:
CANT_HAPPEN("bad WANT argument");
}
}
}
val->val_type = valtype;
}