/***********************************************************************
*                                                                      *
*               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
 *
 * make metarule routines
 */

#include "make.h"

/*
 * return a pointer to the metarule that builds
 * a file that matches the metarule pattern out from
 * a file that matches the metarule pattern in
 * force causes the rule to be created
 */

Rule_t*
metarule(char* in, char* out, int force)
{
	register Rule_t*	r;
	register char*		s;

	sfprintf(internal.met, "%s>%s", in, out ? out : "%");
	s = sfstruse(internal.met);
	r = getrule(s);
	if (force)
	{
		if (!r)
			r = makerule(NiL);
		r->property |= (P_metarule|P_use);
		r->property &= ~(P_state|P_staterule|P_statevar);
	}
	else
	{
		/*
		 * if in -> out is a secondary metrule then return primary
		 */

		if (!r && (r = metainfo('P', in, out, 0)))
		{
			if (r->prereqs)
			{
				sfprintf(internal.met, "%s>%s", in, r->prereqs->rule->name);
				r = getrule(sfstruse(internal.met));
			}
			else
				r = 0;
		}
		if (r && (!(r->property & P_metarule) || !r->action && !r->uname))
			r = 0;
	}
	return r;
}

/*
 * metarule supplementary info
 *
 * type
 *
 *	'I'	inputs
 *	'N'	nonterminal unconstrained inputs
 *	'O'	outputs
 *	'P'	primary output
 *	'S'	primary secondary outputs
 *	'T'	terminal unconstrained inputs
 *	'X'	unconstrained output exclusions
 */

Rule_t*
metainfo(int type, char* s1, char* s2, int force)
{
	register Rule_t*	r;

	sfprintf(internal.met, "%s.%c.%s%s%s", internal.metarule->name, type, s1 ? s1 : null, s2 ? ">" : null, s2 ? s2 : null);
	s1 = sfstruse(internal.met);
	if (!(r = getrule(s1)) && force)
	{
		r = makerule(NiL);
		r->property |= P_readonly;
	}
	return r;
}

#if _WINIX

/*
 * yes, metarules are affected by the case insensitive filesystem botch
 * we do case insensitive metarule pattern matching for all patterns that
 * do not match `[-+]*'
 */

static int
metaccmp(register char* p, register int a, register int b)
{
	return a == b || *p != '-' && *p != '+' && (isupper(a) ? tolower(a) : a) == (isupper(b) ? tolower(b) : b);
}

#else

#define metaccmp(p,a,b)	((a)==(b))

#endif

/*
 * match s against metarule pattern
 * leading unmatched directories before % are ignored
 * returns 0 if no match
 * otherwise if stem!=0 then non-dir part of matched stem is copied there
 */

int
metamatch(char* stem, register char* s, char* pattern)
{
	register char*	p;
	register char*	t;
	register char*	x;
	register char*	y;
	char*		b;
	register int	targetprefix;
	
	b = s;
	p = pattern;
	targetprefix = state.targetprefix ? *state.targetprefix : -1;
	while (*p != '%')
	{
		if (!metaccmp(pattern, *p, *s))
		{
			do
			{
				if (*s == '/')
				{
					while (*++s == '/');
					break;
				}
				else if (*s == targetprefix)
				{
					x = state.targetprefix;
					y = s;
					while (*++x == *++y && *x);
					if (!*x)
					{
						s = y;
						break;
					}
				}
			} while (*++s);
			if (!*(b = s))
				return 0;
			p = pattern;
		}
		else if (!*p++)
		{
			if (p = stem)
			{
				s = b;
				while (*p++ = *s++);
			}
			return 1;
		}
		else
			s++;
	}
	t = s;
	while (*s)
		s++;
	while (*p)
		p++;
	while (*--p != '%')
		if (s <= t || !metaccmp(pattern, *p, *--s))
			return 0;
	if (stem)
	{
		if ((p = strrchr(t, '/')) && p < s)
			t = p + 1;
		if (state.targetprefix)
		{
			b = t;
			while ((p = strchr(b, targetprefix)) && p < s)
			{
				x = state.targetprefix;
				y = p;
				while (*++x == *++y && *x);
				if (!*x)
					b = t = y;
				else
					b++;
			}
		}
		p = stem;
		while (t < s)
			*p++ = *t++;
		*p = 0;
	}
	return 1;
}

/*
 * expand the metarule pattern p into sp using stem
 */

