
/*
 * EXECOM.C
 *
 * (c)1986 Matthew Dillon     9 October 1986
 *
 *    Handles command parsing.
 *
 *
 */

#include "shell.h"

#define ST_COND   0x01
#define ST_NAME   0x02
#define ST_EXA	  0x04	    /*	exact match required	*/


struct COMMAND {
    int (*func)();
    short minargs;
    short stat;
    int   val;
    char *name;
};

extern char *format_insert_string();
extern char *mpush(), *exarg();

extern int do_run(), do_number();
extern int do_quit(), do_set_var(), do_unset_var(), do_setenv(), do_unsetenv();
extern int do_printenv(), do_echo(), do_source(), do_mv();
extern int do_cd(), do_rm(), do_mkdir(), do_history();
extern int do_mem(), do_cat(), do_dir();
extern int do_foreach(), do_return(), do_if(), do_label(), do_goto();
extern int do_forever(), do_inc(), do_abortline();
extern int do_input(), do_ver(), do_sleep(), do_help();
extern int do_strhead(), do_strtail(), do_cp();
extern int do_comment(), do_shellstat(), do_ipc(), do_cldres();

static struct COMMAND Command[] = {
    do_run	, 0,  ST_NAME,	  0 ,	"\001",
    do_number	, 0,  0,	  0 ,	"\001",
    do_quit	, 0,  ST_EXA,	  0 ,	"quit",
    do_quit	, 0,  ST_EXA,	  0 ,	"exit",
    do_set_var	, 0,  0, LEVEL_SET  ,	"set",
    do_unset_var, 0,  0, LEVEL_SET  ,	"unset",
    do_setenv	, 2,  0,	  0 ,	"setenv",
    do_unsetenv , 0,  0,	  0 ,	"unsetenv",
    do_printenv , 0,  0,	  0 ,	"printenv",
    do_set_var	, 0,  0, LEVEL_ALIAS,	"alias",
    do_unset_var, 0,  0, LEVEL_ALIAS,	"unalias",
    do_echo	, 0,  0,	  0 ,	"echo",
    do_source	, 0,  0,	  0 ,	"source",
    do_mv	, 2,  ST_EXA,	  0 ,	"mv",
    do_cd	, 0,  0,	  0 ,	"cd",
    do_cd	, 0,  0,	 -1 ,	"pwd",
    do_rm	, 0,  ST_EXA,	  0 ,	"rm",
    do_mkdir	, 0,  ST_EXA,	  0 ,	"mkdir",
    do_history	, 0,  0,	  0 ,	"history",
    do_mem	, 0,  0,	  0 ,	"mem",
    do_cat	, 0,  0,	  0 ,	"cat",
    do_cp	, 1,  ST_EXA,	  0 ,	"cp",
    do_dir	, 0,  0,	  0 ,	"dir",
    do_dir	, 0,  0,	 -1 ,	"devinfo",
    do_foreach	, 3,  0,	  0 ,	"foreach",
    do_forever	, 1,  0,	  0 ,	"forever",
    do_return	, 0,  ST_EXA,	  0 ,	"return",
    do_ipc	, 1,  0,	  0 ,	"ipc",
    do_cldres	, 0,  0,	  0 ,	"cldres",
    do_if	, 1,  ST_COND,	  0 ,	"if",
    do_if	, 0,  ST_COND,	  1 ,	"else",
    do_if	, 0,  ST_COND,	  2 ,	"endif",
    do_label	, 1,  ST_COND,	  0 ,	"label",
    do_goto	, 1,  0,	  0 ,	"goto",
    do_strhead	, 3,  0,	  0 ,	"strhead",
    do_strtail	, 3,  0,	  0 ,	"strtail",
    do_inc	, 1,  0,	  1 ,	"inc",
    do_inc	, 1,  0,	  -1,	"dec",
    do_input	, 1,  0,	  0,	"input",
    do_ver	, 0,  0,	  0,	"version",
    do_sleep	, 0,  0,	  0,	"sleep",
    do_help	, 0,  0,	  0,	"help",
    do_abortline, 0,  0,	  0,	"abortline",
    do_comment	, 2,  0,	  0,	"comment",
    do_shellstat, 0,  0,	  0,	"shellstat",
    NULL	, 0,  0,	  0 ,	NULL
};


