Logo Search packages:      
Sourcecode: vile version File versions

eval.c

/*
 *    eval.c -- function and variable evaluation
 *    original by Daniel Lawrence
 *
 * $Header: /usr/build/vile/vile/RCS/eval.c,v 1.322 2003/05/25 23:34:52 tom Exp $
 *
 */

#include    "estruct.h"
#include    "edef.h"
#include    "nevars.h"
#include    "nefsms.h"

#include    <ctype.h>

#define     ILLEGAL_NUM -1

#if OPT_EVAL

#if OPT_FILTER
#include    <filters.h>
#endif

/* "generic" variable wrapper, to insulate some users of variables
 * from needing to distinguish the different types */
typedef struct {
    int v_type;               /* type: values below */
    int v_num;                /* index, for state vars */
    UVAR *v_ptr;        /* pointer, for temp vars */
} VWRAP;

/* values for v_type */
#define VW_NOVAR  0
#define VW_STATEVAR     1
#define VW_TEMPVAR      2
#define VW_MODE         3

typedef struct PROC_ARGS {
    struct PROC_ARGS *nxt_args;
    int num_args;       /* total argument count */
    TBUFF **all_args;
} PROC_ARGS;

static PROC_ARGS *arg_stack;

static size_t s2size(const char *s);
static char **init_vars_cmpl(void);
static char *get_statevar_val(int vnum);
static const char *s2offset(const char *s, const char *n);
static int SetVarValue(VWRAP * var, const char *name, const char *value);
static int lookup_statevar(const char *vname);
static void FindVar(char *var, VWRAP * vd);
static void free_vars_cmpl(void);
#endif

/*--------------------------------------------------------------------------*/
#if OPT_SHOW_CTYPE

/* list the current character-classes into the current buffer */
/* ARGSUSED */
static void
makectypelist(int dum1 GCC_UNUSED, void *ptr GCC_UNUSED)
{
    UINT i, j;
    CHARTYPE k;
    const char *s;

    bprintf("--- Printable Characters %*P\n", term.cols - 1, '-');
    for (i = 0; i < N_chars; i++) {
      bprintf("\n%d\t", i);
      if ((i == '\n') || (i == '\t'))           /* vtlistc() may not do these */
          bprintf("^%c", '@' | i);
#if OPT_LOCALE
      else if (!isPrint(i) && i > 127 && i < 160)     /* C1 controls? */
          bprintf(
                   global_w_val(WMDNONPRINTOCTAL)
                   ? "\\%3o"
                   : "\\x%2x",
                   i);
#endif
      else
          bprintf("%c", i);
      bputc('\t');
      for (j = 0; j != vl_UNUSED; j++) {
          if ((s = choice_to_name(fsm_charclass_choices, j)) != 0) {
            k = (1 << j);
            if (j != 0)
                bputc(' ');
            bprintf("%*s",
                  strlen(s),
                  (vl_chartypes_[i] & k)
                  ? s
                  : "-");
          }
      }
    }
}

/* ARGSUSED */
static int
show_CharClasses(BUFFER *bp GCC_UNUSED)
{
    return liststuff(PRINTABLECHARS_BufName,
                 FALSE, makectypelist, 0, (void *) 0);
}

#if OPT_UPBUFF
static void
update_char_classes(void)
{
    update_scratch(PRINTABLECHARS_BufName, show_CharClasses);
}

#else
#define update_char_classes() /*nothing */
#endif

/* ARGSUSED */
int
desprint(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return show_CharClasses(curbp);
}

static int
cclass_complete(DONE_ARGS)
{
    return kbd_complete(PASS_DONE_ARGS,
                  (const char *) fsm_charclass_choices,
                  sizeof(fsm_charclass_choices[0]));
}

/*
 * Using name-completion, we can only accept one name at a time - so the
 * set/unset operations do not take a list.
 */
static int
get_charclass_code(void)
{
    static TBUFF *var;
    int status;

    if (var == 0)
      tb_scopy(&var, "");
    status = kbd_reply("Character class: ", &var,
                   eol_history, '=',
                   KBD_NOEVAL | KBD_LOWERC,
                   cclass_complete);
    return (status == TRUE)
      ? choice_to_code(
                      fsm_charclass_choices,
                      tb_values(var),
                      tb_length(var))
      : -1;
}

/*
 * Prompt for a regular expression that we can use to match single characters.
 * Actually, regexp won't really tell us that much - so we'll get an expression
 * and try it.
 */
static REGEXVAL *
get_charclass_regexp(void)
{
    static TBUFF *var;
    int status;

    if (var == 0)
      tb_scopy(&var, "");
    status = kbd_reply("Character pattern: ", &var,
                   eol_history, '=', KBD_NOEVAL, no_completion);
    return (status == TRUE)
      ? new_regexval(tb_values(var), TRUE)
      : 0;
}

static int
match_charclass_regexp(int ch, REGEXVAL * exp)
{
    char temp[2];
    temp[0] = (char) ch;

    return regexec(exp->reg, temp, temp + 1, 0, 0);
}

static int
update_charclasses(char *tag, int count)
{
    if (count) {
      mlwrite("[%s %d character%s]", tag, count, PLURAL(count));
      update_char_classes();
    } else {
      mlwrite("[%s no characters]", tag);
    }
    return TRUE;
}

/* ARGSUSED */
int
set_charclass(int f GCC_UNUSED, int n GCC_UNUSED)
{
    int ch;
    int code;
    int count;
    REGEXVAL *exp;

    if ((code = get_charclass_code()) > 0) {
      code = (1 << code);
      if ((exp = get_charclass_regexp()) != 0) {
          for (count = 0, ch = 0; ch < N_chars; ch++) {
            if (!istype(code, ch)
                && match_charclass_regexp(ch, exp)) {
                vl_chartypes_[ch] |= code;
                count++;
            }
          }
          free_regexval(exp);
          return update_charclasses("Set", count);
      }
    }
    return FALSE;
}

/* ARGSUSED */
int
unset_charclass(int f GCC_UNUSED, int n GCC_UNUSED)
{
    int ch;
    int code;
    int count;
    REGEXVAL *exp;

    if ((code = get_charclass_code()) > 0) {
      code = (1 << code);
      if ((exp = get_charclass_regexp()) != 0) {
          for (count = 0, ch = 0; ch < N_chars; ch++) {
            if (istype(code, ch)
                && match_charclass_regexp(ch, exp)) {
                vl_chartypes_[ch] &= ~code;
                count++;
            }
          }
          free_regexval(exp);
          return update_charclasses("Unset", count);
      }
    }
    return FALSE;
}

/* ARGSUSED */
int
reset_charclasses(int f GCC_UNUSED, int n GCC_UNUSED)
{
    charinit();
    update_char_classes();
    return TRUE;
}

#endif /* OPT_SHOW_CTYPE */

#if OPT_EVAL
/*
 * Compute the intersection of the character classes in the argument.
 */
static CHARTYPE
charclass_of(const char *arg)
{
    CHARTYPE k;
    if (arg == error_val) {
      k = 0;
    } else {
      k = vl_chartypes_[char2int(*arg)];
      if (*arg) {
          while (*++arg) {
            k &= vl_chartypes_[char2int(*arg)];
          }
      }
    }
    return k;
}

/*
 * Make display of the intersection of the character classes in the argument.
 */
static void
show_charclass(TBUFF ** result, const char *arg)
{
    CHARTYPE k = charclass_of(arg);
#if OPT_SHOW_CTYPE
    UINT j;
    const char *s;

    for (j = 0; j != vl_UNUSED; j++) {
      if (((1 << j) & k) != 0
          && (s = choice_to_name(fsm_charclass_choices, j)) != 0) {
          if (tb_length(*result))
            tb_sappend0(result, "+");
          tb_sappend0(result, s);
      }
    }
#else /* well, show something, so we can compare against it */
    lsprintf(tb_values(*result), "%X", k);
#endif
}
#endif

/*--------------------------------------------------------------------------*/

#if OPT_SHOW_EVAL
/* list the current vars into the current buffer */
/* ARGSUSED */
static void
makevarslist(int dum1 GCC_UNUSED, void *ptr)
{
    UVAR *p;
    int j, k;

    bprintf("--- State variables %*P\n", term.cols - 1, '-');
    bprintf("%s", (char *) ptr);
    if (arg_stack != 0 && ((k = arg_stack->num_args) != 0)) {
      bprintf("--- %s parameters %*P",
            tb_values(arg_stack->all_args[0]),
            term.cols - 1, '-');
      for (j = 1; j <= k; j++) {
          bprintf("\n$%d = %s", j,
                tb_values(arg_stack->all_args[j]));
      }
    }
    if (temp_vars != 0)
      bputc('\n');
    for (p = temp_vars, j = 0; p != 0; p = p->next) {
      if (!j++)
          bprintf("--- Temporary variables %*P", term.cols - 1, '-');
      bprintf("\n%%%s = %s", p->u_name, p->u_value);
    }
}

