/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * A copy of the CDDL is also available via the Internet at
 * http://www.opensource.org/licenses/cddl1.txt
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

#if defined(sun)
#pragma ident	"@(#)cmd.c	1.17	08/01/29 SMI"
#endif

#include "defs.h"

/*
 * Copyright 2008-2020 J. Schilling
 *
 * @(#)cmd.c	1.54 20/03/25 2008-2020 J. Schilling
 */
#ifndef lint
static	UConst char sccsid[] =
	"@(#)cmd.c	1.54 20/03/25 2008-2020 J. Schilling";
#endif

/*
 * UNIX shell
 */

#include	"sym.h"

static	unsigned char *getstor	__PR((int asize));
	struct trenod *makefork	__PR((int flgs, struct trenod *i));
static struct trenod *makelist	__PR((int type, struct trenod *i,
						struct trenod *r));
	struct trenod *cmd	__PR((int sym, int flg));
static	struct trenod *list	__PR((int flg));
static	struct trenod *term	__PR((int flg));
static	struct regnod *syncase	__PR((int esym));
static	struct trenod *item	__PR((BOOL flag));
static	int	skipnl		__PR((int flag));
static	struct ionod *inout	__PR((struct ionod *lastio));
static	void	chkword		__PR((void));
static	void	chksym		__PR((int sym));
static	void	prsym		__PR((int sym));
static	void	synbad		__PR((void));

int	abegin;

/* ======== storage allocation for functions ======== */

static unsigned char *
getstor(asize)
	int asize;
{
	if (fndef)
		return ((unsigned char *)alloc(asize));
	else
		return (getstak((Intptr_t)asize));
}


/* ========	command line decoding	======== */

struct trenod *
makefork(flgs, i)
	int	flgs;
	struct trenod *i;
{
	struct forknod *t;

	t = (struct forknod *)getstor(sizeof (struct forknod));
	t->forktyp = flgs|TFORK;
	t->forktre = i;
	t->forkio = 0;
	return ((struct trenod *)t);
}

static struct trenod *
makelist(type, i, r)
	int	type;
	struct trenod *i, *r;
{
	struct lstnod *t = 0;

	if (i == 0 || r == 0) {
		synbad();
	} else {
		t = (struct lstnod *)getstor(sizeof (struct lstnod));
		t->lsttyp = type;
		t->lstlef = i;
		t->lstrit = r;
	}
	return ((struct trenod *)t);
}

/*
 * cmd
 *	empty			(Only if called with MTFLG)
 *	list
 *	list & [ cmd ]
 *	list [ ; cmd ]
 *
 * This is the main parser entry point that is called as cmd(NL, MTFLG)
 * from main.c::exfile(). MTFLG permits empty commands in the main loop.
 *
 * It cmd() is called with NLFLG in flg at top level, this causes the whole
 * file to be read at once and a single treenode * to be constructed from that.
 * This method is e.g. used by "eval" and ".".
 */
struct trenod *
cmd(sym, flg)
	int	sym;
	int		flg;
{
	struct trenod *i, *e;

	wdset = 0;
	i = list(flg);
	if (wdval == NL) {
		if (flg & NLFLG) {
			wdval = ';';
			chkpr();
		}
	} else if (i == 0 && (flg & MTFLG) == 0) {
		synbad();
	}
	switch (wdval) {
	case '&':
		if (i)
			i = makefork(FAMP, i);
		else
			synbad();
		/* FALLTHROUGH */

	case ';':
		if ((e = cmd(sym, flg | MTFLG)) != NULL)
			i = makelist(TLST, i, e);
		else if (i == 0) {
			synbad();
		}
		break;

	case EOFSYM:
		if (sym == NL)
			break;
		/* FALLTHROUGH */

	default:
		if (sym)
			chksym(sym);
	}
	return (i);
}

/*
 * list
 *	term
 *	list && term
 *	list || term
 */
static struct trenod *
list(flg)
	int	flg;
{
	struct trenod *r;
	int		b;
	r = term(flg);
	while (r && ((b = (wdval == ANDFSYM)) != 0 || wdval == ORFSYM))
		r = makelist((b ? TAND : TORF), r, term(NLFLG));
	return (r);
}

