root/1.8.3/tags/p3/src/funmisc.c

Revision 919, 19.7 KB (checked in by shawnw, 19 months ago)

1.8.3p3

Line 
1/**
2 * \file funmisc.c
3 *
4 * \brief Miscellaneous functions for mushcode.
5 *
6 *
7 */
8#include "copyrite.h"
9
10#include "config.h"
11#include <time.h>
12#include <string.h>
13#include <ctype.h>
14#include "conf.h"
15#include "case.h"
16#include "externs.h"
17#include "version.h"
18#include "htab.h"
19#include "flags.h"
20#include "lock.h"
21#include "match.h"
22#include "mushdb.h"
23#include "dbdefs.h"
24#include "parse.h"
25#include "function.h"
26#include "command.h"
27#include "game.h"
28#include "attrib.h"
29#include "confmagic.h"
30#include "ansi.h"
31
32#ifdef WIN32
33#pragma warning( disable : 4761)        /* NJG: disable warning re conversion */
34#endif
35
36extern FUN flist[];
37static char *soundex(char *str);
38extern char cf_motd_msg[BUFFER_LEN], cf_wizmotd_msg[BUFFER_LEN],
39  cf_downmotd_msg[BUFFER_LEN], cf_fullmotd_msg[BUFFER_LEN];
40extern HASHTAB htab_function;
41
42/* ARGSUSED */
43FUNCTION(fun_valid)
44{
45  /* Checks to see if a given <something> is valid as a parameter of a
46   * given type (such as an object name.)
47   */
48
49  if (!args[0] || !*args[0])
50    safe_str("#-1", buff, bp);
51  else if (!strcasecmp(args[0], "name"))
52    safe_boolean(ok_name(args[1]), buff, bp);
53  else if (!strcasecmp(args[0], "attrname"))
54    safe_boolean(good_atr_name(upcasestr(args[1])), buff, bp);
55  else if (!strcasecmp(args[0], "playername"))
56    safe_boolean(ok_player_name(args[1], executor, executor), buff, bp);
57  else if (!strcasecmp(args[0], "password"))
58    safe_boolean(ok_password(args[1]), buff, bp);
59  else if (!strcasecmp(args[0], "command"))
60    safe_boolean(ok_command_name(upcasestr(args[1])), buff, bp);
61  else if (!strcasecmp(args[0], "function"))
62    safe_boolean(ok_function_name(upcasestr(args[1])), buff, bp);
63  else
64    safe_str("#-1", buff, bp);
65}
66
67/* ARGSUSED */
68FUNCTION(fun_pemit)
69{
70  int ns = string_prefix(called_as, "NS");
71  int flags = PEMIT_LIST;
72  dbref saved_orator = orator;
73  if (!command_check_byname(executor, ns ? "@nspemit" : "@pemit") ||
74      fun->flags & FN_NOSIDEFX) {
75    safe_str(T(e_perm), buff, bp);
76    return;
77  }
78  orator = executor;
79  if (ns)
80    flags |= PEMIT_SPOOF;
81  do_pemit_list(executor, args[0], args[1], flags);
82  orator = saved_orator;
83}
84
85
86/* ARGSUSED */
87FUNCTION(fun_oemit)
88{
89  int ns = string_prefix(called_as, "NS");
90  int flags = ns ? PEMIT_SPOOF : 0;
91  if (!command_check_byname(executor, ns ? "@nsoemit" : "@oemit") ||
92      fun->flags & FN_NOSIDEFX) {
93    safe_str(T(e_perm), buff, bp);
94    return;
95  }
96  orator = executor;
97  do_oemit_list(executor, args[0], args[1], flags);
98}
99
100/* ARGSUSED */
101FUNCTION(fun_emit)
102{
103  int ns = string_prefix(called_as, "NS");
104  int flags = ns ? PEMIT_SPOOF : 0;
105  if (!command_check_byname(executor, ns ? "@nsemit" : "@emit") ||
106      fun->flags & FN_NOSIDEFX) {
107    safe_str(T(e_perm), buff, bp);
108    return;
109  }
110  orator = executor;
111  do_emit(executor, args[0], flags);
112}
113
114/* ARGSUSED */
115FUNCTION(fun_remit)
116{
117  int ns = string_prefix(called_as, "NS");
118  int flags = ns ? PEMIT_SPOOF : 0;
119  if (!command_check_byname(executor, ns ? "@nsremit" : "@remit") ||
120      fun->flags & FN_NOSIDEFX) {
121    safe_str(T(e_perm), buff, bp);
122    return;
123  }
124  orator = executor;
125  do_remit(executor, args[0], args[1], flags);
126}
127
128/* ARGSUSED */
129FUNCTION(fun_lemit)
130{
131  int ns = string_prefix(called_as, "NS");
132  int flags = ns ? PEMIT_SPOOF : 0;
133  if (!command_check_byname(executor, ns ? "@nslemit" : "@lemit") ||
134      fun->flags & FN_NOSIDEFX) {
135    safe_str(T(e_perm), buff, bp);
136    return;
137  }
138  orator = executor;
139  do_lemit(executor, args[0], flags);
140}
141
142/* ARGSUSED */
143FUNCTION(fun_zemit)
144{
145  int ns = string_prefix(called_as, "NS");
146  int flags = ns ? PEMIT_SPOOF : 0;
147  if (!command_check_byname(executor, ns ? "@nszemit" : "@zemit") ||
148      fun->flags & FN_NOSIDEFX) {
149    safe_str(T(e_perm), buff, bp);
150    return;
151  }
152  orator = executor;
153  do_zemit(executor, args[0], args[1], flags);
154}
155
156/* ARGSUSED */
157FUNCTION(fun_prompt)
158{
159  int ns = string_prefix(called_as, "NS");
160  int flags = PEMIT_LIST | PEMIT_PROMPT;
161  if (!command_check_byname(executor, ns ? "@nspemit" : "@pemit") ||
162      fun->flags & FN_NOSIDEFX) {
163    safe_str(T(e_perm), buff, bp);
164    return;
165  }
166  orator = executor;
167  if (ns)
168    flags |= PEMIT_SPOOF;
169  do_pemit_list(executor, args[0], args[1], flags);
170}
171
172
173extern signed char qreg_indexes[UCHAR_MAX + 1];
174/* ARGSUSED */
175FUNCTION(fun_setq)
176{
177  /* sets a variable into a local register */
178  int qindex;
179  int n;
180
181  if ((nargs % 2) != 0) {
182    safe_format(buff, bp,
183                T("#-1 FUNCTION (%s) EXPECTS AN EVEN NUMBER OF ARGUMENTS"),
184                called_as);
185    return;
186  }
187
188  for (n = 0; n < nargs; n += 2) {
189    if (*args[n] && (*(args[n] + 1) == '\0') &&
190        ((qindex = qreg_indexes[(unsigned char) args[n][0]]) != -1)
191        && global_eval_context.renv[qindex]) {
192      strcpy(global_eval_context.renv[qindex], args[n + 1]);
193      if (n == 0 && !strcmp(called_as, "SETR"))
194        safe_strl(args[n + 1], arglens[n + 1], buff, bp);
195    } else
196      safe_str(T("#-1 REGISTER OUT OF RANGE"), buff, bp);
197  }
198}
199
200FUNCTION(fun_letq)
201{
202  char **values = NULL;
203  int *regs = NULL;
204  int npairs;
205  int n;
206  char tbuf[BUFFER_LEN], *tbp;
207  char *preserve[NUMQ];
208  const char *p;
209
210  if ((nargs % 2) != 1) {
211    safe_str(T("#-1 FUNCTION (letq) EXPECTS AN ODD NUMBER OF ARGUMENTS"),
212             buff, bp);
213    return;
214  }
215
216  npairs = (nargs - 1) / 2;
217
218  for (n = 0; n < NUMQ; n++)
219    preserve[n] = NULL;
220
221  if (npairs) {
222    values = mush_calloc(npairs, sizeof(char *), "letq.values");
223    if (!values) {
224      safe_str(T("#-1 UNABLE TO ALLOCATE MEMORY"), buff, bp);
225      return;
226    }
227
228    regs = mush_calloc(npairs, sizeof(int), "letq.registers");
229    if (!regs) {
230      safe_str(T("#-1 UNABLE TO ALLOCATE MEMORY"), buff, bp);
231      mush_free(values, "letq.values");
232      return;
233    }
234
235    for (n = 0; n < npairs; n++) {
236      int i = n * 2;
237
238      tbp = tbuf;
239      p = args[i];
240      process_expression(tbuf, &tbp, &p, executor, caller, enactor, PE_DEFAULT,
241                         PT_DEFAULT, pe_info);
242      *tbp = '\0';
243      regs[n] = qreg_indexes[(unsigned char) tbuf[0]];
244      if (regs[n] < 0) {
245        safe_str(T("#-1 REGISTER OUT OF RANGE"), buff, bp);
246        goto cleanup;
247      }
248
249      tbp = tbuf;
250      p = args[i + 1];
251      process_expression(tbuf, &tbp, &p, executor, caller, enactor, PE_DEFAULT,
252                         PT_DEFAULT, pe_info);
253      *tbp = '\0';
254      values[n] = mush_strdup(tbuf, "letq.value");
255      if (!values[n]) {
256        safe_str(T("#-1 UNABLE TO ALLOCATE MEMORY"), buff, bp);
257        goto cleanup;
258      }
259    }
260
261    for (n = 0; n < npairs; n++) {
262      save_partial_global_reg("letq", preserve, regs[n]);
263      mush_strncpy(global_eval_context.renv[regs[n]], values[n], BUFFER_LEN);
264    }
265  }
266  p = args[nargs - 1];
267  process_expression(buff, bp, &p, executor, caller, enactor, PE_DEFAULT,
268                     PT_DEFAULT, pe_info);
269
270cleanup:
271  if (regs)
272    mush_free(regs, "letq.regs");
273  if (values) {
274    restore_partial_global_regs("letq", preserve);
275    for (n = 0; n < npairs; n++)
276      mush_free(values[n], "letq.value");
277    mush_free(values, "letq.values");
278  }
279}
280
281/* ARGSUSED */
282FUNCTION(fun_r)
283{
284  /* returns a local register */
285  int qindex;
286
287  if (*args[0] && (*(args[0] + 1) == '\0') &&
288      ((qindex = qreg_indexes[(unsigned char) args[0][0]]) != -1)
289      && global_eval_context.renv[qindex])
290    safe_str(global_eval_context.renv[qindex], buff, bp);
291  else
292    safe_str(T("#-1 REGISTER OUT OF RANGE"), buff, bp);
293}
294
295/* --------------------------------------------------------------------------
296 * Utility functions: RAND, DIE, SECURE, SPACE, BEEP, SWITCH, EDIT,
297 *      ESCAPE, SQUISH, ENCRYPT, DECRYPT, LIT
298 */
299
300/* ARGSUSED */
301FUNCTION(fun_rand)
302{
303  /*
304   * Uses Sh'dow's random number generator, found in utils.c.  Better
305   * distribution than original, w/ minimal speed losses.
306   */
307  int low, high;
308  if (!is_integer(args[0])) {
309    safe_str(T(e_int), buff, bp);
310    return;
311  }
312  if (nargs == 1) {
313    low = 0;
314    high = parse_integer(args[0]) - 1;
315  } else {
316    if (!is_integer(args[1])) {
317      safe_str(T(e_ints), buff, bp);
318      return;
319    }
320    low = parse_integer(args[0]);
321    high = parse_integer(args[1]);
322  }
323
324  if (low > high) {
325    safe_str(T(e_range), buff, bp);
326    return;
327  }
328
329  safe_integer(get_random_long(low, high), buff, bp);
330}
331
332/* ARGSUSED */
333FUNCTION(fun_die)
334{
335  unsigned int n;
336  unsigned int die;
337  unsigned int count;
338  unsigned int total = 0;
339  int show_all = 0, first = 1;
340
341  if (!is_uinteger(args[0]) || !is_uinteger(args[1])) {
342    safe_str(T(e_uints), buff, bp);
343    return;
344  }
345  n = parse_uinteger(args[0]);
346  die = parse_uinteger(args[1]);
347  if (nargs == 3)
348    show_all = parse_boolean(args[2]);
349
350  if (n == 0 || n > 20) {
351    safe_str(T("#-1 NUMBER OUT OF RANGE"), buff, bp);
352    return;
353  }
354  if (show_all) {
355    for (count = 0; count < n; count++) {
356      if (first)
357        first = 0;
358      else
359        safe_chr(' ', buff, bp);
360      safe_uinteger(get_random_long(1, die), buff, bp);
361    }
362  } else {
363    for (count = 0; count < n; count++)
364      total += get_random_long(1, die);
365
366    safe_uinteger(total, buff, bp);
367  }
368}
369
370/* ARGSUSED */
371FUNCTION(fun_switch)
372{
373  /* this works a bit like the @switch command: it returns the string
374   * appropriate to the match. It picks the first match, like @select
375   * does, though.
376   * Args to this function are passed unparsed. Args are not evaluated
377   * until they are needed.
378   */
379
380  int j, per;
381  char mstr[BUFFER_LEN], pstr[BUFFER_LEN], *dp;
382  char const *sp;
383  char *tbuf1 = NULL;
384  int first = 1, found = 0, exact = 0;
385
386  if (strstr(called_as, "ALL"))
387    first = 0;
388
389  if (string_prefix(called_as, "CASE"))
390    exact = 1;
391
392  dp = mstr;
393  sp = args[0];
394  process_expression(mstr, &dp, &sp, executor, caller, enactor,
395                     PE_DEFAULT, PT_DEFAULT, pe_info);
396  *dp = '\0';
397
398  /* try matching, return match immediately when found */
399
400  for (j = 1; j < (nargs - 1); j += 2) {
401    dp = pstr;
402    sp = args[j];
403    process_expression(pstr, &dp, &sp, executor, caller, enactor,
404                       PE_DEFAULT, PT_DEFAULT, pe_info);
405    *dp = '\0';
406
407    if ((!exact)
408        ? local_wild_match(pstr, mstr)
409        : (strcmp(pstr, mstr) == 0)) {
410      /* If there's a #$ in a switch's action-part, replace it with
411       * the value of the conditional (mstr) before evaluating it.
412       */
413      if (!exact)
414        tbuf1 = replace_string("#$", mstr, args[j + 1]);
415      else
416        tbuf1 = args[j + 1];
417
418      sp = tbuf1;
419
420      per = process_expression(buff, bp, &sp,
421                               executor, caller, enactor,
422                               PE_DEFAULT, PT_DEFAULT, pe_info);
423      if (!exact)
424        mush_free((Malloc_t) tbuf1, "replace_string.buff");
425      found = 1;
426      if (per || first)
427        return;
428    }
429  }
430
431  if (!(nargs & 1) && !found) {
432    /* Default case */
433    if (!exact) {
434      tbuf1 = replace_string("#$", mstr, args[nargs - 1]);
435      sp = tbuf1;
436    } else
437      sp = args[nargs - 1];
438    process_expression(buff, bp, &sp, executor, caller, enactor,
439                       PE_DEFAULT, PT_DEFAULT, pe_info);
440    if (!exact)
441      mush_free((Malloc_t) tbuf1, "replace_string.buff");
442  }
443}
444
445FUNCTION(fun_reswitch)
446{
447  /* this works a bit like the @switch/regexp command */
448
449  int j, per;
450  char mstr[BUFFER_LEN], pstr[BUFFER_LEN], *dp;
451  char const *sp;
452  char *tbuf1;
453  int first = 1, found = 0, cs = 1;
454
455  if (strstr(called_as, "ALL"))
456    first = 0;
457
458  if (strcmp(called_as, "RESWITCHI") == 0
459      || strcmp(called_as, "RESWITCHALLI") == 0)
460    cs = 0;
461
462  dp = mstr;
463  sp = args[0];
464  process_expression(mstr, &dp, &sp, executor, caller, enactor,
465                     PE_DEFAULT, PT_DEFAULT, pe_info);
466  *dp = '\0';
467
468  /* try matching, return match immediately when found */
469
470  for (j = 1; j < (nargs - 1); j += 2) {
471    dp = pstr;
472    sp = args[j];
473    process_expression(pstr, &dp, &sp, executor, caller, enactor,
474                       PE_DEFAULT, PT_DEFAULT, pe_info);
475    *dp = '\0';
476
477    if (quick_regexp_match(pstr, mstr, cs)) {
478      /* If there's a #$ in a switch's action-part, replace it with
479       * the value of the conditional (mstr) before evaluating it.
480       */
481      tbuf1 = replace_string("#$", mstr, args[j + 1]);
482
483      sp = tbuf1;
484
485      per = process_expression(buff, bp, &sp,
486                               executor, caller, enactor,
487                               PE_DEFAULT, PT_DEFAULT, pe_info);
488      mush_free((Malloc_t) tbuf1, "replace_string.buff");
489      found = 1;
490      if (per || first)
491        return;
492    }
493  }
494
495  if (!(nargs & 1) && !found) {
496    /* Default case */
497    tbuf1 = replace_string("#$", mstr, args[nargs - 1]);
498    sp = tbuf1;
499    process_expression(buff, bp, &sp, executor, caller, enactor,
500                       PE_DEFAULT, PT_DEFAULT, pe_info);
501    mush_free((Malloc_t) tbuf1, "replace_string.buff");
502  }
503}
504
505/* ARGSUSED */
506FUNCTION(fun_if)
507{
508  char tbuf[BUFFER_LEN], *tp;
509  char const *sp;
510
511  tp = tbuf;
512  sp = args[0];
513  process_expression(tbuf, &tp, &sp, executor, caller, enactor,
514                     PE_DEFAULT, PT_DEFAULT, pe_info);
515  *tp = '\0';
516  if (parse_boolean(tbuf)) {
517    sp = args[1];
518    process_expression(buff, bp, &sp, executor, caller, enactor,
519                       PE_DEFAULT, PT_DEFAULT, pe_info);
520  } else if (nargs > 2) {
521    sp = args[2];
522    process_expression(buff, bp, &sp, executor, caller, enactor,
523                       PE_DEFAULT, PT_DEFAULT, pe_info);
524  }
525}
526
527/* ARGSUSED */
528FUNCTION(fun_mudname)
529{
530  safe_str(MUDNAME, buff, bp);
531}
532
533/* ARGSUSED */
534FUNCTION(fun_version)
535{
536  safe_format(buff, bp, "PennMUSH version %s patchlevel %s %s",
537              VERSION, PATCHLEVEL, PATCHDATE);
538}
539
540/* ARGSUSED */
541FUNCTION(fun_numversion)
542{
543  safe_integer(NUMVERSION, buff, bp);
544}
545
546/* ARGSUSED */
547FUNCTION(fun_starttime)
548{
549  safe_str(show_time(globals.first_start_time, 0), buff, bp);
550}
551
552/* ARGSUSED */
553FUNCTION(fun_restarttime)
554{
555  safe_str(show_time(globals.start_time, 0), buff, bp);
556}
557
558/* ARGSUSED */
559FUNCTION(fun_restarts)
560{
561  safe_integer(globals.reboot_count, buff, bp);
562}
563
564/* Data for soundex functions */
565static char soundex_val[26] = {
566  0, 1, 2, 3, 0, 1, 2, 0, 0,
567  2, 2, 4, 5, 5, 0, 1, 2, 6,
568  2, 3, 0, 1, 0, 2, 0, 2
569};
570
571/* The actual soundex routine */
572static char *
573soundex(char *str)
574{
575  static char tbuf1[BUFFER_LEN];
576  char *p, *q;
577
578  tbuf1[0] = '\0';
579  tbuf1[1] = '\0';
580  tbuf1[2] = '\0';
581  tbuf1[3] = '\0';
582
583  p = tbuf1;
584  q = upcasestr(remove_markup(str, NULL));
585  /* First character is just copied */
586  *p = *q++;
587  /* Special case for PH->F */
588  if ((*p == 'P') && *q && (*q == 'H')) {
589    *p = 'F';
590    q++;
591  }
592  p++;
593  /* Convert letters to soundex values, squash duplicates */
594  while (*q) {
595    if (!isalpha((unsigned char) *q) || (unsigned char) *q > 127) {
596      q++;
597      continue;
598    }
599    *p = soundex_val[*q++ - 'A'] + '0';
600    if (*p != *(p - 1))
601      p++;
602  }
603  *p = '\0';
604  /* Remove zeros */
605  p = q = tbuf1;
606  while (*q) {
607    if (*q != '0')
608      *p++ = *q;
609    q++;
610  }
611  *p = '\0';
612  /* Pad/truncate to 4 chars */
613  if (tbuf1[1] == '\0')
614    tbuf1[1] = '0';
615  if (tbuf1[2] == '\0')
616    tbuf1[2] = '0';
617  if (tbuf1[3] == '\0')
618    tbuf1[3] = '0';
619  tbuf1[4] = '\0';
620  return tbuf1;
621}
622
623/* ARGSUSED */
624FUNCTION(fun_soundex)
625{
626  /* Returns the soundex code for a word. This 4-letter code is:
627   * 1. The first letter of the word (exception: ph -> f)
628   * 2. Replace each letter with a numeric code from the soundex table
629   * 3. Remove consecutive numbers that are the same
630   * 4. Remove 0's
631   * 5. Truncate to 4 characters or pad with 0's.
632   * It's actually a bit messier than that to make it faster.
633   */
634  if (!args[0] || !*args[0] || !isalpha((unsigned char) *args[0])
635      || strchr(args[0], ' ')) {
636    safe_str(T("#-1 FUNCTION (SOUNDEX) REQUIRES A SINGLE WORD ARGUMENT"), buff,
637             bp);
638    return;
639  }
640  safe_str(soundex(args[0]), buff, bp);
641  return;
642}
643
644/* ARGSUSED */
645FUNCTION(fun_soundlike)
646{
647  /* Return 1 if the two arguments have the same soundex.
648   * This can be optimized to go character-by-character, but
649   * I deem the modularity to be more important. So there.
650   */
651  char tbuf1[5];
652  if (!*args[0] || !*args[1] || !isalpha((unsigned char) *args[0])
653      || !isalpha((unsigned char) *args[1]) || strchr(args[0], ' ')
654      || strchr(args[1], ' ')) {
655    safe_str(T("#-1 FUNCTION (SOUNDLIKE) REQUIRES TWO ONE-WORD ARGUMENTS"),
656             buff, bp);
657    return;
658  }
659  /* soundex uses a static buffer, so we need to save it */
660  strcpy(tbuf1, soundex(args[0]));
661  safe_boolean(!strcmp(tbuf1, soundex(args[1])), buff, bp);
662}
663
664/* ARGSUSED */
665FUNCTION(fun_functions)
666{
667  safe_str(list_functions(), buff, bp);
668}
669
670/* ARGSUSED */
671FUNCTION(fun_null)
672{
673  return;
674}
675
676/* ARGSUSED */
677FUNCTION(fun_atat)
678{
679  return;
680}
681
682/* ARGSUSED */
683FUNCTION(fun_list)
684{
685  if (!args[0] || !*args[0])
686    safe_str("#-1", buff, bp);
687  else if (string_prefix("motd", args[0]))
688    safe_str(cf_motd_msg, buff, bp);
689  else if (string_prefix("wizmotd", args[0]) && Hasprivs(executor))
690    safe_str(cf_wizmotd_msg, buff, bp);
691  else if (string_prefix("downmotd", args[0]) && Hasprivs(executor))
692    safe_str(cf_downmotd_msg, buff, bp);
693  else if (string_prefix("fullmotd", args[0]) && Hasprivs(executor))
694    safe_str(cf_fullmotd_msg, buff, bp);
695  else if (string_prefix("functions", args[0]))
696    safe_str(list_functions(), buff, bp);
697  else if (string_prefix("commands", args[0]))
698    safe_str(list_commands(), buff, bp);
699  else if (string_prefix("attribs", args[0]))
700    safe_str(list_attribs(), buff, bp);
701  else if (string_prefix("locks", args[0]))
702    list_locks(buff, bp, NULL);
703  else if (string_prefix("flags", args[0]))
704    safe_str(list_all_flags("FLAG", "", executor, 0x3), buff, bp);
705  else if (string_prefix("powers", args[0]))
706    safe_str(list_all_flags("POWER", "", executor, 0x3), buff, bp);
707  else
708    safe_str("#-1", buff, bp);
709  return;
710}
711
712/* ARGSUSED */
713FUNCTION(fun_scan)
714{
715  dbref thing;
716  char save_ccom[BUFFER_LEN];
717  char *cmdptr;
718  if (nargs == 1) {
719    thing = executor;
720    cmdptr = args[0];
721  } else {
722    thing = match_thing(executor, args[0]);
723    if (!GoodObject(thing)) {
724      safe_str(T(e_notvis), buff, bp);
725      return;
726    }
727    if (!See_All(executor) && !controls(executor, thing)) {
728      notify(executor, T("Permission denied."));
729      safe_str("#-1", buff, bp);
730      return;
731    }
732    cmdptr = args[1];
733  }
734  strcpy(save_ccom, global_eval_context.ccom);
735  strncpy(global_eval_context.ccom, cmdptr, BUFFER_LEN);
736  global_eval_context.ccom[BUFFER_LEN - 1] = '\0';
737  safe_str(scan_list(thing, cmdptr), buff, bp);
738  strcpy(global_eval_context.ccom, save_ccom);
739}
740
741
742enum whichof_t { DO_FIRSTOF, DO_ALLOF };
743static void
744do_whichof(