/* Test a name to ensure that it is a mode-name, filtering out modish-stuff
 * such as "all" and "noview"
 */
static int
is_mode_name(const char *name, int showall, VALARGS * args)
{
    if (is_varmode(name)) {
      if (find_mode(curbp, name, -TRUE, args)) {
          if (!showall && !strcmp(name, args->names->shortname))
            return FALSE;
          return TRUE;
      }
      return SORTOFTRUE;
    }
    return FALSE;
}

/* filter out mode-names that apply only to abbreviations */
static const char *
get_listvalue(const char *name, int showall)
{
    VALARGS args;
    register int s;
    int vnum;

    if ((s = is_mode_name(name, showall, &args)) == TRUE)
      return string_mode_val(&args);
    else if (s == SORTOFTRUE) {
      vnum = lookup_statevar(name);
      if (vnum != ILLEGAL_NUM)
          return get_statevar_val(vnum);
    }
    return 0;
}
#endif /* OPT_SHOW_EVAL */

#if OPT_SHOW_EVAL

static int show_vars_f;
static int show_vars_n;

/* ARGSUSED */
static int
show_VariableList(BUFFER *bp GCC_UNUSED)
{
    char *values;
    register WINDOW *wp = curwp;
    register int s;
    register size_t t;
    register char *v;
    register const char *vv;
    static const char fmt[] =
    {"$%s = %*S\n"};
    const char *const *Names;
    int showall;
    int rc = FALSE;

    TRACE((T_CALLED "show_VariableList()\n"));

    Names = show_vars_f ? list_of_modes() : statevars;
    showall = show_vars_f ? (show_vars_n > 1) : FALSE;
    /* collect data for state-variables, since some depend on window */
    for (s = t = 0; Names[s] != 0; s++) {
      if ((vv = get_listvalue(Names[s], showall)) != 0)
          t += strlen(Names[s]) + strlen(fmt) + strlen(vv);
    }

    beginDisplay();
    if ((values = typeallocn(char, t)) != 0) {
      for (s = 0, v = values; Names[s] != 0; s++) {
          if ((vv = get_listvalue(Names[s], showall)) != 0) {
            t = strlen(vv);
            if (t == 0) {
                t = 1;
                vv = "";
            } else if (vv[t - 1] == '\n')
                t--;    /* suppress trailing newline */
            v = lsprintf(v, fmt, Names[s], t, vv);
          }
      }
      rc = liststuff(VARIABLES_BufName, FALSE,
                   makevarslist, 0, (void *) values);
      free(values);

      /* back to the buffer whose modes we just listed */
      swbuffer(wp->w_bufp);
    }
    endofDisplay();
    returnCode(rc);
}

#if OPT_UPBUFF
/*
 * If the list-variables window is visible, update it after operations that
 * would modify the list.
 */
void
updatelistvariables(void)
{
    update_scratch(VARIABLES_BufName, show_VariableList);
}
#endif

int
listvars(int f, int n)
{
    show_vars_f = f;
    show_vars_n = n;
    return show_VariableList(curbp);
}
#endif /* OPT_SHOW_EVAL */

#if OPT_EVAL

/*
 * Find a function in the function list.  The table is all lowercase, so we can
 * compare ignoring case.  Check for any unique abbreviation of the table's
 * names.
 */
int
vl_lookup_func(const char *name)
{
    char downcased[NSTRING];
    int fnum = ILLEGAL_NUM;
    int n;
    unsigned m;

    if (name[0] == '&'
      && name[1] != EOS) {

      mklower(vl_strncpy(downcased, name + 1, sizeof(downcased)));

      m = strlen(downcased);
      for (n = 0; n < NFUNCS; n++) {
          if (!strncmp(downcased, vl_ufuncs[n].f_name, m)) {
            if ((n + 1 >= NFUNCS
                 || strncmp(downcased, vl_ufuncs[n + 1].f_name, m)))
                fnum = n;
            break;
          }
      }
    }
    TRACE(("vl_lookup_func(%s) = %d\n", TRACE_NULL(name), fnum));
    return (fnum);
}

/*
 * Find the given token, returning true if it is found in the string.
 */
static int
search_token(const char *token, const char *delims, const char *string)
{
    size_t length = strlen(token);

    while (*string != EOS) {
      const char *first = string;
      string += strcspn(first, delims);
      if (string - first == (int) length
          && !strncmp(first, token, length))
          return TRUE;
      string += strspn(string, delims);
    }
    return FALSE;
}

/*
 * Find the count'th token, counting from zero.  This will return an empty
 * result only if we run past the end of the number of tokens.
 */
static void
extract_token(TBUFF ** result, const char *count, const char *delims, const char *string)
{
    int num = scan_int(count);

    while (num-- > 0) {
      string += strspn(string, delims);
      string += strcspn(string, delims);
    }

    string += strspn(string, delims);
    tb_init(result, 0);
    tb_bappend(result, string, strcspn(string, delims));
    tb_append(result, EOS);
}

/*
 * Translate string 'from' 'to'.
 */
static void
translate_string(TBUFF ** result, const char *from, const char *to, const char *string)
{
    int len_to = strlen(to);
    char *s, *t;

    if (tb_scopy(result, string) != 0) {
      for (s = tb_values(*result); *s != EOS; ++s) {
          if ((t = strchr(from, *s)) != 0
            && (t - from) < len_to) {
            *s = to[t - from];
          }
      }
    }
}

static void
path_trim(char *path)
{
#if SYS_UNIX
    while ((int) strlen(path) > 1) {
      char *last = path + strlen(path) - 1;
      if (is_slashc(*last))
          *last = EOS;
      else
          break;
    }
#else
#if OPT_MSDOS_PATH
    while (strlen(path) > 2) {
      char *last = path + strlen(path) - 1;
      if (is_slashc(*last)
          && last[-1] != ':')
          *last = EOS;
      else
          break;
    }
#endif
#endif
}

/*
 * Extract the head of the given path
 */
static char *
path_head(TBUFF ** result, const char *path)
{
    char *temp;
    if ((temp = tb_values(tb_scopy(result, path))) != error_val) {
      *pathleaf(temp) = EOS;
      path_trim(temp);
    }
    return temp;
}

/*
 * If the given argument contains non-path characters, quote it.  Otherwise
 * simply copy it.
 */
static void
path_quote(TBUFF ** result, const char *path)
{
#if SYS_VMS
    tb_scopy(result, path);   /* no point in quoting here */
#else
    CHARTYPE have = charclass_of(path);
    CHARTYPE want = (vl_pathn | vl_nonspace);

    if ((want & have) != want) {
      tb_sappend(result, "\"");
#if SYS_UNIX
#define QUOTE_UNIX "\"\\$"
      if (strpbrk(path, QUOTE_UNIX)) {
          while (*path) {
            if (strchr(QUOTE_UNIX, *path))
                tb_append(result, BACKSLASH);
            tb_append(result, *path++);
          }
      } else
#endif
          tb_sappend(result, path);
      tb_sappend0(result, "\"");
    } else {
      tb_scopy(result, path);
    }
#endif
}

static void
render_date(TBUFF ** result, char *format, time_t stamp)
{
#ifdef HAVE_STRFTIME
    struct tm *tm = localtime(&stamp);
    char temp[NSTRING];

    if (strftime(temp, sizeof(temp) - 1, format, tm) != 0)
      tb_scopy(result, temp);
    else
#endif
    {
      /* FIXME (2001/5/20) I'd write a q/d fallback for strftime() using
       * struct tm, but don't want to get sidetracked with its portability
       * -TD
       */
      *result = tb_string(ctime(&stamp));
      tb_unput(*result);      /* trim the newline */
      tb_append(result, EOS);
    }
}

static void
default_mode_value(TBUFF ** result, char *name)
{
    VALARGS args;
    VWRAP vd;
    int mode_class, mode_name;
    struct VAL mode;

    FindVar(name, &vd);
    switch (vd.v_type) {
    case VW_STATEVAR:
      tb_scopy(result, init_state_value(vd.v_num));
      break;
    case VW_MODE:
      for (mode_class = UNI_MODE; mode_class <= WIN_MODE; mode_class++) {
          switch (mode_class) {
          default:
          case UNI_MODE:      /* universal modes */
            args.names = g_valnames;
            break;
          case BUF_MODE:      /* buffer modes */
            args.names = b_valnames;
            break;
          case WIN_MODE:      /* window modes */
            args.names = w_valnames;
            break;
          }
          if ((mode_name = lookup_valnames(name, args.names)) >= 0) {
            init_mode_value(&mode, (MODECLASS) mode_class, mode_name);
            args.global = &mode;
            args.local = args.global;
            tb_scopy(result, string_mode_val(&args));
            break;
          }
      }
      break;
    default:
      tb_init(result, EOS);   /* FIXME: handle %name and &ind name */
    }
}

