/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
*/
/*
 * ShapeTools/shape program - rule.c
 *
 * Author: Axel Mahler (Axel.Mahler@cs.tu-berlin.de) and Wolfgang Obst
 *
 * $Header: rule.c[8.0] Tue May 24 15:19:17 1994 axel@cs.tu-berlin.de frozen $
 */
#ifndef lint
static char *AtFSid = "$Header: rule.c[8.0] Tue May 24 15:19:17 1994 axel@cs.tu-berlin.de frozen $";
#endif

/* intermixing of implicit "." rules and normal targets is *not* allowed */

#include <ctype.h>
#include "shape.h"

#define RULETABSIZE 257
#define MAXRULELENGTH 2048

EXPORT struct rules *ruletab[RULETABSIZE];
EXPORT char *firsttarget;

LOCAL char *special_target[] = {
    ".BPOOL",
    ".DEFAULT",
    ".IGNORE",
    ".NOBPOOL",
    ".SILENT",
    ".SUFFIXES",
    (char *) 0 } ;

LOCAL Bool oldrule;
LOCAL Bool doublecolon;

LOCAL int depnr, targnr, cmdnr, heritnr;
LOCAL char *targfield[MAXTARGS], *depfield[MAXDEPS], *cmdfield[MAXCMDS];
LOCAL char *heritfield[MAXHERIT];

#define LINELEN 128
#define copy_token(source, dest) { register char *s = source, \
				      *d = dest; \
					while (*s && !isspace(*s)) \
					  *d++ = *s++; \
					    *d = '\0'; }

#define advance(tok) { while (*tok && !isspace(*tok)) tok++; \
			  while (*tok && isspace(*tok)) tok++; }

#define skip_leading_space(tok) { while (*tok && isspace(*tok)) tok++; }

LOCAL Bool is_macro (token) char *token; {
  register char *tc = token;

  while (*tc && !isspace (*tc) && !(*tc == '$')) tc++;
  return *tc == '$';
}

LOCAL char *get_token_from_deplist (dep_rule, nth, junk)
     struct rules *dep_rule;
     int nth;
     int junk; /* unused; kept for compatibility during testing */
{
  /*
   * Returns "nth" token from the list of dependents in the
   * dependency rule "dep_rule". Macros are not expanded.
   */

  static struct rules *last_rule;
  static int last_token_number;
  static char nth_dependent[PATH_MAX];
  static Bool got_token = FALSE;
  char *this_token;
  int this_token_position = 0;

  if (!dep_rule) return NIL;
  if (!dep_rule->deplist) return NIL;

  if ((last_rule == dep_rule) && (nth == last_token_number))
      return got_token ? nth_dependent : NIL;
  else {
    last_rule = dep_rule;
    last_token_number = nth;
    got_token = FALSE;
  }

  this_token = dep_rule->deplist;
  skip_leading_space (this_token);

  do {
    if (nth == this_token_position) {
      if (*this_token) {
	copy_token (this_token, nth_dependent);
	got_token = TRUE;
	/*
	 * special treatment for "./targets"
	 */
	if (nth_dependent[0] == '.')
	  if (nth_dependent[1] == '/') {
	    register char *s1 = nth_dependent, *s2 = nth_dependent + 2;
	    while ((*s1++ = *s2++));
	  }
	return nth_dependent;
      }
      else
	return NIL;
    }
    else {
      advance (this_token);
      this_token_position++;
    }
  } while (*this_token);
  return NIL;
}

LOCAL char *expand_token (tok) char *tok; {
  char tokbuf[LINELEN];

  copy_token (tok, tokbuf);
  return expandmacro (tokbuf);
}

LOCAL char *insert_expansion (baseaddr, position, insertion)
     char **baseaddr, *position, *insertion;
{
  char tokbuf[LINELEN], *base = *baseaddr;
  unsigned int replacement_offset = position - base, 
               trailer_length, replace_length, replacement_length,
               new_base_length;
  char *new_base, *hook;

  copy_token (position, tokbuf);
  replace_length = strlen (tokbuf);
  trailer_length = strlen (position + replace_length);
  replacement_length = strlen (insertion);
  new_base_length = replacement_offset + replacement_length + trailer_length+1;
  if ((new_base = malloc (new_base_length)) == NIL) return position;
  strncpy (new_base, base, replacement_offset);
  new_base[replacement_offset] = '\0';
  strcat (new_base, insertion);
  strcat (new_base, position + replace_length);
  if ((hook = realloc (base, new_base_length)) == NIL) {
    free (new_base);
    return position;
  }
  else {
    *baseaddr = hook;
    base = *baseaddr;
  }
  strcpy (base, new_base);
  position = base + replacement_offset;
  free (new_base);
  return position;
}

LOCAL char *get_src_name(name)
     char *name;
{
  static char fname[MYMAXNAMLEN];
  struct rules *dep_rule;
  char *f_suffix, *d_suffix, *dep;

  if (!name) return NIL;
  dep_rule = get_target (name);
  if (!dep_rule) return NIL;

  dep = get_token_from_deplist (dep_rule, 0, 0);
  if (!is_pattern (dep))
    return dep;

  strcpy(fname,name);
  f_suffix = strrchr (fname, '.');
  d_suffix = strchr (dep, '.');

  if (f_suffix) 
    *f_suffix = '\0';
  if (d_suffix) 
    strcat (fname, d_suffix);

  return fname;
}

LOCAL Bool is_old_rule(string)
     char *string;
{
  if (strchr(string,'/') != NIL)
    return(FALSE);
  if ((string[0] == '.') &&
      (strchr(string+2,'.') != NIL))
    return(TRUE);
  else
    return(FALSE);
}
   
LOCAL void overload_stdrule(k)
     int k;
{
  register int i;
  
  for(i = 0; implicit_suffs[i] != -1; i++)
    {
      if((strcmp(stdruletab[implicit_suffs[i]]->name,targfield[k]) == 0) &&
	 (strcmp(get_dep(stdruletab[implicit_suffs[i]],0), depfield[1]) == 0))
	{
	  implicit_suffs[i] = lastrule;
	  return;
	}
    }
  implicit_suffs[lastrule] = lastrule;
}

