[comp.sources.misc] v19i050: dmake - dmake version 3.7, Part29/37

Dennis Vadura <dvadura@watdragon.waterloo.edu> (05/13/91)

Submitted-by: Dennis Vadura <dvadura@watdragon.waterloo.edu>
Posting-number: Volume 19, Issue 50
Archive-name: dmake/part29
Supersedes: dmake-3.6: Volume 15, Issue 52-77

---- Cut Here and feed the following to sh ----
#!/bin/sh
# this is dmake.shar.29 (part 29 of a multipart archive)
# do not concatenate these parts, unpack them in order with /bin/sh
# file dmake/rulparse.c continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 29; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test -f _shar_wnt_.tmp; then
sed 's/^X//' << 'SHAR_EOF' >> 'dmake/rulparse.c' &&
X   be an illegal rule definition, and it returns 1 if it is a rule definition.
X   */
int *state;
{
X   TKSTR 	input;		/* input string struct for token search	  */
X   CELLPTR	targets;	/* list of targets if any		  */
X   CELLPTR	prereq;		/* list of prereq if any		  */
X   CELLPTR	prereqtail;	/* tail of prerequisite list		  */
X   CELLPTR	cp;		/* temporary cell pointer for list making */
X   char 	*result;	/* temporary storage for result	  	  */
X   char		*tok;		/* temporary pointer for tokens		  */
X   char         *set_dir;       /* value of setdir attribute              */
X   char		*brk;		/* break char list for Get_token	  */
X   char         *firstrcp;      /* first recipe line, from ; in rule line */
X   t_attr       attr;           /* sum of attribute flags for current tgts*/
X   t_attr	at;		/* temp place to keep an attribute code	  */
X   int		op;		/* rule operator			  */
X   int		special;	/* indicate special targets in rule	  */
X   int		percent;	/* indicate percent rule target		  */
X   int		mixed_glob_prq; /* indicate mixed %-rule prereq possible  */
X
X   DB_ENTER( "Parse_rule_def" );
X
X   op	      = 0;
X   attr       = 0;
X   special    = 0;
X   percent    = 0;
X   set_dir    = NIL( char );
X   targets    = NIL(CELL);
X   prereq     = NIL(CELL);
X   prereqtail = NIL(CELL);
X   mixed_glob_prq = 0;
X
X   /* Check to see if the line is of the form:
X    *    targets : prerequisites; first recipe line
X    * If so remember the first_recipe part of the line. */
X
X   firstrcp = strchr( Buffer, ';' );
X   if( firstrcp != NIL( char ) ) {
X      *firstrcp++ = 0;
X      firstrcp = _strspn( firstrcp, " \t" );
X   }
X
X   result = Expand( Buffer );
X   for( brk=strchr(result,'\\'); brk != NIL(char); brk=strchr(brk,'\\') )
X      if( brk[1] == '\n' )
X	 *brk = ' ';
X      else
X         brk++;
X
X   DB_PRINT( "par", ("Scanning: [%s]", result) );
X
X   SET_TOKEN( &input, result );
X   brk = ":-^!";
X   Def_targets = TRUE;
X   
X   /* Scan the input rule line collecting targets, the operator, and any
X    * prerequisites.  Stop when we run out of targets and prerequisites. */
X
X   while( *(tok = Get_token( &input, brk, TRUE )) != '\0' )
X      if( !op ) {
X	 /* we are scanning targets and attributes
X	  * check to see if token is an operator.  */
X
X	 op = Rule_op( tok );
X
X	 if( !op ) {
X	    /* define a new cell, or get old cell  */
X	    cp = Def_cell( tok );
X	    DB_PRINT( "par", ("tg_cell [%s]", tok) );
X	    
X	    if( at = _is_attribute( tok ) ) {
X	       /* Logically OR the attributes specified into one main
X	        * ATTRIBUTE mask. */
X
X	       if( at == A_SETDIR )
X	          if( set_dir != NIL( char ) )
X	             Warning( "Multiple .SETDIR attribute ignored" );
X	          else
X	             set_dir = _strdup( tok );
X
X	       attr |= at;
X	    }
X	    else {
X	       int tmp;
X	       
X	       tmp = _is_special( tok );
X	       if( _is_percent( tok ) ) percent++;
X
X	       if( percent )
X	          if( targets != NIL(CELL) )
X		     Fatal( "Multiple targets are not allowed in %% rules" );
X		  else
X		     cp->ce_flag |= F_PERCENT;
X
X	       if( special )
X	          Fatal( "Special target must appear alone", tok );
X	       else if( !(cp->ce_flag & F_MARK) ) {
X		  cp->ce_link  = targets;  /* targets are stacked in this list*/
X		  cp->ce_flag |= F_MARK | F_EXPLICIT;
X		  targets      = cp;
X
X		  special = tmp;
X	       }
X	       else if( !(cp->ce_attr & A_LIBRARY) )
X		  Warning("Duplicate entry [%s] in target list",cp->CE_NAME);
X	    }
X	 }
X	 else {
X	    /* found an operator so empty out break list
X	     * and clear mark bits on target list, setting them all to F_USED */
X
X	    brk  = "";
X	    for( cp=targets; cp != NIL(CELL); cp=cp->ce_link ) {
X	       cp->ce_flag ^= F_MARK;
X	       cp->ce_flag |= F_USED;
X	    }
X
X	    Def_targets = FALSE;
X	 }
X      }
X      else {
X         /* Scanning prerequisites so build the prerequisite list.  We use
X          * F_MARK flag to make certain we have only a single copy of the
X          * prerequisite in the list */
X
X	 cp = Def_cell( tok );
X
X	 if( _is_percent( tok ) ) {
X	    if( !percent && !attr )
X	       Fatal( "Syntax error in %% rule, missing %% target");
X	    mixed_glob_prq = 1;
X	 }
X
X	 if( cp->ce_flag & F_USED ) {
X	    if( cp->ce_attr & A_COMPOSITE )
X	       continue;
X	    else
X	       Fatal( "Detected circular dependency in graph at [%s]",
X		      cp->CE_NAME );
X	 }
X         else if( !(cp->ce_flag & F_MARK) ) {
X	    DB_PRINT( "par", ("pq_cell [%s]", tok) );
X	    cp->ce_flag |= F_MARK;
X
X	    if( prereqtail == NIL(CELL) )	/* keep prereq's in order */
X	       prereq = cp;
X	    else
X	       prereqtail->ce_link = cp;
X
X	    prereqtail = cp;
X	    cp->ce_link = NIL(CELL);
X	 }
X	 else if( !(cp->ce_attr & A_LIBRARY) )
X	    Warning("Duplicate entry [%s] in prerequisite list",cp->CE_NAME);
X      }
X      
X   /* Check to see if we have a percent rule that has only global
X    * prerequisites.  If so then set the flag so that later on, we don't issue
X    * an error if such targets supply an empty set of rules. */
X
X   if( percent && !mixed_glob_prq && (prereq != NIL(CELL)) )
X      _sv_globprq_only = 1;
X
X   /* It's ok to have targets with attributes, and no prerequisites, but it's
X    * not ok to have no targets and no attributes, or no operator */
X
X   if( !op ) {
X      CLEAR_TOKEN( &input );
X      DB_PRINT( "par", ("Not a rule [%s]", Buffer) );
X      DB_RETURN( 0 );
X   }
X
X   if( !attr && targets == NIL(CELL) ) {
X      Fatal( "Missing targets or attributes in rule" );
X      if( set_dir != NIL( char )) FREE( set_dir );
X      DB_RETURN( 0 );
X   }
X
X   /* We have established we have a legal rules line, so we must process it.
X    * In doing so we must handle any special targets.  Special targets must
X    * appear alone possibly accompanied by attributes.
X    * NOTE:  special != 0  ==> targets != NIL(CELL) */
X    
X   if( prereqtail != NIL(CELL) ) prereqtail->ce_link = NIL(CELL);
X
X   /* Clear out MARK bits used in duplicate checking.  I originally wanted
X    * to do this as the lists get processed but that got too error prone
X    * so I bit the bullit and added these two loops. */
X
X   for( cp=prereq;  cp != NIL(CELL); cp=cp->ce_link ) cp->ce_flag &= ~F_MARK;
X   for( cp=targets; cp != NIL(CELL); cp=cp->ce_link ) cp->ce_flag &= ~F_USED;
X
X   /* Check to see if the previous rule line was bound if, not the call
X    * Bind_rules_to_targets to go and bind the line */
X
X   if( _sv_rules != NIL(STRING) ) Bind_rules_to_targets( F_DEFAULT );
X
X   /* Add the first recipe line to the list */
X   if( firstrcp != NIL( char ) )
X      Add_recipe_to_list( firstrcp, TRUE, FALSE );
X
X   /* Save these prior to calling _do_targets, since _build_graph needs the
X    * _sv_setdir value for matching edges. */
X   _sv_op     = op;
X   _sv_setdir = set_dir;
X
X   if( special )
X      _do_special( special, op, attr, set_dir, targets, prereq, state );
X   else
X      *state = _do_targets( op, attr, set_dir, targets, prereq );
X
X   DB_RETURN( 1 );
}
X
X
PUBLIC int
Rule_op( op )/*
================
X   Check the passed in op string and map it to one of the rule operators */
char *op;
{
X   int ret = 0;
X
X   DB_ENTER( "rule_op" );
X   
X   if( *op == TGT_DEP_SEP ) {
X      ret = R_OP_CL;
X      op++;
X
X      /* All rule operations begin with a :, but may include any one of the
X       * four modifiers.  In order for the rule to be properly mapped we must
X       * check for each of the modifiers in turn, building up our return bit
X       * string. */
X
X      while( *op && ret )
X         switch( *op ) {
X	    case ':': ret |= R_OP_DCL; op++; break;
X	    case '!': ret |= R_OP_BG;  op++; break;
X	    case '^': ret |= R_OP_UP;  op++; break;
X	    case '-': ret |= R_OP_MI;  op++; break;
X
X	    default : ret  = 0;  /* an invalid modifier, chuck whole string */
X         }
X
X      if( *op != '\0' ) ret = 0;
X   }
X
X   DB_RETURN( ret );
}
X
X
PUBLIC void
Add_recipe_to_list( rule, white_too, no_check )/*
=================================================
X        Take the provided string and add it to the list of recipe lines
X	we are saving to be added to the list of targets we have built
X	previously.  If white_too == TRUE add the rule EVEN IF it contains only
X        whitespace. */
char *rule;
int  white_too;
int  no_check;
{
X   DB_ENTER( "Add_recipe_to_list" );
X
X   if( rule != NIL( char ) && (*rule != '\0' || white_too) ) {
X      DB_PRINT( "par", ("Adding recipe [%s]", rule) );
X      _sv_crule = Def_recipe( rule, _sv_crule, white_too, no_check );
X
X      if( _sv_rules == NIL(STRING) )
X         _sv_rules = _sv_crule;
X   }
X
X   DB_VOID_RETURN;
}
X
X
PUBLIC void
Bind_rules_to_targets( flag )/*
===============================
X        Take the rules we have defined and bind them with proper attributes
X        to the targets that were previously defined in the parse.  The
X        attributes that get passed here are merged with those that are were
X        previously defined.  (namely F_SINGLE) */
int flag;
{
X   CELLPTR tg;             /* pointer to current target in list */
X   LINKPTR lp;		   /* pointer to link cell		*/
X   int     magic;          /* TRUE if target is .xxx.yyy form   */
X   int     tflag;          /* TRUE if we assigned targets here  */
X
X   DB_ENTER( "Bind_rules_to_targets" );
X
X   /* This line is needed since Parse may call us twice when the last
X    * GROUP rule appears at the end of file.  In this case the rules
X    * have already been bound and we want to ignore them. */
X
X   if( _sv_targets == NIL(CELL) ) { DB_VOID_RETURN; }
X
X   tflag  = FALSE;
X   flag  |= (_sv_flag & F_SINGLE);
X
X   for( tg = _sv_targets; tg != NIL(CELL); tg = tg->ce_link ) {
X      DB_PRINT( "par", ("Binding to %s, %04x", tg->CE_NAME, tg->ce_flag) );
X      magic = tg->ce_flag & F_PERCENT;
X
X      /* Check to see if we had a rule of the form '%.o : a.h b.h ; xxx'
X       * In which case we must build a NULL prq node to hold the recipe */
X      if( _sv_globprq_only && (_sv_rules != NIL(STRING)) )
X	 _build_graph( _sv_op, tg, NIL(CELL) );
X
X      /* NOTE:  For targets that are magic we ignore any previously defined
X       *        rules.  ie. We throw away the old definition and use the new. */
X      if( !(tg->ce_flag & F_MULTI) && !magic && (tg->CE_RECIPE != NIL(STRING))
X	  && !_sp_target && (_sv_rules != NIL(STRING)) )
X         Fatal( "Multiply defined recipe for target %s", tg->CE_NAME );
X
X      if( (magic || _sp_target) && (_sv_rules == NIL(STRING)) &&
X	  !(tg->ce_flag & F_SPECIAL) && !_sv_globprq_only )
X         Warning( "Empty recipe for special target %s", tg->CE_NAME );
X
X      if( magic ) {
X	 CELLPTR ep;
X
X	 for( ep=_sv_edgel; ep != NIL(CELL); ep=ep->ce_link ) {
X	    _set_attributes( _sv_attro, _sv_setdir, ep );
X	    ep->ce_flag |= (F_TARGET|flag);
X
X	    if( _sv_rules != NIL(STRING) ) {
X	       ep->ce_recipe  = _sv_rules;
X	       ep->ce_indprq  = _sv_glb_prq;
X	    }
X	 }
X      }
X      else {
X	 tg->ce_attr |= _sv_attr;
X	 tg->ce_flag |= flag;
X
X	 if( _sv_rules != NIL(STRING) ) {
X	    tg->ce_recipe  = _sv_rules;
X	    tg->ce_flag   |= F_RULES | F_TARGET;
X
X	    /* Bind the current set of prerequisites as belonging to the
X	     * original recipe given for the target */
X	    for( lp=tg->ce_prq; lp != NIL(LINK); lp = lp->cl_next )
X		  if( !(lp->cl_flag & F_USED) ) lp->cl_flag |= F_TARGET;
X         }
X	 else for( lp=tg->ce_prq; lp != NIL(LINK); lp = lp->cl_next )
X	    lp->cl_flag |= F_USED;
X      }
X
X      if( !Target && !magic && !(tg->ce_flag & F_SPECIAL) ) {
X	 Add_prerequisite( Root, tg, FALSE, TRUE );
X
X	 tg->ce_flag |= F_TARGET;
X	 tg->ce_attr |= A_FRINGE;
X	 tflag        = TRUE;
X      }
X   }
X
X   if( tflag ) Target = TRUE;
X   if( _sv_setdir ) FREE(_sv_setdir);
X   _sv_rules   = NIL(STRING);
X   _sv_crule   = NIL(STRING);
X   _sv_targets = NIL(CELL);
X   _sv_glb_prq = NIL(LINK);
X   _sv_edgel   = NIL(CELL);
X   _sp_target  = FALSE;
X   _sv_globprq_only = 0;
X
X   DB_VOID_RETURN;
}
X
X
X
PUBLIC int
Set_group_attributes( list )/*
==============================
X	Scan list looking for the standard @ and - (as in recipe line defs)
X	and set the flags accordingly so that they apply when we bind the
X	rules to the appropriate targets. */
char *list;
{
X   int res = (*_strspn(list,"@-%+ \t") == '[');
X   if( res ) _sv_attr |= Rcp_attribute(list);
X   return(res);
}
X
X
static void
_do_special( special, op, attr, set_dir, target, prereq, state )/*
==================================================================
X   Process a special target.  So far the only special targets we have
X   are those recognized by the _is_special function.
X
X   target is always only a single special target.
X   
X   NOTE:  For the cases of .IMPORT, and .INCLUDE, the cells created by the
X   	  parser are never freed.  This is due to the fact that it is too much
X	  trouble to get them out of the hash table once they are defined, and
X	  if they are per chance used again it will be ok, anyway, since the
X	  cell is not really used by the code below.  */
X
int	special;
int	op;
t_attr	attr;
char	*set_dir;
CELLPTR target;
CELLPTR prereq;
int     *state;
{
X   HASHPTR	hp;		/* pointer to macro def cell		*/
X   CELLPTR	cp;		/* temporary pointer into cells list	*/
X   CELLPTR 	dp;		/* pointer to directory dir cell	*/
X   LINKPTR 	lp;		/* pointer at prerequisite list 	*/
X   char    	*dir;		/* current dir to prepend		*/
X   char    	*path;		/* resulting path to try to read	*/
X   char 	*name;		/* File name for processing a .INCLUDE	*/
X   char		*tmp;		/* temporary string pointer		*/
X   FILE 	*fil;		/* File descriptor returned by Openfile	*/
X
X   DB_ENTER( "_do_special" );
X
X   target->ce_flag = F_SPECIAL;	/* mark the target as special */
X
X   switch( special ) {
X      case ST_EXPORT:
X	 for( ; prereq != NIL(CELL); prereq = prereq->ce_link ) {
X	    DB_PRINT( "par", ("Exporting [%s]", prereq->CE_NAME) );
X	    hp = GET_MACRO( prereq->CE_NAME );
X
X	    if( hp != NIL(HASH) ) {
X	       char *tmpstr = hp->ht_value;
X
X	       if( tmpstr == NIL(char) ) tmpstr = "";
X
X	       if( Write_env_string( prereq->CE_NAME, tmpstr ) != 0 )
X		  Warning( "Could not export %s", prereq->CE_NAME );
X	    }
X	 }
X	 break;
X
X      case ST_IMPORT:
X	 for( ; prereq != NIL(CELL); prereq = prereq->ce_link ) {
X	    char *tmpstr;
X
X	    DB_PRINT( "par", ("Importing [%s]", prereq->CE_NAME) );
X
X	    if( strcmp(prereq->CE_NAME, ".EVERYTHING") == 0 ) {
X	       t_attr sattr = Glob_attr;
X	       Glob_attr |= A_SILENT;
X
X	       ReadEnvironment();
X
X	       Glob_attr = sattr;
X	    }
X	    else {
X	       tmpstr = Read_env_string( prereq->CE_NAME );
X
X	       if( tmpstr != NIL(char) )
X		  Def_macro( prereq->CE_NAME,tmpstr,M_EXPANDED | M_LITERAL);
X	       else
X		  if( !((Glob_attr | attr) & A_IGNORE) )
X		     Fatal("Imported macro `%s' not found",prereq->CE_NAME);
X	    }
X	 }
X
X	 attr &= ~A_IGNORE;
X	 break;
X
X      case ST_INCLUDE:
X      {
X	 int ignore     = (((Glob_attr | attr) & A_IGNORE) != 0);
X	 int pushed     = FALSE;
X	 LINKPTR prqlnk = NIL(LINK);
X	 LINKPTR prqlst = NIL(LINK);
X
X	 if( prereq == NIL(CELL) )  Fatal( "No .INCLUDE file(s) specified" );
X
X	 dp = Def_cell( ".INCLUDEDIRS" );
X
X	 if( (attr & A_SETDIR) && *(dir = strchr(set_dir, '=')+1) )
X	    pushed = Push_dir( dir, ".INCLUDE", ignore );
X
X	 for( cp=prereq; cp != NIL(CELL); cp = cp->ce_link ) {
X	    LINKPTR ltmp;
X	    TALLOC(ltmp, 1, LINK);
X	    ltmp->cl_prq = cp;
X
X	    if( prqlnk == NIL(LINK) )
X	       prqlst = ltmp;
X	    else
X	       prqlnk->cl_next = ltmp;
X
X	    prqlnk = ltmp;
X	 }
X
X	 for( ; prqlst != NIL(LINK); FREE(prqlst), prqlst=prqlnk ) {
X	    prqlnk = prqlst->cl_next;
X	    cp     = prqlst->cl_prq;
X	    name   = cp->CE_NAME;
X	    
X	    if( *name == '<' ) {
X	       /* We have a file name enclosed in <....>
X	        * so get rid of the <> arround the file name */
X
X	       name++;
X	       if( (tmp = strrchr( name, '>' )) != NIL( char ) )
X		  *tmp = 0;
X
X	       if( If_root_path( name ) )
X	          fil = Openfile( name, FALSE, FALSE );
X	       else
X		  fil = NIL(FILE);
X	    }
X	    else
X	       fil = Openfile( name, FALSE, FALSE );
X	       
X	    if( fil == NIL(FILE) ) {	/*if true ==> not found in current dir*/
X	       /* Now we must scan the list of prerequisites for .INCLUDEDIRS
X	        * looking for the file in each of the specified directories.
X		* if we don't find it then we issue an error.  The error
X		* message is suppressed if the .IGNORE attribute of attr is
X		* set.  If a file is found we call Parse on the file to
X		* perform the parse and then continue on from where we left
X		* off.  */
X
X	       for(lp=dp->CE_PRQ; lp && fil == NIL(FILE); lp=lp->cl_next) {
X		  dir  = lp->cl_prq->CE_NAME;
X		  if( strchr(dir, '$') ) dir = Expand(dir);
X		  path = Build_path( dir, name );
X
X		  DB_PRINT( "par", ("Trying to include [%s]", path) );
X
X		  fil = Openfile( path, FALSE, FALSE );
X		  if( dir != lp->cl_prq->CE_NAME ) FREE(dir);
X	       }
X	    }
X
X	    if( fil != NIL(FILE) )
X	       Parse( fil );
X	    else if( !((Glob_attr | attr) & A_IGNORE) )
X	       Fatal( "Include file %s, not found", name );
X	 }
X
X	 if( pushed ) Pop_dir(FALSE);
X	 attr &= ~(A_IGNORE|A_SETDIR);
X      }
X      break;
X	 
X      case ST_SOURCE:
X      /* case ST_SUFFIXES: */
X      	 if( prereq != NIL(CELL) )
X	    _do_targets( op & (R_OP_CL | R_OP_MI | R_OP_UP), attr, set_dir,
X			 target, prereq );
X	 else {
X	    /* The old semantics of .SOURCE were that an empty list of
X	     * prerequisites clears the .SOURCE list.  So we must implement
X	     * that here as a clearout prerequisite operation.  Since this is
X	     * a standard operation with the :- opcode we can simply call the
X	     * proper routine with the target cell and it should do the trick
X	     */
X
X	    if( op == R_OP_CL || (op & R_OP_MI) )
X	       Clear_prerequisites( target );
X	 }
X
X	 op &= ~(R_OP_MI | R_OP_UP);
X	 break;
X
X      case ST_KEEP:
X	 if( Keep_state != NIL(char) ) break;
X	 Def_macro( ".KEEP_STATE", "_state.mk", M_EXPANDED );
X	 break;
X
X      case ST_REST:
X         /* The rest of the special targets can all take rules, as such they
X	  * must be able to affect the state of the parser. */
X
X	 {
X	    int s_targ = Target;
X
X	    Target     = TRUE;
X	    _sp_target = TRUE;
X	    *state     = _do_targets( op, attr, set_dir, target, prereq );
X	    Target     = s_targ;
X
X	    target->ce_flag |= F_TARGET;
X
X	    attr    = A_DEFAULT;
X	    op      = R_OP_CL;
X	 }
X	 break;
X
X      default:break;
X   }
X      
X   if( op   != R_OP_CL   ) Warning( "Modifier(s) for operator ignored" );
X   if( attr != A_DEFAULT ) Warning( "Extra attributes ignored" );
X
X   DB_VOID_RETURN;
}
X
X
X
static int
_do_targets( op, attr, set_dir, targets, prereq )/*
================================================= */
int	op;
t_attr	attr;
char	*set_dir;
CELLPTR targets;
CELLPTR prereq;
{
X   CELLPTR	tg1;		/* temporary target pointer		*/
X   CELLPTR	tp1;		/* temporary prerequisite pointer	*/
X   char		*p;		/* temporary char pointer		*/
X   CELLPTR      prev_cell;	/* pointer for .UPDATEALL processing	*/
X   CELLPTR      first_cell;	/* pointer for .UPDATEALL processing	*/
X   int		update;		/* A_UPDATEALL attribute flag		*/
X   int		smagic = 0;	/* collective amount of magic :-)	*/
X
X   DB_ENTER( "_do_targets" );
X
X   if( update = ((attr & A_UPDATEALL) != 0) )
X      if( targets == NIL(CELL) )
X	 Fatal( ".UPDATEALL attribute requires non-empty list of targets" );
X
X   first_cell = prev_cell = NIL(CELL);
X   for( tg1 = targets; tg1 != NIL(CELL); tg1 = tg1->ce_link ) {
X      /* Check each target.  Check for inconsistencies between :: and : rule
X       * sets.  :: may follow either : or :: but not the reverse.
X       *
X       * Any targets that contain :: rules are represented by a prerequisite
X       * list hanging off the main target cell where each of the prerequisites
X       * is a copy of the target cell but is not entered into the hash table.
X       */
X      int magic  = (tg1->ce_flag & F_PERCENT) && !(tg1->ce_flag & F_MAGIC);
X      smagic |= magic;
X
X      if( !(op & R_OP_DCL ) && (tg1->ce_flag & F_MULTI) && !magic )
X	 Fatal( "Inconsistency in inference rules for %s", tg1->CE_NAME );
X
X      if( magic )
X         do {
X	    _build_graph( op, tg1, prereq );
X	    if( prereq != NIL(CELL) ) prereq = prereq->ce_link;
X	 } while( prereq != NIL(CELL) );
X      else if( !(tg1->ce_flag & F_SPECIAL) && 
X		(prereq == NIL(CELL)) &&
X		(p = _is_magic( tg1->CE_NAME )) != NIL(char))
X         smagic |= _do_magic( op, p, tg1, prereq, attr, set_dir );
X      else if( op & R_OP_DCL ) {
X	 CELLPTR tmp_cell = _make_multi(tg1);
X	 targets = _replace_cell( targets, tg1, tmp_cell );
X	 tg1 = tmp_cell;
X      }
X
X      if( !magic ) _set_attributes( attr, set_dir, tg1 );
X
X      if( update ) {
X	 if( smagic ) Fatal( ".UPDATEALL attribute not legal in meta rule" );
X
X	 /* Check this as it would break another circular .UPATEALL list if
X	  * we blindly assign it and it is part of another list already. */
X	 if( tg1->ce_all != NIL(CELL) )
X	    Fatal( "Target [%s] appears on multiple .UPDATEALL lists" );
X
X	 tg1->ce_all = prev_cell;
X	 if( prev_cell == NIL(CELL) ) first_cell = tg1;
X	 prev_cell = tg1;
X      }
X
X      /* Build the proper prerequisite list of the target.  If the `-',
X       * modifier was used clear the prerequisite list before adding any
X       * new prerequisites.  Else add them to the head/tail as appropriate.
X       *
X       * If the target has F_PERCENT set then no prerequisites are used. */
X
X      if( !(tg1->ce_flag & F_PERCENT) ) {
X	 if( op & R_OP_MI ) Clear_prerequisites( tg1 );
X
X	 if( (op & R_OP_UP) && (tg1->ce_prq != NIL(LINK)) )
X	    _stick_at_head( tg1, prereq );
X	 else for( tp1=prereq; tp1 != NIL(CELL); tp1 = tp1->ce_link )
X	    Add_prerequisite( tg1, tp1, FALSE, FALSE );
X      }
X      else if( op & (R_OP_MI | R_OP_UP) )
X	 Warning( "Modifier(s) `^!' for %-meta target ignored" );
X   }
X
X   if( first_cell != NIL(CELL) ) first_cell->ce_all = prev_cell;
X
X
X   /* Check to see if we have NO targets but some attributes.  IF so then
X    * apply all of the attributes to the complete list of prerequisites.
X    * Cannot happen for F_PERCENT targets. (ie. in that case targets is always
X    * not NIL) */
X
X   if( (targets == NIL(CELL)) && attr )
X      if( prereq != NIL(CELL) )
X	 for( tp1=prereq; tp1 != NIL(CELL); tp1 = tp1->ce_link )
X	    _set_attributes( attr, set_dir, tp1 );
X      else
X	 _set_global_attr( attr );
X
X   /* Fix up the ce_link pointers so that when we go to attach a recipe in
X    * Bind_targets to rules we get the right thing if it's an .UPDATEALL ::
X    * recipe */
X   if( update ) {
X      for( tp1=NIL(CELL),tg1=prev_cell; tg1!=first_cell; tg1=tg1->ce_all ) {
X	 tg1->ce_link = tp1;
X	 tp1 = tg1;
X      }
X      tg1->ce_link = tp1;
X      targets = first_cell;
X   }
X
X   /* Now that we have built the lists of targets, the parser must parse the
X    * rules if there are any.  However we must start the rule list with the
X    * rule specified as via the ; kludge, if there is one */
X   _sv_targets = targets;
X   _sv_attr    = _sv_attro = attr;
X   _sv_flag    = ((op & R_OP_BG) ? F_SINGLE : F_DEFAULT);
X      
X   DB_RETURN( RULE_SCAN );
}
X
X
static int
_do_magic( op, dot, target, prereq, attr, set_dir )/*
=====================================================
X   This function takes a magic target of the form .<chars>.<chars> or
X   .<chars> and builds the appropriate % rules for that target.
X   
X   The function builds the % rule, `%.o : %.c'  from .c.o, and
X   `%.a :' from .a */
X
int	op;
char    *dot;
CELLPTR target;
CELLPTR prereq;
t_attr  attr;
char    *set_dir;
{
X   CELLPTR tg;
X   CELLPTR prq;
X   char    *tmp, *tmp2;
X
X   DB_ENTER( "_do_magic" );
X
X   if( prereq != NIL(CELL) )
X      Warning( "Ignoring prerequisites of old style meta-target" );
X
X   if( dot == target->CE_NAME )	{		/* its of the form .a	*/
X      tg  = Def_cell( "%" );			/* ==> no prerequisite  */
X      tmp = _build_meta( target->CE_NAME );
X      prq = Def_cell( tmp );
X      FREE( tmp );
X
X      _build_graph( op, tg, prq );
X   }
X   else {
X      tmp = _build_meta( dot );
X      tg  = Def_cell( tmp );
X      FREE( tmp );
X
X      tmp = _build_meta( tmp2 = _substr( target->CE_NAME, dot ) );
X      prq = Def_cell( tmp );
X      FREE( tmp  );
X      FREE( tmp2 );
X
X      _build_graph( op, tg, prq );
X   }
X
X   tg->ce_flag      |= F_PERCENT;
X   target->ce_flag  |= (F_MAGIC|F_PERCENT);
X
X   _set_attributes( attr, set_dir, tg );
X
X   DB_RETURN(1);
}
X
X
static CELLPTR
_replace_cell( lst, cell, rep )
CELLPTR lst;
CELLPTR cell;
CELLPTR rep;
{
X   register CELLPTR tp;
X   
X   if( lst == cell ) {
X      rep->ce_link = lst->ce_link;
X      lst = rep;
X   }
X   else {
X      for( tp=lst; tp->ce_link != cell; tp=tp->ce_link );
X      rep->ce_link = tp->ce_link->ce_link;
X      tp->ce_link = rep;
X   }
X
X   return(lst);
}
X
X
static char *
_build_meta( name )/*
=====================
X   Check to see if the name is of the form .c~ if so and if Augmake
X   translation is enabled then return s.%.c, else return %.suff, where if the
X   suffix ends in '~' then leave it be.*/
char *name;
{
X   char *tmp;
X   int  test = Augmake ? name[strlen(name)-1] == '~' : 0;
X
X   tmp = _strjoin( test ? "s.%" : "%", name, -1, FALSE);
X   if( test ) tmp[ strlen(tmp)-1 ] = '\0';
X
X   return(tmp);
}
X
X
X
static void
_build_graph( op, target, prereq )/*
====================================
X   This function is called to build the graph for the % rule given by
X   target : prereq cell combination.  This function assumes that target
X   is a % target and that prereq is a single % prerequisite.  R_OP_CL
X   rules replace existing rules if any, only R_OP_CL works for meta-rules.
X   %.o :: %.c is meaningless. 
X   
X   It also assumes that target cell has F_PERCENT set already. */
int op;
CELLPTR target;
CELLPTR prereq;
{
X   LINKPTR edl;
X   CELLPTR edge;
X   int match;
X
X   DB_ENTER( "_build_graph" );
X   DB_PRINT( "%", ("Building graph for [%s : %s]", target->CE_NAME,
X   	     (prereq == NIL(CELL)) ? "" : prereq->CE_NAME) );
X
X   if( prereq != NIL(CELL) ) {
X      char *name = prereq->CE_NAME;
X      int   len  = strlen(name);
X
X      if( *name == '\'' && name[len-1]=='\'' ){
X	 _add_global_prereq( prereq );
X	 name[len-1] = '\0';
X	 strcpy(name, name+1);
X	 DB_VOID_RETURN;
X      }
X   }
X
X   /* Search the list of prerequisites for the current target and see if
X    * any of them match the current %-meta : prereq pair.  NOTE that %-metas
X    * are built as if they were F_MULTI targets. */
X
X   match = FALSE;
X   for( edl=target->ce_prq; edl != NIL(LINK); edl=edl->cl_next ) {
X      edge = edl->cl_prq;
X
X      DB_PRINT( "%", ("Trying to match [%s]", edge?edge->CE_NAME:"(nil)") );
X
X      if(    (!edge->ce_prq && !prereq)
X	  || (   edge->ce_prq->cl_prq == prereq
X	      && (   edge->ce_dir == _sv_setdir
X		  || (   edge->ce_dir
X		      && _sv_setdir
X		      && !strcmp(edge->ce_dir,strchr(_sv_setdir,'=')+1)
X		     )
X		 )
X	     )
X	) {
X	 match = TRUE;
X	 break;
X      }
X   }
X
X   if( match ) {
X      /* match is TRUE hence, we found an edge joining the target and the
X       * prerequisite so reset the new edge's how values to reflect the new
X       * recipe etc. */
X      DB_PRINT( "%", ("It's an old edge") );
X
X      edge->ce_dir    = NIL(char);
X      edge->ce_flag  &= (F_PERCENT|F_MAGIC|F_DFA);
X      edge->ce_attr  &= A_NOINFER;
X   }
X   else {
X      DB_PRINT( "%", ("Adding a new edge") );
X
X      if( !(target->ce_flag & F_DFA) ) {
X	 Add_nfa( target->CE_NAME );
X	 target->ce_flag |= F_DFA;
X      }
X      edge = _make_multi(target);
X      if( prereq ) Add_prerequisite(edge, prereq, FALSE, TRUE);
X   }
X
X   if( op & R_OP_DCL )
X   Warning( "'::' operator for meta-target '%s' ignored, ':' operator assumed.",
X	       target->CE_NAME );
X
X   edge->ce_link = _sv_edgel;
X   _sv_edgel = edge;
X   _sv_globprq_only = 0;
X
X   DB_VOID_RETURN;
}
X
X
static CELLPTR
_make_multi( tg )
CELLPTR tg;
{
X   CELLPTR cp;
X
X   /* This first handle the case when a : foo ; exists prior to seeing
X    * a :: fee; */
X   if( !(tg->ce_flag & F_MULTI) && (tg->ce_prq || tg->ce_recipe) ) {
X      TALLOC(cp, 1, CELL);
X      *cp = *tg;
X
X      tg->ce_prq    = NIL(LINK);
X      tg->ce_flag  |= F_RULES|F_MULTI|F_TARGET;
X      tg->ce_attr  |= A_SEQ;
X      tg->ce_recipe = NIL(STRING);
X      tg->ce_dir    = NIL(char);
X      cp->ce_count  = ++tg->ce_index;
X
X      Add_prerequisite(tg, cp, FALSE, TRUE);
X   }
X
X   TALLOC(cp, 1, CELL);
X   *cp = *tg;
X
X   if( !(tg->ce_flag & F_MULTI) ) {
X      tg->ce_prq    = NIL(LINK);
X      tg->ce_flag  |= F_RULES|F_MULTI|F_TARGET;
X      tg->ce_attr  |= A_SEQ;
X      tg->ce_recipe = NIL(STRING);
X      tg->ce_dir    = NIL(char);
X   }
X   else {
X      cp->ce_flag  &= ~(F_RULES|F_MULTI);
X      cp->ce_attr  &= ~A_SEQ;
X      cp->ce_prq    = NIL(LINK);
X      cp->ce_index  = 0;
X   }
X   cp->ce_count = ++tg->ce_index;
X   cp->ce_flag |= F_TARGET;
X
X   Add_prerequisite(tg, cp, FALSE, TRUE);
X   return(cp);
}
X
X
static void
_add_global_prereq( pq )/*
==========================
X	Prerequisite is a non-% prerequisite for a %-rule target, add it to
X	the target's list of global prerequsites to add on match */
CELLPTR pq;
{
X   register LINKPTR ln;
X
X   TALLOC( ln, 1, LINK );
X   ln->cl_next = _sv_glb_prq;
X   ln->cl_prq  = pq;
X   _sv_glb_prq = ln;
}
X
X
X
static void
_set_attributes( attr, set_dir, cp )/*
======================================
X	Set the appropriate attributes for a cell */
t_attr	attr;
char	*set_dir;
CELLPTR cp;
{
X   char   *dir;
X
X   DB_ENTER( "_set_attributes" );
X
X   /* If .SETDIR attribute is set then we have at least .SETDIR= in the
X    * set_dir string.  So go and fishout what is at the end of the =.
X    * If not set and not NULL then propagate it to the target cell. */
X
X   if( attr & A_SETDIR ) {
X      dir = strchr( set_dir, '=' ) + 1;
X
X      if( cp->ce_dir )
X	 Warning( "Multiple .SETDIR for %s ignored", cp->CE_NAME );
X      else
X	 if( *dir ) cp->ce_dir = _strdup(dir);
X   }
X   cp->ce_attr |= attr;		/* set rest of attributes for target */
X
X   DB_VOID_RETURN;
}
X
X
X
static void
_set_global_attr( attr )/*
==========================
X	Handle the setting of the global attribute functions based on
X	The attribute flags set in attr. */
t_attr attr;
{
X   int flag;
X
X   /* Some compilers can't handle a switch on a long, and t_attr is now a long
X    * integer on some systems.  foey! */
X   for( flag = MAX_ATTR; flag; flag >>= 1 )
X      if( flag & attr )
X	 if( flag == A_PRECIOUS)      Def_macro(".PRECIOUS",  "y", M_EXPANDED);
X	 else if( flag == A_SILENT)   Def_macro(".SILENT",    "y", M_EXPANDED);
X	 else if( flag == A_IGNORE )  Def_macro(".IGNORE",    "y", M_EXPANDED);
X	 else if( flag == A_EPILOG )  Def_macro(".EPILOG",    "y", M_EXPANDED);
X	 else if( flag == A_PROLOG )  Def_macro(".PROLOG",    "y", M_EXPANDED);
X	 else if( flag == A_NOINFER ) Def_macro(".NOINFER",   "y", M_EXPANDED);
X	 else if( flag == A_SEQ )     Def_macro(".SEQUENTIAL","y", M_EXPANDED);
X	 else if( flag == A_SHELL )   Def_macro(".USESHELL",  "y", M_EXPANDED);
X	 else if( flag == A_MKSARGS ) Def_macro(".MKSARGS",   "y", M_EXPANDED);
X	 else if( flag == A_SWAP )    Def_macro(".SWAP",      "y", M_EXPANDED);
X
X   attr &= ~A_GLOB;
X   if( attr ) Warning( "Non global attribute(s) ignored" );
}
X
X
X
static void
_stick_at_head( cp, pq )/*
==========================
X	Add the prerequisite list to the head of the existing prerequisite
X	list */
X
CELLPTR cp;  		/* cell for target node	*/
CELLPTR pq;		/* list of prerequisites to add */
{
X   DB_ENTER( "_stick_at_head" );
X
X   if( pq->ce_link != NIL(CELL) ) _stick_at_head( cp, pq->ce_link );
X   Add_prerequisite( cp, pq, TRUE, FALSE );
X
X   DB_VOID_RETURN;
}
X
X
X
static t_attr
_is_attribute( name )/*
=======================
X   Check the passed name against the list of valid attributes and return the
X   attribute index if it is, else return 0, indicating the name is not a valid
X   attribute.  The present attributes are defined in dmake.h as A_xxx #defines,
X   with the corresponding makefile specification:  (note they must be named
X   exactly as defined below)
X   
X   Valid attributes are:  .IGNORE, .SETDIR=, .SILENT, .PRECIOUS, .LIBRARY,
X                          .EPILOG, .PROLOG,  .LIBRARYM, .SYMBOL, .UPDATEALL,
X			  .USESHELL, .NOINFER, .PHONY, .SWAP, .SEQUENTIAL
X			  .NOSTATE,  .MKSARGS
X
X   NOTE:  The strcmp's are OK since at most three are ever executed for any
X          one attribute check, and that happens only when we can be fairly
X          certain we have an attribute.  */
char *name;
{
X   t_attr attr = 0;
X   
X   DB_ENTER( "_is_attribute" );
X   
X   if( *name++ == '.' )
X      switch( *name )
X      {
X         case 'E': attr = (strcmp(name, "EPILOG"))   ? 0 : A_EPILOG;  break;
X         case 'I': attr = (strcmp(name, "IGNORE"))   ? 0 : A_IGNORE;  break;
X         case 'L': attr = (strcmp(name, "LIBRARY"))  ? 0 : A_LIBRARY; break;
X         case 'M': attr = (strcmp(name, "MKSARGS"))  ? 0 : A_MKSARGS; break;
X
X         case 'N':
X	    if( !strcmp(name, "NOINFER") )      attr = A_NOINFER;
X	    else if( !strcmp(name, "NOSTATE"))  attr = A_NOSTATE;
X	    else attr = 0;
X	    break;
X
X         case 'U':
X	    if( !strcmp(name, "UPDATEALL") )    attr = A_UPDATEALL;
X	    else if( !strcmp(name, "USESHELL")) attr = A_SHELL;
X	    else attr = 0;
X	    break;
X
X         case 'P':
X            if( !strcmp(name, "PRECIOUS") )     attr = A_PRECIOUS;
X            else if( !strcmp(name, "PROLOG") )  attr = A_PROLOG;
X            else if( !strcmp(name, "PHONY") )   attr = A_PHONY;
X            else attr = 0;
X            break;
X
X         case 'S':
X            if( !strncmp(name, "SETDIR=", 7) )    attr = A_SETDIR;
X            else if( !strcmp(name, "SILENT") )    attr = A_SILENT;
X            else if( !strcmp(name, "SYMBOL") )    attr = A_SYMBOL;
X            else if( !strcmp(name, "SEQUENTIAL")) attr = A_SEQ;
X            else if( !strcmp(name, "SWAP"))       attr = A_SWAP;
X            else attr = 0;
X            break;
X      }
X
X   DB_RETURN( attr );
}
X
X
X
static int
_is_special( tg )/*
===================
X   This function returns TRUE if the name passed in represents a special
X   target, otherwise it returns false.  A special target is one that has
X   a special meaning to dmake, and may require processing at the time that
X   it is parsed.
X   
X   Current Special targets are:
X	.GROUPPROLOG	.GROUPEPILOG	.INCLUDE	.IMPORT
X	.EXPORT		.SOURCE 	.SUFFIXES	.ERROR
X	.INCLUDEDIRS	.MAKEFILES	.REMOVE		.KEEP_STATE
*/
char *tg;
{
X   DB_ENTER( "_is_special" );
X   
X   if( *tg++ != '.' ) DB_RETURN( 0 );
X   
X   switch( *tg )
X   {
X      case 'I':
X         if( !strcmp( tg, "IMPORT" ) )		DB_RETURN( ST_IMPORT   );
X         else if( !strcmp( tg, "INCLUDE" ) )	DB_RETURN( ST_INCLUDE  );
X	 else if( !strcmp( tg, "INCLUDEDIRS" )) DB_RETURN( ST_REST     );
X	 break;
X      
X      case 'M':
X         if( !strcmp( tg, "MAKEFILES" ) )	DB_RETURN( ST_REST     );
X	 break;
X
X      case 'E':
X         if( !strcmp( tg, "ERROR" ) )		DB_RETURN( ST_REST     );
X         else if( !strcmp( tg, "EXPORT" ) )	DB_RETURN( ST_EXPORT   );
X	 break;
X
X      case 'G':
X	 if( !strcmp( tg, "GROUPPROLOG" ))      DB_RETURN( ST_REST     );
X	 else if( !strcmp( tg, "GROUPEPILOG" )) DB_RETURN( ST_REST     );
X	 break;
X
X      case 'K':
X         if( !strcmp( tg, "KEEP_STATE" ) )	DB_RETURN( ST_KEEP     );
X	 break;
X
X      case 'R':
X         if( !strcmp( tg, "REMOVE" ) )		DB_RETURN( ST_REST     );
X	 break;
X
X      case 'S':
X         if( !strncmp( tg, "SOURCE", 6 ) )	DB_RETURN( ST_SOURCE   );
X         else if( !strncmp(tg, "SUFFIXES", 8 )) DB_RETURN( ST_SOURCE   );
X	 break;
X   }
X   
X   DB_RETURN( 0 );
}
X
X
X
static int
_is_percent( np )/*
===================
X	return TRUE if np points at a string containing a % sign */
char *np;
{
X   return( (strchr(np,'%') && (*np != '\'' && np[strlen(np)-1] != '\'')) ?
X	   TRUE : FALSE );
}
X
X
static char *
_is_magic( np )/*
=================
X	return TRUE if np points at a string of the form
X	      .<chars>.<chars>  or  .<chars>
X	where chars are only alpha characters.
X
X        NOTE:  reject target if it begins with ./ or ../ */
char *np;
{
X   register char *n;
X
X   n = np;
X   if( *n != '.' ) return( NIL(char) );
X   if (strchr(DirBrkStr, *(n+1))!=NULL || *(n+1) == '.' )
X      return (NIL(char));
X
X   for( n++; isgraph(*n) && (*n != '.'); n++ );
X
X   if( *n != '\0' ) {
X      if( *n != '.' )  return( NIL(char) );
X      for( np = n++; isgraph( *n ) && (*n != '.'); n++ );
X      if( *n != '\0' ) return( NIL(char) );
X   }
X   else if( !Augmake )
X      return( NIL(char) );
X
X   /* np points at the second . of .<chars>.<chars> string.
X    * if the special target is of the form .<chars> then np points at the
X    * first . in the token. */
X
X   return( np );
}
X
SHAR_EOF
chmod 0640 dmake/rulparse.c ||
echo 'restore of dmake/rulparse.c failed'
Wc_c="`wc -c < 'dmake/rulparse.c'`"
test 39277 -eq "$Wc_c" ||
	echo 'dmake/rulparse.c: original size 39277, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= dmake/stat.c ==============
