/* loop.c 	(c) copyright 1986 (Dan Heller) */

/*
 * Here is where the main loop for text mode exists. Also, all the
 * history is kept here and all the command parsing and execution
 * and alias expansion in or out of text/graphics mode is done here.
 */

#include "mush.h"
#include "version.h"

#ifdef BSD
#include <sys/wait.h>
#else
#ifndef SYSV
#include <wait.h>
#endif /* SYSV */
#endif /* BSD */

#ifdef REGCOMP
#include <regex.h>
#endif /* REGCOMP */

#define ever (;;)
#define MAXARGS		100
#define isdelimeter(c)	(index(" \t;|", c))

char *alias_expand(), *hist_expand(), *reference_hist(), *hist_from_str();
char *calloc();

struct history {
    int histno;
    char **argv;
    struct history *prev;
    struct history *next;
};
static struct history *hist_head, *hist_tail;
#define malloc(n)	(struct history *)calloc((unsigned)1,(unsigned)(n))
#define NULL_HIST	(struct history *)0

static char *last_aliased;
static int hist_size, print_only;

do_loop()
{
    register char *p, **argv;
    char	  **last_argv = DUBL_NULL, line[256];
    int   	  argc, c = (iscurses - 1);
#ifdef CURSES
    int		  save_echo_flg = FALSE;
#endif /* CURSES */

    /* catch the right signals -- see main.c for other signal catching */
    (void) signal(SIGINT, catch);
    (void) signal(SIGQUIT, catch);
    (void) signal(SIGHUP, catch);
    (void) signal(SIGTERM, catch);
    (void) signal(SIGCHLD,
#ifndef SYSV
			   sigchldcatcher
#else /* SYSV */
			   SIG_DFL
#endif /* SYSV */
			   );

    turnoff(glob_flags, IGN_SIGS);
    if (hist_size == 0) /* if user didn't set history in .rc file */
	hist_size = 1;

    for ever {
	if (setjmp(jmpbuf)) {
	    Debug("jumped back to main loop (%s: %d)\n", __FILE__,__LINE__);
#ifdef CURSES
	    if (c > 0) { /* don't pass last command back to curses_command() */
		iscurses = TRUE;
		c = hit_return();
	    }
#endif /* CURSES */
	}
	/* If we got back to here, we shouldn't be holding any file locks */
	droplocks();
#ifdef CURSES
	if (iscurses || c > -1) {
	    /* if !iscurses, we know that we returned from a curses-based
	     * call and we really ARE still in curses. Reset tty modes!
	     */
	    if (ison(glob_flags, ECHO_FLAG)) {
		turnoff(glob_flags, ECHO_FLAG);
		echo_off();
		save_echo_flg = TRUE;
	    }
	    if (!iscurses) {
		iscurses = TRUE;
		c = hit_return();
	    }
	    if (c < 0)
		c = 0;
	    if ((c = curses_command(c)) == -1 && save_echo_flg) {
		echo_on();
		turnon(glob_flags, ECHO_FLAG);
		save_echo_flg = FALSE;
	    }
	    continue;
	}
#endif /* CURSES */
	clear_msg_list(msg_list);
	(void) check_new_mail();

	/* print a prompt according to printf like format:
	 * (current message, deleted, unread, etc) are found in mail_status.
	 */
	mail_status(1);
	if (Getstr(line, sizeof(line), 0) > -1)
	    p = line;
	else {
	    if (isatty(0) && (p = do_set(set_options, "ignoreeof"))) {
		if (!*p)
		    continue;
		else
		    p = strcpy(line, p); /* so processing won't destroy var */
	    } else {
		putchar('\n');
		(void) mush_quit(0, DUBL_NULL);
		continue; /* quit may return if new mail arrives */
	    }
	}

	skipspaces(0);
	if (!*p && !(p = do_set(set_options, "newline"))) {
	    (void) readmsg(0, DUBL_NULL, msg_list);
	    continue;
	}
	if (!*p) /* if newline is set, but no value, then continue */
	    continue;

	/* upon error, argc = -1 -- still save in history so user can
	 * modify syntax error. if !argv, error is too severe.  We pass
	 * the last command typed in last_argv for history reference, and
	 * get back the current command _as typed_ (unexpanded by aliases
	 * or history) in last_argv.
	 */
	if (!(argv = make_command(p, &last_argv, &argc)))
	    continue;
	/* now save the old argv in a newly created history structure */
	(void) add_history(0, last_argv); /* argc is currently ignored */

	if (print_only) {
	    print_only = 0;
	    free_vec(argv);
	} else if (argc > -1)
	    (void) do_command(argc, argv, msg_list);
    }
}

/* Add a command to the history list
 */
/*ARGSUSED*/
add_history(un_used, argv)
char **argv;
{
    struct history *new;

    if (!(new = malloc(sizeof (struct history))))
	error("can't increment history");
    else {
	new->histno = ++hist_no;
	new->argv = argv;	/* this is the command _as typed_ */
	new->next = NULL_HIST;
	new->prev = hist_head;
	/* if first command, the tail of the list is "new" because
	 * nothing is in the list.  If not the first command, the
	 * head of the list's "next" pointer points to the new command.
	 */
	if (hist_head)
	    hist_head->next = new;
	else
	    hist_tail = new;
	hist_head = new;
    }
    /*
     * truncate the history list to the size of the history.
     * Free the outdated command (argv) and move the tail closer to front.
     * use a while loop in case the last command reset histsize to "small"
     */
    while (hist_head->histno - hist_tail->histno >= hist_size) {
	hist_tail = hist_tail->next;
	free_vec(hist_tail->prev->argv);
	xfree((char *) (hist_tail->prev));
	hist_tail->prev = NULL_HIST;
    }
}

