/* for xc:
% cc -O -o mkdep mkdep.c
*/

#include <stdio.h>
#include <ctype.h>

#define Tolower(c) (isupper(c)?tolower(c):(c))

#define MAGIC '#'

#define MAXDIRS 50
char *dirs[50];
char **dirp = dirs;

char *outfile;
FILE *outfp;
int dflag;
int nflag;

struct file {
	struct file *f_link;
	struct file *f_hlink;
	char *f_name;
	char *f_xname;
	struct dep *f_dhead;
	struct dep *f_dtail;
	char f_done;
	char f_type;
};

#define T_UNKNOWN	0
#define T_C		1
#define T_PASCAL	2
#define T_FORTRAN	3

char *typename[] = { "UNKNOWN", "C", "PASCAL", "FORTRAN" };

struct dep {
	struct dep *d_link;
	struct file *d_file;
};

#define HASHSIZE 127
struct file *fhead, *ftail;
struct file *ftab[HASHSIZE];

struct file *addfile(), *findfile();
char *getstr(), *expand(), *try(), *alloc();
extern char *sys_errlist[];
extern int errno;

#define ALLOC(x) ((struct x *) alloc(sizeof(struct x)))

#define next(a) ((*++*(a))?*(a):(*++(a)?*(a):(char *)usage()))

main(argc, argv)
register char **argv;
{
	register struct file *fp;
	register char **ap;

	while (*++argv && **argv == '-') {
		switch (*++*argv) {
			case 'd':
				dflag++;
				break;
			case 'I':
				*dirp++ = next(argv);
				break;
			case 'f':
				outfile = next(argv);
				break;
			case 'n':
				nflag++;
				break;
			default:
				usage();
		}
	}
	if (*argv == 0)
		usage();
	*dirp++ = "/usr/include";
	for (ap = argv; *ap; ap++) {
		fp = addfile(*ap);
		ftype(fp);
		dofile(fp);
	}
	for (fp = fhead; fp; fp = fp->f_link)
		if (!fp->f_done)
			dofile(fp);
	if (outfile) {
		if ((outfp = fopen(outfile, "a")) == 0) {
			perror(outfile);
			exit(1);
		}
	} else
		outfp = stdout;
	for (ap = argv; *ap; ap++)
		printfile(findfile(*ap));
	fclose(outfp);
}

int lineno;

dofile(fp)
register struct file *fp;
{
	register FILE *f;

	if (dflag)
		printf("dofile: fp=%x fp->f_name=%s(%x)\n",
			fp, fp->f_name, fp->f_name);
	if ((f = fopen(fp->f_name, "r")) == 0) {
		perror(fp->f_name);
		goto out;
	}
	lineno = 1;
	if (fp->f_type == T_FORTRAN)
		dofort(fp, f);
	else
		doC(fp, f);
	fclose(f);
out:
	fp->f_done = 1;
}

doC(fp, f)
register struct file *fp;
register FILE *f;
{
	register c;
	register char *p, *q;

	while ((c = getc(f)) != EOF) {
		if (c != MAGIC)
			goto nextline;
		if (skip(f) < 0)
			continue;
		for (p = "include";
			*p && (c = getc(f)) == *p;
			p++)
			;
		if (*p != 0)
			goto nextline;
		if (skip(f) < 0)
			continue;
		c = getc(f);
		if (c == '<' && nflag)
			goto nextline;
		if (c != '<' && c != '"') {
			fprintf(stderr,
				"%s, %d: bad include syntax\n",
				fp->f_name, lineno);
			goto nextline;
		}
		if ((p = getstr(f, c=='<'?'>':'"')) == 0) {
			fprintf(stderr, "%s, %d: bad include syntax\n",
				fp->f_name, lineno);
			goto nextline;
		}
		if ((q = expand(p, c=='<'?0:1)) == 0)
			fprintf(stderr, "%s, %d: %c%s%c %s\n",
				fp->f_name, lineno, c, p, c=='<'?'>':'"',
				sys_errlist[errno]);
		else
			adddep(fp, q);
nextline:
		while (c != '\n' && c != EOF)
			c = getc(f);
		lineno++;
	}
}

