Logo Search packages:      
Sourcecode: vile version File versions

exec.c

/*    This file is for functions dealing with execution of
 *    commands, command lines, buffers, files and startup files
 *
 *    original by Daniel Lawrence, but
 *    much modified since then.  assign no blame to him.  -pgf
 *
 * $Header: /usr/build/vile/vile/RCS/exec.c,v 1.259 2003/06/21 14:59:47 tom Exp $
 *
 */

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

#define isSPorTAB(c) ((c) == ' ' || (c) == '\t')

static int rangespec(const char *specp, LINEPTR * fromlinep, LINEPTR *
                 tolinep, CMDFLAGS * flagp);

/* while loops are described by a list of small structs.  these point
 * at the line on which they were found, and at the line to which one
 * might want to go from there, i.e.:
 *
 *    |-> while ---------
 *    |     .           |
 *    |     .           |
 *    |     .           |
 *    |     break ----  |
 *    |     .        |  |
 *    |     .        |  |
 *    --- endwhile <--<-|
 *
 * the list of structs is created in a pre-processing pass in
 * setup_dobuf() and then used in perform_dobuf().
 */

typedef struct WHLOOP {
    LINEPTR w_here;           /* line containing this directive */
    LINEPTR w_there;          /* if endwhile, ptr to while, else to endwhile */
    DIRECTIVE w_type;         /* block type: D_WHILE, D_BREAK, D_ENDWHILE */
    struct WHLOOP *w_next;
} WHLOOP;

static int token_ended_line;  /* did the last token end at end of line? */

#if !SMALLER

typedef struct locals {
    struct locals *next;
    char *name;
    char *m_value;            /* malloc'd value */
    const char *p_value;      /* pure value */
} LOCALS;

typedef struct {
    int disabled;       /* nonzero to disable execution */
    int level;                /* ~if ... ~endif nesting level */
    int fired;                /* at top-level, ~if/... used */
    REGIONSHAPE shape;
    const CMDFUNC *motion;
    LOCALS *locals;
    TBUFF *prefix;
} IFSTK;

#else /* SMALLER */

typedef struct {
    int disabled;       /* nonzero to disable execution */
} IFSTK;                /* just a dummy variable to simplify ifdef's */

#endif

static IFSTK ifstk;

/* while storing away a macro, this is where it goes.
 * if NULL, we're not storing a macro.  */
static BUFFER *macrobuffer = NULL;

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

/*ARGSUSED*/
static int
eol_range(const char *buffer, unsigned cpos, int c, int eolchar GCC_UNUSED)
{
    if (is_edit_char(c))
      return FALSE;

    /* sorry, cannot scroll with arrow keys */
    if (isSpecial(c) || isCntrl(c))
      return TRUE;

    if (islinespecchar(c)
      || (c == ':' && (cpos == 0 || buffer[cpos - 1] == c))
      ||                /* special test for 'a style mark references */
      (cpos != 0
       && buffer[cpos - 1] == SQUOTE
       && can_set_nmmark(c)))
      return FALSE;
    return TRUE;
}

/*
 * Returns true iff the user ended the last prompt with a carriage-return.
 */
static int
end_of_cmd(void)
{
    register int c = end_string();
    return isreturn(c);
}

/* returns true iff we are in a named-command and if the user ended the last
 * prompt with a carriage-return.
 */
int
end_named_cmd(void)
{
    if (isnamedcmd) {
      if (clexec)
          return token_ended_line;
      else
          return end_of_cmd();
    }
    return FALSE;
}

/* returns true iff we are in a named-command and if the user did not end the
 * last prompt with a carriage-return.
 */
int
more_named_cmd(void)
{
    if (isnamedcmd) {
      if (clexec)
          return !token_ended_line;
      else
          return !end_of_cmd();
    }
    return FALSE;
}

/*
 * namedcmd:      execute a named command even if it is not bound
 */

#if SMALLER
#define execute_named_command namedcmd
#else
static                        /* next procedure is local */
#endif

