/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2011 AT&T Intellectual Property          *
*          Copyright (c) 2020-2021 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*          http://www.eclipse.org/org/documents/epl-v10.html           *
*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
/*
 * David Korn
 * AT&T Labs
 *
 * shell deparser
 *
 */

#include	"defs.h"
#include	"shnodes.h"
#include	"test.h"


#define HUGE_INT	(((unsigned)-1)>>1)
#define	BEGIN	0
#define MIDDLE	1
#define	END	2
#define PRE	1
#define POST	2


/* flags that can be specified with p_tree() */
#define NO_NEWLINE	(1 << 0)
#define NEED_BRACE	(1 << 1)
#define NO_BRACKET	(1 << 2)
#define PROCSUBST	(1 << 3)

static void p_comlist(const struct dolnod*,int);
static void p_arg(const struct argnod*, int endchar, int opts);
static void p_comarg(const struct comnod*);
static void p_keyword(const char*,int);
static void p_redirect(const struct ionod*);
static void p_switch(const struct regnod*);
static void here_body(const struct ionod*);
static void p_tree(const Shnode_t*,int);

static int level;
static int begin_line;
static int end_line;
static char io_op[7];
static char un_op[3] = "-?";
static const struct ionod *here_doc;
static Sfio_t *outfile;
static const char *forinit = "";

void sh_deparse(Sfio_t *out, const Shnode_t *t,int tflags)
{
	outfile = out;
	p_tree(t,tflags);
}
/*
 * print script corresponding to shell tree <t>
 */