#define MAXARGS 3

/* quote string, so toktyp() will know what it is */
static void
render_string(TBUFF ** rp)
{
    char *value = tb_values(*rp);

    if (value != error_val
      && *value != EOS
      && toktyp(value) != TOK_LITSTR
      && toktyp(value) != TOK_QUOTSTR) {
      UINT j;
      UINT have = strlen(value);    /* FIXME: assumes we used EOS */
      UINT need = 2 + have;

      for (j = 0; j < have; ++j) {
          if (value[j] == SQUOTE
            || value[j] == BACKSLASH) {
            ++need;
          }
      }
      tb_alloc(rp, need + 1);
      (*rp)->tb_used = need + 1;
#if 0
      /*
       * FIXME:  We _should_ quote the result just as if it had come from a
       * file.  But some inputs come from a prompt, which does not (yet)
       * quote its input.  Taking out the check for empty string above, the
       * following loop would quote the string "properly".  But returning ''
       * would break for various reasons.
       */
      value[need] = EOS;
      value[need - 1] = SQUOTE;
      for (j = 0; j < have; ++j) {
          UINT i = have - j - 1;
          UINT k = need - j - 2;
          value[k] = value[i];
          if (value[k] == SQUOTE
            || value[k] == BACKSLASH) {
            --need;
            value[k - 1] = BACKSLASH;
          }
      }
#else
      /*
       * What we actually do is simpler: put a quote character at the front
       * of the data so it will be treated as a string.
       */
      value[have + 1] = EOS;
      for (j = 0; j < have; ++j) {
          UINT i = have - j;
          value[i] = value[i - 1];
      }
#endif
      value[0] = SQUOTE;
    }
}

/*
 * execute a builtin function
 */
static char *
run_func(int fnum)
{
    static TBUFF *result;     /* function result */

    BUFFER *bp;
    TBUFF *args[MAXARGS];
    char *arg[MAXARGS];       /* function arguments */
    char *cp;
    const char *sp;
    int args_numeric, args_boolean, ret_numeric, ret_boolean;
    int bools[MAXARGS];
    int i, nargs;
    int is_error = FALSE;
    long value = 0;
    long nums[MAXARGS];

    TRACE((T_CALLED "run_func(%d)\n", fnum));

    nargs = vl_ufuncs[fnum].f_code & NARGMASK;
    args_numeric = vl_ufuncs[fnum].f_code & NUM;
    args_boolean = vl_ufuncs[fnum].f_code & BOOL;

    ret_numeric = vl_ufuncs[fnum].f_code & NRET;
    ret_boolean = vl_ufuncs[fnum].f_code & BRET;

    TPRINTF(("** evaluate '%s' (0x%x), %d args\n",
           vl_ufuncs[fnum].f_name,
           vl_ufuncs[fnum].f_code,
           nargs));

    /* fetch required arguments */
    for (i = 0; i < nargs; i++) {
      args[i] = 0;
      if ((arg[i] = mac_tokval(&args[i])) == 0
          || (arg[i] == error_val)
          || (args[i]->tb_errs)) {
          arg[i] = error_val;
          is_error = TRUE;
      } else if (arg[i][0] == SQUOTE) {
          /* trim leading quote from recursive result from this function */
          for (cp = arg[i] + 1; (cp[-1] = cp[0]) != EOS; ++cp) {
            ;
          }
      }
      tb_free(&result); /* in case mac_tokval() called us */
      TPRINTF(("...arg[%d] = '%s'\n", i, arg[i]));
      if (args_numeric)
          nums[i] = scan_long(arg[i]);
      else if (args_boolean)
          bools[i] = scan_bool(arg[i]);
    }

    tb_init(&result, EOS);

    switch ((UFuncCode) fnum) {
    case UFADD:
      value = nums[0] + nums[1];
      break;
    case UFSUB:
      value = nums[0] - nums[1];
      break;
    case UFTIMES:
      value = nums[0] * nums[1];
      break;
    case UFDIV:
      if (nums[1] == 0)
          is_error = TRUE;
      if (!is_error)
          value = nums[0] / nums[1];
      break;
    case UFMOD:
      if (nums[1] <= 0)
          is_error = TRUE;
      if (!is_error)
          value = nums[0] % nums[1];
      break;
    case UFNEG:
      value = -nums[0];
      break;
    case UFCAT:
      tb_scopy(&result, arg[0]);
      tb_sappend0(&result, arg[1]);
      break;
    case UFCCLASS:
      show_charclass(&result, arg[0]);
      break;
    case UFDATE:
      render_date(&result, arg[0], nums[1]);
      break;
    case UFLEFT:
      tb_bappend(&result, arg[0], s2size(arg[1]));
      tb_append(&result, EOS);
      break;
    case UFRIGHT:
      tb_scopy(&result, s2offset(arg[0], arg[1]));
      break;
    case UFMID:
      tb_bappend(&result, s2offset(arg[0], arg[1]), s2size(arg[2]));
      tb_append(&result, EOS);
      break;
    case UFNOT:
      value = !bools[0];
      break;
    case UFEQUAL:
      value = (nums[0] == nums[1]);
      break;
    case UFERROR:
      is_error = FALSE;
      value = (arg[0] == error_val);
      break;
    case UFLT:                /* FALLTHRU */
    case UFLESS:
      value = (nums[0] < nums[1]);
      break;
    case UFLEQ:
      value = (nums[0] <= nums[1]);
      break;
    case UFNEQ:
      value = (nums[0] != nums[1]);
      break;
    case UFGT:                /* FALLTHRU */
    case UFGREATER:
      value = (nums[0] > nums[1]);
      break;
    case UFGEQ:
      value = (nums[0] >= nums[1]);
      break;
    case UFSNEQ:
      value = (strcmp(arg[0], arg[1]) != 0);
      break;
    case UFSEQUAL:
      value = (strcmp(arg[0], arg[1]) == 0);
      break;
    case UFSLT:         /* FALLTHRU */
    case UFSLESS:
      value = (strcmp(arg[0], arg[1]) < 0);
      break;
    case UFSLEQ:
      value = (strcmp(arg[0], arg[1]) <= 0);
      break;
    case UFSGT:         /* FALLTHRU */
    case UFSGREAT:
      value = (strcmp(arg[0], arg[1]) > 0);
      break;
    case UFSGEQ:
      value = (strcmp(arg[0], arg[1]) >= 0);
      break;
    case UFINDIRECT:
      if ((sp = tokval(arg[0])) != error_val)
          tb_scopy(&result, sp);
      else
          is_error = TRUE;
      break;
    case UFAND:
      value = (bools[0] && bools[1]);
      break;
    case UFOR:
      value = (bools[0] || bools[1]);
      break;
    case UFLENGTH:
      value = (long) strlen(arg[0]);
      break;
    case UFUPPER:
      mkupper(tb_values(tb_scopy(&result, arg[0])));
      break;
    case UFLOWER:
      mklower(tb_values(tb_scopy(&result, arg[0])));
      break;
    case UFTRIM:
      mktrimmed(tb_values(tb_scopy(&result, arg[0])));
      break;
    case UFASCII:
      value = (long) arg[0][0];
      break;
    case UFCHR:
      tb_append(&result, (char) nums[0]);
      tb_append(&result, EOS);
      break;
    case UFGTKEY:
      tb_append(&result, (char) keystroke_raw8());
      tb_append(&result, EOS);
      break;
    case UFGTSEQ:
      (void) kcod2escape_seq(kbd_seq_nomap(), tb_values(result));
      result->tb_used = strlen(tb_values(result));
      break;
    case UFRANDOM:            /* FALLTHRU */
    case UFRND:
      if (!is_error)
          value = rand() % absol(nums[0]);
      value++;          /* return 1 to N */
      break;
    case UFABS:
      value = absol(nums[0]);
      break;
    case UFSINDEX:
      value = ourstrstr(arg[0], arg[1], FALSE);
      break;
    case UFENV:
      if (!is_error)
          tb_scopy(&result, vile_getenv(arg[0]));
      break;
    case UFBIND:
      if (!is_error) {
          if ((sp = prc2engl(arg[0])) == error_val)
            is_error = TRUE;
          else
            tb_scopy(&result, sp);
      }
      break;
    case UFDEFAULT:
      if (!is_error)
          default_mode_value(&result, arg[0]);
      break;
    case UFREADABLE:          /* FALLTHRU */
    case UFRD:
      value = (!is_error &&
             doglob(arg[0]) &&
             cfg_locate(arg[0], FL_CDIR | FL_READABLE) != NULL);
      break;
    case UFWRITABLE:
      value = (!is_error &&
             doglob(arg[0]) &&
             cfg_locate(arg[0], FL_CDIR | FL_WRITEABLE) != NULL);
      break;
    case UFEXECABLE:
      value = (!is_error &&
             doglob(arg[0]) &&
             cfg_locate(arg[0], FL_CDIR | FL_EXECABLE) != NULL);
      break;
    case UFFILTER:
      value = FALSE;
#if OPT_FILTER
      if (flt_lookup(arg[0]))
          value = TRUE;
#endif
      break;
    case UFFTIME:
      if (!is_error
          && (bp = find_any_buffer(arg[0])) != 0
          && !is_internalname(bp->b_fname))
          value = file_modified(bp->b_fname);
      break;
    case UFLOCMODE:
    case UFGLOBMODE:
      if (!is_error) {
          VALARGS vargs;
          if (find_mode(curbp, arg[0],
                    (fnum == UFGLOBMODE), &vargs) != TRUE)
            break;
          tb_scopy(&result, string_mode_val(&vargs));
      }
      break;
    case UFDQUERY:
      if (!is_error) {
          if ((cp = user_reply(arg[0], arg[1])) != 0
            && (cp != error_val))
            tb_scopy(&result, cp);
          else
            is_error = TRUE;
      }
      break;
    case UFQPASSWD:
      if (!is_error) {
          qpasswd = TRUE;
          if ((cp = user_reply(arg[0], NULL)) != 0
            && (cp != error_val))
            tb_scopy(&result, cp);
          else
            is_error = TRUE;
          qpasswd = FALSE;
      }
      break;
    case UFQUERY:
      if (!is_error) {
          if ((cp = user_reply(arg[0], error_val)) != 0
            && (cp != error_val))
            tb_scopy(&result, cp);
          else
            is_error = TRUE;
      }
      break;
    case UFLOOKUP:
      if ((i = combine_choices(fsm_lookup_choices, arg[0])) > 0)
          tb_scopy(&result, SL_TO_BSL(cfg_locate(arg[1], i)));
      break;
    case UFPATH:
      if (!is_error) {
          switch (choice_to_code(fsm_path_choices, arg[0], strlen(arg[0]))) {
          case PATH_END:
            cp = pathleaf(arg[1]);
            if ((cp = strchr(cp, '.')) != 0)
                tb_scopy(&result, SL_TO_BSL(cp));
            break;
          case PATH_FULL:
            tb_scopy(&result, arg[1]);
            tb_alloc(&result, NFILEN);
            SL_TO_BSL(lengthen_path(tb_values(result)));
            break;
          case PATH_HEAD:
            SL_TO_BSL(path_head(&result, arg[1]));
            break;
          case PATH_ROOT:
            tb_scopy(&result, SL_TO_BSL(pathleaf(arg[1])));
            if ((cp = strchr(tb_values(result), '.')) != 0)
                *cp = EOS;
            break;
          case PATH_SHORT:
            tb_scopy(&result, arg[1]);
            tb_alloc(&result, NFILEN);
            SL_TO_BSL(shorten_path(tb_values(result), FALSE));
            break;
          case PATH_TAIL:
            tb_scopy(&result, pathleaf(arg[1]));
            break;
          default:
            is_error = TRUE;
            break;
          }
      }
      break;
    case UFPATHCAT:
      tb_alloc(&result, NFILEN);
      SL_TO_BSL(pathcat(tb_values(result), arg[0], arg[1]));
      break;
    case UFPATHQUOTE:
      path_quote(&result, SL_TO_BSL(arg[0]));
      break;
    case UFSTIME:
      value = time((time_t *) 0);
      break;
    case UFSTOKEN:
      value = search_token(arg[0], arg[1], arg[2]);
      break;
    case UFTOKEN:
      extract_token(&result, arg[0], arg[1], arg[2]);
      break;
    case UFTRANS:
      translate_string(&result, arg[0], arg[1], arg[2]);
      break;
    case UFWORD:
      extract_token(&result, arg[0], "\t ", arg[1]);
      break;
    case UFREGISTER:
      if (!is_error) {
          i = reg2index(*arg[0]);
          if ((i = index2ukb(i)) < NKREGS
            && i >= 0) {
            KILL *kp = kbs[i].kbufh;
            while (kp != 0) {
                tb_bappend(&result,
                         (char *) (kp->d_chunk),
                         KbSize(i, kp));
                kp = kp->d_next;
            }
            tb_append(&result, EOS);
            break;
          }
      }
      /* FALLTHRU */
    case NFUNCS:
      TRACE(("unknown function #%d\n", fnum));
      is_error = TRUE;
      break;
    }

    if (ret_numeric)
      render_long(&result, value);
    else if (ret_boolean)
      render_boolean(&result, value);
    else
      render_string(&result);

    TPRINTF(("-> %s'%s'\n",
           is_error ? "*" : "",
           is_error ? error_val : tb_values(result)));

    for (i = 0; i < nargs; i++) {
      tb_free(&args[i]);
    }
    returnString(is_error ? error_val : tb_values(result));
}