dofort(fp, f)
register struct file *fp;
register FILE *f;
{
	register c;
	register char *p, *q;
	register i;

	while ((c = getc(f)) != EOF) {
		if (c == 'c' || c == 'C' || c == '\n' || c == '&')
			goto nextline;
		for (i = 0;
			i < 5 && c != EOF && c != '\n' && c != '\t';
			i++, c = getc(f))
			;
		if (c == EOF || c == '\n')
			goto nextline;
		if (c != '\t' && c != ' ')
			goto nextline;
		for (c = getc(f); c == ' ' || c == '\t'; c = getc(f))
			;
		for (p = "include"; *p && Tolower(c) == *p; p++, c = getc(f))
			;
		if (*p)
			goto nextline;
		for (; c == ' ' || c == '\t'; c = getc(f))
			;
		if (c != '\'' && c != '"' || (p = getstr(f, c)) == 0) {
			fprintf(stderr, "%s, %d: bad include syntax\n",
				fp->f_name, lineno);
			goto nextline;
		}
		if ((q = try("", p)) == 0)
			fprintf(stderr, "%s, %d: '%s' %s\n",
				fp->f_name, lineno, p, sys_errlist[errno]);
		else
			adddep(fp, q);
nextline:
		while (c != '\n' && c != EOF)
			c = getc(f);
		lineno++;
	}
}

struct dep *dhead, *dtail;

printfile(fp)
struct file *fp;
{
	register struct dep *dp;
	register col, n;

	if (fp->f_dhead == 0)
		return;
	dhead = dtail = 0;
	pf0(fp);
	fprintf(outfp, "%s:", fp->f_xname);
	col = strlen(fp->f_name) + 1;
	for (dp = dhead; dp; dp = dp->d_link) {
		n = strlen(dp->d_file->f_name);
		col += n + 1;
		if (col >= 78) {
			fprintf(outfp, " \\\n\t%s", dp->d_file->f_name);
			col = n + 8;
		} else
			fprintf(outfp, " %s", dp->d_file->f_name);
		free(dp);
	}
	putc('\n', outfp);
}

pf0(fp)
register struct file *fp;
{
	register struct dep *dp, *dq;

	if (dflag)
		printf("pf0: fp=%x fp->f_name=%s(%x)\n",
			fp, fp->f_name, fp->f_name);
	for (dp = fp->f_dhead; dp; dp = dp->d_link) {
		fp = dp->d_file;
		for (dq = dhead; dq && dq->d_file != fp; dq = dq->d_link)
			;
		if (dq == 0) {
			dq = ALLOC(dep);
			dq->d_file = fp;
			if (dtail)
				dtail->d_link = dq;
			else
				dhead = dq;
			dtail = dq;
			if (dflag)
				printf("pf0: adding %s\n", fp->f_name);
			pf0(fp);
		}
	}
}

ftype(fp)
register struct file *fp;
{
	register char *p, *q;

	for (p = fp->f_name, q = 0; *p; p++)
		if (*p == '.')
			q = p + 1;
	if (q) {
		if (strcmp(q, "c") == 0 || strcmp(q, "h") == 0)
			fp->f_type = T_C;
		else if (strcmp(q, "p") == 0 || strcmp(q, "i") == 0)
			fp->f_type = T_PASCAL;
		else if (strcmp(q, "f") == 0)
			fp->f_type = T_FORTRAN;
		else
			fp->f_type = T_UNKNOWN;
	} else
		fp->f_type = T_UNKNOWN;
	if (fp->f_type != T_UNKNOWN) {
		fp->f_xname = alloc(q - fp->f_name + 2);
		p = fp->f_xname + (q - fp->f_name);
		*p-- = 'o';
		for (q--; q >= fp->f_name;)
			*p-- = *q--;
	} else
		fp->f_xname = fp->f_name;
	if (dflag)
		printf("ftype: name=%s type=%s xname=%s\n",
			fp->f_name, typename[fp->f_type], fp->f_xname);
}