static unsigned char elast;	     /* last end delimeter */
static char Cin_ispipe, Cout_ispipe;

exec_command(base)
char *base;
{
    register char *scr;
    register int i;
    char buf[32];

    if (!H_stack) {
	add_history(base);
	sprintf(buf, "%ld", H_tail_base + H_len);
	set_var(LEVEL_SET, V_HISTNUM, buf);
    }
    scr = malloc((strlen(base) << 2) + 2);    /* 4X */
    preformat(base, scr);
    i = fcomm(scr, 1);
    return ((i) ? -1 : 1);
}


isalphanum(c)
char c;
{
    if (c >= '0' && c <= '9')
	return (1);
    if (c >= 'a' && c <= 'z')
	return (1);
    if (c >= 'A' && c <= 'Z')
	return (1);
    if (c == '_')
	return (1);
    return (0);
}

preformat(s, d)
register char *s, *d;
{
    register short si, di, qm;

    si = di = qm = 0;
    while (s[si] == ' ' || s[si] == 9)
	++si;
    while (s[si]) {
	if (qm && s[si] != '\"' && s[si] != '\\') {
	    d[di++] = s[si++] | 0x80;
	    continue;
	}
	switch (s[si]) {
	case ' ':
	case 9:
	    d[di++] = ' ';
	    while (s[si] == ' ' || s[si] == 9)
		++si;
	    if (s[si] == 0 || s[si] == '|' || s[si] == ';')
		--di;
	    break;
	case '*':
	case '?':
	    d[di++] = 0x80;	 /* follow thru */
	case '!':
	    d[di++] = s[si++];
	    break;
	case '#':
	    d[di++] = '\0';
	    while (s[si])
		++si;
	    break;
	case ';':
	case '|':
	    d[di++] = s[si++];
	    while (s[si] == ' ' || s[si] == 9)
		++si;
	    break;
	case '\\':
	    d[di++] = s[++si] | 0x80;
	    if (s[si]) ++si;
	    break;
	case '\"':
	    qm = 1 - qm;
	    ++si;
	    break;
	case '^':
	    d[di++] = s[++si] & 0x1F;
	    if (s[si]) ++si;
	    break;
	case '$':         /* search end of var name and place false space */
	    d[di++] = 0x80;
	    d[di++] = s[si++];
	    while (isalphanum(s[si]))
		d[di++] = s[si++];
	    d[di++] = 0x80;
	    break;
	default:
	    d[di++] = s[si++];
	    break;
	}
    }
    d[di++] = 0;
    d[di]   = 0;
#ifdef DEBUG
    if (SDebug & 0x01) {
	fhprintf (Cerr, "PREFORMAT: %ld :%s:\n", strlen(d), d);
    }
#endif
}

/*
 * process formatted string.  ' ' is the delimeter.
 *
 *    0: check '\0': no more, stop, done.
 *    1: check $.     if so, extract, format, insert
 *    2: check alias. if so, extract, format, insert. goto 1
 *    3: check history or substitution, extract, format, insert. goto 1
 *
 *    4: assume first element now internal or disk based command.
 *
 *    5: extract each ' ' or 0x80 delimited argument and process, placing
 *	 in av[] list (except 0x80 args appended).  check in order:
 *
 *	       '$'         insert string straight
 *	       '>'         setup stdout
 *	       '>>'        setup stdout flag for append
 *	       '<'         setup stdin
 *	       '*' or '?'  do directory search and insert as separate args.
 *
 *	       ';' 0 '|'   end of command.  if '|' setup stdout
 *			    -execute command, fix stdin and out (|) sets
 *			     up stdin for next guy.
 */


