[comp.os.minix] a more compatible make for minix

beattie@netsys.UUCP (Brian Beattie) (02/27/88)

enclosed is a shar file for a make that is, I believe is
compatible with v7 make.  It comes from mod.sources.  I have
been useing it for about a week.  It seems to fix the problems
that have been reported with minix.

--------------------- cut-here ---------------------------
echo x - Makefile
sed 's/^X//' > Makefile << '/'
X# Makefile for make!
X
XCFLAGS = -Dunix -DMINIX
X
XOBJS	=	check.s input.s macro.s main.s \
X		make.s reader.s rules.s
X
Xm:		$(OBJS)
X	cc -o m $(OBJS)
X
Xcheck.s:	h.h check.c
X	$(CC) -S $(CFLAGS) check.c
X
Xinput.s:	h.h input.c
X	$(CC) -S $(CFLAGS) input.c
X
Xmacro.s:	h.h macro.c
X	$(CC) -S $(CFLAGS) macro.c
X
Xmain.s:		h.h main.c
X	$(CC) -S $(CFLAGS) main.c
X
Xmake.s:		h.h make.c
X	$(CC) -S $(CFLAGS) make.c
X
Xreader.s:	h.h reader.c
X	$(CC) -S $(CFLAGS) reader.c
X
Xrules.s:	h.h rules.c
X	$(CC) -S $(CFLAGS) rules.c
/
echo x - ReadMe
sed 's/^X//' > ReadMe << '/'
XFollowing is a repost of the public domain 'make' that I posted
Xto net.sources a couple of months ago.  I have fixed a few bugs, and
Xadded some more features, and the resulting changes amounted to
Xabout as much text as the whole program (hence the repost).
X
XFor those that missed the net.sources posting, this is a public domain
Xre-implementation of the UNIX make program.  There is no manual included;
Xfor documentation, refer to a UNIX manual, or the source.
X
XHere is a list of the changes made:
X
Xi)	If '-' (ignore) or '@' (silent) where used at the start
X	of a command, their effect was not turned off for the following
X	commands.
Xii)	A special target (.SUFFIXES, .PRECIOUS) or a rule (.c.o, .a.o),
X	if first in the file would be taken as the default target.
X	This resulted in error messages like "Don't know how to
X	make .c", because things like .SUFFIXES were being made.
X	This was further complicated by ---
Xiii)	Special target lines with no dependents (ie. .SUFFIXES:\n)
X	were not clearing out the existing dependents like
X	they should.
Xiv)	Default rules could not be redefined because of the error
X	checking for commands being defined twice.  Now you are
X	allowed to define a target beinging with '.', having
X	no dependents with commands.
Xv)	The -q option didn't do the time comparison correctly,
X	or clear the variable used to keep track of this.  Thus
X	it didn't work very well.
Xvi)	The syntax ${..} for macro's supported by UNIX make was
X	not supported.
Xvii)	There wuz a couple of spelling errors.
Xviii)	When make checked for implicit rules on targets without
X	a suffix, there were problems.  (Note: The ~ feature of
X	UNIX make wasn't and still isn't supported)
Xix)	The -n option did not print @ lines like it was supposed to.
Xx)	:: added.  (See UNIX manual)
Xxi)	$? added.  (see UNIX manual)
/
echo x - h.h
sed 's/^X//' > h.h << '/'
X/*
X *	Include header for make
X */
X
X
X#ifndef uchar
X#ifdef os9
X#define uchar		char
X#define void		int
X#define fputc		putc
X#else
X#define uchar		unsigned char
X#endif
X#endif
X
X#define bool		uchar
X#define time_t		long
X#define TRUE		(1)
X#define FALSE		(0)
X#define max(a,b)	((a)>(b)?(a):(b))
X
X#define DEFN1		"makefile"		/*  Default names  */
X#ifdef unix
X#define DEFN2		"Makefile"
X#endif
X#ifdef eon
X#define DEFN2		"Makefile"
X#endif
X/* os9 is case insensitive */
X
X#define LZ		(1024)			/*  Line size  */
X
X
X
X/*
X *	A name.  This represents a file, either to be made, or existant
X */
X
Xstruct name
X{
X	struct name *		n_next;		/* Next in the list of names */
X	char *			n_name;		/* Called */
X	struct line *		n_line;		/* Dependencies */
X	time_t			n_time;		/* Modify time of this name */
X	uchar			n_flag;		/* Info about the name */
X};
X
X#define N_MARK		0x01			/* For cycle check */
X#define N_DONE		0x02			/* Name looked at */
X#define N_TARG		0x04			/* Name is a target */
X#define N_PREC		0x08			/* Target is precious */
X#define N_DOUBLE	0x10			/* Double colon target */
X
X/*
X *	Definition of a target line.
X */
Xstruct	line
X{
X	struct line *		l_next;		/* Next line (for ::) */
X	struct depend *		l_dep;		/* Dependents for this line */
X	struct cmd *		l_cmd;		/* Commands for this line */
X};
X
X
X/*
X *	List of dependents for a line
X */
Xstruct	depend
X{
X	struct depend *		d_next;		/* Next dependent */
X	struct name *		d_name;		/* Name of dependent */
X};
X
X
X/*
X *	Commands for a line
X */
Xstruct	cmd
X{
X	struct cmd *		c_next;		/* Next command line */
X	char *			c_cmd;		/* Command line */
X};
X
X
X/*
X *	Macro storage
X */
Xstruct	macro
X{
X	struct macro *		m_next;		/* Next variable */
X	char *			m_name;		/* Called ... */
X	char *			m_val;		/* Its value */
X	uchar			m_flag;		/* Infinite loop check */
X};
X
Xextern char *		myname;
Xextern struct name	namehead;
Xextern struct macro *	macrohead;
Xextern struct name *	firstname;
Xextern bool		silent;
Xextern bool		ignore;
Xextern bool		rules;
Xextern bool		dotouch;
Xextern bool		quest;
Xextern bool		domake;
Xextern char		str1[];
Xextern char		str2[];
Xextern int		lineno;
X
Xchar *			fgets();
Xchar *			index();
Xchar *			rindex();
Xchar *			malloc();
Xextern int		errno;
X
Xchar *			getmacro();
Xstruct macro *		setmacro();
Xvoid			input();
Xvoid			error();
Xvoid			fatal();
Xint			make();
Xstruct name *		newname();
Xstruct depend *		newdep();
Xstruct cmd *		newcmd();
Xvoid			newline();
Xchar *			suffix();
Xvoid			touch();
Xvoid			makerules();
Xchar *			gettok();
Xvoid			precious();
/
echo x - check.c
sed 's/^X//' > check.c << '/'
X/*
X *	Check structures for make.
X */
X
X#include <stdio.h>
X#include "h.h"
X
X
X/*
X *	Prints out the structures as defined in memory.  Good for check
X *	that you make file does what you want (and for debugging make).
X */
Xvoid
Xprt()
X{
X	register struct name *		np;
X	register struct depend *	dp;
X	register struct line *		lp;
X	register struct cmd *		cp;
X	register struct macro *		mp;
X
X
X	for (mp = macrohead; mp; mp = mp->m_next)
X		fprintf(stderr, "%s = %s\n", mp->m_name, mp->m_val);
X
X	fputc('\n', stderr);
X
X	for (np = namehead.n_next; np; np = np->n_next)
X	{
X		if (np->n_flag & N_DOUBLE)
X			fprintf(stderr, "%s::\n", np->n_name);
X		else
X			fprintf(stderr, "%s:\n", np->n_name);
X		if (np == firstname)
X			fprintf(stderr, "(MAIN NAME)\n");
X		for (lp = np->n_line; lp; lp = lp->l_next)
X		{
X			fputc(':', stderr);
X			for (dp = lp->l_dep; dp; dp = dp->d_next)
X				fprintf(stderr, " %s", dp->d_name->n_name);
X			fputc('\n', stderr);
X
X			for (cp = lp->l_cmd; cp; cp = cp->c_next)
X#ifdef os9
X				fprintf(stderr, "-   %s\n", cp->c_cmd);
X#else
X				fprintf(stderr, "-\t%s\n", cp->c_cmd);
X#endif
X			fputc('\n', stderr);
X		}
X		fputc('\n', stderr);
X	}
X}
X
X
X/*
X *	Recursive routine that does the actual checking.
X */
Xvoid
Xcheck(np)
Xstruct name *		np;
X{
X	register struct depend *	dp;
X	register struct line *		lp;
X
X
X	if (np->n_flag & N_MARK)
X		fatal("Circular dependency from %s", np->n_name);
X
X	np->n_flag |= N_MARK;
X
X	for (lp = np->n_line; lp; lp = lp->l_next)
X		for (dp = lp->l_dep; dp; dp = dp->d_next)
X			check(dp->d_name);
X
X	np->n_flag &= ~N_MARK;
X}
X
X
X/*
X *	Look for circular dependancies.
X *	ie.
X *		a: b
X *		b: a
X *	is a circular dep
X */
Xvoid
Xcirch()
X{
X	register struct name *	np;
X
X
X	for (np = namehead.n_next; np; np = np->n_next)
X		check(np);
X}
X
X
X/*
X *	Check the target .PRECIOUS, and mark its dependentd as precious
X */
Xvoid
Xprecious()
X{
X	register struct depend *	dp;
X	register struct line *		lp;
X	register struct name *		np;
X
X
X	if (!((np = newname(".PRECIOUS"))->n_flag & N_TARG))
X		return;
X
X	for (lp = np->n_line; lp; lp = lp->l_next)
X		for (dp = lp->l_dep; dp; dp = dp->d_next)
X			dp->d_name->n_flag |= N_PREC;
X}
/
echo x - input.c
sed 's/^X//' > input.c << '/'
X/*
X *	Parse a makefile
X */
X
X
X#include <stdio.h>
X#include	<ctype.h>
X#include "h.h"
X
X
Xstruct name		namehead;
Xstruct name *		firstname;
X
Xchar 			str1[LZ];		/*  General store  */
Xchar			str2[LZ];
X
X
X/*
X *	Intern a name.  Return a pointer to the name struct
X */
Xstruct name *
Xnewname(name)
Xchar *			name;
X{
X	register struct name *	rp;
X	register struct name *	rrp;
X	register char *		cp;
X
X
X	for
X	(
X		rp = namehead.n_next, rrp = &namehead;
X		rp;
X		rp = rp->n_next, rrp = rrp->n_next
X	)
X		if (strcmp(name, rp->n_name) == 0)
X			return rp;
X
X	if ((rp = (struct name *)malloc(sizeof (struct name)))
X				== (struct name *)0)
X		fatal("No memory for name");
X	rrp->n_next = rp;
X	rp->n_next = (struct name *)0;
X	if ((cp = malloc(strlen(name)+1)) == (char *)0)
X		fatal("No memory for name");
X	strcpy(cp, name);
X	rp->n_name = cp;
X	rp->n_line = (struct line *)0;
X	rp->n_time = (time_t)0;
X	rp->n_flag = 0;
X
X	return rp;
X}
X
X
X/*
X *	Add a dependant to the end of the supplied list of dependants.
X *	Return the new head pointer for that list.
X */
Xstruct depend *
Xnewdep(np, dp)
Xstruct name *		np;
Xstruct depend *		dp;
X{
X	register struct depend *	rp;
X	register struct depend *	rrp;
X
X
X	if ((rp = (struct depend *)malloc(sizeof (struct depend)))
X				== (struct depend *)0)
X		fatal("No memory for dependant");
X	rp->d_next = (struct depend *)0;
X	rp->d_name = np;
X
X	if (dp == (struct depend *)0)
X		return rp;
X
X	for (rrp = dp; rrp->d_next; rrp = rrp->d_next)
X		;
X
X	rrp->d_next = rp;
X
X	return dp;
X}
X
X
X/*
X *	Add a command to the end of the supplied list of commands.
X *	Return the new head pointer for that list.
X */
Xstruct cmd *
Xnewcmd(str, cp)
Xchar *			str;
Xstruct cmd *		cp;
X{
X	register struct cmd *	rp;
X	register struct cmd *	rrp;
X	register char *		rcp;
X
X
X	if (rcp = rindex(str, '\n'))
X		*rcp = '\0';		/*  Loose newline  */
X
X	while (isspace(*str))
X		str++;
X
X	if (*str == '\0')		/*  If nothing left, the exit  */
X		return;
X
X	if ((rp = (struct cmd *)malloc(sizeof (struct cmd)))
X				== (struct cmd *)0)
X		fatal("No memory for command");
X	rp->c_next = (struct cmd *)0;
X	if ((rcp = malloc(strlen(str)+1)) == (char *)0)
X		fatal("No memory for command");
X	strcpy(rcp, str);
X	rp->c_cmd = rcp;
X
X	if (cp == (struct cmd *)0)
X		return rp;
X
X	for (rrp = cp; rrp->c_next; rrp = rrp->c_next)
X		;
X
X	rrp->c_next = rp;
X
X	return cp;
X}
X
X
X/*
X *	Add a new 'line' of stuff to a target.  This check to see
X *	if commands already exist for the target.  If flag is set,
X *	the line is a double colon target.
X *
X *	Kludges:
X *	i)  If the new name begins with a '.', and there are no dependents,
X *	    then the target must cease to be a target.  This is for .SUFFIXES.
X *	ii) If the new name begins with a '.', with no dependents and has
X *	    commands, then replace the current commands.  This is for
X *	    redefining commands for a default rule.
X *	Neither of these free the space used by dependents or commands,
X *	since they could be used by another target.
X */
Xvoid
Xnewline(np, dp, cp, flag)
Xstruct name *		np;
Xstruct depend *		dp;
Xstruct cmd *		cp;
X{
X	bool			hascmds = FALSE;  /*  Target has commands  */
X	register struct line *	rp;
X	register struct line *	rrp;
X
X
X	/* Handle the .SUFFIXES case */
X	if (np->n_name[0] == '.' && !dp && !cp)
X	{
X		for (rp = np->n_line; rp; rp = rrp)
X		{
X			rrp = rp->l_next;
X			free(rp);
X		}
X		np->n_line = (struct line *)0;
X		np->n_flag &= ~N_TARG;
X		return;
X	}
X
X	/* This loop must happen since rrp is used later. */
X	for
X	(
X		rp = np->n_line, rrp = (struct line *)0;
X		rp;
X		rrp = rp, rp = rp->l_next
X	)
X		if (rp->l_cmd)
X			hascmds = TRUE;
X
X	if (hascmds && cp && !(np->n_flag & N_DOUBLE))
X		/* Handle the implicit rules redefinition case */
X		if (np->n_name[0] == '.' && dp == (struct depend *)0)
X		{
X			np->n_line->l_cmd = cp;
X			return;
X		}
X		else
X			error("Commands defined twice for target %s", np->n_name);
X	if (np->n_flag & N_TARG)
X		if (!(np->n_flag & N_DOUBLE) != !flag)		/* like xor */
X			error("Inconsistent rules for target %s", np->n_name);
X
X	if ((rp = (struct line *)malloc(sizeof (struct line)))
X				== (struct line *)0)
X		fatal("No memory for line");
X	rp->l_next = (struct line *)0;
X	rp->l_dep = dp;
X	rp->l_cmd = cp;
X
X	if (rrp)
X		rrp->l_next = rp;
X	else
X		np->n_line = rp;
X
X	np->n_flag |= N_TARG;
X	if (flag)
X		np->n_flag |= N_DOUBLE;
X}
X
X
X/*
X *	Parse input from the makefile, and construct a tree structure
X *	of it.
X */
Xvoid
Xinput(fd)
XFILE *			fd;
X{
X	char *			p;		/*  General  */
X	char *			q;
X	struct name *		np;
X	struct depend *		dp;
X	struct cmd *		cp;
X	bool			dbl;
X
X
X	if (getline(str1, fd))	/*  Read the first line  */
X		return;
X
X	for(;;)
X	{
X#ifdef os9
X		if (*str1 == ' ')	/*  Rules without targets  */
X#else
X		if (*str1 == '\t')	/*  Rules without targets  */
X#endif
X			error("Rules not allowed here");
X
X		p = str1;
X
X		while (isspace(*p))	/*  Find first target  */
X			p++;
X
X		while (((q = index(p, '=')) != (char *)0) &&
X		    (p != q) && (q[-1] == '\\'))	/*  Find value */
X		{
X			register char *		a;
X
X			a = q - 1;	/*  Del \ chr; move rest back  */
X			p = q;
X			while(*a++ = *q++)
X				;
X		}
X
X		if (q != (char *)0)
X		{
X			register char *		a;
X
X			*q++ = '\0';		/*  Separate name and val  */
X			while (isspace(*q))
X				q++;
X			if (p = rindex(q, '\n'))
X				*p = '\0';
X
X			p = str1;
X			if ((a = gettok(&p)) == (char *)0)
X				error("No macro name");
X
X			setmacro(a, q);
X
X			if (getline(str1, fd))
X				return;
X			continue;
X		}
X
X		expand(str1);
X		p = str1;
X
X		while (((q = index(p, ':')) != (char *)0) &&
X		    (p != q) && (q[-1] == '\\'))	/*  Find dependents  */
X		{
X			register char *		a;
X
X			a = q - 1;	/*  Del \ chr; move rest back  */
X			p = q;
X			while(*a++ = *q++)
X				;
X		}
X
X		if (q == (char *)0)
X			error("No targets provided");
X
X		*q++ = '\0';	/*  Separate targets and dependents  */
X
X		if (*q == ':')		/* Double colon */
X		{
X			dbl = 1;
X			q++;
X		}
X		else
X			dbl = 0;
X
X		for (dp = (struct depend *)0; ((p = gettok(&q)) != (char *)0);)
X					/*  get list of dep's */
X		{
X			np = newname(p);		/*  Intern name  */
X			dp = newdep(np, dp);		/*  Add to dep list */
X		}
X
X		*((q = str1) + strlen(str1) + 1) = '\0';
X			/*  Need two nulls for gettok (Remember separation)  */
X
X		cp = (struct cmd *)0;
X		if (getline(str2, fd) == FALSE)		/*  Get commands  */
X		{
X#ifdef os9
X			while (*str2 == ' ')
X#else
X			while (*str2 == '\t')
X#endif
X			{
X				cp = newcmd(&str2[0], cp);
X				if (getline(str2, fd))
X					break;
X			}
X		}
X
X		while ((p = gettok(&q)) != (char *)0)	/* Get list of targ's */
X		{
X			np = newname(p);		/*  Intern name  */
X			newline(np, dp, cp, dbl);
X			if (!firstname && p[0] != '.')
X				firstname = np;
X		}
X
X		if (feof(fd))				/*  EOF?  */
X			return;
X
X		strcpy(str1, str2);
X	}
X}
/
echo x - macro.c
sed 's/^X//' > macro.c << '/'
X/*
X *	Macro control for make
X */
X
X
X#include "h.h"
X
X
Xstruct macro *		macrohead;
X
X
Xstruct macro *
Xgetmp(name)
Xchar *			name;
X{
X	register struct macro *	rp;
X
X	for (rp = macrohead; rp; rp = rp->m_next)
X		if (strcmp(name, rp->m_name) == 0)
X			return rp;
X	return (struct macro *)0;
X}
X
X
Xchar *
Xgetmacro(name)
Xchar *			name;
X{
X	struct macro *		mp;
X
X	if (mp = getmp(name))
X		return mp->m_val;
X	else
X		return "";
X}
X
X
Xstruct macro *
Xsetmacro(name, val)
Xchar *			name;
Xchar *			val;
X{
X	register struct macro *	rp;
X	register char *		cp;
X
X
X			/*  Replace macro definition if it exists  */
X	for (rp = macrohead; rp; rp = rp->m_next)
X		if (strcmp(name, rp->m_name) == 0)
X		{
X			free(rp->m_val);	/*  Free space from old  */
X			break;
X		}
X
X	if (!rp)		/*  If not defined, allocate space for new  */
X	{
X		if ((rp = (struct macro *)malloc(sizeof (struct macro)))
X					 == (struct macro *)0)
X			fatal("No memory for macro");
X
X		rp->m_next = macrohead;
X		macrohead = rp;
X		rp->m_flag = FALSE;
X
X		if ((cp = malloc(strlen(name)+1)) == (char *)0)
X			fatal("No memory for macro");
X		strcpy(cp, name);
X		rp->m_name = cp;
X	}
X
X	if ((cp = malloc(strlen(val)+1)) == (char *)0)
X		fatal("No memory for macro");
X	strcpy(cp, val);		/*  Copy in new value  */
X	rp->m_val = cp;
X
X	return rp;
X}
X
X
X/*
X *	Do the dirty work for expand
X */
Xvoid
Xdoexp(to, from, len, buf)
Xchar **			to;
Xchar *			from;
Xint *			len;
Xchar *			buf;
X{
X	register char *		rp;
X	register char *		p;
X	register char *		q;
X	register struct macro *	mp;
X
X
X	rp = from;
X	p = *to;
X	while (*rp)
X	{
X		if (*rp != '$')
X		{
X			*p++ = *rp++;
X			(*len)--;
X		}
X		else
X		{
X			q = buf;
X			if (*++rp == '{')
X				while (*++rp && *rp != '}')
X					*q++ = *rp;
X			else if (*rp == '(')
X				while (*++rp && *rp != ')')
X					*q++ = *rp;
X			else if (!*rp)
X			{
X				*p++ = '$';
X				break;
X			}
X			else
X				*q++ = *rp;
X			*q = '\0';
X			if (*rp)
X				rp++;
X			if (!(mp = getmp(buf)))
X				mp = setmacro(buf, "");
X			if (mp->m_flag)
X				fatal("Infinitely recursive macro %s", mp->m_name);
X			mp->m_flag = TRUE;
X			*to = p;
X			doexp(to, mp->m_val, len, buf);
X			p = *to;
X			mp->m_flag = FALSE;
X		}
X		if (*len <= 0)
X			error("Expanded line too line");
X	}
X	*p = '\0';
X	*to = p;
X}
X
X
X/*
X *	Expand any macros in str.
X */
Xvoid
Xexpand(str)
Xchar *		str;
X{
X	static char		a[LZ];
X	static char		b[LZ];
X	char *			p = str;
X	int			len = LZ-1;
X
X	strcpy(a, str);
X	doexp(&p, a, &len, b);
X}
/
echo x - main.c
sed 's/^X//' > main.c << '/'
X/*
X *	make [-f makefile] [-ins] [target(s) ...]
X *
X *	(Better than EON mk but not quite as good as UNIX make)
X *
X *	-f makefile name
X *	-i ignore exit status
X *	-n Pretend to make
X *	-p Print all macros & targets
X *	-q Question up-to-dateness of target.  Return exit status 1 if not
X *	-r Don't not use inbuilt rules
X *	-s Make silently
X *	-t Touch files instead of making them
X *	-m Change memory requirements (EON only)
X */
X
X#include <stdio.h>
X#include "h.h"
X
X#ifdef unix
X#include <sys/errno.h>
X#endif
X#ifdef eon
X#include <sys/err.h>
X#endif
X#ifdef os9
X#include <errno.h>
X#endif
X
X
X#ifdef eon
X#define MEMSPACE	(16384)
X#endif
X
X
Xchar *			myname;
Xchar *			makefile;	/*  The make file  */
X#ifdef eon
Xunsigned		memspace = MEMSPACE;
X#endif
X
XFILE *			ifd;		/*  Input file desciptor  */
Xbool			domake = TRUE;	/*  Go through the motions option  */
Xbool			ignore = FALSE;	/*  Ignore exit status option  */
Xbool			silent = FALSE;	/*  Silent option  */
Xbool			print = FALSE;	/*  Print debuging information  */
Xbool			rules = TRUE;	/*  Use inbuilt rules  */
Xbool			dotouch = FALSE;/*  Touch files instead of making  */
Xbool			quest = FALSE;	/*  Question up-to-dateness of file  */
X
X
Xvoid
Xmain(argc, argv)
Xint			argc;
Xchar **			argv;
X{
X	register char *		p;		/*  For argument processing  */
X	int			estat = 0;	/*  For question  */
X	register struct name *	np;
X
X
X	myname = (argc-- < 1) ? "make" : *argv++;
X
X	while ((argc > 0) && (**argv == '-'))
X	{
X		argc--;		/*  One less to process  */
X		p = *argv++;	/*  Now processing this one  */
X
X		while (*++p != '\0')
X		{
X			switch(*p)
X			{
X			case 'f':	/*  Alternate file name  */
X				if (*++p == '\0')
X				{
X					if (argc-- <= 0)
X						usage();
X					p = *argv++;
X				}
X				makefile = p;
X				goto end_of_args;
X#ifdef eon
X			case 'm':	/*  Change space requirements  */
X				if (*++p == '\0')
X				{
X					if (argc-- <= 0)
X						usage();
X					p = *argv++;
X				}
X				memspace = atoi(p);
X				goto end_of_args;
X#endif
X			case 'n':	/*  Pretend mode  */
X				domake = FALSE;
X				break;
X			case 'i':	/*  Ignore fault mode  */
X				ignore = TRUE;
X				break;
X			case 's':	/*  Silent about commands  */
X				silent = TRUE;
X				break;
X			case 'p':
X				print = TRUE;
X				break;
X			case 'r':
X				rules = FALSE;
X				break;
X			case 't':
X				dotouch = TRUE;
X				break;
X			case 'q':
X				quest = TRUE;
X				break;
X			default:	/*  Wrong option  */
X				usage();
X			}
X		}
X	end_of_args:;
X	}
X
X#ifdef eon
X	if (initalloc(memspace) == 0xffff)  /*  Must get memory for alloc  */
X		fatal("Cannot initalloc memory");
X#endif
X
X	if (strcmp(makefile, "-") == 0)	/*  Can use stdin as makefile  */
X		ifd = stdin;
X	else
X		if (!makefile)		/*  If no file, then use default */
X		{
X			if ((ifd = fopen(DEFN1, "r")) == (FILE *)0)
X#ifdef eon
X				if (errno != ER_NOTF)
X					fatal("Can't open %s; error %02x", DEFN1, errno);
X#endif
X#ifdef unix
X				if (errno != ENOENT)
X					fatal("Can't open %s; error %02x", DEFN1, errno);
X#endif
X#ifndef os9
X			if ((ifd == (FILE *)0)
X				  && ((ifd = fopen(DEFN2, "r")) == (FILE *)0))
X				fatal("Can't open %s", DEFN2);
X#else
X				fatal("Can't open %s", DEFN1);
X#endif
X		}
X		else
X			if ((ifd = fopen(makefile, "r")) == (FILE *)0)
X				fatal("Can't open %s", makefile);
X
X	makerules();
X
X	setmacro("$", "$");
X
X	while (argc && (p = index(*argv, '=')))
X	{
X		char		c;
X
X		c = *p;
X		*p = '\0';
X		setmacro(*argv, p+1);
X		*p = c;
X
X		argv++;
X		argc--;
X	}
X
X	input(ifd);	/*  Input all the gunga  */
X	fclose(ifd);	/*  Finished with makefile  */
X	lineno = 0;	/*  Any calls to error now print no line number */
X
X	if (print)
X		prt();	/*  Print out structures  */
X
X	np = newname(".SILENT");
X	if (np->n_flag & N_TARG)
X		silent = TRUE;
X
X	np = newname(".IGNORE");
X	if (np->n_flag & N_TARG)
X		ignore = TRUE;
X
X	precious();
X
X	if (!firstname)
X		fatal("No targets defined");
X
X	circh();	/*  Check circles in target definitions  */
X
X	if (!argc)
X		estat = make(firstname, 0);
X	else while (argc--)
X	{
X		if (!print && !silent && strcmp(*argv, "love") == 0)
X			printf("Not war!\n");
X		estat |= make(newname(*argv++), 0);
X	}
X
X	if (quest)
X		exit(estat);
X	else
X		exit(0);
X}
X
X
Xusage()
X{
X	fprintf(stderr, "Usage: %s [-f makefile] [-inpqrst] [macro=val ...] [target(s) ...]\n", myname);
X	exit(1);
X}
X
X
Xvoid
Xfatal(msg, a1, a2, a3, a4, a5, a6)
Xchar	*msg;
X{
X	fprintf(stderr, "%s: ", myname);
X	fprintf(stderr, msg, a1, a2, a3, a4, a5, a6);
X	fputc('\n', stderr);
X	exit(1);
X}
/
echo x - make.c
sed 's/^X//' > make.c << '/'
X/*
X *	Do the actual making for make
X */
X
X#include <stdio.h>
X#ifdef unix
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/errno.h>
X#endif
X#ifdef eon
X#include <sys/stat.h>
X#include <sys/err.h>
X#endif
X#ifdef os9
X#include <time.h>
X#include <os9.h>
X#include <modes.h>
X#include <direct.h>
X#include <errno.h>
X#endif
X#include "h.h"
X
X
X
X/*
X *	Exec a shell that returns exit status correctly (/bin/esh).
X *	The standard EON shell returns the process number of the last
X *	async command, used by the debugger (ugg).
X *	[exec on eon is like a fork+exec on unix]
X */
Xint
Xdosh(string, shell)
Xchar *			string;
Xchar *			shell;
X{
X	int	number;
X
X#ifdef unix
X	return system(string);
X#endif
X#ifdef eon
X	return ((number = execl(shell, shell,"-c", string, 0)) == -1) ?
X		-1:	/* couldn't start the shell */
X		wait(number);	/* return its exit status */
X#endif
X#ifdef os9
X	int	status, pid;
X
X	strcat(string, "\n");
X	if ((number = os9fork(shell, strlen(string), string, 0, 0, 0)) == -1)
X		return -1;		/* Couldn't start a shell */
X	do
X	{
X		if ((pid = wait(&status)) == -1)
X			return -1;	/* child already died!?!? */
X	} while (pid != number);
X
X	return status;
X#endif
X}
X
X
X/*
X *	Do commands to make a target
X */
Xvoid
Xdocmds1(np, lp)
Xstruct name *		np;
Xstruct line *		lp;
X{
X	bool			ssilent;
X	bool			signore;
X	int			estat;
X	register char *		q;
X	register char *		p;
X	char *			shell;
X	register struct cmd *	cp;
X
X
X	if (*(shell = getmacro("SHELL")) == '\0')
X#ifdef eon
X		shell = ":bin/esh";
X#endif
X#ifdef unix
X		shell = "/bin/sh";
X#endif
X#ifdef os9
X		shell = "shell";
X#endif
X
X	for (cp = lp->l_cmd; cp; cp = cp->c_next)
X	{
X		strcpy(str1, cp->c_cmd);
X		expand(str1);
X		q = str1;
X		ssilent = silent;
X		signore = ignore;
X		while ((*q == '@') || (*q == '-'))
X		{
X			if (*q == '@')	   /*  Specific silent  */
X				ssilent = TRUE;
X			else		   /*  Specific ignore  */
X				signore = TRUE;
X			q++;		   /*  Not part of the command  */
X		}
X
X		if (!domake)
X			ssilent = 0;
X
X		if (!ssilent)
X			fputs("    ", stdout);
X
X		for (p=q; *p; p++)
X		{
X			if (*p == '\n' && p[1] != '\0')
X			{
X				*p = ' ';
X				if (!ssilent)
X					fputs("\\\n", stdout);
X			}
X			else if (!ssilent)
X				putchar(*p);
X		}
X		if (!ssilent)
X			printf("\n");
X
X		if (domake)
X		{			/*  Get the shell to execute it  */
X			if ((estat = dosh(q, shell)) != 0)
X			{
X				if (estat == -1)
X					fatal("Couldn't execute %s", shell);
X				else
X				{
X					printf("%s: Error code %d", myname, estat);
X					if (signore)
X						fputs(" (Ignored)\n", stdout);
X					else
X					{
X						putchar('\n');
X						if (!(np->n_flag & N_PREC))
X							if (unlink(np->n_name) == 0)
X								printf("%s: '%s' removed.\n", myname, np->n_name);
X						exit(estat);
X					}
X				}
X			}
X		}
X	}
X}
X
X
Xdocmds(np)
Xstruct name *		np;
X{
X	register struct line *	lp;
X
X
X	for (lp = np->n_line; lp; lp = lp->l_next)
X		docmds1(np, lp);
X}
X
X
X#ifdef os9
X/*
X *	Some stuffing around to get the modified time of a file
X *	in an os9 file system
X */
Xgetmdate(fd, tbp)
Xstruct sgtbuf *		tbp;
X{
X	struct registers	regs;
X	static struct fildes	fdbuf;
X
X
X	regs.rg_a = fd;
X	regs.rg_b = SS_FD;
X	regs.rg_x = &fdbuf;
X	regs.rg_y = sizeof (fdbuf);
X
X	if (_os9(I_GETSTT, &regs) == -1)
X	{
X		errno = regs.rg_b & 0xff;
X		return -1;
X	}
X	if (tbp)
X	{
X		_strass(tbp, fdbuf.fd_date, sizeof (fdbuf.fd_date));
X		tbp->t_second = 0;	/* Files are only acurate to mins */
X	}
X	return 0;
X}
X
X
X/*
X *	Kludge routine to return an aproximation of how many
X *	seconds since 1980.  Dates will be in order, but will not
X *	be lineer
X */
Xtime_t
Xcnvtime(tbp)
Xstruct sgtbuf		*tbp;
X{
X	long			acc;
X
X
X	acc = tbp->t_year - 80;		/* Baseyear is 1980 */
X	acc = acc * 12 + tbp->t_month;
X	acc = acc * 31 + tbp->t_day;
X	acc = acc * 24 + tbp->t_hour;
X	acc = acc * 60 + tbp->t_minute;
X	acc = acc * 60 + tbp->t_second;
X
X	return acc;
X}
X
X
X/*
X *	Get the current time in the internal format
X */
Xtime(tp)
Xtime_t *		tp;
X{
X	struct sgtbuf		tbuf;
X
X
X	if (getime(&tbuf) < 0)
X		return -1;
X
X	if (tp)
X		*tp = cnvtime(&tbuf);
X
X	return 0;
X}
X#endif
X
X
X/*
X *	Get the modification time of a file.  If the first
X *	doesn't exist, it's modtime is set to 0.
X */
Xvoid
Xmodtime(np)
Xstruct name *		np;
X{
X#ifdef unix
X	struct stat		info;
X	int			fd;
X
X
X	if (stat(np->n_name, &info) < 0)
X	{
X		if (errno != ENOENT)
X			fatal("Can't open %s; error %d", np->n_name, errno);
X
X		np->n_time = 0L;
X	}
X	else
X		np->n_time = info.st_mtime;
X#endif
X#ifdef eon
X	struct stat		info;
X	int			fd;
X
X
X	if ((fd = open(np->n_name, 0)) < 0)
X	{
X		if (errno != ER_NOTF)
X			fatal("Can't open %s; error %02x", np->n_name, errno);
X
X		np->n_time = 0L;
X	}
X	else if (getstat(fd, &info) < 0)
X		fatal("Can't getstat %s; error %02x", np->n_name, errno);
X	else
X		np->n_time = info.st_mod;
X
X	close(fd);
X#endif
X#ifdef os9
X	struct sgtbuf		info;
X	int			fd;
X
X
X	if ((fd = open(np->n_name, 0)) < 0)
X	{
X		if (errno != E_PNNF)
X			fatal("Can't open %s; error %02x", np->n_name, errno);
X
X		np->n_time = 0L;
X	}
X	else if (getmdate(fd, &info) < 0)
X		fatal("Can't getstat %s; error %02x", np->n_name, errno);
X	else
X		np->n_time = cnvtime(&info);
X
X	close(fd);
X#endif
X}
X
X
X/*
X *	Update the mod time of a file to now.
X */
Xvoid
Xtouch(np)
Xstruct name *		np;
X{
X	char			c;
X	int			fd;
X
X
X	if (!domake || !silent)
X		printf("    touch(%s)\n", np->n_name);
X
X	if (domake)
X	{
X#ifdef unix
X		long		a[2];
X
X		a[0] = a[1] = time(0);
X		if (utime(np->n_name, &a[0]) < 0)
X			printf("%s: '%s' not touched - non-existant\n",
X					myname, np->n_name);
X#endif
X#ifdef eon
X		if ((fd = open(np->n_name, 0)) < 0)
X			printf("%s: '%s' not touched - non-existant\n",
X					myname, np->n_name);
X		else
X		{
X			uread(fd, &c, 1, 0);
X			uwrite(fd, &c, 1);
X		}
X		close(fd);
X#endif
X#ifdef os9
X		/*
X		 *	Strange that something almost as totally useless
X		 *	as this is easy to do in os9!
X		 */
X		if ((fd = open(np->n_name, S_IWRITE)) < 0)
X			printf("%s: '%s' not touched - non-existant\n",
X					myname, np->n_name);
X		close(fd);
X#endif
X	}
X}
X
X
X/*
X *	Recursive routine to make a target.
X */
Xint
Xmake(np, level)
Xstruct name *		np;
Xint			level;
X{
X	register struct depend *	dp;
X	register struct line *		lp;
X	register struct depend *	qdp;
X	time_t				dtime = 1;
X	bool				didsomething = 0;
X
X
X	if (np->n_flag & N_DONE)
X		return 0;
X
X	if (!np->n_time)
X		modtime(np);		/*  Gets modtime of this file  */
X
X	if (rules)
X	{
X		for (lp = np->n_line; lp; lp = lp->l_next)
X			if (lp->l_cmd)
X				break;
X		if (!lp)
X			dyndep(np);
X	}
X
X	if (!(np->n_flag & N_TARG) && np->n_time == 0L)
X		fatal("Don't know how to make %s", np->n_name);
X
X	for (qdp = (struct depend *)0, lp = np->n_line; lp; lp = lp->l_next)
X	{
X		for (dp = lp->l_dep; dp; dp = dp->d_next)
X		{
X			make(dp->d_name, level+1);
X			if (np->n_time < dp->d_name->n_time)
X				qdp = newdep(dp->d_name, qdp);
X			dtime = max(dtime, dp->d_name->n_time);
X		}
X		if (!quest && (np->n_flag & N_DOUBLE) && (np->n_time < dtime))
X		{
X			make1(np, lp, qdp);	/* free()'s qdp */
X			dtime = 1;
X			qdp = (struct depend *)0;
X			didsomething++;
X		}
X	}
X
X	np->n_flag |= N_DONE;
X
X	if (quest)
X	{
X		long		t;
X
X		t = np->n_time;
X		time(&np->n_time);
X		return t < dtime;
X	}
X	else if (np->n_time < dtime && !(np->n_flag & N_DOUBLE))
X	{
X		make1(np, (struct line *)0, qdp);	/* free()'s qdp */
X		time(&np->n_time);
X	}
X	else if (level == 0 && !didsomething)
X		printf("%s: '%s' is up to date\n", myname, np->n_name);
X	return 0;
X}
X
X
Xmake1(np, lp, qdp)
Xregister struct depend *	qdp;
Xstruct line *			lp;
Xstruct name *			np;
X{
X	register struct depend *	dp;
X
X
X	if (dotouch)
X		touch(np);
X	else
X	{
X		strcpy(str1, "");
X		for (dp = qdp; dp; dp = qdp)
X		{
X			if (strlen(str1))
X				strcat(str1, " ");
X			strcat(str1, dp->d_name->n_name);
X			qdp = dp->d_next;
X			free(dp);
X		}
X		setmacro("?", str1);
X		setmacro("@", np->n_name);
X		if (lp)		/* lp set if doing a :: rule */
X			docmds1(np, lp);
X		else
X			docmds(np);
X	}
X}
/
echo x - reader.c
sed 's/^X//' > reader.c << '/'
X/*
X *	Read in makefile
X */
X
X
X#include <stdio.h>
X#include	<ctype.h>
X#include "h.h"
X
X
Xint			lineno;
X
X
X/*
X *	Syntax error handler.  Print message, with line number, and exits.
X */
Xvoid
Xerror(msg, a1, a2, a3)
Xchar *			msg;
X{
X	fprintf(stderr, "%s: ", myname);
X	fprintf(stderr, msg, a1, a2, a3);
X	if (lineno)
X		fprintf(stderr, " near line %d", lineno);
X	fputc('\n', stderr);
X	exit(1);
X}
X
X
X/*
X *	Read a line into the supplied string of length LZ.  Remove
X *	comments, ignore blank lines. Deal with	quoted (\) #, and
X *	quoted newlines.  If EOF return TRUE.
X */
Xbool
Xgetline(str, fd)
Xchar *		str;
XFILE *		fd;
X{
X	register char *		p;
X	char *			q;
X	int			pos = 0;
X
X
X	for (;;)
X	{
X		if (fgets(str+pos, LZ-pos, fd) == (char *)0)
X			return TRUE;		/*  EOF  */
X
X		lineno++;
X
X		if ((p = index(str+pos, '\n')) == (char *)0)
X			error("Line too long");
X
X		if (p[-1] == '\\')
X		{
X			p[-1] = '\n';
X			pos = p - str;
X			continue;
X		}
X
X		p = str;
X		while (((q = index(p, '#')) != (char *)0) &&
X		    (p != q) && (q[-1] == '\\'))
X		{
X			char	*a;
X
X			a = q - 1;	/*  Del \ chr; move rest back  */
X			p = q;
X			while (*a++ = *q++)
X				;
X		}
X		if (q != (char *)0)
X		{
X			q[0] = '\n';
X			q[1] = '\0';
X		}
X
X		p = str;
X		while (isspace(*p))	/*  Checking for blank  */
X			p++;
X
X		if (*p != '\0')
X			return FALSE;
X		pos = 0;
X	}
X}
X
X
X/*
X *	Get a word from the current line, surounded by white space.
X *	return a pointer to it. String returned has no white spaces
X *	in it.
X */
Xchar *
Xgettok(ptr)
Xchar	**ptr;
X{
X	register char *		p;
X
X
X	while (isspace(**ptr))	/*  Skip spaces  */
X		(*ptr)++;
X
X	if (**ptr == '\0')	/*  Nothing after spaces  */
X		return NULL;
X
X	p = *ptr;		/*  word starts here  */
X
X	while ((**ptr != '\0') && (!isspace(**ptr)))
X		(*ptr)++;	/*  Find end of word  */
X
X	*(*ptr)++ = '\0';	/*  Terminate it  */
X
X	return(p);
X}
/
echo x - rules.c
sed 's/^X//' > rules.c << '/'
X/*
X *	Control of the implicit suffix rules
X */
X
X
X#include "h.h"
X
X
X/*
X *	Return a pointer to the suffix of a name
X */
Xchar *
Xsuffix(name)
Xchar *			name;
X{
X	return rindex(name, '.');
X}
X
X
X/*
X *	Dynamic dependency.  This routine applies the suffis rules
X *	to try and find a source and a set of rules for a missing
X *	target.  If found, np is made into a target with the implicit
X *	source name, and rules.  Returns TRUE if np was made into
X *	a target.
X */
Xbool
Xdyndep(np)
Xstruct name *		np;
X{
X	register char *		p;
X	register char *		q;
X	register char *		suff;		/*  Old suffix  */
X	register char *		basename;	/*  Name without suffix  */
X	struct name *		op;		/*  New dependent  */
X	struct name *		sp;		/*  Suffix  */
X	struct line *		lp;
X	struct depend *		dp;
X	char *			newsuff;
X
X
X	p = str1;
X	q = np->n_name;
X	if (!(suff = suffix(q)))
X		return FALSE;		/* No suffix */
X	while (q < suff)
X		*p++ = *q++;
X	*p = '\0';
X	basename = setmacro("*", str1)->m_val;
X
X	if (!((sp = newname(".SUFFIXES"))->n_flag & N_TARG))
X		return FALSE;
X
X	for (lp = sp->n_line; lp; lp = lp->l_next)
X		for (dp = lp->l_dep; dp; dp = dp->d_next)
X		{
X			newsuff = dp->d_name->n_name;
X			if (strlen(suff)+strlen(newsuff)+1 >= LZ)
X				fatal("Suffix rule too long");
X			p = str1;
X			q = newsuff;
X			while (*p++ = *q++)
X				;
X			p--;
X			q = suff;
X			while (*p++ = *q++)
X				;
X			sp = newname(str1);
X			if (sp->n_flag & N_TARG)
X			{
X				p = str1;
X				q = basename;
X				if (strlen(basename) + strlen(newsuff)+1 >= LZ)
X					fatal("Implicit name too long");
X				while (*p++ = *q++)
X					;
X				p--;
X				q = newsuff;
X				while (*p++ = *q++)
X					;
X				op = newname(str1);
X				if (!op->n_time)
X					modtime(op);
X				if (op->n_time)
X				{
X					dp = newdep(op, 0);
X					newline(np, dp, sp->n_line->l_cmd, 0);
X					setmacro("<", op->n_name);
X					return TRUE;
X				}
X			}
X		}
X	return FALSE;
X}
X
X
X/*
X *	Make the default rules
X */
Xvoid
Xmakerules()
X{
X	struct cmd *		cp;
X	struct name *		np;
X	struct depend *		dp;
X
X
X#ifdef eon
X	setmacro("BDSCC", "asm");
X	/*	setmacro("BDSCFLAGS", "");	*/
X	cp = newcmd("$(BDSCC) $(BDSCFLAGS) -n $<", 0);
X	np = newname(".c.o");
X	newline(np, 0, cp, 0);
X
X	setmacro("CC", "c");
X	setmacro("CFLAGS", "-O");
X	cp = newcmd("$(CC) $(CFLAGS) -c $<", 0);
X	np = newname(".c.obj");
X	newline(np, 0, cp, 0);
X
X	setmacro("M80", "asm -n");
X	/*	setmacro("M80FLAGS", "");	*/
X	cp = newcmd("$(M80) $(M80FLAGS) $<", 0);
X	np = newname(".mac.o");
X	newline(np, 0, cp, 0);
X
X	setmacro("AS", "zas");
X	/*	setmacro("ASFLAGS", "");	*/
X	cp = newcmd("$(ZAS) $(ASFLAGS) -o $@ $<", 0);
X	np = newname(".as.obj");
X	newline(np, 0, cp, 0);
X
X	np = newname(".as");
X	dp = newdep(np, 0);
X	np = newname(".obj");
X	dp = newdep(np, dp);
X	np = newname(".c");
X	dp = newdep(np, dp);
X	np = newname(".o");
X	dp = newdep(np, dp);
X	np = newname(".mac");
X	dp = newdep(np, dp);
X	np = newname(".SUFFIXES");
X	newline(np, dp, 0, 0);
X#endif
X
X/*
X *	Some of the UNIX implicit rules
X */
X#ifdef unix
X	setmacro("CC", "cc");
X	setmacro("CFLAGS", "-O");
X#ifdef MINIX
X	cp = newcmd("$(CC) $(CFLAGS) -S $<", 0);
X	np = newname(".c.s");
X#else
X	cp = newcmd("$(CC) $(CFLAGS) -c $<", 0);
X	np = newname(".c.o");
X#endif MINIX
X	newline(np, 0, cp, 0);
X
X	setmacro("AS", "as");
X	cp = newcmd("$(AS) -o $@ $<", 0);
X	np = newname(".s.o");
X	newline(np, 0, cp, 0);
X
X	setmacro("YACC", "yacc");
X	/*	setmacro("YFLAGS", "");	*/
X	cp = newcmd("$(YACC) $(YFLAGS) $<", 0);
X	cp = newcmd("mv y.tab.c $@", cp);
X	np = newname(".y.c");
X	newline(np, 0, cp, 0);
X
X	cp = newcmd("$(YACC) $(YFLAGS) $<", 0);
X	cp = newcmd("$(CC) $(CFLAGS) -c y.tab.c", cp);
X	cp = newcmd("rm y.tab.c", cp);
X	cp = newcmd("mv y.tab.o $@", cp);
X	np = newname(".y.o");
X	newline(np, 0, cp, 0);
X
X	np = newname(".s");
X	dp = newdep(np, 0);
X	np = newname(".o");
X	dp = newdep(np, dp);
X	np = newname(".c");
X	dp = newdep(np, dp);
X	np = newname(".y");
X	dp = newdep(np, dp);
X	np = newname(".SUFFIXES");
X	newline(np, dp, 0, 0);
X#endif
X#ifdef os9
X/*
X *	Fairlight use an enhanced version of the C sub-system.
X *	They have a specialised macro pre-processor.
X */
X	setmacro("CC", "cc");
X	setmacro("CFLAGS", "-z");
X	cp = newcmd("$(CC) $(CFLAGS) -r $<", 0);
X
X	np = newname(".c.r");
X	newline(np, 0, cp, 0);
X	np = newname(".ca.r");
X	newline(np, 0, cp, 0);
X	np = newname(".a.r");
X	newline(np, 0, cp, 0);
X	np = newname(".o.r");
X	newline(np, 0, cp, 0);
X	np = newname(".mc.r");
X	newline(np, 0, cp, 0);
X	np = newname(".mca.r");
X	newline(np, 0, cp, 0);
X	np = newname(".ma.r");
X	newline(np, 0, cp, 0);
X	np = newname(".mo.r");
X	newline(np, 0, cp, 0);
X
X	np = newname(".r");
X	dp = newdep(np, 0);
X	np = newname(".mc");
X	dp = newdep(np, dp);
X	np = newname(".mca");
X	dp = newdep(np, dp);
X	np = newname(".c");
X	dp = newdep(np, dp);
X	np = newname(".ca");
X	dp = newdep(np, dp);
X	np = newname(".ma");
X	dp = newdep(np, dp);
X	np = newname(".mo");
X	dp = newdep(np, dp);
X	np = newname(".o");
X	dp = newdep(np, dp);
X	np = newname(".a");
X	dp = newdep(np, dp);
X	np = newname(".SUFFIXES");
X	newline(np, dp, 0, 0);
X#endif
X}
/
exit
-- 
Brian Beattie           ...netsys!beattie
11525 Hickory Cluster
Reston VA 22090         (703)471-5513