int
execute_named_command(int f, int n)
{
    register int status;
    register const CMDFUNC *cfp;    /* function to execute */
    register char *fnp;       /* ptr to the name of the cmd to exec */

    LINEPTR fromline;         /* first linespec */
    LINEPTR toline;           /* second linespec */
    MARK save_DOT;
    MARK last_DOT;
    static TBUFF *lspec;      /* text of line spec */
    char cspec[NLINE];        /* text of command spec */
    int cmode = 0;
    int c;
    char repeat_cmd = EOS;
    int maybe_reg, maybe_num;
    CMDFLAGS lflag, flags;

    tb_scopy(&lspec, "");
    last_DOT = DOT;

    /* prompt the user to type a named command */
#ifdef SMALLER
    mlprompt(": ");
#else
    mlprompt(tb_values(prompt_string));
#endif

    /* and now get the function name to execute */
    for_ever {
      if (cmode == 0) { /* looking for range-spec, if any */
          status = kbd_reply((char *) 0,  /* no-prompt => splice */
                         &lspec,    /* in/out buffer */
                         eol_range,
                         EOS, /* may be a conflict */
                         0,   /* no expansion, etc. */
                         no_completion);
          c = end_string();
          if (status != TRUE && status != FALSE) {
            if (status == SORTOFTRUE) {
                (void) keystroke();       /* cancel ungetc */
                return FALSE;
            }
            return status;
          } else if (isreturn(c) && (status == FALSE)) {
            return FALSE;
          } else {
            unkeystroke(c);   /* ...so we can splice */
          }
          cmode = 1;
          repeat_cmd = EOS;
      } else {          /* looking for command-name */
          fnp = NULL;
          status = kbd_engl_stat((char *) 0, cspec, KBD_STATED);
          if (status == TRUE) {
            fnp = cspec;
#if !SMALLER
            if (isRepeatable(*fnp) && clexec) {
                repeat_cmd = *fnp;
                cmode++;
                hst_glue(EOS);
            } else
#endif
            if (isRepeatable(*fnp) && (*fnp == end_string())) {
                unkeystroke(*fnp);
                repeat_cmd = *fnp;
                cmode++;
                hst_glue(EOS);
            } else {
                c = fnp[strlen(fnp) - 1];
                if (isPunct(c) && (c != '&')) {
                  c = end_string();
                  if (c != NAMEC
                      && c != ' '
                      && !isreturn(c)) {
                      unkeystroke(c);
                      /* e.g., !-command */
                  }
                } else {      /* e.g., ":e#" */
                  c = end_string();
                  if (isPunct(c)
                      && strchr(global_g_val_ptr(GVAL_EXPAND_CHARS),
                              c) != 0)
                      unkeystroke(c);
                }
                break;
            }
          } else if (status == SORTOFTRUE) {
            hst_remove((cmode > 1) ? "*" : "");
            if (cmode > 1)
                (void) keystroke();       /* eat the delete */
            if (--cmode <= 1) {
                cmode = 0;
                hst_remove(tb_values(lspec));
            }
          } else if ((status == ABORT) || (lastkey == killc)) {
            return status;
          } else {            /* status == FALSE (killc/wkillc) */
            if (cmode > 1) {
                fnp = cspec;
                cmode--;
                break;
            } else {
                if (tb_length(lspec) <= 1) {
                  return status;
                } else {
                  break;      /* range-only */
                }
            }
          }
      }
    }

    /* reconstruct command if user edited it */
    if (repeat_cmd != EOS && fnp != 0 && *fnp == EOS) {
      fnp[0] = repeat_cmd;
      fnp[1] = EOS;
    }

    /* infer repeat count from repeated-command */
    if (cmode > 1) {
      if (!f) {
          f = TRUE;
          n = cmode;
      } else {
          mlforce("[Redundant repeat-count]");
          return FALSE;
      }
    }

    /* parse the accumulated lspec */
    if (rangespec(tb_values(lspec), &fromline, &toline, &lflag) != TRUE) {
      mlforce("[Improper line range]");
      return FALSE;
    }

    /* if range given, and it wasn't "0" and the buffer's empty */
    if (!(lflag & (DFLALL | ZERO)) && is_empty_buf(curbp)) {
      mlforce("[No range possible in empty buffer '%s']", fnp);
      return FALSE;
    }

    /* did we get a name? */
    if (fnp == NULL) {
      if ((lflag & DFLALL)) { /* no range, no function */
          return FALSE;
      } else {          /* range, no function */
          cfp = &f_gomark;
          fnp = "";
      }
    } else if ((cfp = engl2fnc(fnp)) == NULL) {       /* bad function */
      return no_such_function(fnp);
    }

    ev_end_of_cmd = end_of_cmd();
    flags = cfp->c_flags;

    /* bad arguments? */
#ifdef EXRC_FILES
    /*
     * seems like we need one more check here-- is it from a.exrc file ?
     * cmd not ok in.exrc empty file
     */
    if (!(flags & EXRCOK) && is_empty_buf(curbp)) {
      mlforce("[Can't use the \"%s\" command in a %s file.]",
            cmdnames[cmdidx].name, EXRC);
      return FALSE;
    }
#endif

    /* was: if (!(flags & (ZERO | EXRCOK)) && fromline == NULL ) */
    if ((lflag & ZERO)) {
      if (!(flags & ZERO)) {
          mlforce("[Can't use address 0 with \"%s\" command]", fnp);
          return FALSE;
      }
      if (fromline == null_ptr)
          fromline = buf_head(curbp);           /* buffer is empty */

      /*  we're positioned at fromline == curbp->b_linep, so commands
         must be willing to go _down_ from there.  Seems easiest
         to special case the commands that prefer going up */
      if (cfp == &f_insfile) {
          /* EMPTY */ ;
      } else if (cfp == &f_lineputafter) {
          cfp = &f_lineputbefore;
          fromline = lforw(fromline);
      } else if (cfp == &f_opendown) {
          cfp = &f_openup;
          fromline = lforw(fromline);
      } else if (cfp == &f_gomark) {
          fromline = lforw(fromline);
      } else {
          mlforce("[Configuration error: ZERO]");
          return FALSE;
      }
      flags = cfp->c_flags;
      toline = fromline;
    }

    /* if we're not supposed to have a line no., and the line no. isn't
       the current line, and there's more than one line */
    if (!(flags & FROM) && (fromline != DOT.l) &&
      !is_empty_buf(curbp) &&
      (lforw(lforw(buf_head(curbp))) != buf_head(curbp))) {
      mlforce("[Can't use address with \"%s\" command.]", fnp);
      return FALSE;
    }
    /* if we're not supposed to have a second line no., and the line no.
       isn't the same as the first line no., and there's more than
       one line */
    if (!(flags & TO) && (toline != fromline) &&
      !is_empty_buf(curbp) &&
      (lforw(lforw(buf_head(curbp))) != buf_head(curbp))) {
      mlforce("[Can't use a range with \"%s\" command.]", fnp);
      return FALSE;
    }

    /* some commands have special default ranges */
    if (lflag & DFLALL) {
      if (flags & DFLALL) {
          if (cfp == &f_showlength) {
            fromline = lforw(buf_head(curbp));
            toline = lback(buf_head(curbp));
          } else if (cfp == &f_operwrite) {
            cfp = &f_filewrite;
          } else if (cfp == &f_operglobals) {
            cfp = &f_globals;
          } else if (cfp == &f_opervglobals) {
            cfp = &f_vglobals;
          } else {
            mlforce("[Configuration error: DFLALL]");
            return FALSE;
          }
          lflag |= (FROM | TO);     /* avoid prompt for line-count */
      } else if (flags & DFLNONE) {
#if OPT_SHELL
          if (cfp == &f_operfilter) {
            cfp = &f_spawn;
            (void) setmark(); /* not that it matters */
          } else
#endif
#if OPT_PERL
          if (cfp == &f_perl) {
            perl_default_region();
          } else
#endif
          {
            mlforce("[Configuration error: DFLNONE]");
            return FALSE;
          }
          fromline = toline = null_ptr;
          lflag |= (FROM | TO);     /* avoid prompt for line-count */
      } else
          lflag &= ~DFLALL;
    }

    /* Process argument(s) for named-buffer & line-count, if present.  The
     * line-count is expected only if no "to" address specification was
     * given.
     */
    if ((*fnp != EOS) && !end_of_cmd() && !isPunct(end_string())) {
      maybe_reg = ((flags & OPTREG) == OPTREG);
      maybe_num = ((flags & TO) == TO)
          && !((lflag & TO) == TO);
      if (maybe_num && (lflag & FROM)) {
          toline = fromline;
          maybe_num = FALSE;
      }
    } else {
      maybe_reg = FALSE;
      maybe_num = FALSE;
    }

    if (maybe_reg || maybe_num) {
      int num;
      int that = (maybe_num && maybe_reg) ? 0 : (maybe_num ? 1 : -1);
      int last = maybe_num ? 2 : 1;

      while (!end_of_cmd() && (that < last)) {
          status = mlreply_reg_count(that, &num, &that);
          if (status == ABORT)
            return status;
          else if (status != TRUE)
            break;
          if (that == 2) {
            if (is_empty_buf(curbp)) {
                mlforce("[No line count possible in empty buffer '%s']", fnp);
                return FALSE;
            }
            swapmark();
            DOT.l = fromline;
            (void) forwline(TRUE, num - 1);
            toline = DOT.l;
            swapmark();
          } else {
            ukb = (short) num;
            kregflag = (short) ((that == 1) ? KAPPEND : 0);
            that = 1;
            /* patch: need to handle recursion */
          }
      }
    }

    save_DOT = DOT;
    if (!(flags & NOMOVE)
      && ((toline != null_ptr) || (fromline != null_ptr))) {
      /* assume it's an absolute motion */
      /* we could probably do better */
      curwp->w_lastdot = DOT;
    }
    if ((toline != null_ptr) && (flags & TO)) {
      DOT.l = toline;
      (void) firstnonwhite(FALSE, 1);
      (void) setmark();
    }
    if ((fromline != null_ptr) && (flags & FROM)) {
      DOT.l = fromline;
      (void) firstnonwhite(FALSE, 1);
      if (toline == null_ptr)
          (void) setmark();
    }
    if (!sameline(last_DOT, DOT))
      curwp->w_flag |= WFMOVE;

    /* and then execute the command */
    isnamedcmd = TRUE;
    havemotion = &f_gomark;
    regionshape = FULLLINE;

    /* if the command ended with a bang, let the function know
       that so it can take special action */
    if ((flags & BANG) && (fnp[strlen(fnp) - 1] == '!')) {
      f = TRUE;
      n = SPECIAL_BANG_ARG;
    }

    /*
     * Ensure that we've completed the command properly.  (We could make
     * the same check after 'execute()', but that would tend to obscure
     * informational/warning messages...).
     */
    if (flags == NONE
      && end_string() != ' '
      && !end_of_cmd()) {
      status = FALSE;
      if (!mapped_c_avail())
          unkeystroke(end_string());
      (void) kbd_seq();
      mlforce("[Extra characters after \"%s\" command]", fnp);
    } else {
      status = execute(cfp, f, n);
    }

    havemotion = NULL;
    isnamedcmd = FALSE;
    regionshape = EXACT;

    /* don't use this if the command modifies! */
    if (flags & NOMOVE)
      restore_dot(save_DOT);
    else if (status == ABORT)
      restore_dot(last_DOT);

    return status;
}

#if !SMALLER
/* intercept calls on 'namedcmd()' to allow logging of all commands, even
 * those that have errors in them.
 */
int
namedcmd(int f, int n)
{
    int status;
    hst_init(EOS);
    status = execute_named_command(f, n);
    hst_flush();
    return status;
}
#endif

/*
 * parse an ex-style line spec -- code originally from elvis, file ex.c, by
 * Steve Kirkendall
 */
static const char *
parse_linespec(const char *s, LINEPTR * markptr)
{
    int num;
    LINE *lp;                 /* where the linespec takes us */
    register const char *t;
    int status;

    (void) setmark();
    lp = NULL;

    /* parse each ;-delimited clause of this linespec */
    do {
      /* skip an initial ';', if any */
      if (*s == ';')
          s++;

      /* skip leading spaces */
      while (isSPorTAB(*s))
          s++;

      /* dot means current position */
      if (*s == '.') {
          s++;
          lp = DOT.l;
      } else if (*s == '$') { /* '$' means the last line */
          s++;
          status = gotoeob(TRUE, 1);
          if (status)
            lp = DOT.l;
      } else if (isDigit(*s)) {
          /* digit means an absolute line number */
          for (num = 0; isDigit(*s); s++) {
            num = num * 10 + *s - '0';
          }
          status = gotoline(TRUE, num);
          if (status)
            lp = DOT.l;
      } else if (*s == SQUOTE) {
          /* apostrophe means go to a set mark */
          s++;
          status = gonmmark(*s);
          if (status)
            lp = DOT.l;
          s++;
      } else if (*s == '+') {
          s++;
          for (num = 0; isDigit(*s); s++)
            num = num * 10 + *s - '0';
          if (num == 0)
            num++;
          while (*s == '+')
            s++, num++;
          status = forwline(TRUE, num);
          if (status)
            lp = DOT.l;
      } else if (*s == '-') {
          s++;
          for (num = 0; isDigit(*s); s++)
            num = num * 10 + *s - '0';
          if (num == 0)
            num++;
          while (*s == '-')
            s++, num++;
          status = forwline(TRUE, -num);
          if (status)
            lp = DOT.l;
      }
#if VILE_MAYBE
      else if (*s == '/' || *s == '?') {  /* slash means do a search */
          /* put a null at the end of the search pattern */
          t = parseptrn(s);

          /* search for the pattern */
          lp &= ~(BLKSIZE - 1);
          if (*s == '/') {
            pfetch(markline(lp));
            if (plen > 0)
                lp += plen - 1;
            lp = m_fsrch(lp, s);
          } else {
            lp = m_bsrch(lp, s);
          }

          /* adjust command string pointer */
          s = t;
      }
#endif
      else if (*s == EOS) {   /* empty string matches '.' */
          lp = DOT.l;
      }

      /* if linespec was faulty, quit now */
      if (!lp) {
          *markptr = lp;
          swapmark();
          return s;
      }

      /* maybe add an offset */
      t = s;
      if (*t == '-' || *t == '+') {
          s++;
          for (num = 0; isDigit(*s); s++) {
            num = num * 10 + *s - '0';
          }
          if (num == 0)
            num = 1;
          if (forwline(TRUE, (*t == '+') ? num : -num) == TRUE)
            lp = DOT.l;
      }
    } while (*s == ';' || *s == '+' || *s == '-');

    *markptr = lp;
    swapmark();
    return s;
}