static void p_tree(register const Shnode_t *t,register int tflags)
{
	register char *cp=0;
	int save = end_line;
	int needbrace = (tflags&NEED_BRACE);
	int procsub = (tflags&PROCSUBST);
	tflags &= ~NEED_BRACE;
	if(tflags&NO_NEWLINE)
		end_line = ' ';
	else if(procsub)
		end_line = ')';
	else
		end_line = '\n';
	switch(t->tre.tretyp&COMMSK)
	{
		case TTIME:
			if(t->tre.tretyp&COMSCAN)
				p_keyword("!",BEGIN);
			else
				p_keyword("time",BEGIN);
			if(t->par.partre)
				p_tree(t->par.partre,tflags); 
			level--;
			break;

		case TCOM:
			if(begin_line && level>0)
				sfnputc(outfile,'\t',level);
			begin_line = 0;
			p_comarg((struct comnod*)t);
			break;

		case TSETIO:
			if(t->tre.tretyp&FPCL)
				tflags |= NEED_BRACE;
			else
				tflags = NO_NEWLINE|NEED_BRACE;
			p_tree(t->fork.forktre,tflags);
			p_redirect(t->fork.forkio);
			break;

		case TFORK:
			if(needbrace)
				tflags |= NEED_BRACE;
			if((t->tre.tretyp&(FAMP|FCOOP)) && (t->tre.tretyp&(FAMP|FINT))!=FAMP)
			{
				tflags = NEED_BRACE|NO_NEWLINE;
				end_line = ' ';
			}
			else if(t->fork.forkio)
				tflags = NO_NEWLINE;
			p_tree(t->fork.forktre,tflags);
			if(t->fork.forkio)
				p_redirect(t->fork.forkio);
			if(t->tre.tretyp&FCOOP)
			{
				sfputr(outfile,"|&",procsub?')':'\n');
				begin_line = 1;
			}
			else if((t->tre.tretyp&(FAMP|FINT))==(FAMP|FINT))
			{
				sfputr(outfile,"&",procsub?')':'\n');
				begin_line = 1;
			}
			break;
	
		case TIF:
			p_keyword("if",BEGIN);
			p_tree(t->if_.iftre,0);
			p_keyword("then",MIDDLE);
			p_tree(t->if_.thtre,0);
			if(t->if_.eltre)
			{
				p_keyword("else",MIDDLE);
				p_tree(t->if_.eltre,0);
			}
			p_keyword("fi",END);
			break;

		case TWH:
			if(t->wh.whinc)
				cp = "for";
			else if(t->tre.tretyp&COMSCAN)
				cp = "until";
			else
				cp = "while";
			p_keyword(cp,BEGIN);
			if(t->wh.whinc)
			{
				struct argnod *arg = (t->wh.whtre)->ar.arexpr;
				sfprintf(outfile,"(( %s; ",forinit);
				forinit = "";
				sfputr(outfile,arg->argval,';');
				arg = (t->wh.whinc)->arexpr;
				sfprintf(outfile," %s))\n",arg->argval);
			}
			else
				p_tree(t->wh.whtre,0);
			t = t->wh.dotre;
			goto dolist;

		case TLST:
		{
			Shnode_t *tr = t->lst.lstrit;
			if(tr->tre.tretyp==TWH && tr->wh.whinc && t->lst.lstlef->tre.tretyp==TARITH)
			{
				/* arithmetic for statement */
				struct argnod *init = (t->lst.lstlef)->ar.arexpr;
				forinit= init->argval;
				p_tree(t->lst.lstrit,tflags);
				break;
			}
			if(needbrace)
				p_keyword("{",BEGIN);
			p_tree(t->lst.lstlef,0);
			if(needbrace)
				tflags = 0;
			p_tree(t->lst.lstrit,tflags);
			if(needbrace)
				p_keyword("}",END);
			break;
		}

		case TAND:
			cp = "&&";
			goto andor;
		case TORF:
			cp = "||";
			goto andor;
		case TFIL:
			cp = "|";
		andor:
		{
			int bracket = 0;
			if(t->tre.tretyp&TTEST)
			{
				tflags |= NO_NEWLINE;
				if(!(tflags&NO_BRACKET))
				{
					p_keyword("[[",BEGIN);
					tflags |= NO_BRACKET;
					bracket=1;
				}
			}
			p_tree(t->lst.lstlef,NEED_BRACE|NO_NEWLINE|(tflags&NO_BRACKET));
			if(tflags&FALTPIPE)
			{
				Shnode_t *tt = t->lst.lstrit;
				if(tt->tre.tretyp!=TFIL || !(tt->lst.lstlef->tre.tretyp&FALTPIPE))
				{
					sfputc(outfile,'\n');
					return;
				}
			}
			sfputr(outfile,cp,here_doc?'\n':' ');
			if(here_doc)
			{
				here_body(here_doc);
				here_doc = 0;
			}
			level++;
			p_tree(t->lst.lstrit,tflags|NEED_BRACE);
			if(bracket)
				p_keyword("]]",END);
			level--;
			break;
		}
	
		case TPAR:
			p_keyword("(",BEGIN);
			p_tree(t->par.partre,0); 
			p_keyword(")",END);
			break;

		case TARITH:
		{
			register struct argnod *ap = t->ar.arexpr;
			if(begin_line && level)
				sfnputc(outfile,'\t',level);
			sfprintf(outfile,"(( %s ))%c",ap->argval,end_line);
			if(!(tflags&NO_NEWLINE))
				begin_line=1;
			break;
		}

		case TFOR:
			cp = ((t->tre.tretyp&COMSCAN)?"select":"for");
			p_keyword(cp,BEGIN);
			sfputr(outfile,t->for_.fornam,' ');
			if(t->for_.forlst)
			{
				sfputr(outfile,"in",' ');
				tflags = end_line;
				end_line = '\n';
				p_comarg(t->for_.forlst);
				end_line = tflags;
			}
			else
				sfputc(outfile,'\n');
			begin_line = 1;
			t = t->for_.fortre;
		dolist:
			p_keyword("do",MIDDLE);
			p_tree(t,0);
			p_keyword("done",END);
			break;
	
		case TSW:
			p_keyword("case",BEGIN);
			p_arg(t->sw.swarg,' ',0);
			if(t->sw.swlst)
			{
				begin_line = 1;
				sfputr(outfile,"in",'\n');
				tflags = end_line;
				end_line = '\n';
				p_switch(t->sw.swlst);
				end_line = tflags;
			}
			p_keyword("esac",END);
			break;

		case TFUN:
			if(t->tre.tretyp&FPOSIX)
			{
				sfprintf(outfile,"%s",t->funct.functnam);
				p_keyword("()\n",BEGIN);
			}
			else
			{
				p_keyword("function",BEGIN);
				tflags = (t->funct.functargs?' ':'\n');
				sfputr(outfile,t->funct.functnam,tflags);
				if(t->funct.functargs)
				{
					tflags = end_line;
					end_line = '\n';
					p_comarg(t->funct.functargs);
					end_line = tflags;
				}
			}
			begin_line = 1;
			p_keyword("{\n",MIDDLE);
			begin_line = 1;
			p_tree(t->funct.functtre,0); 
			p_keyword("}",END);
			break;
		/* new test compound command */
		case TTST:
			if(!(tflags&NO_BRACKET))
				p_keyword("[[",BEGIN);
			if((t->tre.tretyp&TPAREN)==TPAREN)
			{
				p_keyword("(",BEGIN);
				p_tree(t->lst.lstlef,NO_BRACKET|NO_NEWLINE); 
				p_keyword(")",END);
			}
			else
			{
				int flags = (t->tre.tretyp)>>TSHIFT;
				if(t->tre.tretyp&TNEGATE)
					sfputr(outfile,"!",' ');
				if(t->tre.tretyp&TUNARY)
				{
					un_op[1] = flags;
					sfputr(outfile,un_op,' ');
				}
				else
					cp = ((char*)(shtab_testops+(flags&037)-1)->sh_name);
				p_arg(&(t->lst.lstlef->arg),' ',0);
				if(t->tre.tretyp&TBINARY)
				{
					sfputr(outfile,cp,' ');
					p_arg(&(t->lst.lstrit->arg),' ',0);
				}
			}
			if(!(tflags&NO_BRACKET))
				p_keyword("]]",END);
	}
	while(begin_line && here_doc)
	{
		here_body(here_doc);
		here_doc = 0;
	}
	end_line = save;
	return;
}

