/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1984-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * convert single char edit ops to long form
 */

static const char id[] = "\n@(#)$Id: make-convert (AT&T Research) 1995-12-25 $\0\n";

#include <ast.h>
#include <error.h>
#include <debug.h>
#include <ctype.h>

#include "expand.h"

#define EXBUF		1024
#define DELETE		((char*)0)
#define KEEP		((char*)sfstdout)
#define RE_ALL		(1<<0)
#define RE_LOWER	(1<<1)
#define RE_UPPER	(1<<2)

static struct Convstate_s
{
	int		delimiter;
	int		function;
	const char*	longflag;
	Edit_map_t**	map;
} state;

static char*	expand(char*, char*);

static char*
editcontext(register char* beg, register char* cur)
{
	register char*	s;

	static char	buf[EXBUF];

	s = strcopy(buf, ">>>");
	strncpy(s, beg, cur - beg);
	s += strlen(s);
	strcpy(s, "<<<");
	return(buf);
}

#define DEL	(del==':'?state.delimiter:del)

static char*
expandops(register char* xp, register char* ed, int del)
{
	register int			c;
	register char*			s;
	register int			op;
	register const Edit_map_t*	mp;
	register const Edit_opt_t*	fp;
	const Edit_map_t*		zp;
	Edit_map_t* const*		mpp;
	char*				dir;
	char*				bas;
	char*				suf;
	char*				val;
	char*				oldp;
	char*				newp;
	char*				eb;
	char*				dp;
	char*				vp;
	const Edit_xxx_t*		bp;
	int				glob;
	int				cnt;
	int				cntlim;
	int				qual;
	int				n;
	int				m;
	int				e;
	int				sep;
	int				arg;
	int				aux;
	char				buf[EXBUF];
	char				tmp[16];

	int				bound = 0;
	int				delimited = 0;

	expand(buf, ed);
	ed = buf;
	dir = bas = suf = DELETE;
	eb = ed;
	debug((-5, "expandops(`%s')", ed));
	op = 0;
	for (;;)
	{
		if (op != 'T') bound = 0;
		for (bp = editxxx; bp->old; bp++)
			if (strneq(bp->old, ed, bp->len))
			{
				strncpy(ed, bp->xxx, bp->len);
				break;
			}
		if (!(op = *ed++)) break;
		if (op == del) continue;
		switch (op)
		{
		case '@':
		case 'V':
			sep = 1;
			switch (op)
			{
			case '@':
				sep = 0;
				val = "join";
				break;
			case 'V':
				val = "literal";
				switch (*ed)
				{
				case 'A':
					ed++;
					val = "auxilliary";
					break;
				case 'P':
					ed++;
					val = "primary";
					break;
				}
				break;
			}
			*xp++ = ' ';
			if (!delimited)
			{
				delimited = 1;
				if (del != ':')
					*xp++ = '`';
			}
			*xp++ = DEL;
			*xp++ = ' ';
			xp = strcopy(xp, val);
			if (!sep)
				delimited = -1;
			continue;
		}
		sep = EQ;

		/*
		 * collect the operands
		 */

		if (op == 'C' || op == '/' || op == 'Y' || op == '?')
		{
			/*
			 * substitute: <delim><old><delim><new><delim>[g]
			 * conditional: <delim><non-null><delim><null><delim>
			 */

			val = ed;
			n = op;
			switch (op)
			{
			case 'C':
			case 'Y':
				if (n = *ed) ed++;
				break;
			case '/':
				op = 'C';
				val--;
				break;
			case '?':
				op = 'Y';
				val--;
				break;
			}
			oldp = ed;
			for (cnt = 0; c = *ed; ed++)
			{
				if (c == '(') cnt++;
				else if (c == ')' && !--n) cnt = 0;
				else if (cnt <= 0)
				{
					if (c == n)
					{
						ed++;
						break;
					}
					if (c == '\\' && !*++ed)
						error(3, "unterminated lhs of %s: %s", op == 'C' ? "substitution" : "conditional", editcontext(eb, ed));
				}
			}
			newp = ed;
			for (cnt = 0; c = *ed; ed++)
			{
				if (c == '(') cnt++;
				else if (c == ')' && !--n) cnt = 0;
				else if (cnt <= 0)
				{
					if (c == n)
					{
						ed++;
						break;
					}
					if (c == '\\' && !*++ed)
						error(3, "unterminated rhs of %s: %s", op == 'C' ? "substitution" : "conditional", editcontext(eb, ed));
				}
			}
			glob = 0;
			while (*ed && *ed != del) switch (*ed++)
			{
			case 'G':
				*(ed - 1) = 'g';
				/*FALLTHROUGH*/
			case 'g':
				glob |= RE_ALL;
				break;
			case 'L':
				*(ed - 1) = 'l';
				/*FALLTHROUGH*/
			case 'l':
				glob |= RE_LOWER;
				break;
			case 'O':
				*(ed - 1) = 'o';
				/*FALLTHROUGH*/
			case 'o':
				glob |= RE_ALL;
				break;
			case 'U':
				*(ed - 1) = 'u';
				/*FALLTHROUGH*/
			case 'u':
				glob |= RE_UPPER;
				break;
			default:
				error(1, "invalid character `%c' after %s", *(ed - 1), op == 'C' ? "substitution" : "conditional");
				break;
			}
			s = ed;
			if (*ed) ed++;
			*s = 0;
		}
		else if (*ed == del)
		{
			ed++;
			val = KEEP;
		}
		else if (*ed)
		{
			/*
			 * value: [!<>=][=][<val>]
			 */

			if (op != '$') switch (*ed++)
			{
			case '!':
				if (*ed == '=')
				{
					ed++;
					sep = NE;
				}
				else sep = NOT;
				break;
			case '=':
				if (*ed == '=') ed++;
				sep = EQ;
				break;
			case '<':
				if (*ed == '=')
				{
					ed++;
					sep = LE;
				}
				else if (*ed == '>')
				{
					ed++;
					sep = NE;
				}
				else sep = LT;
				break;
			case '>':
				if (*ed == '=')
				{
					ed++;
					sep = GE;
				}
				else sep = GT;
				break;
			default:
				error(3, "edit operator delimiter omitted: %s", editcontext(eb, ed));
				break;
			}
			val = ed;
			for (cnt = n = 0; c = *ed; ed++)
			{
				if (cnt)
				{
					if (c == cnt) n++;
					else if (c == cntlim && !--n) cnt = 0;
				}
				else if (c == '(')
				{
					cnt = '(';
					cntlim = ')';
					n++;
				}
				else if (c == '[')
				{
					cnt = '[';
					cntlim = ']';
					n++;
				}
				else if (c == del && n <= 0)
				{
					*ed++ = 0;
					break;
				}
			}
			if (!*val) val = DELETE;
		}
		else val = KEEP;
		dp = xp;
		*xp++ = ' ';
		if (!delimited)
		{
			delimited = 1;
			if (del != ':')
				*xp++ = '`';
		}
		if (delimited < 0)
			delimited = 1;
		else
		{
			*xp++ = DEL;
			*xp++ = ' ';
		}

		/*
		 * B, D and S are grouped before application
		 */

		switch (op)
		{
		case '$':
			val--;
			xp = strcopy(xp, val);
			error(1, "%s: expand result may need conversion too", val);
			continue;
		case 'B':
		case 'D':
		case 'S':
			switch (op)
			{
			case 'B':
				bas = val;
				break;
			case 'D':
				dir = val;
				break;
			case 'S':
				suf = val;
				break;
			}
			switch (*ed)
			{
			case 'B':
				if (bas == DELETE)
				{
					xp = dp;
					continue;
				}
				break;
			case 'D':
				if (dir == DELETE)
				{
					xp = dp;
					continue;
				}
				break;
			case 'S':
				if (suf == DELETE)
				{
					xp = dp;
					continue;
				}
				break;
			}
			sep = 0;
			if (dir == DELETE)
			{
				xp = strcopy(xp, "nodirectory");
				sep = 1;
			}
			else
			{
				if (dir != KEEP)
				{
					xp += sfsprintf(xp, EXBUF, "directory %s", dir);
					sep = 1;
				}
				dir = DELETE;
			}
			if (bas == DELETE)
			{
				if (sep)
				{
					*xp++ = ' ';
					*xp++ = DEL;
					*xp++ = ' ';
				}
				else sep = 1;
				xp = strcopy(xp, "nobase");
			}
			else
			{
				if (bas != KEEP)
				{
					if (sep)
					{
						*xp++ = ' ';
						*xp++ = DEL;
						*xp++ = ' ';
					}
					else sep = 1;
					xp += sfsprintf(xp, EXBUF, "base %s", bas);
				}
				bas = DELETE;
			}
			if (suf == DELETE)
			{
				if (sep)
				{
					*xp++ = ' ';
					*xp++ = DEL;
					*xp++ = ' ';
				}
				else sep = 1;
				xp = strcopy(xp, "nosuffix");
			}
			else
			{
				if (suf != KEEP)
				{
					if (sep)
					{
						*xp++ = ' ';
						*xp++ = DEL;
						*xp++ = ' ';
					}
					else sep = 1;
					xp += sfsprintf(xp, EXBUF, "suffix %s", suf);
				}
				suf = DELETE;
			}
			break;
		default:
			qual = 0;
			if (op == 'T')
			{
				for (;; val++)
				{
					switch (*val)
					{
					case 'R':
						xp += sfsprintf(xp, EXBUF, "time %srule", state.longflag);
						op = 0;
						break;
					case 'W':
						qual |= ED_NOWAIT;
						continue;
					case 'X':
						qual |= ED_NOBIND;
						continue;
					}
					break;
				}
				if (*val == 'S' && *(val + 1) == 'F')
				{
					qual |= ED_FORCE;
					sfsprintf(tmp, sizeof(tmp), "S%s", val + 2);
					val = tmp;
				}
			}
			if (sep == NE)
				xp = strcopy(xp, "! ");
			if (!op) break;
			if (val == DELETE || val == KEEP)
			{
				val = "";
				arg = 0;
				aux = 0;
			}
			else
			{
				arg = val[0];
				aux = val[1];
			}
			vp = val;
			zp = 0;
			for (mpp = state.map; mpp < &state.map[elementsof(editmap)]; mpp++)
			{
				mp = *mpp;
				if (mp->cmd.type != ED_QUAL)
				{
					if (mp->cmd.op != op)
					{
						if (!(fp = mp->options))
							continue;
						for (; fp->name; fp++)
							if (fp->cmd.type == ED_OP && fp->cmd.op == op)

								break;
						if (!fp->name)
							continue;
					}
					m = 0;
					switch (mp->cmd.arg)
					{
					case 0:
						if (!zp || zp->options && !mp->options)
							zp = mp;
						if (arg && !mp->options)
							continue;
						break;
					case '<':
						if (!(sep & LT))
							continue;
						break;
					case '>':
						if (!(sep & GT))
							continue;
						break;
					case '*':
						if (arg)
						{
							if (arg != 'F' && arg != '*')
								continue;
							val++;
						}
						break;
					default:
						if (arg != mp->cmd.arg)
							continue;
						if (mp->cmd.aux)
						{
							if (aux != mp->cmd.aux)
								continue;
							if (!mp->options)
							{
								if (arg && *val)
									val++;
								if (*val)
									val++;
							}
						}
						else if (arg)
						{
							m = 1;
							if (*val)
								val++;
						}
						break;
					}
					dp = xp;
					if (op == 'T')
					{
						if (bound) bound = 0;
						else if (!(qual & ED_NOBIND))
						{
							bound = 1;
							xp = strcopy(xp, "bind");
							if (qual & ED_NOWAIT)
								xp += sfsprintf(xp, EXBUF, " %snowait", state.longflag);
							if (streq(mp->name, "bind"))
								break;
							xp += sfsprintf(xp, EXBUF, " %c ", DEL);
						}
					}
					xp = strcopy(xp, mp->name);
					if (qual & ED_FORCE)
						xp += sfsprintf(xp, EXBUF, " %sforce", state.longflag);
					if (fp = mp->options)
					{
						n = sep;
						for (e = 0; fp->name; fp++)
						{
							if (!*fp->name && fp->cmd.type == ED_QUAL)
								sep ^= fp->cmd.op;
							else if (fp->cmd.type == ED_QUAL && (sep & fp->cmd.op) || fp->cmd.type == ED_OP && (fp->cmd.op && op == fp->cmd.op || !fp->cmd.op && arg == fp->cmd.arg && (!fp->cmd.aux || aux == fp->cmd.aux)))
							{
								if (!*fp->name)
									e = 1;
								else
								{
									if (!m) m = 1;
									if (m > 0 && fp->cmd.type == ED_OP)
									{
										m = -1;
										val++;
										if (fp->cmd.aux)
											val++;
									}
									*xp++ = ' ';
									xp = strcopy(xp, state.longflag);
									xp = strcopy(xp, fp->name);
								}
							}
						}
						if (!m)
						{
							if (e)
							{
								if (*val) val++;
							}
							else if (!aux || isupper(aux))
							{
								if (!zp) zp = mp;
								sep = n;
								xp = dp;
								val = vp;
								continue;
							}
						}
					}
					if (aux == '=' && *val == '=')
						val++;
					if (*val)
					{
						*xp++ = ' ';
						if (*val == '-')
						{
							*xp++ = '-';
							*xp++ = '-';
							*xp++ = ' ';
						}
						if (op != 'P' || arg != 'P')
							xp = strcopy(xp, val);
						else
						{
							while (c = *val++)
								*xp++ = c == ',' ? ' ' : c;
							*xp = 0;
						}
					}
					break;
				}
			}
			if (mpp >= &state.map[elementsof(editmap)])
			{
				if (zp)
				{
					xp = strcopy(xp, zp->name);
					if (*val)
					{
						*xp++ = ' ';
						xp = strcopy(xp, val);
					}
				}
				else
				{
					error(2, "%c: operator not matched", op);
					xp = strcopy(xp, ">>>HUH<<<");
				}
			}
			break;
		}
	}
	*xp = 0;
	return(xp);
}