/*
 * parse an ex-style line range -- code originally from elvis, file ex.c, by
 * Steve Kirkendall
 */
static int
rangespec(const char *specp, LINEPTR * fromlinep, LINEPTR * tolinep,
        CMDFLAGS * flagp)
{
    register const char *scan;      /* used to scan thru specp */
    LINEPTR fromline;         /* first linespec */
    LINEPTR toline;           /* second linespec */

    *flagp = 0;

    /* ignore command lines that start with a double-quote */
    if (*specp == '"') {
      *fromlinep = *tolinep = DOT.l;
      return TRUE;
    }

    /* permit extra colons at the start of the line */
    while (isSPorTAB(*specp) || *specp == ':') {
      specp++;
    }

    /* parse the line specifier */
    scan = specp;
    if (*scan == '0') {
      fromline = toline = buf_head(curbp);      /* _very_ top of buffer */
      *flagp |= (FROM | ZERO);
      scan++;
    } else if (*scan == '%') {
      /* '%' means all lines */
      fromline = lforw(buf_head(curbp));
      toline = lback(buf_head(curbp));
      scan++;
      *flagp |= (FROM | TO);
    } else {
      scan = parse_linespec(scan, &toline);
      *flagp |= FROM;
      if (toline == null_ptr)
          toline = DOT.l;
      fromline = toline;
      while (*scan == ',') {
          fromline = toline;
          scan++;
          scan = parse_linespec(scan, &toline);
          *flagp |= TO;
          if (toline == null_ptr) {
            /* faulty line spec */
            return FALSE;
          }
      }
    }

    if (is_empty_buf(curbp))
      fromline = toline = null_ptr;

    if (scan == specp)
      *flagp |= DFLALL;

    /* skip whitespace */
    while (isSPorTAB(*scan))
      scan++;

    if (*scan) {
      /* dbgwrite("crud at end %s (%s)",specp, scan); */
      return FALSE;
    }

    *fromlinep = fromline;
    *tolinep = toline;

    return TRUE;
}

/*
 * take a passed string as a command line and translate it to be executed as a
 * command.  This function will be used by execute-command-line and by all
 * source and startup files.
 *
 * format of the command line is:
 *
 *    {# arg} <command-name> {<argument string(s)>}
 */
int
docmd(char *cline, int execflag, int f, int n)
{
    int status;               /* return status of function */
    int oldcle;               /* old contents of clexec flag */
    char *oldestr;            /* original exec string */
    TBUFF *tok = 0;
    char *token;        /* next token off of command line */
    const CMDFUNC *cfp;

    TRACE((T_CALLED "docmd(cline=%s, execflag=%d, f=%d, n=%d)\n",
         str_visible(cline), execflag, f, n));

    set_end_string(EOS);
    oldestr = execstr;        /* save ptr to starting command string to execute */
    execstr = cline;          /* and change execstr to the new one */

    do {
      if ((token = mac_tokval(&tok)) == 0) {    /* grab first token */
          execstr = oldestr;
          returnCode(FALSE);
      }
      if (*token == ':') {    /* allow leading ':' on line */
          register int j;
          for (j = 0; (token[j] = token[j + 1]) != EOS; j++) ;
      }
    } while (!*token);

    /* if it doesn't look like a command (and command names can't
     * be hidden in variables), then it must be a leading argument */
    if (toktyp(token) != TOK_LITSTR || isDigit(token[0])) {
      f = TRUE;
      n = strtol(tokval(token), 0, 0);

      /* and now get the command to execute */
      if ((token = mac_tokval(&tok)) == 0) {
          execstr = oldestr;
          returnCode(FALSE);
      }
    }

    /* and match the token to see if it exists */
    if ((cfp = engl2fnc(token)) == NULL) {
      status = no_such_function(token);
    } else {
      /* save the arguments and go execute the command */
      oldcle = clexec;  /* save old clexec flag */
      clexec = execflag;      /* in cline execution */
      /*
       * Flag the first time through for some commands -- e.g.  subst
       * must know to not prompt for strings again, and pregion must
       * only restart the p-lines buffer once for each command.
       */
      calledbefore = FALSE;
      status = execute(cfp, f, n);
      setcmdstatus(status);
      clexec = oldcle;
    }

    execstr = oldestr;
    tb_free(&tok);
    returnCode(status);
}

/*
 *  Call the appropriate action for a given CMDFUNC
 */
int
call_cmdfunc(const CMDFUNC * p, int f, int n)
{
    switch (p->c_flags & CMD_TYPE) {
    case CMD_FUNC:            /* normal CmdFunc */
      return (CMD_U_FUNC(p)) (f, n);

#if OPT_NAMEBST
#if OPT_PROCEDURES
    case CMD_PROC:            /* named procedure */
      return dobuf(CMD_U_BUFF(p), f ? n : 1);
#endif

#if OPT_PERL
    case CMD_PERL:            /* perl subroutine */
      return perl_call_sub(CMD_U_PERL(p), p->c_flags & OPER, f, n);
#endif
#endif /* OPT_NAMEBST */
    }

    mlforce("BUG: invalid CMDFUNC type");
    return FALSE;
}

/*
 * This is the general command execution routine. It takes care of checking
 * flags, globals, etc, to be sure we're not doing something dumb.
 * Return the status of command.
 */
int
execute(const CMDFUNC * execfunc, int f, int n)
{
    register int status;
    register CMDFLAGS flags;

    if (execfunc == NULL) {
#if OPT_REBIND
      mlwarn("[Key not bound]");    /* complain             */
#else
      mlwarn("[Not a command]");    /* complain             */
#endif
      returnCode(FALSE);
    }

    TRACE((T_CALLED "execute(execfunc=%p(%s), f=%d, n=%d)\n",
         execfunc, execfunc->c_name ? execfunc->c_name : "?", f, n));

    flags = execfunc->c_flags;

    /* commands following operators can't be redone or undone */
    if (!doingopcmd && !doingsweep) {
      /* don't record non-redoable cmds, */
      /* but if we're in insertmode, it's okay, since we must
         be executing a function key, like an arrow key,
         that the user will want to have replayed later */
      if ((curwp == NULL || !insertmode) && (flags & REDO) == 0)
          dotcmdstop();
      if (flags & UNDO) {
          /* undoable command can't be permitted when read-only */
          if (!(flags & VIEWOK)) {
            if (b_val(curbp, MDVIEW))
                returnCode(rdonly());
#ifdef MDCHK_MODTIME
            if (!b_is_changed(curbp) &&
                !ask_shouldchange(curbp))
                returnCode(FALSE);
#endif
          }
          if (!kbd_replaying(FALSE))
            mayneedundo();
      }
    }

    if (dotcmdactive != PLAY) {
      if (execfunc != &f_dotcmdplay) {
          /* reset dotcmdkreg on any command where ukb is
           * unspecified.  usekreg() does it on the one's
           * where it is specified.  */
          if (ukb == 0)
            dotcmdkreg = 0;

          /* override the saved dot-cmd argument, if this
             is a new redoable command */
          if (flags & REDO)
            dotcmdarg = FALSE;
      }
    } else {
      /* if we _are_ playing, re-use the previously kreg */
      if (dotcmdkreg != 0)
          ukb = dotcmdkreg;
    }

    if (curwp->w_tentative_lastdot.l == null_ptr)
      curwp->w_tentative_lastdot = DOT;

    status = call_cmdfunc(execfunc, f, n);
    if ((flags & GOAL) == 0) {      /* goal should not be retained */
      curgoal = -1;
    }

    /* if motion was absolute, and it wasn't just on behalf of an
       operator, and we moved, update the "last dot" mark */
    if ((flags & ABSM) && !doingopcmd &&
      !sameline(DOT, curwp->w_tentative_lastdot)) {
      curwp->w_lastdot = curwp->w_tentative_lastdot;
    }

    curwp->w_tentative_lastdot = DOT;

    returnCode(status);
}