/* find a temp variable */
static UVAR *
lookup_tempvar(const char *name)
{
    register UVAR *p;

    if (*name) {
      for (p = temp_vars; p != 0; p = p->next)
          if (!vl_stricmp(name, p->u_name))
            return p;
    }
    return NULL;
}

/* find a state variable */
static int
lookup_statevar(const char *name)
{
    register int vnum;        /* ordinal number of var referenced */
    if (!*name)
      return ILLEGAL_NUM;

    /* scan the list, looking for the referenced name */
    for (vnum = 0; statevars[vnum] != 0; vnum++)
      if (strcmp(name, statevars[vnum]) == 0)
          return vnum;

    return ILLEGAL_NUM;
}

/* get a state variable's value */
static char *
get_statevar_val(int vnum)
{
    static TBUFF *result;
    int s;

    if (vnum == ILLEGAL_NUM)
      return error_val;

    tb_init(&result, EOS);
    s = (*statevar_func[vnum]) (&result, (const char *) NULL);

    if (s == TRUE) {
      tb_append(&result, EOS);      /* trailing null, just in case... */
      return tb_values(result);
    } else {
      return error_val;
    }
}

/*
 * Find a variable's type and name
 */
static void
FindVar(char *var, VWRAP * vd)
{
    UVAR *p;

    vd->v_type = VW_NOVAR;
    vd->v_num = ILLEGAL_NUM;
    vd->v_ptr = 0;

    if (!var[1]) {
      vd->v_type = VW_NOVAR;
      return;
    }

    switch (var[0]) {
    case '$':                 /* check for legal state var */
      vd->v_num = lookup_statevar(var + 1);
      if (vd->v_num != ILLEGAL_NUM)
          vd->v_type = VW_STATEVAR;
#if !SMALLER
      else {
          VALARGS args;
          if (is_mode_name(&var[1], TRUE, &args) == TRUE) {
            vd->v_type = VW_MODE;
          }
      }
#endif
      break;

    case '%':                 /* check for legal temp variable */
      vd->v_ptr = lookup_tempvar(var + 1);
      if (vd->v_ptr) {  /* existing */
          vd->v_type = VW_TEMPVAR;
      } else {          /* new */
          beginDisplay();
          p = typealloc(UVAR);
          if (p &&
            (p->u_name = strmalloc(var + 1)) != 0) {
            p->next = temp_vars;
            p->u_value = 0;
            temp_vars = vd->v_ptr = p;
            vd->v_type = VW_TEMPVAR;
            free_vars_cmpl();
          }
          endofDisplay();
      }
      break;

    case '&':                 /* indirect operator? */
      if (vl_lookup_func(var) == UFINDIRECT) {
          TBUFF *tok = 0;
          /* grab token, and eval it */
          execstr = get_token(execstr, &tok, EOS, (int *) 0);
          (void) vl_strncpy(var, tokval(tb_values(tok)), NLINE);
          FindVar(var, vd);   /* recursive, but probably safe */
          tb_free(&tok);
      }
      break;
    }

}

/*
 * Handles name-completion for variables.  Note that since the first character
 * can be either a '$' or '%', we'll postpone name-completion until we've seen
 * a '$'.
 */
static int
vars_complete(DONE_ARGS)
{
    int status;

    if (buf[0] == '$') {
      *pos -= 1;        /* account for leading '$', not in tables */
      status = kbd_complete(flags, c, buf + 1, pos,
                        (const char *) list_of_modes(), sizeof(char *));
      *pos += 1;
    } else if (buf[0] == '%') {
      *pos -= 1;        /* account for leading '%', not in tables */
      status = kbd_complete(KBD_MAYBEC, c, buf + 1, pos,
                        (const char *) init_vars_cmpl(), sizeof(char *));
      *pos += 1;
    } else {
      if (c != NAMEC)         /* cancel the unget */
          unkeystroke(c);
      status = TRUE;
    }
    return status;
}