void
metaexpand(Sfio_t* sp, char* stem, char* p)
{
	register int	c;

	while ((c = *p++) != '%')
	{
		if (!c)
			return;
		sfputc(sp, c);
	}
	sfputr(sp, stem, -1);
	sfputr(sp, p, -1);
}

/*
 * update the metarule closure graph
 *
 * NOTE: c==0 (PREREQ_APPEND alias) is used to cut off part of the closure recursion
 */

void
metaclose(Rule_t* in, Rule_t* out, int c)
{
	char*		s;
	Rule_t*		x;
	Rule_t*		y;
	Rule_t*		z;
	List_t*		p;
	List_t*		q;
	char		stem[MAXNAME];

#if DEBUG
	static int	eloop = 0;
#endif

#if DEBUG
	if (eloop++ > 64)
	{
		eloop--;
		error(1, "metaclose: %s -> %s recursion!", in->name, out->name);
		return;
	}
#endif
	addprereq(internal.metarule, out, c == PREREQ_DELETE ? c : PREREQ_LENGTH);
	addprereq(metainfo('I', out->name, NiL, 1), in, c);
	addprereq(metainfo('O', in->name, NiL, 1), out, c);
	if (!c && !(state.questionable & 0x00000100))
		return;
	for (q = internal.metarule->prereqs; q; q = q->next)
	{
		if (metamatch(stem, out->name, q->rule->name) && (x = metainfo('O', q->rule->name, NiL, 0)))
		{
			for (p = x->prereqs; p; p = p->next)
			{
				metaexpand(internal.met, stem, p->rule->name);
				z = makerule(sfstruse(internal.met));
				x = metarule(in->name, z->name, 1);
				if (!x->action || (state.questionable & 0x00000100))
				{
					if (c == PREREQ_DELETE)
						s = 0;
					else
					{
						if (z != in && !(x->mark & M_metarule))
						{
							x->mark |= M_metarule;
							metaclose(in, z, PREREQ_APPEND);
							x->mark &= ~M_metarule;
						}
						s = ((y = metarule(q->rule->name, z->name, 0)) && y->uname) ? y->uname : q->rule->name;
					}
					x->uname = s;
					x->dynamic &= ~D_compiled;
				}
			}
		}
		if (c != PREREQ_DELETE && metamatch(stem, q->rule->name, in->name) && !streq(stem, "%") && (x = metainfo('I', q->rule->name, NiL, 0)))
		{
			metaexpand(internal.met, stem, out->name);
			if (!streq(stem, q->rule->name))
			{
				z = makerule(sfstruse(internal.met));
				for (p = x->prereqs; p; p = p->next)
				{
					x = metarule(p->rule->name, z->name, 1);
					if (!(x->mark & M_metarule))
					{
						x->mark |= M_metarule;
						metaclose(p->rule, z, 0);
						x->mark &= ~M_metarule;
					}
					x->uname = in->name;
					x->dynamic &= ~D_compiled;
				}
			}
		}
	}
	if (c != PREREQ_DELETE && (z = metainfo('I', in->name, NiL, 0)))
		for (q = z->prereqs; q; q = q->next)
			if ((z = q->rule) != out)
			{
				x = metarule(z->name, out->name, 1);
				if (!x->action || (state.questionable & 0x00000100))
				{
					if (!(x->mark & M_metarule))
					{
						x->mark |= M_metarule;
						metaclose(z, out, 0);
						x->mark &= ~M_metarule;
					}
					if (!x->uname)
					{
						x->uname = in->name;
						x->dynamic &= ~D_compiled;
					}
				}
			}
#if DEBUG
	eloop--;
#endif
}

/*
 * return the primary metarule source that generates r
 * *meta is the matching metarule
 */