/*
 * Chop a token off a string 'src' return a pointer past the token in 'tok'. 
 * 'eolchar' is the nominal delimiter we expect (perhaps '=' for the "set"
 * command), and 'actual' returns the first character past the token that we
 * actually found.
 */
char *
get_token(char *src, TBUFF ** tok, int eolchar, int *actual)
{
    tb_init(tok, EOS);
    return get_token2(src, tok, eolchar, actual);
}

/*
 * This is split-out from get_token() so we can handle the special case of
 * re-gluing a "!" to a quoted string from kbd_reply().
 */
char *
get_token2(char *src, TBUFF ** tok, int eolchar, int *actual)
{
    int quotef = EOS;         /* nonzero iff the current string quoted */
    int c, i, d, chr;
    int shell = tb_length(*tok) && isShellOrPipe(tb_values(*tok));
    int first = TRUE;

    if (actual != 0)
      *actual = EOS;
    if (src == 0)
      return src;

    /* first scan past any whitespace in the source string */
    while (isSPorTAB(*src))
      ++src;

    /* scan through the source string, which may be quoted */
    while ((c = *src) != EOS) {
      /* single-quotes override backslashes */
      if (quotef == SQUOTE) {
          if (c == SQUOTE) {
            if (*++src == SQUOTE) {
                /* double a quote to insert one */
                chr = *src++;
            } else {
                quotef = EOS;
                continue;
            }
          } else {
            chr = *src++;
          }
      } else if (quotef == EOS && c == SQUOTE) {
          quotef = SQUOTE;
          chr = *src++;
      } else if (c == BACKSLASH) {  /* process special characters */
          src++;
          if (*src == EOS)
            break;
          switch (c = *src++) {
          case 'r':
            chr = '\r';
            break;
          case 'n':
            chr = '\n';
            break;
          case 't':
            chr = '\t';
            break;
          case 'b':
            chr = '\b';
            break;
          case 'f':
            chr = '\f';
            break;
          case 'a':
            chr = '\a';
            break;
          case 's':
            chr = ' ';
            break;
          case 'e':
            chr = ESC;
            break;

          case 'x':
          case 'X':
            i = 2;            /* allow \xNN hex */
            c = 0;
            while (isAlnum(*src) && i--) {
                if (isDigit(*src)) {
                  d = *src - '0';
                } else if (isLower(*src)) {
                  d = *src - 'a' + 10;
                } else {
                  d = *src - 'A' + 10;
                }
                if (d > 15)
                  break;
                c = (c * 16) + d;
                src++;
            }
            chr = (char) c;
            break;

          default:
            if (c >= '0' && c <= '7') {
                i = 2;  /* allow \NNN octal */
                c -= '0';
                while (isDigit(*src)
                     && *src < '8'
                     && i--) {
                  c = (c * 8) + (*src++ - '0');
                }
            }
            chr = (char) c;
          }
      } else {
          /* check for the end of the token */
          if (quotef != EOS) {
            if (c == quotef) {
                src++;
                if (actual != 0)
                  *actual = *src;
                break;
            }
          } else {
            if (c == eolchar) {
                if (actual != 0)
                  *actual = *src;
                if (!isSPorTAB(c))
                  src++;
                break;
            } else if (c == DQUOTE) {
                quotef = c;
                /* note that leading quote
                   is included */
            } else if (isSPorTAB(c)) {
                if (actual != 0)
                  *actual = *src;
                break;
            }
          }

          chr = *src++; /* record the character */
      }
      if (first && shell && chr == DQUOTE) {
          /* eat the leading quote if we're re-gluing a shell command */
          /* EMPTY */ ;
      } else {
          tb_append(tok, chr);
      }
      first = FALSE;
    }

    /* scan past any whitespace remaining in the source string */
    while (isSPorTAB(*src))
      ++src;
    token_ended_line = isreturn(*src) || *src == EOS;

    tb_append(tok, EOS);
    /* both double and single-quote strings are reduced to a single-quote
     * string with no trailing quote, to simplify use in the caller.
     */
    if (tb_length(*tok) && *tb_values(*tok) == DQUOTE)
      *tb_values(*tok) = SQUOTE;
    return src;
}

/*
 * Convert the string 'src' into a string that we can read back with 'token()'.
 * If it is a shell-command, this will be a single-token.  Repeated shift
 * commands are multiple tokens.
 */
int
macroize(TBUFF ** p, TBUFF * src, int skip)
{
    register int c;
    char *ref = tb_values(src);     /* FIXME */
    int multi = !isShellOrPipe(ref);      /* shift command? */
    int count = 0;

    if (tb_init(p, esc_c) != 0) {
      size_t n, len = tb_length(src);
      char *txt = tb_values(src);

      TRACE(("macroizing %s\n", tb_visible(src)));
      (void) tb_append(p, DQUOTE);
      for (n = skip; n < len; n++) {
          c = txt[n];
          if (multi && count++)
            (void) tb_sappend(p, "\" \"");
          if (c == BACKSLASH || c == DQUOTE)
            (void) tb_append(p, BACKSLASH);
          (void) tb_append(p, c);
      }
      (void) tb_append(p, DQUOTE);
      TRACE(("macroized %s\n", tb_visible(*p)));
      return (tb_append(p, EOS) != 0);
    }
    return FALSE;
}

/* estimate maximum number of macro tokens, for allocating arrays */
unsigned
mac_tokens(void)
{
    const char *s = execstr;
    unsigned result = 0;

    while (s != 0 && *s != EOS) {
      if (isSpace(*s)) {
          s = skip_cblanks(s);
      } else if (*s != EOS) {
          result++;
          s = skip_ctext(s);
      }
    }
    return result;
}

/* fetch and isolate the next token from execstr */
int
mac_token(TBUFF ** tok)
{
    int savcle;
    const char *oldstr = execstr;

    savcle = clexec;
    clexec = TRUE;

    /* get and advance past token */
    execstr = get_token(execstr, tok, EOS, (int *) 0);

    clexec = savcle;
    return (execstr != oldstr);
}

/* fetch and isolate and evaluate the next token from execstr */
char *
mac_tokval(TBUFF ** tok)
{
    if (mac_token(tok) != 0) {
      /* evaluate the token */
      (void) tb_scopy(tok, tokval(tb_values(*tok)));
      return (tb_values(*tok));
    }
    tb_free(tok);
    return (0);
}

/*
 * get a macro line argument
 */
int
mac_literalarg(TBUFF ** tok)
{                       /* buffer to place argument */
    /* grab everything on this line, literally */
    (void) tb_scopy(tok, execstr);
    execstr += strlen(execstr);
    token_ended_line = TRUE;
    return TRUE;
}

#if OPT_MACRO_ARGS
/*
 * Parameter info is given as a keyword, optionally followed by a prompt string.
 */
static int
decode_parameter_info(TBUFF * tok, PARAM_INFO * result)
{
    char name[NSTRING];
    char text[NSTRING];
    char *s = vl_strncpy(name, tb_values(tok), sizeof(name));
    int code;

    if ((s = strchr(s, '=')) != 0) {
      vl_strncpy(text, tokval(s + 1), sizeof(text));
      *s = EOS;
    } else {
      text[0] = EOS;
    }
    if ((s = strchr(name, ':')) != 0)
      *s++ = EOS;

    code = choice_to_code(fsm_paramtypes_choices, name, strlen(name));
    /* split-up to work around gcc bug */
    result->pi_type = (PARAM_TYPES) code;
    if (result->pi_type >= 0) {
      result->pi_text = *text ? strmalloc(text) : 0;
#if OPT_ENUM_MODES
      if (s != 0)
          result->pi_choice = name_to_choices(s);
      else
          result->pi_choice = 0;
#endif
      return TRUE;
    }
    return FALSE;
}
#endif

