/* funvars.c - structure, variable, stack, and regexp functions */
/* $Id: funvars.c,v 1.73 2004/04/03 07:13:24 rmg Exp $ */
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "alloc.h" /* required by mudconf */
#include "flags.h" /* required by mudconf */
#include "htab.h" /* required by mudconf */
#include "mudconf.h" /* required by code */
#include "db.h" /* required by externs */
#include "externs.h" /* required by code */
#include "functions.h" /* required by code */
#include "match.h" /* required by code */
#include "attrs.h" /* required by code */
#include "powers.h" /* required by code */
#include "pcre.h" /* required by code */
/* ---------------------------------------------------------------------------
* setq, setr, r: set and read global registers.
*/
/*
* ASCII character table for %qa - %qz
*
* 0 - 47 : NULL to / (3 rows)
* 48 - 63 : 0 to ?
* 64 - 79 : @, A to O
* 80 - 95 : P to _
* 96 - 111 : `, a to o
* 112 - 127 : p to DEL
* 128 - 255 : specials (8 rows)
*/
char qidx_chartab[256] =
{
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1,-1,-1,-1,-1,-1,
-1,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,
25,26,27,28,29,30,31,32, 33,34,35,-1,-1,-1,-1,-1,
-1,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,
25,26,27,28,29,30,31,32, 33,34,35,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1
};
int set_register(funcname, name, data)
const char *funcname;
char *name, *data;
{
/* Return number of characters set.
* -1 indicates a name error.
* -2 indicates that a limit was exceeded.
*/
int i, regnum, len, a_size, *tmp_lens;
char *p, **tmp_regs;
if (!name || !*name)
return -1;
if (name[1] == '\0') {
/* Single-letter q-register.
* We allocate these either as a block of 10 or a block of 36.
* (Most code won't go beyond %q0-%q9, especially legacy code
* which predates the larger number of global registers.)
*/
regnum = qidx_chartab[(unsigned char) *name];
if ((regnum < 0) || (regnum >= MAX_GLOBAL_REGS))
return -1;
/* Check to see if we're just clearing. If we're clearing a register
* that doesn't exist, then we do nothing. Otherwise we wipe out
* the data.
*/
if (!data || !*data) {
if (!mudstate.rdata || !mudstate.rdata->q_alloc ||
(regnum >= mudstate.rdata->q_alloc))
return 0;
if (mudstate.rdata->q_regs[regnum]) {
free_lbuf(mudstate.rdata->q_regs[regnum]);
mudstate.rdata->q_regs[regnum] = NULL;
mudstate.rdata->q_lens[regnum] = 0;
mudstate.rdata->dirty++;
}
return 0;
}
/* We're actually setting a register. Take care of allocating
* space first.
*/
if (!mudstate.rdata) {
Init_RegData(funcname, mudstate.rdata);
}
if (!mudstate.rdata->q_alloc) {
a_size = (regnum < 10) ? 10 : MAX_GLOBAL_REGS;
mudstate.rdata->q_alloc = a_size;
mudstate.rdata->q_regs = XCALLOC(a_size, sizeof(char *), "q_regs");
mudstate.rdata->q_lens = XCALLOC(a_size, sizeof(int), "q_lens");
mudstate.rdata->q_alloc = a_size;
} else if (regnum >= mudstate.rdata->q_alloc) {
a_size = MAX_GLOBAL_REGS;
tmp_regs = XREALLOC(mudstate.rdata->q_regs,
a_size * sizeof(char *), "q_regs");
tmp_lens = XREALLOC(mudstate.rdata->q_lens,
a_size * sizeof(int), "q_lens");
memset(&tmp_regs[mudstate.rdata->q_alloc], (int) NULL,
(a_size - mudstate.rdata->q_alloc) * sizeof(char *));
memset(&tmp_lens[mudstate.rdata->q_alloc], 0,
(a_size - mudstate.rdata->q_alloc) * sizeof(int));
mudstate.rdata->q_regs = tmp_regs;
mudstate.rdata->q_lens = tmp_lens;
mudstate.rdata->q_alloc = a_size;
}
/* Set it. */
if (!mudstate.rdata->q_regs[regnum])
mudstate.rdata->q_regs[regnum] = alloc_lbuf(funcname);
len = strlen(data);
memcpy(mudstate.rdata->q_regs[regnum], data, len + 1);
mudstate.rdata->q_lens[regnum] = len;
mudstate.rdata->dirty++;
return len;
}
/* We have an arbitrarily-named register.
* Check for data-clearing first, since that's easier.
*/
if (!data || !*data) {
if (!mudstate.rdata || !mudstate.rdata->xr_alloc)
return 0;
for (p = name; *p; p++)
*p = tolower(*p);
for (i = 0; i < mudstate.rdata->xr_alloc; i++) {
if (mudstate.rdata->x_names[i] &&
!strcmp(name, mudstate.rdata->x_names[i])) {
if (mudstate.rdata->x_regs[i]) {
free_sbuf(mudstate.rdata->x_names[i]);
mudstate.rdata->x_names[i] = NULL;
free_lbuf(mudstate.rdata->x_regs[i]);
mudstate.rdata->x_regs[i] = NULL;
mudstate.rdata->x_lens[i] = 0;
mudstate.rdata->dirty++;
return 0;
} else {
return 0;
}
}
}
return 0; /* register unset, so just return */
}
/* Check for a valid name.
* We enforce names beginning with a letter, in case we want to do
* something special with naming conventions at some later date.
* We also limit the characters than can go into a name.
*/
if (strlen(name) >= SBUF_SIZE)
return -1;
if (!isalpha(*name))
return -1;
for (p = name; *p; p++) {
if (isalnum(*p) ||
(*p == '_') || (*p == '-') || (*p == '.') || (*p == '#'))
*p = tolower(*p);
else
return -1;
}
len = strlen(data);
/* If we have no existing data, life is easy; just set it. */
if (!mudstate.rdata) {
Init_RegData(funcname, mudstate.rdata);
}
if (!mudstate.rdata->xr_alloc) {
a_size = NUM_ENV_VARS;
mudstate.rdata->x_names = XCALLOC(a_size, sizeof(char *), "x_names");
mudstate.rdata->x_regs = XCALLOC(a_size, sizeof(char *), "x_regs");
mudstate.rdata->x_lens = XCALLOC(a_size, sizeof(int), "x_lens");
mudstate.rdata->xr_alloc = a_size;
mudstate.rdata->x_names[0] = alloc_sbuf(funcname);
strcpy(mudstate.rdata->x_names[0], name);
mudstate.rdata->x_regs[0] = alloc_lbuf(funcname);
memcpy(mudstate.rdata->x_regs[0], data, len + 1);
mudstate.rdata->x_lens[0] = len;
mudstate.rdata->dirty++;
return len;
}
/* Search for an existing entry to replace. */
for (i = 0; i < mudstate.rdata->xr_alloc; i++) {
if (mudstate.rdata->x_names[i] &&
!strcmp(name, mudstate.rdata->x_names[i])) {
memcpy(mudstate.rdata->x_regs[i], data, len + 1);
mudstate.rdata->x_lens[i] = len;
mudstate.rdata->dirty++;
return len;
}
}
/* Check for an empty cell to insert into. */
for (i = 0; i < mudstate.rdata->xr_alloc; i++) {
if (mudstate.rdata->x_names[i] == NULL) {
mudstate.rdata->x_names[i] = alloc_sbuf(funcname);
strcpy(mudstate.rdata->x_names[i], name);
if (!mudstate.rdata->x_regs[i]) /* should never happen */
mudstate.rdata->x_regs[i] = alloc_lbuf(funcname);
memcpy(mudstate.rdata->x_regs[i], data, len + 1);
mudstate.rdata->x_lens[i] = len;
mudstate.rdata->dirty++;
return len;
}
}
/* Oops. We're out of room in our existing array. Go allocate
* more space, unless we're at our limit.
*/
regnum = mudstate.rdata->xr_alloc;
a_size = regnum + NUM_ENV_VARS;
if (a_size > mudconf.register_limit) {
a_size = mudconf.register_limit;
if (a_size <= regnum)
return -2;
}
tmp_regs = (char **) XREALLOC(mudstate.rdata->x_names,
a_size * sizeof(char *), funcname);
mudstate.rdata->x_names = tmp_regs;
tmp_regs = (char **) XREALLOC(mudstate.rdata->x_regs,
a_size * sizeof(char *), funcname);
mudstate.rdata->x_regs = tmp_regs;
tmp_lens = (int *) XREALLOC(mudstate.rdata->x_lens,
a_size * sizeof(int), funcname);
mudstate.rdata->x_lens = tmp_lens;
memset(&mudstate.rdata->x_names[mudstate.rdata->xr_alloc], (int) NULL,
(a_size - mudstate.rdata->xr_alloc) * sizeof(char *));
memset(&mudstate.rdata->x_regs[mudstate.rdata->xr_alloc], (int) NULL,
(a_size - mudstate.rdata->xr_alloc) * sizeof(char *));
memset(&mudstate.rdata->x_lens[mudstate.rdata->xr_alloc], 0,
(a_size - mudstate.rdata->xr_alloc) * sizeof(int));
mudstate.rdata->xr_alloc = a_size;
/* Now we know we can insert into the first empty. */
mudstate.rdata->x_names[regnum] = alloc_sbuf(funcname);
strcpy(mudstate.rdata->x_names[regnum], name);
mudstate.rdata->x_regs[regnum] = alloc_lbuf(funcname);
memcpy(mudstate.rdata->x_regs[regnum], data, len + 1);
mudstate.rdata->x_lens[regnum] = len;
mudstate.rdata->dirty++;
return len;
}
FUNCTION(fun_setq)
{
int result, count, i;
if (nfargs < 2) {
safe_tprintf_str(buff, bufc,
"#-1 FUNCTION (SETQ) EXPECTS AT LEAST 2 ARGUMENTS BUT GOT %d",
nfargs);
return;
}
if (nfargs % 2 != 0) {
safe_tprintf_str(buff, bufc,
"#-1 FUNCTION (SETQ) EXPECTS AN EVEN NUMBER OF ARGUMENTS BUT GOT %d",
nfargs);
return;
}
if (nfargs > MAX_NFARGS - 2) {
/* Prevent people from doing something dumb by providing this
* too many arguments and thus having the fifteenth register
* contain the remaining args. Cut them off at the fourteenth.
*/
safe_tprintf_str(buff, bufc,
"#-1 FUNCTION (SETQ) EXPECTS NO MORE THAN %d ARGUMENTS BUT GOT %d",
MAX_NFARGS - 2, nfargs);
return;
}
if (nfargs == 2) {
result = set_register("fun_setq", fargs[0], fargs[1]);
if (result == -1)
safe_str("#-1 INVALID GLOBAL REGISTER", buff, bufc);
else if (result == -2)
safe_str("#-1 REGISTER LIMIT EXCEEDED", buff, bufc);
return;
}
count = 0;
for (i = 0; i < nfargs; i += 2) {
result = set_register("fun_setq", fargs[i], fargs[i + 1]);
if (result < 0)
count++;
}
if (count > 0)
safe_tprintf_str(buff, bufc, "#-1 ENCOUNTERED %d ERRORS", count);
}
FUNCTION(fun_setr)
{
int result;
result = set_register("fun_setr", fargs[0], fargs[1]);
if (result == -1)
safe_str("#-1 INVALID GLOBAL REGISTER", buff, bufc);
else if (result == -2)
safe_str("#-1 REGISTER LIMIT EXCEEDED", buff, bufc);
else if (result > 0)
safe_known_str(fargs[1], result, buff, bufc);
}
FUNCTION(fun_r)
{
int regnum;
char *p;
if (fargs[0][1] == '\0') {
regnum = qidx_chartab[(unsigned char) *fargs[0]];
if ((regnum < 0) || (regnum >= MAX_GLOBAL_REGS)) {
safe_str("#-1 INVALID GLOBAL REGISTER", buff, bufc);
} else {
if (mudstate.rdata && (mudstate.rdata->q_alloc > regnum) &&
mudstate.rdata->q_regs[regnum]) {
safe_known_str(mudstate.rdata->q_regs[regnum],
mudstate.rdata->q_lens[regnum],
buff, bufc);
}
}
return;
}
if (!mudstate.rdata || !mudstate.rdata->xr_alloc)
return;
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
for (regnum = 0; regnum < mudstate.rdata->xr_alloc; regnum++) {
if (mudstate.rdata->x_names[regnum] &&
!strcmp(fargs[0], mudstate.rdata->x_names[regnum])) {
if (mudstate.rdata->x_regs[regnum]) {
safe_known_str(mudstate.rdata->x_regs[regnum],
mudstate.rdata->x_lens[regnum],
buff, bufc);
return;
}
}
}
}
/* --------------------------------------------------------------------------
* wildmatch: Set the results of a wildcard match into the global registers.
* wildmatch(<string>,<wildcard pattern>,<register list>)
*/
FUNCTION(fun_wildmatch)
{
int i, nqregs;
char *t_args[NUM_ENV_VARS], **qregs; /* %0-%9 is limiting */
if (!wild(fargs[1], fargs[0], t_args, NUM_ENV_VARS)) {
safe_chr('0', buff, bufc);
return;
}
safe_chr('1', buff, bufc);
/* Parse the list of registers. Anything that we don't get is assumed
* to be -1. Fill them in.
*/
nqregs = list2arr(&qregs, NUM_ENV_VARS, fargs[2], &SPACE_DELIM);
for (i = 0; i < nqregs; i++) {
set_register("fun_wildmatch", qregs[i], t_args[i]);
}
/* Need to free up allocated memory from the match. */
for (i = 0; i < NUM_ENV_VARS; i++) {
if (t_args[i])
free_lbuf(t_args[i]);
}
XFREE(qregs, "fun_wildmatch.qregs");
}
/* --------------------------------------------------------------------------
* Auxiliary stuff for structures and variables.
*/
#define Set_Max(x,y) (x) = ((y) > (x)) ? (y) : (x);
static void print_htab_matches(obj, htab, buff, bufc)
dbref obj;
HASHTAB *htab;
char *buff;
char **bufc;
{
/* Lists out hashtable matches.
* Things which use this are computationally expensive, and should
* be discouraged.
*/
char tbuf[SBUF_SIZE], *tp, *bb_p;
HASHENT *hptr;
int i, len;
tp = tbuf;
safe_ltos(tbuf, &tp, obj);
safe_sb_chr('.', tbuf, &tp);
*tp = '\0';
len = strlen(tbuf);
bb_p = *bufc;
for (i = 0; i < htab->hashsize; i++) {
for (hptr = htab->entry[i]; hptr != NULL; hptr = hptr->next) {
if (!strncmp(tbuf, hptr->target.s, len)) {
if (*bufc != bb_p)
safe_chr(' ', buff, bufc);
safe_str(strchr(hptr->target.s, '.') + 1, buff, bufc);
}
}
}
}
/* ---------------------------------------------------------------------------
* fun_x: Returns a variable. x(<variable name>)
* fun_setx: Sets a variable. setx(<variable name>,<value>)
* fun_store: Sets and returns a variable. store(<variable name>,<value>)
* fun_xvars: Takes a list, parses it, sets it into variables.
* xvars(<space-separated variable list>,<list>,<delimiter>)
* fun_let: Takes a list of variables and their values, sets them, executes
* a function, and clears out the variables. (Scheme/ML-like.)
* If <list> is empty, the values are reset to null.
* let(<space-separated var list>,<list>,<body>,<delimiter>)
* fun_lvars: Shows a list of variables associated with that object.
* fun_clearvars: Clears all variables associated with that object.
*/
void set_xvar(obj, name, data)
dbref obj;
char *name;
char *data;
{
VARENT *xvar;
char tbuf[SBUF_SIZE], *tp, *p;
/* If we don't have at least one character in the name, toss it. */
if (!name || !*name)
return;
/* Variable string is '<dbref number minus #>.<variable name>'. We
* lowercase all names. Note that we're going to end up automatically
* truncating long names.
*/
tp = tbuf;
safe_ltos(tbuf, &tp, obj);
safe_sb_chr('.', tbuf, &tp);
for (p = name; *p; p++)
*p = tolower(*p);
safe_sb_str(name, tbuf, &tp);
*tp = '\0';
/* Search for it. If it exists, replace it. If we get a blank string,
* delete the variable.
*/
if ((xvar = (VARENT *) hashfind(tbuf, &mudstate.vars_htab))) {
if (xvar->text) {
XFREE(xvar->text, "xvar_data");
}
if (data && *data) {
xvar->text = (char *) XMALLOC(sizeof(char) * (strlen(data) + 1),
"xvar_data");
if (!xvar->text)
return; /* out of memory */
strcpy(xvar->text, data);
} else {
xvar->text = NULL;
XFREE(xvar, "xvar_struct");
hashdelete(tbuf, &mudstate.vars_htab);
s_VarsCount(obj, VarsCount(obj) - 1);
}
} else {
/* We haven't found it. If it's non-empty, set it, provided we're
* not running into a limit on the number of vars per object.
*/
if (VarsCount(obj) + 1 > mudconf.numvars_lim)
return;
if (data && *data) {
xvar = (VARENT *) XMALLOC(sizeof(VARENT), "xvar_struct");
if (!xvar)
return; /* out of memory */
xvar->text = (char *) XMALLOC(sizeof(char) * (strlen(data) + 1),
"xvar_data");
if (!xvar->text)
return; /* out of memory */
strcpy(xvar->text, data);
hashadd(tbuf, (int *) xvar, &mudstate.vars_htab, 0);
s_VarsCount(obj, VarsCount(obj) + 1);
Set_Max(mudstate.max_vars, mudstate.vars_htab.entries);
}
}
}
static void clear_xvars(obj, xvar_names, n_xvars)
dbref obj;
char **xvar_names;
int n_xvars;
{
/* Clear out an array of variable names. */
char pre[SBUF_SIZE], tbuf[SBUF_SIZE], *tp, *p;
VARENT *xvar;
int i;
/* Build our dbref bit first. */
tp = pre;
safe_ltos(pre, &tp, obj);
safe_sb_chr('.', pre, &tp);
*tp = '\0';
/* Go clear stuff. */
for (i = 0; i < n_xvars; i++) {
for (p = xvar_names[i]; *p; p++)
*p = tolower(*p);
tp = tbuf;
safe_sb_str(pre, tbuf, &tp);
safe_sb_str(xvar_names[i], tbuf, &tp);
*tp = '\0';
if ((xvar = (VARENT *) hashfind(tbuf, &mudstate.vars_htab))) {
if (xvar->text) {
XFREE(xvar->text, "xvar_data");
xvar->text = NULL;
}
XFREE(xvar, "xvar_struct");
hashdelete(tbuf, &mudstate.vars_htab);
}
}
s_VarsCount(obj, VarsCount(obj) - n_xvars);
}
void xvars_clr(player)
dbref player;
{
char tbuf[SBUF_SIZE], *tp;
HASHTAB *htab;
HASHENT *hptr, *last, *next;
int i, len;
VARENT *xvar;
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
*tp = '\0';
len = strlen(tbuf);
htab = &mudstate.vars_htab;
for (i = 0; i < htab->hashsize; i++) {
last = NULL;
for (hptr = htab->entry[i]; hptr != NULL; hptr = next) {
next = hptr->next;
if (!strncmp(tbuf, hptr->target.s, len)) {
if (last == NULL)
htab->entry[i] = next;
else
last->next = next;
xvar = (VARENT *) hptr->data;
XFREE(xvar->text, "xvar_data");
XFREE(xvar, "xvar_struct");
XFREE(hptr->target.s, "xvar_hptr_target");
XFREE(hptr, "xvar_hptr");
htab->deletes++;
htab->entries--;
if (htab->entry[i] == NULL)
htab->nulls++;
} else {
last = hptr;
}
}
}
s_VarsCount(player, 0);
}
FUNCTION(fun_x)
{
VARENT *xvar;
char tbuf[SBUF_SIZE], *tp, *p;
/* Variable string is '<dbref number minus #>.<variable name>' */
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[0], tbuf, &tp);
*tp = '\0';
if ((xvar = (VARENT *) hashfind(tbuf, &mudstate.vars_htab)))
safe_str(xvar->text, buff, bufc);
}
FUNCTION(fun_setx)
{
set_xvar(player, fargs[0], fargs[1]);
}
FUNCTION(fun_store)
{
set_xvar(player, fargs[0], fargs[1]);
safe_str(fargs[1], buff, bufc);
}
FUNCTION(fun_xvars)
{
char **xvar_names, **elems;
int n_xvars, n_elems;
char *varlist, *elemlist;
int i;
Delim isep;
VaChk_Only_In(3);
varlist = alloc_lbuf("fun_xvars.vars");
strcpy(varlist, fargs[0]);
n_xvars = list2arr(&xvar_names, LBUF_SIZE / 2, varlist, &SPACE_DELIM);
if (n_xvars == 0) {
free_lbuf(varlist);
XFREE(xvar_names, "fun_xvars.xvar_names");
return;
}
if (!fargs[1] || !*fargs[1]) {
/* Empty list, clear out the data. */
clear_xvars(player, xvar_names, n_xvars);
free_lbuf(varlist);
XFREE(xvar_names, "fun_xvars.xvar_names");
return;
}
elemlist = alloc_lbuf("fun_xvars.elems");
strcpy(elemlist, fargs[1]);
n_elems = list2arr(&elems, LBUF_SIZE / 2, elemlist, &isep);
if (n_elems != n_xvars) {
safe_str("#-1 LIST MUST BE OF EQUAL SIZE", buff, bufc);
free_lbuf(varlist);
free_lbuf(elemlist);
XFREE(xvar_names, "fun_xvars.xvar_names");
XFREE(elems, "fun_xvars.elems");
return;
}
for (i = 0; i < n_elems; i++) {
set_xvar(player, xvar_names[i], elems[i]);
}
free_lbuf(varlist);
free_lbuf(elemlist);
XFREE(xvar_names, "fun_xvars.xvar_names");
XFREE(elems, "fun_xvars.elems");
}
FUNCTION(fun_let)
{
char **xvar_names, **elems;
char *old_xvars[LBUF_SIZE / 2];
int n_xvars, n_elems;
char *varlist, *elemlist;
char *str, *bp, *p;
char pre[SBUF_SIZE], tbuf[SBUF_SIZE], *tp;
VARENT *xvar;
int i;
Delim isep;
VaChk_Only_In(4);
if (!fargs[0] || !*fargs[0])
return;
varlist = bp = alloc_lbuf("fun_let.vars");
str = fargs[0];
exec(varlist, &bp, player, caller, cause,
EV_FCHECK | EV_STRIP | EV_EVAL, &str, cargs, ncargs);
n_xvars = list2arr(&xvar_names, LBUF_SIZE / 2, varlist, &SPACE_DELIM);
if (n_xvars == 0) {
free_lbuf(varlist);
XFREE(xvar_names, "fun_let.xvar_names");
return;
}
/* Lowercase our variable names. */
for (i = 0, p = xvar_names[i]; *p; p++)
*p = tolower(*p);
/* Save our original values. Copying this stuff into an array is
* unnecessarily expensive because we allocate and free memory
* that we could theoretically just trade pointers around for --
* but this way is cleaner.
*/
tp = pre;
safe_ltos(pre, &tp, player);
safe_sb_chr('.', pre, &tp);
*tp = '\0';
for (i = 0; i < n_xvars; i++) {
tp = tbuf;
safe_sb_str(pre, tbuf, &tp);
safe_sb_str(xvar_names[i], tbuf, &tp);
*tp = '\0';
if ((xvar = (VARENT *) hashfind(tbuf, &mudstate.vars_htab))) {
if (xvar->text)
old_xvars[i] = XSTRDUP(xvar->text, "fun_let.preserve");
else
old_xvars[i] = NULL;
} else {
old_xvars[i] = NULL;
}
}
if (fargs[1] && *fargs[1]) {
/* We have data, so we should initialize variables to their values,
* ala xvars(). However, unlike xvars(), if we don't get a list,
* we just leave the values alone (we don't clear them out).
*/
elemlist = bp = alloc_lbuf("fun_let.elemlist");
str = fargs[1];
exec(elemlist, &bp, player, caller, cause,
EV_FCHECK | EV_STRIP | EV_EVAL, &str, cargs, ncargs);
n_elems = list2arr(&elems, LBUF_SIZE / 2, elemlist, &isep);
if (n_elems != n_xvars) {
safe_str("#-1 LIST MUST BE OF EQUAL SIZE", buff, bufc);
free_lbuf(varlist);
free_lbuf(elemlist);
for (i = 0; i < n_xvars; i++) {
if (old_xvars[i])
XFREE(old_xvars[i], "fun_let");
}
XFREE(xvar_names, "fun_let.xvar_names");
XFREE(elems, "fun_let.elems");
return;
}
for (i = 0; i < n_elems; i++) {
set_xvar(player, xvar_names[i], elems[i]);
}
free_lbuf(elemlist);
}
/* Now we go to execute our function body. */
str = fargs[2];
exec(buff, bufc, player, caller, cause,
EV_FCHECK | EV_STRIP | EV_EVAL, &str, cargs, ncargs);
/* Restore the old values. */
for (i = 0; i < n_xvars; i++) {
set_xvar(player, xvar_names[i], old_xvars[i]);
if (old_xvars[i])
XFREE(old_xvars[i], "fun_let");
}
free_lbuf(varlist);
XFREE(xvar_names, "fun_let.xvar_names");
XFREE(elems, "fun_let.elems");
}
FUNCTION(fun_lvars)
{
print_htab_matches(player, &mudstate.vars_htab, buff, bufc);
}
FUNCTION(fun_clearvars)
{
/* This is computationally expensive. Necessary, but its use should
* be avoided if possible.
*/
xvars_clr(player);
}
/* ---------------------------------------------------------------------------
* Structures.
*/
static int istype_char(str)
char *str;
{
if (strlen(str) == 1)
return 1;
else
return 0;
}
static int istype_dbref(str)
char *str;
{
dbref it;
if (*str++ != NUMBER_TOKEN)
return 0;
if (*str) {
it = parse_dbref(str);
return (Good_obj(it));
}
return 0;
}
static int istype_int(str)
char *str;
{
return (is_integer(str));
}
static int istype_float(str)
char *str;
{
return (is_number(str));
}
static int istype_string(str)
char *str;
{
char *p;
for (p = str; *p; p++) {
if (isspace(*p))
return 0;
}
return 1;
}
FUNCTION(fun_structure)
{
Delim isep; /* delim for default values */
Delim osep; /* output delim for structure values */
char tbuf[SBUF_SIZE], *tp;
char cbuf[SBUF_SIZE], *cp;
char *p;
char *comp_names, *type_names, *default_vals;
char **comp_array, **type_array, **def_array;
int n_comps, n_types, n_defs;
int i;
STRUCTDEF *this_struct;
COMPONENT *this_comp;
int check_type = 0;
VaChk_Only_In_Out(6);
/* Prevent null delimiters and line delimiters. */
if ((osep.len > 1) || (osep.str[0] == '\0') || (osep.str[0] == '\r')) {
notify_quiet(player, "You cannot use that output delimiter.");
safe_chr('0', buff, bufc);
return;
}
/* Enforce limits. */
if (StructCount(player) > mudconf.struct_lim) {
notify_quiet(player, "Too many structures.");
safe_chr('0', buff, bufc);
return;
}
/* If our structure name is too long, reject it. */
if (strlen(fargs[0]) > (SBUF_SIZE / 2) - 9) {
notify_quiet(player, "Structure name is too long.");
safe_chr('0', buff, bufc);
return;
}
/* No periods in structure names */
if (strchr(fargs[0], '.')) {
notify_quiet(player, "Structure names cannot contain periods.");
safe_chr('0', buff, bufc);
return;
}
/* The hashtable is indexed by <dbref number>.<structure name> */
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[0], tbuf, &tp);
*tp = '\0';
/* If we have this structure already, reject. */
if (hashfind(tbuf, &mudstate.structs_htab)) {
notify_quiet(player, "Structure is already defined.");
safe_chr('0', buff, bufc);
return;
}
/* Split things up. Make sure lists are the same size.
* If everything eventually goes well, comp_names and default_vals will
* REMAIN allocated.
*/
comp_names = XSTRDUP(fargs[1], "struct.comps");
n_comps = list2arr(&comp_array, LBUF_SIZE / 2, comp_names, &SPACE_DELIM);
if (n_comps < 1) {
notify_quiet(player, "There must be at least one component.");
safe_chr('0', buff, bufc);
XFREE(comp_names, "struct.comps");
XFREE(comp_array, "fun_structure.comp_array");
return;
}
/* Make sure that we have a sane name for the components. They must
* be smaller than half an SBUF.
*/
for (i = 0; i < n_comps; i++) {
if (strlen(comp_array[i]) > (SBUF_SIZE / 2) - 9) {
notify_quiet(player, "Component name is too long.");
safe_chr('0', buff, bufc);
XFREE(comp_names, "struct.comps");
XFREE(comp_array, "fun_structure.comp_array");
return;
}
}
type_names = alloc_lbuf("struct.types");
strcpy(type_names, fargs[2]);
n_types = list2arr(&type_array, LBUF_SIZE / 2, type_names, &SPACE_DELIM);
/* Make sure all types are valid. We look only at the first char, so
* typos will not be caught.
*/
for (i = 0; i < n_types; i++) {
switch (*(type_array[i])) {
case 'a': case 'A':
case 'c': case 'C':
case 'd': case 'D':
case 'i': case 'I':
case 'f': case 'F':
case 's': case 'S':
/* Valid types */
break;
default:
notify_quiet(player, "Invalid data type specified.");
safe_chr('0', buff, bufc);
XFREE(comp_names, "struct.comps");
XFREE(comp_array, "fun_structure.comp_array");
free_lbuf(type_names);
XFREE(type_array, "fun_structure.type_array");
return;
}
}
if (fargs[3] && *fargs[3]) {
default_vals = XSTRDUP(fargs[3], "struct.defaults");
n_defs = list2arr(&def_array, LBUF_SIZE / 2, default_vals, &isep);
} else {
default_vals = NULL;
n_defs = 0;
}
if ((n_comps != n_types) || (n_defs && (n_comps != n_defs))) {
notify_quiet(player, "List sizes must be identical.");
safe_chr('0', buff, bufc);
XFREE(comp_names, "struct.comps");
XFREE(comp_array, "fun_structure.comp_array");
free_lbuf(type_names);
XFREE(type_array, "fun_structure.type_array");
if (default_vals) {
XFREE(default_vals, "struct.defaults");
XFREE(def_array, "fun_structure.def_array");
}
return;
}
/* Allocate the structure and stuff it in the hashtable. Note that
* we retain one of the string arrays allocated by list2arr!
*/
this_struct = (STRUCTDEF *) XMALLOC(sizeof(STRUCTDEF), "struct_alloc");
this_struct->s_name = XSTRDUP(fargs[0], "struct.s_name");
this_struct->c_names = comp_array;
this_struct->c_array = (COMPONENT **) XCALLOC(n_comps, sizeof(COMPONENT *), "struct.n_comps");
this_struct->c_count = n_comps;
this_struct->delim = osep.str[0];
this_struct->n_instances = 0;
this_struct->names_base = comp_names;
this_struct->defs_base = default_vals;
hashadd(tbuf, (int *) this_struct, &mudstate.structs_htab, 0);
Set_Max(mudstate.max_structs, mudstate.structs_htab.entries);
/* Now that we're done with the base name, we can stick the
* joining period on the end.
*/
safe_sb_chr('.', tbuf, &tp);
*tp = '\0';
/* Allocate each individual component. */
for (i = 0; i < n_comps; i++) {
cp = cbuf;
safe_sb_str(tbuf, cbuf, &cp);
for (p = comp_array[i]; *p; p++)
*p = tolower(*p);
safe_sb_str(comp_array[i], cbuf, &cp);
*cp = '\0';
this_comp = (COMPONENT *) XMALLOC(sizeof(COMPONENT), "comp_alloc");
this_comp->def_val = (default_vals ? def_array[i] : NULL);
switch (*(type_array[i])) {
case 'a': case 'A':
this_comp->typer_func = NULL;
break;
case 'c': case 'C':
this_comp->typer_func = istype_char;
check_type = 1;
break;
case 'd': case 'D':
this_comp->typer_func = istype_dbref;
check_type = 1;
break;
case 'i': case 'I':
this_comp->typer_func = istype_int;
check_type = 1;
break;
case 'f': case 'F':
this_comp->typer_func = istype_float;
check_type = 1;
break;
case 's': case 'S':
this_comp->typer_func = istype_string;
check_type = 1;
break;
default:
/* Should never happen */
this_comp->typer_func = NULL;
}
this_struct->need_typecheck = check_type;
this_struct->c_array[i] = this_comp;
hashadd(cbuf, (int *) this_comp, &mudstate.cdefs_htab, 0);
Set_Max(mudstate.max_cdefs, mudstate.cdefs_htab.entries);
}
free_lbuf(type_names);
XFREE(type_array, "fun_structure.type_array");
if (default_vals) {
XFREE(def_array, "fun_structure.def_array");
}
s_StructCount(player, StructCount(player) + 1);
safe_chr('1', buff, bufc);
}
FUNCTION(fun_construct)
{
Delim isep;
char tbuf[SBUF_SIZE], *tp;
char ibuf[SBUF_SIZE], *ip;
char cbuf[SBUF_SIZE], *cp;
char *p;
STRUCTDEF *this_struct;
char *comp_names, *init_vals;
char **comp_array, **vals_array;
int n_comps, n_vals;
int i;
COMPONENT *c_ptr;
INSTANCE *inst_ptr;
STRUCTDATA *d_ptr;
int retval;
/* This one is complicated: We need two, four, or five args. */
VaChk_In(2, 5);
if (nfargs == 3) {
safe_tprintf_str(buff, bufc,
"#-1 FUNCTION (CONSTRUCT) EXPECTS 2 OR 4 OR 5 ARGUMENTS BUT GOT %d",
nfargs);
return;
}
/* Enforce limits. */
if (InstanceCount(player) > mudconf.instance_lim) {
notify_quiet(player, "Too many instances.");
safe_chr('0', buff, bufc);
return;
}
/* If our instance name is too long, reject it. */
if (strlen(fargs[0]) > (SBUF_SIZE / 2) - 9) {
notify_quiet(player, "Instance name is too long.");
safe_chr('0', buff, bufc);
return;
}
/* Make sure this instance doesn't exist. */
ip = ibuf;
safe_ltos(ibuf, &ip, player);
safe_sb_chr('.', ibuf, &ip);
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[0], ibuf, &ip);
*ip = '\0';
if (hashfind(ibuf, &mudstate.instance_htab)) {
notify_quiet(player, "That instance has already been defined.");
safe_chr('0', buff, bufc);
return;
}
/* Look up the structure. */
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
for (p = fargs[1]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[1], tbuf, &tp);
*tp = '\0';
this_struct = (STRUCTDEF *) hashfind(tbuf, &mudstate.structs_htab);
if (!this_struct) {
notify_quiet(player, "No such structure.");
safe_chr('0', buff, bufc);
return;
}
/* Check to make sure that all the component names are valid, if we
* have been given defaults. Also, make sure that the defaults are
* of the appropriate type.
*/
safe_sb_chr('.', tbuf, &tp);
*tp = '\0';
if (fargs[2] && *fargs[2] && fargs[3] && *fargs[3]) {
comp_names = alloc_lbuf("construct.comps");
strcpy(comp_names, fargs[2]);
n_comps = list2arr(&comp_array, LBUF_SIZE / 2, comp_names, &SPACE_DELIM);
init_vals = alloc_lbuf("construct.vals");
strcpy(init_vals, fargs[3]);
n_vals = list2arr(&vals_array, LBUF_SIZE / 2, init_vals, &isep);
if (n_comps != n_vals) {
notify_quiet(player, "List sizes must be identical.");
safe_chr('0', buff, bufc);
free_lbuf(comp_names);
free_lbuf(init_vals);
XFREE(comp_array, "fun_construct.comp_array");
XFREE(vals_array, "fun_construct.vals_array");
return;
}
for (i = 0; i < n_comps; i++) {
cp = cbuf;
safe_sb_str(tbuf, cbuf, &cp);
for (p = comp_array[i]; *p; p++)
*p = tolower(*p);
safe_sb_str(comp_array[i], cbuf, &cp);
c_ptr = (COMPONENT *) hashfind(cbuf, &mudstate.cdefs_htab);
if (!c_ptr) {
notify_quiet(player, "Invalid component name.");
safe_chr('0', buff, bufc);
free_lbuf(comp_names);
free_lbuf(init_vals);
XFREE(comp_array, "fun_construct.comp_array");
XFREE(vals_array, "fun_construct.vals_array");
return;
}
if (c_ptr->typer_func) {
retval = (*(c_ptr->typer_func)) (vals_array[i]);
if (!retval) {
notify_quiet(player, "Default value is of invalid type.");
safe_chr('0', buff, bufc);
free_lbuf(comp_names);
free_lbuf(init_vals);
XFREE(comp_array, "fun_construct.comp_array");
XFREE(vals_array, "fun_construct.vals_array");
return;
}
}
}
} else if ((!fargs[2] || !*fargs[2]) && (!fargs[3] || !*fargs[3])) {
/* Blank initializers. This is just fine. */
comp_names = init_vals = NULL;
comp_array = vals_array = NULL;
n_comps = n_vals = 0;
} else {
notify_quiet(player, "List sizes must be identical.");
safe_chr('0', buff, bufc);
return;
}
/* Go go gadget constructor.
* Allocate the instance. We should have already made sure that the
* instance doesn't exist.
*/
inst_ptr = (INSTANCE *) XMALLOC(sizeof(INSTANCE), "constructor.inst");
inst_ptr->datatype = this_struct;
hashadd(ibuf, (int *) inst_ptr, &mudstate.instance_htab, 0);
Set_Max(mudstate.max_instance, mudstate.instance_htab.entries);
/* Populate with default values. */
for (i = 0; i < this_struct->c_count; i++) {
d_ptr = (STRUCTDATA *) XMALLOC(sizeof(STRUCTDATA), "constructor.data");
if (this_struct->c_array[i]->def_val) {
d_ptr->text = (char *)
XSTRDUP(this_struct->c_array[i]->def_val, "constructor.dtext");
} else {
d_ptr->text = NULL;
}
tp = tbuf;
safe_sb_str(ibuf, tbuf, &tp);
safe_sb_chr('.', tbuf, &tp);
safe_sb_str(this_struct->c_names[i], tbuf, &tp);
*tp = '\0';
hashadd(tbuf, (int *) d_ptr, &mudstate.instdata_htab, 0);
Set_Max(mudstate.max_instdata, mudstate.instdata_htab.entries);
}
/* Overwrite with component values. */
for (i = 0; i < n_comps; i++) {
tp = tbuf;
safe_sb_str(ibuf, tbuf, &tp);
safe_sb_chr('.', tbuf, &tp);
safe_sb_str(comp_array[i], tbuf, &tp);
*tp = '\0';
d_ptr = (STRUCTDATA *) hashfind(tbuf, &mudstate.instdata_htab);
if (d_ptr) {
if (d_ptr->text)
XFREE(d_ptr->text, "constructor.dtext");
if (vals_array[i] && *(vals_array[i]))
d_ptr->text = XSTRDUP(vals_array[i], "constructor.dtext");
else
d_ptr->text = NULL;
}
}
if (comp_names) {
free_lbuf(comp_names);
XFREE(comp_array, "fun_construct.comp_array");
}
if (init_vals) {
free_lbuf(init_vals);
XFREE(vals_array, "fun_construct.vals_array");
}
this_struct->n_instances += 1;
s_InstanceCount(player, InstanceCount(player) + 1);
safe_chr('1', buff, bufc);
}
static void load_structure(player, buff, bufc, inst_name, str_name, raw_text,
sep, use_def_delim)
dbref player;
char *buff, **bufc;
char *inst_name, *str_name, *raw_text;
char sep;
int use_def_delim;
{
char tbuf[SBUF_SIZE], *tp;
char ibuf[SBUF_SIZE], *ip;
char *p;
STRUCTDEF *this_struct;
char *val_list;
char **val_array;
int n_vals;
INSTANCE *inst_ptr;
STRUCTDATA *d_ptr;
int i;
Delim isep;
/* Enforce limits. */
if (InstanceCount(player) > mudconf.instance_lim) {
notify_quiet(player, "Too many instances.");
safe_chr('0', buff, bufc);
return;
}
/* If our instance name is too long, reject it. */
if (strlen(inst_name) > (SBUF_SIZE / 2) - 9) {
notify_quiet(player, "Instance name is too long.");
safe_chr('0', buff, bufc);
return;
}
/* Make sure this instance doesn't exist. */
ip = ibuf;
safe_ltos(ibuf, &ip, player);
safe_sb_chr('.', ibuf, &ip);
for (p = inst_name; *p; p++)
*p = tolower(*p);
safe_sb_str(inst_name, ibuf, &ip);
*ip = '\0';
if (hashfind(ibuf, &mudstate.instance_htab)) {
notify_quiet(player, "That instance has already been defined.");
safe_chr('0', buff, bufc);
return;
}
/* Look up the structure. */
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
for (p = str_name; *p; p++)
*p = tolower(*p);
safe_sb_str(str_name, tbuf, &tp);
*tp = '\0';
this_struct = (STRUCTDEF *) hashfind(tbuf, &mudstate.structs_htab);
if (!this_struct) {
notify_quiet(player, "No such structure.");
safe_chr('0', buff, bufc);
return;
}
/* Chop up the raw stuff according to the delimiter. */
if (use_def_delim)
isep.str[0] = this_struct->delim;
else
isep.str[0] = sep;
val_list = alloc_lbuf("load.val_list");
strcpy(val_list, raw_text);
n_vals = list2arr(&val_array, LBUF_SIZE / 2, val_list, &isep);
if (n_vals != this_struct->c_count) {
notify_quiet(player, "Incorrect number of components.");
safe_chr('0', buff, bufc);
free_lbuf(val_list);
XFREE(val_array, "load_structure.val_array");
return;
}
/* Check the types of the data we've been passed. */
for (i = 0; i < n_vals; i++) {
if (this_struct->c_array[i]->typer_func &&
!((*(this_struct->c_array[i]->typer_func)) (val_array[i]))) {
notify_quiet(player, "Value is of invalid type.");
safe_chr('0', buff, bufc);
free_lbuf(val_list);
XFREE(val_array, "load_structure.val_array");
return;
}
}
/* Allocate the instance. We should have already made sure that the
* instance doesn't exist.
*/
inst_ptr = (INSTANCE *) XMALLOC(sizeof(INSTANCE), "constructor.inst");
inst_ptr->datatype = this_struct;
hashadd(ibuf, (int *) inst_ptr, &mudstate.instance_htab, 0);
Set_Max(mudstate.max_instance, mudstate.instance_htab.entries);
/* Stuff data into memory. */
for (i = 0; i < this_struct->c_count; i++) {
d_ptr = (STRUCTDATA *) XMALLOC(sizeof(STRUCTDATA), "constructor.data");
if (val_array[i] && *(val_array[i]))
d_ptr->text = XSTRDUP(val_array[i], "constructor.dtext");
else
d_ptr->text = NULL;
tp = tbuf;
safe_sb_str(ibuf, tbuf, &tp);
safe_sb_chr('.', tbuf, &tp);
safe_sb_str(this_struct->c_names[i], tbuf, &tp);
*tp = '\0';
hashadd(tbuf, (int *) d_ptr, &mudstate.instdata_htab, 0);
Set_Max(mudstate.max_instdata, mudstate.instdata_htab.entries);
}
free_lbuf(val_list);
XFREE(val_array, "load_structure.val_array");
this_struct->n_instances += 1;
s_InstanceCount(player, InstanceCount(player) + 1);
safe_chr('1', buff, bufc);
}
FUNCTION(fun_load)
{
Delim isep;
VaChk_Only_InPure(4);
load_structure(player, buff, bufc,
fargs[0], fargs[1], fargs[2],
isep.str[0], (nfargs != 4) ? 1 : 0);
}
FUNCTION(fun_read)
{
dbref it, aowner;
int atr, aflags, alen;
char *atext;
if (!parse_attrib(player, fargs[0], &it, &atr, 1) || (atr == NOTHING)) {
safe_chr('0', buff, bufc);
return;
}
atext = atr_pget(it, atr, &aowner, &aflags, &alen);
load_structure(player, buff, bufc,
fargs[1], fargs[2], atext,
GENERIC_STRUCT_DELIM, 0);
free_lbuf(atext);
}
FUNCTION(fun_delimit)
{
dbref it, aowner;
int atr, aflags, alen, nitems, i, over = 0;
char *atext, **ptrs;
Delim isep;
/* This function is unusual in that the second argument is a delimiter
* string of arbitrary length, rather than a character. The input
* delimiter is the final, optional argument; if it's not specified
* it defaults to the "null" structure delimiter. (This function's
* primary purpose is to extract out data that's been stored as a
* "null"-delimited structure, but it's also useful for transforming
* any delim-separated list to a list whose elements are separated
* by arbitrary strings.)
*/
VaChk_Only_InPure(3);
if (nfargs != 3)
isep.str[0] = GENERIC_STRUCT_DELIM;
if (!parse_attrib(player, fargs[0], &it, &atr, 1) || (atr == NOTHING)) {
safe_noperm(buff, bufc);
return;
}
atext = atr_pget(it, atr, &aowner, &aflags, &alen);
nitems = list2arr(&ptrs, LBUF_SIZE / 2, atext, &isep);
if (nitems) {
over = safe_str_fn(ptrs[0], buff, bufc);
}
for (i = 1; !over && (i < nitems); i++) {
over = safe_str_fn(fargs[1], buff, bufc);
if (!over)
over = safe_str_fn(ptrs[i], buff, bufc);
}
free_lbuf(atext);
XFREE(ptrs, "fun_delimit.ptrs");
}
FUNCTION(fun_z)
{
char tbuf[SBUF_SIZE], *tp;
char *p;
STRUCTDATA *s_ptr;
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[0], tbuf, &tp);
safe_sb_chr('.', tbuf, &tp);
for (p = fargs[1]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[1], tbuf, &tp);
*tp = '\0';
s_ptr = (STRUCTDATA *) hashfind(tbuf, &mudstate.instdata_htab);
if (!s_ptr || !s_ptr->text)
return;
safe_str(s_ptr->text, buff, bufc);
}
FUNCTION(fun_modify)
{
char tbuf[SBUF_SIZE], *tp;
char cbuf[SBUF_SIZE], *cp;
char *endp, *p;
INSTANCE *inst_ptr;
COMPONENT *c_ptr;
STRUCTDATA *s_ptr;
char **words, **vals;
int retval, nwords, nvals, i, n_mod;
Delim isep;
VaChk_Only_In(4);
/* Find the instance first, since this is how we get our typechecker. */
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[0], tbuf, &tp);
*tp = '\0';
endp = tp; /* save where we are */
inst_ptr = (INSTANCE *) hashfind(tbuf, &mudstate.instance_htab);
if (!inst_ptr) {
notify_quiet(player, "No such instance.");
safe_chr('0', buff, bufc);
return;
}
/* Process for each component in the list. */
nwords = list2arr(&words, LBUF_SIZE / 2, fargs[1], &SPACE_DELIM);
nvals = list2arr(&vals, LBUF_SIZE / 2, fargs[2], &isep);
n_mod = 0;
for (i = 0; i < nwords; i++) {
/* Find the component and check the type. */
if (inst_ptr->datatype->need_typecheck) {
cp = cbuf;
safe_ltos(cbuf, &cp, player);
safe_sb_chr('.', cbuf, &cp);
safe_sb_str(inst_ptr->datatype->s_name, cbuf, &cp);
safe_sb_chr('.', cbuf, &cp);
for (p = words[i]; *p; p++)
*p = tolower(*p);
safe_sb_str(words[i], cbuf, &cp);
*cp = '\0';
c_ptr = (COMPONENT *) hashfind(cbuf, &mudstate.cdefs_htab);
if (!c_ptr) {
notify_quiet(player, "No such component.");
continue;
}
if (c_ptr->typer_func) {
retval = (*(c_ptr->typer_func)) (fargs[2]);
if (!retval) {
notify_quiet(player, "Value is of invalid type.");
continue;
}
}
}
/* Now go set it. */
tp = endp;
safe_sb_chr('.', tbuf, &tp);
safe_sb_str(words[i], tbuf, &tp);
*tp = '\0';
s_ptr = (STRUCTDATA *) hashfind(tbuf, &mudstate.instdata_htab);
if (!s_ptr) {
notify_quiet(player, "No such data.");
continue;
}
if (s_ptr->text)
XFREE(s_ptr->text, "modify.dtext");
if ((i < nvals) && vals[i] && *vals[i]) {
s_ptr->text = XSTRDUP(vals[i], "modify.dtext");
} else {
s_ptr->text = NULL;
}
n_mod++;
}
XFREE(words, "fun_modify.words");
XFREE(vals, "fun_modify.vals");
safe_ltos(buff, bufc, n_mod);
}
static void unload_structure(player, buff, bufc, inst_name, sep, use_def_delim)
dbref player;
char *buff, **bufc;
char *inst_name;
char sep;
int use_def_delim;
{
char tbuf[SBUF_SIZE], *tp;
char ibuf[SBUF_SIZE], *ip;
INSTANCE *inst_ptr;
char *p;
STRUCTDEF *this_struct;
STRUCTDATA *d_ptr;
int i;
/* Get the instance. */
ip = ibuf;
safe_ltos(ibuf, &ip, player);
safe_sb_chr('.', ibuf, &ip);
for (p = inst_name; *p; p++)
*p = tolower(*p);
safe_sb_str(inst_name, ibuf, &ip);
*ip = '\0';
inst_ptr = (INSTANCE *) hashfind(ibuf, &mudstate.instance_htab);
if (!inst_ptr)
return;
/* From the instance, we can get a pointer to the structure. We then
* have the information we need to figure out what components are
* associated with this, and print them appropriately.
*/
safe_sb_chr('.', ibuf, &ip);
*ip = '\0';
this_struct = inst_ptr->datatype;
/* Our delimiter is a special case. */
if (use_def_delim)
sep = this_struct->delim;
for (i = 0; i < this_struct->c_count; i++) {
if (i != 0) {
safe_chr(sep, buff, bufc);
}
tp = tbuf;
safe_sb_str(ibuf, tbuf, &tp);
safe_sb_str(this_struct->c_names[i], tbuf, &tp);
*tp = '\0';
d_ptr = (STRUCTDATA *) hashfind(tbuf, &mudstate.instdata_htab);
if (d_ptr && d_ptr->text)
safe_str(d_ptr->text, buff, bufc);
}
}
FUNCTION(fun_unload)
{
Delim isep;
VaChk_Only_InPure(2);
unload_structure(player, buff, bufc, fargs[0], isep.str[0],
(nfargs != 2) ? 1 : 0);
}
FUNCTION(fun_write)
{
dbref it, aowner;
int atrnum, aflags;
char tbuf[LBUF_SIZE], *tp, *str;
ATTR *attr;
if (!parse_thing_slash(player, fargs[0], &str, &it)) {
safe_nomatch(buff, bufc);
return;
}
tp = tbuf;
*tp = '\0';
unload_structure(player, tbuf, &tp, fargs[1], GENERIC_STRUCT_DELIM, 0);
if (*tbuf) {
atrnum = mkattr(str);
if (atrnum <= 0) {
safe_str("#-1 UNABLE TO CREATE ATTRIBUTE", buff, bufc);
return;
}
attr = atr_num(atrnum);
atr_pget_info(it, atrnum, &aowner, &aflags);
if (!attr || !Set_attr(player, it, attr, aflags) ||
(attr->check != NULL)) {
safe_noperm(buff, bufc);
} else {
atr_add(it, atrnum, tbuf, Owner(player), aflags | AF_STRUCTURE);
}
}
}
FUNCTION(fun_destruct)
{
char tbuf[SBUF_SIZE], *tp;
char ibuf[SBUF_SIZE], *ip;
INSTANCE *inst_ptr;
char *p;
STRUCTDEF *this_struct;
STRUCTDATA *d_ptr;
int i;
/* Get the instance. */
ip = ibuf;
safe_ltos(ibuf, &ip, player);
safe_sb_chr('.', ibuf, &ip);
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[0], ibuf, &ip);
*ip = '\0';
inst_ptr = (INSTANCE *) hashfind(ibuf, &mudstate.instance_htab);
if (!inst_ptr) {
notify_quiet(player, "No such instance.");
safe_chr('0', buff, bufc);
return;
}
/* Now we can get a pointer to the structure and find the rest of the
* components.
*/
this_struct = inst_ptr->datatype;
XFREE(inst_ptr, "constructor.inst");
hashdelete(ibuf, &mudstate.instance_htab);
safe_sb_chr('.', ibuf, &ip);
*ip = '\0';
for (i = 0; i < this_struct->c_count; i++) {
tp = tbuf;
safe_sb_str(ibuf, tbuf, &tp);
safe_sb_str(this_struct->c_names[i], tbuf, &tp);
*tp = '\0';
d_ptr = (STRUCTDATA *) hashfind(tbuf, &mudstate.instdata_htab);
if (d_ptr) {
if (d_ptr->text)
XFREE(d_ptr->text, "constructor.data");
XFREE(d_ptr, "constructor.data");
hashdelete(tbuf, &mudstate.instdata_htab);
}
}
this_struct->n_instances -= 1;
s_InstanceCount(player, InstanceCount(player) - 1);
safe_chr('1', buff, bufc);
}
FUNCTION(fun_unstructure)
{
char tbuf[SBUF_SIZE], *tp;
char cbuf[SBUF_SIZE], *cp;
char *p;
STRUCTDEF *this_struct;
int i;
/* Find the structure */
tp = tbuf;
safe_ltos(tbuf, &tp, player);
safe_sb_chr('.', tbuf, &tp);
for (p = fargs[0]; *p; p++)
*p = tolower(*p);
safe_sb_str(fargs[0], tbuf, &tp);
*tp = '\0';
this_struct = (STRUCTDEF *) hashfind(tbuf, &mudstate.structs_htab);
if (!this_struct) {
notify_quiet(player, "No such structure.");
safe_chr('0', buff, bufc);
return;
}
/* Can't delete what's in use. */
if (this_struct->n_instances > 0) {
notify_quiet(player, "This structure is in use.");
safe_chr('0', buff, bufc);
return;
}
/* Wipe the structure from the hashtable. */
hashdelete(tbuf, &mudstate.structs_htab);
/* Wipe out every component definition. */
safe_sb_chr('.', tbuf, &tp);
*tp = '\0';
for (i = 0; i < this_struct->c_count; i++) {
cp = cbuf;
safe_sb_str(tbuf, cbuf, &cp);
safe_sb_str(this_struct->c_names[i], cbuf, &cp);
*cp = '\0';
if (this_struct->c_array[i]) {
XFREE(this_struct->c_array[i], "comp_alloc");
}
hashdelete(cbuf, &mudstate.cdefs_htab);
}
/* Free up our bit of memory. */
XFREE(this_struct->s_name, "struct.s_name");
if (this_struct->names_base)
XFREE(this_struct->names_base, "struct.names_base");
if (this_struct->defs_base)
XFREE(this_struct->defs_base, "struct.defs_base");
XFREE(this_struct->c_names, "struct.c_names");
XFREE(this_struct, "struct_alloc");
s_StructCount(player, StructCount(player) - 1);
safe_chr('1', buff, bufc);
}
FUNCTION(fun_lstructures)
{
print_htab_matches(player, &mudstate.structs_htab, buff, bufc);
}
FUNCTION(fun_linstances)
{
print_htab_matches(player, &mudstate.instance_htab, buff, bufc);
}
void structure_clr(thing)
dbref thing;
{
/* Wipe out all structure information associated with an object.
* Find all the object's instances. Destroy them.
* Then, find all the object's defined structures, and destroy those.
*/
HASHTAB *htab;
HASHENT *hptr;
char tbuf[SBUF_SIZE], ibuf[SBUF_SIZE], cbuf[SBUF_SIZE], *tp, *ip, *cp;
int i, j, len, count;
INSTANCE **inst_array;
char **name_array;
STRUCTDEF *this_struct;
STRUCTDATA *d_ptr;
STRUCTDEF **struct_array;
/* The instance table is indexed as <dbref number>.<instance name> */
tp = tbuf;
safe_ltos(tbuf, &tp, thing);
safe_sb_chr('.', tbuf, &tp);
*tp = '\0';
len = strlen(tbuf);
/* Because of the hashtable rechaining that's done, we cannot simply
* walk the hashtable and delete entries as we go. Instead, we've
* got to keep track of all of our pointers, and go back and do
* them one by one.
*/
inst_array = (INSTANCE **) XCALLOC(mudconf.instance_lim + 1,
sizeof(INSTANCE *),
"structure_clr.inst_array");
name_array = (char **) XCALLOC(mudconf.instance_lim + 1, sizeof(char *),
"structure_clr.name_array");
htab = &mudstate.instance_htab;
count = 0;
for (i = 0; i < htab->hashsize; i++) {
for (hptr = htab->entry[i]; hptr != NULL; hptr = hptr->next) {
if (!strncmp(tbuf, hptr->target.s, len)) {
name_array[count] = (char *) hptr->target.s;
inst_array[count] = (INSTANCE *) hptr->data;
count++;
}
}
}
/* Now that we have the pointers to the instances, we can get the
* structure definitions, and use that to hunt down and wipe the
* components.
*/
if (count > 0) {
for (i = 0; i < count; i++) {
this_struct = inst_array[i]->datatype;
XFREE(inst_array[i], "constructor.inst");
hashdelete(name_array[i], &mudstate.instance_htab);
ip = ibuf;
safe_sb_str(name_array[i], ibuf, &ip);
safe_sb_chr('.', ibuf, &ip);
*ip = '\0';
for (j = 0; j < this_struct->c_count; j++) {
cp = cbuf;
safe_sb_str(ibuf, cbuf, &cp);
safe_sb_str(this_struct->c_names[j], cbuf, &cp);
*cp = '\0';
d_ptr = (STRUCTDATA *) hashfind(cbuf, &mudstate.instdata_htab);
if (d_ptr) {
if (d_ptr->text)
XFREE(d_ptr->text, "constructor.data");
XFREE(d_ptr, "constructor.data");
hashdelete(cbuf, &mudstate.instdata_htab);
}
}
this_struct->n_instances -= 1;
}
}
XFREE(inst_array, "structure_clr.inst_array");
XFREE(name_array, "structure_clr.name_array");
/* The structure table is indexed as <dbref number>.<struct name> */
tp = tbuf;
safe_ltos(tbuf, &tp, thing);
safe_sb_chr('.', tbuf, &tp);
*tp = '\0';
len = strlen(tbuf);
/* Again, we have the hashtable rechaining problem. */
struct_array = (STRUCTDEF **) XCALLOC(mudconf.struct_lim + 1,
sizeof(STRUCTDEF *),
"structure_clr.struct_array");
name_array = (char **) XCALLOC(mudconf.struct_lim + 1, sizeof(char *),
"structure_clr.name_array2");
htab = &mudstate.structs_htab;
count = 0;
for (i = 0; i < htab->hashsize; i++) {
for (hptr = htab->entry[i]; hptr != NULL; hptr = hptr->next) {
if (!strncmp(tbuf, hptr->target.s, len)) {
name_array[count] = (char *) hptr->target.s;
struct_array[count] = (STRUCTDEF *) hptr->data;
count++;
}
}
}
/* We have the pointers to the structures. Flag a big error if they're
* still in use, wipe them from the hashtable, then wipe out every
* component definition. Free up the memory.
*/
if (count > 0) {
for (i = 0; i < count; i++) {
if (struct_array[i]->n_instances > 0) {
STARTLOG(LOG_ALWAYS, "BUG", "STRUCT")
log_name(thing);
log_printf(" structure %s has %d allocated instances uncleared.",
name_array[i], struct_array[i]->n_instances);
ENDLOG
}
hashdelete(name_array[i], &mudstate.structs_htab);
ip = ibuf;
safe_sb_str(name_array[i], ibuf, &ip);
safe_sb_chr('.', ibuf, &ip);
*ip = '\0';
for (j = 0; j < struct_array[i]->c_count; j++) {
cp = cbuf;
safe_sb_str(ibuf, cbuf, &cp);
safe_sb_str(struct_array[i]->c_names[j], cbuf, &cp);
*cp = '\0';
if (struct_array[i]->c_array[j]) {
XFREE(struct_array[i]->c_array[j], "comp_alloc");
}
hashdelete(cbuf, &mudstate.cdefs_htab);
}
XFREE(struct_array[i]->s_name, "struct.s_name");
if (struct_array[i]->names_base)
XFREE(struct_array[i]->names_base, "struct.names_base");
if (struct_array[i]->defs_base)
XFREE(struct_array[i]->defs_base, "struct.defs_base");
XFREE(struct_array[i]->c_names, "struct.c_names");
XFREE(struct_array[i], "struct_alloc");
}
}
XFREE(struct_array, "structure_clr.struct_array");
XFREE(name_array, "structure_clr.name_array2");
}
/* --------------------------------------------------------------------------
* Auxiliary functions for stacks.
*/
#define stack_get(x) ((OBJSTACK *) nhashfind(x, &mudstate.objstack_htab))
#define stack_object(p,x) \
x = match_thing(p, fargs[0]); \
if (!Good_obj(x)) { \
return; \
} \
if (!Controls(p, x)) { \
notify_quiet(p, NOPERM_MESSAGE); \
return; \
}
/* ---------------------------------------------------------------------------
* Object stack functions.
*/
void stack_clr(thing)
dbref thing;
{
OBJSTACK *sp, *tp, *xp;
sp = stack_get(thing);
if (sp) {
for (tp = sp; tp != NULL; ) {
XFREE(tp->data, "stack_clr_data");
xp = tp;
tp = tp->next;
XFREE(xp, "stack_clr");
}
nhashdelete(thing, &mudstate.objstack_htab);
s_StackCount(thing, 0);
}
}
static int stack_set(thing, sp)
dbref thing;
OBJSTACK *sp;
{
OBJSTACK *xsp;
int stat;
if (!sp) {
nhashdelete(thing, &mudstate.objstack_htab);
return 1;
}
xsp = stack_get(thing);
if (xsp) {
stat = nhashrepl(thing, (int *) sp, &mudstate.objstack_htab);
} else {
stat = nhashadd(thing, (int *) sp, &mudstate.objstack_htab);
Set_Max(mudstate.max_stacks, mudstate.objstack_htab.entries);
}
if (stat < 0) { /* failure for some reason */
STARTLOG(LOG_BUGS, "STK", "SET")
log_name(thing);
ENDLOG
stack_clr(thing);
return 0;
}
return 1;
}
FUNCTION(fun_empty)
{
dbref it;
VaChk_Range(0, 1);
if (!fargs[0]) {
it = player;
} else {
stack_object(player, it);
}
stack_clr(it);
}
FUNCTION(fun_items)
{
dbref it;
if (!fargs[0]) {
it = player;
} else {
stack_object(player, it);
}
safe_ltos(buff, bufc, StackCount(it));
}
FUNCTION(fun_push)
{
dbref it;
char *data;
OBJSTACK *sp;
VaChk_Range(1, 2);
if (!fargs[1]) {
it = player;
data = fargs[0];
} else {
stack_object(player, it);
data = fargs[1];
}
if (StackCount(it) + 1 > mudconf.stack_lim)
return;
sp = (OBJSTACK *) XMALLOC(sizeof(OBJSTACK), "stack_push");
if (!sp) /* out of memory, ouch */
return;
sp->next = stack_get(it);
sp->data = (char *) XMALLOC(sizeof(char) * (strlen(data) + 1),
"stack_push_data");
if (! sp->data)
return;
strcpy(sp->data, data);
if (stack_set(it, sp))
s_StackCount(it, StackCount(it) + 1);
}
FUNCTION(fun_dup)
{
dbref it;
OBJSTACK *hp; /* head of stack */
OBJSTACK *tp; /* temporary stack pointer */
OBJSTACK *sp; /* new stack element */
int pos, count = 0;
VaChk_Range(0, 2);
if (!fargs[0]) {
it = player;
} else {
stack_object(player, it);
}
if (StackCount(it) + 1 > mudconf.stack_lim)
return;
if (!fargs[1] || !*fargs[1]) {
pos = 0;
} else {
pos = atoi(fargs[1]);
}
hp = stack_get(it);
for (tp = hp; (count != pos) && (tp != NULL); count++, tp = tp->next)
;
if (!tp) {
notify_quiet(player, "No such item on stack.");
return;
}
sp = (OBJSTACK *) XMALLOC(sizeof(OBJSTACK), "stack_dup");
if (!sp)
return;
sp->next = hp;
sp->data = (char *) XMALLOC(sizeof(char) * (strlen(tp->data) + 1),
"stack_dup_data");
if (!sp->data)
return;
strcpy(sp->data, tp->data);
if (stack_set(it, sp))
s_StackCount(it, StackCount(it) + 1);
}
FUNCTION(fun_swap)
{
dbref it;
OBJSTACK *sp, *tp;
VaChk_Range(0, 1);
if (!fargs[0]) {
it = player;
} else {
stack_object(player, it);
}
sp = stack_get(it);
if (!sp || (sp->next == NULL)) {
notify_quiet(player, "Not enough items on stack.");
return;
}
tp = sp->next;
sp->next = tp->next;
tp->next = sp;
stack_set(it, tp);
}
FUNCTION(handle_pop)
{
dbref it;
int pos, count = 0, peek_flag, toss_flag;
OBJSTACK *sp;
OBJSTACK *prev = NULL;
peek_flag = Is_Func(POP_PEEK);
toss_flag = Is_Func(POP_TOSS);
VaChk_Range(0, 2);
if (!fargs[0]) {
it = player;
} else {
stack_object(player, it);
}
if (!fargs[1] || !*fargs[1]) {
pos = 0;
} else {
pos = atoi(fargs[1]);
}
sp = stack_get(it);
if (!sp)
return;
while (count != pos) {
if (!sp)
return;
prev = sp;
sp = sp->next;
count++;
}
if (!sp)
return;
if (!toss_flag) {
safe_str(sp->data, buff, bufc);
}
if (!peek_flag) {
if (count == 0) {
stack_set(it, sp->next);
} else {
prev->next = sp->next;
}
XFREE(sp->data, "stack_pop_data");
XFREE(sp, "stack_pop");
s_StackCount(it, StackCount(it) - 1);
}
}
FUNCTION(fun_popn)
{
dbref it;
int pos, nitems, i, count = 0, over = 0;
OBJSTACK *sp, *tp, *xp;
OBJSTACK *prev = NULL;
Delim osep;
char *bb_p;
VaChk_Only_Out(4);
stack_object(player, it);
pos = atoi(fargs[1]);
nitems = atoi(fargs[2]);
sp = stack_get(it);
if (!sp)
return;
while (count != pos) {
if (!sp)
return;
prev = sp;
sp = sp->next;
count++;
}
if (!sp)
return;
/* We've now hit the start item, the first item. Copy 'em off. */
for (i = 0, tp = sp, bb_p = *bufc; (i < nitems) && (tp != NULL); i++) {
if (!over) {
/* We have to pop off the items regardless of whether
* or not there's an overflow, but we can save ourselves
* some copying if so.
*/
if (*bufc != bb_p) {
print_sep(&osep, buff, bufc);
}
over = safe_str_fn(tp->data, buff, bufc);
}
xp = tp;
tp = tp->next;
XFREE(xp->data, "stack_popn_data");
XFREE(xp, "stack_popn");
s_StackCount(it, StackCount(it) - 1);
}
/* Relink the chain. */
if (count == 0) {
stack_set(it, tp);
} else {
prev->next = tp;
}
}
FUNCTION(fun_lstack)
{
Delim osep;
dbref it;
OBJSTACK *sp;
char *bp, *bb_p;
int over = 0;
VaChk_Out(0, 2);
if (!fargs[0]) {
it = player;
} else {
stack_object(player, it);
}
bp = buff;
bb_p = *bufc;
for (sp = stack_get(it); (sp != NULL) && !over; sp = sp->next) {
if (*bufc != bb_p) {
print_sep(&osep, buff, bufc);
}
over = safe_str_fn(sp->data, buff, bufc);
}
}
/* --------------------------------------------------------------------------
* regedit: Edit a string for sed/perl-like s//
* regedit(<string>,<regexp>,<replacement>)
* Derived from the PennMUSH code.
*/
FUNCTION(perform_regedit)
{
pcre *re;
pcre_extra *study = NULL;
const char *errptr;
int case_option, all_option;
int erroffset, subpatterns, len;
int offsets[PCRE_MAX_OFFSETS];
char *r, *start;
char tbuf[LBUF_SIZE];
char tmp;
int match_offset = 0;
case_option = Func_Mask(REG_CASELESS);
all_option = Func_Mask(REG_MATCH_ALL);
if ((re = pcre_compile(fargs[1], case_option,
&errptr, &erroffset, mudstate.retabs)) == NULL) {
/* Matching error. Note that this returns a null string rather
* than '#-1 REGEXP ERROR: <error>', as PennMUSH does, in order
* to remain consistent with our other regexp functions.
*/
notify_quiet(player, errptr);
return;
}
/* Study the pattern for optimization, if we're going to try multiple
* matches.
*/
if (all_option) {
study = pcre_study(re, 0, &errptr);
if (errptr != NULL) {
XFREE(re, "perform_regedit.re");
notify_quiet(player, errptr);
return;
}
}
len = strlen(fargs[0]);
start = fargs[0];
subpatterns = pcre_exec(re, study, fargs[0], len, 0, 0, offsets,
PCRE_MAX_OFFSETS);
/* If there's no match, just return the original. */
if (subpatterns < 0) {
XFREE(re, "perform_regedit.re");
if (study) {
XFREE(study, "perform_regedit.study");
}
safe_str(fargs[0], buff, bufc);
return;
}
do {
/* If we had too many subpatterns for the offsets vector, set the
* number to 1/3rd of the size of the offsets vector.
*/
if (subpatterns == 0)
subpatterns = PCRE_MAX_OFFSETS / 3;
/* Copy up to the start of the matched area. */
tmp = fargs[0][offsets[0]];
fargs[0][offsets[0]] = '\0';
safe_str(start, buff, bufc);
fargs[0][offsets[0]] = tmp;
/* Copy in the replacement, putting in captured sub-expressions. */
for (r = fargs[2]; *r; r++) {
int offset, have_brace = 0;
char *endsub;
if (*r != '$') {
safe_chr(*r, buff, bufc);
continue;
}
r++;
if (*r == '{') {
have_brace = 1;
r++;
}
offset = strtoul(r, &endsub, 10);
if (r == endsub || (have_brace && *endsub != '}')) {
/* Not a valid number. */
safe_chr('$', buff, bufc);
if (have_brace)
safe_chr('{', buff, bufc);
r--;
continue;
}
r = endsub - 1;
if (have_brace)
r++;
if (pcre_copy_substring(fargs[0], offsets, subpatterns, offset,
tbuf, LBUF_SIZE) >= 0) {
safe_str(tbuf, buff, bufc);
}
}
start = fargs[0] + offsets[1];
match_offset = offsets[1];
} while (all_option &&
(((offsets[0] == offsets[1]) &&
/* PCRE docs note:
* Perl special-cases the empty-string match in split and /g.
* To emulate, first try the match again at the same position
* with PCRE_NOTEMPTY, then advance the starting offset if that
* fails.
*/
(((subpatterns = pcre_exec(re, study, fargs[0], len,
match_offset, PCRE_NOTEMPTY, offsets,
PCRE_MAX_OFFSETS)) >= 0) ||
((match_offset++ < len) &&
(subpatterns = pcre_exec(re, study, fargs[0], len,
match_offset, 0, offsets,
PCRE_MAX_OFFSETS)) >= 0))) ||
((match_offset <= len) &&
(subpatterns = pcre_exec(re, study, fargs[0], len,
match_offset, 0, offsets,
PCRE_MAX_OFFSETS)) >= 0)));
/* Copy everything after the matched bit. */
safe_str(start, buff, bufc);
XFREE(re, "perform_regedit.re");
if (study) {
XFREE(study, "perform_regedit.study");
}
}
/* --------------------------------------------------------------------------
* wildparse: Set the results of a wildcard match into named variables.
* wildparse(<string>,<pattern>,<list of variable names>)
*/
FUNCTION(fun_wildparse)
{
int i, nqregs;
char *t_args[NUM_ENV_VARS], **qregs;
if (!wild(fargs[1], fargs[0], t_args, NUM_ENV_VARS))
return;
nqregs = list2arr(&qregs, NUM_ENV_VARS, fargs[2], &SPACE_DELIM);
for (i = 0; i < nqregs; i++) {
if (qregs[i] && *qregs[i])
set_xvar(player, qregs[i], t_args[i]);
}
/* Need to free up allocated memory from the match. */
for (i = 0; i < NUM_ENV_VARS; i++) {
if (t_args[i])
free_lbuf(t_args[i]);
}
XFREE(qregs, "wildparse.qregs");
}
/* ---------------------------------------------------------------------------
* perform_regparse: Slurp a string into up to ten named variables ($0 - $9).
* REGPARSE, REGPARSEI. Unlike regmatch(), this returns no value.
* regparse(string, pattern, named vars)
*/
FUNCTION(perform_regparse)
{
int i, nqregs;
int case_option;
char **qregs;
char matchbuf[LBUF_SIZE];
pcre *re;
const char *errptr;
int erroffset;
int offsets[PCRE_MAX_OFFSETS];
int subpatterns;
case_option = Func_Mask(REG_CASELESS);
if ((re = pcre_compile(fargs[1], case_option,
&errptr, &erroffset, mudstate.retabs)) == NULL) {
/* Matching error. */
notify_quiet(player, errptr);
return;
}
subpatterns = pcre_exec(re, NULL, fargs[0], strlen(fargs[0]),
0, 0, offsets, PCRE_MAX_OFFSETS);
/* If we had too many subpatterns for the offsets vector, set the
* number to 1/3rd of the size of the offsets vector.
*/
if (subpatterns == 0)
subpatterns = PCRE_MAX_OFFSETS / 3;
nqregs = list2arr(&qregs, NUM_ENV_VARS, fargs[2], &SPACE_DELIM);
for (i = 0; i < nqregs; i++) {
if (qregs[i] && *qregs[i]) {
if (pcre_copy_substring(fargs[0], offsets, subpatterns, i,
matchbuf, LBUF_SIZE) < 0) {
set_xvar(player, qregs[i], NULL);
} else {
set_xvar(player, qregs[i], matchbuf);
}
}
}
XFREE(re, "perform_regparse.re");
XFREE(qregs, "perform_regparse.qregs");
}
/* ---------------------------------------------------------------------------
* perform_regrab: Like grab() and graball(), but with a regexp pattern.
* REGRAB, REGRABI. Derived from PennMUSH.
*/
FUNCTION(perform_regrab)
{
Delim isep, osep;
int case_option, all_option;
char *r, *s, *bb_p;
pcre *re;
pcre_extra *study;
const char *errptr;
int erroffset;
int offsets[PCRE_MAX_OFFSETS];
case_option = Func_Mask(REG_CASELESS);
all_option = Func_Mask(REG_MATCH_ALL);
if (all_option) {
VaChk_Only_In_Out(4);
} else {
VaChk_Only_In(3);
}
s = trim_space_sep(fargs[0], &isep);
bb_p = *bufc;
if ((re = pcre_compile(fargs[1], case_option, &errptr, &erroffset,
mudstate.retabs)) == NULL) {
/* Matching error.
* Note difference from PennMUSH behavior:
* Regular expression errors return 0, not #-1 with an error
* message.
*/
notify_quiet(player, errptr);
return;
}
study = pcre_study(re, 0, &errptr);
if (errptr != NULL) {
notify_quiet(player, errptr);
XFREE(re, "perform_regrab.re");
return;
}
do {
r = split_token(&s, &isep);
if (pcre_exec(re, study, r, strlen(r), 0, 0,
offsets, PCRE_MAX_OFFSETS) >= 0) {
if (*bufc != bb_p) { /* if true, all_option also true */
print_sep(&osep, buff, bufc);
}
safe_str(r, buff, bufc);
if (!all_option)
break;
}
} while (s);
XFREE(re, "perform_regrab.re");
if (study) {
XFREE(study, "perform_regrab.study");
}
}
/* ---------------------------------------------------------------------------
* perform_regmatch: Return 0 or 1 depending on whether or not a regular
* expression matches a string. If a third argument is specified, dump
* the results of a regexp pattern match into a set of arbitrary r()-registers.
* REGMATCH, REGMATCHI
*
* regmatch(string, pattern, list of registers)
* If the number of matches exceeds the registers, those bits are tossed
* out.
* If -1 is specified as a register number, the matching bit is tossed.
* Therefore, if the list is "-1 0 3 5", the regexp $0 is tossed, and
* the regexp $1, $2, and $3 become r(0), r(3), and r(5), respectively.
*
* PCRE modifications adapted from PennMUSH.
*
*/
FUNCTION(perform_regmatch)
{
int case_option;
int i, nqregs;
char **qregs;
pcre *re;
const char *errptr;
int erroffset;
int offsets[PCRE_MAX_OFFSETS];
int subpatterns;
char tbuf[LBUF_SIZE], *p;
case_option = Func_Mask(REG_CASELESS);
VaChk_Range(2, 3);
if ((re = pcre_compile(fargs[1], case_option,
&errptr, &erroffset, mudstate.retabs)) == NULL) {
/* Matching error.
* Note difference from PennMUSH behavior:
* Regular expression errors return 0, not #-1 with an error
* message.
*/
notify_quiet(player, errptr);
safe_chr('0', buff, bufc);
return;
}
subpatterns = pcre_exec(re, NULL, fargs[0], strlen(fargs[0]),
0, 0, offsets, PCRE_MAX_OFFSETS);
safe_bool(buff, bufc, (subpatterns >= 0));
/* If we had too many subpatterns for the offsets vector, set the
* number to 1/3rd of the size of the offsets vector.
*/
if (subpatterns == 0)
subpatterns = PCRE_MAX_OFFSETS / 3;
/* If we don't have a third argument, we're done. */
if (nfargs != 3) {
XFREE(re, "perform_regmatch.re");
return;
}
/* We need to parse the list of registers. Anything that we don't get
* is assumed to be -1. If we didn't match, or the match went wonky,
* then set the register to empty. Otherwise, fill the register
* with the subexpression.
*/
nqregs = list2arr(&qregs, NUM_ENV_VARS, fargs[2], &SPACE_DELIM);
for (i = 0; i < nqregs; i++) {
if (pcre_copy_substring(fargs[0], offsets, subpatterns, i,
tbuf, LBUF_SIZE) < 0) {
set_register("perform_regmatch", qregs[i], NULL);
} else {
set_register("perform_regmatch", qregs[i], tbuf);
}
}
XFREE(re, "perform_regmatch.re");
XFREE(qregs, "perform_regmatch.qregs");
}
/* ---------------------------------------------------------------------------
* fun_until: Much like while(), but operates on multiple lists ala mix().
* until(eval_fn,cond_fn,list1,list2,compare_str,delim,output delim)
* The delimiter terminators are MANDATORY.
* The termination condition is a REGEXP match (thus allowing this
* to be also used as 'eval until a termination condition is NOT met').
*/
FUNCTION(fun_until)
{
Delim isep, osep;
dbref aowner1, thing1, aowner2, thing2;
int aflags1, aflags2, anum1, anum2, alen1, alen2;
ATTR *ap, *ap2;
char *atext1, *atext2, *atextbuf, *condbuf;
char *cp[NUM_ENV_VARS], *os[NUM_ENV_VARS];
int count[LBUF_SIZE / 2];
int i, is_exact_same, is_same, nwords, lastn, wc;
char *str, *dp, *savep, *bb_p;
char tmpbuf[2];
pcre *re;
const char *errptr;
int erroffset;
int offsets[PCRE_MAX_OFFSETS];
int subpatterns;
/* We need at least 6 arguments. The last 2 args must be delimiters. */
VaChk_Range(6, 12);
VaChk_InSep(nfargs - 1, 0);
VaChk_OutSep(nfargs, 0);
lastn = nfargs - 4;
/* Make sure we have a valid regular expression. */
if ((re = pcre_compile(fargs[lastn + 1], 0,
&errptr, &erroffset, mudstate.retabs)) == NULL) {
/* Return nothing on a bad match. */
notify_quiet(player, errptr);
return;
}
/* Our first and second args can be <obj>/<attr> or just <attr>.
* Use them if we can access them, otherwise return an empty string.
*
* Note that for user-defined attributes, atr_str() returns a pointer
* to a static, and that therefore we have to be careful about what
* we're doing.
*/
Parse_Uattr(player, fargs[0], thing1, anum1, ap);
Get_Uattr(player, thing1, ap, atext1, aowner1, aflags1, alen1);
Parse_Uattr(player, fargs[1], thing2, anum2, ap2);
if (!ap2) {
free_lbuf(atext1); /* we allocated this, remember? */
return;
}
/* If our evaluation and condition are the same, we can save ourselves
* some time later. There are two possibilities: we have the exact
* same obj/attr pair, or the attributes contain identical text.
*/
if ((thing1 == thing2) && (ap->number == ap2->number)) {
is_same = 1;
is_exact_same = 1;
} else {
is_exact_same = 0;
atext2 = atr_pget(thing2, ap2->number, &aowner2, &aflags2, &alen2);
if (!*atext2 || !See_attr(player, thing2, ap2, aowner2, aflags2)) {
free_lbuf(atext1);
free_lbuf(atext2);
return;
}
if (!strcmp(atext1, atext2))
is_same = 1;
else
is_same = 0;
}
atextbuf = alloc_lbuf("fun_while.eval");
if (!is_same)
condbuf = alloc_lbuf("fun_while.cond");
bb_p = *bufc;
/* Process the list one element at a time. We need to find out what
* the longest list is; assume null-padding for shorter lists.
*/
for (i = 0; i < NUM_ENV_VARS; i++)
cp[i] = NULL;
cp[2] = trim_space_sep(fargs[2], &isep);
nwords = count[2] = countwords(cp[2], &isep);
for (i = 3; i <= lastn; i++) {
cp[i] = trim_space_sep(fargs[i], &isep);
count[i] = countwords(cp[i], &isep);
if (count[i] > nwords)
nwords = count[i];
}
for (wc = 0;
(wc < nwords) && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
!Too_Much_CPU();
wc++) {
for (i = 2; i <= lastn; i++) {
if (count[i]) {
os[i - 2] = split_token(&cp[i], &isep);
} else {
tmpbuf[0] = '\0';
os[i - 2] = tmpbuf;
}
}
if (*bufc != bb_p) {
print_sep(&osep, buff, bufc);
}
StrCopyKnown(atextbuf, atext1, alen1);
str = atextbuf;
savep = *bufc;
exec(buff, bufc, player, caller, cause,
EV_STRIP | EV_FCHECK | EV_EVAL, &str, &(os[0]), lastn - 1);
if (!is_same) {
StrCopyKnown(atextbuf, atext2, alen2);
dp = savep = condbuf;
str = atextbuf;
exec(condbuf, &dp, player, caller, cause,
EV_STRIP | EV_FCHECK | EV_EVAL, &str, &(os[0]), lastn - 1);
}
subpatterns = pcre_exec(re, NULL, savep, strlen(savep),
0, 0, offsets, PCRE_MAX_OFFSETS);
if (subpatterns >= 0)
break;
}
XFREE(re, "until.re");
free_lbuf(atext1);
if (!is_exact_same)
free_lbuf(atext2);
free_lbuf(atextbuf);
if (!is_same)
free_lbuf(condbuf);
}
/* ---------------------------------------------------------------------------
* perform_grep: grep (exact match), wildgrep (wildcard match),
* regrep (regexp match), and case-insensitive versions. (There is no
* case-insensitive wildgrep, since all wildcard matches are caseless.)
*/
FUNCTION(perform_grep)
{
int grep_type, caseless;
pcre *re = NULL;
pcre_extra *study = NULL;
const char *errptr;
int erroffset;
int offsets[PCRE_MAX_OFFSETS];
char *patbuf, *patc, *attrib, *p, *bb_p;
int ca, aflags, alen;
dbref thing, aowner, it;
Delim osep;
VaChk_Only_Out(4);
grep_type = Func_Mask(REG_TYPE);
caseless = Func_Mask(REG_CASELESS);
it = match_thing(player, fargs[0]);
if (!Good_obj(it)) {
safe_nomatch(buff, bufc);
return;
} else if (!(Examinable(player, it))) {
safe_noperm(buff, bufc);
return;
}
/* Make sure there's an attribute and a pattern */
if (!fargs[1] || !*fargs[1]) {
safe_str("#-1 NO SUCH ATTRIBUTE", buff, bufc);
return;
}
if (!fargs[2] || !*fargs[2]) {
safe_str("#-1 INVALID GREP PATTERN", buff, bufc);
return;
}
switch (grep_type) {
case GREP_EXACT:
if (caseless) {
for (p = fargs[2]; *p; p++)
*p = tolower(*p);
}
break;
case GREP_REGEXP:
if ((re = pcre_compile(fargs[2], caseless, &errptr, &erroffset,
mudstate.retabs)) == NULL) {
notify_quiet(player, errptr);
return;
}
study = pcre_study(re, 0, &errptr);
if (errptr != NULL) {
XFREE(re, "perform_grep.re");
notify_quiet(player, errptr);
return;
}
break;
default:
/* No special set-up steps. */
break;
}
bb_p = *bufc;
patc = patbuf = alloc_lbuf("perform_grep.parse_attrib");
safe_tprintf_str(patbuf, &patc, "#%d/%s", it, fargs[1]);
olist_push();
if (parse_attrib_wild(player, patbuf, &thing, 0, 0, 1, 1)) {
for (ca = olist_first(); ca != NOTHING; ca = olist_next()) {
attrib = atr_get(thing, ca, &aowner, &aflags, &alen);
if ((grep_type == GREP_EXACT) && caseless) {
for (p = attrib; *p; p++)
*p = tolower(*p);
}
if (((grep_type == GREP_EXACT) && strstr(attrib, fargs[2])) ||
((grep_type == GREP_WILD) && quick_wild(fargs[2], attrib)) ||
((grep_type == GREP_REGEXP) &&
(pcre_exec(re, study, attrib, alen, 0, 0,
offsets, PCRE_MAX_OFFSETS) >= 0))) {
if (*bufc != bb_p) {
print_sep(&osep, buff, bufc);
}
safe_str((char *)(atr_num(ca))->name, buff, bufc);
}
free_lbuf(attrib);
}
}
free_lbuf(patbuf);
olist_pop();
if (re) {
XFREE(re, "perform_grep.re");
}
if (study) {
XFREE(study, "perform_grep.study");
}
}