LOCAL int convert_comm(j,suff)
     int j;
     char *suff;
{
  char newcomm[1024];
  register char *p,*p2;
  register int i;
  Bool mod = FALSE;
  p = cmdfield[j];
  i = 0;
  if(strchr(cmdfield[j],'$') == NIL)
    return(0);
  while(*p != '\0')
    {
      if(*p != '$')
	{
	  newcomm[i] = *p;
	  p++;
	  i++;
	  newcomm[i] = '\000';
	}
      else
	{
	  switch(*(p+1))
	    {
	    case '*':
	      mod = TRUE;
	      newcomm[i] = '%';
	      i++;
	      newcomm[i] = '\0';
	      p = p+2;
	      break;
	    case '<':
	      mod = TRUE;
	      p2 = suff;
	      while(*p2 != '\0')
		{
		  newcomm[i] = *p2;
		  p2++;
		  i++;
		}
	      p = p+2;
	      break;
	    default:
	      newcomm[i] = '$';
	      i++;
	      p++;
	      break;
	    }
	}
    }
  if(!mod)
    return(0);
  newcomm[i] = '\0';
  if((cmdfield[j] = realloc(cmdfield[j],
			    (unsigned)(strlen(newcomm)
				       + sizeof(char)))) == NIL)
    errexit(10,"realloc");
  strcpy(cmdfield[j],newcomm);
  return(0);
}

LOCAL void convertrule(i)
     int i;
{
  char *p;
  register int j;
  p = strrchr (targfield[i],'.');
  while ((p != NULL) && (*p == '.')) p--;
  if (p != NULL) {
    *++p = '\0';
    p++;
  }
  if ((depfield[1] = malloc((unsigned) (strlen(targfield[i]) + 3))) == NIL)
    errexit(10,"malloc");
  strcpy(depfield[1],"%");
  strcat(depfield[1],targfield[i]);
  if((targfield[1] = malloc((unsigned) (strlen(p) + 3))) == NIL)
    errexit(10,"malloc");
  strcpy(targfield[1],"%.");
  strcat(targfield[1],p);
  targnr = 1;
  depnr = 1;
  for(j = 1; j <= cmdnr; j++)
    convert_comm(j,depfield[1]);
}

EXPORT void ruledef(string)
     char *string;
{
  register int i = 0, k = 0;
  int i2 = 0;
  char klazu;
  char t[MAXRULELENGTH], string2[MAXRULELENGTH];

  targnr = 0;
  depnr = 0;
  cmdnr = 0;
  heritnr = 0;

  while(string[i] != ':')
    i++;
  i2 = i;
  strncpy(string2,string,i + 1);
  string2[i+1] = '\0';

  i = 0;
  while(string2[i] != ':') {
    while((string2[i] != ' ') && (string2[i] != '\t') && (string2[i] != ':'))
      {
	t[k] = string2[i];
	i++;
	k++;
      }
    t[k] = '\0';
    targnr++;
    if (targnr > MAXTARGS)
      errexit(27, "targets");
    if ((targfield[targnr] = malloc((unsigned) strlen(t) + 1)) == NIL)
      errexit(10,"malloc");
    strcpy(targfield[targnr], t);
    k = 0;
    while((string2[i] == ' ') || (string2[i] == '\t'))
      i++;
  }
  i = i2;
  if (string[i+1] == ':')
    {
      doublecolon = TRUE;
      i = i + 2;
    }
  else
    i++;

  while((string[i] != '\0') && (string[i] != ';') && (string[i] != ':')) {
    while((string[i] == ' ') || (string[i] == '\t') ||
	  (string[i] == '\\'))
      {
	if ((string[i] == '\\') && (string[i+1] == '\n'))
	  i = i+2;
	else
	  i++;
      }
    while((string[i] != ' ') && (string[i] != '\t') &&
	  (string[i] != ';') && (string[i] != '\0') &&
	  (string[i] != ':') && (string[i] != '\\'))
      {
	t[k] = string[i];
	i++;
	k++;
      }
    t[k] = '\0';
    if (k != 0)
      {
	depnr++;
	if (depnr > MAXDEPS)
	  errexit(27, "dependents");
	if ((depfield[depnr] = malloc((unsigned) strlen(t) + 1)) == NIL)
	  errexit(10,"malloc");
	strcpy(depfield[depnr],t);
      }
    k = 0;
  }

  while((string[i] == ' ') || (string[i] == '\t'))
    i++;

  /* heritage */

  k = 0;
  if (string[i] == ':') {
    i++;
    while (string[i]) {
      while(isspace(string[i])) i++;
      if (!string[i]) break;
      if (string[i] != '+')
	errexit(26, &string[i]);
      switch (string[i+1]) {
      case '(':
	klazu = ')';
	break;
      case '{':
	klazu = '}';
	break;
      default:
	klazu = ' ';
      }
    
      while (string[i] != klazu) {
	t[k] = string[i];
	i++;
	k++;
	if (string[i] == '\0')
	  errexit(26,&string[i]);
      }
      t[k] = string[i];
      k++;
      i++;
      t[k] = '\0';
      heritnr++;
      if (heritnr > MAXHERIT)
	errexit(27, "iherits");
      if ((heritfield[heritnr] = malloc((unsigned) strlen(t) + 1)) == NIL)
	errexit(10,"malloc");
      strcpy(heritfield[heritnr],t);
      k = 0;
    }
  }

  /* commands on ruledef line */
  if( string[i] == ';') {
    i++;
    while( (string[i] != '\0'))
      {
	t[k] = string[i];
	i++;
	k++;
      }
    t[k] = '\0';
    cmdnr++;
    if (cmdnr > MAXCMDS)
      errexit(27, "commands");
    if ((cmdfield[cmdnr] = malloc((unsigned) strlen(t) + 1)) == NIL)
      errexit(10,"malloc");
    if (t[strlen(t)-1] == '\n')
      t[strlen(t)-1] = '\0';
    strcpy(cmdfield[cmdnr], t);
  }
}

EXPORT void rulecont(string)
     char *string;
{
  /* only commands have been found */
  cmdnr++;
  if(cmdnr > MAXCMDS)
    errexit(27, "commands");
  if ((cmdfield[cmdnr] = malloc ((unsigned) strlen(string) + 1)) == NIL)
    errexit(10,"malloc");
  if (string[strlen(string)-1] == '\n')
    string[strlen(string)-1] = '\0';
  strcpy(cmdfield[cmdnr], string);
}

