#include "copyrite.h" #include "config.h" #ifdef I_STRING #include <string.h> #else #include <strings.h> #endif #include <ctype.h> #include "externs.h" #include "intrface.h" #include "parse.h" #include "mymalloc.h" #include "regexp.h" #include "confmagic.h" #define ALPHANUM_LIST 0 #define NUMERIC_LIST 1 #define DBREF_LIST 2 #ifdef FLOATING_POINTS #define FLOAT_LIST 3 #else #define FLOAT_LIST 1 #endif #define MAX_SORTSIZE (BUFFER_LEN / 2) static char *next_token _((char *str, char sep)); static int list2arr _((char *r[], int max, char *list, char sep)); static void arr2list _((char *r[], int max, char *list, char **lp, char sep)); static void swap _((char **p, char **q)); static int autodetect_list _((char **ptrs, int nptrs)); static int get_list_type _((char **args, int nargs, int type_pos, char **ptrs, int nptrs)); static int a_comp _((const void *s1, const void *s2)); static int i_comp _((const void *s1, const void *s2)); static int f_comp _((const void *s1, const void *s2)); static int u_comp _((const void *s1, const void *s2)); void do_gensort _((char **s, int n, int sort_type)); static void sane_qsort _((void **array, int left, int right, int (*compare) (const void *, const void *))); static void do_itemfuns _((char *buff, char **bp, char *str, char *num, char *word, char *sep, int flag)); #ifdef CAN_NEWSTYLE static char * next_token(char *str, char sep) #else static char * next_token(str, sep) char *str; char sep; #endif { /* move pointer to start of the next token */ while (*str && (*str != sep)) str++; if (!*str) return NULL; str++; if (sep == ' ') { while (*str == sep) str++; } return str; } #ifdef CAN_NEWSTYLE static int list2arr(char *r[], int max, char *list, char sep) #else static int list2arr(r, max, list, sep) char *r[]; int max; char *list; char sep; #endif { /* chops up a list of words into an array of words. The list is * destructively modified. */ char *p; int i; list = trim_space_sep(list, sep); p = split_token(&list, sep); for (i = 0; p && (i < max); i++, p = split_token(&list, sep)) r[i] = p; return i; } #ifdef CAN_NEWSTYLE static void arr2list(char *r[], int max, char *list, char **lp, char sep) #else static void arr2list(r, max, list, lp, sep) char *r[]; int max; char *list; char **lp; char sep; #endif { int i; if (!max) return; safe_str(r[0], list, lp); for (i = 1; i < max; i++) { safe_chr(sep, list, lp); safe_str(r[i], list, lp); } **lp = '\0'; } static void swap(p, q) char **p; char **q; { /* swaps two pointers to strings */ char *temp; temp = *p; *p = *q; *q = temp; } /* ARGSUSED */ FUNCTION(fun_munge) { /* This is a function which takes three arguments. The first is * an obj-attr pair referencing a u-function to be called. The * other two arguments are lists. The first list is passed to the * u-function. The second list is then rearranged to match the * order of the first list as returned from the u-function. * This rearranged list is returned by MUNGE. * A fourth argument (separator) is optional. */ char list1[BUFFER_LEN], *lp, rlist[BUFFER_LEN], *rp; char *ptrs1[MAX_SORTSIZE], *ptrs2[MAX_SORTSIZE], *results[MAX_SORTSIZE]; int i, j, nptrs1, nptrs2, nresults; dbref thing; ATTR *attrib; char sep; int first; if (!delim_check(buff, bp, nargs, args, 4, &sep)) return; /* find our object and attribute */ parse_attrib(executor, args[0], &thing, &attrib); if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) return; if (SAFER_UFUN) if ((!Wizard(executor) && Wizard(thing)) || (!Hasprivs(executor) && Hasprivs(thing))) return; /* Copy the first list, since we need to pass it to two destructive * routines. */ strcpy(list1, args[1]); /* Break up the two lists into their respective elements. */ nptrs1 = list2arr(ptrs1, MAX_SORTSIZE, args[1], sep); nptrs2 = list2arr(ptrs2, MAX_SORTSIZE, args[2], sep); if (nptrs1 != nptrs2) { safe_str("#-1 LISTS MUST BE OF EQUAL SIZE", buff, bp); return; } /* Call the user function */ lp = list1; rp = rlist; do_userfn(rlist, &rp, thing, attrib, 1, &lp, executor, caller, enactor, pe_info); *rp = '\0'; /* 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. Mark used elements with * NULL to handle duplicates */ nresults = list2arr(results, MAX_SORTSIZE, rlist, sep); first = 1; for (i = 0; i < nresults; i++) { for (j = 0; j < nptrs1; j++) { if (ptrs2[j] && !strcmp(results[i], ptrs1[j])) { if (first) first = 0; else safe_chr(sep, buff, bp); safe_str(ptrs2[j], buff, bp); ptrs2[j] = NULL; break; } } } } /* ARGSUSED */ FUNCTION(fun_elements) { /* Given a list and a list of numbers, return the corresponding * elements of the list. elements(ack bar eep foof yay,2 4) = bar foof * A separator for the first list is allowed. * This code modified slightly from the Tiny 2.2.1 distribution */ int nwords, cur; char *ptrs[BUFFER_LEN / 2]; char wordlist[BUFFER_LEN]; char *s, *r, sep; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* Turn the first list into an array. */ strcpy(wordlist, args[0]); nwords = list2arr(ptrs, BUFFER_LEN / 2, wordlist, sep); s = trim_space_sep(args[1], ' '); /* Go through the second list, grabbing the numbers and finding the * corresponding elements. */ r = split_token(&s, ' '); cur = atoi(r) - 1; if ((cur >= 0) && (cur < nwords) && ptrs[cur]) { safe_str(ptrs[cur], buff, bp); } while (s) { r = split_token(&s, ' '); cur = atoi(r) - 1; if ((cur >= 0) && (cur < nwords) && ptrs[cur]) { safe_chr(sep, buff, bp); safe_str(ptrs[cur], buff, bp); } } } /* ARGSUSED */ FUNCTION(fun_matchall) { /* Check each word individually, returning the word number of all * that match. If none match, return an empty string. */ int wcount; char *r, *s, *b, sep; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; wcount = 1; s = trim_space_sep(args[0], sep); b = *bp; do { r = split_token(&s, sep); if (quick_wild(args[1], r)) { if (*bp != b) safe_chr(' ', buff, bp); safe_str(unparse_integer(wcount), buff, bp); } wcount++; } while (s); } /* ARGSUSED */ FUNCTION(fun_graball) { /* Check each word individually, returning all that match. * If none match, return an empty string. This is to grab() * what matchall() is to match(). */ char *r, *s, *b, sep; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; s = trim_space_sep(args[0], sep); b = *bp; do { r = split_token(&s, sep); if (quick_wild(args[1], r)) { if (*bp != b) safe_chr(sep, buff, bp); safe_str(r, buff, bp); } } while (s); } /* ARGSUSED */ FUNCTION(fun_fold) { /* iteratively evaluates an attribute with a list of arguments and * optional base case. With no base case, the first list element is * passed as %0, and the second as %1. The attribute is then evaluated * with these args. The result is then used as %0, and the next arg as * %1. Repeat until no elements are left in the list. The base case * can provide a starting point. */ dbref thing; ATTR *attrib; char const *abuf, *ap; char result[BUFFER_LEN], *rp; char rsave[BUFFER_LEN]; char *cp; char *tptr[2]; char sep; if (!delim_check(buff, bp, nargs, args, 4, &sep)) return; /* find our object and attribute */ parse_attrib(executor, args[0], &thing, &attrib); if (!GoodObject(thing) || !attrib) { safe_str("#-1 NOT FOUND", buff, bp); return; } if (!Can_Read_Attr(executor, thing, attrib)) { safe_str("#-1 NO PERMISSION TO GET ATTRIBUTE", buff, bp); return; } if (SAFER_UFUN) if ((!Wizard(executor) && Wizard(thing)) || (!Hasprivs(executor) && Hasprivs(thing))) { safe_str("#-1 NO PERMISSION TO RUN ATTRIBUTE", buff, bp); return; } abuf = safe_uncompress(attrib->value); /* save our stack */ tptr[0] = wenv[0]; tptr[1] = wenv[1]; cp = args[1]; /* If we have three or more arguments, the third one is the base case */ if (nargs >= 3) { wenv[0] = args[2]; wenv[1] = split_token(&cp, sep); } else { wenv[0] = split_token(&cp, sep); wenv[1] = split_token(&cp, sep); } rp = result; ap = abuf; process_expression(result, &rp, &ap, thing, executor, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); *rp = '\0'; strcpy(rsave, result); /* handle the rest of the cases */ while (cp && *cp) { wenv[0] = rsave; wenv[1] = split_token(&cp, sep); rp = result; ap = abuf; process_expression(result, &rp, &ap, thing, executor, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); *rp = '\0'; strcpy(rsave, result); } safe_str(rsave, buff, bp); /* restore the stack */ wenv[0] = tptr[0]; wenv[1] = tptr[1]; free((Malloc_t) abuf); } /* ARGSUSED */ FUNCTION(fun_filter) { /* take a user-def function and a list, and return only those elements * of the list for which the function evaluates to 1. */ dbref thing; ATTR *attrib; char const *abuf, *ap; char result[BUFFER_LEN], *rp; char *cp; char *tptr; char sep; int first; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* find our object and attribute */ parse_attrib(executor, args[0], &thing, &attrib); if (!GoodObject(thing) || !attrib) { safe_str("#-1 NOT FOUND", buff, bp); return; } if (!Can_Read_Attr(executor, thing, attrib)) { safe_str("#-1 NO PERMISSION TO GET ATTRIBUTE", buff, bp); return; } if (SAFER_UFUN) if ((!Wizard(executor) && Wizard(thing)) || (!Hasprivs(executor) && Hasprivs(thing))) { safe_str("#-1 NO PERMISSION TO RUN ATTRIBUTE", buff, bp); return; } abuf = safe_uncompress(attrib->value); tptr = wenv[0]; cp = trim_space_sep(args[1], sep); first = 1; while (cp && *cp) { wenv[0] = split_token(&cp, sep); ap = abuf; rp = result; process_expression(result, &rp, &ap, thing, executor, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); if (*result == '1') { if (first) first = 0; else safe_chr(sep, buff, bp); safe_str(wenv[0], buff, bp); } } wenv[0] = tptr; free((Malloc_t) abuf); } /* ARGSUSED */ FUNCTION(fun_shuffle) { /* given a list of words, randomize the order of words. * We do this by taking each element, and swapping it with another * element with a greater array index (thus, words[0] can be swapped * with anything up to words[n], words[5] with anything between * itself and words[n], etc. * This is relatively fast - linear time - and reasonably random. * Will take an optional delimiter argument. */ char *words[BUFFER_LEN / 2]; int n, i, j; char sep; if (!delim_check(buff, bp, nargs, args, 2, &sep)) return; /* split the list up, or return if the list is empty */ if (!*args[0]) return; n = list2arr(words, BUFFER_LEN / 2, args[0], sep); /* shuffle it */ for (i = 0; i < n; i++) { j = getrandom(n - i) + i; swap(&words[i], &words[j]); } arr2list(words, n, buff, bp, sep); } static int autodetect_list(ptrs, nptrs) char *ptrs[]; int nptrs; { int sort_type, i; char *p; sort_type = NUMERIC_LIST; for (i = 0; i < nptrs; i++) { switch (sort_type) { case NUMERIC_LIST: if (!is_strict_number(ptrs[i])) { /* If we get something non-numeric, switch to an * alphanumeric guess, unless this is the first * element and we have a dbref. */ if (i == 0) { p = ptrs[i]; if (*p++ != NUMBER_TOKEN) return ALPHANUM_LIST; else if (is_strict_number(p)) sort_type = DBREF_LIST; else return ALPHANUM_LIST; } else { return ALPHANUM_LIST; } } else if (strchr(ptrs[i], '.')) sort_type = FLOAT_LIST; break; #ifdef FLOATING_POINTS case FLOAT_LIST: if (!is_strict_number(ptrs[i])) return ALPHANUM_LIST; break; #endif case DBREF_LIST: /* If what we get following the '#' sign isn't a number, * we sort on alphanumeric. */ p = ptrs[i]; if (*p++ != NUMBER_TOKEN) return ALPHANUM_LIST; if (!is_strict_number(p)) return ALPHANUM_LIST; break; default: return ALPHANUM_LIST; } } return sort_type; } static int get_list_type(args, nargs, type_pos, ptrs, nptrs) char *args[]; int nargs; int type_pos; char *ptrs[]; int nptrs; { if (nargs >= type_pos) { switch (tolower(*args[type_pos - 1])) { case 'a': return ALPHANUM_LIST; case 'd': return DBREF_LIST; case 'n': return NUMERIC_LIST; case 'f': return FLOAT_LIST; case '\0': return autodetect_list(ptrs, nptrs); default: return ALPHANUM_LIST; } } return autodetect_list(ptrs, nptrs); } static int a_comp(s1, s2) const void *s1, *s2; { return strcmp(*(char **) s1, *(char **) s2); } typedef struct i_record i_rec; struct i_record { char *str; int num; }; static int i_comp(s1, s2) const void *s1, *s2; { if (((i_rec *) s1)->num > ((i_rec *) s2)->num) return 1; if (((i_rec *) s1)->num < ((i_rec *) s2)->num) return -1; return 0; } #ifdef FLOATING_POINTS typedef struct f_record f_rec; struct f_record { char *str; NVAL num; }; static int f_comp(s1, s2) const void *s1, *s2; { if (((f_rec *) s1)->num > ((f_rec *) s2)->num) return 1; if (((f_rec *) s1)->num < ((f_rec *) s2)->num) return -1; return 0; } #endif /* FLOATING_POINTS */ static dbref ucomp_executor, ucomp_caller, ucomp_enactor; static char ucomp_buff[BUFFER_LEN]; static PE_Info *ucomp_pe_info; static int u_comp(s1, s2) const void *s1, *s2; { char result[BUFFER_LEN], *rp; char const *tbuf; int n; /* Our two arguments are passed as %0 and %1 to the sortby u-function. */ /* Note that this function is for use in conjunction with our own * sane_qsort routine, NOT with the standard library qsort! */ wenv[0] = (char *) s1; wenv[1] = (char *) s2; /* Run the u-function, which should return a number. */ tbuf = ucomp_buff; rp = result; process_expression(result, &rp, &tbuf, ucomp_executor, ucomp_caller, ucomp_enactor, PE_DEFAULT, PT_DEFAULT, ucomp_pe_info); n = parse_integer(result); return n; } void do_gensort(s, n, sort_type) char *s[]; int n; int sort_type; { int i; #ifdef FLOATING_POINTS f_rec *fp; #endif i_rec *ip; switch (sort_type) { case ALPHANUM_LIST: qsort((void *) s, n, sizeof(char *), a_comp); break; case NUMERIC_LIST: ip = (i_rec *) mush_malloc(n * sizeof(i_rec), "do_gensort.int_list"); for (i = 0; i < n; i++) { ip[i].str = s[i]; ip[i].num = parse_integer(s[i]); } qsort((void *) ip, n, sizeof(i_rec), i_comp); for (i = 0; i < n; i++) s[i] = ip[i].str; mush_free((Malloc_t) ip, "do_gensort.int_list"); break; case DBREF_LIST: ip = (i_rec *) mush_malloc(n * sizeof(i_rec), "do_gensort.dbref_list"); for (i = 0; i < n; i++) { ip[i].str = s[i]; ip[i].num = parse_dbref(s[i]); } qsort((void *) ip, n, sizeof(i_rec), i_comp); for (i = 0; i < n; i++) s[i] = ip[i].str; mush_free((Malloc_t) ip, "do_gensort.dbref_list"); break; #ifdef FLOATING_POINTS case FLOAT_LIST: fp = (f_rec *) mush_malloc(n * sizeof(f_rec), "do_gensort.num_list"); for (i = 0; i < n; i++) { fp[i].str = s[i]; fp[i].num = parse_number(s[i]); } qsort((void *) fp, n, sizeof(f_rec), f_comp); for (i = 0; i < n; i++) s[i] = fp[i].str; mush_free((Malloc_t) fp, "do_gensort.num_list"); break; #endif /* FLOATING_POINTS */ } } /* ARGSUSED */ FUNCTION(fun_sort) { char *ptrs[MAX_SORTSIZE]; int nptrs, sort_type; char sep; if (!nargs || !*args[0]) return; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; nptrs = list2arr(ptrs, MAX_SORTSIZE, args[0], sep); sort_type = get_list_type(args, nargs, 2, ptrs, nptrs); do_gensort(ptrs, nptrs, sort_type); arr2list(ptrs, nptrs, buff, bp, sep); } static void sane_qsort(array, left, right, compare) void *array[]; int left, right; int (*compare) _((const void *, const void *)); { /* 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). */ /* Actually, this sort doesn't require commutivity. * Sorting doesn't make sense without transitivity... */ 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 = getrandom(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; } } /* ARGSUSED */ FUNCTION(fun_sortby) { char *ptrs[MAX_SORTSIZE], *tptr[10]; char *up, sep; int nptrs, i; dbref thing; ATTR *attrib; if (!nargs || !*args[0]) return; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* Find object and attribute to get sortby function from. */ parse_attrib(executor, args[0], &thing, &attrib); if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) return; if (SAFER_UFUN) if ((!Wizard(executor) && Wizard(thing)) || (!Hasprivs(executor) && Hasprivs(thing))) return; up = ucomp_buff; safe_str(uncompress(attrib->value), ucomp_buff, &up); *up = '\0'; ucomp_executor = thing; ucomp_caller = executor; ucomp_enactor = enactor; ucomp_pe_info = pe_info; /* Save the stack. */ for (i = 0; i < 10; i++) tptr[i] = wenv[i]; /* Split up the list, sort it, reconstruct it. */ nptrs = list2arr(ptrs, MAX_SORTSIZE, args[1], sep); if (nptrs > 1) /* pointless to sort less than 2 elements */ sane_qsort((void *) ptrs, 0, nptrs - 1, u_comp); arr2list(ptrs, nptrs, buff, bp, sep); /* Restore the stack */ for (i = 0; i < 10; i++) wenv[i] = tptr[i]; } /* ARGSUSED */ FUNCTION(fun_setunion) { char sep; char *a1[MAX_SORTSIZE]; char *a2[MAX_SORTSIZE]; char tempbuff[BUFFER_LEN * 2]; int n1, i, a; /* if no lists, then no work */ if (!*args[0] && !*args[1]) return; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* Concat both lists, make array, sort */ sprintf(tempbuff, "%s%c%s", args[0], sep, args[1]); n1 = list2arr(a1, MAX_SORTSIZE, tempbuff, sep); do_gensort(a1, n1, ALPHANUM_LIST); /* Strip the duplicates and make a2 contain the list */ a = 0; for (i = 0; i < n1; i++) { if (((a == 0) || (strcmp(a1[i], a2[a - 1]) != 0)) && (*a1[i])) { a2[a] = a1[i]; a++; } } /* Return our sorted result */ arr2list(a2, a, buff, bp, sep); } /* ARGSUSED */ FUNCTION(fun_setinter) { char sep; char *a1[MAX_SORTSIZE]; char *a2[MAX_SORTSIZE]; int n1, n2, x1, x2, val; /* if no lists, then no work */ if (!*args[0] && !*args[1]) return; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* make arrays out of the lists */ n1 = list2arr(a1, MAX_SORTSIZE, args[0], sep); n2 = list2arr(a2, MAX_SORTSIZE, args[1], sep); /* sort each array */ do_gensort(a1, n1, ALPHANUM_LIST); do_gensort(a2, n2, ALPHANUM_LIST); /* get the first value for the intersection, removing duplicates */ x1 = x2 = 0; while ((val = strcmp(a1[x1], a2[x2]))) { if (val < 0) { x1++; if (x1 >= n1) return; } else { x2++; if (x2 >= n2) return; } } safe_str(a1[x1], buff, bp); while (!strcmp(a1[x1], a2[x2])) { x1++; if (x1 >= n1) return; } /* get values for the intersection, until at least one list is empty */ while ((x1 < n1) && (x2 < n2)) { while ((val = strcmp(a1[x1], a2[x2]))) { if (val < 0) { x1++; if (x1 >= n1) return; } else { x2++; if (x2 >= n2) return; } } safe_chr(sep, buff, bp); safe_str(a1[x1], buff, bp); while (!strcmp(a1[x1], a2[x2])) { x1++; if (x1 >= n1) return; } } } /* ARGSUSED */ FUNCTION(fun_setdiff) { char sep; char *a1[MAX_SORTSIZE]; char *a2[MAX_SORTSIZE]; int n1, n2, x1, x2, val; /* if no lists, then no work */ if (!*args[0] && !*args[1]) return; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* make arrays out of the lists */ n1 = list2arr(a1, MAX_SORTSIZE, args[0], sep); n2 = list2arr(a2, MAX_SORTSIZE, args[1], sep); /* sort each array */ do_gensort(a1, n1, ALPHANUM_LIST); do_gensort(a2, n2, ALPHANUM_LIST); /* get the first value for the difference, removing duplicates */ x1 = x2 = 0; while ((val = strcmp(a1[x1], a2[x2])) >= 0) { if (val > 0) { x2++; if (x2 >= n2) break; } if (!val) { x1++; if (x1 >= n1) return; } } safe_str(a1[x1], buff, bp); do { x1++; if (x1 >= n1) return; } while (!strcmp(a1[x1], a1[x1 - 1])); /* get values for the difference, until at least one list is empty */ while (x2 < n2) { if ((val = strcmp(a1[x1], a2[x2])) < 0) { safe_chr(sep, buff, bp); safe_str(a1[x1], buff, bp); } if (val <= 0) { do { x1++; if (x1 >= n1) return; } while (!strcmp(a1[x1], a1[x1 - 1])); } if (val >= 0) x2++; } /* empty out remaining values, still removing duplicates */ while (x1 < n1) { safe_chr(sep, buff, bp); safe_str(a1[x1], buff, bp); do { x1++; } while ((x1 < n1) && !strcmp(a1[x1], a1[x1 - 1])); } } /* ARGSUSED */ FUNCTION(fun_lnum) { NVAL j; NVAL start; NVAL end; char const *osep = " "; if (!is_number(args[0])) { safe_str(e_num, buff, bp); return; } end = parse_number(args[0]); if (nargs > 1) { if (!is_number(args[1])) { safe_str(e_num, buff, bp); return; } start = end; end = parse_number(args[1]); } else { if (end == 0) return; /* Special case - lnum(0) -> blank string */ end--; if (end < 0) { safe_str("#-1 NUMBER OUT OF RANGE", buff, bp); return; } start = 0; } if (nargs > 2) { osep = args[2]; } if (start <= end) { for (j = start; j <= end; j++) { if (j > start) safe_str(osep, buff, bp); if (safe_str(unparse_number(j), buff, bp)) break; } } else { for (j = start; j >= end; j--) { if (j < start) safe_str(osep, buff, bp); if (safe_str(unparse_number(j), buff, bp)) break; } } } /* ARGSUSED */ FUNCTION(fun_first) { /* read first word from a string */ char *p; char sep; if (!*args[0]) return; if (!delim_check(buff, bp, nargs, args, 2, &sep)) return; p = trim_space_sep(args[0], sep); safe_str(split_token(&p, sep), buff, bp); } /* ARGSUSED */ FUNCTION(fun_rest) { char *p; char sep; if (!*args[0]) return; if (!delim_check(buff, bp, nargs, args, 2, &sep)) return; p = trim_space_sep(args[0], sep); (void) split_token(&p, sep); safe_str(p, buff, bp); } /* ARGSUSED */ FUNCTION(fun_last) { /* read last word from a string */ char *p, *r; char sep; if (!*args[0]) return; if (!delim_check(buff, bp, nargs, args, 2, &sep)) return; p = trim_space_sep(args[0], sep); if (!(r = strrchr(p, sep))) r = p; else r++; safe_str(r, buff, bp); } /* ARGSUSED */ FUNCTION(fun_grab) { /* compares two strings with possible wildcards, returns the * word matched. Based on the 2.2 version of this function. */ char *r, *s, sep; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* Walk the wordstring, until we find the word we want. */ s = trim_space_sep(args[0], sep); do { r = split_token(&s, sep); if (quick_wild(args[1], r)) { safe_str(r, buff, bp); return; } } while (s); } /* ARGSUSED */ FUNCTION(fun_match) { /* compares two strings with possible wildcards, returns the * word position of the match. Based on the 2.0 version of this * function. */ char *s, *r; char sep; int wcount = 1; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; /* Walk the wordstring, until we find the word we want. */ s = trim_space_sep(args[0], sep); do { r = split_token(&s, sep); if (quick_wild(args[1], r)) { safe_str(unparse_integer(wcount), buff, bp); return; } wcount++; } while (s); safe_chr('0', buff, bp); } /* ARGSUSED */ FUNCTION(fun_wordpos) { int charpos, i; char *cp, *tp, *xp; char sep; if (!is_integer(args[1])) { safe_str(e_int, buff, bp); return; } charpos = parse_integer(args[1]); cp = args[0]; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; if ((charpos <= 0) || ((Size_t) charpos > strlen(cp))) { safe_str("#-1", buff, bp); return; } tp = cp + charpos - 1; cp = trim_space_sep(cp, sep); xp = split_token(&cp, sep); for (i = 1; xp; i++) { if (tp < (xp + strlen(xp))) break; xp = split_token(&cp, sep); } safe_str(unparse_integer(i), buff, bp); } /* ARGSUSED */ FUNCTION(fun_extract) { char sep; int start, len; char *s, *r; if (!is_integer(args[1]) || !is_integer(args[2])) { safe_str(e_ints, buff, bp); return; } s = args[0]; start = parse_integer(args[1]); len = parse_integer(args[2]); if (!delim_check(buff, bp, nargs, args, 4, &sep)) return; if ((start < 1) || (len < 1)) return; /* Go to the start of the token we're interested in. */ start--; s = trim_space_sep(s, sep); while (start && s) { s = next_token(s, sep); start--; } if (!s || !*s) /* ran off the end of the string */ return; /* Find the end of the string that we want. */ r = s; len--; while (len && s) { s = next_token(s, sep); len--; } /* Chop off the end, and copy. No length checking needed. */ if (s && *s) (void) split_token(&s, sep); safe_str(r, buff, bp); } /* ARGSUSED */ FUNCTION(fun_cat) { int i; safe_str(args[0], buff, bp); for (i = 1; i < nargs; i++) { safe_chr(' ', buff, bp); safe_str(args[i], buff, bp); } } /* ARGSUSED */ FUNCTION(fun_remove) { char *s, *sp; char sep; /* zap word from string */ if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; if (strchr(args[1], sep)) { safe_str("#-1 CAN ONLY DELETE ONE ELEMENT", buff, bp); return; } s = args[0]; sp = split_token(&s, sep); if (!strcmp(sp, args[1])) { sp = split_token(&s, sep); safe_str(sp, buff, bp); } else { safe_str(sp, buff, bp); while (s && strcmp(sp = split_token(&s, sep), args[1])) { safe_chr(sep, buff, bp); safe_str(sp, buff, bp); } } while (s) { sp = split_token(&s, sep); safe_chr(sep, buff, bp); safe_str(sp, buff, bp); } } /* ARGSUSED */ FUNCTION(fun_items) { /* the equivalent of WORDS for an arbitrary separator */ /* This differs from WORDS in its treatment of the space * separator. */ char *s = args[0]; char c = *args[1]; int count = 1; if (c == '\0') c = ' '; while ((s = strchr(s, c))) { count++; s++; } safe_str(unparse_integer(count), buff, bp); } /* ARGSUSED */ FUNCTION(fun_element) { /* the equivalent of MEMBER for an arbitrary separator */ /* This differs from MEMBER in its use of quick_wild() * instead of strcmp(). */ char *s, *t; char c; int el; c = *args[2]; if (c == '\0') c = ' '; if (strchr(args[1], c)) { safe_str("#-1 CAN ONLY TEST ONE ELEMENT", buff, bp); return; } s = args[0]; el = 1; do { t = s; s = seek_char(t, c); if (*s) *s++ = '\0'; if (quick_wild(args[1], t)) { safe_str(unparse_integer(el), buff, bp); return; } el++; } while (*s); safe_chr('0', buff, bp); /* no match */ } /* ARGSUSED */ FUNCTION(fun_index) { /* more or less the equivalent of EXTRACT for an arbitrary separator */ /* This differs from EXTRACT in its handling of space separators. */ int start, end; char c; char *s, *p; if (!is_integer(args[2]) || !is_integer(args[3])) { safe_str(e_ints, buff, bp); return; } s = args[0]; c = *args[1]; if (!c) c = ' '; start = parse_integer(args[2]); end = parse_integer(args[3]); if ((start < 1) || (end < 1) || (*s == '\0')) return; /* move s to the start of the item we want */ while (--start) { if (!(s = strchr(s, c))) return; s++; } /* skip just spaces, not tabs or newlines, since people may MUSHcode things * like "%r%tPolgara %r%tDurnik %r%tJavelin" */ while (*s == ' ') s++; if (!*s) return; /* now figure out where to end the string */ p = s + 1; /* we may already be pointing to a separator */ if (*s == c) end--; while (end--) if (!(p = strchr(p, c))) break; else p++; if (p) p--; else p = s + strlen(s); /* trim trailing spaces (just true spaces) */ while ((p > s) && (p[-1] == ' ')) p--; *p = '\0'; safe_str(s, buff, bp); } static void do_itemfuns(buff, bp, str, num, word, sep, flag) char *buff; /* the return buffer */ char **bp; /* the active point in the return buffer */ char *str; /* the original string */ char *num; /* the element number */ char *word; /* word to insert or replace */ char *sep; /* the separator */ int flag; /* op -- 0 delete, 1 replace, 2 insert */ { char c; int el, count; char *sptr, *eptr; if (!is_integer(num)) { safe_str(e_int, buff, bp); return; } el = parse_integer(num); /* figure out the separator character */ if (sep && *sep) c = *sep; else c = ' '; /* we can't remove anything before the first position */ if (el < 1) { safe_str(str, buff, bp); return; } sptr = str; eptr = strchr(sptr, c); count = 1; /* go to the correct item in the string */ /* Loop invariant: if sptr and eptr are not NULL, eptr points to * the count'th instance of c in str, and sptr is the beginning of * the count'th item. */ while (eptr && (count < el)) { sptr = eptr + 1; eptr = strchr(sptr, c); count++; } if (!eptr && (count < el)) { /* we've run off the end of the string without finding anything */ safe_str(str, buff, bp); return; } /* now find the end of that element */ if (sptr != str) sptr[-1] = '\0'; switch (flag) { case 0: /* deletion */ if (!eptr) { /* last element in the string */ if (el != 1) safe_str(str, buff, bp); } else if (sptr == str) { /* first element in the string */ eptr++; /* chop leading separator */ safe_str(eptr, buff, bp); } else { safe_str(str, buff, bp); safe_str(eptr, buff, bp); } break; case 1: /* replacing */ if (!eptr) { /* last element in string */ if (el != 1) { safe_str(str, buff, bp); safe_chr(c, buff, bp); } safe_str(word, buff, bp); } else if (sptr == str) { /* first element in string */ safe_str(word, buff, bp); safe_str(eptr, buff, bp); } else { safe_str(str, buff, bp); safe_chr(c, buff, bp); safe_str(word, buff, bp); safe_str(eptr, buff, bp); } break; case 2: /* insertion */ if (sptr == str) { /* first element in string */ safe_str(word, buff, bp); safe_chr(c, buff, bp); safe_str(str, buff, bp); } else { safe_str(str, buff, bp); safe_chr(c, buff, bp); safe_str(word, buff, bp); safe_chr(c, buff, bp); safe_str(sptr, buff, bp); } break; } } /* ARGSUSED */ FUNCTION(fun_ldelete) { /* delete a word at position X of a list */ do_itemfuns(buff, bp, args[0], args[1], NULL, args[2], 0); } /* ARGSUSED */ FUNCTION(fun_replace) { /* replace a word at position X of a list */ do_itemfuns(buff, bp, args[0], args[1], args[2], args[3], 1); } /* ARGSUSED */ FUNCTION(fun_insert) { /* insert a word at position X of a list */ do_itemfuns(buff, bp, args[0], args[1], args[2], args[3], 2); } /* ARGSUSED */ FUNCTION(fun_member) { char *s, *t; char sep; int el; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; if (strchr(args[1], sep)) { safe_str("#-1 CAN ONLY TEST ONE ELEMENT", buff, bp); return; } s = trim_space_sep(args[0], sep); el = 1; do { t = split_token(&s, sep); if (!strcmp(args[1], t)) { safe_str(unparse_integer(el), buff, bp); return; } el++; } while (s); safe_chr('0', buff, bp); /* not found */ } /* ARGSUSED */ FUNCTION(fun_before) { char *p; if (!*args[1]) p = strchr(args[0], ' '); else p = strstr(args[0], args[1]); if (p) *p = '\0'; safe_str(args[0], buff, bp); } /* ARGSUSED */ FUNCTION(fun_after) { char *p; if (!*args[1]) { args[1][0] = ' '; args[1][1] = '\0'; } p = strstr(args[0], args[1]); if (p) safe_str(p + strlen(args[1]), buff, bp); } /* ARGSUSED */ FUNCTION(fun_revwords) { char *words[BUFFER_LEN / 2]; char *p; int count; char sep; if (!delim_check(buff, bp, nargs, args, 2, &sep)) return; count = 0; p = args[0]; while ((words[count] = split_token(&p, sep))) count++; safe_str(words[--count], buff, bp); while (count) { safe_chr(sep, buff, bp); safe_str(words[--count], buff, bp); } } /* ARGSUSED */ FUNCTION(fun_words) { char sep; if (!delim_check(buff, bp, nargs, args, 2, &sep)) return; safe_str(unparse_integer(do_wordcount(trim_space_sep(args[0], sep), sep)), buff, bp); } /* ARGSUSED */ FUNCTION(fun_splice) { /* like MERGE(), but does it for a word */ char *s0, *s1, *s2; char *p0, *p1; char sep; if (!delim_check(buff, bp, nargs, args, 4, &sep)) return; s0 = trim_space_sep(args[0], sep); s1 = trim_space_sep(args[1], sep); s2 = trim_space_sep(args[2], sep); /* length checks */ if (!*args[2]) { safe_str("#-1 NEED A WORD", buff, bp); return; } if (do_wordcount(s2, sep) != 1) { safe_str("#-1 TOO MANY WORDS", buff, bp); return; } if (do_wordcount(s0, sep) != do_wordcount(s1, sep)) { safe_str("#-1 NUMBER OF WORDS MUST BE EQUAL", buff, bp); return; } /* loop through the two lists */ p0 = split_token(&s0, sep); p1 = split_token(&s1, sep); safe_str(strcmp(p0, s2) ? p0 : p1, buff, bp); while (s0) { p0 = split_token(&s0, sep); p1 = split_token(&s1, sep); safe_chr(sep, buff, bp); safe_str(strcmp(p0, s2) ? p0 : p1, buff, bp); } } /* ARGSUSED */ FUNCTION(fun_iter) { /* Based on the TinyMUSH 2.0 code for this function. Please note that * arguments to this function are passed _unparsed_. */ char sep; char outsep[BUFFER_LEN]; char list[BUFFER_LEN]; char *tbuf1, *tbuf2, *lp; char const *sp; int place; if (nargs >= 3) { /* We have a delimiter. We've got to parse the third arg in place */ char insep[BUFFER_LEN]; char *isep = insep; const char *arg3 = args[2]; process_expression(insep, &isep, &arg3, executor, caller, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); *isep = '\0'; strcpy(args[2], insep); } if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; if (nargs < 4) strcpy(outsep, " "); else { const char *arg4 = args[3]; char *osep = outsep; process_expression(outsep, &osep, &arg4, executor, caller, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); *osep = '\0'; } lp = list; sp = args[0]; process_expression(list, &lp, &sp, executor, caller, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); *lp = '\0'; lp = trim_space_sep(list, sep); if (!*lp) return; place = 0; while (lp) { if (place) safe_str(outsep, buff, bp); place++; tbuf1 = split_token(&lp, sep); tbuf2 = replace_string("##", tbuf1, args[1]); tbuf1 = replace_string("#@", unparse_integer(place), tbuf2); mush_free((Malloc_t) tbuf2, "replace_string.buff"); sp = tbuf1; process_expression(buff, bp, &sp, executor, caller, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); mush_free((Malloc_t) tbuf1, "replace_string.buff"); } } /* ARGSUSED */ FUNCTION(fun_map) { /* Like iter(), but calls an attribute with list elements as %0 instead. * If the attribute is not found, null is returned, NOT an error. * This function takes delimiters. */ dbref thing; ATTR *attrib; char const *asave, *ap; char *lp; char *tptr; char sep; if (!delim_check(buff, bp, nargs, args, 3, &sep)) return; lp = trim_space_sep(args[1], sep); if (!*lp) return; /* find our object and attribute */ parse_attrib(executor, args[0], &thing, &attrib); if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) return; if (SAFER_UFUN) if ((!Wizard(executor) && Wizard(thing)) || (!Hasprivs(executor) && Hasprivs(thing))) return; asave = safe_uncompress(attrib->value); /* save our stack */ tptr = wenv[0]; wenv[0] = split_token(&lp, sep); ap = asave; process_expression(buff, bp, &ap, thing, executor, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); while (lp) { safe_chr(sep, buff, bp); wenv[0] = split_token(&lp, sep); ap = asave; process_expression(buff, bp, &ap, thing, executor, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); } free((Malloc_t) asave); wenv[0] = tptr; } /* ARGSUSED */ FUNCTION(fun_mix) { /* Like map(), but goes through two lists, passing them as %0 and %1. * If the attribute is not found, null is returned, NOT an error. * This function takes delimiters. */ dbref thing; ATTR *attrib; char const *asave, *ap; char *l1p, *l2p; char *tptr[2]; char sep; if (!delim_check(buff, bp, nargs, args, 4, &sep)) return; l1p = trim_space_sep(args[1], sep); l2p = trim_space_sep(args[2], sep); if (do_wordcount(l1p, sep) != do_wordcount(l2p, sep)) { safe_str("#-1 LISTS MUST BE OF EQUAL SIZE", buff, bp); return; } if (!*l1p) return; /* find our object and attribute */ parse_attrib(executor, args[0], &thing, &attrib); if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) return; if (SAFER_UFUN) if ((!Wizard(executor) && Wizard(thing)) || (!Hasprivs(executor) && Hasprivs(thing))) return; asave = safe_uncompress(attrib->value); /* save our stack */ tptr[0] = wenv[0]; tptr[1] = wenv[1]; wenv[0] = split_token(&l1p, sep); wenv[1] = split_token(&l2p, sep); ap = asave; process_expression(buff, bp, &ap, thing, executor, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); while (l1p) { safe_chr(sep, buff, bp); wenv[0] = split_token(&l1p, sep); wenv[1] = split_token(&l2p, sep); ap = asave; process_expression(buff, bp, &ap, thing, executor, enactor, PE_DEFAULT, PT_DEFAULT, pe_info); } free((Malloc_t) asave); wenv[0] = tptr[0]; wenv[1] = tptr[1]; } /* ARGSUSED */ FUNCTION(fun_table) { /* TABLE(list, field_width, line_length, delimiter, output sep) * Given a list, produce a table (a column'd list) * Optional parameters: field width, line length, delimiter, output sep * Number of columns = line length / (field width+1) */ int line_length = 78; int field_width = 10; int col = 0; int spaces; char sep, osep, *cp, *t; char tbuf1[BUFFER_LEN]; if (!delim_check(buff, bp, nargs, args, 5, &osep)) return; if ((nargs == 5) && !*args[4]) osep = 0; if (!delim_check(buff, bp, nargs, args, 4, &sep)) return; if (nargs > 2) { if (!is_integer(args[2])) { safe_str(e_ints, buff, bp); return; } line_length = parse_integer(args[2]); if (line_length < 2) line_length = 2; } if (nargs > 1) { if (!is_integer(args[1])) { safe_str(e_ints, buff, bp); return; } field_width = parse_integer(args[1]); if (field_width < 1) field_width = 1; } if (field_width >= line_length) field_width = line_length - 1; /* Split out each token, truncate/pad it to field_width, and pack * it onto the line. When the line would go over line_length, * send a return */ cp = trim_space_sep(args[0], sep); if (!*cp) return; col = field_width + !!osep; t = split_token(&cp, sep); strcpy(tbuf1, t); tbuf1[field_width] = '\0'; safe_str(tbuf1, buff, bp); for (spaces = field_width - ansi_strlen(t); spaces > 0; spaces--) safe_chr(' ', buff, bp); while (cp) { col += field_width + !!osep; if (col > line_length) { safe_str("\r\n", buff, bp); col = field_width + !!osep; } else { if (osep) safe_chr(osep, buff, bp); } t = split_token(&cp, sep); strcpy(tbuf1, t); tbuf1[field_width] = '\0'; safe_str(tbuf1, buff, bp); for (spaces = field_width - ansi_strlen(t); spaces > 0; spaces--) safe_chr(' ', buff, bp); } } FUNCTION(fun_regmatch) { /* --------------------------------------------------------------------------- * fun_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(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. * * Based on fun_regmatch from TinyMUSH 2.2.4 */ int i, nqregs, curq, len; char *qregs[10]; int qnums[10]; regexp *re; if ((re = regcomp(args[1])) == NULL) { /* Matching error. */ safe_str("#-1 REGEXP ERROR: ", buff, bp); safe_str((const char *) regexp_errbuf, buff, bp); return; } safe_str(unparse_integer((int) regexec(re, args[0])), buff, bp); /* If we don't have a third argument, we're done. */ if (nargs < 3) { mush_free(re, "regexp"); return; } /* We need to parse the list of registers. Anything that we don't get is * assumed to be -1. */ nqregs = list2arr(qregs, 10, args[2], ' '); for (i = 0; i < 10; i++) { if ((i < nqregs) && qregs[i] && *qregs[i]) qnums[i] = atoi(qregs[i]); else qnums[i] = -1; } /* Now we run a copy. */ for (i = 0; (i < NSUBEXP) && (re->startp[i]) && (re->endp[i]); i++) { curq = qnums[i]; if ((curq >= 0) && (curq < 10)) { len = re->endp[i] - re->startp[i]; if (len > BUFFER_LEN - 1) len = BUFFER_LEN - 1; strncpy(renv[curq], re->startp[i], len); renv[curq][len] = '\0'; /* must null-terminate */ } } mush_free(re, "regexp"); }