/* make a command from "buf".
 * first, expand history references. make an argv from that and save
 * in last_argv (to be passed back and stored in history). After that,
 * THEN expand aliases. return that argv to be executed as a command.
 */
char **
make_command(start, last_argv, argc)
register char *start, ***last_argv;
int *argc;
{
    register char *p, **tmp;
    char buf[BUFSIZ];

    if (!last_argv)
	tmp = DUBL_NULL;
    else
	tmp = *last_argv;
    /* first expand history -- (here's where argc gets set)
     * pass the buffer, the history list to reference if \!* (or whatever)
     * result in static buffer (pointed to by p) -- even if history parsing is
     * ignored, do this to remove \'s behind !'s and verifying matching quotes
     */
    if (!(p = hist_expand(start, tmp, argc)) || Strcpy(buf, p) > sizeof buf)
	return DUBL_NULL;
    /* if history was referenced in the command, echo new command */
    if (*argc)
	puts(buf);

    /* argc may == -1; ignore this error for now but catch it later */
    if (!(tmp = mk_argv(buf, argc, 0)))
	return DUBL_NULL;

    /* save this as the command typed */
    if (last_argv)
	*last_argv = tmp;

    /* expand all aliases (recursively)
     * pass _this_ command (as typed and without aliases) to let aliases
     * with "!*" be able to reference the command line just typed.
     */
    if (alias_stuff(buf, *argc, tmp) == -1)
	return DUBL_NULL;

    if (!last_argv)
	free_vec(tmp);

    /* with everything expanded, build final argv from new buffer
     * Note that backslashes and quotes still exist. Those are removed
     * because argument final is 1.
     */
    tmp = mk_argv(buf, argc, 1);
    return tmp;
}

/* Return values from commands, see check_internal() */
static int last_status;			/* Changes after every command */
#ifndef MSGS_DYNAMIC
static char last_output[MAXMSGS];	/* Changes after SUCCESSFUL command */
#else
char* last_output;		/* Changes after SUCCESSFUL command */
#endif

/*
 * do the command specified by the argument vector, argv.
 * First check to see if argc < 0. If so, someone called this
 * command and they should not have! make_command() will return
 * an argv but it will set argc to -1 if there's a syntax error.
 */
do_command(argc, argv, list)
char **argv, list[];
{
    register char *p;
    char **tmp = argv, *next_cmd = NULL;
    int i, status = 0;
    long do_pipe = ison(glob_flags, DO_PIPE);

    if (argc <= 0) {
	turnoff(glob_flags, DO_PIPE);
	return -1;
    }

    clear_msg_list(list);

    for (i = 0; do_pipe >= 0 && argc; argc--) {
	p = argv[i];
	/* mk_argv inserts a boolean in argv[i][2] for separators */
	if ((!strcmp(p, "|") || !strcmp(p, ";")) && p[2]) {
	    if (do_pipe = (*p == '|'))
		turnon(glob_flags, DO_PIPE);
	    else if (next_cmd = argv[i+1])
		argv[i+1] = NULL, argc--;
	    argv[i] = NULL;
	    if ((status = exec_argv(i, argv, list)) <= -1)
		mac_flush();
	    else
		list_to_str(list, last_output);
	    turnon(glob_flags, IGN_SIGS); /* prevent longjmp */
	    /* if piping, then don't call next command if this one failed. */
	    if (status <= -1 && do_pipe) {
		print("Broken pipe.\n");
		do_pipe = -1, turnoff(glob_flags, DO_PIPE);
	    }
	    last_status = status;
	    /* if command failed and piping, or command worked and not piping */
	    if (do_pipe <= 0)
		status = 0, clear_msg_list(list);
	    /* else command worked and piping: set is_pipe */
	    else if (!status)
		turnon(glob_flags, IS_PIPE), turnoff(glob_flags, DO_PIPE);
	    argv[i] = p;
	    argv += (i+1);
	    i = 0;
	    turnoff(glob_flags, IGN_SIGS);
	} else
	    i++;
    }
    if (*argv && do_pipe >= 0) {
	status = exec_argv(i, argv, list);
	turnon(glob_flags, IGN_SIGS);
	if (status < 0) {
	    mac_flush();
	} else
	    list_to_str(list, last_output);
	last_status = status;
    }
    Debug("freeing: "), print_argv(tmp);
    free_vec(tmp);
    turnoff(glob_flags, DO_PIPE), turnoff(glob_flags, IS_PIPE);
    if (next_cmd) {
	if (tmp = mk_argv(next_cmd, &argc, 1)) {
	    turnoff(glob_flags, IGN_SIGS);
	    status = do_command(argc, tmp, list);
	    turnon(glob_flags, IGN_SIGS);
	} else
	    status = argc;
	xfree(next_cmd);
    }
    turnoff(glob_flags, IGN_SIGS);
    return status;
}