EXPORT void ruleend()
{
  struct rules *current, *previous;
  struct rules *first_target, *next_target;
  struct cmds *curcmd;
  register int i = 0, j = 0;
  int kkk = 0, xx = 0, ss = 0, hasht = 0, targs = 0;
  Bool src_found = FALSE;
  char *p, *srcname, *get_token_from_deplist();
  Bool std_rule = FALSE, found = FALSE;
  
  if(targnr == 0)
    return;

  if(!strcmp(targfield[1],".SILENT"))
    silentflg = TRUE ;

  if(!strcmp(targfield[1],".BPOOL"))
    bpoolflg = TRUE;

  if(!strcmp(targfield[1],".NOBPOOL"))
    nobpoolflg = TRUE;

  if(!strcmp(targfield[1],".IGNORE"))
    ignoreflg = TRUE;
  
  if ((is_old_rule(targfield[1])) && (depfield[1] == NULL) && 
      (strcmp(targfield[1],".SUFFIXES") != 0))
    {
      oldrule = TRUE;
      targs = targnr;
      for(i = 1; i <= targs; i++)
	{
	  if (is_old_rule(targfield[i]))
	    {
	      if (targfield[i] != NIL)
		convertrule(i);
	    }
	      ruleend();
	}
      oldrule = FALSE;
      return;
    }

  for(i = 1; i <= targnr; i++)
    {
      p = strchr(targfield[i],'%');
      if (is_pattern (p))
	{
	  std_rule = TRUE;
	  break;
	}
    }

  for(i = 1; i <= targnr; i++)
    {
      found = FALSE;
      if(!std_rule)
	{
	  hasht = hashval(targfield[i], RULETABSIZE);
	  if ( ruletab[hasht] == (struct rules *) NIL)
	    {
	      if((current = ruletab[hasht] = (struct rules *) malloc( sizeof(struct rules))) == (struct rules *)NIL)
		errexit(10,"malloc");
	      memset (current, 0, sizeof (struct rules));
	    }
	  else
	  {
	    current = ruletab[hasht];
	    if ((!strcmp(ruletab[hasht]->name, targfield[i])) &&
		(strcmp(ruletab[hasht]->name, ".SUFFIXES")) &&
		(current->doublecolon == FALSE) &&
		(doublecolon == FALSE) &&
		(ruletab[hasht]->cmdlist != (struct cmds *) NIL) &&
		(cmdfield[1] != NIL))
	      errexit(1, targfield[i]);

	    if((!strcmp(current->name,targfield[i])) &&
	       ((cmdfield[0] == NIL) ||
		(current->cmdlist == (struct cmds *) NIL)))
	      {
		found = TRUE;
	      }

	    while((current != (struct rules *) NIL) && (!found))
	      {
		if((strcmp(current->name,targfield[i]) == 0) &&
		   ((cmdfield[0] == NIL) ||
		    (current->cmdlist == (struct cmds *) NIL)))
		  {
		    found = TRUE;
		    break;
		  }
		else
		  {
		    previous = current;
		    current = current->nextrule;
		  }
	      }
	    if(found)
	      {
		if ((strcmp(current->name, targfield[i]) == 0) &&
		    (strcmp(ruletab[hasht]->name, ".SUFFIXES") != 0) &&
		    (ruletab[hasht]->cmdlist != (struct cmds *) NIL) &&
		    (current->doublecolon == FALSE) &&
		    (doublecolon == FALSE) &&
		    (cmdfield[1] != NIL))
		  errexit(1, targfield[i]);
		if ((strcmp(current->name, ".SUFFIXES")) == 0)
		  {
		    if (depnr == 0) /* delete suffix list */
		      {
			current->deplist = NIL;
		      }
		    else
		      {
			if (current->deplist == NIL)
			  {
			    if ((current->deplist = malloc((unsigned) 1)) == NIL)
			      errexit(10,"malloc");
			    current->deplist[0] = '\0';
			  }
			for (kkk = 1; kkk <= depnr; kkk++)
			  {
			    if ((current->deplist =
				 realloc(current->deplist, (unsigned) (strlen(depfield[kkk]) + strlen(current->deplist) + 3))) == NIL)
			      errexit(10,"realloc");
			    strcat(current->deplist," ");
			    strcat(current->deplist, depfield[kkk]);
			  }
		      }
		  }
	      }
	    if (!found)
	      {
		if((previous->nextrule = current = 
		    (struct rules *) malloc(sizeof(struct rules)))
		   == (struct rules *) NIL)
		  errexit(10,"malloc");
		memset (current, 0, sizeof (struct rules));
	      }
	  }
	}
      else
	{
	  lastrule++;
	  if((current = stdruletab[lastrule] = 
	      (struct rules *) malloc( sizeof(struct rules))) == 
	     (struct rules *) NIL)
	    errexit(10,"malloc");
	  memset ((char *)current, 0, sizeof (struct rules));
	  overload_stdrule(i);
	  implicit_suffs[lastrule+1] = -1;
	  stdruletab[lastrule+1] = (struct rules *) NIL;
	}
      if (!found)
	{
	  if((current->name = malloc( (unsigned) strlen( targfield[i] ) + 1)) == NIL)
	    errexit(10,"malloc");
	  strcpy(current->name, targfield[i]);
	  current->targetlist[0] = NIL;
	  current->heritage[0] = NIL;
	}
      current->done = 0;
      if (doublecolon)
	current->doublecolon = TRUE;
      else
	current->doublecolon = FALSE;
      if(i == 1)
	{
	  first_target = current;
	  next_target = current;
	  current->next = (struct rules *)NIL;
	  current->saved = FALSE;
	}
      else
	{
	  if(cmdnr >= 1)
	    {
	      next_target->next = current;
	      next_target = current;
	    }
	}
      if((i == 1) && (firsttarget == NIL) && !is_pattern (current->name) &&
	 ! is_old_rule(current->name) && ! is_special_target(current->name))
	 firsttarget = current->name;
      if(!found)
	{
	  current->deplist = NIL;
	  current->cmdlist = (struct cmds *) NIL;
	  current->nextrule = (struct rules *) NIL;
	}
      if ((depnr > 0) && (!found))
	{
	  if ((current->firstdep = malloc((unsigned) (strlen(depfield[1]) + sizeof(char)))) == NIL)
	    errexit(10,"malloc");
	  strcpy(current->firstdep, depfield[1]);
	}
      
      if (found)
	{
	  xx = 0;
	  while (get_token_from_deplist (current, xx, 0)) xx++;
	}	  
      for(j = xx+1; j <= xx+depnr; j++)
	{
	  for(ss = 0; get_token_from_deplist(current,ss,0) != NIL; ss++)
	    {
	      if (!strcmp(get_token_from_deplist(current,ss,0),depfield[j-xx]))
		{
		  src_found = TRUE;
		  break;
		}
	    }
	  if(!src_found)
	    {
	      if(current->deplist == NIL)
		{
		  if((current->deplist = 
		      malloc( (unsigned) (strlen (depfield[j-xx]) + 1))) == NIL)
		    errexit(10,"malloc");
		  strcpy(current->deplist,depfield[j-xx]);
		}
	      else
		{
		  if ((current->deplist =
		       realloc(current->deplist,
			       (unsigned)
			       (strlen(current->deplist) + (strlen(depfield[j-xx]))) + 3)) == NIL)
		    errexit(10,"realloc");
		  strcat(current->deplist," ");
		  strcat(current->deplist, depfield[j-xx]);
		}
	    }
	  src_found = FALSE;
	}
	
      /* get standard dependent */

      if (!is_pattern (current->name))
	{
	  if((srcname = get_src_name(current->name)) != NIL)
	    {
	      for(ss = 0; get_token_from_deplist(current,ss,0) != NIL; ss++)
		{
		  if(!strcmp(srcname,get_token_from_deplist (current,ss,0)))
		    {
		      src_found = TRUE;
		      break;
		    }
		}
	      if (!src_found)
		{
		  if (current->deplist == NIL)
		    {
		      if((current->deplist = 
			  malloc( (unsigned) (strlen(srcname) + 1))) == NIL)
			errexit(10,"malloc");
		      strcpy(current->deplist,srcname);
		    }
		  else
		    {
		      if((current->deplist =
			  realloc(current->deplist,
				  (unsigned) (strlen(srcname) + strlen(current->deplist) + 3))) == NIL)
			errexit(10,"realloc");
		      strcat(current->deplist," ");
		      strcat(current->deplist,srcname);
		    }
		}
	      src_found = FALSE;
	    }
	}

      if (heritnr > 0)
	{
	  j = 0;
	  while(current->heritage[j] != NIL)
	    j++;
	  for (j = j+1; j <= heritnr; j++)
	    {
	      if((current->heritage[j-1] =
		  malloc((unsigned) (strlen(heritfield[j]) + sizeof(char)))) == NIL)
		errexit(10,"malloc");
	      strcpy(current->heritage[j-1], heritfield[j]);
	    }
	  current->heritage[j-1] = NIL;
	}

      if(std_rule)
	{
	  for (j = 1; j <= targnr; j++)
	    {
	      if((current->targetlist[j-1] =
		  malloc((unsigned) (strlen(targfield[j]) + 1))) == NIL)
		errexit(10,"malloc");
	      strcpy(current->targetlist[j-1],targfield[j]);
	    }
	  current->targetlist[j-1] = NIL;
	  current->next = (struct rules *) NIL;
	}
      if (cmdnr > 0)
	{
	  if ((curcmd = current->cmdlist = (struct cmds *) malloc( sizeof( struct cmds))) == (struct cmds *) NIL)
	    errexit(10,"malloc");
	  for (j = 1; j <= cmdnr; j++)
	    {
	      if((curcmd->command = malloc( (unsigned) strlen (cmdfield[j]) + 1)) == NIL)
		errexit(10,"malloc");
	      strcpy(curcmd->command, cmdfield[j]);
	      if (j != cmdnr)
		{
		  if((curcmd = curcmd->nextcmd = (struct cmds *) malloc( sizeof( struct cmds))) == (struct cmds *) NIL)
		    errexit(10,"malloc");
		}
	      else
		curcmd->nextcmd = (struct cmds *) NIL;
	    }
	}

      if((targnr > 1) && (cmdnr > 0))
	current->next = first_target;
      else
	current->next = (struct rules *) NIL;
    }

  doublecolon = FALSE;
  if (!oldrule) {
    targnr = 0;
    targfield[1] = NIL;
    depnr =  0;
    depfield[1] = NIL;
    cmdnr = 0;
    cmdfield[1] = NIL;
    heritnr = 0;
    heritfield[1] = NIL;
  }
} /* end ruleend */