/*
 * term
 *	item
 *	item |^ term
 */
static struct trenod *
term(flg)
	int	flg;
{
	struct trenod *t;

	abegin = 1;
	reserv++;
	if (flg & NLFLG)
		skipnl(flg);
	else
		word();

#if defined(DO_NOTSYM) || defined(DO_TIME)
#if defined(DO_NOTSYM) && !defined(DO_TIME)
	if (wdval == NOTSYM) {
#else
#if defined(DO_TIME) && !defined(DO_NOTSYM)
	if (wdval == TIMSYM) {
#else
	if (wdval == NOTSYM || wdval == TIMSYM) {
#endif
#endif
		struct parnod	*p;

		p = (struct parnod *)getstor(sizeof (struct parnod));
		p->partyp = TTIME;
		if (wdval == NOTSYM)
			p->partyp = TNOT;
		p->partre = term(0);
		t = treptr(p);
	} else
#endif

	/*
	 * ^ is a relic from the days of UPPER CASE ONLY tty model 33s
	 */
	if ((t = item(DOIOFLG | (flg&SEMIFLG))) != 0 &&
	    (wdval == '^' || wdval == '|')) {
		struct trenod	*left;
		struct trenod	*right;
		struct trenod	*tr;
		int		pio = wdnum & IOUFD;

		if (wdnum == 0)
			pio = STDOUT_FILENO;
		left = makefork(FPOU|pio, t);
		tr = term(NLFLG);
#if defined(DO_PIPE_SYNTAX_E) || defined(DO_PIPE_PARENT)
		if (tr == NULL)
			synbad();
#endif
#ifdef	DO_PIPE_PARENT
		/*
		 * Build a tree that allows us to make all pipe commands
		 * children from the main shell process.
		 *
		 * Special right nodes:
		 * -	TFORK	() Avoid to add another fork
		 * -	TFIL	Pipe to right pipe, avoid fork
		 */
		switch (tr->tretyp & COMMSK) {
		case TFORK:
			tr->tretyp |= FPIN;
			right = tr;
			break;

		case TFIL:
		case TCOM:
			right = makefork(FPIN, tr);
			right->tretyp |= TNOFORK;
			break;
		default:
			right = makefork(FPIN, tr);
		}
		if ((t->tretyp & COMMSK) == TCOM)
			left->tretyp |= TNOFORK;
		return (makelist(TFIL, left, right));
#else	/* !DO_PIPE_PARENT */
		right = makefork(FPIN, tr);
		return (makefork(0, makelist(TFIL, left, right)));
#endif
	}
	return (t);
}

/*
 * case statement, parse things after "case <word> in" here
 */
static struct regnod *
syncase(esym)
	int	esym;
{
#ifdef	DO_POSIX_CASE
	wdset |= IN_CASE;
#endif
	skipnl(0);

#ifdef	DO_POSIX_CASE
	if (wdval == 0 &&
	    syslook(wdarg->argval,
			    reserved, no_reserved) == esym) {
		wdval = esym;
	}
#endif

	if (wdval == esym) {
		wdset &= ~IN_CASE;
		return (0);
	} else {
		struct regnod *r =
		    (struct regnod *)getstor(sizeof (struct regnod));
		struct argnod *argp;

		r->regptr = 0;
		r->regflag = 0;
#ifdef	DO_POSIX_CASE
		if (wdval == '(')
			skipnl(0);
#endif
		for (;;) {
			if (fndef) {
				unsigned char	*p;

				argp = wdarg;
				wdarg = (struct argnod *)
						alloc(length(argp->argval) +
							1 + BYTESPERWORD);
				p = movstr(argp->argval, wdarg->argval);
				/*
				 * Hack: A second Nul byte is needed if the
				 * word ends in "\\\0".
				 */
				*++p = '\0';
			}

			wdarg->argnxt = r->regptr;
			r->regptr = wdarg;

			/* 'in' is not a reserved word in this case */
			if (wdval == INSYM) {
				wdval = 0;
			}
			if (wdval || (word() != ')' && wdval != '|'))
				synbad();
			if (wdval == '|')
				word();
			else
				break;
		}
		wdset &= ~IN_CASE;
		r->regcom = cmd(0, NLFLG | MTFLG);
		if (wdval == ECSYM) {			/* ;; */
			r->regnxt = syncase(esym);
#ifdef	DO_FALLTHR_CASE
		} else if (wdval == ECASYM) {		/* ;& */
			r->regnxt = syncase(esym);
			r->regflag = 1;
		} else if (wdval == ECARSYM) {		/* ;;& */
			r->regnxt = syncase(esym);
			r->regflag = 2;
#endif
		} else {
			chksym(esym);
			r->regnxt = 0;
		}
		return (r);
	}
}

/*
 * item
 *
 *	( cmd ) [ < in  ] [ > out ]
 *	word word* [ < in ] [ > out ]
 *	if ... then ... else ... fi
 *	for ... while ... do ... done
 *	case ... in ... esac
 *	begin ... end
 */
#ifdef	PROTOTYPES
static struct trenod *
item(BOOL flag)
#else
static struct trenod *
item(flag)
	BOOL	flag;
#endif
{
	struct trenod *r;
	struct ionod *io;

	if (flag & DOIOFLG)
		io = inout((struct ionod *)0);
	else
		io = 0;
	abegin--;
	switch (wdval) {
	case CASYM:
		{
			struct swnod *t;

			t = (struct swnod *)getstor(sizeof (struct swnod));
			r = (struct trenod *)t;

			chkword();
			if (fndef)
				t->swarg = make(wdarg->argval);
			else
				t->swarg = wdarg->argval;
			skipnl(0);
			chksym(INSYM | BRSYM);
			t->swlst = syncase(wdval == INSYM ? ESSYM : KTSYM);
			t->swtyp = TSW;
			break;
		}

	case IFSYM:
		{
			int	w;
			struct ifnod *t;
			struct trenod *tt = NULL; /* Make silly gcc quiet */

			t = (struct ifnod *)getstor(sizeof (struct ifnod));
			r = (struct trenod *)t;

			t->iftyp = TIF;
			t->iftre = cmd(THSYM, NLFLG);
			t->thtre = cmd(ELSYM | FISYM | EFSYM, NLFLG);
			t->eltre = ((w = wdval) == ELSYM ?
					cmd(FISYM, NLFLG) :
						(w == EFSYM ?
						(wdval = IFSYM, tt = item(0)) :
						0));
			if (w == EFSYM) {
#ifdef	DO_SETIO_NOFORK
				if (tt->tretyp != TSETIO)
					return (r);
				/*
				 * Let post-command IO redirection apply to the
				 * whole command and not only to the else part.
				 */
				t->eltre = forkptr(tt)->forktre;
				forkptr(tt)->forktre = (struct trenod *)t;
				r = tt;
#endif
				return (r);
			}
			break;
		}

	case FORSYM:
	case SELSYM:
		{
			struct fornod *t;

			t = (struct fornod *)getstor(sizeof (struct fornod));
			r = (struct trenod *)t;

			t->fortyp = wdval == FORSYM ? TFOR : TSELECT;
			t->forlst = 0;
			chkword();
			if (fndef)
				t->fornam = make(wdarg->argval);
			else
				t->fornam = wdarg->argval;
			if (skipnl(SEMIFLG) == INSYM) {
#ifdef	DO_POSIX_FOR
				if (word()) {
					/*
					 * "for i in; do cmd ...; done" is valid
					 */
					if (wdval != NL && wdval != ';')
						synbad();
					t->forlst = (struct comnod *)\
					    getstor(sizeof (struct comnod));
					t->forlst->comtyp = TCOM;
					t->forlst->comio = NULL;
					t->forlst->comarg = NULL;
					t->forlst->comset = NULL;
#else
				if (word()) {
					synbad();
#endif
				} else {
					nohash++;
					t->forlst = (struct comnod *)item(0);
					nohash--;
				}

				if (wdval != NL && wdval != ';')
					synbad();
				if (wdval == NL)
					chkpr();
				skipnl(0);
#ifdef	DO_POSIX_FOR
			} else if (wdval == ';') {
				/*
				 * "for i; do cmd ...; done" is valid syntax
				 * see Austin bug #581
				 */
				skipnl(0);
#endif
			}
			chksym(DOSYM | BRSYM);
			t->fortre = cmd(wdval == DOSYM ? ODSYM : KTSYM, NLFLG);
			break;
		}

	case WHSYM:
	case UNSYM:
		{
			struct whnod *t;

			t = (struct whnod *)getstor(sizeof (struct whnod));
			r = (struct trenod *)t;

			t->whtyp = (wdval == WHSYM ? TWH : TUN);
			t->whtre = cmd(DOSYM, NLFLG);
			t->dotre = cmd(ODSYM, NLFLG);
			break;
		}

	case BRSYM:
		r = cmd(KTSYM, NLFLG);
		break;

	case '(':
		{
			struct parnod *p;

			p = (struct parnod *)getstor(sizeof (struct parnod));
			p->partre = cmd(')', NLFLG);
			p->partyp = TPAR;
			r = makefork(0, (struct trenod *)p);
			break;
		}

	default:
		if (io == 0)
			return (0);
		/* FALLTHROUGH */

#ifdef	DO_EMPTY_SEMI
	case ';':
		if (io == 0) {
			if (!(flag&SEMIFLG))
				return (0);

			if (word() == ';')
				synbad();
			else if (wdval != 0)
				return (item(flag));
		}
		/* FALLTHROUGH */
#endif
	case 0:
		{
			struct comnod *t;
			struct argnod *argp;
			struct argnod **argtail;
			struct argnod *argset = 0;
#ifndef	ARGS_RIGHT_TO_LEFT
			struct argnod **argstail = &argset;
#endif
			int	keywd = 1;
			unsigned char	*com;

			if ((wdval != NL) && ((peekn = skipwc()) == '(')) {
				struct fndnod *f;
				struct ionod  *saveio;

				saveio = iotemp;
				peekn = 0;
				if (skipwc() != ')')
					synbad();

				/*
				 * We increase fndef before calling getstor(),
				 * so that getstor() uses malloc to allocate
				 * memory instead of stack. This is necessary
				 * since fndnod will be hung on np->funcval,
				 * which persists over command executions.
				 */
				fndef++;
				f = (struct fndnod *)
					getstor(sizeof (struct fndnod));
				r = (struct trenod *)f;

				f->fndtyp = TFND;
				if (fndef)
					f->fndnam = make(wdarg->argval);
				else
					f->fndnam = wdarg->argval;
				f->fndref = 0;
				reserv++;
				skipnl(0);
				f->fndval = (struct trenod *)item(0);
				fndef--;

				if (iotemp != saveio) {
					struct ionod	*ioptr = iotemp;

					while (ioptr->iolst != saveio)
						ioptr = ioptr->iolst;

					ioptr->iolst = fiotemp;
					fiotemp = iotemp;
					iotemp = saveio;
				}
				return (r);
			} else {
				int envbeg = 0;

				t = (struct comnod *)
					getstor(sizeof (struct comnod));
				r = (struct trenod *)t;

				t->comio = io; /* initial io chain */
				argtail = &(t->comarg);

				while (wdval == 0) {
					if (fndef) {
						unsigned char	*p;

						argp = wdarg;
						wdarg = (struct argnod *)
						    alloc(length(argp->argval) +
							1 + BYTESPERWORD);
						p = movstr(argp->argval,
								wdarg->argval);
						/*
						 * Hack: A second Nul byte is
						 * needed if the word ends in
						 * "\\\0".
						 */
						*++p = '\0';
					}

					argp = wdarg;
					if (wdset && keywd) {
						/*
						 * Revert the effect of abegin--
						 * at the begin of this function
						 * in case that we are in a list
						 * of env= definitions.
						 */
						if (abegin == 0) {
							abegin++;
							envbeg++;
						}
#ifdef	ARGS_RIGHT_TO_LEFT		/* old order: var2=val2 var1=val1 */
						argp->argnxt = argset;
						argset = argp;
#else
						argp->argnxt =
							(struct argnod *)0;
						*argstail = argp;
						argstail = &argp->argnxt;
#endif
					} else {
						/*
						 * If we had env= definitions,
						 * make sure to decrement abegin
						 * To disable begin alias
						 * expansions.
						 */
						if (abegin > 0 && envbeg)
							abegin--;
						*argtail = argp;
						argtail = &(argp->argnxt);
						keywd = flags & keyflg;
					}
					word();
					if (flag) {
						if (io) {
							while (io->ionxt)
								io = io->ionxt;
							io->ionxt = inout(
							    (struct ionod *)0);
						} else {
							t->comio = io = inout(
							    (struct ionod *)0);
						}
					}
				}

				t->comtyp = TCOM;
				t->comset = argset;
				*argtail = 0;

				if (nohash == 0 &&
				    (fndef == 0 || (flags & hashflg))) {
					if (t->comarg) {
						com = t->comarg->argval;
						if (*com && *com != DOLLAR) {
							pathlook(com, 0,
								t->comset);
						}
					}
				}

				return (r);
			}
		}

	}
	reserv++;
	word();
	if ((io = inout(io)) != NULL) {
#ifdef	DO_SETIO_NOFORK
		int type = r->tretyp & COMMSK;
#endif
		r = makefork(0, r);
		r->treio = io;
#ifdef	DO_SETIO_NOFORK
		if (type != TFORK)
			r->tretyp = TSETIO;
#endif
	}
	return (r);
}

/* ARGSUSED */
static int
skipnl(flag)
	int	flag;
{
	while ((reserv++, word() == NL))
		chkpr();
#ifdef	DO_PIPE_SEMI_SYNTAX_E
	if (!(flag&SEMIFLG) && wdval == ';')
		synbad();
#endif
	return (wdval);
}

static struct ionod *
inout(lastio)
	struct ionod *lastio;
{
	int	iof;
	struct ionod *iop;
	unsigned int	c;
	int	obegin = abegin;

	iof = wdnum;
	switch (wdval) {
	case DOCSYM:	/*	<<	*/
		iof |= IODOC|IODOC_SUBST;
		break;

	case APPSYM:	/*	>>	*/
	case '>':
		if (wdnum == 0)
			iof |= 1;
		iof |= IOPUT;
		if (wdval == APPSYM) {
			iof |= IOAPP;
			break;
		}
#ifdef	DO_NOCLOBBER
		else {
			if ((c = nextwc()) == '|')
				iof |= IOCLOB;
			else
				peekn = c | MARK;
		}
#endif
		/* FALLTHROUGH */

	case '<':
		if ((c = nextwc()) == '&')
			iof |= IOMOV;
		else if (c == '>')
			iof |= IORDW;
		else
			peekn = c | MARK;
		break;

	default:
		return (lastio);
	}

	abegin = 0;
	chkword();
	abegin = obegin;
	iop = (struct ionod *)getstor(sizeof (struct ionod));

	if (fndef)
		iop->ioname = (char *)make(wdarg->argval);
	else
		iop->ioname = (char *)(wdarg->argval);

	iop->iolink = 0;
	iop->iofile = iof;
	if (iof & IODOC) {
		iop->iolst = iopend;
		iopend = iop;
	}
	word();
	iop->ionxt = inout(lastio);
	return (iop);
}

static void
chkword()
{
	if (word())
		synbad();
}

static void
chksym(sym)
	int	sym;
{
	int	x = sym & wdval;

	if (((x & SYMFLG) ? x : sym) != wdval)
		synbad();
}

static void
prsym(sym)
	int	sym;
{
	if (sym & SYMFLG) {
		const struct sysnod *sp = reserved;

		while (sp->sysval && sp->sysval != sym)
			sp++;
		prs((unsigned char *)sp->sysnam);
	} else if (sym == EOFSYM) {
		prs(_gettext(endoffile));
	} else {
		if (sym & SYMREP)
			prc(sym);
		if (sym == NL)
			prs(_gettext(nlorsemi));
		else
			prc(sym);
	}
}

static void
synbad()
{
	prp();
	prs(_gettext(synmsg));
	if ((flags & ttyflg) == 0) {
		prs(_gettext(atline));
		prn(standin->flin);
	}
	prs((unsigned char *)colon);
	prc(LQ);
	if (wdval)
		prsym(wdval);
	else
		prs_cntl(wdarg->argval);
	prc(RQ);
	prs(_gettext(unexpected));
	newline();
	exitsh(SYNBAD);
}