exec_argv(argc, argv, list)
register char **argv, list[];
{
    register int n;

    if (!argv || !*argv || argv[0][0] == '\\' && !argv[0][1]) {
	if (ison(glob_flags, IS_PIPE))
	    print("Invalid null command.\n");
	else if (ison(glob_flags, DO_PIPE)) {
	    set_msg_bit(list, current_msg);
	    return 0;
	}
	return -1;
    } else if (argv[0][0] == '\\') {
	/* Can't change *argv (breaks free_vec),
	 *  so shift to remove the backslash
	 */
	for (n = 0; argv[0][n]; n++)
	    argv[0][n] = argv[0][n+1];
    }
    Debug("executing: "), print_argv(argv);

    /* if interrupted during execution of a command, return -1 */
    if (isoff(glob_flags, IGN_SIGS) && setjmp(jmpbuf)) {
	Debug("jumped back to exec_argv (%s: %d)\n", __FILE__, __LINE__);
	return -1;
    }

    /* standard commands */
    for (n = 0; cmds[n].command; n++)
	if (!strcmp(argv[0], cmds[n].command))
	    return (*cmds[n].func)(argc, argv, list);

    /* ucb-Mail compatible commands */
    for (n = 0; ucb_cmds[n].command; n++)
	if (!strcmp(argv[0], ucb_cmds[n].command))
	    return (*ucb_cmds[n].func)(argc, argv, list);

    /* for hidden, undocumented commands */
    for (n = 0; hidden_cmds[n].command; n++)
	if (!strcmp(argv[0], hidden_cmds[n].command))
	    return (*hidden_cmds[n].func)(argc, argv, list);

    n = -1; /* default to failure */
    if ((isdigit(**argv) || index("^.*$-`{}", **argv))
			&& (n = get_msg_list(argv, list)) != 0) {
	if (n < 0)
	    return -1;
	else if (isoff(glob_flags, DO_PIPE))
	    for (n = 0; n < msg_cnt; n++)
		if (msg_bit(list, n)) {
		    display_msg((current_msg = n), (long)0);
		    unset_msg_bit(list, n);
		}
	return 0;
    } else {
	/* get_msg_list will set the current message bit if nothing parsed */
	if (n == 0)
	    unset_msg_bit(list, current_msg);
	if (strlen(*argv) == 1 && index("$^.", **argv)) {
	    if (!msg_cnt) {
		print("No messages.");
		return -1;
	    } else {
		if (**argv != '.')
		    current_msg = (**argv == '$') ? msg_cnt-1 : 0;
		set_msg_bit(list, current_msg);
		display_msg(current_msg, (long)0);
	    }
	    return 0;
	}
    }

    if (!istool && do_set(set_options, "unix")) {
	if (ison(glob_flags, IS_PIPE)) {
	    return pipe_msg(argc, argv, list);
	} else
	    execute(argv);  /* try to execute a unix command */
	return -1; /* doesn't affect messages! */
    }

    print("%s: command not found.\n", *argv);
    if (!istool)
	print("type '?' for valid commands, or type `help'\n");
    return -1;
}

/* recursively look for aliases on a command line.  aliases may
 * reference other aliases.
 */
alias_stuff(b, argc, Argv)
register char 	*b, **Argv;
{
    register char 	*p, **argv = DUBL_NULL;
    register int 	n = 0, i = 0, Argc;
    static int 		loops;
    int 		dummy;

    if (++loops == 20) {
	print("Alias loop.\n");
	return -1;
    }
    for (Argc = 0; Argc < argc; Argc++) {
	register char *h = Argv[n + ++i];
	register char *p2 = "";
	int sep;

	/* we've hit a command separator or the end of the line */
	if (h && strcmp(h, ";") && strcmp(h, "|"))
	    continue;

	/* create a new argv containing this (possible subset) of argv */
	if (!(argv = (char **)calloc((unsigned)(i+1), sizeof (char *))))
	    continue;
	sep = n + i;
	while (i--)
	    strdup(argv[i], Argv[n+i]);

	if ((!last_aliased || strcmp(last_aliased, argv[0]))
			&& (p = alias_expand(argv[0]))) {
	    /* if history was referenced, ignore the rest of argv
	     * else copy all of argv onto the end of the buffer.
	     */
	    if (!(p2 = hist_expand(p, argv, &dummy)))
		break;
	    if (!dummy)
		(void) argv_to_string(p2+strlen(p2), argv+1);
	    if (Strcpy(b, p2) > BUFSIZ) {
		print("Not enough buffer space.\n");
		break;
	    }
	    /* release old argv and build a new one based on new string */
	    free_vec(argv);
	    if (!(argv = mk_argv(b, &dummy, 0)))
		break;
	    if (alias_stuff(b, dummy, argv) == -1)
		break;
	} else
	    b = argv_to_string(b, argv);
	xfree(last_aliased), last_aliased = NULL;
	free_vec(argv);
	b += strlen(b);
	if (h) {
	    b += strlen(sprintf(b, " %s ", h));
	    while (++Argc < argc && (h = Argv[Argc]))
		if (Argc > sep && strcmp(h, ";"))
		    break;
	    n = Argc--;
	}
	i = 0;
    }
    xfree(last_aliased), last_aliased = NULL;
    --loops;
    if (Argc < argc) {
	free_vec(argv);
	return -1;
    }
    return 0;
}