LOCAL struct rules *make_rule_object (target_name, dependent_list,
				      ingredient_macro_list, command_list)
     char *target_name; 
     struct stringlist *dependent_list, *ingredient_macro_list,
                       *command_list;
{
  struct rules *new_rule, *bucket_tail = (struct rules *)NULL;
  int hash_index = hashval (target_name, RULETABSIZE);
  struct rules *this_rule = ruletab[hash_index];
  Bool rule_already_defined = FALSE;
  sb_ptr dep_list;

  while (this_rule) {
    if (strcmp (this_rule->name, target_name)) {
	bucket_tail = this_rule;
	this_rule = this_rule->nextrule;
	continue;
      }
      else { /* rule is already defined somehow - add deps if necessary */
	struct stringlist *this_dependent = dependent_list;
	char *this_old_dependent;
	sb_ptr old_dependent_list = check_sbnew (MYMAXNAMLEN);
	Bool dep_already_known = FALSE;

	rule_already_defined = TRUE;

	check_sbcat (old_dependent_list, ruletab[hash_index]->deplist);
	while (this_dependent->string) {
	  this_old_dependent = stStrtok (sbstr (old_dependent_list));
	  while (this_old_dependent) {
	    if (strcmp (this_dependent->string, this_old_dependent)) {
	      this_old_dependent = stStrtok ((char *)NULL);
	      continue;
	    }
	    else {
	      dep_already_known = TRUE;
	      break;
	    }
	  }
	  if (!dep_already_known) {
	    old_dependent_list = check_sbcat (old_dependent_list, 
					      this_dependent->string);
	  }
	  this_dependent = this_dependent->next;
	}
	free (ruletab[hash_index]->deplist);
	ruletab[hash_index]->deplist = sbstr (old_dependent_list);
	free (old_dependent_list);
      }
  }
  if (rule_already_defined)
    return NULL;

  new_rule = (struct rules *) check_malloc (sizeof (struct rules));
  new_rule = memset (new_rule, 0, (sizeof (struct rules)));
  new_rule->name = check_strdup (target_name);
  dep_list = check_sbnew (MYMAXNAMLEN);
  {
    struct stringlist *this_dependent;

    for (this_dependent = dependent_list; this_dependent; 
	 this_dependent = this_dependent->next) {
      dep_list = check_sbcat (dep_list, this_dependent->string);
      dep_list = check_sbcat (dep_list, " ");
    }
  }
  new_rule->deplist = sbstr (dep_list);
  free (dep_list);
  new_rule->firstdep = check_strdup (dependent_list->string);

  return NULL;
} /* FINISH ME  / 13.4.93 */