static int
PromptForVariableName(TBUFF ** result)
{
    int status;
    static TBUFF *var;

    /* get the variable we want to set */
    if (var == 0)
      tb_scopy(&var, "");
    status = kbd_reply("Variable name: ", &var,
                   mode_eol, '=',
                   KBD_MAYBEC2 | KBD_NOEVAL | KBD_LOWERC,
                   vars_complete);
    *result = var;
    TRACE(("PromptForVariableName returns %s (%d)\n", tb_visible(*result), status));
    return status;
}

static int
PromptAndSet(const char *name, int f, int n)
{
    int status;
    VWRAP vd;
    char var[NLINE];
    char prompt[NLINE];

    TRACE((T_CALLED "PromptAndSet(%s, %d, %d)\n", name, f, n));

    /* look it up -- vd will describe the variable */
    FindVar(vl_strncpy(var, name, sizeof(var)), &vd);

    if (vd.v_type == VW_NOVAR) {
      mlforce("[Can't find variable '%s']", var);
      status = FALSE;
    } else if (vd.v_type == VW_MODE) {
      VALARGS args;
      (void) find_mode(curbp, var + 1, -TRUE, &args);
      set_end_string('=');
      status = adjvalueset(var + 1, FALSE, TRUE, -TRUE, &args);
    } else {
      static TBUFF *tmp;

      tb_init(&tmp, EOS);
      if (f == TRUE) {  /* new (numeric) value passed as arg */
          render_int(&tmp, n);
      } else {          /* get it from user */
          if (vd.v_type == VW_STATEVAR) {
            /* FIXME: this really should allow embedded nulls */
            tb_scopy(&tmp, get_statevar_val(vd.v_num));
          } else {
            /* must be temp var */
            tb_scopy(&tmp, (vd.v_ptr->u_value) ? vd.v_ptr->u_value : "");
          }
          (void) lsprintf(prompt, "Value of %s: ", var);
          status = mlreply2(prompt, &tmp);
          if (status != TRUE)
            returnCode(status);
      }

      status = SetVarValue(&vd, var, tb_values(tmp));

      if (status == ABORT) {
          mlforce("[Variable %s is readonly]", var);
      } else if (status != TRUE) {
          mlforce("[Cannot set %s to %s]", var, tb_values(tmp));
      }
    }

    returnCode(status);
}

static void
free_UVAR_value(UVAR * p)
{
    if (p->u_value != error_val
      && p->u_value != 0) {
      beginDisplay();
      free(p->u_value);
      endofDisplay();
    }
    p->u_value = 0;
}

/*
 * Remove a temp variable.  That's the only type of variable that we can
 * remove.
 */
int
rmv_tempvar(const char *name)
{
    register UVAR *p, *q;

    if (*name == '%')
      name++;

    for (p = temp_vars, q = 0; p != 0; q = p, p = p->next) {
      if (!vl_stricmp(p->u_name, name)) {
          TRACE(("rmv_tempvar(%s) ok\n", name));
          if (q != 0)
            q->next = p->next;
          else
            temp_vars = p->next;
          beginDisplay();
          free_UVAR_value(p);
          free(p->u_name);
          free((char *) p);
          free_vars_cmpl();
          endofDisplay();
          return TRUE;
      }
    }
    TRACE(("cannot rmv_tempvar(%s)\n", name));
    return FALSE;
}

/* entrypoint from modes.c and exec.c, to set state variables (one
 * interactive (from modes.c), one non-interactive) */
int
set_state_variable(const char *name, const char *value)
{
    int status;
    char var[NLINE];

    TRACE(("set_state_variable(%s,%s)\n", name, value));

    /* it may not be "marked" yet -- unadorned names are assumed to
     *  be "state variables
     */
    if (toktyp(name) == TOK_LITSTR) {
      var[0] = '$';
      vl_strncpy(var + 1, name, sizeof(var) - 1);
    } else {
      vl_strncpy(var, name, sizeof(var));
    }

    if (value != NULL) {      /* value supplied */
      VWRAP vd;
      FindVar(var, &vd);
      status = SetVarValue(&vd, var, value);
    } else {                  /* no value supplied:  interactive */
      status = PromptAndSet(var, FALSE, 0);
    }

    updatelistvariables();

    TRACE(("...set_state_variable ->%d\n", status));
    return status;
}

/*
 * this is the externally bindable command function.
 * assign a variable a new value -- any type of variable.
 */
int
setvar(int f, int n)
{
    int status;
    TBUFF *var;

    if ((status = PromptForVariableName(&var)) == TRUE) {
      status = PromptAndSet(tb_values(var), f, n);
    }
    updatelistvariables();
    return status;
}

/*
 * this is the externally bindable command function.
 * assign a variable a new value -- any type of variable.
 */
/* ARGSUSED */
int
unsetvar(int f GCC_UNUSED, int n GCC_UNUSED)
{
    int status;
    TBUFF *var;

    if ((status = PromptForVariableName(&var)) == TRUE) {
      status = set_state_variable(tb_values(var), "");
    }
    updatelistvariables();
    return status;
}

/* int set_statevar_val(int vnum, const char *value);  */

static int
set_statevar_val(int vnum, const char *value)
{
    return (*statevar_func[vnum]) ((TBUFF **) 0, value);
}

/* figure out what type of variable we're setting, and do so */
static int
SetVarValue(VWRAP * var, const char *name, const char *value)
{
    int status;
    int savecmd;
    char *savestr;
    VALARGS args;

    status = TRUE;
    switch (var->v_type) {
    case VW_NOVAR:
      return FALSE;

    case VW_MODE:
      savestr = execstr;
      savecmd = clexec;
      execstr = TYPECAST(char, value);
      clexec = TRUE;
      (void) find_mode(curbp, name + 1, -TRUE, &args);
      set_end_string('=');
      status = adjvalueset(name + 1, FALSE, TRUE, -TRUE, &args);
      execstr = savestr;
      clexec = savecmd;
      break;

    case VW_TEMPVAR:
      beginDisplay();
      free_UVAR_value(var->v_ptr);
      if (value == error_val) {
          var->v_ptr->u_value = error_val;
      } else if ((var->v_ptr->u_value = strmalloc(value)) == 0) {
          status = FALSE;
          var->v_ptr->u_value = error_val;
      }
      endofDisplay();
      break;

    case VW_STATEVAR:
      status = set_statevar_val(var->v_num, value);
      break;
    }

#if   OPT_DEBUGMACROS
    if (tracemacros) {        /* we tracing macros? */
      if (var->v_type == TOK_STATEVAR)
          mlforce("(((%s:&%s:%s)))",
                render_boolean(rp, status),
                statevars[var->v_num], value);
      else if (var->v_type == TOK_TEMPVAR)
          mlforce("(((%s:%%%s:%s)))",
                render_boolean(rp, status),
                var->v_ptr->u_name, value);

      (void) update(TRUE);

      /* make the user hit a key */
      if (ABORTED(keystroke())) {
          mlwarn("[Aborted]");
          status = FALSE;
      }
    }
#endif
    return status;
}
#endif

const char *
skip_cblanks(const char *src)
{
    while (isSpace(*src))
      src++;
    return src;
}

const char *
skip_cstring(const char *src)
{
    return src + strlen(src);
}

const char *
skip_ctext(const char *src)
{
    while (*src != EOS && !isSpace(*src))
      src++;
    return src;
}

/* If we've #define'd const, it's because it doesn't work.  Conversely, if
 * it's not #define'd, we really need distinct string functions.
 */
#ifndef const
char *
skip_blanks(char *src)
{
    while (isSpace(*src))
      src++;
    return src;
}

char *
skip_string(char *src)
{
    return src + strlen(src);
}

char *
skip_text(char *src)
{
    while (*src != EOS && !isSpace(*src))
      src++;
    return src;
}
#endif

/*
 * Blanks and spaces are not synonymous
 */
char *
skip_space_tab(char *src)
{
    while (*src != EOS && isBlank(*src))
      ++src;
    return src;
}

#if OPT_COLOR
void
set_ctrans(const char *thePalette)
{
    int n = 0, value;
    char *next;

    TRACE(("set_ctrans(%s)\n", thePalette));
    while (*thePalette != EOS) {
      thePalette = skip_cblanks(thePalette);
      value = (int) strtol(thePalette, &next, 0);
      if (next == thePalette)
          break;
      thePalette = next;
      ctrans[n] = value;
      if (++n >= NCOLORS)
          break;
    }
#if OPT_TRACE
    TRACE(("...ctrans, where not 1-1:\n"));
    for (n = 0; n < NCOLORS; n++) {
      if (n != ctrans[n])
          TRACE(("\t[%d] = %d\n", n, ctrans[n]));
    }
#endif
}
#endif