char *
alias_expand(cmd)
register char *cmd;
{
    register char *p;
    register int x;

    if (!(p = do_set(functions, cmd)))
	return NULL;
    last_aliased = savestr(cmd); /* to be freed elsewhere; don't strdup! */
    if (isoff(glob_flags, WARNING))
	return p;
    for (x = 0; cmds[x].command; x++)
	if (!strcmp(cmd, cmds[x].command)) {
	    wprint("(real command: \"%s\" aliased to \"%s\")\n", cmd, p);
	    return p;
	}
    for (x = 0; ucb_cmds[x].command; x++)
	if (!strcmp(cmd, ucb_cmds[x].command)) {
	    wprint("(ucb-command: \"%s\" aliased to \"%s\")\n", cmd, p);
	    return p;
	}
    return p;
}

static int nonobang;

/* expand history references and separate message lists from other tokens */
char *
hist_expand(str, argv, hist_was_referenced)
register char *str, **argv;
register int *hist_was_referenced;
{
    static char   buf[BUFSIZ];
    register int  b = 0, inquotes = 0;
    int 	  first_space = 0, ignore_bang;

    ignore_bang = (ison(glob_flags, IGN_BANG) ||
		   do_set(set_options, "ignore_bang"));
    nonobang = !!do_set(set_options, "nonobang");

    if (hist_was_referenced)
	*hist_was_referenced = 0;
    while (*str) {
	while (!inquotes && isspace(*str))
	    str++;
	do  {
	    if (!*str)
		break;
	    if (b >= sizeof(buf)-1) {
		print("argument list too long.\n");
		return NULL;
	    }
	    if ((buf[b] = *str++) == '\'') {
		/* make sure there's a match! */
		inquotes = !inquotes;
	    }
	    if (!first_space && !inquotes && index("0123456789{}*$^.", buf[b])
			     && b && !index("0123456789{}-^. \t", buf[b-1])) {
		buf[b+1] = buf[b];
		buf[b++] = ' ';
		while ((buf[++b] = *str++) && index("0123456789-,${}.", buf[b]))
		    ;
		if (!buf[b])
		    str--;
		first_space++;
	    }
	    /* check for (;) (|) or any other delimiter and separate it from
	     * other tokens.
	     */
	    if (!inquotes && buf[b] != '\0' && isdelimeter(buf[b]) &&
		    (b < 0 || buf[b-1] != '\\')) {
		if (!isspace(buf[b]))
		    first_space = -1; /* resume msg-list separation */
		if (b && !isspace(buf[b-1]))
		    buf[b+1] = buf[b], buf[b++] = ' ';
		b++;
		break;
	    }
	    /*
	     * If double-quotes, just copy byte by byte, char by char,
	     *  but do remove backslashes from in front of !s
	     */
	    if (!inquotes && buf[b] == '"') {
		int B = b;
		while ((buf[++B] = *str++) && buf[B] != '"')
		    if (*str == '!' && buf[B] == '\\')
			buf[B] = '!', str++;
		if (buf[B])
		    b = B;
		else
		    str--;
		b++;
		continue;
	    }
	    if (buf[b] == '\\') {
		first_space = 1;	/* don't split escaped words */
		if ((buf[++b] = *str) == '!')
		    buf[--b] = '!';
		++str;
	    } else if (buf[b] == '!' && *str && *str != '\\' && !isspace(*str)
		       && !ignore_bang) {
		char word[BUFSIZ], *s;
		if (!(s = reference_hist(str, word, argv))) {
		    if (!nonobang)
			return NULL;
		} else {
		    str = s;
		    if (hist_was_referenced)
			*hist_was_referenced = 1;
		    if (strlen(word) + b >= sizeof buf) {
			print("argument list too long.\n");
			return NULL;
		    }
		    b += Strcpy(&buf[b], word) - 1;
		}
	    }
	    b++;
	} while (*str && (!isdelimeter(*str) || str[-1] == '\\'));
	if (!inquotes)
	    first_space++, buf[b++] = ' ';
    }
    buf[b] = 0;
    return buf;
}

/*
 * expand references to internal variables.  This allows such things
 * as $iscurses, $hdrs_only, etc. to work correctly.
 */
char *
check_internal(str)
register char *str;
{
    int ret_val = -1;
    static char version[80], get_status[4];

    if (!strcmp(str, "iscurses"))
	ret_val = (iscurses || ison(glob_flags, PRE_CURSES));
    else if (!strcmp(str, "istool"))
	ret_val = istool;
    else if (!strcmp(str, "hdrs_only"))
	ret_val = (hdrs_only && *hdrs_only);
    else if (!strcmp(str, "is_shell"))
	ret_val = is_shell;
    else if (!strcmp(str, "is_sending"))
	ret_val = (ison(glob_flags, IS_SENDING) != 0);
    else if (!strcmp(str, "redirect"))
	ret_val = (isatty(0) != 0);
    else if (!strcmp(str, "thisfolder"))
	return (mailfile && *mailfile) ? mailfile : NULL;
    else if (!strcmp(str, "status"))
	return sprintf(get_status, "%d", last_status);
    else if (!strcmp(str, "output"))
	return last_output;
    else if (!strcmp(str, "version")) {
	/* Create the version string ONCE, then re-use it. */
	if (!*version)
	    (void) sprintf(version, "%s (%d.%s.%d %s)",
		      MUSHNAME, RELEASE, REVISION, PATCHLEVEL, RELEASE_DATE);
	return version;
    }

    return ret_val > 0 ? "1" : ret_val == 0? "0" : NULL;
}