LOCAL void link_related_rule_objects (rule_name_list) char *rule_name_list;
{
}

LOCAL void insert_rule_object (new_rule) struct rules *new_rule;
{
}

LOCAL char *trim (base, trim_from) char *base, *trim_from; {
  /*
   * Prefixes of <base> that are identical to <trim_from>
   * are removed from the beginning of <base>
   */
  register char *p = base, *q;

  do {
    q = trim_from;
    while (*q && (*p == *q)) { p++; q++; }
  } while (!*q);
  return p;
}

LOCAL Bool test_path (path) char *path; {
  /*
   * returns TRUE if 
   *  a) "path" is just a file name without a path prefix
   *  b) "path" has a path prefix, and the prefix points to
   *     an existing and searchable directory.
   * returns FALSE otherwise.
   */

  char *cp_path, *slash;

  if (path == NULL) return FALSE;

  cp_path = check_strdup (path);
  slash = strrchr (cp_path, '/');

  if (!slash) return TRUE;

  *slash = '\0';

  /* test for existence by cd-ing to directory. If success, go back */
  if (chdir (cp_path) < 0) return FALSE;
  chdir (curvpath[0]);
  return TRUE;
}

LOCAL Bool is_suffix_pattern (pattern) char *pattern; {
  /*
   * check whether a given name of an implicit (pattern) rule 
   * is in suffix normal form (SNF). If it is in SNF, return 
   * TRUE. Return FALSE otherwise.
   * SNF is optional path, immediately followed by '%', immediately
   * followed by an optional period-separated suffix. 
   * The suffix shall not contain any more period chars.
   */

  char *percent;
  char *period;

  if (pattern == NULL) return FALSE;
  percent = strrchr (pattern, '%');
  period = strrchr (pattern, '.');

  if (percent == NULL) return TRUE;

  /* check for leading stuff */
  if ((percent > pattern) && (*(percent-1) != '/') 
       && !isspace (*(percent-1))) return FALSE;

  /* If leading stuff is a path prefix, we need not consider it */

  switch (*(percent+1)) {
  case '\0':
    return TRUE;
  case '.':
    return (percent+1) == period;
  default:
    return isspace(*(percent+1));
  }
}
    

EXPORT char *subst_char (template, s_char, substitution)
     char *template;
     char s_char;
     char *substitution;
{
  /*
   * this function substitutes all occurrences of 's_char' in 'template'
   * with 'substitution'.
   * The modified string is returned in static memory.
   * If no occurrence of 's_char' in 'template' is found, 'template'
   * is returned.
   */
  static char result_str[MYMAXNAMLEN];
  char *c1, *c2;

  c1 = template;
  c2 = strchr (c1, s_char);
  if (c2 == NULL)
    return template;

  (void) strncpy (result_str, c1, c2-c1);
  result_str[c2-c1] = '\0';
  (void) strcat (result_str, substitution ? substitution : "");
  c1 = c2+1;
  c2 = strchr (c1, s_char);
  while (c2) {
    (void) strncat (result_str, c1, c2-c1);
    result_str[c2-c1] = '\0';
    (void) strcat (result_str, substitution ? substitution : "");
    c1 = c2+1;
    c2 = strchr (c1, s_char);
  }
  (void) strcat (result_str, c1);
  return result_str;
}

EXPORT char *escape_char (template, s_char)
     char *template;
     char s_char;
{
  /*
   * this function escapes all occurrences of 's_char' in 'template'
   * by prefixing it with a '\' character.
   * The modified string is returned in static memory.
   * If no occurrence of 's_char' in 'template' is found, 'template'
   * is returned.
   */
  static char result_str[MYMAXNAMLEN];
  char interim[MYMAXNAMLEN], s_str[3];
  char *c1, *c2;

  c1 = template;
  c2 = strchr (c1, s_char);
  if (c2 == NULL)
    return template;

  (void) strcpy (interim, template);
  s_str[0] = '\\';
  s_str[1] = s_char;
  s_str[2] = '\0';
  c2 = interim + (c2-c1);
  c1 = interim;

  (void) strncpy (result_str, c1, c2-c1);
  result_str[c2-c1] = '\0';
  (void) strcat (result_str, s_str);
  c1 = c2+1;
  c2 = strchr (c1, s_char);
  while (c2) {
    (void) strncat (result_str, c1, c2-c1);
    (void) strcat (result_str, s_str);
    c1 = c2+1;
    c2 = strchr (c1, s_char);
  }
  (void) strcat (result_str, c1);
  return result_str;
}

EXPORT void make_derivation_rule (target_list, dependent_list, 
				  ingredient_macro_list, command_list)
     struct stringlist *target_list, *dependent_list, 
                       *ingredient_macro_list, *command_list;
{
  sb_ptr expanded_targets;
  struct stringlist *this_token = target_list;
  struct rules *new_derivation_rule;
  char cur_tok[MYMAXNAMLEN], *next_tok;

  if (!(target_list && target_list->string && *target_list->string))
    return;

  expanded_targets = check_sbnew (MYMAXNAMLEN);
  while (this_token) {
    if (is_macro (this_token->string)) {
      expanded_targets = check_sbcat (expanded_targets, 
				      expandmacro (this_token->string));
    }
    else {
      expanded_targets = check_sbcat (expanded_targets, this_token->string);
    }
    expanded_targets = check_sbcat (expanded_targets, " ");
    this_token = this_token->next;
  }
  next_tok = sbstr (expanded_targets);
  copy_token(next_tok, cur_tok);
  while (*cur_tok) {
    new_derivation_rule = make_rule_object (cur_tok, dependent_list,
					    ingredient_macro_list, 
					    command_list);
    insert_rule_object (new_derivation_rule);
    advance(next_tok);
    copy_token(next_tok, cur_tok);
  }
  link_related_rule_objects (sbstr (expanded_targets));
  sbfree (expanded_targets);
}