static int
setup_macro_buffer(TBUFF * name, int flag)
{
#if OPT_MACRO_ARGS || OPT_ONLINEHELP
    static TBUFF *temp;
    unsigned limit = mac_tokens();
    unsigned count = 0;
#endif
#if OPT_ONLINEHELP
    TBUFF *helpstring = 0;    /* optional help string */
#endif
    char bname[NBUFN];        /* name of buffer to use */
    BUFFER *bp;

    /* construct the buffer name for the macro */
    if (flag < 0)
      (void) add_brackets(bname, tb_values(name));
    else
      (void) lsprintf(bname, MACRO_N_BufName, flag);

    /* set up the buffer for the new macro */
    if ((bp = bfind(bname, BFINVS)) == NULL) {
      mlforce("[Cannot create procedure]");
      return FALSE;
    }
    if (buffer_in_use(bp)) {
      return FALSE;
    }

    /* and make sure it is empty */
    if (!bclear(bp))
      return FALSE;

    set_rdonly(bp, bp->b_fname, MDVIEW);

    /* save this into the list of : names */
#if OPT_NAMEBST
    if (flag < 0) {
      CMDFUNC *cf = typecalloc(CMDFUNC);

      if (!cf)
          return no_memory("registering procedure name");

#ifdef CC_CANNOT_INIT_UNIONS
      cf->c_union = (void *) bp;
#else
      cf->cu.c_buff = bp;
#endif
      cf->c_flags = UNDO | REDO | CMD_PROC | VIEWOK;

#if OPT_MACRO_ARGS || OPT_ONLINEHELP
      if (limit != 0) {
          while (mac_token(&temp) == TRUE) {
            switch (toktyp(tb_values(temp))) {
#if OPT_ONLINEHELP
            case TOK_QUOTSTR:
                tb_copy(&helpstring, temp);
                break;
#endif
#if OPT_MACRO_ARGS
            case TOK_LITSTR:
                if (cf->c_args == 0) {
                  cf->c_args = typeallocn(PARAM_INFO, limit + 1);
                  if (cf->c_args == 0)
                      return no_memory("Allocating parameter info");
                }
                if (decode_parameter_info(temp, &(cf->c_args[count]))) {
                  cf->c_args[++count].pi_type = PT_UNKNOWN;
                  break;
                }
                /* FALLTHRU */
#endif
            default:
                mlforce("[Unexpected token '%s']", tb_values(temp));
                return FALSE;
            }
          }
      }
#endif

#if OPT_ONLINEHELP
      if (helpstring == 0)
          cf->c_help = strmalloc("User-defined procedure");
      else
          cf->c_help = strmalloc(tb_values(helpstring));
      tb_free(&helpstring);
#endif

      if (insert_namebst(tb_values(name), cf, FALSE) != TRUE)
          return FALSE;
      tb_copy(&(bp->b_procname), name);
    }
#endif /* OPT_NAMEBST */

    set_vilemode(bp);

    /* and set the macro store pointers to it */
    macrobuffer = bp;
    return TRUE;
}

/*
 * Set up a macro buffer and flag to store all executed command lines there. 
 * 'n' is the macro number to use.
 */
int
storemac(int f, int n)
{
    /* can't store macros interactively */
    if (!clexec) {
      mlforce("[Cannot store macros interactively]");
      return FALSE;
    }

    /* the numeric arg is our only way of distinguishing macros */
    if (!f) {
      mlforce("[No macro specified]");
      return FALSE;
    }

    /* range check the macro number */
    if (n < 1 || n > OPT_EXEC_MACROS) {
      mlforce("[Macro number out of range]");
      return FALSE;
    }

    return setup_macro_buffer((TBUFF *) 0, n);
}

#if OPT_PROCEDURES
/*
 * Set up a procedure buffer and flag to store all executed command lines
 * there.  'n' is the macro number to use.
 */
int
storeproc(int f, int n)
{
    static TBUFF *name;       /* procedure name */
    register int status;      /* return status */

    /* if a number was given, then they must mean macro N */
    if (f)
      return storemac(f, n);

    /* can't store procedures interactively */
    if (!clexec) {
      mlforce("[Cannot store procedures interactively]");
      return FALSE;
    }

    /* get the name of the procedure */
    tb_scopy(&name, "");
    if ((status = kbd_reply("Procedure name: ", &name,
                      eol_history, ' ', KBD_NORMAL, no_completion)) != TRUE)
      return status;

    return setup_macro_buffer(name, -1);
}

/*
 * Prompt for a procedure name and execute the named procedure
 */
int
execproc(int f, int n)
{
    static char name[NBUFN];

    int status;
    BUFFER *bp;               /* ptr to buffer to execute */
    char bufn[NBUFN];         /* name of buffer to execute */

    if ((status = mlreply("Execute procedure: ",
                    name, sizeof(name))) != TRUE) {
      return status;
    }

    /* construct a name for the procedure buffer */
    (void) add_brackets(bufn, name);

    /* find the buffer with that name */
    if ((bp = find_b_name(bufn)) == NULL) {
      return FALSE;
    }
    return dobuf(bp, f ? n : 1);

}

#endif

#if ! SMALLER
/*
 * Execute the contents of a buffer of commands
 */
int
execbuf(int f, int n)
{
    static char bufn[NBUFN];  /* name of buffer to execute */

    BUFFER *bp;               /* ptr to buffer to execute */
    int status;               /* status return */

    /* find out what buffer the user wants to execute */
    if ((status = ask_for_bname("Execute buffer: ", bufn, sizeof(bufn))) != TRUE)
      return status;

    /* find the pointer to that buffer */
    if ((bp = find_any_buffer(bufn)) == NULL) {
      return FALSE;
    }

    return dobuf(bp, f ? n : 1);
}
#endif

/*
 * free a while block pointer list, given a pointer 'wp' to the head of the list.
 */
static void
free_all_whiles(WHLOOP * wp)
{
    WHLOOP *tp;

    while (wp) {
      tp = wp->w_next;
      free((char *) wp);
      wp = tp;
    }
}

#define DIRECTIVE_CHAR '~'

#if ! SMALLER
#define DDIR_FAILED     -1
#define DDIR_COMPLETE    0
#define DDIR_INCOMPLETE  1
#define DDIR_FORCE       2
#define DDIR_HIDDEN      3
#define DDIR_QUIET       4

/*
 * When we get a "~local", we save the variable's name and its value.  Save the
 * value only the first time we encounter the variable.  We will restore the
 * variables when exiting from the current buffer macro.
 *
 * FIXME:  It would be nice (but more complicated) to implement local variables
 * within while/endwhile blocks.
 */
static int
push_variable(char *name)
{
    LOCALS *p;
    VALARGS args;

    TPRINTF(("...push_variable: {%s}%s\n", name, execstr));

    switch (toktyp(name)) {
    case TOK_STATEVAR:
    case TOK_TEMPVAR:
      break;
    default:
      return FALSE;
    }

    /* Check if we've saved this before */
    for (p = ifstk.locals; p != 0; p = p->next) {
      if (!strcmp(name, p->name))
          return TRUE;
    }

    /* We've not saved it - do it now */
    p = typealloc(LOCALS);
    p->next = ifstk.locals;
    p->name = strmalloc(name);
    p->m_value = 0;
    p->p_value = tokval(name);
    if (p->p_value == error_val) {
      /* special case: so we can delete vars */
      if (toktyp(name) != TOK_TEMPVAR) {
          free(p->name);
          free((char *) p);
          return FALSE;
      }
    } else {
      /* FIXME: this won't handle embedded nulls */
      if (find_mode(curbp, (*name == '$') ? (name + 1) : name, -TRUE, &args)
          && must_quote_token(p->p_value, strlen(p->p_value))) {
          TBUFF *tmp = 0;
          append_quoted_token(&tmp, p->p_value, strlen(p->p_value));
          tb_append(&tmp, EOS);
          p->p_value = tb_values(tmp);
          free(tmp);
      } else {
          p->m_value = strmalloc(p->p_value);
          p->p_value = 0;
      }
    }

    ifstk.locals = p;

    return TRUE;
}

/*
 * On exit from the buffer, restore variables declared local, in reverse order.
 */
static void
pop_variable(void)
{
    LOCALS *p = ifstk.locals;
    const char *value = (p->p_value != 0) ? p->p_value : p->m_value;

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

    ifstk.locals = p->next;
    TPRINTF(("...pop_variable(%s) %s\n", p->name, value));
    if (!strcmp(value, error_val)) {
      rmv_tempvar(p->name);
    } else {
      set_state_variable(p->name, value);
      if (p->m_value != 0)
          free(p->m_value);
    }
    free(p->name);
    free((char *) p);

    returnVoid();
}

static void
push_buffer(IFSTK * save)
{
    static const IFSTK new_ifstk =
    {0, 0, FALSE, EXACT, 0, 0, 0};  /* all 0's */

    *save = ifstk;
    save->shape = regionshape;
    save->motion = havemotion;
    save->prefix = with_prefix;

    ifstk = new_ifstk;
    havemotion = NULL;
    regionshape = EXACT;
    with_prefix = 0;
}

static void
pop_buffer(IFSTK * save)
{
    while (ifstk.locals)
      pop_variable();

    tb_free(&with_prefix);

    ifstk = *save;
    havemotion = ifstk.motion;
    regionshape = ifstk.shape;
    with_prefix = ifstk.prefix;
}

