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