EXPORT void dump_rule (dest, rule, dump_commands)
     FILE *dest;
     struct rules *rule;
     Bool dump_commands;
{
  struct cmds *this_cmd;
  register int i;
  char *deps, *this_dep;

  if (rule == NULL) return;

  for (i = 0; rule->targetlist[i] && (i < MAXHERIT); i++) {
    fprintf (dest, "%s ", rule->targetlist[i]);
  }
  fprintf (dest, "%s:", rule->targetlist[0] ? "" : rule->name);
  deps = expandmacro (rule->deplist);
  this_dep = stStrtok (deps);
  if (atBindTestRule (this_dep)) 
    this_dep = stStrtok (NULL);
  while (this_dep) {
    fprintf (dest, " %s", trim (this_dep, "./"));
    this_dep = stStrtok (NULL);
  }
  fprintf (dest, "\n");

  if (!dump_commands) return;

  this_cmd = rule->cmdlist;
  while (this_cmd) {
    if (this_cmd->command && *this_cmd->command)
      fprintf (dest, "\t%s\n", expandmacro (this_cmd->command));
    this_cmd = this_cmd->nextcmd;
  }
}

EXPORT void ruledump(fd) FILE *fd; {
  register struct rules *cur;
  register struct cmds *ccmd;
  register int i, k;
  char *pp = NIL;

  for(i = 0; i< RULETABSIZE; i++) {
    if (ruletab[i] != (struct rules *) NIL) {
      cur = ruletab[i];
      while( cur != (struct rules *) NIL)
	{
	  if(fd == stdout)
	    fprintf(fd,"%s\n", cur->name);
	  else
	    fprintf(fd,"%s", cur->name);

	  if (fd == stdout)
	    fprintf(fd," depends on:");
	  else
	    fprintf(fd,":");

	  if(cur->deplist != NIL) {
	    if(atBindTestRule(get_dep(cur,0))) {
	      pp = strchr(cur->deplist,' ');
	      if(pp != NIL) {
		pp++;
		fprintf(fd," %s",pp);
	      }
	    }
	    else
	      fprintf(fd," %s",cur->deplist);
	  }

	  if(fd == stdout)
	    fprintf(fd,"\n");

	  if (cur->heritage[0] != NIL) {
	    if(fd == stdout)
	      fprintf(fd," inherits:");
	    else
	      fprintf(fd," :");
	  }

	  k = 0;
	  while (cur->heritage[k] != NIL) {
	    fprintf(fd," %s", cur->heritage[k]);
	    k++;
	  }
	  fprintf(fd,"\n");

	  ccmd = cur->cmdlist;
	  if (ccmd == '\0')
	    ccmd = (struct cmds *) NIL;

	  if(fd == stdout)
	    fprintf(fd," commands:\n");
	      
	  while (ccmd != (struct cmds *) NIL) {
	    if(ccmd->command != NIL)
	      fprintf(fd,"%s\n", ccmd->command);
	    ccmd = ccmd->nextcmd;
	  }
	  fprintf(fd,"\n");
	  cur = cur->nextrule;
	}
    }
  }

  for(i = 0; i <= lastrule; i++) {
    cur = stdruletab[i];

    if(fd == stdout)
      fprintf(fd,"%s\n", cur->name);
    else
      fprintf(fd,"%s", cur->name);

    if (fd == stdout)
      fprintf(fd," depends on:");
    else
      fprintf(fd," :");

    fprintf(fd," %s",cur->deplist);

    if (fd == stdout)
      fprintf(fd,"\n");

    k = 0;
    
    if (fd == stdout)
      fprintf(fd," inherits:");
    else
      fprintf(fd," :");

    while (cur->heritage[k] != NIL) {
      fprintf(fd," %s", cur->heritage[k]);
      k++;
    }
    fprintf(fd,"\n");

    ccmd = cur->cmdlist;
    
    if (fd == stdout)
      fprintf(fd," commands:\n");

    while (ccmd != (struct cmds *) NIL) {
      if(ccmd->command != NIL)
	fprintf(fd,"%s\n", ccmd->command);
      ccmd = ccmd->nextcmd;
      if (ccmd == '\0')
	ccmd = (struct cmds *) NIL;
    }
    fprintf(fd,"\n");
  }

  fflush(fd);
} /* end ruledump */


EXPORT void adjust_stdrules(suffs)
     char *suffs;
{
  /* empty */;
}

	
	
EXPORT void init_ruletab()
{
  memset((char *) ruletab, 0, 257 * sizeof(struct rules *));
}

EXPORT void reset_dep_list (dep_rule)
     struct rules *dep_rule;
{
  if (dep_rule->_deplist_buffer)
    free (dep_rule->_deplist_buffer);

  if (dep_rule->deplist)
    dep_rule->_deplist_buffer = check_strdup (dep_rule->deplist);
  else
    dep_rule->_deplist_buffer = NULL;
}