/*
 * Lookup a directory name.
 *    *cmdp points to a command
 *    length is the nominal maximum length (*cmdp is null-terminated)
 */
DIRECTIVE
dname_to_dirnum(char **cmdpp, int length)
{
    DIRECTIVE dirnum = D_UNKNOWN;
    int n;
    int code;
    const char *ptr = (*cmdpp);

    if ((--length > 0)
      && (*ptr++ == DIRECTIVE_CHAR)) {
      for (n = 0; n < length; n++) {
          if (!isAlnum(ptr[n]))
            length = n;
      }
      code = choice_to_code(fsm_directive_choices, ptr, length);
      /* split-up to work around gcc bug */
      dirnum = (DIRECTIVE) code;
      if (dirnum < 0)
          dirnum = D_UNKNOWN;
      else
          *cmdpp += (1 + length);
    }

    return dirnum;
}

#define directive_name(code) choice_to_name(fsm_directive_choices, code)

static int
unbalanced_directive(DIRECTIVE dirnum)
{
    mlforce("[Unexpected directive: %s]", directive_name(dirnum));
    return DDIR_FAILED;
}

static int
navigate_while(LINEPTR * lpp, WHLOOP * whlist)
{
    WHLOOP *wht;

    /* find the while control block we're on.  do what it says.
     *  (i.e. if we're at a break or while, we're sent to the
     *  bottom, if we're at an endwhile, we're sent to the top.)
     */
    for (wht = whlist; wht != 0; wht = wht->w_next) {
      if (wht->w_here == *lpp) {
          *lpp = wht->w_there;
          return TRUE;
      }
    }
    return FALSE;
}

/*
 * Do the bookkeeping to begin a directive.
 *    *cmdp points to the first token after the directive name
 *    dirnum is the number of the directive
 *    whlish is state/data for while/loops
 */
static int
begin_directive(char **const cmdpp,
            DIRECTIVE dirnum,
            WHLOOP * whlist,
            BUFFER *bp,
            LINEPTR * lpp)
{
    int status = DDIR_COMPLETE;     /* assume directive is self-contained */
    TBUFF *argtkn = 0;
    char *value;
    char *old_execstr = execstr;

    execstr = *cmdpp;

    switch (dirnum) {
    case D_LOCAL:
      while (mac_token(&argtkn) == TRUE) {
          value = tb_values(argtkn);
          if (push_variable(value) != TRUE) {
            mlforce("[cannot save '%s']", value);
            status = DDIR_FAILED;
            break;
          }
      }
      break;

    case D_WHILE:
      if (!ifstk.disabled) {
          if ((value = mac_tokval(&argtkn)) == 0) {
            status = DDIR_INCOMPLETE;
            break;
          } else if (scan_bool(value) != TRUE) {
            if (navigate_while(lpp, whlist) != TRUE)
                status = unbalanced_directive(dirnum);
          }
      }
      break;

    case D_BREAK:
      if (!ifstk.disabled) {
          if (navigate_while(lpp, whlist) != TRUE)
            status = unbalanced_directive(dirnum);
      }
      break;

    case D_ENDWHILE:
      if (!ifstk.disabled) {
          if (navigate_while(lpp, whlist) != TRUE)
            status = unbalanced_directive(dirnum);
          else
            *lpp = lback(*lpp);
      }
      break;

    case D_IF:
      ifstk.level++;
      if (!ifstk.disabled) {
          ifstk.fired = FALSE;
          ifstk.disabled = ifstk.level;
          if ((value = mac_tokval(&argtkn)) == 0)
            status = DDIR_INCOMPLETE;
          else if (scan_bool(value) == TRUE) {
            ifstk.disabled = 0;
            ifstk.fired = TRUE;
          }
      }
      break;

    case D_ELSEIF:
      if (ifstk.level == 0) {
          status = unbalanced_directive(dirnum);
      } else {
          if (ifstk.fired) {
            if (!ifstk.disabled)
                ifstk.disabled = ifstk.level;
          } else if ((value = mac_tokval(&argtkn)) == 0) {
            status = DDIR_INCOMPLETE;
          } else if (!ifstk.fired
                   && ifstk.disabled == ifstk.level
                   && (scan_bool(value) == TRUE)) {
            ifstk.disabled = 0;
            ifstk.fired = TRUE;
          }
      }
      break;

    case D_ELSE:
      if (ifstk.level == 0) {
          status = unbalanced_directive(dirnum);
      } else {
          if (ifstk.fired) {
            if (!ifstk.disabled)
                ifstk.disabled = ifstk.level;
          } else if (ifstk.disabled == ifstk.level) {
            ifstk.disabled = 0;
            ifstk.fired = TRUE;
          }
      }
      break;

    case D_ENDIF:
      if (ifstk.level == 0) {
          status = unbalanced_directive(dirnum);
      } else {
          ifstk.level--;
          if (ifstk.disabled > ifstk.level) {
            ifstk.disabled = 0;
            ifstk.fired = TRUE;
          }
      }
      break;

    case D_GOTO:
      if (!ifstk.disabled) {
          TBUFF *label = 0;
          LINEPTR glp;  /* line to goto */

          /* grab label to jump to */
          *cmdpp = (char *) get_token(*cmdpp, &label, EOS, (int *) 0);
#if VILE_MAYBE
          /* i think a simple re-eval would get us variant
           * targets, i.e. ~goto %somelabel.  */
          tb_scopy(label, tokval(tb_values(label)));
#endif
          value = tb_values(label);
          glp = label2lp(bp, value);
          if (glp == 0) {
            mlforce("[No such label \"%s\"]", value);
            status = DDIR_FAILED;
          } else {
            *lpp = glp;
          }
          tb_free(&label);
      }
      break;

    case D_RETURN:
      if (!ifstk.disabled)
          status = DDIR_INCOMPLETE;
      break;

    case D_FORCE:
      status = DDIR_FORCE;
      break;

    case D_HIDDEN:
      status = DDIR_HIDDEN;
      break;

    case D_QUIET:
      status = DDIR_QUIET;
      break;

    case D_ELSEWITH:
      if (!tb_length(with_prefix)) {
          status = unbalanced_directive(dirnum);
          break;
      }
      /* FALLTHRU */
    case D_WITH:
      execstr = skip_space_tab(execstr);
      tb_scopy(&with_prefix, execstr);
      status = DDIR_COMPLETE;
      break;

    case D_ENDWITH:
      tb_free(&with_prefix);
      status = DDIR_COMPLETE;
      break;

    case D_UNKNOWN:
    case D_ENDM:
      break;

    case D_TRACE:
      if (!ifstk.disabled) {
          if ((value = mac_tokval(&argtkn)) == 0) {
            mlforce("[Trace is %s]", tracemacros ? "on" : "off");
          } else {
            tracemacros = scan_bool(value);
          }
      }
      break;
    }
    execstr = old_execstr;
    tb_free(&argtkn);
    return status;
}

static WHLOOP *
alloc_WHLOOP(WHLOOP * whp, DIRECTIVE dirnum, LINEPTR lp)
{
    WHLOOP *nwhp;

    if ((nwhp = typealloc(WHLOOP)) == 0) {
      mlforce("[Out of memory during '%s' scan]",
            directive_name(dirnum));
      free_all_whiles(whp);
      return 0;
    }
    nwhp->w_here = lp;
    nwhp->w_there = 0;
    nwhp->w_type = dirnum;
    nwhp->w_next = whp;
    return nwhp;
}

static int
setup_dobuf(BUFFER *bp, WHLOOP ** result)
{
    int status = TRUE;
    LINEPTR lp;
    char *cmdp;
    WHLOOP *twhp, *whp = 0;
    DIRECTIVE dirnum;

    /* create the WHILE blocks */
    bp->b_dot.o = 0;
    *result = 0;
    for_each_line(lp, bp) {
      int i;

      bp->b_dot.l = lp;

      /* find the first printable character */
      cmdp = lp->l_text;
      i = llength(lp);
      while (i > 0 && isBlank(*cmdp)) {
          cmdp++;
          i--;
      }

      /* blank line */
      if (i <= 0)
          continue;

      dirnum = dname_to_dirnum(&cmdp, i);
      if (dirnum == D_WHILE ||
          dirnum == D_BREAK ||
          dirnum == D_ENDWHILE) {

          if ((whp = alloc_WHLOOP(whp, dirnum, lp)) == 0) {
            status = FALSE;
            break;
          }

          if (dirnum == D_ENDWHILE) {
            /* walk back, filling in unfilled
             * 'there' pointers on the breaks,
             * until we come to a while with an
             * unfilled 'there'.  fill it in, and
             * use its 'here' to fill in the
             * 'there' pointer on the endwhile.
             */
            twhp = whp->w_next;
            while (twhp) {
                if (!twhp->w_there) {
                  twhp->w_there = whp->w_here;
                  if (twhp->w_type == D_WHILE) {
                      whp->w_there = twhp->w_here;
                      break;
                  }
                }
                twhp = twhp->w_next;
            }
            if (!twhp) {
                /* no while for an endwhile */
                mlforce("[%s doesn't follow %s in '%s']",
                      directive_name(D_ENDWHILE),
                      directive_name(D_WHILE),
                      bp->b_bname);
                status = FALSE;
                break;
            }
          }

      }
    }

    /* no endwhile for a while or break */
    if (status == TRUE && whp && whp->w_type != D_ENDWHILE) {
      mlforce("[%s with no matching %s in '%s']",
            directive_name(D_WHILE),
            directive_name(D_ENDWHILE),
            bp->b_bname);
      status = FALSE;
    }

    *result = whp;
    return status;
}
#else
#define dname_to_dirnum(cmdpp,length) \
            ((*cmdpp)[0] == DIRECTIVE_CHAR && !strcmp((*cmdpp)+1, "endm") \
            ? D_ENDM \
            : D_UNKNOWN)