/*
 * Parse and expand a single variable reference.  Variable references
 * begin with a '$' and thereafter look like any of:
 *	$	$$ is the pid of the current process
 *	[%x]	$[%x] expands %x as a hdr_format character ($%x is same)
 *	(%x)	$(%x) expands %x as a prompt format character
 *	name	Value of variable "name" (error if not set)
 *	v:x	Modified expansion; v is any of above, x is any of
 *			h	head of a file pathname
 *			t	tail of a file pathname
 *			l	value converted to lowercase
 *			u	value converted to uppercase
 *	 		q	quote against further expansion (not yet)
 *		      <num>	select the <num>th space-separated field
 *	?name	Set/unset truth value of "name"
 *	{v}	Separate v (any of above) from surrounding text
 * A variable name may include alphabetics, numbers, or underscores but
 * must begin with an alphabetic or underscore.
 */
varexp(ref)
struct expand *ref;
{
    char *str = ref->orig, c, *p, *var, *end = NULL, *op = NULL;
    int do_bool, do_fmt = 0, expanded = 0;

    if (*str == '$') {
	/* Allow a $ all by itself to stand */
	if (!*++str || isspace(*str)) {
	    ref->exp = savestr("$");
	    ref->rest = str;
	    return 1;
	}
	/* Handle $?{name} for backwards compatibility */
	if (do_bool = (*str == '?'))
	    str++;
	if (*str == '{')
	    if (p = index(str + 1, '}')) {
		var = str + 1;
		end = p;
	    } else
		goto bad_var;
	else
	    var = str;
	/* Handle $?name and ${?name} (normal cases) */
	if (*var == '?') {
	    if (do_bool) /* backwards compatibility clash */
		goto bad_var;
	    ++var, do_bool = 1;
	}
	switch (*var) {
	    case '$':
		if (str[0] == '{' && str[2] != '}')
		    goto bad_var;
		else {
		    char buf[16];
		    (void) sprintf(buf, "%d", getpid());
		    ref->exp = savestr(buf);
		    ref->rest = (end ? end : var) + 1;
		    return 1;
		}
	    when '%':
		for (p = var + 1; *p && !index(" \t\n;|\"'$", *p); p++)
		    if (*p == ':') {
			if (!do_bool && !op) {
			    op = p;
			    do_fmt = p - var;
			} else
			    break;
		    }
		if (!do_fmt)
		    do_fmt = p - var;
		end = p;
	    when '[': case '(':  /*)*/
		p = any(var, *var == '(' ? ") \t\n" : "] \t\n");
		if (!p || isspace(*p))
		    goto bad_var;
		if (end && p > end)
		    goto bad_var;
		else {
		    var++;
		    do_fmt = p - var;
		    if (*++p == ':')
			op = p;
		    else
			end = p;
		}
		/* fall through */
	    default:
		if (!do_fmt && !isalpha(*var) && *var != '_')
		    goto bad_var;
		if (!end)
		    end = var + strlen(var);
		for (p = (op ? op : var + do_fmt) + 1; p < end; p++)
		    if (!do_bool && !op && *p == ':') {
			op = p;
		    } else if (!isalnum(*p) && *p != '_') {
			if (*str == '{') /*}*/
			    goto bad_var;
			end = p;
			break;
		    }
		if (op && op > end)
		    op = NULL;
	}
	/* replace the end of "var" (end) with a nul,
	 * and save char in `c'.  Similarly chop at op.
	 */
	c = *end, *end = 0;
	if (op)
	    *op++ = 0;

	if (!do_fmt && debug > 3)
	    printf("expanding (%s) ", var);

	/* get the value of the variable. */
	if (do_fmt) {
	    char c1 = var[do_fmt];
	    var[do_fmt] = 0;
	    if (debug > 3)
		printf("expanding (%s) ", var);
	    if (/*(*/ ')' == c1)
		p = format_prompt(current_msg, var);
	    else
		p = format_hdr(current_msg, var, FALSE) + 9;
	    var[do_fmt] = c1;
	} else if (!(p = check_internal(var)))
	    p = do_set(set_options, var);
	if (do_bool) {
	    ref->exp = savestr((p && (*p || !do_fmt)) ? "1" : "0");
	    expanded = 1;
	    if (debug > 3)
		printf("--> (%s)\n", p);
	} else if (p) {
	    if (debug > 3)
		printf("--> (%s)", p);
	    if (op && isdigit(*op)) {
		int varc, ix = atoi(op) - 1;
		char **varv = mk_argv(p, &varc, FALSE);
		/* Ignore non-fatal errors like unmatched quotes */
		if (varv && varc < 0)
		    for (varc = 0; varv[varc]; varc++)
			;
		if (ix < 0 || varc <= ix || !varv)
		    ref->exp = savestr("");
		else
		    ref->exp = savestr(varv[ix]);
		expanded = 1;
		free_vec(varv);
	    } else if (op) {
		char *p2 = rindex(p, '/');
		expanded = (*op == 'h' || *op == 't');
		if (*op == 't' && p2)
		    p = p2 + 1;
		else if (*op == 'h' && p2)
		    *p2 = 0;
		ref->exp = savestr(p);
		if (*op == 'h' && p2)
		    *p2 = '/';
		else if (*op == 'l' || *op == 'u') {
		    expanded = 1;
		    for (p = ref->exp; *p; p++)
			if (*op == 'u')
			    Upper(*p);
			else
			    Lower(*p);
		}
		if (!expanded) {
		    print("Unknown colon modifier :%c.\n", *op);
		    xfree(ref->exp);
		} else
		    if (debug > 3)
			printf("--> (%s)\n", p);
	    } else {
		ref->exp = savestr(p);
		expanded = 1;
		if (debug > 3)
		    printf("\n");
	    }
	} else {
	    print("%s: undefined variable\n", var);
	    expanded = 0;
	}
	*end = c; /* replace the null with the old character */
	if (op)
	    *--op = ':'; /* Put back the colon */
	ref->rest = end + (*str == '{'); /* } */
    }
    return expanded;
bad_var:
    print("Illegal variable name.\n");
    return 0;
}