#if OPT_SHOW_COLORS
static void
show_attr(int color, const char *attr, const char *name)
{
    bprintf(" %c%d%sC", CONTROL_A, strlen(name), attr);
    if (color >= 0)
      bprintf("%X", color);
    bprintf(":%s", name);
}
/*
 * This will show the foreground colors, which we can display with attributes.
 */
/* ARGSUSED */
static void
makecolorlist(int dum1 GCC_UNUSED, void *ptr GCC_UNUSED)
{
    int j, k;
    REGION region;
    char temp[11];
    char *s;

    bprintf("--- Color palette %*P\n", term.cols - 1, '-');
    bprintf("\nColor name       Internal  $palette   Examples\n");
    for (j = -1; j < NCOLORS; j++) {
      if (!is_color_code(j))
          continue;
      k = j >= 0 ? ctrans[j] : j;
      bprintf("\n%16s ", get_color_name(j));

      /* show internal codes in the form that we'll read from the
       * filters.  That's 'C' followed by a single digit, in
       * hexadecimal because we allow up to 16 colors.
       */
      if (j >= 0)
          bprintf("C%X%*P", j, 10, ' ');
      else
          bprintf("%*P", 10, ' ');
      /*
       * $palette values are in decimal, but it's useful to see the
       * hexadecimal equivalents.
       */
      s = lsprintf(temp, "%d", k);
      if (k > 9)
          (void) lsprintf(s, " (%X)", k);
      bprintf("%s%*P", temp, sizeof(temp) - 1, ' ');

      show_attr(j, "", "Normal");
      show_attr(j, "B", "Bold");
      show_attr(j, "I", "Italic");
      show_attr(j, "U", "Underline");
      show_attr(j, "R", "Reverse");
    }
    bprintf("\n");            /* tweak to ensure we get final line */

    MK.l = lback(buf_head(curbp));
    MK.o = 0;
    DOT.l = lforw(buf_head(curbp));
    DOT.o = 0;

    if (getregion(&region) == TRUE)
      attribute_cntl_a_seqs_in_region(&region, FULLLINE);
}

/* ARGSUSED */
int
descolors(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return liststuff(PALETTE_COLORS_BufName,
                 FALSE, makecolorlist, 0, (void *) 0);
}

#if OPT_UPBUFF
/* ARGSUSED */
static int
update_colorlist(BUFFER *bp GCC_UNUSED)
{
    return descolors(FALSE, 1);
}

void
relist_descolor(void)
{
    update_scratch(PALETTE_COLORS_BufName, update_colorlist);
}
#endif /* OPT_UPBUFF */

#endif /* OPT_SHOW_COLORS */

/*
 * This function is used in several terminal drivers.
 */
#if OPT_EVAL || OPT_COLOR
int
set_palette(const char *value)
{
    tb_curpalette = tb_scopy(&tb_curpalette, value);
#if OPT_COLOR
    if (term.setpal == nullterm_setpal)
      return FALSE;

    relist_descolor();
    term.setpal(tb_values(tb_curpalette));
    vile_refresh(FALSE, 0);
#endif
    return TRUE;
}
#endif

/* represent integer as string */
char *
render_int(TBUFF ** rp, int i)
{
    char *p, *q;

    p = tb_values(tb_alloc(rp, 32));
    q = lsprintf(p, "%d", i);
    (*rp)->tb_used = (q - p + 1);
    return p;
}

/* represent long integer as string */
char *
render_long(TBUFF ** rp, long i)
{
    char *p, *q;

    p = tb_values(tb_alloc(rp, 32));
    q = lsprintf(p, "%ld", i);
    (*rp)->tb_used = (q - p + 1);
    return p;
}

#if OPT_EVAL

/* represent boolean as string */
char *
render_boolean(TBUFF ** rp, int val)
{
    static char *bools[] =
    {"FALSE", "TRUE"};
    return tb_values(tb_scopy(rp, bools[val ? 1 : 0]));
}

#if (SYS_WINNT||SYS_VMS)
/* unsigned to hex */
char *
render_hex(TBUFF ** rp, unsigned i)
{
    char *p, *q;

    p = tb_values(tb_alloc(rp, 32));
    q = lsprintf(p, "%x", i);
    (*rp)->tb_used = (q - p + 1);
    return p;
}
#endif

/* pull an integer out of a string.  allow 'c' character constants as well */
long
scan_long(const char *s)
{
    char *ns;
    long l;

    if (s[0] == '\'' && s[2] == '\'' && s[3] == EOS)
      return s[1];

    l = strtol(s, &ns, 0);

    /* the endpointer better point at a 0, otherwise
     * it wasn't all digits */
    if (*s && !*ns)
      return l;

    return 0;
}

#endif

/* pull a boolean value out of a string */
int
scan_bool(const char *val)
{
    /* check for logical true and false */
    if (is_falsem(val))
      return FALSE;
    if (is_truem(val))
      return TRUE;

    /* check for "numeric truth", like C.  0 is false, everything else
     * is true
     */
    return (strtol(val, 0, 0) != 0);
}

/* discriminate types of tokens */
int
toktyp(const char *tokn)
{
    if (tokn[0] == EOS)
      return TOK_NULL;
    if (tokn[0] == DQUOTE)
      return TOK_QUOTSTR;
    if (tokn[0] == SQUOTE)
      return TOK_QUOTSTR;
#if ! SMALLER
    if (tokn[1] == EOS)
      return TOK_LITSTR;      /* only one character */
    switch (tokn[0]) {
    case '~':
      return TOK_DIRECTIVE;
    case '@':
      return TOK_QUERY;
    case '<':
      return TOK_BUFLINE;
    case '$':
      return TOK_STATEVAR;
    case '%':
      return TOK_TEMPVAR;
    case '&':
      return TOK_FUNCTION;
    case '*':
      return TOK_LABEL;

    default:
      return TOK_LITSTR;
    }
#else
    return TOK_LITSTR;
#endif
}

/*--------------------------------------------------------------------------*/

#if OPT_MACRO_ARGS

/* ARGSUSED */
static int
complete_integer(DONE_ARGS)
{
    char *tmp;

    (void) flags;
    (void) pos;
    (void) strtol(buf, &tmp, 0);
    if ((tmp != 0) && (tmp != buf) && (*tmp == 0) && isSpace(c)) {
      if (c != NAMEC)         /* put it back (cf: kbd_complete) */
          unkeystroke(c);
      return TRUE;
    }
    return FALSE;
}

static int
complete_FSM(DONE_ARGS, const FSM_CHOICES * choices)
{
    int status;

    if (isDigit(*buf)) {      /* allow numbers for colors */
      status = complete_integer(PASS_DONE_ARGS);
    } else if (choices != 0) {
      status = kbd_complete(PASS_DONE_ARGS,
                        (const char *) choices,
                        sizeof(FSM_CHOICES));
    } else {
      status = FALSE;
    }

    return status;
}

static int
complete_boolean(DONE_ARGS)
{
    return complete_FSM(PASS_DONE_ARGS, fsm_bool_choices);
}

#if OPT_ENUM_MODES
static const FSM_CHOICES *complete_enum_ptr;

static int
complete_enum(DONE_ARGS)
{
    return complete_FSM(PASS_DONE_ARGS, complete_enum_ptr);
}
#endif

static int
complete_mode(DONE_ARGS)
{
    return kbd_complete(PASS_DONE_ARGS,
                  (const char *) list_of_modes(),
                  sizeof(char *));
}

static int
qs_vars_cmp(const void *a, const void *b)
{
    return vl_stricmp(*(const char *const *) a, (*(const char *const *) b));
}

static char **vars_cmpl_list;

static void
free_vars_cmpl(void)
{
    beginDisplay();
    FreeAndNull(vars_cmpl_list);
    endofDisplay();
}

static char **
init_vars_cmpl(void)
{
    int pass;
    UVAR *p;
    unsigned count = 0;

    if (vars_cmpl_list == 0) {
      for (pass = 0; pass < 2; pass++) {
          count = 0;
          for (p = temp_vars; p != 0; p = p->next) {
            if (pass != 0)
                vars_cmpl_list[count] = p->u_name;
            count++;
          }
          if (pass == 0) {
            beginDisplay();
            vars_cmpl_list = typeallocn(char *, count + 1);
            endofDisplay();
          }
      }

      if (vars_cmpl_list != 0) {
          vars_cmpl_list[count] = 0;
          qsort(vars_cmpl_list, count, sizeof(char *), qs_vars_cmp);
      }

    }
    return vars_cmpl_list;
}

static int
complete_vars(DONE_ARGS)
{
    int status = FALSE;
    char **nptr;

    if ((nptr = init_vars_cmpl()) != 0) {
      status = kbd_complete(PASS_DONE_ARGS,
                        (const char *) nptr,
                        sizeof(char *));
    }
    return status;
}

