tinymush-3.1p1/game/backups/
tinymush-3.1p1/game/bin/
tinymush-3.1p1/game/data/
tinymush-3.1p1/game/modules/
tinymush-3.1p1/game/modules/old/
tinymush-3.1p1/src/modules/comsys/
tinymush-3.1p1/src/modules/hello/
tinymush-3.1p1/src/modules/mail/
tinymush-3.1p1/src/tools/
/* funiter.c - functions for user-defined iterations over lists */
/* $Id: funiter.c,v 1.32 2004/02/23 04:35:14 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 "attrs.h"	/* required by code */
#include "powers.h"	/* required by code */

/* ---------------------------------------------------------------------------
 * perform_loop: backwards-compatible looping constructs: LOOP, PARSE
 * See notes on perform_iter for the explanation.
 */

FUNCTION(perform_loop)
{
    Delim isep, osep;
    int flag;			/* 0 is parse(), 1 is loop() */
    char *curr, *objstring, *buff2, *buff3, *cp, *dp, *str, *result, *bb_p;
    char tbuf[8];
    int number = 0;

    flag = Func_Mask(LOOP_NOTIFY);
    
    if (flag) {
	VaChk_Only_In(3);
    } else {
	VaChk_InEval_OutEval(2, 4);
    }

    dp = cp = curr = alloc_lbuf("perform_loop.1");
    str = fargs[0];
    exec(curr, &dp, player, caller, cause, EV_STRIP | EV_FCHECK | EV_EVAL,
	 &str, cargs, ncargs);
    cp = trim_space_sep(cp, &isep);
    if (!*cp) {
	free_lbuf(curr);
	return;
    }

    bb_p = *bufc;

    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	   !Too_Much_CPU()) {
	if (!flag && (*bufc != bb_p)) {
	    print_sep(&osep, buff, bufc);
	}
	number++;
	objstring = split_token(&cp, &isep);
	buff2 = replace_string(BOUND_VAR, objstring, fargs[1]);
	ltos(tbuf, number);
	buff3 = replace_string(LISTPLACE_VAR, tbuf, buff2);
	str = buff3;
	if (!flag) {
	    exec(buff, bufc, player, caller, cause,
		 EV_STRIP | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
	} else {
	    dp = result = alloc_lbuf("perform_loop.2");
	    exec(result, &dp, player, caller, cause,
		 EV_STRIP | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
	    notify(cause, result);
	    free_lbuf(result);
	}
	free_lbuf(buff2);
	free_lbuf(buff3);
    }
    free_lbuf(curr);
}

/* ---------------------------------------------------------------------------
 * perform_iter: looping constructs
 *
 * iter() and list() parse an expression, substitute elements of
 * a list, one at a time, using the '##' replacement token. Uses of these
 * functions can be nested.
 * In older versions of MUSH, these functions could not be nested.
 * parse() and loop() exist for reasons of backwards compatibility,
 * since the peculiarities of the way substitutions were done in the string
 * replacements make it necessary to provide some way of doing backwards
 * compatibility, in order to avoid breaking a lot of code that relies upon
 * particular patterns of necessary escaping.
 *
 * whentrue() and whenfalse() work similarly to iter(). 
 * whentrue() loops as long as the expression evaluates to true.
 * whenfalse() loops as long as the expression evaluates to false.
 *
 * istrue() and isfalse() are inline filterbool() equivalents returning
 * the elements of the list which are true or false, respectively.
 */

FUNCTION(perform_iter)
{
    Delim isep, osep;
    int flag;			/* 0 is iter(), 1 is list() */
    int bool_flag, filt_flag;
    int need_result, need_bool;
    char *list_str, *lp, *str, *input_p, *bb_p, *work_buf;
    char *savep, *dp, *result;
    int is_true, cur_lev, elen;

    flag = Func_Mask(LOOP_NOTIFY);
    bool_flag = Func_Mask(BOOL_COND_TYPE);
    filt_flag = Func_Mask(FILT_COND_TYPE);

    need_result = (flag || (filt_flag != FILT_COND_NONE)) ? 1 : 0;
    need_bool = ((bool_flag != BOOL_COND_NONE) ||
		 (filt_flag != FILT_COND_NONE)) ? 1 : 0;
    
    if (flag) {
	VaChk_Only_In(3);
    } else {
	VaChk_InEval_OutEval(2, 4);
    }

    /* Enforce maximum nesting level. */

    if (mudstate.in_loop >= MAX_ITER_NESTING - 1) {
	notify_quiet(player, "Exceeded maximum iteration nesting.");
	return;
    }

    /* The list argument is unevaluated. Go evaluate it. */

    input_p = lp = list_str = alloc_lbuf("perform_iter.list");
    str = fargs[0];
    exec(list_str, &lp, player, caller, cause,
	 EV_STRIP | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
    input_p = trim_space_sep(input_p, &isep);
    if (!*input_p) {
	free_lbuf(list_str);
	return;
    }

    cur_lev = mudstate.in_loop++;
    mudstate.loop_token[cur_lev] = NULL;
    mudstate.loop_number[cur_lev] = 0;

    bb_p = *bufc;
    elen = strlen(fargs[1]);

    while (input_p && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	   !Too_Much_CPU()) {
	if (!need_result && (*bufc != bb_p)) {
	    print_sep(&osep, buff, bufc);
	}
	mudstate.loop_token[cur_lev] = split_token(&input_p, &isep);
	mudstate.loop_number[cur_lev] += 1;
	work_buf = alloc_lbuf("perform_iter.eval");
	StrCopyKnown(work_buf, fargs[1], elen); /* we might nibble this */
	str = work_buf;
	savep = *bufc;
	if (!need_result) {
	    exec(buff, bufc, player, caller, cause,
		 EV_STRIP | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
	    if (need_bool) {
		is_true = xlate(savep);
	    }
	} else {
	    dp = result = alloc_lbuf("perform_iter.out");
	    exec(result, &dp, player, caller, cause,
		 EV_STRIP | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
	    if (need_bool) {
		is_true = xlate(result);
	    }
	    if (flag) {
		notify(cause, result);
	    } else if (((filt_flag == FILT_COND_TRUE) && is_true) ||
		       ((filt_flag == FILT_COND_FALSE) && !is_true)) {
		if (*bufc != bb_p) {
		    print_sep(&osep, buff, bufc);
		}
		safe_str(mudstate.loop_token[cur_lev], buff, bufc);
	    }
	    free_lbuf(result);
	}
	free_lbuf(work_buf);
	if (bool_flag != BOOL_COND_NONE) {
	    if (!is_true && (bool_flag == BOOL_COND_TRUE))
		break;
	    if (is_true && (bool_flag == BOOL_COND_FALSE))
		break;
	} 
    }

    free_lbuf(list_str);
    mudstate.in_loop--;
}

/* ---------------------------------------------------------------------------
 * itext(), inum(), ilev(): Obtain nested iter tokens (##, #@, #!).
 */

FUNCTION(fun_ilev)
{
    safe_ltos(buff, bufc, mudstate.in_loop - 1);
}

FUNCTION(fun_itext)
{
    int lev;

    lev = atoi(fargs[0]);
    if ((lev > mudstate.in_loop - 1) || (lev < 0))
	return;

    safe_str(mudstate.loop_token[lev], buff, bufc);
}

FUNCTION(fun_inum)
{
    int lev;

    lev = atoi(fargs[0]);
    if ((lev > mudstate.in_loop - 1) || (lev < 0)) {
	safe_chr('0', buff, bufc);
	return;
    }
    
    safe_ltos(buff, bufc, mudstate.loop_number[lev]);
}

/* ---------------------------------------------------------------------------
 * fun_fold: iteratively eval an attrib with a list of arguments
 *        and an optional base case.  With no base case, the first list element
 *    is passed as %0 and the second is %1.  The attrib is then evaluated
 *    with these args, the result is then used as %0 and the next arg is
 *    %1 and so it goes as there are elements left in the list.  The
 *    optinal base case gives the user a nice starting point.
 *
 *    > &REP_NUM object=[%0][repeat(%1,%1)]
 *    > say fold(OBJECT/REP_NUM,1 2 3 4 5,->)
 *    You say "->122333444455555"
 *
 *      NOTE: To use added list separator, you must use base case!
 */

FUNCTION(fun_fold)
{
	dbref aowner, thing;
	int aflags, alen, anum, i;
	ATTR *ap;
	char *atext, *result, *curr, *bp, *str, *cp, *atextbuf;
	char *op, *clist[3], *rstore;
	Delim isep;

	/* We need two to four arguments only */

	VaChk_In(2, 4);

	/* Two possibilities for the first arg: <obj>/<attr> and <attr>. */

	Parse_Uattr(player, fargs[0], thing, anum, ap);
	Get_Uattr(player, thing, ap, atext, aowner, aflags, alen);

	/* Evaluate it using the rest of the passed function args */

	cp = curr = fargs[1];
	atextbuf = alloc_lbuf("fun_fold");
	StrCopyKnown(atextbuf, atext, alen);

	/* may as well handle first case now */
	
	i = 1;
	clist[2] = alloc_sbuf("fun_fold.objplace");
	op = clist[2];
	safe_ltos(clist[2], &op, i);

	if ((nfargs >= 3) && (fargs[2])) {
		clist[0] = fargs[2];
		clist[1] = split_token(&cp, &isep);
		result = bp = alloc_lbuf("fun_fold");
		str = atextbuf;
		exec(result, &bp, player, caller, cause,
		     EV_STRIP | EV_FCHECK | EV_EVAL, &str, clist, 3);
		i++;
	} else {
		clist[0] = split_token(&cp, &isep);
		clist[1] = split_token(&cp, &isep);
		result = bp = alloc_lbuf("fun_fold");
		str = atextbuf;
		exec(result, &bp, player, caller, cause,
		     EV_STRIP | EV_FCHECK | EV_EVAL, &str, clist, 3);
		i += 2;
	}

	rstore = result;
	result = NULL;

	while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	       !Too_Much_CPU()) {
		clist[0] = rstore;
		clist[1] = split_token(&cp, &isep);
		op = clist[2];
		safe_ltos(clist[2], &op, i);
		StrCopyKnown(atextbuf, atext, alen);
		result = bp = alloc_lbuf("fun_fold");
		str = atextbuf;
		exec(result, &bp, player, caller, cause,
		     EV_STRIP | EV_FCHECK | EV_EVAL, &str, clist, 3);
		strcpy(rstore, result);
		free_lbuf(result);
		i++;
	}
	safe_str(rstore, buff, bufc);
	free_lbuf(rstore);
	free_lbuf(atext);
	free_lbuf(atextbuf);
	free_sbuf(clist[2]);
}

/* ---------------------------------------------------------------------------
 * fun_filter: iteratively perform a function with a list of arguments
 *             and return the arg, if the function evaluates to TRUE using the 
 *      arg.
 *
 *      > &IS_ODD object=mod(%0,2)
 *      > say filter(object/is_odd,1 2 3 4 5)
 *      You say "1 3 5"
 *      > say filter(object/is_odd,1-2-3-4-5,-)
 *      You say "1-3-5"
 *
 *  NOTE:  If you specify a separator it is used to delimit returned list
 */

FUNCTION(handle_filter)
{
	Delim isep, osep;
	int flag;			/* 0 is filter(), 1 is filterbool() */
	dbref aowner, thing;
	int aflags, alen, anum, i;
	ATTR *ap;
	char *atext, *result, *curr, *objs[2], *bp, *str, *cp, *op, *atextbuf;
	char *bb_p;

	flag = Func_Mask(LOGIC_BOOL);

	VaChk_Only_In_Out(4);

	/* Two possibilities for the first arg: <obj>/<attr> and <attr>. */

	Parse_Uattr(player, fargs[0], thing, anum, ap);
	Get_Uattr(player, thing, ap, atext, aowner, aflags, alen);

	/* Now iteratively eval the attrib with the argument list */

	cp = curr = trim_space_sep(fargs[1], &isep);
	atextbuf = alloc_lbuf("fun_filter.atextbuf");
	objs[1] = alloc_sbuf("fun_filter.objplace");
	bb_p = *bufc;
	i = 1;
	while (cp) {
		objs[0] = split_token(&cp, &isep);
		op = objs[1];
		safe_ltos(objs[1], &op, i);
		StrCopyKnown(atextbuf, atext, alen);
		result = bp = alloc_lbuf("fun_filter");
		str = atextbuf;
		exec(result, &bp, player, caller, cause,
		     EV_STRIP | EV_FCHECK | EV_EVAL, &str, objs, 2);
		if ((!flag && (*result == '1')) || (flag && xlate(result))) {
		        if (*bufc != bb_p) {
			    print_sep(&osep, buff, bufc);
			}
			safe_str(objs[0], buff, bufc);
		}
		free_lbuf(result);
		i++;
	}
	free_lbuf(atext);
	free_lbuf(atextbuf);
	free_sbuf(objs[1]);
}

/* ---------------------------------------------------------------------------
 * fun_map: iteratively evaluate an attribute with a list of arguments.
 *  > &DIV_TWO object=fdiv(%0,2)
 *  > say map(1 2 3 4 5,object/div_two)
 *  You say "0.5 1 1.5 2 2.5"
 *  > say map(object/div_two,1-2-3-4-5,-)
 *  You say "0.5-1-1.5-2-2.5"
 *
 */

FUNCTION(fun_map)
{
	dbref aowner, thing;
	int aflags, alen, anum;
	ATTR *ap;
	char *atext, *objs[2], *str, *cp, *atextbuf, *bb_p, *op;
	Delim isep, osep;
	int i;

	VaChk_Only_In_Out(4);

	/* If we don't have anything for a second arg, don't bother. */
	if (!fargs[1] || !*fargs[1])
	        return;

	/* Two possibilities for the second arg: <obj>/<attr> and <attr>. */

	Parse_Uattr(player, fargs[0], thing, anum, ap);
	Get_Uattr(player, thing, ap, atext, aowner, aflags, alen);

	/* now process the list one element at a time */

	cp = trim_space_sep(fargs[1], &isep);
	atextbuf = alloc_lbuf("fun_map.atextbuf");
	objs[1] = alloc_sbuf("fun_map.objplace");
	bb_p = *bufc;
	i = 1;
	while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	       !Too_Much_CPU()) {
	        if (*bufc != bb_p) {
		    print_sep(&osep, buff, bufc);
		}
		objs[0] = split_token(&cp, &isep);
		op = objs[1];
		safe_ltos(objs[1], &op, i);
		StrCopyKnown(atextbuf, atext, alen);
		str = atextbuf;
		exec(buff, bufc, player, caller, cause,
		     EV_STRIP | EV_FCHECK | EV_EVAL, &str, objs, 2);
		i++;
	}
	free_lbuf(atext);
	free_lbuf(atextbuf);
	free_sbuf(objs[1]);
}

/* ---------------------------------------------------------------------------
 * fun_mix: Like map, but operates on two lists or more lists simultaneously,
 * passing the elements as %0, %1, %2, etc.
 */

FUNCTION(fun_mix)
{
    dbref aowner, thing;
    int aflags, alen, anum, i, lastn, nwords, wc;
    ATTR *ap;
    char *str, *atext, *os[NUM_ENV_VARS], *atextbuf, *bb_p;
    Delim isep;
    char *cp[NUM_ENV_VARS];
    int count[LBUF_SIZE / 2];
    char tmpbuf[2];

    /* Check to see if we have an appropriate number of arguments.
     * If there are more than three arguments, the last argument is
     * ALWAYS assumed to be a delimiter.
     */

    VaChk_Range(3, 12);
    if (nfargs < 4) {
	isep.str[0] = ' ';
	isep.len = 1;
	lastn = nfargs - 1;
    } else if (!delim_check( FUNCTION_ARGLIST, nfargs, &isep, DELIM_STRING)) {
	return;
    } else {
	lastn = nfargs - 2;
    }

    /* Get the attribute, check the permissions. */

    Parse_Uattr(player, fargs[0], thing, anum, ap);
    Get_Uattr(player, thing, ap, atext, aowner, aflags, alen);

    for (i = 0; i < NUM_ENV_VARS; i++)
	cp[i] = NULL;

    bb_p = *bufc;

    /* process the lists, one element at a time. */

    for (i = 1; i <= lastn; i++) {
	cp[i] = trim_space_sep(fargs[i], &isep);
    }
    nwords = count[1] = countwords(cp[1], &isep);
    for (i = 2; i<= lastn; i++) {
	count[i] = countwords(cp[i], &isep);
	if (count[i] > nwords)
	    nwords = count[i];
    }
    atextbuf = alloc_lbuf("fun_mix");

    for (wc = 0;
	 (wc < nwords) && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	     !Too_Much_CPU();
	 wc++) {
	for (i = 1; i <= lastn; i++) {
	    if (count[i]) {
		os[i - 1] = split_token(&cp[i], &isep);
	    } else {
		tmpbuf[0] = '\0';
		os[i - 1] = tmpbuf;
	    }
	}
	StrCopyKnown(atextbuf, atext, alen);

	if (*bufc != bb_p) {
	    print_sep(&isep, buff, bufc);
	}

	str = atextbuf;
	
	exec(buff, bufc, player, caller, cause,
	     EV_STRIP | EV_FCHECK | EV_EVAL, &str, &(os[0]), lastn);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
}

/* ---------------------------------------------------------------------------
 * fun_step: A little like a fusion of iter() and mix(), it takes elements
 * of a list X at a time and passes them into a single function as %0, %1,
 * etc.   step(<attribute>,<list>,<step size>,<delim>,<outdelim>)
 */

FUNCTION(fun_step)
{
    ATTR *ap;
    dbref aowner, thing;
    int aflags, alen, anum;
    char *atext, *str, *cp, *atextbuf, *bb_p, *os[NUM_ENV_VARS];
    Delim isep, osep;
    int step_size, i;

    VaChk_Only_In_Out(5);

    step_size = atoi(fargs[2]);
    if ((step_size < 1) || (step_size > NUM_ENV_VARS)) {
	notify(player, "Illegal step size.");
	return;
    }

    /* Get attribute. Check permissions. */

    Parse_Uattr(player, fargs[0], thing, anum, ap);
    Get_Uattr(player, thing, ap, atext, aowner, aflags, alen);

    cp = trim_space_sep(fargs[1], &isep);
    atextbuf = alloc_lbuf("fun_step");
    bb_p = *bufc;
    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	   !Too_Much_CPU()) {
	if (*bufc != bb_p) {
	    print_sep(&osep, buff, bufc);
	}
	for (i = 0; cp && (i < step_size); i++)
	    os[i] = split_token(&cp, &isep);
	StrCopyKnown(atextbuf, atext, alen);
	str = atextbuf;
	exec(buff, bufc, player, caller, cause,
	     EV_STRIP | EV_FCHECK | EV_EVAL, &str, &(os[0]), i);
    }
    free_lbuf(atext);
    free_lbuf(atextbuf);
}

/* ---------------------------------------------------------------------------
 * fun_foreach: like map(), but it operates on a string, rather than on a list,
 * calling a user-defined function for each character in the string.
 * No delimiter is inserted between the results.
 */

FUNCTION(fun_foreach)
{
    dbref aowner, thing;
    int aflags, alen, anum, i;
    ATTR *ap;
    char *str, *atext, *atextbuf, *cp, *cbuf[2], *op;
    char start_token, end_token;
    int in_string = 1;

    VaChk_Range(2, 4);

    Parse_Uattr(player, fargs[0], thing, anum, ap);
    Get_Uattr(player, thing, ap, atext, aowner, aflags, alen);

    atextbuf = alloc_lbuf("fun_foreach");
    cbuf[0] = alloc_lbuf("fun_foreach.cbuf");
    cp = Eat_Spaces(fargs[1]);

    start_token = '\0';
    end_token = '\0';

    if (nfargs > 2) {
	in_string = 0;
	start_token = *fargs[2];
    }
    if (nfargs > 3) {
	end_token = *fargs[3];
    }

    i = -1;			/* first letter in string is 0, not 1 */
    cbuf[1] = alloc_sbuf("fun_foreach.objplace");

    while (cp && *cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	   !Too_Much_CPU()) {

	if (!in_string) {
	    /* Look for a start token. */
	    while (*cp && (*cp != start_token)) {
		safe_chr(*cp, buff, bufc);
		cp++;
		i++;
	    }
	    if (!*cp)
		break;
	    /* Skip to the next character. Don't copy the start token. */
	    cp++;
	    i++;
	    if (!*cp)
		break;
	    in_string = 1;
	}
	if (*cp == end_token) {
	    /* We've found an end token. Skip over it. Note that it's
	     * possible to have a start and end token next to one
	     * another.
	     */
	    cp++;
	    i++;
	    in_string = 0;
	    continue;
	}

	i++;
	cbuf[0][0] = *cp++;
	cbuf[0][1] = '\0';
	op = cbuf[1];
	safe_ltos(cbuf[1], &op, i);
	StrCopyKnown(atextbuf, atext, alen);
	str = atextbuf; 
	exec(buff, bufc, player, caller, cause,
	     EV_STRIP | EV_FCHECK | EV_EVAL, &str, cbuf, 2);
    }

    free_lbuf(atextbuf);
    free_lbuf(atext);
    free_lbuf(cbuf[0]);
    free_sbuf(cbuf[1]);
}

/* ---------------------------------------------------------------------------
 * fun_munge: combines two lists in an arbitrary manner.
 */

FUNCTION(fun_munge)
{
	dbref aowner, thing;
	int aflags, alen, anum, nptrs1, nptrs2, nresults, i, j;
	ATTR *ap;
	char *list1, *list2, *rlist, *st[2];
	char **ptrs1, **ptrs2, **results;
	char *atext, *bp, *str, *oldp;
	Delim isep, osep;

	oldp = *bufc;
	if ((nfargs == 0) || !fargs[0] || !*fargs[0]) {
		return;
	}
	VaChk_Only_In_Out(5);

	/* Find our object and attribute */

	Parse_Uattr(player, fargs[0], thing, anum, ap);
	Get_Uattr(player, thing, ap, atext, aowner, aflags, alen);

	/* Copy our lists and chop them up. */

	list1 = alloc_lbuf("fun_munge.list1");
	list2 = alloc_lbuf("fun_munge.list2");
	strcpy(list1, fargs[1]);
	strcpy(list2, fargs[2]);
	nptrs1 = list2arr(&ptrs1, LBUF_SIZE / 2, list1, &isep);
	nptrs2 = list2arr(&ptrs2, LBUF_SIZE / 2, list2, &isep);

	if (nptrs1 != nptrs2) {
		safe_str("#-1 LISTS MUST BE OF EQUAL SIZE", buff, bufc);
		free_lbuf(atext);
		free_lbuf(list1);
		free_lbuf(list2);
		XFREE(ptrs1, "fun_munge.ptrs1");
		XFREE(ptrs2, "fun_munge.ptrs2");
		return;
	}

	/* Call the u-function with the first list as %0. Pass the
	 * input separator as %1, which makes sorting, etc. easier.
	 */

	st[0] = fargs[1];
	st[1] = alloc_lbuf("fun_munge.sep");
	bp = st[1];
	print_sep(&isep, st[1], &bp);

	bp = rlist = alloc_lbuf("fun_munge.rlist");
	str = atext;
	exec(rlist, &bp, player, caller, cause,
	     EV_STRIP | EV_FCHECK | EV_EVAL, &str, st, 2);

	/* Now that we have our result, put it back into array form. Search
	 * through list1 until we find the element position, then 
	 * copy the corresponding element from list2. 
	 */

	nresults = list2arr(&results, LBUF_SIZE / 2, rlist, &isep);

	for (i = 0; i < nresults; i++) {
		for (j = 0; j < nptrs1; j++) {
			if (!strcmp(results[i], ptrs1[j])) {
			        if (*bufc != oldp) {
				    print_sep(&osep, buff, bufc);
				}
				safe_str(ptrs2[j], buff, bufc);
				ptrs1[j][0] = '\0';
				break;
			}
		}
	}
	free_lbuf(atext);
	free_lbuf(list1);
	free_lbuf(list2);
	free_lbuf(rlist);
	free_lbuf(st[1]);
	XFREE(ptrs1, "fun_munge.ptrs1");
	XFREE(ptrs2, "fun_munge.ptrs2");
	XFREE(results, "fun_munge.results");
}

/* ---------------------------------------------------------------------------
 * fun_while: Evaluate a list until a termination condition is met:
 * while(EVAL_FN,CONDITION_FN,foo|flibble|baz|meep,1,|,-)
 * where EVAL_FN is "[strlen(%0)]" and CONDITION_FN is "[strmatch(%0,baz)]"
 * would result in '3-7-3' being returned.
 * The termination condition is an EXACT not wild match.
 */

FUNCTION(fun_while)
{
    Delim isep, osep;
    dbref aowner1, thing1, aowner2, thing2;
    int aflags1, aflags2, anum1, anum2, alen1, alen2, i, tmp_num;
    int is_same, is_exact_same;
    ATTR *ap, *ap2;
    char *atext1, *atext2, *atextbuf, *condbuf;
    char *objs[2], *cp, *str, *dp, *savep, *bb_p, *op;

    VaChk_Only_In_Out(6);

    /* If our third arg is null (empty list), don't bother. */

    if (!fargs[2] || !*fargs[2])
	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);
    tmp_num = ap->number;
    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) && (tmp_num == 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;
    }

    /* Process the list one element at a time. */

    cp = trim_space_sep(fargs[2], &isep);
    atextbuf = alloc_lbuf("fun_while.eval");
    if (!is_same)
	condbuf = alloc_lbuf("fun_while.cond");
    objs[1] = alloc_sbuf("fun_while.objplace");
    bb_p = *bufc;
    i = 1; 
    while (cp && (mudstate.func_invk_ctr < mudconf.func_invk_lim) &&
	   !Too_Much_CPU()) {
	if (*bufc != bb_p) {
	    print_sep(&osep, buff, bufc);
	}
	objs[0] = split_token(&cp, &isep);
	op = objs[1];
	safe_ltos(objs[1], &op, i);
	StrCopyKnown(atextbuf, atext1, alen1);
	str = atextbuf;
	savep = *bufc;
	exec(buff, bufc, player, caller, cause,
	     EV_STRIP | EV_FCHECK | EV_EVAL, &str, objs, 2);
	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, objs, 2);
	}
	if (!strcmp(savep, fargs[3]))
	    break;
	i++;
    }
    free_lbuf(atext1);
    if (!is_exact_same)
	free_lbuf(atext2);
    free_lbuf(atextbuf);
    if (!is_same)
	free_lbuf(condbuf);
    free_sbuf(objs[1]);
}