Rule_t*
metaget(Rule_t* r, List_t* prereqs, char* stem, Rule_t** meta)
{
	register List_t*	p;
	register List_t*	q;
	register List_t*	v;
	Rule_t*			m;
	Rule_t*			s;
	Rule_t*			x;
	Rule_t*			y;
	List_t*			terminal;
	char*			b;
	char*			u;
	char*			t;
	int			matched;

	u = unbound(r);
	if (prereqs)
	{
		/*
		 * check explicit constrained prereqs -- easy
		 */

		debug((-9, "check explicit constrained metarule prerequisites"));
		t = sfstrbase(internal.met);
		matched = 0;
		for (p = internal.metarule->prereqs; p; p = p->next)
			if (metamatch(stem, u, p->rule->name) && (x = metainfo('I', p->rule->name, NiL, 0)) && (matched = 1, x->prereqs))
				for (q = prereqs; q; q = q->next)
					if (q->rule->status != UPDATE)
						for (v = x->prereqs; v; v = v->next)
							if (metamatch(t, unbound(q->rule), v->rule->name) && streq(t, stem) && (m = metarule(v->rule->name, p->rule->name, 0)) && (!(r->property & P_terminal) || (m->property & P_terminal)))
							{
								s = q->rule;
								goto primary;
							}
	}
	else
		matched = 1;
	if (b = strrchr(u, '/'))
		b++;
	else
		b = u;
	terminal = (x = metainfo('T', NiL, NiL, 0)) ? x->prereqs : 0;
	if (matched)
	{
		Sfio_t*	buf = 0;
		Sfio_t*	tmp = 0;

		/*
		 * check implicit constrained prereqs -- a little harder
		 */

		debug((-9, "check implicit constrained metarule prerequisites"));
		matched = 0;
		for (p = internal.metarule->prereqs; p; p = p->next)
			if (metamatch(stem, u, p->rule->name) && (x = metainfo('I', p->rule->name, NiL, 0)))
				for (matched = 1, q = x->prereqs; q; q = q->next)
					if ((m = metarule(q->rule->name, p->rule->name, 0)) && (!(r->property & P_terminal) || (m->property & P_terminal)))
					{
						if (!tmp)
							tmp = sfstropen();
						if (b != u && *u != '/' && *q->rule->name && ((state.questionable & 0x00000008) || !strchr(q->rule->name, '/')))
						{
							char*	x = u;

							for (;;)
							{
								sfprintf(tmp, "%-.*s", b - x, x);
								metaexpand(tmp, stem, q->rule->name);
								t = sfstruse(tmp);
								if ((s = bindfile(NiL, t, 0)) && s->status != UPDATE && (s->time || (s->property & P_target)) || !(state.questionable & 0x08000000) && m->action && !*m->action && (s = makerule(t)))
								{
									sfstrclose(tmp);
									goto primary;
								}
								if (!(x = strchr(x, '/')))
									break;
								x++;
							}
						}
						else
						{
							metaexpand(tmp, stem, q->rule->name);
							t = sfstruse(tmp);
							if (isstatevar(t) && (s = getrule(t)) && (s = bindstate(s, NiL)))
								goto primary;
							if ((s = bindfile(NiL, t, 0)) && s->status != UPDATE && (s->time || (s->property & P_target)) || !(state.questionable & 0x08000000) && m->action && !*m->action && (s = makerule(t)))
							{
								sfstrclose(tmp);
								goto primary;
							}
						}

						/*
						 * check terminal unconstrained metarules
						 */

						debug((-9, "check terminal unconstrained metarules"));
						for (v = terminal; v; v = v->next)
							if (metarule(v->rule->name, NiL, 0))
							{
								if (!buf)
									buf = sfstropen();
								metaexpand(buf, t, v->rule->name);
								if ((s = bindfile(NiL, sfstruse(buf), 0)) && s->status != UPDATE && s->time)
								{
									s->status = EXISTS;
									s = makerule(t);
									sfstrclose(buf);
									sfstrclose(tmp);
									goto primary;
								}
							}
					}
		if (buf)
			sfstrclose(buf);
		if (tmp)
			sfstrclose(tmp);
	}

	/*
	 * unconstrained metarules ignored on recursive calls
	 */

	if (!meta)
		return 0;

	/*
	 * check terminal unconstrained metarules
	 */

	if (prereqs)
	{
		/*
		 * check explicit terminal unconstrained prereqs
		 */

		t = sfstrbase(internal.met);
		for (p = terminal; p; p = p->next)
			if (m = metarule(p->rule->name, NiL, 0))
				for (q = prereqs; q; q = q->next)
					if (metamatch(t, q->rule->name, p->rule->name) && streq(t, b) && (s = bindfile(q->rule, NiL, 0)) && s->time)
					{
						s->status = EXISTS;
						goto unconstrained;
					}
	}

	/*
	 * check implicit terminal unconstrained prereqs
	 */

	for (p = terminal; p; p = p->next)
		if ((m = metarule(p->rule->name, NiL, 0)) && (!(r->property & P_terminal) || (m->property & P_terminal)))
		{
			metaexpand(internal.met, b, p->rule->name);
			if ((s = bindfile(NiL, sfstruse(internal.met), 0)) && s->time)
			{
				s->status = EXISTS;
				goto unconstrained;
			}
		}

	/*
	 * check nonterminal unconstrained metarules
	 * nonterminals skipped if r matched any constrained metarule target
	 */

	if (!matched && !(r->property & P_terminal) && (x = metainfo('N', NiL, NiL, 0)) && x->prereqs)
	{
		/*
		 * don't try to match excluded patterns
		 */

		if ((s = metainfo('X', NiL, NiL, 0)))
			for (p = s->prereqs; p; p = p->next)
				if (metamatch(NiL, u, p->rule->name))
				{
					matched = 1;
					break;
				}
		if (!matched)
		{
			debug((-9, "check nonterminal unconstrained metarules"));
			if (prereqs)
			{
				/*
				 * check explicit prereqs -- easy
				 */

				debug((-9, "check explicit nonterminal unconstrained metarule prerequisites"));
				t = sfstrbase(internal.met);
				for (p = x->prereqs; p; p = p->next)
					if (m = metarule(p->rule->name, NiL, 0))
						for (q = prereqs; q; q = q->next)
							if (metamatch(t, q->rule->name, p->rule->name) && streq(t, b))
							{
								s = q->rule;
								goto unconstrained;
							}
			}

			/*
			 * check implicit prereqs -- hardest
			 */

			debug((-9, "check implicit nonterminal unconstrained metarule prerequisites"));
			for (q = x->prereqs; q; q = q->next)
				if (m = metarule(q->rule->name, NiL, 0))
				{
					metaexpand(internal.met, b, q->rule->name);
					t = sfstruse(internal.met);
					s = getrule(t);
					if (isstatevar(t))
					{
				 		if (s && (s = bindstate(s, NiL)))
							goto unconstrained;
					}
					else if (s = bindfile(s, t, BIND_RULE))
					{
						/*
						 * use s if it exists
						 */

						if (s->status != UPDATE && (s->time || (s->property & P_target)))
							goto unconstrained;

						/*
						 * use s if it can be generated
						 */

						if (metaget(s, NiL, stem, NiL))
							goto unconstrained;
					}
				}
		}
	}

	/*
	 * no metarule match
	 */

	return 0;

 unconstrained:

	/*
	 * unconstrained match
	 */

	strcpy(stem, b);

 primary:

	/*
	 * primary match
	 */

	if (meta)
	{
		if (m->uname)
		{
			/*
			 * metarule intermediate prerequisite
			 */

			if (!(x = metarule(m->uname, p->rule->name, 0)) || x->uname)
			{
				x = 0;
				if (y = metainfo('O', m->uname, NiL, 0))
					for (q = y->prereqs; q; q = q->next)
						if (metamatch(stem, u, q->rule->name) && (x = metarule(m->uname, q->rule->name, 0)))
						{
							if (!x->uname)
								break;
							x = 0;
						}
			}
			if (x)
			{
				metaexpand(internal.met, stem, m->uname);
				if (!((y = makerule(sfstruse(internal.met)))->property & P_terminal))
				{
					*meta = x;
					s = y;
					goto found;
				}
			}
		}
		*meta = m;
	}
	if (m->property & P_terminal)
		s->property |= P_terminal;
	else if ((x = metainfo('S', m->name, NiL, 0)) && !(x->property & P_terminal))
		for (p = x->prereqs; p; p = p->next)
		{
			metaexpand(internal.met, stem, p->rule->name);
			if ((x = getrule(sfstruse(internal.met))) && (x->property & P_terminal))
				return 0;
		}
 found:
	if (meta && (state.targetcontext || state.targetprefix) && (t = strrchr(u = unbound(r), '/')))
	{
		*t = 0;
		if (!strchr(s->name, '/'))
		{
			sfprintf(internal.met, "%s%s%s", u, state.targetprefix ? state.targetprefix : "/", s->name);
			s = makerule(sfstruse(internal.met));
		}
		if (!strchr(stem, '/'))
		{
			sfputr(internal.met, stem, 0);
			sfsprintf(stem, MAXNAME - 1, "%s%s%s", u, state.targetprefix ? state.targetprefix : "/", sfstruse(internal.met));
		}
		*t = '/';
	}
	return s;
}