static int
read_argument(TBUFF ** paramp, const PARAM_INFO * info)
{
    int status = TRUE;
    char *prompt;
    TBUFF *temp;
    int (*complete) (DONE_ARGS) = no_completion;
    int save_clexec;
    int save_isnamed;
    char *save_execstr;
    KBD_OPTIONS flags = 0;    /* no expansion, etc. */

    if (mac_tokval(paramp) == 0) {
      prompt = "String";
      if (info != 0) {
          switch (info->pi_type) {
          case PT_BOOL:
            prompt = "Boolean";
            complete = complete_boolean;
            break;
          case PT_BUFFER:
            prompt = "Buffer";
            break;
          case PT_DIR:
            prompt = "Directory";
            break;
#if OPT_ENUM_MODES
          case PT_ENUM:
            prompt = "Enum";
            flags = KBD_NOEVAL | KBD_LOWERC;
            complete = complete_enum;
            complete_enum_ptr = info->pi_choice;
            break;
#endif
          case PT_FILE:
            prompt = "Filename";
            break;
          case PT_INT:
            prompt = "Integer";
            complete = complete_integer;
            break;
          case PT_MODE:
            prompt = "Mode";
            flags = KBD_NOEVAL | KBD_LOWERC;
            complete = complete_mode;
            break;
#if OPT_MAJORMODE
          case PT_MAJORMODE:
            prompt = "Majormode";
            flags = KBD_NOEVAL;
            complete = major_complete;
            break;
#endif
          case PT_VAR:
            prompt = "Variable";
            complete = complete_vars;
            flags = KBD_NOEVAL | KBD_LOWERC | KBD_MAYBEC;
            break;
          default:
            break;
          }
          if (info->pi_text != 0)
            prompt = info->pi_text;
      }

      temp = 0;
      tb_scopy(&temp, prompt);
      tb_sappend0(&temp, ": ");

      save_clexec = clexec;
      save_execstr = execstr;
      save_isnamed = isnamedcmd;

      /*
       * If we've run out of parameters on the command-line, force this
       * to be interactive.
       */
      if (execstr == 0 || !*skip_blanks(execstr)) {
          clexec = FALSE;
          isnamedcmd = TRUE;
          execstr = "";
      }

      if (info->pi_type == PT_FILE) {
          char fname[NFILEN];

          status = mlreply_file(tb_values(temp), (TBUFF **) 0,
                          FILEC_UNKNOWN, fname);
          if (status != ABORT)
            tb_scopy(paramp, fname);
      } else if (info->pi_type == PT_DIR) {
          char fname[NFILEN];

          status = mlreply_dir(tb_values(temp), (TBUFF **) 0, fname);
          if (status != ABORT)
            tb_scopy(paramp, fname);
      } else if (info->pi_type == PT_BUFFER) {
          char bname[NBUFN];

          status = ask_for_bname(tb_values(temp), bname, sizeof(bname));
          if (status != ABORT)
            tb_scopy(paramp, bname);
      } else {
          status = kbd_reply(tb_values(temp),
                         paramp,    /* in/out buffer */
                         eol_history,
                         '\n',      /* expect a newline or return */
                         flags,     /* no expansion, etc. */
                         complete);
      }
      clexec = save_clexec;
      execstr = save_execstr;
      isnamedcmd = save_isnamed;

      tb_free(&temp);
    }
    return status;
}

/*
 * When first executing a macro, save the remainder of the argument string
 * as an array of strings.
 */
int
save_arguments(BUFFER *bp)
{
    PROC_ARGS *p;
    int num_args = 0;
    int max_args;
    char temp[NBUFN];
    const CMDFUNC *cmd = engl2fnc(strip_brackets(temp, bp->b_bname));
    int ok = TRUE;
    int status = FALSE;
    const char *cp;

    if (cmd != 0 && cmd->c_args != 0) {
      for (max_args = 0;
           cmd->c_args[max_args].pi_type != PT_UNKNOWN;
           max_args++) ;
    } else {
      max_args = 0;
    }

    TRACE((T_CALLED "save_arguments(%s) max_args=%d\n", bp->b_bname, max_args));

    beginDisplay();
    if ((p = typealloc(PROC_ARGS)) != 0) {
      status = TRUE;
      p->nxt_args = arg_stack;
      arg_stack = p;
      p->all_args = typecallocn(TBUFF *, max_args + 1);
      tb_scopy(&(p->all_args[num_args++]), bp->b_bname);

      while (num_args < max_args + 1) {
          if (ok) {
            status = read_argument(&(p->all_args[num_args]),
                               &(cmd->c_args[num_args - 1]));
            if (status == ABORT)
                break;
            ok = (status == TRUE);
          }
          if (ok) {
            if ((cp = tokval(tb_values(p->all_args[num_args]))) != error_val) {
                tb_scopy(&(p->all_args[num_args]), cp);
            } else {
                status = ABORT;
                break;
            }
          } else {
            tb_scopy(&(p->all_args[num_args]), "");
          }
          TPRINTF(("...ARG%d:%s\n", num_args, tb_values(p->all_args[num_args])));
          num_args++;
      }

      p->num_args = num_args - 1;
    }
    endofDisplay();

    updatelistvariables();
    returnCode(status);
}

/*
 * Pop the list of arguments off our stack
 */
/* ARGSUSED */
void
restore_arguments(BUFFER *bp GCC_UNUSED)
{
    PROC_ARGS *p = arg_stack;

    TRACE(("restore_arguments(%s)\n", bp->b_bname));

    if (p != 0) {
      arg_stack = p->nxt_args;
      while (p->num_args >= 0) {
          tb_free(&(p->all_args[p->num_args]));
          p->num_args -= 1;
      }
      beginDisplay();
      free(p->all_args);
      free(p);
      endofDisplay();
    }

    updatelistvariables();
}

/*
 * Return the argument value if it is a number in range, the number of
 * arguments for '#', and the whole argument list for '*'.  As a special case,
 * return the list with individual words quoted for '@'.
 */
static char *
get_argument(const char *name)
{
    static TBUFF *value;
    static char empty[] = "";
    int num;
    char *str;
    char *result = error_val;

    if (arg_stack != 0) {
      if (*name == '#') {
          result = render_int(&value, arg_stack->num_args);
      } else if (*name == '*') {
          tb_init(&value, EOS);
          for (num = 1; num <= arg_stack->num_args; num++) {
            if (num > 1)
                tb_append(&value, ' ');
            tb_sappend(&value, tb_values(arg_stack->all_args[num]));
          }
          tb_append(&value, EOS);
          result = tb_values(value);
      } else if (*name == '@') {
          tb_init(&value, EOS);
          for (num = 1; num <= arg_stack->num_args; num++) {
            if (num > 1)
                tb_append(&value, ' ');
            str = tb_values(arg_stack->all_args[num]);
            tb_append(&value, DQUOTE);
            while (*str != EOS) {
                if (*str == BACKSLASH || *str == DQUOTE)
                  tb_append(&value, BACKSLASH);
                tb_append(&value, *str++);
            }
            tb_append(&value, DQUOTE);
          }
          tb_append(&value, EOS);
          result = tb_values(value);
      } else if (isDigit(*name)) {
          if ((num = scan_int(name)) <= arg_stack->num_args) {
            if (num > 0 || !strcmp(name, "0"))
                result = tb_values(arg_stack->all_args[num]);
          } else {
            result = empty;
          }
      }
    }
    return result;
}

#endif /* OPT_MACRO_ARGS */

/*--------------------------------------------------------------------------*/

#if OPT_EVAL

/* the argument simply represents itself */
static const char *
simple_arg_eval(char *argp)
{
    return argp;
}

/* the returned value is the user's response to the specified prompt */
static const char *
query_arg_eval(char *argp)
{
    argp = user_reply(tokval(argp + 1), error_val);
    return argp ? argp : error_val;
}

/* the returned value is the next line from the named buffer */
static const char *
buffer_arg_eval(char *argp)
{
    argp = next_buffer_line(tokval(argp + 1));
    return argp ? argp : error_val;
}

/* expand it as a temp variable */
static const char *
tempvar_arg_eval(char *argp)
{
    static TBUFF *tmp;
    UVAR *p;
    char *result;
    if ((p = lookup_tempvar(argp + 1)) != 0) {
      tb_scopy(&tmp, p->u_value);
      result = tb_values(tmp);
    } else {
      result = error_val;
    }
    return result;
}

/* state variables are expanded.  if it's
 * not an statevar, perhaps it's a mode?
 */
static const char *
statevar_arg_eval(char *argp)
{
    int vnum;
    const char *result;

    if ((result = get_argument(++argp)) == error_val) {
      vnum = lookup_statevar(argp);
      if (vnum != ILLEGAL_NUM)
          result = get_statevar_val(vnum);
#if !SMALLER
      else {
          VALARGS args;
          if (is_mode_name(argp, TRUE, &args) == TRUE)
            result = string_mode_val(&args);
      }
#endif
    }
    return result;
}