#define push_buffer(save)     /* nothing */
#define pop_buffer(save)      /* nothing */
#endif

#if OPT_TRACE && !SMALLER
static const char *
TraceIndent(int level, char *cmdp, int length)
{
    static const char indent[] = ".  .  .  .  .  .  .  .  ";
    switch (dname_to_dirnum(&cmdp, length)) {
    case D_ELSE:        /* FALLTHRU */
    case D_ELSEIF:            /* FALLTHRU */
    case D_ENDIF:
      if (level > 0)
          level--;
      break;
    default:
      break;
    }
    level = strlen(indent) - (3 * level);
    if (level < 0)
      level = 0;
    return &indent[level];
}
#define TRACE_INDENT(level, cmdp) TraceIndent(level, cmdp, linlen)
#else
#define TRACE_INDENT(level, cmdp) ""      /* nothing */
#endif

static int
line_in_buffer(BUFFER *bp, LINE *test_lp)
{
    LINE *lp;
    for_each_line(lp, bp) {
      if (lp == test_lp)
          return TRUE;
    }
    return FALSE;
}

#if ! SMALLER
static void
compute_indent(char *cmd, int length, int *indent, int *indstate)
{
    static BUFFER *save;

    /* reset caller's indent-state if we switched to a new macro */
    if (macrobuffer == 0
      || macrobuffer != save) {
      save = macrobuffer;
      if (indent != 0) {
          *indent = 0;
          *indstate = 0;
      }
    }
    if (cmd != 0
      && length != 0) {
      DIRECTIVE dirnum;
      char *base = cmd;
      int here = *indstate;
      int next = 0;

      cmd = skip_blanks(cmd);
      dirnum = dname_to_dirnum(&cmd, (length - (cmd - base)));
      switch (dirnum) {
      case D_IF:        /* FALLTHRU */
      case D_WHILE:           /* FALLTHRU */
      case D_WITH:
          next = 1;
          break;
      case D_ELSE:            /* FALLTHRU */
      case D_ELSEIF:          /* FALLTHRU */
      case D_ELSEWITH:
          --here;
          next = 1;
          break;
      case D_ENDIF:           /* FALLTHRU */
      case D_ENDM:            /* FALLTHRU */
      case D_ENDWHILE:  /* FALLTHRU */
      case D_ENDWITH:
          --here;
          break;
      default:
          break;
      }
      *indstate = next;
      if ((*indent += here) < 0)
          *indent = 0;
    }
}

static char *
add_indent(char *dst, int indent)
{
    while (indent-- > 0) {
      *dst++ = '\t';
    }
    return dst;
}
#endif

static void
handle_endm(void)
{
    if (macrobuffer != 0) {
      macrobuffer->b_dot.l = lforw(buf_head(macrobuffer));
      macrobuffer->b_dot.o = 0;
      macrobuffer = 0;
#if !SMALLER
      compute_indent((char *) 0, 0, (int *) 0, (int *) 0);
#endif
    }
}

static int
perform_dobuf(BUFFER *bp, WHLOOP * whlist)
{
    int status = TRUE;
    int glue = 0;       /* nonzero to append lines */
    LINEPTR lp;
    size_t linlen;
    DIRECTIVE dirnum;
    WINDOW *wp;
    char *linebuf = 0;        /* buffer holding copy of line executing */
    char *cmdp;               /* text to execute */
    int save_clhide = clhide;
    int save_no_errs = no_errs;
    int save_quiet = quiet;
    int indent = 0;
    int indstate = 0;

    static BUFFER *dobuferrbp = 0;

    TRACE((T_CALLED "perform_dobuf(bp=%p, whlist=%p) buffer '%s'\n",
         bp, whlist, bp->b_bname));

    bp->b_inuse++;

    /* starting at the beginning of the buffer */
    for_each_line(lp, bp) {
      bp->b_dot.l = lp;

      /* calculate the line length and make a local copy */

      if (llength(lp) <= 0)
          linlen = 0;
      else
          linlen = llength(lp);

      if (glue) {
          if ((linebuf = castrealloc(char, linebuf, glue + linlen + 1)) == 0) {
            status = no_memory("during macro execution");
            break;
          }
          cmdp = linebuf + glue;
      } else {
          FreeIfNeeded(linebuf);

#if !SMALLER
          compute_indent(lp->l_text, linlen, &indent, &indstate);
#endif
          if ((linebuf = cmdp = castalloc(char, indent + linlen + 1)) == 0) {
            status = no_memory("during macro execution");
            break;
          }
      }

      if (linlen == 0) {
          cmdp[0] = EOS;      /* make sure it ends */
      } else {
          char *src;
          char *dst;
#if !SMALLER
          if (!glue) {
            cmdp = add_indent(cmdp, indent);
          }
#endif
          (void) memcpy(cmdp, lp->l_text, linlen);
          cmdp[linlen] = EOS; /* make sure it ends */

          src = skip_space_tab(cmdp);
          dst = cmdp;
          /* compress out extra leading whitespace */
          while ((*dst++ = *src++) != EOS) {
            ;
          }
          linlen -= (size_t) (src - dst);
      }
      glue = 0;

      TPRINTF(("%s:%d (%d/%d):%s\n", bp->b_bname,
             line_no(bp, lp),
             ifstk.level, ifstk.disabled,
             cmdp));

      /*
       * If the last character on the line is a backslash, glue the
       * following line to the one we're processing.
       */
      if (lforw(lp) != buf_head(bp)
          && linlen != 0
          && cmdp[linlen - 1] == BACKSLASH) {
          glue = linlen + (size_t) (cmdp - linebuf) - 1;
          continue;
      }
      cmdp = skip_space_tab(linebuf);

      /* Skip comments and blank lines.
       * ';' for uemacs backward compatibility, and
       * '"' for vi compatibility
       */
      if (*cmdp == ';'
          || *cmdp == '"'
          || *cmdp == EOS) {
          continue;
      }
#if OPT_DEBUGMACROS
      /* echo lines and get user confirmation when debugging */
      if (tracemacros) {
          mlforce("<<<%s:%d/%d:%s>>>", bp->b_bname,
                ifstk.level, ifstk.disabled,
                str_visible(cmdp));
          (void) update(TRUE);

          /* and get the keystroke */
          if (ABORTED(keystroke())) {
            mlforce("[Macro aborted]");
            status = FALSE;
            break;
          }
      }
#endif
#if !SMALLER
      TRACE(("<<<%s%s:%d/%d%c%s%s>>>\n",
             (bp == curbp) ? "*" : "",
             bp->b_bname, ifstk.level, ifstk.disabled,
             ifstk.fired ? '+' : ' ',
             TRACE_INDENT(ifstk.level, cmdp),
             str_visible(cmdp)));
#endif

      if (*cmdp == DIRECTIVE_CHAR) {

          dirnum = dname_to_dirnum(&cmdp, (int) linlen);
          if (dirnum == D_UNKNOWN) {
            mlforce("[Unknown directive \"%s\"]", cmdp);
            status = FALSE;
            break;
          }

          /* service the ~endm directive here */
          if (dirnum == D_ENDM && !ifstk.disabled) {
            if (macrobuffer == 0) {
                mlforce("[No macro definition in progress]");
                status = FALSE;
                break;
            }
            handle_endm();
            continue;
          }
      } else {
          dirnum = D_UNKNOWN;
      }

      /* if macro store is on, just salt this away */
      if (macrobuffer != 0) {
          /* allocate the space for the line */
          if (addline(macrobuffer, linebuf, -1) == FALSE) {
            mlforce("[Out of memory while storing macro]");
            status = FALSE;
            break;
          }
          continue;
      }

      /* goto labels are no-ops */
      if (*cmdp == '*') {
          continue;
      }

      no_errs = save_no_errs;

#if ! SMALLER
      /* deal with directives */
      if (dirnum != D_UNKNOWN) {
          int code;

        next_directive:
          /* move past directive */
          cmdp = skip_space_tab(cmdp);

          code = begin_directive(&cmdp, dirnum, whlist, bp, &lp);
          if (code == DDIR_FAILED) {
            status = FALSE;
            break;
          } else if (code == DDIR_COMPLETE) {
            continue;
          } else if (code == DDIR_INCOMPLETE) {
            status = TRUE;    /* not exactly an error */
            break;
          } else if (code == DDIR_FORCE) {
            TRACE(("~force\n"));
            no_errs = TRUE;
          } else if (code == DDIR_HIDDEN) {
            TRACE(("~hidden\n"));
            clhide = TRUE;
          } else if (code == DDIR_QUIET) {
            TRACE(("~quiet\n"));
            quiet = TRUE;
          }

          /* check if next word is also a directive */
          cmdp = skip_space_tab(cmdp);
          if (*cmdp == DIRECTIVE_CHAR) {
            dirnum = dname_to_dirnum(&cmdp, (int) linlen);
            if (dirnum == D_UNKNOWN) {
                mlforce("[Unknown directive \"%s\"]", cmdp);
                status = FALSE;
                break;
            }
            goto next_directive;
          }
      } else if (*cmdp != DIRECTIVE_CHAR) {
          /* prefix lines with "WITH" value, if any */
          if (tb_length(with_prefix)) {
            char *temp = (char *) malloc(tb_length(with_prefix)
                                   + strlen(cmdp) + 2);
            (void) lsprintf(temp, "%s %s", tb_values(with_prefix), cmdp);
            free(linebuf);
            cmdp = linebuf = temp;
          }
      }
#endif

      /* if we are only scanning, come back here */
      if (ifstk.disabled)
          status = TRUE;
      else
          status = docmd(cmdp, TRUE, FALSE, 1);

      if (no_errs)
          status = TRUE;

      clhide = save_clhide;
      no_errs = save_no_errs;
      quiet = save_quiet;

      if (status == TRUE) {
          dobuferrbp = NULL;
      } else if (line_in_buffer(bp, lp)) {
          /* update window if visible */
          for_each_visible_window(wp) {
            if (wp->w_bufp == bp) {
                wp->w_dot.l = lp;
                wp->w_dot.o = 0;
                wp->w_flag |= WFHARD;
            }
          }
          /* set the buffer's dot */
          bp->b_dot.l = lp;
          bp->b_dot.o = 0;
          bp->b_wline.l = lforw(buf_head(bp));
          if (dobuferrbp == NULL) {
            dobuferrbp = bp;
            (void) swbuffer(bp);
            kbd_alarm();
          }
          break;
      } else {
          break;
      }
    }

    FreeIfNeeded(linebuf);

    if (--(bp->b_inuse) < 0)
      bp->b_inuse = 0;
    returnCode(status);
}