/*
 * find mush variable references and expand them to their values.
 * variables are preceded by a '$' and cannot be within single
 * quotes.  Only if expansion has been made do we copy buf back into str.
 * We expand only as far as the first unprotected `;' separator in str,
 * to get the right behavior when multiple commands are on one line.
 * RETURN 0 on failure, 1 on success.
 */
variable_expand(str)
register char *str;
{
    register int     b = 0, inquotes = 0;
    char             buf[BUFSIZ], *start = str;
    int		     expanded = 0;

    while (*str && b < sizeof buf - 1) {
	if (*str == '~' && (str == start || isspace(*(str-1)))) {
	    register char *p, *tmp;
	    int x = 1;
	    /* Is it ever possible to have a user name start with tilde?
	     * On the assumption it isn't, recur in case of ~$foo or ~/$foo
	     */
	    if (str[1] != '~' && variable_expand(&str[1]) == 0)
		return 0;
	    if (p = any(str, " \t"))
		*p = 0;
	    tmp = getpath(str, &x);
	    /* if error, print message and return 0 */
	    if (x == -1) {
		wprint("%s: %s\n", str, tmp);
		return 0;
	    }
	    /* Use strncat instead of strncpy to get \0 terminator */
	    buf[b] = 0; /* Just in case */
	    b += strlen(strncat(buf + b, tmp, sizeof buf - 1 - b));
	    if (p && b < sizeof buf - 1) {
		*p = ' ';
		b += strlen(strncat(buf + b, p, sizeof buf - 1 - b));
	    }
	    expanded = 1;
	    break;
	}
	/* if single-quotes, just copy byte by byte, char by char ... */
	if ((buf[b] = *str++) == '\'' && !inquotes) {
	    while ((buf[++b] = *str++) && buf[b] != '\'')
		;
	    if (!buf[b])
		str--;
	} else if (!inquotes && buf[b] == '\\' && *str) {
	    buf[++b] = *str++;
	    b++;
	    continue;
	} else if (buf[b] == '"')
	    inquotes = !inquotes;
	/* If $ is eol, continue.  Variables must start with a `$'
	 * and continue with {, _, a-z, A-Z or it is not a variable.      }
	 */
	if (buf[b] == '$' && *str) {
	    struct expand expansion;
	    expansion.orig = str - 1;
	    if (varexp(&expansion)) {
		b += Strcpy(&buf[b],
			    quoteit(expansion.exp, inquotes? '"' : 0, FALSE));
		xfree(expansion.exp);
		str = expansion.rest;
		expanded = 1;
	    } else
		return 0;
	} else if (!inquotes && (buf[b] == ';' || buf[b] == '|')) {
	    while ((buf[++b] = *str++) && b < sizeof buf - 2)
		;
	    b++;
	    break;
	} else
	    b++;
    }
    buf[b] = 0;
    if (expanded) /* if any expansions were done, copy back into orig buf */
	(void) strcpy(start, buf);
    if (debug > 3)
	printf("expanded to: %s\n", start);
    return 1;
}

/* make an argv of space delimited character strings out of string "str".
 * place in "argc" the number of args made.  If final is true, then expand
 * variables and file names and remove quotes and backslants according to
 * standard.
 */