skip(f)
register FILE *f;
{
	register quote = 0, comment = 0;
	register c;

	for (;;) {
		c = getc(f);
		if (c == EOF)
			return -1;
		else if (c == '\n') {
			if (!quote || comment != 2) {
				ungetc(c, f);
				return -1;
			}
			lineno++;
		} else if (c == '/') {
			if (comment == 0)
				comment++;
			else if (comment == 3)
				comment = 0;
		} else if (c == '*') {
			if (comment == 1 || comment == 2)
				comment++;
		} else if (c != ' ' && c != '\t') {
			if (comment != 2) {
				ungetc(c, f);
				return 0;
			}
		}
		if (c == '\\')
			quote = !quote;
		else
			quote = 0;
	}
}

char *
getstr(f, t)
register FILE *f;
register t;
{
	static char buf[BUFSIZ];
	register char *p;
	register c;

	for (p = buf; (c = getc(f)) != EOF && c != '\n' && c != t; p++)
		*p = c;
	*p = 0;
	if (c != t) {
		ungetc(c, f);
		return 0;
	} else
		return buf;
}

char *
expand(s, flag)
char *s;
{
	register char *p, **dp;

	if (!flag) {
		if ((p = try("", s)) != 0)
			return p;
	}
	for (dp = dirs; *dp; dp++)
		if ((p = try(*dp, s)) != 0)
			return p;
	if (flag) {
		if ((p = try("", s)) != 0)
			return p;
	}
	return 0;
}

char *
try(a, b)
register char *a, *b;
{
	char *buf;
	register char *p;

	buf = p = alloc(strlen(a) + strlen(b) + 2);
	if (a) {
		while (*p++ = *a++)
			;
		p[-1] = '/';
	}
	while (*p++ = *b++)
		;
	if (access(buf, 4) >= 0)
		return buf;
	else {
		free(buf);
		return 0;
	}
}

adddep(fp, name)
register struct file *fp;
char *name;
{
	register struct file *fq;
	register struct dep *dp;

	fq = addfile(name);
	if (fq->f_type != T_UNKNOWN && fp->f_type != fq->f_type)
		fprintf(stderr, "%s, %d: include file type conflict (%s)\n",
			fp->f_name, lineno, name);
	else
		fq->f_type = fp->f_type;
	for (dp = fp->f_dhead; dp && dp->d_file != fq; dp = dp->d_link)
		;
	if (dp == 0) {
		dp = ALLOC(dep);
		dp->d_file = fq;
		if (fp->f_dtail)
			fp->f_dtail->d_link = dp;
		else
			fp->f_dhead = dp;
		fp->f_dtail = dp;
	}
}

struct file *
addfile(name)
char *name;
{
	register struct file *fp;
	int h;

	if ((fp = findfile(name)) == 0) {
		fp = ALLOC(file);
		fp->f_name = name;
		if (ftail)
			ftail->f_link = fp;
		else
			fhead = fp;
		ftail = fp;
		h = hash(name);
		fp->f_hlink = ftab[h];
		ftab[h] = fp;
		if (dflag)
			printf("addfile: name=%s(%x) fp=%x h=%d\n",
				name, name, fp, h);
	}
	return fp;
}

struct file *
findfile(name)
register char *name;
{
	register struct file *fp;

	
	for (fp = ftab[hash(name)];
		fp && strcmp(name, fp->f_name);
		fp = fp->f_hlink)
		;
	return fp;
}

hash(name)
register char *name;
{
	register sum;

	for (sum = 0; *name; sum += *name++)
		;
	return sum % HASHSIZE;
}

char *
alloc(s)
{
	char *a, *aa, *calloc();
	int x;

	if (!(a = calloc(s, 1))) {
		fprintf(stderr, "Out of Memory\n");
		exit(1);
	}
	aa = a;
	for (x = 0; x < s; x++, aa++) *aa = '\0';
	return a;
}

usage()
{
	fprintf(stderr, "mkdep: [-n] [-I dir] [-f file] files ...\n");
	exit(1);
}