/*
 * expand var name into xp
 */

static char*
expandvar(register char* xp, register char* v)
{
	register int	c;
	register char*	s;

	switch (c = *v++)
	{
	case 0:
		break;
	case '$':
	case '"':
		return(expand(xp, v - 1));
	case '-':
	case '+':
		xp = strcopy(xp, "option ");
		if (c == '+')
			xp += sfsprintf(xp, EXBUF, "%sset ", state.longflag);
		xp = expand(xp, v);
		break;
	case '=':
		xp = strcopy(xp, "makeargs");
		break;
	case '#':
		s = "argc";
		goto internal;
	case ';':
		s = "data";
		goto internal;
	case '<':
		s = "target";
		goto internal;
	case '>':
		s = "prereqs new";
		goto internal;
	case '*':
		s = "prereqs";
		goto internal;
	case '~':
		s = "prereqs all";
		goto internal;
	case '@':
		s = "action";
		goto internal;
	case '%':
		s = state.function ? "args" : "stem";
		goto internal;
	case '!':
		s = "prereqs implicit";
		goto internal;
	case '&':
		s = "prereqs implicit state";
		goto internal;
	case '?':
		s = "prereqs all implicit";
		goto internal;
	case '^':
		s = "target original";
		goto internal;
	internal:
		while (*v == c)
		{
			v++;
			xp = strcopy(xp, "parent ");
		}
		for (;;)
		{
			switch (*xp++ = *s++)
			{
			case 0:
				xp--;
				break;
			case ' ':
				xp = strcopy(xp, state.longflag);
				continue;
			default:
				continue;
			}
			break;
		}
		if (*v)
		{
			*xp++ = ' ';
			xp = expand(xp, v);
		}
		break;
	case '.':
		if (v[0] != '.' || v[1] != '.')
			goto normal;
		xp = strcopy(xp, "rules");
		break;
	default:
		if (!isalnum(c))
			error(2, "%s: unknown internal variable", v - 1);
		/*FALLTHROUGH*/
	case '_':
	case '(':
	normal:
		xp = expand(xp, v - 1);
		break;
	}
	*xp = 0;
	return(xp);
}