char **
mk_argv(str, argc, final)
register char *str;
int *argc;
{
    register char	*s = NULL, *p;
    register int	tmp, err = 0, unq_sep = 0;
    char		*newargv[MAXARGS], **argv, *p2, c, buf[BUFSIZ];
    char		strbuf[BUFSIZ];

    str = strcpy(strbuf, str);

    if (debug > 3)
	(void) printf("Working on: %s\n",str);
    /* If final is true, do variable expansions first */
    if (final) {
	(void) strcpy(buf, str);
	str = buf;
	if (!variable_expand(str))
	    return DUBL_NULL;
    }
    *argc = 0;
    while (*str && *argc < MAXARGS) {
	while (isspace(*str))
	    ++str;
	/* When we have hit an unquoted `;', final must be true,
	 * so we're finished.  Stuff the rest of the string at the
	 * end of the argv -- do_command will pass it back later,
	 * for further processing -- and break out of the loop.
	 * NOTE: *s is not yet valid the first time through this
	 * loop, so unq_sep should always be initialized to 0.
	 */
	if (unq_sep && s && *s == ';') {
	    if (*str) { /* Don't bother saving a null string */
		newargv[*argc] = savestr(str);
		(*argc)++;
	    }
	    break;
	}
	if (*str) {		/* found beginning of a word */
	    unq_sep = 0;	/* innocent until proven guilty */
	    s = p = str;
	    do  {
		if (p - s >= sizeof buf-1) {
		    print("argument list too long.\n");
		    return DUBL_NULL;
		}
		if (*str == ';' || *str == '|')
		    unq_sep = final; /* Mark an unquoted separator */
		if ((*p = *str++) == '\\') {
		    if (final && (*str == ';' || *str == '|'))
			--p; /* Back up to overwrite the backslash */
		    if (*++p = *str) /* assign and compare to NUL */
			str++;
		    continue;
		}
		if (p2 = index("\"'", *p)) {
		    register char c2 = *p2;
		    /* you can't escape quotes inside quotes of the same type */
		    if (!(p2 = index(str, c2))) {
			if (final)
			    print("Unmatched %c.\n", c2);
			err++;
			p2 = str;
		    }
		    /* This is the intent of the following loop:
		     *  tmp = (int)(p2 - str) + 1;
		     *  (void) strncpy(p + !final, str, tmp);
		     * The strncpy() can't be used directly because
		     * it may be overlapping, which fails sometimes.
		     */
		    /* copy up to and including quote */
		    for (tmp = 0; tmp < (int)(p2 - str) + 1; tmp++)
			p[tmp+!final] = str[tmp];
		    p += tmp - 2 * !!final; /* change final to a boolean */
		    if (*(str = p2))
			str++;
		}
	    } while (++p, *str && (!isdelimeter(*str) || str[-1] == '\\'));
	    if (c = *str) /* set c = *str, check for null */
		str++;
	    *p = 0;
	    if (*s) {
		/* To differentiate real separators from quoted or
		 * escaped ones, always store 3 chars:
		 *  1) The separator character
		 *  2) A nul (string terminator)
		 *  3) An additional boolean (0 or 1)
		 * The boolean is checked by do_command.  Note that this
		 * applies only to "solitary" separators, i.e. those not
		 * part of a larger word.
		 */
		if (final && (!strcmp(s, ";") || !strcmp(s, "|"))) {
		    char *sep = savestr("xx"); /* get 3 char slots */
		    sep[0] = *s, sep[1] = '\0', sep[2] = unq_sep;
		    newargv[*argc] = sep;
		} else
		    newargv[*argc] = savestr(s);
		(*argc)++;
	    }
	    *p = c;
	}
    }
    if (!*argc)
	return DUBL_NULL;
    /* newargv[*argc] = NULL; */
    if (!(argv = (char **)calloc((unsigned)((*argc)+1), sizeof(char *)))) {
	perror("mk_argv: calloc");
	return DUBL_NULL;
    }
    for (tmp = 0; tmp < *argc; tmp++)
	argv[tmp] = newargv[tmp];
    if (err)
	*argc = -1;
    else if (debug > 3)
	(void) printf("Made argv: "), print_argv(argv);
    return argv;
}

/*
 * Report a history parsing error.
 * Suppress the message if nonobang is true.
 */
#define hist_error	if (nonobang) {;} else print

/*
 * reference previous history from syntax of str and place result into buf
 * We know we've got a history reference -- we're passed the string starting
 * the first char AFTER the '!' (which indicates history reference)
 */
char *
reference_hist(str, buf, hist_ref)
register char *str, **hist_ref;
char buf[];
{
    int 	   relative; /* relative from current hist_no */
    int 	   old_hist, argstart = 0, lastarg, argend = 0, n = 0;
    register char  *p, *rb = NULL, **argv = hist_ref;
    struct history *hist;

    buf[0] = 0;
    if (*str == '{')
	if (!(rb = index(str, '}'))) {   /* { */
	    hist_error("Unmatched '}'");
	    return NULL;
	} else
	    *rb = 0, ++str;
    relative = *str == '-';
    if (index("!:$*", *str)) {
	old_hist = hist_no;
	if (*str == '!')
	    str++;
    } else if (isdigit(*(str + relative)))
	str = my_atoi(str + relative, &old_hist);
    else if (!(p = hist_from_str(str, &old_hist))) {
	if (rb) /* { */
	    *rb = '}';
	return NULL;
    } else
	str = p;
    if (relative)
	old_hist = (hist_no - old_hist) + 1;
    if (old_hist == hist_no) {
	if (!(argv = hist_ref))
	    hist_error("You haven't done anything yet!\n");
    } else {
	if (old_hist <= hist_no-hist_size || old_hist > hist_no ||
	    old_hist <= 0) {
	    if (old_hist <= 0)
		hist_error("You haven't done that many commands, yet.\n");
	    else
		hist_error("Event %d %s.\n", old_hist,
		    (old_hist > hist_no)? "hasn't happened yet": "expired");
	    if (rb) /* { */
		*rb = '}';
	    return NULL;
	}
	hist = hist_head;
	while (hist && hist->histno != old_hist)
	    hist = hist->prev;
	if (hist)
	    argv = hist->argv;
    }
    if (!argv) {
	if (rb) /* { */
	    *rb = '}';
	return NULL;
    }
    while (argv[argend+1])
	argend++;
    lastarg = argend;
    if (*str && index(":$*-", *str)) {
	int isrange;
	if (*str == ':' && isdigit(*++str))
	    str = my_atoi(str, &argstart);
	if (isrange = (*str == '-'))
	    str++;
	if (!isdigit(*str)) {
	    if (*str == 'p')
		str++, print_only = 1;
	    else if (*str == '*') {
		str++;
		if (!isrange) {
		    if (argv[0]) {
			if (argv[1])
			    argstart = 1;
			else {
			    if (rb) /* { */
				*rb = '}';
			    return (rb ? rb + 1 : str);
			}
		    } else
			argstart = 0;
		}
	    } else if (*str == '$') {
		if (!isrange)
		    argstart = argend;
		str++;
	    } else if (isrange && argend > argstart)
		argend--; /* unspecified end of range implies last-1 arg */
	    else 
		argend = argstart; /* no range specified; use arg given */
	} else
	    str = my_atoi(str, &argend);
    }
    if (argstart > lastarg || argend > lastarg || argstart > argend) {
	hist_error("Bad argument selector.\n");
	if (rb) /* { */
	    *rb = '}';
	return (nonobang ? rb ? rb + 1 : str : NULL);
    }
    if (debug > 3)
	print("history expanding from "), print_argv(argv);
    while (argstart <= argend) {
	n += Strcpy(&buf[n], argv[argstart++]);
	buf[n++] = ' ';
    }
    buf[--n] = 0;
    if (rb) /* { */
	*rb = '}';
    return (rb ? rb + 1 : str);
}