/* run a function to evalute it */
static const char *
function_arg_eval(char *argp)
{
    int fnum = vl_lookup_func(argp);
    return (fnum != ILLEGAL_NUM) ? run_func(fnum) : error_val;
}

/* goto targets evaluate to their line number in the buffer */
static const char *
label_arg_eval(char *argp)
{
    static TBUFF *label;
    LINE *lp = label2lp(curbp, argp);

    if (lp)
      return render_int(&label, line_no(curbp, lp));
    else
      return "0";
}

/* the value of a quoted string begins just past the leading quote */
static const char *
qstring_arg_eval(char *argp)
{
    return argp + 1;
}

static const char *
directive_arg_eval(char *argp)
{
    static TBUFF *tkbuf;
#if !SYS_UNIX
    /*
     * major hack alert -- the directives start with '~'.  on unix, we'd also
     * like to be able to expand ~user as a home directory.  handily, these two
     * uses don't conflict.  too much.
     *
     * For other systems, we can usually expand our own "home" directory.
     */
    if (strncmp("~/", argp, 2))
      return error_val;
#endif
    tb_alloc(&tkbuf, NFILEN + strlen(argp));
    return lengthen_path(strcpy(tb_values(tkbuf), argp));
}

typedef const char *(ArgEvalFunc) (char *argp);

static ArgEvalFunc *eval_func[] =
{
    simple_arg_eval,          /* TOK_NULL */
    query_arg_eval,           /* TOK_QUERY */
    buffer_arg_eval,          /* TOK_BUFLINE */
    tempvar_arg_eval,         /* TOK_TEMPVAR */
    statevar_arg_eval,        /* TOK_STATEVAR */
    function_arg_eval,        /* TOK_FUNCTION */
    directive_arg_eval,       /* TOK_DIRECTIVE */
    label_arg_eval,           /* TOK_LABEL */
    qstring_arg_eval,         /* TOK_QUOTSTR */
    simple_arg_eval           /* TOK_LITSTR */
};

#endif /* OPT_EVAL */

/* evaluate to the string-value of a token */
const char *
tokval(char *tokn)
{
    const char *result;

#if OPT_EVAL
    {
      int toknum = toktyp(tokn);
      if (toknum < 0 || toknum > MAXTOKTYPE)
          result = error_val;
      else
          result = (*eval_func[toknum]) (tokn);
      TRACE2(("tokval(%s) = %s\n", TRACE_NULL(tokn), TRACE_NULL(result)));
    }
#else
    result = (toktyp(tokn) == TOK_QUOTSTR) ? tokn + 1 : tokn;
#endif
    return (result);
}

/*
 * Return true if the argument is one of the strings that we accept as a
 * synonym for "true".
 */
int
is_truem(const char *val)
{
    char temp[8];
    (void) mklower(strncpy0(temp, val, sizeof(temp)));
#if OPT_BOOL_CHOICES
    return choice_to_code(fsm_bool_choices, temp, strlen(temp)) == TRUE;
#else
    return !strncmp(temp, "true", strlen(temp));
#endif
}

/*
 * Return true if the argument is one of the strings that we accept as a
 * synonym for "false".
 */
int
is_falsem(const char *val)
{
    char temp[8];
    (void) mklower(strncpy0(temp, val, sizeof(temp)));
#if OPT_BOOL_CHOICES
    return choice_to_code(fsm_bool_choices, temp, strlen(temp)) == FALSE;
#else
    return !strncmp(temp, "false", strlen(temp));
#endif
}

#if OPT_EVAL
int
evaluate(int f, int n)
{
    static PARAM_INFO info =
    {PT_STR, "Expression", 0};
    TBUFF *params = 0, *tok = 0, *cmd = 0;
    char *old, *tmp;
    int code = FALSE;

    if (read_argument(&params, &info) == TRUE) {
      if (tb_values(params) != error_val) {
          old = execstr;
          execstr = tb_values(params);
          TRACE(("EVAL %s\n", execstr));
          while ((tmp = mac_tokval(&tok)) != 0) {
            if (tb_length(cmd))
                tb_sappend0(&cmd, " ");
            tb_sappend0(&cmd, tmp);
            TRACE(("...token {%s}\n", tmp));
          }
          if ((tmp = tb_values(cmd)) != 0) {
            execstr = tmp;
            TRACE(("...docmd {%s}\n", execstr));
            code = docmd(tmp, TRUE, f, n);
          }
          execstr = old;
      }
    }
    tb_free(&cmd);
    tb_free(&tok);
    tb_free(&params);
    TRACE(("...EVAL ->%d\n", code));
    return code;
}

/* use this as a wrapper when evaluating an array index, etc., that cannot
 * be negative.
 */
static size_t
s2size(const char *s)
{
    int n = strtol(s, 0, 0);
    if (n < 0)
      n = 0;
    return n;
}

/* use this to return a pointer to the string after the n-1 first characters */
static const char *
s2offset(const char *s, const char *n)
{
    size_t len = strlen(s) + 1;
    UINT off = s2size(n);
    if (off > len)
      off = len;
    if (off == 0)
      off = 1;
    return s + (off - 1);
}

/*
 * Line Labels begin with a "*" as the first nonblank char, like:
 *
 *   *LBL01
 */
LINE *
label2lp(BUFFER *bp, const char *label)
{
    LINE *glp;
    LINE *result = 0;
    size_t len = strlen(label);
    int need = len + 1;
    int col;

    if (len > 1) {
      for_each_line(glp, bp) {
          if (llength(glp) >= need) {
            for (col = 0; col < llength(glp); ++col)
                if (!isSpace(glp->l_text[col]))
                  break;
            if (llength(glp) >= need + col
                && glp->l_text[col] == '*'
                && !memcmp(&glp->l_text[col + 1], label, len)) {
                result = glp;
                break;
            }
          }
      }
    }
    TRACE(("label2lp(%s) ->%d\n", label, line_no(curbp, result)));
    return result;
}

#endif

#if OPT_EVAL || !SMALLER
char *
mkupper(char *s)
{
    if (s != error_val) {
      char *sp;

      for (sp = s; *sp; sp++)
          if (isLower(*sp))
            *sp = (char) toUpper(*sp);

    }
    return s;
}
#endif

char *
mklower(char *s)
{
    if (s != error_val) {
      char *sp;

      for (sp = s; *sp; sp++)
          if (isUpper(*sp))
            *sp = (char) toLower(*sp);

    }
    return s;
}

char *
mktrimmed(char *str)
{                       /* trim whitespace */
    char *base = str;
    char *dst = str;

    if (str != error_val) {
      while (*str != EOS) {
          if (isSpace(*str)) {
            if (dst != base)
                *dst++ = ' ';
            str = skip_blanks(str);
          } else {
            *dst++ = *str++;
          }
      }
      if (dst != base && isSpace(dst[-1]))
          dst--;
      *dst = EOS;
    }
    return base;
}

int
absol(int x)
{                       /* return absolute value of given integer */
    return (x < 0) ? -x : x;
}

#if !SMALLER
/*
 * Returns true if the buffer contains blanks or quotes which would confuse us
 * when parsing.
 */
int
must_quote_token(const char *values, unsigned last)
{
    unsigned n;

    if (last != 0) {
      for (n = 0; n < last; n++) {
          int ch = CharOf(values[n]);
          if (ch == SQUOTE
            || ch == DQUOTE
            || ch == BACKSLASH
            || !isGraph(ch))
            return TRUE;
      }
    }
    return FALSE;
}

/*
 * Appends the buffer, with quotes
 */
void
append_quoted_token(TBUFF ** dst, const char *values, unsigned last)
{
    unsigned n;

    TRACE(("append_quoted_token\n"));
    tb_append(dst, DQUOTE);
    for (n = 0; n < last; n++) {
      int ch = CharOf(values[n]);
      switch (ch) {
      case DQUOTE:
      case BACKSLASH:
          tb_append(dst, BACKSLASH);
          tb_append(dst, ch);
          break;
      case '\b':
          tb_sappend(dst, "\\b");
          break;
      case '\t':
          tb_sappend(dst, "\\t");
          break;
      case '\r':
          tb_sappend(dst, "\\r");
          break;
      case '\n':
          tb_sappend(dst, "\\n");
          break;
      default:
          /* as for other characters, including nonprinting ones, they are
           * not a problem
           */
          tb_append(dst, ch);
          break;
      }
    }
    tb_append(dst, DQUOTE);
}
#endif /* !SMALLER */

#if NO_LEAKS
void
vars_leaks(void)
{
#if OPT_EVAL
    free_vars_cmpl();
#endif
}
#endif

Generated by  Doxygen 1.6.0   Back to index