/*
 * expand `$(...)' from a into xp
 */

static char*
expand(register char* xp, register char* a)
{
	register int	c;
	register char*	s;
	int		del;
	int		p;
	int		q;
	char*		ed;
	char*		var;
	char*		vp;

	char		varbuf[EXBUF];

	debug((-4, "expand(`%s')", a));
	if (!(s = strchr(a, '$')))
		return(strcopy(xp, a));
	strncpy(xp, a, s - a);
	xp += s - a;
	a = s;
	while (*a)
	{
		if (*a != '$') *xp++ = *a++;
		else if (*++a == '(')
		{
			*xp++ = '$';
			*xp++ = '(';
			if (isspace(*++a))
				*xp++ = *a++;
			else
			{
				var = a;
				ed = 0;
				vp = 0;
				del = ':';
				q = 0;
				p = 1;
				while (c = *a++)
				{
					if (c == '"') q = !q;
					else if (q) /* quoted */;
					else if (c == '(') p++;
					else if (c == ')')
					{
						if (!--p) break;
					}
					else if (!ed && p == 1)
					{
						if (c == '|')
						{
							*(a - 1) = 0;
							if (!vp) vp = varbuf;
							else *vp++ = ' ';
							vp = expandvar(vp, var);
							var = a;
							*(a - 1) = c;
							c = 0;
						}
						else if (c == del)
						{
							c = 0;
							ed = a - 1;
						}
						else if (c == '`')
						{
							c = 0;
							ed = a - 1;
							if (!(del = *a++))
							{
								ed--;
								break;
							}
						}
					}
				}
				if (q || !c)
				{
					a--;
					error(1, "missing %c in %s variable expansion", q ? '"' : ')', s);
				}
				if (vp)
				{
					xp += sfsprintf(xp, EXBUF, "value ");
					if (*var == '"')
					{
						c = *(a - 1);
						*(a - 1) = 0;
						xp += sfsprintf(xp, EXBUF, "%sdefault=%s ", state.longflag, var);
						*(a - 1) = c;
						var = 0;
					}
				}
				if (ed && *(ed + 1) == 'V')
				{
					*ed = 0;
					if (!vp) xp += sfsprintf(xp, EXBUF, "value ");
					xp = strcopy(xp, state.longflag);
					switch (*(ed + 2))
					{
					case 'A':
						ed += 3;
						xp = strcopy(xp, "auxiliary ");
						break;
					case 'P':
						ed += 3;
						xp = strcopy(xp, "primary ");
						break;
					default:
						ed += 2;
						xp = strcopy(xp, "literal ");
						break;
					}
					if (ed == (a - 1))
						ed = 0;
				}
				if (ed)
				{
					if (var && strneq(var, "\"\":T=R", 6))
						xp += sfsprintf(xp, EXBUF, "time %swall", state.longflag);
					else
					{
						c = *ed;
						*ed = 0;
						if (vp)
						{
							xp = strcopy(xp, varbuf);
							if (var) *xp++ = ' ';
						}
						if (var) xp = expandvar(xp, var);
						*ed = c;
						c = *(a - 1);
						*(a - 1) = 0;
						xp = expandops(xp, ed + 1, del);
						*(a - 1) = c;
					}
				}
				else
				{
					c = *(a - 1);
					*(a - 1) = 0;
					if (vp)
					{
						xp = strcopy(xp, varbuf);
						if (var) *xp++ = ' ';
					}
					if (var) xp = expandvar(xp, var);
					*(a - 1) = c;
				}
			}
			*xp++ = ')';
		}
		else *xp++ = '$';
	}
	*xp = 0;
	return(xp);
}

