/* funlist.c - list functions */ /* $Id: funlist.c,v 1.51 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 */ #include "ansi.h" /* required by code */ /* --------------------------------------------------------------------------- * List management utilities. */ #define ALPHANUM_LIST 1 #define NUMERIC_LIST 2 #define DBREF_LIST 3 #define FLOAT_LIST 4 #define NOCASE_LIST 5 static int autodetect_list(ptrs, nitems) char *ptrs[]; int nitems; { int sort_type, i; char *p; sort_type = NUMERIC_LIST; for (i = 0; i < nitems; i++) { switch (sort_type) { case NUMERIC_LIST: if (!is_number(ptrs[i])) { /* If non-numeric, switch to alphanum sort. * Exception: if this is the first element * and it is a good dbref, switch to a dbref * sort. We're a little looser than the * normal 'good dbref' rules, any number * following the #-sign is accepted. */ if (i == 0) { p = ptrs[i]; if (*p++ != NUMBER_TOKEN) { return ALPHANUM_LIST; } else if (is_integer(p)) { sort_type = DBREF_LIST; } else { return ALPHANUM_LIST; } } else { return ALPHANUM_LIST; } } else if (strchr(ptrs[i], '.')) { sort_type = FLOAT_LIST; } break; case FLOAT_LIST: if (!is_number(ptrs[i])) { sort_type = ALPHANUM_LIST; return ALPHANUM_LIST; } break; case DBREF_LIST: p = ptrs[i]; if (*p++ != NUMBER_TOKEN) return ALPHANUM_LIST; if (!is_integer(p)) return ALPHANUM_LIST; break; default: return ALPHANUM_LIST; } } return sort_type; } static int get_list_type(fargs, nfargs, type_pos, ptrs, nitems) char *fargs[], *ptrs[]; int nfargs, nitems, type_pos; { if (nfargs >= type_pos) { switch (tolower(*fargs[type_pos - 1])) { case 'd': return DBREF_LIST; case 'n': return NUMERIC_LIST; case 'f': return FLOAT_LIST; case 'i': return NOCASE_LIST; case '\0': return autodetect_list(ptrs, nitems); default: return ALPHANUM_LIST; } } return autodetect_list(ptrs, nitems); } static int dbnum(dbr) char *dbr; { if ((*dbr != '#') || (dbr[1] == '\0')) return 0; else return atoi(dbr + 1); } /* --------------------------------------------------------------------------- * fun_words: Returns number of words in a string. Aka vdim. * Added 1/28/91 Philip D. Wasson */ FUNCTION(fun_words) { Delim isep; if (nfargs == 0) { safe_chr('0', buff, bufc); return; } VaChk_Only_In(2); safe_ltos(buff, bufc, countwords(fargs[0], &isep)); } /* --------------------------------------------------------------------------- * fun_first: Returns first word in a string */ FUNCTION(fun_first) { char *s, *first; Delim isep; /* If we are passed an empty arglist return a null string */ if (nfargs == 0) { return; } VaChk_Only_In(2); s = trim_space_sep(fargs[0], &isep); /* leading spaces */ first = split_token(&s, &isep); if (first) { safe_str(first, buff, bufc); } } /* --------------------------------------------------------------------------- * fun_rest: Returns all but the first word in a string */ FUNCTION(fun_rest) { char *s, *rest; Delim isep; int ansi_state = ANST_NONE; /* If we are passed an empty arglist return a null string */ if (nfargs == 0) { return; } VaChk_Only_In(2); s = trim_space_sep(fargs[0], &isep); /* leading spaces */ rest = next_token_ansi(s, &isep, &ansi_state); if (rest) { safe_str(ansi_transition_esccode(ANST_NORMAL, ansi_state), buff, bufc); safe_str(rest, buff, bufc); } } /* --------------------------------------------------------------------------- * fun_last: Returns last word in a string */ FUNCTION(fun_last) { char *s, *last; Delim isep; int ansi_state = ANST_NONE; /* If we are passed an empty arglist return a null string */ if (nfargs == 0) { return; } VaChk_Only_In(2); if (isep.len == 1) { last = s = trim_space_sep(fargs[0], &isep); do { /* this is like next_token(), but tracking ansi */ while (*s == ESC_CHAR) { track_esccode(s, ansi_state); } while (*s && (*s != isep.str[0])) { ++s; while (*s == ESC_CHAR) { track_esccode(s, ansi_state); } } if (*s) { ++s; if (isep.str[0] == ' ') { while (*s == ' ') ++s; } last = s; } } while (*s); safe_str(ansi_transition_esccode(ANST_NORMAL, ansi_state), buff, bufc); safe_known_str(last, s - last, buff, bufc); } else { s = fargs[0]; /* Walk backwards through the string to find the separator. * Find the last character, and compare the previous characters, * to find the separator. If we can't find the last character * or we know we're going to fall off the string, return * the original string. */ if ((last = strrchr(s, isep.str[isep.len - 1])) == NULL) { safe_str(s, buff, bufc); return; } while (last >= s + isep.len - 1) { if ((*last == isep.str[isep.len - 1]) && !strncmp(isep.str, last - isep.len + 1, isep.len)) { safe_str(++last, buff, bufc); return; } last--; } safe_str(s, buff, bufc); } } /* --------------------------------------------------------------------------- * fun_match: Match arg2 against each word of arg1, returning index of * first match. */ FUNCTION(fun_match) { int wcount; char *r, *s; Delim isep; VaChk_Only_In(3); /* Check each word individually, returning the word number of the * first one that matches. If none match, return 0. */ wcount = 1; s = trim_space_sep(fargs[0], &isep); do { r = split_token(&s, &isep); if (quick_wild(fargs[1], r)) { safe_ltos(buff, bufc, wcount); return; } wcount++; } while (s); safe_chr('0', buff, bufc); } FUNCTION(fun_matchall) { int wcount; char *r, *s, *old; Delim isep, osep; VaChk_Only_In_Out(4); /* SPECIAL CASE: If there's no output delimiter specified, we use * a space, NOT the delimiter given for the list! */ if (nfargs < 4) { osep.str[0] = ' '; osep.len = 1; } old = *bufc; /* Check each word individually, returning the word number of all * that match. If none match, return a null string. */ wcount = 1; s = trim_space_sep(fargs[0], &isep); do { r = split_token(&s, &isep); if (quick_wild(fargs[1], r)) { if (old != *bufc) { print_sep(&osep, buff, bufc); } safe_ltos(buff, bufc, wcount); } wcount++; } while (s); } /* --------------------------------------------------------------------------- * fun_extract: extract words from string: * extract(foo bar baz,1,2) returns 'foo bar' * extract(foo bar baz,2,1) returns 'bar' * extract(foo bar baz,2,2) returns 'bar baz' * * Now takes optional separator extract(foo-bar-baz,1,2,-) returns 'foo-bar' */ FUNCTION(fun_extract) { int start, len; char *r, *s, *t; Delim isep; VaChk_Only_In(4); s = fargs[0]; start = atoi(fargs[1]); len = atoi(fargs[2]); if ((start < 1) || (len < 1)) { return; } /* Skip to the start of the string to save */ start--; s = trim_space_sep(s, &isep); while (start && s) { s = next_token(s, &isep); start--; } /* If we ran of the end of the string, return nothing */ if (!s || !*s) { return; } /* Count off the words in the string to save */ r = s; len--; while (len && s) { s = next_token(s, &isep); len--; } /* Chop off the rest of the string, if needed */ if (s && *s) t = split_token(&s, &isep); safe_str(r, buff, bufc); } /* --------------------------------------------------------------------------- * fun_index: like extract(), but it works with an arbitrary separator. * index(a b | c d e | f gh | ij k, |, 2, 1) => c d e * index(a b | c d e | f gh | ij k, |, 2, 2) => c d e | f g h */ FUNCTION(fun_index) { int start, end; char c, *s, *p; s = fargs[0]; c = *fargs[1]; start = atoi(fargs[2]); end = atoi(fargs[3]); if ((start < 1) || (end < 1) || (*s == '\0')) return; if (c == '\0') c = ' '; /* move s to point to the start of the item we want */ start--; while (start && s && *s) { if ((s = strchr(s, c)) != NULL) s++; start--; } /* skip over just spaces */ while (s && (*s == ' ')) s++; if (!s || !*s) return; /* figure out where to end the string */ p = s; while (end && p && *p) { if ((p = strchr(p, c)) != NULL) { if (--end == 0) { do { p--; } while ((*p == ' ') && (p > s)); *(++p) = '\0'; safe_str(s, buff, bufc); return; } else { p++; } } } /* if we've gotten this far, we've run off the end of the string */ safe_str(s, buff, bufc); } /* --------------------------------------------------------------------------- * ldelete: Remove a word from a string by place * ldelete(<list>,<position>[,<separator>]) * * insert: insert a word into a string by place * insert(<list>,<position>,<new item> [,<separator>]) * * replace: replace a word into a string by place * replace(<list>,<position>,<new item>[,<separator>]) */ #define IF_DELETE 0 #define IF_REPLACE 1 #define IF_INSERT 2 static void do_itemfuns(buff, bufc, str, el, word, sep, flag) char *buff, **bufc, *str, *word; const Delim *sep; int el, flag; { int ct, overrun; char *sptr, *iptr, *eptr; char nullb; /* If passed a null string return an empty string, except that we * are allowed to append to a null string. */ if ((!str || !*str) && ((flag != IF_INSERT) || (el != 1))) { return; } /* we can't fiddle with anything before the first position */ if (el < 1) { safe_str(str, buff, bufc); return; } /* Split the list up into 'before', 'target', and 'after' chunks * pointed to by sptr, iptr, and eptr respectively. */ nullb = '\0'; if (el == 1) { /* No 'before' portion, just split off element 1 */ sptr = NULL; if (!str || !*str) { eptr = NULL; iptr = NULL; } else { eptr = trim_space_sep(str, sep); iptr = split_token(&eptr, sep); } } else { /* Break off 'before' portion */ sptr = eptr = trim_space_sep(str, sep); overrun = 1; for (ct = el; ct > 2 && eptr; eptr = next_token(eptr, sep), ct--) ; if (eptr) { overrun = 0; iptr = split_token(&eptr, sep); } /* If we didn't make it to the target element, just return * the string. Insert is allowed to continue if we are * exactly at the end of the string, but replace * and delete are not. */ if (!(eptr || ((flag == IF_INSERT) && !overrun))) { safe_str(str, buff, bufc); return; } /* Split the 'target' word from the 'after' portion. */ if (eptr) iptr = split_token(&eptr, sep); else iptr = NULL; } switch (flag) { case IF_DELETE: /* deletion */ if (sptr) { safe_str(sptr, buff, bufc); if (eptr) { print_sep(sep, buff, bufc); } } if (eptr) { safe_str(eptr, buff, bufc); } break; case IF_REPLACE: /* replacing */ if (sptr) { safe_str(sptr, buff, bufc); print_sep(sep, buff, bufc); } safe_str(word, buff, bufc); if (eptr) { print_sep(sep, buff, bufc); safe_str(eptr, buff, bufc); } break; case IF_INSERT: /* insertion */ if (sptr) { safe_str(sptr, buff, bufc); print_sep(sep, buff, bufc); } safe_str(word, buff, bufc); if (iptr) { print_sep(sep, buff, bufc); safe_str(iptr, buff, bufc); } if (eptr) { print_sep(sep, buff, bufc); safe_str(eptr, buff, bufc); } break; } } FUNCTION(fun_ldelete) { /* delete a word at position X of a list */ Delim isep; VaChk_Only_In(3); do_itemfuns(buff, bufc, fargs[0], atoi(fargs[1]), NULL, &isep, IF_DELETE); } FUNCTION(fun_replace) { /* replace a word at position X of a list */ Delim isep; VaChk_Only_In(4); do_itemfuns(buff, bufc, fargs[0], atoi(fargs[1]), fargs[2], &isep, IF_REPLACE); } FUNCTION(fun_insert) { /* insert a word at position X of a list */ Delim isep; VaChk_Only_In(4); do_itemfuns(buff, bufc, fargs[0], atoi(fargs[1]), fargs[2], &isep, IF_INSERT); } /* --------------------------------------------------------------------------- * fun_remove: Remove a word from a string */ FUNCTION(fun_remove) { char *s, *sp, *word, *bb_p; Delim isep; int found; VaChk_Only_In(3); if (((isep.len == 1) && strchr(fargs[1], isep.str[0])) || ((isep.len > 1) && strstr(fargs[1], isep.str))) { safe_str("#-1 CAN ONLY DELETE ONE ELEMENT", buff, bufc); return; } s = fargs[0]; word = fargs[1]; /* Walk through the string copying words until (if ever) we get to * one that matches the target word. */ sp = s; found = 0; bb_p = *bufc; while (s) { sp = split_token(&s, &isep); if (found || strcmp(sp, word)) { if (*bufc != bb_p) { print_sep(&isep, buff, bufc); } safe_str(sp, buff, bufc); } else { found = 1; } } } /* --------------------------------------------------------------------------- * fun_member: Is a word in a string */ FUNCTION(fun_member) { int wcount; char *r, *s; Delim isep; VaChk_Only_In(3); wcount = 1; s = trim_space_sep(fargs[0], &isep); do { r = split_token(&s, &isep); if (!strcmp(fargs[1], r)) { safe_ltos(buff, bufc, wcount); return; } wcount++; } while (s); safe_chr('0', buff, bufc); } /* --------------------------------------------------------------------------- * fun_revwords: Reverse the order of words in a list. */ FUNCTION(fun_revwords) { char *bb_p, **elems; Delim isep; int n_elems, i; /* If we are passed an empty arglist return a null string */ if (nfargs == 0) { return; } VaChk_Only_In(2); /* Nasty bounds checking */ if ((int)strlen(fargs[0]) >= LBUF_SIZE - (*bufc - buff) - 1) { *(fargs[0] + (LBUF_SIZE - (*bufc - buff) - 1)) = '\0'; } /* Chop it up into an array of words and reverse them. */ n_elems = list2arr(&elems, LBUF_SIZE / 2, fargs[0], &isep); bb_p = *bufc; for (i = n_elems - 1; i >= 0; i--) { if (*bufc != bb_p) { print_sep(&isep, buff, bufc); } safe_str(elems[i], buff, bufc); } XFREE(elems, "fun_revwords.elems"); } /* --------------------------------------------------------------------------- * fun_splice: given two lists and a word, merge the two lists * by replacing words in list1 that are the same as the given * word by the corresponding word in list2 (by position). * The lists must have the same number of words. Compare to MERGE(). */ FUNCTION(fun_splice) { char *p1, *p2, *q1, *q2, *bb_p; Delim isep, osep; int words, i; VaChk_Only_In_Out(5); /* length checks */ if (countwords(fargs[2], &isep) > 1) { safe_str("#-1 TOO MANY WORDS", buff, bufc); return; } words = countwords(fargs[0], &isep); if (words != countwords(fargs[1], &isep)) { safe_str("#-1 NUMBER OF WORDS MUST BE EQUAL", buff, bufc); return; } /* loop through the two lists */ p1 = fargs[0]; q1 = fargs[1]; bb_p = *bufc; for (i = 0; i < words; i++) { p2 = split_token(&p1, &isep); q2 = split_token(&q1, &isep); if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } if (!strcmp(p2, fargs[2])) safe_str(q2, buff, bufc); /* replace */ else safe_str(p2, buff, bufc); /* copy */ } } /* --------------------------------------------------------------------------- * fun_sort: Sort lists. */ typedef struct f_record f_rec; typedef struct i_record i_rec; struct f_record { double data; char *str; }; struct i_record { long data; char *str; }; static int a_comp(s1, s2) const void *s1, *s2; { return strcmp(*(char **)s1, *(char **)s2); } static int c_comp(s1, s2) const void *s1, *s2; { return strcasecmp(*(char **)s1, *(char **)s2); } static int f_comp(s1, s2) const void *s1, *s2; { if (((f_rec *) s1)->data > ((f_rec *) s2)->data) return 1; if (((f_rec *) s1)->data < ((f_rec *) s2)->data) return -1; return 0; } static int i_comp(s1, s2) const void *s1, *s2; { if (((i_rec *) s1)->data > ((i_rec *) s2)->data) return 1; if (((i_rec *) s1)->data < ((i_rec *) s2)->data) return -1; return 0; } static void do_asort(s, n, sort_type) char *s[]; int n, sort_type; { int i; f_rec *fp = NULL; i_rec *ip = NULL; switch (sort_type) { case ALPHANUM_LIST: qsort((void *)s, n, sizeof(char *), (int (*)(const void *, const void *))a_comp); break; case NOCASE_LIST: qsort((void *)s, n, sizeof(char *), (int (*)(const void *, const void *))c_comp); break; case NUMERIC_LIST: ip = (i_rec *) XCALLOC(n, sizeof(i_rec), "do_asort"); for (i = 0; i < n; i++) { ip[i].str = s[i]; ip[i].data = atoi(s[i]); } qsort((void *)ip, n, sizeof(i_rec), (int (*)(const void *, const void *))i_comp); for (i = 0; i < n; i++) { s[i] = ip[i].str; } XFREE(ip, "do_asort"); break; case DBREF_LIST: ip = (i_rec *) XCALLOC(n, sizeof(i_rec), "do_asort.2"); for (i = 0; i < n; i++) { ip[i].str = s[i]; ip[i].data = dbnum(s[i]); } qsort((void *)ip, n, sizeof(i_rec), (int (*)(const void *, const void *))i_comp); for (i = 0; i < n; i++) { s[i] = ip[i].str; } XFREE(ip, "do_asort.2"); break; case FLOAT_LIST: fp = (f_rec *) XCALLOC(n, sizeof(f_rec), "do_asort.3"); for (i = 0; i < n; i++) { fp[i].str = s[i]; fp[i].data = aton(s[i]); } qsort((void *)fp, n, sizeof(f_rec), (int (*)(const void *, const void *))f_comp); for (i = 0; i < n; i++) { s[i] = fp[i].str; } XFREE(fp, "do_asort.3"); break; } } FUNCTION(fun_sort) { int nitems, sort_type; char *list, **ptrs; Delim isep, osep; /* If we are passed an empty arglist return a null string */ if (nfargs == 0) { return; } VaChk_In_Out(1, 4); /* Convert the list to an array */ list = alloc_lbuf("fun_sort"); strcpy(list, fargs[0]); nitems = list2arr(&ptrs, LBUF_SIZE / 2, list, &isep); sort_type = get_list_type(fargs, nfargs, 2, ptrs, nitems); do_asort(ptrs, nitems, sort_type); arr2list(ptrs, nitems, buff, bufc, &osep); free_lbuf(list); XFREE(ptrs, "fun_sort.ptrs"); } /* --------------------------------------------------------------------------- * sortby: Sort using a user-defined function. */ static char ucomp_buff[LBUF_SIZE]; static dbref ucomp_cause, ucomp_player, ucomp_caller; static int u_comp(s1, s2) const void *s1, *s2; { /* Note that this function is for use in conjunction with our own * sane_qsort routine, NOT with the standard library qsort! */ char *result, *tbuf, *elems[2], *bp, *str; int n; if ((mudstate.func_invk_ctr > mudconf.func_invk_lim) || (mudstate.func_nest_lev > mudconf.func_nest_lim) || Too_Much_CPU()) return 0; tbuf = alloc_lbuf("u_comp"); elems[0] = (char *)s1; elems[1] = (char *)s2; strcpy(tbuf, ucomp_buff); result = bp = alloc_lbuf("u_comp"); str = tbuf; exec(result, &bp, ucomp_player, ucomp_caller, ucomp_cause, EV_STRIP | EV_FCHECK | EV_EVAL, &str, &(elems[0]), 2); if (!result) n = 0; else { n = atoi(result); free_lbuf(result); } free_lbuf(tbuf); return n; } static void sane_qsort(array, left, right, compare) void *array[]; int left, right; int (*compare) (); { /* Andrew Molitor's qsort, which doesn't require transitivity between * comparisons (essential for preventing crashes due to * boneheads who write comparison functions where a > b doesn't * mean b < a). */ int i, last; void *tmp; loop: if (left >= right) return; /* Pick something at random at swap it into the leftmost slot */ /* This is the pivot, we'll put it back in the right spot later */ i = Randomize(1 + (right - left)); tmp = array[left + i]; array[left + i] = array[left]; array[left] = tmp; last = left; for (i = left + 1; i <= right; i++) { /* Walk the array, looking for stuff that's less than our */ /* pivot. If it is, swap it with the next thing along */ if ((*compare) (array[i], array[left]) < 0) { last++; if (last == i) continue; tmp = array[last]; array[last] = array[i]; array[i] = tmp; } } /* Now we put the pivot back, it's now in the right spot, we never */ /* need to look at it again, trust me. */ tmp = array[last]; array[last] = array[left]; array[left] = tmp; /* At this point everything underneath the 'last' index is < the */ /* entry at 'last' and everything above it is not < it. */ if ((last - left) < (right - last)) { sane_qsort(array, left, last - 1, compare); left = last + 1; goto loop; } else { sane_qsort(array, last + 1, right, compare); right = last - 1; goto loop; } } FUNCTION(fun_sortby) { char *atext, *list, **ptrs; Delim isep, osep; int nptrs, aflags, alen, anum; dbref thing, aowner; ATTR *ap; if ((nfargs == 0) || !fargs[0] || !*fargs[0]) { return; } VaChk_Only_In_Out(4); Parse_Uattr(player, fargs[0], thing, anum, ap); Get_Uattr(player, thing, ap, atext, aowner, aflags, alen); strcpy(ucomp_buff, atext); ucomp_player = thing; ucomp_caller = player; ucomp_cause = cause; list = alloc_lbuf("fun_sortby"); strcpy(list, fargs[1]); nptrs = list2arr(&ptrs, LBUF_SIZE / 2, list, &isep); if (nptrs > 1) /* pointless to sort less than 2 elements */ sane_qsort((void **)ptrs, 0, nptrs - 1, u_comp); arr2list(ptrs, nptrs, buff, bufc, &osep); free_lbuf(list); free_lbuf(atext); XFREE(ptrs, "fun_sortby.ptrs"); } /* --------------------------------------------------------------------------- * handle_sets: Set management: SETUNION, SETDIFF, SETINTER. * Also LUNION, LDIFF, LINTER: Same thing, but takes a sort type like * sort() does. There's an unavoidable PennMUSH conflict, as * setunion() and friends have a 4th-arg output delimiter in TM3, but * the 4th arg is used for the sort type in PennMUSH. Also, adding the * sort type as a fifth arg for setunion(), etc. would be confusing, * since the last two args are, by convention, delimiters. So we add * new funcs. */ #define NUMCMP(f1,f2) \ ((f1 > f2) ? 1 : ((f1 < f2) ? -1 : 0)) #define GENCMP(x1,x2,s) \ ((s == ALPHANUM_LIST) ? strcmp(ptrs1[x1],ptrs2[x2]) : \ ((s == NOCASE_LIST) ? strcasecmp(ptrs1[x1],ptrs2[x2]) : \ ((s == FLOAT_LIST) ? NUMCMP(fp1[x1],fp2[x2]) : NUMCMP(ip1[x1],ip2[x2])))) FUNCTION(handle_sets) { Delim isep, osep; int oper, type_arg; char *list1, *list2, *oldp, *bb_p; char **ptrs1, **ptrs2; int i1, i2, n1, n2, val, sort_type; int *ip1, *ip2; double *fp1, *fp2; oper = Func_Mask(SET_OPER); type_arg = Func_Mask(SET_TYPE); if (type_arg) { VaChk_In_Out(2, 5); } else { VaChk_Only_In_Out(4); } list1 = alloc_lbuf("fun_setunion.1"); strcpy(list1, fargs[0]); n1 = list2arr(&ptrs1, LBUF_SIZE, list1, &isep); if (type_arg) sort_type = get_list_type(fargs, nfargs, 3, ptrs1, n1); else sort_type = ALPHANUM_LIST; do_asort(ptrs1, n1, sort_type); list2 = alloc_lbuf("fun_setunion.2"); strcpy(list2, fargs[1]); n2 = list2arr(&ptrs2, LBUF_SIZE, list2, &isep); do_asort(ptrs2, n2, sort_type); /* This conversion is inefficient, since it's already happened * once in do_asort(). */ ip1 = ip2 = NULL; fp1 = fp2 = NULL; if (sort_type == NUMERIC_LIST) { ip1 = (int *) XCALLOC(n1, sizeof(int), "handle_sets.n1"); ip2 = (int *) XCALLOC(n2, sizeof(int), "handle_sets.n2"); for (val = 0; val < n1; val++) ip1[val] = atoi(ptrs1[val]); for (val = 0; val < n2; val++) ip2[val] = atoi(ptrs2[val]); } else if (sort_type == DBREF_LIST) { ip1 = (int *) XCALLOC(n1, sizeof(int), "handle_sets.n1"); ip2 = (int *) XCALLOC(n2, sizeof(int), "handle_sets.n2"); for (val = 0; val < n1; val++) ip1[val] = dbnum(ptrs1[val]); for (val = 0; val < n2; val++) ip2[val] = dbnum(ptrs2[val]); } else if (sort_type == FLOAT_LIST) { fp1 = (double *) XCALLOC(n1, sizeof(double), "handle_sets.n1"); fp2 = (double *) XCALLOC(n2, sizeof(double), "handle_sets.n2"); for (val = 0; val < n1; val++) fp1[val] = aton(ptrs1[val]); for (val = 0; val < n2; val++) fp2[val] = aton(ptrs2[val]); } i1 = i2 = 0; bb_p = oldp = *bufc; **bufc = '\0'; switch (oper) { case SET_UNION: /* Copy elements common to both lists */ /* Handle case of two identical single-element lists */ if ((n1 == 1) && (n2 == 1) && (!strcmp(ptrs1[0], ptrs2[0]))) { safe_str(ptrs1[0], buff, bufc); break; } /* Process until one list is empty */ while ((i1 < n1) && (i2 < n2)) { /* Skip over duplicates */ if ((i1 > 0) || (i2 > 0)) { while ((i1 < n1) && !strcmp(ptrs1[i1], oldp)) i1++; while ((i2 < n2) && !strcmp(ptrs2[i2], oldp)) i2++; } /* Compare and copy */ if ((i1 < n1) && (i2 < n2)) { if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } oldp = *bufc; if (GENCMP(i1, i2, sort_type) < 0) { safe_str(ptrs1[i1], buff, bufc); i1++; } else { safe_str(ptrs2[i2], buff, bufc); i2++; } **bufc = '\0'; } } /* Copy rest of remaining list, stripping duplicates */ for (; i1 < n1; i1++) { if (strcmp(oldp, ptrs1[i1])) { if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } oldp = *bufc; safe_str(ptrs1[i1], buff, bufc); **bufc = '\0'; } } for (; i2 < n2; i2++) { if (strcmp(oldp, ptrs2[i2])) { if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } oldp = *bufc; safe_str(ptrs2[i2], buff, bufc); **bufc = '\0'; } } break; case SET_INTERSECT: /* Copy elements not in both lists */ while ((i1 < n1) && (i2 < n2)) { val = GENCMP(i1, i2, sort_type); if (!val) { /* Got a match, copy it */ if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } oldp = *bufc; safe_str(ptrs1[i1], buff, bufc); i1++; i2++; while ((i1 < n1) && !strcmp(ptrs1[i1], oldp)) i1++; while ((i2 < n2) && !strcmp(ptrs2[i2], oldp)) i2++; } else if (val < 0) { i1++; } else { i2++; } } break; case SET_DIFF: /* Copy elements unique to list1 */ while ((i1 < n1) && (i2 < n2)) { val = GENCMP(i1, i2, sort_type); if (!val) { /* Got a match, increment pointers */ oldp = ptrs1[i1]; while ((i1 < n1) && !strcmp(ptrs1[i1], oldp)) i1++; while ((i2 < n2) && !strcmp(ptrs2[i2], oldp)) i2++; } else if (val < 0) { /* Item in list1 not in list2, copy */ if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } safe_str(ptrs1[i1], buff, bufc); oldp = ptrs1[i1]; i1++; while ((i1 < n1) && !strcmp(ptrs1[i1], oldp)) i1++; } else { /* Item in list2 but not in list1, discard */ oldp = ptrs2[i2]; i2++; while ((i2 < n2) && !strcmp(ptrs2[i2], oldp)) i2++; } } /* Copy remainder of list1 */ while (i1 < n1) { if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } safe_str(ptrs1[i1], buff, bufc); oldp = ptrs1[i1]; i1++; while ((i1 < n1) && !strcmp(ptrs1[i1], oldp)) i1++; } } free_lbuf(list1); free_lbuf(list2); if ((sort_type == NUMERIC_LIST) || (sort_type == DBREF_LIST)) { XFREE(ip1, "handle_sets.n1"); XFREE(ip2, "handle_sets.n2"); } else if (sort_type == FLOAT_LIST) { XFREE(fp1, "handle_sets.n1"); XFREE(fp2, "handle_sets.n2"); } XFREE(ptrs1, "handle_sets.ptrs1"); XFREE(ptrs2, "handle_sets.ptrs2"); } /*--------------------------------------------------------------------------- * Format a list into columns. */ FUNCTION(fun_columns) { unsigned int spaces, number, ansinumber, striplen; unsigned int count, i, indent = 0; int isansi = 0, rturn = 1; char *p, *q, *buf, *curr, *objstring, *bp, *cp, *str, *cr = NULL; Delim isep; VaChk_Range(2, 4); VaChk_InSep(3, DELIM_EVAL); number = (unsigned int) safe_atoi(fargs[1]); indent = (unsigned int) safe_atoi(fargs[3]); if (indent > 77) { /* unsigned int, always a positive number */ indent = 1; } /* Must check number separately, since number + indent can * result in an integer overflow. */ if ((number < 1) || (number > 77) || ((unsigned int) (number + indent) > 78)) { safe_str("#-1 OUT OF RANGE", buff, bufc); return; } cp = curr = bp = alloc_lbuf("fun_columns"); str = fargs[0]; exec(curr, &bp, player, caller, cause, EV_STRIP | EV_FCHECK | EV_EVAL, &str, cargs, ncargs); cp = trim_space_sep(cp, &isep); if (!*cp) { free_lbuf(curr); return; } for (i = 0; i < indent; i++) safe_chr(' ', buff, bufc); buf = alloc_lbuf("fun_columns"); while (cp) { objstring = split_token(&cp, &isep); ansinumber = number; striplen = strip_ansi_len(objstring); if (ansinumber > striplen) ansinumber = striplen; p = objstring; q = buf; count = 0; while (p && *p) { if (count == number) { break; } if (*p == ESC_CHAR) { /* Start of ANSI code. Skip to end. */ isansi = 1; while (*p && !isalpha(*p)) *q++ = *p++; if (*p) *q++ = *p++; } else { *q++ = *p++; count++; } } if (isansi) safe_ansi_normal(buf, &q); *q = '\0'; isansi = 0; safe_str(buf, buff, bufc); if (striplen < number) { /* We only need spaces if we need to pad out. * Sanitize the number of spaces, too. */ spaces = number - striplen; if (spaces > LBUF_SIZE) { spaces = LBUF_SIZE; } for (i = 0; i < spaces; i++) safe_chr(' ', buff, bufc); } if (!(rturn % (int)((78 - indent) / number))) { safe_crlf(buff, bufc); cr = *bufc; for (i = 0; i < indent; i++) safe_chr(' ', buff, bufc); } else { cr = NULL; } rturn++; } if (cr) { *bufc = cr; **bufc = '\0'; } else { safe_crlf(buff, bufc); } free_lbuf(buf); free_lbuf(curr); } /*--------------------------------------------------------------------------- * fun_table: Turn a list into a table. * table(<list>,<field width>,<line length>,<list delim>,<field sep>,<pad>) * Only the <list> parameter is mandatory. * tables(<list>,<field widths>,<lead str>,<trail str>, * <list delim>,<field sep str>,<pad>) * Only the <list> and <field widths> parameters are mandatory. * * There are a couple of PennMUSH incompatibilities. The handling here is * more complex and probably more desirable behavior. The issues are: * - ANSI states are preserved even if a word is truncated. Thus, the * next word will start with the correct color. * - ANSI does not bleed into the padding or field separators. * - Having a '%r' embedded in the list will start a new set of columns. * This allows a series of %r-separated lists to be table-ified * correctly, and doesn't mess up the character count. */ static void tables_helper(list, last_state, n_cols, col_widths, lead_str, trail_str, list_sep, field_sep, pad_char, buff, bufc, just) char *list; int *last_state, n_cols, col_widths[]; char *lead_str, *trail_str; const Delim *list_sep, *field_sep, *pad_char; char *buff, **bufc; int just; { int i, nwords, nstates, cpos, wcount, over, ansi_state; int max, nleft, lead_chrs, lens[LBUF_SIZE / 2], states[LBUF_SIZE / 2 + 1]; char *s, **words; /* Split apart the list. We need to find the length of each de-ansified * word, as well as keep track of the state of each word. * Overly-long words eventually get truncated, but the correct ANSI * state is preserved nonetheless. */ nstates = list2ansi(states, last_state, LBUF_SIZE / 2, list, list_sep); nwords = list2arr(&words, LBUF_SIZE / 2, list, list_sep); if (nstates != nwords) { safe_tprintf_str(buff, bufc, "#-1 STATE/WORD COUNT OFF: %d/%d", nstates, nwords); XFREE(words, "tables_helper.words"); return; } for (i = 0; i < nwords; i++) lens[i] = strip_ansi_len(words[i]); over = wcount = 0; while ((wcount < nwords) && !over) { /* Beginning of new line. Insert newline if this isn't the first * thing we're writing. Write left margin, if appropriate. */ if (wcount != 0) safe_crlf(buff, bufc); if (lead_str) over = safe_str_fn(lead_str, buff, bufc); /* Do each column in the line. */ for (cpos = 0; (cpos < n_cols) && (wcount < nwords) && !over; cpos++, wcount++) { /* Write leading padding if we need it. */ if (just == JUST_RIGHT) { nleft = col_widths[cpos] - lens[wcount]; print_padding(nleft, max, pad_char->str[0]); } else if (just == JUST_CENTER) { lead_chrs = (int)((col_widths[cpos] / 2) - (lens[wcount] / 2) + .5); print_padding(lead_chrs, max, pad_char->str[0]); } /* If we had a previous state, we have to write it. */ safe_str(ansi_transition_esccode(ANST_NONE, states[wcount]), buff, bufc); /* Copy in the word. */ if (lens[wcount] <= col_widths[cpos]) { over = safe_str_fn(words[wcount], buff, bufc); safe_str(ansi_transition_esccode(states[wcount + 1], ANST_NONE), buff, bufc); } else { /* Bleah. We have a string that's too long. Truncate it. * Write an ANSI normal at the end at the end if we need * one (we'll restore the correct ANSI code with the * next word, if need be). */ ansi_state = states[wcount]; for (s = words[wcount], i = 0; *s && (i < col_widths[cpos]); ) { if (*s == ESC_CHAR) { track_esccode(s, ansi_state); } else { s++; i++; } } safe_known_str(words[wcount], s - words[wcount], buff, bufc); safe_str(ansi_transition_esccode(ansi_state, ANST_NONE), buff, bufc); } /* Writing trailing padding if we need it. */ if (just == JUST_LEFT) { nleft = col_widths[cpos] - lens[wcount]; print_padding(nleft, max, pad_char->str[0]); } else if (just == JUST_CENTER) { nleft = col_widths[cpos] - lead_chrs - lens[wcount]; print_padding(nleft, max, pad_char->str[0]); } /* Insert the field separator if this isn't the last column * AND this is not the very last word in the list. */ if ((cpos < n_cols - 1) && (wcount < nwords - 1)) { print_sep(field_sep, buff, bufc); } } if (!over && trail_str) { /* If we didn't get enough columns to fill out a line, and * this is the last line, then we have to pad it out. */ if ((wcount == nwords) && ((nleft = nwords % n_cols) > 0)) { for (cpos = nleft; (cpos < n_cols) && !over; cpos++) { print_sep(field_sep, buff, bufc); print_padding(col_widths[cpos], max, pad_char->str[0]); } } /* Write the right margin. */ over = safe_str_fn(trail_str, buff, bufc); } } /* Save the ANSI state of the last word. */ *last_state = states[nstates - 1]; /* Clean up. */ XFREE(words, "tables_helper.words"); } static void perform_tables(player, list, n_cols, col_widths, lead_str, trail_str, list_sep, field_sep, pad_char, buff, bufc, just) dbref player; char *list; int n_cols, col_widths[]; char *lead_str, *trail_str; const Delim *list_sep, *field_sep, *pad_char; char *buff, **bufc; int just; { char *p, *savep, *bb_p; int ansi_state = ANST_NONE; if (!list || !*list) return; bb_p = *bufc; savep = list; p = strchr(list, '\r'); while (p) { *p = '\0'; if (*bufc != bb_p) safe_crlf(buff, bufc); tables_helper(savep, &ansi_state, n_cols, col_widths, lead_str, trail_str, list_sep, field_sep, pad_char, buff, bufc, just); savep = p + 2; /* must skip '\n' too */ p = strchr(savep, '\r'); } if (*bufc != bb_p) safe_crlf(buff, bufc); tables_helper(savep, &ansi_state, n_cols, col_widths, lead_str, trail_str, list_sep, field_sep, pad_char, buff, bufc, just); } FUNCTION(process_tables) { int just; int i, num, n_columns, *col_widths; Delim list_sep, field_sep, pad_char; char **widths; just = Func_Mask(JUST_TYPE); VaChk_Range(2, 7); VaChk_Sep(&list_sep, 5, DELIM_STRING); VaChk_Sep(&field_sep, 6, DELIM_STRING|DELIM_NULL|DELIM_CRLF); VaChk_Sep(&pad_char, 7, 0); n_columns = list2arr(&widths, LBUF_SIZE / 2, fargs[1], &SPACE_DELIM); if (n_columns < 1) { XFREE(widths, "process_tables.widths"); return; } col_widths = (int *) XCALLOC(n_columns, sizeof(int), "process_tables.col_widths"); for (i = 0; i < n_columns; i++) { num = atoi(widths[i]); col_widths[i] = (num < 1) ? 1 : num; } perform_tables(player, fargs[0], n_columns, col_widths, ((nfargs > 2) && *fargs[2]) ? fargs[2] : NULL, ((nfargs > 3) && *fargs[3]) ? fargs[3] : NULL, &list_sep, &field_sep, &pad_char, buff, bufc, just); XFREE(col_widths, "process_tables.col_widths"); XFREE(widths, "process_tables.widths"); } FUNCTION(fun_table) { int line_length = 78; int field_width = 10; int i, field_sep_width, n_columns, *col_widths; Delim list_sep, field_sep, pad_char; VaChk_Range(1, 6); VaChk_Sep(&list_sep, 4, DELIM_STRING); VaChk_Sep(&field_sep, 5, DELIM_STRING|DELIM_NULL|DELIM_CRLF); VaChk_Sep(&pad_char, 6, 0); /* Get line length and column width. All columns are the same width. * Calculate what we need to. */ if (nfargs > 2) { line_length = atoi(fargs[2]); if (line_length < 2) line_length = 2; } if (nfargs > 1) { field_width = atoi(fargs[1]); if (field_width < 1) field_width = 1; else if (field_width > LBUF_SIZE - 1) field_width = LBUF_SIZE - 1; } if (field_width >= line_length) field_width = line_length - 1; if (field_sep.len == 1) { switch (field_sep.str[0]) { case '\r': case '\0': case '\n': case '\a': field_sep_width = 0; break; default: field_sep_width = 1; break; } } else { field_sep_width = strip_ansi_len(field_sep.str); } n_columns = (int)(line_length / (field_width + field_sep_width)); col_widths = (int *) XCALLOC(n_columns, sizeof(int), "fun_table.widths"); for (i = 0; i < n_columns; i++) col_widths[i] = field_width; perform_tables(player, fargs[0], n_columns, col_widths, NULL, NULL, &list_sep, &field_sep, &pad_char, buff, bufc, JUST_LEFT); XFREE(col_widths, "fun_table.widths"); } /* --------------------------------------------------------------------------- * fun_elements: given a list of numbers, get corresponding elements from * the list. elements(ack bar eep foof yay,2 4) ==> bar foof * The function takes a separator, but the separator only applies to the * first list. */ /* Borrowed from PennMUSH 1.50 */ FUNCTION(fun_elements) { int nwords, cur; char **ptrs; char *wordlist, *s, *r, *oldp; Delim isep, osep; VaChk_Only_In_Out(4); oldp = *bufc; /* Turn the first list into an array. */ wordlist = alloc_lbuf("fun_elements.wordlist"); strcpy(wordlist, fargs[0]); nwords = list2arr(&ptrs, LBUF_SIZE / 2, wordlist, &isep); s = Eat_Spaces(fargs[1]); /* Go through the second list, grabbing the numbers and finding the * corresponding elements. */ do { r = split_token(&s, &SPACE_DELIM); cur = atoi(r) - 1; if ((cur >= 0) && (cur < nwords) && ptrs[cur]) { if (oldp != *bufc) { print_sep(&osep, buff, bufc); } safe_str(ptrs[cur], buff, bufc); } } while (s); free_lbuf(wordlist); XFREE(ptrs, "fun_elements.ptrs"); } /* --------------------------------------------------------------------------- * fun_grab: a combination of extract() and match(), sortof. We grab the * single element that we match. * * grab(Test:1 Ack:2 Foof:3,*:2) => Ack:2 * grab(Test-1+Ack-2+Foof-3,*o*,+) => Ack:2 * * fun_graball: Ditto, but like matchall() rather than match(). We * grab all the elements that match, and we can take * an output delimiter. */ FUNCTION(fun_grab) { char *r, *s; Delim isep; VaChk_Only_In(3); /* Walk the wordstring, until we find the word we want. */ s = trim_space_sep(fargs[0], &isep); do { r = split_token(&s, &isep); if (quick_wild(fargs[1], r)) { safe_str(r, buff, bufc); return; } } while (s); } FUNCTION(fun_graball) { char *r, *s, *bb_p; Delim isep, osep; VaChk_Only_In_Out(4); s = trim_space_sep(fargs[0], &isep); bb_p = *bufc; do { r = split_token(&s, &isep); if (quick_wild(fargs[1], r)) { if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } safe_str(r, buff, bufc); } } while (s); } /* --------------------------------------------------------------------------- * fun_shuffle: randomize order of words in a list. */ /* Borrowed from PennMUSH 1.50 */ static void swap(p, q) char **p; char **q; { /* swaps two points to strings */ char *temp; temp = *p; *p = *q; *q = temp; } FUNCTION(fun_shuffle) { char **words; int n, i, j; Delim isep, osep; if (!nfargs || !fargs[0] || !*fargs[0]) { return; } VaChk_Only_In_Out(3); n = list2arr(&words, LBUF_SIZE, fargs[0], &isep); for (i = 0; i < n; i++) { j = random_range(i, n - 1); swap(&words[i], &words[j]); } arr2list(words, n, buff, bufc, &osep); XFREE(words, "fun_shuffle.words"); } /* --------------------------------------------------------------------------- * ledit(<list of words>,<old words>,<new words>[,<delim>[,<output delim>]]) * If a <word> in <list of words> is in <old words>, replace it with the * corresponding word from <new words>. This is basically a mass-edit. * This is an EXACT, not a case-insensitive or wildcarded, match. */ FUNCTION(fun_ledit) { Delim isep, osep; char *old_list, *new_list; int nptrs_old, nptrs_new; char **ptrs_old, **ptrs_new; char *r, *s, *bb_p; int i; int got_it; VaChk_Only_In_Out(5); old_list = alloc_lbuf("fun_ledit.old"); new_list = alloc_lbuf("fun_ledit.new"); strcpy(old_list, fargs[1]); strcpy(new_list, fargs[2]); nptrs_old = list2arr(&ptrs_old, LBUF_SIZE / 2, old_list, &isep); nptrs_new = list2arr(&ptrs_new, LBUF_SIZE / 2, new_list, &isep); /* Iterate through the words. */ bb_p = *bufc; s = trim_space_sep(fargs[0], &isep); do { if (*bufc != bb_p) { print_sep(&osep, buff, bufc); } r = split_token(&s, &isep); for (i = 0, got_it = 0; i < nptrs_old; i++) { if (!strcmp(r, ptrs_old[i])) { got_it = 1; if ((i < nptrs_new) && *ptrs_new[i]) { /* If we specify more old words than we have new words, * we assume we want to just nullify. */ safe_str(ptrs_new[i], buff, bufc); } break; } } if (!got_it) { safe_str(r, buff, bufc); } } while (s); free_lbuf(old_list); free_lbuf(new_list); XFREE(ptrs_old, "fun_ledit.ptrs_old"); XFREE(ptrs_new, "fun_ledit.ptrs_new"); } /* --------------------------------------------------------------------------- * fun_itemize: Turn a list into a punctuated list. */ FUNCTION(fun_itemize) { Delim isep, osep; int n_elems, i; char *conj_str, **elems; VaChk_Range(1, 4); if (!fargs[0] || !*fargs[0]) return; VaChk_InSep(2, 0); if (nfargs < 3) { conj_str = (char *) "and"; } else { conj_str = fargs[2]; } if (nfargs < 4) { osep.str[0] = ','; osep.len = 1; } else { VaChk_OutSep(4, 0); } n_elems = list2arr(&elems, LBUF_SIZE / 2, fargs[0], &isep); if (n_elems == 1) { safe_str(elems[0], buff, bufc); } else if (n_elems == 2) { safe_str(elems[0], buff, bufc); if (*conj_str) { safe_chr(' ', buff, bufc); safe_str(conj_str, buff, bufc); } safe_chr(' ', buff, bufc); safe_str(elems[1], buff, bufc); } else { for (i = 0; i < (n_elems - 1); i++) { safe_str(elems[i], buff, bufc); print_sep(&osep, buff, bufc); safe_chr(' ', buff, bufc); } if (*conj_str) { safe_str(conj_str, buff, bufc); safe_chr(' ', buff, bufc); } safe_str(elems[i], buff, bufc); } XFREE(elems, "fun_itemize.elems"); } /* --------------------------------------------------------------------------- * fun_choose: Weighted random choice from a list. * choose(<list of items>,<list of weights>,<input delim>) */ FUNCTION(fun_choose) { Delim isep; char **elems, **weights; int i, num, n_elems, n_weights, *ip; int sum = 0; VaChk_Only_In(3); n_elems = list2arr(&elems, LBUF_SIZE / 2, fargs[0], &isep); n_weights = list2arr(&weights, LBUF_SIZE / 2, fargs[1], &SPACE_DELIM); if (n_elems != n_weights) { safe_str("#-1 LISTS MUST BE OF EQUAL SIZE", buff, bufc); XFREE(elems, "fun_choose.elems"); XFREE(weights, "fun_choose.weights"); return; } /* Store the breakpoints, not the choose weights themselves. */ ip = (int *) XCALLOC(n_weights, sizeof(int), "fun_choose.ip"); for (i = 0; i < n_weights; i++) { num = atoi(weights[i]); if (num < 0) num = 0; if (num == 0) { ip[i] = 0; } else { sum += num; ip[i] = sum; } } num = (int) Randomize(sum); for (i = 0; i < n_weights; i++) { if ((ip[i] != 0) && (num < ip[i])) { safe_str(elems[i], buff, bufc); break; } } XFREE(ip, "fun_choose.ip"); XFREE(elems, "fun_choose.elems"); XFREE(weights, "fun_choose.weights"); } /* --------------------------------------------------------------------------- * fun_group: group(<list>, <number of groups>, <idelim>, <odelim>, <gdelim>) * Sort a list by numerical-size group, i.e., take every Nth * element. Useful for passing to a column-type function where you want * the list to go down rather than across, for instance. */ FUNCTION(fun_group) { char *bb_p, **elems; Delim isep, osep, gsep; int n_elems, n_groups, i, j; /* Separator checking is weird in this, since we can delimit by group, * too, as well as the element delimiter. The group delimiter defaults * to the output delimiter. */ VaChk_Range(2, 5); VaChk_InSep(3, 0); VaChk_DefaultOut(4) { VaChk_OutSep(4, 0); } if (nfargs < 5) { Delim_Copy(&gsep, &osep); } else { VaChk_Sep(&gsep, 5, DELIM_NULL|DELIM_CRLF|DELIM_STRING); } /* Go do it, unless the group size doesn't make sense. */ n_groups = atoi(fargs[1]); n_elems = list2arr(&elems, LBUF_SIZE / 2, fargs[0], &isep); if (n_groups < 2) { arr2list(elems, n_elems, buff, bufc, &osep); XFREE(elems, "fun_group.elems"); return; } if (n_groups >= n_elems) { arr2list(elems, n_elems, buff, bufc, &gsep); XFREE(elems, "fun_group.elems"); return; } bb_p = *bufc; for (i = 0; i < n_groups; i++) { for (j = 0; i + j < n_elems; j += n_groups) { if (*bufc != bb_p) { if (j == 0) { print_sep(&gsep, buff, bufc); } else { print_sep(&osep, buff, bufc); } } safe_str(elems[i + j], buff, bufc); } } XFREE(elems, "fun_group.elems"); }