/*
 * print a keyword
 * increment indent level for flag==BEGIN
 * decrement indent level for flag==END
 */
static void p_keyword(const char *word,int flag)
{
	register int sep;
	if(flag==END)
		sep = end_line;
	else if(*word=='[' || *word=='(')
		sep = ' ';
	else
		sep = '\t';
	if(flag!=BEGIN)
		level--;
	if(begin_line && level)
		sfnputc(outfile,'\t',level);
	sfputr(outfile,word,sep);
	if(sep=='\n')
		begin_line=1;
	else
		begin_line=0;
	if(flag!=END)
		level++;
}

static void p_arg(register const struct argnod *arg,register int endchar,int opts)
{
	register const char *cp;
	register int flag=0;
	do
	{
		if(!arg->argnxt.ap)
			flag = endchar;
		else if(opts&PRE)
		{
			/* case alternation lists in reverse order */
			p_arg(arg->argnxt.ap,'|',opts);
			flag = endchar;
		}
		else if(opts)
			flag = ' ';
		cp = arg->argval;
		if(*cp==0 && (arg->argflag&ARG_EXP) && arg->argchn.ap)
		{
			/* process substitution */
			int c = (arg->argflag&ARG_RAW)?'>':'<';
			sfputc(outfile,c);
			sfputc(outfile,'(');
			p_tree((Shnode_t*)arg->argchn.ap,PROCSUBST);
		}
		else if(*cp==0 && opts==POST && arg->argchn.ap)
		{
			/* compound assignment */
			struct fornod *fp=(struct fornod*)arg->argchn.ap;
			sfprintf(outfile,"%s=(\n",fp->fornam);
			sfnputc(outfile,'\t',++level);
			p_tree(fp->fortre,0);
			if(--level)
				sfnputc(outfile,'\t',level);
			sfputc(outfile,')');
		}
		else if((arg->argflag&ARG_RAW) && (cp[1] || (*cp!='[' && *cp!=']')))
			cp = sh_fmtq(cp);
		sfputr(outfile,cp,flag);
		if(flag=='\n')
			begin_line = 1;
		arg = arg->argnxt.ap;
	}
	while((opts&POST) && arg);
	return;
}