/*
 * execute the contents of a buffer
 */
int
dobuf(BUFFER *bp, int limit)
{
    int status = FALSE;
    WHLOOP *whlist;
    int save_vl_msgs;
    int save_cmd_count;
    int counter;

    static int dobufnesting;  /* flag to prevent runaway recursion */

    beginDisplay();
    if (++dobufnesting < 9) {

      save_vl_msgs = vl_msgs;
      save_cmd_count = cmd_count;

      /* macro arguments are readonly, so we do this once */
      if (save_arguments(bp) != ABORT) {
          vl_msgs = FALSE;

          for (counter = 1; counter <= limit; counter++) {
            if (dobufnesting == 1)
                cmd_count = counter;

#if ! SMALLER
            if (setup_dobuf(bp, &whlist) != TRUE) {
                status = FALSE;
            } else
#else
            whlist = NULL;
#endif
            {
                IFSTK save_ifstk;
                push_buffer(&save_ifstk);
                status = perform_dobuf(bp, whlist);
                pop_buffer(&save_ifstk);
            }

            handle_endm();
            free_all_whiles(whlist);

            if (status != TRUE)
                break;
          }

          restore_arguments(bp);
          vl_msgs = save_vl_msgs;
          cmd_count = save_cmd_count;
      }
    } else {
      mlwarn("[Too many levels of files]");
    }
    dobufnesting--;
    endofDisplay();

    return status;
}

/*
 * Common function for startup-file, and for :so command.
 */
int
do_source(char *fname, int n, int optional)
{
    int status = TRUE;        /* return status of name query */
    char *fspec;        /* full file spec */

    TRACE((T_CALLED "do_source(%s, %d, %d)\n", TRACE_NULL(fname), n, optional));

    /* look for the file in the configuration paths */
    fspec = cfg_locate(fname, LOCATE_SOURCE);

    /* nonexistant */
    if (fspec == NULL) {
      if (!optional)
          status = no_such_file(fname);
    } else {

      /* do it */
      while (n-- > 0)
          if ((status = dofile(fspec)) != TRUE)
            break;
    }

    returnCode(status);
}

#if ! SMALLER
/*
 * execute a series of commands in a file
 */
/* ARGSUSED */
int
execfile(int f GCC_UNUSED, int n)
{
    register int status;
    char fname[NFILEN];       /* name of file to execute */
    static TBUFF *last;

    if ((status = mlreply_file("File to execute: ", &last, FILEC_READ,
                         fname)) != TRUE)
      return status;

    return do_source(fname, n, FALSE);
}
#endif

static L_NUM
get_b_lineno(BUFFER *bp)
{
    L_NUM result;
    WINDOW *wp;

    /* try to save the original location, so we can restore it */
    if (curwp->w_bufp == bp)
      result = line_no(bp, curwp->w_dot.l);
    else if ((wp = bp2any_wp(bp)) != 0)
      result = line_no(bp, wp->w_dot.l);
    else
      result = line_no(bp, bp->b_dot.l);
    return result;
}

static void
set_b_lineno(BUFFER *bp, L_NUM n)
{
    WINDOW *wp;
    LINE *lp;

    for_each_line(lp, bp) {
      if (--n <= 0) {
          bp->b_dot.l = lp;
          bp->b_dot.o = 0;
          for_each_visible_window(wp) {
            if (wp->w_bufp == bp) {
                wp->w_dot = bp->b_dot;
                wp->w_flag |= WFMOVE;
            }
          }
          break;
      }
    }
}

/*
 * Yank a file named 'fname' into a buffer and execute it.
 * If there are no errors, delete the buffer on exit.
 */
int
dofile(char *fname)
{
    register BUFFER *bp;      /* buffer to place file to execute */
    register int status;      /* results of various calls */
    int clobber = FALSE;
    int original;

    TRACE((T_CALLED "dofile(%s)\n", TRACE_NULL(fname)));

    if (fname == 0 || *fname == EOS)
      returnCode(no_file_found());

    /*
     * Check first for the name, assuming it's a filename.  If we don't
     * find an existing buffer with that filename, create a buffer.
     */
    if ((bp = find_b_file(fname)) == 0) {
      if ((bp = make_bp(fname, 0)) == 0)
          returnCode(FALSE);
      clobber = TRUE;
    }

    bp->b_flag |= BFEXEC;

    /* try to save the original location, so we can restore it */
    original = get_b_lineno(bp);

    /* and try to read in the file to execute */
    if ((status = readin(fname, FALSE, bp, TRUE)) == TRUE) {

      /* go execute it! */
      bp->b_refcount += 1;
      status = dobuf(bp, 1);
      bp->b_refcount -= 1;

      /*
       * If no errors occurred, and if the buffer isn't displayed,
       * remove it, unless it was loaded before we entered this
       * function.  In that case, (try to) jump back to the original
       * location.
       */
      if (status != TRUE)
          (void) swbuffer(bp);
      else if ((bp->b_nwnd == 0) && (bp->b_refcount == 0) && clobber)
          (void) zotbuf(bp);
      else
          set_b_lineno(bp, original);
    }
    returnCode(status);
}

/*
 * Execute the contents of a numbered buffer 'bufnum'
 */
static int
cbuf(int f, int n, int bufnum)
{
    BUFFER *bp;               /* ptr to buffer to execute */
    char bufname[NBUFN];

    /* make the buffer name */
    (void) lsprintf(bufname, MACRO_N_BufName, bufnum);

    /* find the pointer to that buffer */
    if ((bp = find_b_name(bufname)) == NULL) {
      mlforce("[Macro %d not defined]", bufnum);
      return FALSE;
    }

    return dobuf(bp, f ? n : 1);
}

#include "neexec.h"

Generated by  Doxygen 1.6.0   Back to index