/* 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"); } }