fcomm(str, freeok)
register char *str;
{
    static short alias_count;
    char *istr;
    char *nextstr;
    char *command;
    char *pend_alias = NULL;
    char err = 0;

    ++alias_count;
    mpush_base();
    if (*str == 0)
	goto done1;
step1:
    if (alias_count == MAXALIAS) {
	Eputs("Alias Loop");
	err = 20;
	goto done1;
    }
    if (*str == '$') {
	if (istr = get_var (LEVEL_SET, str + 1))
	    str = format_insert_string(str, istr, &freeok);
    }
    istr = NULL;
    if (*(unsigned char *)str < 0x80)
	istr = get_var (LEVEL_ALIAS, str);  /* only if not \command */
    *str &= 0x7F;			   /* remove \ teltail	   */
    if (istr) {
	if (*istr == '%') {
	    pend_alias = istr;
	} else {
	    str = format_insert_string(str, istr, &freeok);
	    goto step1;
	}
    }
    if (*str == '!') {
	istr = get_history(str);
	replace_head(istr);
	str = format_insert_string(str, istr, &freeok);
	goto step1;
    }
    nextstr = str;
    command = exarg(&nextstr);
    if (*command == 0)
	goto done0;
    if (pend_alias == 0) {
	register int ccno;
	ccno = find_command(command);
	if (Command[ccno].stat & ST_COND)
	    goto skipgood;
    }
    if (SDisable) {
	while (elast && elast != ';' && elast != '|')
	    exarg(&nextstr);
	goto done0;
    }
skipgood:
    {
	register char *arg, *ptr, *scr;
	short redir;
	short doexpand;
	short cont;
	short inc;

	ac = 1;
	av[0] = command;
step5:						/* ac = nextac */
	if (!elast || elast == ';' || elast == '|')
	    goto stepdone;

	av[ac] = NULL;
	cont = 1;
	doexpand = redir = inc = 0;

	while (cont && elast) {
	    ptr = exarg(&nextstr);
	    inc = 1;
	    arg = "";
	    cont = (elast == 0x80);
	    switch (*ptr) {
	    case '<':
		redir = -2;	     /* -2 so ++ still keeps it negative */
	    case '>':
		++redir;	     /* normal >  */
		arg = ptr + 1;
		if (*arg == '>') {
		    redir = 2;	      /* append >> (not impl yet) */
		    ++arg;
		}
		cont = 1;
		break;
	    case '$':
		if ((arg = get_var(LEVEL_SET, ptr + 1)) == NULL)
		    arg = ptr;
		break;
	     case '*':
	     case '?':
		doexpand = 1;
		arg = ptr;
		break;
	    default:
		arg = ptr;
		break;
	    }

	    /* Append arg to av[ac] */

	    for (scr = arg; *scr; ++scr)
		*scr &= 0x7F;
	    if (av[ac]) {
		register char *old = av[ac];
		av[ac] = mpush(strlen(arg)+1+strlen(av[ac]));
		strcpy(av[ac], old);
		strcat(av[ac], arg);
	    } else {
		av[ac] = mpush(strlen(arg)+1);
		strcpy(av[ac], arg);
	    }
	    if (elast != 0x80)
		break;
	}

	/* process expansion */

	if (doexpand) {
	    char **eav, **ebase;
	    int eac;

	    eav = ebase = expand(av[ac], &eac);
	    inc = 0;
	    if (eav) {
		if (ac + eac + 2 > MAXAV) {
		    ierror (NULL, 506);
		    err = 1;
		} else {
		    QuickSort(eav, eac);
		    for (; eac; --eac, ++eav)
			av[ac++] = strcpy(mpush(strlen(*eav)+1), *eav);
		}
		free_expand (ebase);
	    }
	}

	/* process redirection	*/

	if (redir && !err) {
	    register char *file = (doexpand) ? av[--ac] : av[ac];

	    if (redir < 0) {
		Cin_name = file;
	    } else {
		Cout_name = file;
		Cout_append = (redir == 2);
	    }
	    inc = 0;
	}

	/* check elast for space */

	if (inc) {
	    ++ac;
	    if (ac + 2 > MAXAV) {
		ierror (NULL, 506);
		err = 1;		/* error condition */
		elast = 0;		/* don't process any more arguemnts */
	    }
	}
	if (elast == ' ')
	    goto step5;
    }
stepdone:
    av[ac] = NULL;

    /* process pipes via files */

    if (elast == '|' && !err) {
	static int which;	      /* 0 or 1 in case of multiple pipes */
	which = 1 - which;
	Cout_name = (which) ? Pipe1 : Pipe2;
	Cout_ispipe = 1;
    }

    if (err)
	goto done0;

    {
	register long i, len;
	char save_elast;
	register char *avline;

	save_elast = elast;
	for (i = len = 0; i < ac; ++i)
	    len += strlen(av[i]) + 1;
	avline = malloc(len+2);
	for (len = 0, i = ((pend_alias) ? 1 : 0); i < ac; ++i) {
#ifdef DEBUG
	    if (SDebug & 0x02) fhprintf (Cerr, "AV[%2ld] %ld :%s:\n", i, strlen(av[i]), av[i]);
#endif
	    strcpy(avline + len, av[i]);
	    len += strlen(av[i]);
	    if (i + 1 < ac)
		avline[len++] = ' ';
	}
	avline[len] = 0;
	if (pend_alias) {                               /* special % alias */
	    register char *ptr, *scr;
	    for (ptr = pend_alias; *ptr && *ptr != ' '; ++ptr);
	    set_var (LEVEL_SET, pend_alias + 1, avline);
	    free (avline);
	    scr = malloc((strlen(ptr) << 2) + 2);
	    preformat (ptr, scr);
	    fcomm (scr, 1);
	    unset_var (LEVEL_SET, pend_alias + 1);
	} else {					/* normal command  */
	    register short ccno;
	    register long cin, cout;

	    ccno = find_command (command);
	    if ((Command[ccno].stat & ST_NAME) == 0) {
		if (Cin_name) {
		    cin = Cin;
		    Cin = Open(Cin_name, 1005);
		    if (Cin == 0) {
			ierror (NULL, 504);
			err = 1;
			Cin = cin;
			Cin_name = NULL;
		    }
		}
		if (Cout_name) {
		    cout = Cout;
		    if (Cout_append) {
			if (Cout = Open(Cout_name, 1005))
			Seek(Cout, 0, 1);
		    } else {
			Cout = Open(Cout_name, 1006);
		    }
		    if (Cout == NULL) {
			err = 1;
			ierror (NULL, 504);
			Cout = cout;
			Cout_name = NULL;
			Cout_append = 0;
		    }
		}
	    }
	    if (ac < Command[ccno].minargs + 1) {
		ierror (NULL, 500);
		err = -1;
	    } else {
		i = (*Command[ccno].func)(avline, Command[ccno].val);
		if (i < 0)
		    i = 20;
		err = i;
	    }
	    free (avline);
	    if (Exec_ignoreresult == 0 && Lastresult != err) {
		Lastresult = err;
		seterr();
	    }
	    if ((Command[ccno].stat & ST_NAME) == 0) {
		if (Cin_name) {
		    Close(Cin);
		    Cin = cin;
		}
		if (Cout_name) {
		    Close(Cout);
		    Cout = cout;
		    Cout_append = 0;
		}
	    }
	}
	if (Cin_ispipe && Cin_name)
	    DeleteFile(Cin_name);
	if (Cout_ispipe) {
	    Cin_name = Cout_name;	  /* ok to assign.. static name */
	    Cin_ispipe = 1;
	} else {
	    Cin_name = NULL;
	}
	Cout_name = NULL;
	Cout_ispipe = Cout_append = 0;
	elast = save_elast;
    }
    mpop_tobase();                      /* free arguments   */
    mpush_base();                       /* push dummy base  */

done0:
    {
	register char *str;
#ifdef DEBUG
	if (SDebug & 0x10)
	    printf ("err = %ld, E_stack = %ld\n", err, E_stack);
#endif
	if (err && E_stack == 0) {
	    str = get_var(LEVEL_SET, V_EXCEPT);
	    if (err >= ((str)?atoi(str):1)) {
		if (str) {
		    ++H_stack;
		    ++E_stack;
		    exec_command(str);
		    --E_stack;
		    --H_stack;
		} else {
		  Exec_abortline = 1;
		}
	    }
	}
#ifdef DEBUG
	if (SDebug & 0x10) {
	    printf ("elast = %ld  Exec_abortline = %ld\n", elast, Exec_abortline);
	    printf ("nextstr = %s\n", nextstr);
	}
#endif
	if (elast != 0 && Exec_abortline == 0)
	    err = fcomm(nextstr, 0);
	Exec_abortline = 0;
	if (Cin_name)
	    DeleteFile(Cin_name);
	Cin_name = NULL;
	Cin_ispipe = 0;
    }
done1:
    mpop_tobase();
    if (freeok)
	free(str);
    --alias_count;
    return ((int)err);                  /* TRUE = error occured    */
}