EXPORT char *get_dep (dep_rule, nth)
     struct rules *dep_rule;
     int nth;
{
  /*
   * Returns "nth" dependent from the list of dependents in the
   * dependency rule "dep_rule".
   * Principle is: for each token that has a lower token number than 
   * "nth", check if it is a macro, and expand it if needed.
   * Proceed through tokens in expanded macro. If tokens in expanded
   * macro are exhausted, proceed as before .....
   */

  static struct rules *last_rule;
  static int last_token_number;
  static char nth_dependent[PATH_MAX];
  static Bool got_token = FALSE;
  char **expand_string, *this_token, *m, *expand_token(), *insert_expansion();
  int this_token_position = 0;

  if (!dep_rule) return "";
  if (!dep_rule->deplist) return "";

  expand_string = dep_rule->_deplist_buffer ? &(dep_rule->_deplist_buffer) :
    &(dep_rule->deplist);

  if ((last_rule == dep_rule) && (nth == last_token_number))
      return got_token ? nth_dependent : "";
  else {
    last_rule = dep_rule;
    last_token_number = nth;
    got_token = FALSE;
  }

  this_token = *expand_string;
  skip_leading_space (this_token);

  do {
    if (nth == this_token_position) {
      if (!is_macro (this_token)) {
	if (*this_token) {
	  copy_token (this_token, nth_dependent);
	  got_token = TRUE;
	  /*
	   * special treatment for "./targets"
	   */
	  if (nth_dependent[0] == '.')
	    if (nth_dependent[1] == '/') {
	      register char *s1 = nth_dependent, *s2 = nth_dependent + 2;
	      while ((*s1++ = *s2++));
	    }
	  return nth_dependent;
	}
	else
	  return "";
      }
      else {
	m = expand_token (this_token);
	this_token = insert_expansion (expand_string, this_token, m);
	skip_leading_space (this_token);
	continue;
      }
    }
    if (!is_macro (this_token)) {
      advance (this_token);
      this_token_position++;
    }
    else {
      m = expand_token (this_token);
      this_token = insert_expansion (expand_string, this_token, m);
      skip_leading_space (this_token);
    }
  } while (*this_token);
  return "";
}

EXPORT void add_defaults (targ, rule)
     char *targ; struct rules *rule;
{
  /*
   * Make sure that the "primary dependent" of a target (e.g. "foo.c"
   * for target "foo.o" actually appears in the dependents list.
   * This is necessary, because there are often various explicit 
   * dependencies in a Shape/Makefile but the most basic dependency
   * is not specified, because it's the default.
   *
   * First, find applicable default rule for "targ". Then construct
   * a regular Shapefile dependency from the pattern rule and the 
   * "targ" name. Having the rule formed, it is passed to shape's 
   * rule definition machinery.
   */
  char default_dependency_line[2 * PATH_MAX], targ_edit[PATH_MAX],
        *this_target, *suff;
  struct rules *implicit_target(), 
      *default_rule = implicit_target (targ);
  register int i;
  register char *line_end, *deplist;

  if (!default_rule)
    default_rule = get_target (".DEFAULT");

  if (!default_rule)
    return;

  if (confid)
    add_rule_to_confid (default_rule);

  rule->cmdlist = default_rule->cmdlist;
  for (i = 0; (i < MAXHERIT) && default_rule->heritage[i]; i++)
    rule->heritage[i] = default_rule->heritage[i];

  /*
   * make target part ... 
   */
  default_dependency_line[0] = '\0';
  for (i = 0; (i < MAXHERIT) && 
       (this_target = default_rule->targetlist[i]); i++) {
    if (is_pattern (this_target)) {
      targ_edit[0] = '\0';
      strcat (targ_edit, targ);
      suff = suffix (targ_edit);
      if ((suff != targ_edit) && *suff)
	*suff = '\0';
      suff = suffix (this_target);
      if (suff != this_target)
	strcat (targ_edit, suff);
      strcat (default_dependency_line, targ_edit);
    }
    else 
      strcat (default_dependency_line, this_target);
    strcat (default_dependency_line, " ");
  }

  strcat (default_dependency_line, ": ");

  /*
   * make dependents part....
   */

  targ_edit[0] = '0';
  strcpy (targ_edit, targ);
  suff = suffix (targ_edit);
  if ((suff != targ_edit) && *suff)
    *suff = '\0';
  
  line_end = default_dependency_line;
  while (*line_end) line_end++;
  deplist = default_rule->deplist;
  while (deplist && *deplist) {
    if (*deplist == '%') {
      *line_end = '\0';
      strcat (default_dependency_line, targ_edit);
      while (*line_end) line_end++;
      deplist++;
    }
    else
      *line_end++ = *deplist++;
  }
  *line_end = '\0';

  /*
   * make additional dependency known to shape...
   */

  ruledef (default_dependency_line);
  ruleend ();
  reset_dep_list (rule);
  return;
}

EXPORT Bool is_special_target(string)
     char * string ;
{
  int i ;

  for (i = 0; special_target[i]; i++) {
    if (!strcmp(string, special_target[i])) return TRUE ;
  }
  return FALSE ;
}

EXPORT Bool is_pattern (string) char * string; {
  /*
   * recognize implicit rules that use the pattern notation
   */

  if (!string) return FALSE;
  if (string[0] == '%') return TRUE;
  if (strchr (string, '%')) return TRUE;
  return FALSE;
}


#define STEMLEN 128

EXPORT char *stem (pattern, string) char *pattern, *string; {
  /*
   * Match "string" against "pattern" (consists of '%' and a possibly
   * empty constant string) and returns the substring of string that
   * matched the wildcard character. If "string" didn't match, NULL
   * is returned.
   */

  register char *p1, *p2, *q1, *q2;
  unsigned int l;
  static char wild_stem[STEMLEN];

  if (!(pattern && string && *pattern && *string))
    return NULL;
  if (strchr (string, '%')) {
    fprintf (stderr, "String to match (%s) must not be a pattern.\n",
	     string);
    return NULL;
  }
  p1 = pattern;
  p2 = string;
  while (*p1 && (*p1 == *p2)) { p1++ ; p2++; }
  if ((*p1 == *p2) && (*p1 == '\0')) {
    /* pattern and string are identical */
    strcpy (wild_stem, string);
    return wild_stem;
  }
  q1 = pattern + ((l = strlen (pattern)) ? l-1 : 0);
  q2 = string + ((l = strlen (string)) ? l-1 : 0);
  while (*q1 == *q2) { q1--; q2--; }
  if ((p1 == q1) && (*q1 == '%')) {
    q2++;
    strncpy (wild_stem, p2, q2 - p2);
    wild_stem[q2 - p2] = '\0';
    return wild_stem;
  }
  else
    return NULL;
}