/* find a history command that contains the string "str"
 * place that history number in "hist" and return the end of the string
 * parsed: !?foo (find command with "foo" in it) !?foo?bar (same, but add "bar")
 * in the second example, return the pointer to "bar"
 */
char *
hist_from_str(str, hist_number)
register char *str;
register int *hist_number;
{
    register char *p = NULL, c = 0;
    int 	  full_search = 0, len, found;
    char 	  buf[BUFSIZ];
    struct history *hist;
    char *rex = NULL;
#ifndef REGCMP
# ifdef REGCOMP
    regex_t re;
    int code;
# else
    extern char   *re_comp();
# endif
#else
    extern char   *regcmp(), *regex();
#endif /* REGCMP */

    /* For !{something}, the {} are stripped in reference_hist() */
    if (*str == '?') {
	if (p = index(++str, '?'))
	    c = *p, *p = 0;
	else
	    p = str + strlen(str);
	full_search = 1;
    } else {
	p = str;
	while (*p && *p != ':' && !isspace(*p))
	    p++;
	c = *p, *p = 0;
    }
    if (*str) {
#ifndef REGCMP
# ifdef REGCOMP
	if ((code = regcomp(&re, str, REG_EXTENDED)) != 0)
# else
	if (re_comp(str))
# endif
#else
	if ((rex = regcmp(str, NULL)) == NULL)	/* Assign and test */
#endif /* REGCMP */
	{
	    if (c)
		*p = c;
	    return NULL;
	}
    } else {
	*hist_number = hist_no;
	if (c)
	    *p = c;
	return (*p == '?' ? p + 1 : p);
    }
    len = strlen(str);
    /* move thru the history in reverse searching for a string match. */
    for (hist = hist_head; hist; hist = hist->prev) {
	if (full_search) {
	    (void) argv_to_string(buf, hist->argv);
	    Debug("Checking for (%s) in (#%d: %s)\n", str, hist->histno, buf);
	}
	if (!full_search) {
	    (void) strcpy(buf, hist->argv[0]);
	    Debug("Checking for (%s) in (#%d: %*s)\n",
		str, hist->histno, len, buf);
	    found = !strncmp(buf, str, len);
	} else
	    found =
#ifndef REGCMP
# ifdef REGCOMP
		regexec(&re, buf, 0, 0, 0) == 0
# else
		re_exec(buf)
# endif
#else
		regex(rex, buf, NULL) != NULL;
#endif /* REGCMP */

	if (found) {
	    *hist_number = hist->histno;
	    Debug("Found it in history #%d\n", *hist_number);
	    *p = c;
#ifdef REGCMP
	    /* XXX: regfree */
	    free(rex);
#else
# ifdef REGCOMP
	    regfree(&re);
# endif
#endif
	    return (*p == '?' ? p + 1 : p);
	}
    }
    hist_error("%s: event not found\n", str);
    *p = c;
#ifdef REGCMP
    free(rex);
#else
# ifdef REGCOMP
    /* XXX: regfree */
    free(rex);
# endif
#endif
    return NULL;
}

disp_hist(n, argv)  /* argc not used -- use space for the variable, "n" */
register int n;
char **argv;
{
    register int	list_num = TRUE, num_of_hists = hist_size;
    register int	reverse = FALSE;
    struct history	*hist = hist_tail;

    while (*++argv && *argv[0] == '-') {
	n = 1;
	do  switch(argv[0][n]) {
		case 'h': list_num = FALSE;
		when 'r': reverse = TRUE; hist = hist_head;
		otherwise: return help(0, "history", cmd_help);
	    }
	while (argv[0][++n]);
    }

    if (!hist) {
	print("No history yet.\n");
	return -1;
    }
    if (*argv)
	if (!isdigit(**argv)) {
	    print("history: badly formed number\n");
	    return -1;
	} else
	    num_of_hists = atoi(*argv);

    if (num_of_hists > hist_size || num_of_hists > hist_no)
	num_of_hists = min(hist_size, hist_no);

    if (!reverse)
	while (hist_no - hist->histno >= num_of_hists) {
	    Debug("skipping %d\n", hist->histno);
	    hist = hist->next;
	}

    (void) do_pager(NULL, TRUE);
    for (n = 0; n < num_of_hists && hist; n++) {
	char buf[256];
	if (list_num)
	    (void) do_pager(sprintf(buf, "%4.d  ", hist->histno), FALSE);
	(void) argv_to_string(buf, hist->argv);
	(void) do_pager(buf, FALSE);
	if (do_pager("\n", FALSE) == -1)
	    break;
	hist = (reverse)? hist->prev : hist->next;
    }
    (void) do_pager(NULL, FALSE);
    return 0;
}

init_history(newsize)
{
    if ((hist_size = newsize) < 1)
	hist_size = 1;
}