static int
byop(const char* a, const char* b)
{
	register Edit_map_t*	ap = (Edit_map_t*)a;
	register Edit_map_t*	bp = (Edit_map_t*)b;

	if (ap->cmd.type == ED_QUAL) return(-1);
	if (bp->cmd.type == ED_QUAL) return(1);
	if (ap->cmd.op < bp->cmd.op) return(-1);
	if (ap->cmd.op > bp->cmd.op) return(1);
	if (!ap->cmd.arg) return(1);
	if (!bp->cmd.arg) return(-1);
	if (ap->cmd.arg < bp->cmd.arg) return(-1);
	if (ap->cmd.arg > bp->cmd.arg) return(1);
	if (!ap->cmd.aux) return(1);
	if (!bp->cmd.aux) return(-1);
	if (ap->cmd.aux < bp->cmd.aux) return(-1);
	if (ap->cmd.aux > bp->cmd.aux) return(1);
	return(0);
}

main(int argc, char** argv)
{
	register char*	s;
	register int	c;
	Edit_map_t*	mp;
	Edit_map_t**	mpp;
	Sfio_t*		ip;
	char		buf[EXBUF];

	error_info.id = "convert";
	state.delimiter = ':';
	state.longflag = "-o ";
	if (!(state.map = newof(0, Edit_map_t*, elementsof(editmap), 0)))
		error(ERROR_SYSTEM|3, "out of space [editmap sort]");
	mpp = state.map;
	mp = (Edit_map_t*)editmap;
	while (mp < &editmap[elementsof(editmap)])
		*mpp++ = mp++;
	strsort((char**)state.map, elementsof(editmap), byop);
	while (c = optget(argv, "d:[delimiter]o:[longflag]D#[debug-level]")) switch (c)
	{
	case 'd':
		state.delimiter = *opt_info.arg;
		break;
	case 'o':
		state.longflag = opt_info.arg;
		break;
	case 'D':
		error_info.trace = -opt_info.num;
		break;
	case '?':
		error(ERROR_USAGE|4, opt_info.arg);
		break;
	case ':':
		error(2, opt_info.arg);
		break;
	}
	if (error_info.errors)
		error(ERROR_USAGE|4, "%s", optusage(NiL));
	argv += opt_info.index;
	for (;;)
	{
		s = *argv++;
		if (!s || streq(s, "-") || streq(s, "/dev/stdin") || streq(s, "/dev/fd/0"))
			ip = sfstdin;
		else if (!(ip = sfopen(NiL, s, "r")))
		{
			error(2, "%s: cannot read", s);
			continue;
		}
		error_info.file = s;
		error_info.line = 0;
		while (s = sfgetr(ip, '\n', 1))
		{
			error_info.line++;
			if (*s && !isspace(*s))
				state.function = strmatch(s, "*FUNCTION*");
			expand(buf, s);
			sfputr(sfstdout, buf, '\n');
		}
		error_info.file = 0;
		error_info.line = 0;
		if (!s) break;
		if (ip != sfstdin) sfclose(ip);
	}
	exit(error_info.errors != 0);
}