char *
exarg(ptr)
unsigned char **ptr;
{
    register unsigned char *end;
    register unsigned char *start;

    start = end = *ptr;
    while (*end && *end != 0x80 && *end != ';' && *end != '|' && *end != ' ')
	++end;
    elast = *end;
    *end = '\0';
    *ptr = end + 1;
    return ((char *)start);
}

static char **Mlist;

mpush_base()
{
    register char *str;

    str = malloc(5);
    *(char ***)str = Mlist;
    str[4] = 0;
    Mlist = (char **)str;
}

char *
mpush(bytes)
{
    register char *str;

    str = malloc(5 + bytes);
    *(char ***)str = Mlist;
    str[4] = 1;
    Mlist = (char **)str;
    return (str + 5);
}

mpop_tobase()
{
    register char *next;

    while (Mlist) {
	next = *Mlist;
	if (((char *)Mlist)[4] == 0) {
	    free (Mlist);
	    Mlist = (char **)next;
	    break;
	}
	free (Mlist);
	Mlist = (char **)next;
    }
}


/*
 * Insert 'from' string in front of 'str' while deleting the
 * first entry in 'str'.  if freeok is set, then 'str' will be
 * free'd
 */

char *
format_insert_string(str, from, freeok)
char *str;
char *from;
int *freeok;
{
    register char *new1, *new2;
    register unsigned char *strskip;
    register short len;

    for (strskip = (UBYTE *)str; *strskip && *strskip != ' ' && *strskip != ';' && *strskip != '|' && *strskip != 0x80; ++strskip);
    len = strlen(from);
    new1 = malloc((len << 2) + 2);
    preformat(from, new1);
    len = strlen(new1) + strlen(strskip);
    new2 = malloc(len+2);
    strcpy(new2, new1);
    strcat(new2, strskip);
    new2[len+1] = 0;
    free (new1);
    if (*freeok)
	free (str);
    *freeok = 1;
    return (new2);
}


find_command(str)
register char *str;
{
    register short i;
    register short len = strlen(str);
    register struct COMMAND *cmd;

    if (*str >= '0'  &&  *str <= '9')
	return (1);
    for (i = 0, cmd = Command; cmd->func; ++cmd, ++i) {
	if (cmd->stat & ST_EXA) {
	    if (strcmp(str, cmd->name) == 0)
		return((int)i);
	} else {
	    if (strncmp (str, cmd->name, len) == 0)
		return ((int)i);
	}
    }
    return (0);
}


do_help()
{
    register struct COMMAND *com;

    for (com = &Command[2]; com->func; ++com)
	fhprintf (Cout, "%s ", com->name);
    Oputs ("");
    return (0);
}