static void p_redirect(register const struct ionod *iop)
{
	register char *cp;
	register int iof,iof2;
	for(;iop;iop=iop->ionxt)
	{
		iof=iop->iofile;
		cp = io_op;
		if(iop->iovname)
		{
			sfwrite(outfile,"(;",2);
			sfputr(outfile,iop->iovname,')');
			cp++;
		}
		else
			*cp = '0'+(iof&IOUFD);
		if(iof&IOPUT)
		{
			if(*cp == '1' && !iop->iovname)
				cp++;
			io_op[1] = '>';
		}
		else
		{
			if(*cp == '0' && !iop->iovname)
				cp++;
			io_op[1] = '<';
		}
		io_op[2] = 0;
		io_op[3] = 0;
		if(iof&IOLSEEK)
		{
			io_op[1] = '#';
			if(iof&IOARITH)
				strcpy(&io_op[3]," ((");
		}
		else if(iof&IOMOV)
			io_op[2] = '&';
		else if(iof&(IORDW|IOAPP))
			io_op[2] = '>';
		else if(iof&IOCLOB)
			io_op[2] = '|';
		if(iop->iodelim)
		{
			/* here document */
			here_doc  = iop;
			io_op[2] = '<';
		}
		sfputr(outfile,cp,' ');
		if(iop->ionxt)
			iof = ' ';
		else
		{
			if((iof=end_line)=='\n')
				begin_line = 1;
		}
		if((iof&IOLSEEK) && (iof&IOARITH))
			iof2 = iof, iof = ' ';
		if((iop->iofile & IOPROCSUB) && !(iop->iofile & IOLSEEK))
		{
			/* process substitution as argument to redirection */
			if(iop->iofile & IOPUT)
				sfwrite(outfile,">(",2);
			else
				sfwrite(outfile,"<(",2);
			p_tree((Shnode_t*)iop->ioname,PROCSUBST);
			sfputc(outfile,iof);
		}
		else if(iop->iodelim)
		{
			if(!(iop->iofile&IODOC))
				sfwrite(outfile,"''",2);
			sfputr(outfile,sh_fmtq(iop->iodelim),iof);
		}
		else if(iop->iofile&IORAW)
			sfputr(outfile,sh_fmtq(iop->ioname),iof);
		else
			sfputr(outfile,iop->ioname,iof);
		if((iof&IOLSEEK) && (iof&IOARITH))
			sfputr(outfile, "))", iof2);
	}
	return;
}

static void p_comarg(register const struct comnod *com)
{
	register int flag = end_line;
	if(com->comtyp&FAMP)
		sfwrite(outfile,"& ",2);
	if(com->comarg || com->comio)
		flag = ' ';
	if(com->comset)
		p_arg(com->comset,flag,POST);
	if(com->comarg)
	{
		if(!com->comio)
			flag = end_line;
		if(com->comtyp&COMSCAN)
			p_arg(com->comarg,flag,POST);
		else
			p_comlist((struct dolnod*)com->comarg,flag);
	}
	if(com->comio)
		p_redirect(com->comio);
	return;
}

static void p_comlist(const struct dolnod *dol,int endchar)
{
	register char *cp, *const*argv;
	register int flag = ' ', special;
	argv = dol->dolval+ARG_SPARE;
	cp = *argv;
	special = (*cp=='[' && cp[1]==0);
	do
	{
		if(cp)
			argv++;
		else
			cp = "";
		if(*argv==0)
		{
			if((flag=endchar)=='\n')
				begin_line = 1;
			special = (*cp==']' && cp[1]==0);
		}
		sfputr(outfile,special?cp:sh_fmtq(cp),flag);
		special = 0;
	}
	while(cp  = *argv);
	return;
}

static void p_switch(register const struct regnod *reg)
{
	if(level>1)
		sfnputc(outfile,'\t',level-1);
	p_arg(reg->regptr,')',PRE);
	begin_line = 0;
	sfputc(outfile,'\t');
	if(reg->regcom)
		p_tree(reg->regcom,0);
	level++;
	if(reg->regflag)
		p_keyword(";&",END);
	else
		p_keyword(";;",END);
	if(reg->regnxt)
		p_switch(reg->regnxt);
	return;
}

/*
 * output here documents
 */
static void here_body(register const struct ionod *iop)
{
	Sfio_t *infile;
	if(iop->iofile&IOSTRG)
		infile = sfnew((Sfio_t*)0,iop->ioname,iop->iosize,-1,SF_STRING|SF_READ);
	else
		sfseek(infile=sh.heredocs,iop->iooffset,SEEK_SET);
	sfmove(infile,outfile,iop->iosize,-1);
	if(iop->iofile&IOSTRG)
		sfclose(infile);
	sfputr(outfile,iop->iodelim,'\n');
}