EXPORT char *rule_id (rule) struct rules *rule; {
  struct rules *this_ruletab_entry, *canonical_rule = rule;
  static char edit_buffer[32], *canonical_name;
  int hasht, hasht_ext = 0;

  if (!rule)
    return "";

  canonical_name = rule->name;
  if (rule->next) {
    /* find "canonical" (lexically first) name in rule group */
    struct rules *this_rule = rule->next;
    while (this_rule && (this_rule != rule)) {
      /* cyclic list ! */
      if (strcmp (canonical_name, this_rule->name) > 0) {
	canonical_name = this_rule->name;
	canonical_rule = this_rule;
      }
      this_rule = this_rule->next;
    }
  }

  hasht = hashval(canonical_name, RULETABSIZE);
  this_ruletab_entry = ruletab[hasht];
  while (this_ruletab_entry && (this_ruletab_entry != canonical_rule)) {
    this_ruletab_entry = this_ruletab_entry->nextrule;
    hasht_ext++;
  }
  if (this_ruletab_entry == NULL)
    warning (100, "make_derivation_key: inconsistent rule parameters");
  if (hasht_ext)
    sprintf (edit_buffer, "%d.%d", hasht, hasht_ext);
  else
    sprintf (edit_buffer, "%d", hasht);
  
  return edit_buffer;
}


EXPORT struct rules *get_target (targ) char *targ; {
  int hasht;
  register struct rules *current = (struct rules *) NIL;

  /*
   * Find and return the production rule for the specified target.
   */

  if (!*targ)
    return((struct rules *) NIL);
  
  if (!test_path (targ)) return NULL;

  hasht = hashval (targ, RULETABSIZE);
  
  if (ruletab[hasht] != (struct rules *) NIL) {
    current = ruletab[hasht];
    while ((current != (struct rules *) NIL ) && 
	   (strcmp (targ, current->name) != 0)) {
      /* look through hash chain */
      current = current->nextrule;
    }
  }

  if ((current) && (strcmp(targ, current->name) == 0))
    return current;

  /*
   * Either there is no rule for hashval(targ), or the rule 
   * doesn't match, i.e. hashval(targ) is accidentally the same as
   * for some explicit target. We have to look for std rule .
   */
  
  return implicit_target (targ);
}

EXPORT struct rules *implicit_target (targ) char *targ; {
  char *p = NIL, *percent, *pathprefix;
  char fname[MYMAXNAMLEN], targrule_names[2][MYMAXNAMLEN],
       *targrulename = targrule_names[0], 
       *targrulename_with_path = targrule_names[1];
  register int i, j, k;
  Bool match_found = FALSE, prerequisites_available = TRUE;

  /*
   * Find and return the applicable implicit production rule for 
   * the specified target.
   * Before a rule is returned as applicable, "implicit_target" makes
   * sure that we know rules to produce all dependents of "targ".
   */

  if (!*targ)
    return((struct rules *) NIL);

  /* First, build the names for the sought rule */
  strcpy (fname, targ);
  p = strrchr (fname, '.');
  while ((p != NULL) && (*p == '.')) p--;
  if ((p != NULL) && (*(p+1) == '.')) p++;
  pathprefix = af_afpath (targ);

  /* build rule names */
  if (*pathprefix)
    (void) sprintf (targrulename_with_path, "%s/%c%s%s", pathprefix,
		    '%', p ? "." : "", p ? p+1 : "");
  else {
    targrulename_with_path[0] = '\0';
  }
  (void) sprintf (targrulename, "%c%s%s", '%', p ? "." : "", p ? p+1 : "");

  /*
   * If we have a target with a relative path prefix, we shall first
   * try to find an implicit rule with a corresponding relative path
   * part. If no such rule exists, try to find a rule without a path part.
   */
  for (k = 1; k >= 0; k--) {
    char *trn = targrule_names[k];
    if (trn[0] == (char) NULL) continue;

    for (i = lastrule; i >= 0; i--) {
      if (implicit_suffs[i] == -1)
	continue;
    
      prerequisites_available = TRUE;
      match_found = FALSE;

      if (!is_suffix_pattern (stdruletab[implicit_suffs[i]]->name)) {
	register char *c;
	char *cleaned_pattern = stdruletab[implicit_suffs[i]]->name, 
	*rpat, *rmsg;
	for (c = "\\.*[]|+?^$()"; *c; c++) {
	  cleaned_pattern = escape_char (cleaned_pattern, *c); 
	}
#       define MATCHALL ".*"
	rpat = check_strdup (subst_char (cleaned_pattern,
				    '%', MATCHALL));
	cleaned_pattern = check_malloc (strlen (rpat) + 3);
	cleaned_pattern[0] = '^';
	cleaned_pattern[1] = '\0';
	(void) strcat (cleaned_pattern, rpat);
	(void) strcat (cleaned_pattern, "$");
	free (rpat);
	rpat = cleaned_pattern;
	rmsg = re_comp (rpat);
	free (rpat);
	if (re_exec (targ)) {
	  /*
	   * The rule name matches our target. Now let's extract
	   * the substring that matched our wildcard.
	   */
	  percent = stem (stdruletab[implicit_suffs[i]]->name, targ);
	  match_found = TRUE;
	}
      }
      else if (strcmp (trn, stdruletab[implicit_suffs[i]]->name) == 0) {
	/*
	 * We have found an implicit rule that matches our target.
	 * Now, construct the implicit dependent object(s) name(s)
	 * by substituting the '%' character with the filename-stem
	 * of targ. Then, see if a corresponding object can be located
	 * or derived.
	 */
	percent = stem (trn, targ);
	match_found = TRUE;
      }
      if (match_found) {
	for (j = 0; *get_dep (stdruletab[implicit_suffs[i]], j); j++) {
	  Bool object_derivable, object_exists;
	  static int depth;
	  char *depname = check_strdup (subst_char (get_dep 
						    (stdruletab[implicit_suffs[i]], j), '%', percent));
	  if (depth > MAXDEPTH) {
	    char wrng[MYMAXNAMLEN + 18];
	    strcpy (wrng, "(implicit ");
	    strcat (wrng, stdruletab[implicit_suffs[i]]->name);
	    strcat (wrng, ") ");
	    strcat (wrng, depname);
	    errexit (40, wrng);
	  }
	  depth++;
	  object_derivable = (Bool) get_target (depname);
	  depth--;
	  object_exists = locate_object (depname, SOURCE);
	  free (depname);
	  prerequisites_available = 
	    prerequisites_available && ( object_derivable || object_exists);
	} /* end for */

	if (prerequisites_available)
	  return stdruletab[implicit_suffs[i]];
      } /* end if (match_found) */
    } /* end for (i >= 0 ) */ 
  } /* end for (k >= 0) */
  return (struct rules *) NIL;
}