if test -f 'dmake/stat.c' -a X"$1" != X"-c"; then
	echo 'x - skipping dmake/stat.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
sed 's/^X//' << 'SHAR_EOF' > 'dmake/stat.c' &&
/* RCS      -- $Header: /u2/dvadura/src/generic/dmake/src/RCS/stat.c,v 1.1 91/05/06 15:23:29 dvadura Exp $
-- SYNOPSIS -- bind a target name to a file.
-- 
-- DESCRIPTION
--	This file contains the code to go and stat a target.  The stat rules
--	follow a predefined order defined in the comment for Stat_target.
-- 
-- AUTHOR
--      Dennis Vadura, dvadura@watdragon.uwaterloo.ca
--      CS DEPT, University of Waterloo, Waterloo, Ont., Canada
--
-- COPYRIGHT
--      Copyright (c) 1990 by Dennis Vadura.  All rights reserved.
-- 
--      This program is free software; you can redistribute it and/or
--      modify it under the terms of the GNU General Public License
--      (version 1), as published by the Free Software Foundation, and
--      found in the file 'LICENSE' included with this distribution.
-- 
--      This program is distributed in the hope that it will be useful,
--      but WITHOUT ANY WARRANTY; without even the implied warrant of
--      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--      GNU General Public License for more details.
-- 
--      You should have received a copy of the GNU General Public License
--      along with this program;  if not, write to the Free Software
--      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
--
-- LOG
--     $Log:	stat.c,v $
X * Revision 1.1  91/05/06  15:23:29  dvadura
X * dmake Release Version 3.7
X * 
*/
X
#include "extern.h"
X
X
static	int	_check_dir_list ANSI((CELLPTR, CELLPTR, int));
X
#ifdef DBUG
X   /* Just a little ditty for debugging this thing */
X   static time_t
X   _do_stat( name, lib, sym )
X   char *name;
X   char *lib;
X   char **sym;
X   {
X      time_t res;
X      DB_ENTER( "_do_stat" );
X
X      res = Do_stat(name, lib, sym);
X      DB_PRINT( "stat", ("Statted [%s,%s,%d,%ld]", name, lib, sym, res) );
X
X      DB_RETURN( res );
X   }   
#define DO_STAT(A,B,C)  _do_stat(A,B,C)
#else
#define DO_STAT(A,B,C)  Do_stat(A,B,C)
#endif
X
static char *_first;	/* local storage of first attempted path */
SHAR_EOF
true || echo 'restore of dmake/stat.c failed'
fi
echo 'End of part 29, continue with part 30'
echo 30 > _shar_seq_.tmp
exit 0

exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.