/*{{{  proc-incl.c*/
/*----------------------------------------------------------------------*/
/* Usage:								*/
/* proc-incl  [-D include-dictionary-file]  [-I include-dir]... [file]	*/
/*----------------------------------------------------------------------*/
/*{{{  global decls*/
#include <stdio.h>
#include <sys/types.h>		/* for 'stat ()' */
#include <sys/stat.h>		/*  "     "    " */

#define  BUFLEN			1000	/* max len of ID or keyword in i/p */
#define  PROTECT_LINE_CHAR	'@'	/* replaces some '#include's */
#define  TEMP_FILE_PREFIX	"tmp_h"	/* replacement 'include' files */

typedef  int  bool;

#define  FALSE	0
#define  TRUE	1

typedef struct Incl_file {
	char  *filename;
	int    val;
	int    processed;	/* a char: u == user incl file (unprocessed) */
				/*         p ==   "    "    "   (processed)  */
	struct Incl_file  *next;
	struct Incl_file  *next_file;
} Incl_file;

#define  UNPROCESSED_USR_FILE	'u'
#define  PROCESSED_USR_FILE	'p'

typedef  struct Dir_list {
	char  *dir;
	struct Dir_list  *next;
} Dir_list;

Dir_list  *std_incl_list,  *usr_incl_list;

char  *current_dir = NULL,  *ip_filename = NULL;
char  *std_incl_dirs[] = {"/usr/include",  0};      /* '0' is a sentinel */

#define  HASHSIZE  101

char  buf [BUFLEN];
Incl_file  *filetab [HASHSIZE],  *first_file,  *last_file;

int  next_val;

char  *incl_dict_filename = "tmp_incl_dict";	/* 'file dictionary' filename */

bool    file_exists (),  find_file (),  ordinary_file (),
        replace_filename_by_tmp ();
unsigned   hash ();
int    fileval ();
/*}}}*/

/*{{{  main (argc, argv) -- OK*/
main (argc, argv)
/*-------------*/
	int  argc;
	char  *argv [];
/*----------------------------------------------------------------------*/
/* Usage:								*/
/* proc-incl  [-D include-dictionary-file]  [-I include-dir]... [file]	*/
/*----------------------------------------------------------------------*/
{
	char  *progname = argv [0];
	char  **pp;
	Dir_list  *dp,  *last_dp;

	/*{{{  initialise 'std_incl_list'*/
	/*------------------------------*/
	/* Initialise 'std_incl_list'	*/
	/*------------------------------*/
	for  (pp = std_incl_dirs,  last_dp = NULL;    *pp;    pp++,  last_dp = dp) {
	
		dp = (Dir_list *) malloc (sizeof (Dir_list));
		dp->dir = *pp;
		dp->next = NULL;
	
		if (last_dp)
			last_dp->next = dp;
		else
			std_incl_list = dp;
	}
	/*}}}*/
	
	/*{{{  process command line options (starting '-')*/
	/*--------------------------------------------------------------*/
	/* Command line options, viz:					*/
	/*	[-D include-dictionary-file]  [-I include-dir]...	*/
	/*--------------------------------------------------------------*/
	while (--argc && *(*++argv) == '-') {
		Dir_list  *dp;
		int  c;
		
		switch (c = *++(*argv)) {
		case 'D' :
			/*{{{  'incl_dict_filename' (default = "tmp_dict")*/
			/*--------------------------------------------------------------*/
			/* -D option:  should be followed (with or without an space)	*/
			/* by the filename of the 'dictionary of include files',	*/
			/* ('incl_dict_filename').					*/
			/*--------------------------------------------------------------*/
			
			/*--------------------------------------------------------------*/
			/* set '*argv' to point to the start of the filename (which is	*/
			/* either the next character or the next word).			*/
			/*--------------------------------------------------------------*/
			if (*++(*argv))
				/*------------------------------------------------------*/
				/* filename follows -D immediately (without a space)	*/
				/*------------------------------------------------------*/
				;
			
			else if ( ! --argc || **++argv == '-') {
				fputs ("Expect a filename after the \'-D\' option.\n", stderr);
				goto  abort;
			}
			
			incl_dict_filename = *argv;
			
			break;
			
			/*}}}*/
	
		case 'I' :
			/*{{{  include directory: add to end 'usr_incl_list' list*/
			/*--------------------------------------------------------------*/
			/* -I option:  should be followed (with or without an space)	*/
			/* by a directory name, which is added to the end of the list	*/
			/* of 'usr_incl_list'. 						*/
			/*--------------------------------------------------------------*/
			
			/*--------------------------------------------------------------*/
			/* set '*argv' to point to the start of the directory name	*/
			/* (which is either the next character or the next word)	*/
			/*--------------------------------------------------------------*/
			if (*++(*argv))
				/*------------------------------------------------------*/
				/* directory follows -I immediately (without a space)	*/
				/*------------------------------------------------------*/
				;
			
			else if ( ! --argc || **++argv == '-') {
				fputs ("Expect a directory name after the \'-I\' option.\n", stderr);
				goto  abort;
			}
			
			/*--------------------------------------------------------------*/
			/* add directory name to the end of the list 'usr_incl_dir'	*/
			/*--------------------------------------------------------------*/
			dp = (Dir_list *) malloc (sizeof (Dir_list));
			dp->dir = *argv;
			dp->next = NULL;
			
			if (! usr_incl_list)
				usr_incl_list = dp;
			
			else {
				Dir_list  *ndp;
			
				for (ndp = usr_incl_list;  ndp->next;  ndp = ndp->next)
					;
				ndp->next = dp;
			}
			
			break;
			/*}}}*/
	
		case '\0' :
			fputs ("Expected an option letter after \'-\'.\n", stderr);
			goto  abort;
	
		default :
			fprintf (stderr, "Illegal option \'-%c\'.\n", c);
			goto  abort;
		}
	}
	/*}}}*/

	/*{{{  if given, read & open 'ip_filename' & extract its directory path*/
	if (argc) {
		/*--------------------------------------*/
		/* Read and open the 'ip_filename'.	*/
		/*--------------------------------------*/
		char  *dirsep_p,  *p;
	
		ip_filename = *argv;
		
		if ( ! freopen (ip_filename, "r", stdin) ) {
			fprintf (stderr, "Cannot open file \"%s\".\n", ip_filename);
			exit (1);
		}
		
		/*------------------------------------------------------*/
		/* If the 'ip_filename' includes a directory path, set	*/
		/* the global variable 'current_dir' to point to it.	*/
		/* (This will be the first directory that is searched	*/
		/* for "..." -delimited 'include' files.  If		*/
		/* 'current_dir' isn't set here, its default is ".").	*/
		/*------------------------------------------------------*/
		for (p = ip_filename,  dirsep_p = NULL;   *p;   p++)
			if (*p == '/')
				dirsep_p = p;
	
		if (dirsep_p) {
			*dirsep_p = '\0';	/* ...so '*ip_filename' is just the directory */
	
			current_dir = (char *) malloc (strlen (ip_filename) + 1);
			strcpy (current_dir, ip_filename);
	
			*dirsep_p = '/';	/* restore 'ip_filename' */
		}
		
		argc--;
		argv++;
	}
	/*}}}*/
	
	if (argc) {
		fprintf (stderr, "%s: unexpected argument: \'%s\'.\n",
		                  progname, *argv);
		goto  abort;
	}

	read_old_dict ();
	translate_file ();
	write_new_dict ();
	exit (0);	/* successful execution! */
	
abort :
	fprintf (stderr, "Usage: %s  [-I include-dir]...  [-D include-dictionary-file]  [input-file]\n",
	                  progname);
	exit (1);
}
/*}}}*/

/*{{{  << 'file dictionary' I/O >> -- OK*/
/*{{{  read_old_dict () -- OK*/
read_old_dict ()
/*------------*/
{
	register  char  *p;
	char  *filename;
	FILE  *dict_file;
	int    processed,  val;

	if ( !(dict_file = fopen (incl_dict_filename, "r")) )
		return;

	next_val = 0;
	while (fgets (buf, BUFLEN, dict_file) ) {
		/*{{{  read (filename, val, processed) & install in 'filetab'*/
		/*--------------------------------------------------------------*/
		/* Read the (filename, processed) record from 'buf []', and	*/
		/* install it in the filename table.				*/
		/*								*/
		/* N.B.  The line's syntax *isn't* checked.			*/
		/*--------------------------------------------------------------*/
		
		/*------------------------------*/
		/* Read the 'processed' field	*/
		/*------------------------------*/
		p = buf;
		
		processed = *p;		/* a char: 'u' or 'p'; see definition of 'Incl_file' */
		
		filename = (p += 2);	/* skip over '\t' to start of 'filename' field */
		
		/*---------------*/
		/* Set the 'val' */
		/*---------------*/
		val = ++next_val;
		
		/*--------------------------------------------------------------*/
		/* Now '*p' should be a 'filename', terminated by '\n'.  (Even	*/
		/* if there are blank chars before the '\n', they are still	*/
		/* taken to be part of the filename! )				*/
		/*	We only have to replace the terminating '\n' by '\0',	*/
		/* so '*p' can be used as a string.				*/
		/*								*/
		/* N.B. We don't check for the possibility that the 'buf' is	*/
		/* too small for the whole filename (indicated by '\0' being	*/
		/* found at the end of 'buf', without a prior '\n').  We assume	*/
		/* that BUFLEN (1000 chars) is big enough!!			*/
		/*--------------------------------------------------------------*/
		while (*++p != '\n');	/* skip over 'filename' to '\n' */
		
		*p = '\0';		/* terminate 'filename' */
		
		/*----------------------------------------------*/
		/* Install 'filename' in the filename table.	*/
		/*----------------------------------------------*/
		install_filename (filename, val, processed);
		/*}}}*/
	}

	fclose (dict_file);
	return;
}
/*}}}*/
/*{{{  write_new_dict () -- OK*/
write_new_dict ()
/*-------------*/
{
	FILE   *dict_file;
	Incl_file *sp;

	if ( ! (dict_file = fopen (incl_dict_filename, "w")) )
		fprintf (stderr, "*** Error: can\'t open \"%s\" for writing.\n\n",
		                 incl_dict_filename);

	/*------------------------------------------------------*/	
	/* Output all filenames with their 'processed' values.	*/
	/*------------------------------------------------------*/
	for (sp = first_file;  sp;  sp = sp->next_file)	
		fprintf (dict_file, "%c\t%s\n", sp->processed, sp->filename);

	fclose (dict_file);
}
/*}}}*/
/*}}}*/

/*{{{  translate_file () -- OK*/
translate_file ()
/*-------------*/
/*----------------------------------------------------------------
	This function copies the input C file to output, performing
the following transformations:

-- delete comments & splice lines terminated by '\\';
-- replace whitespace sequences by a single blank;
-- transform '#include' lines as follows:
	#include  <filename>      } -- protect the '#include' from 'cpp'
	#include  token-sequence  } by putting an '@' at start-of-line.

	#include  "filename"      -- if filename is in a directory
		in the 'std_incl_list', protect '#include' as above;
		otherwise, change 'filename' to, e.g., "tmp_xx".
----------------------------------------------------------------*/
{
	/*{{{  decls*/
	bool  start_of_line;
	register  int  c;
	int  delim;
	/*}}}*/

	start_of_line = TRUE;
	c = getchar ();
	while (c != EOF) {
		switch (c) {
		case  '\'' : case '\"' :
			/*{{{  start of a char const or string:  copy out*/
			/*--------------------------------------------------------------*/
			/* A character constant or string.  Copy it straight to output.	*/
			/*--------------------------------------------------------------*/
			delim = c;
			
			putchar (delim);   start_of_line = FALSE;
			
			while ((c = getchar ()) != delim) {
				switch (c) {
				case  EOF :
					return;
			
				case  '\\' :
					/*---------------------------------------*/
					/* copy this & next char unconditionally */
					/*---------------------------------------*/
					putchar (c);
					if ((c = getchar ()) == EOF)
						return;
					putchar (c);
					break;
			
				default :
					putchar (c);
					break;
				}
			}
			putchar (delim);
			c = getchar ();
			
			break;
			/*}}}*/

		case  '/' :
			/*{{{  if start of a comment, replace comment by ' '*/
			/*--------------------------------------------------------------*/
			/* '/' may be the start of a comment.  If so, replace the whole	*/
			/* comment by a single space.					*/
			/*								*/
			/* N.B. Comments don't change the status of 'start_of_line'	*/
			/* (since they're equivalent to whitespace!).			*/
			/*--------------------------------------------------------------*/
			if ((c = getchar ()) == '*') {
				/*-----------------*/
				/* It's a comment! */
				/*-----------------*/
				bool  in_comment;
			
				if (!start_of_line)
					putchar (' ');		/* replace comment by a single ' ' */
				in_comment = TRUE;
				do {
					switch (getchar ()) {
					case  EOF :
						return;
					case  '*' :
						switch (c = getchar ()) {
						case  EOF :
							return;
						case  '/' :
							in_comment = FALSE;
							break;
						default :
							ungetc (c, stdin);
							break;
						}
						break;
					}
				} while (in_comment);
				c = getchar ();
			}
			else {
				/*----------------------------------------------*/
				/* Not the start of a comment -- output the '/'	*/
				/*----------------------------------------------*/
				putchar ('/');   start_of_line = FALSE;
			}
			
			break;
			/*}}}*/

		case  '#' :
			if (start_of_line) {
				/*{{{  process it if it's a '#include' line (see intro)*/
				/*--------------------------------------------------------------*/
				/* If this is a '#include' line, transform it as follows:	*/
				/*								*/
				/* #include  <filename>      } -- protect '#include' from 'cpp'	*/
				/* #include  token-sequence  } by putting '@' at start-of-line.	*/
				/* #include  "filename"		-- if filename is:		*/
				/*		    in a directory in the 'std_incl_list';	*/
				/*		OR  not found;					*/
				/*		OR  not terminated by " (but by by '\n' or EOF)	*/
				/*	protect '#include' as above;				*/
				/*	otherwise, change 'filename' to, e.g., "tmp_xx".	*/
				/*								*/
				/* N.B. Whitespace preceding the '#' is removed, as is *all*	*/
				/* whitespace at the start of a line!				*/ 
				/*--------------------------------------------------------------*/
				char  *filename,  *p,  *word;
				
				start_of_line = FALSE;
				
				p = buf;
				*p++ = '#';
				
				/*{{{  copy whitespace to 'buf []'*/
				/*-----------------------------*/
				/* copy whitespace to 'buf []' */
				/*-----------------------------*/
				while ((c = getchar ()) == ' ' || c == '\t' || c == '\\') {
					if (c == '\\') {
						if ((c = getchar ()) != '\n') {
							*p++ = '\\';
							goto  copy_out;
						}
					}
					else
						*p++ = c;
				}
				/*}}}*/
				
				/*{{{  read next word.  If not "include", copy out and break*/
				if  (c != 'i')
					/*-----------------------------*/
					/* Not "# include" -- copy out */
					/*-----------------------------*/
					goto  copy_out;
				
				/*--------------------------------------------------------------*/
				/* Copy the next word to 'buf'.					*/
				/*								*/
				/* N.B. Though we expect only lower case letters (e.g. "ifdef",	*/
				/* "include", etc), we test for all characters allowed in an	*/
				/* identifier.  This avoids the (unlikely) case of:		*/
				/*	#include23						*/
				/* being replaced by:						*/
				/*	@ 23							*/
				/* and thence back to "#include 23" -- i.e. inserting a space	*/
				/* in an (admittedly misplaced) identifier!			*/
				/*--------------------------------------------------------------*/
				word = p;		/* start of word after "#[whitespace]" */
				do {
					if (c == '\\') {
						if ((c = getchar ()) != '\n') {
							*p++ = '\\';
							goto  copy_out;
						}
					}
					else
						*p++ = c;
					c = getchar ();
				} while (isalnum (c) || c == '_' || c == '\\');
				*p = '\0';
				
				if (strcmp (word, "include"))
					/*-----------------------------*/
					/* Not "# include" -- copy out */
					/*-----------------------------*/
					goto  copy_out;
				/*}}}*/
				
				/*{{{  copy whitespace to 'buf []'*/
				/*--------------------------------------------------------------*/
				/* Copy whitespace to 'buf []'.					*/
				/*								*/
				/* N.B. If we find a '\\' not followed by a '\n', we output a	*/
				/* '@' at the start-of-line to protect the '#include' -- i.e.	*/
				/* we treat it like any "#include" not followed by a `\"`!	*/
				/*--------------------------------------------------------------*/
				while (c == ' ' || c == '\t' || c == '\\') {
					if (c == '\\') {
						if ((c = getchar ()) != '\n') {
							*p++ = '\\';
							putchar (PROTECT_LINE_CHAR);
							goto  copy_out;
						}
					}
					else
						*p++ = c;
					c = getchar ();
				}
				/*}}}*/
				
				if (c != '\"') {
					/*{{{  #include <...> | token-seq | err;  o/p "@" & 'buf', and break*/
					/*--------------------------------------------------------------*/
					/* The line is either:						*/
					/*								*/
					/*		#include  <...>					*/
					/*	or	#include  token-sequence			*/
					/*	or	#include  syntax-error				*/
					/*								*/
					/* Whichever it is, put '@' at the start-of-line to protect the	*/
					/* '#include', so the 'include' file isn't expanded by 'cpp'.	*/
					/* (The '@' will subsequently be removed by 'ctran', exposing	*/
					/* the '#include' again!)					*/
					/*--------------------------------------------------------------*/
					putchar (PROTECT_LINE_CHAR);
					goto  copy_out;
					/*}}}*/
				}
				
				*p++ = c;		/* copy the '\"' to 'buf[]' */
				filename = p;		/* start of filename */
				
				/*{{{  read the rest of the filename into 'buf[]'*/
				/*--------------------------------------------------------------
				Read the rest of the filename, up to but excluding the final '\"',
				into 'buf []'.
				
				Notes:
				------
				(1) This isn't really a C string, but rather a character-for-
				character copy of a filename (though the C standard says nothing
				about this!).  Hence:
				-- we don't treat '\\' as an escape character, except to escape '\n';
				-- we terminate the filename if '\n' (unescaped) is seen before
					'\"' -- otherwise a missing '\"' may result in a
					ridiculously long filename!
				
				(2) If an error occurs (i.e. the string isn't terminated by '\"',
				but rather by '\n' or EOF), we give an errmsg and replace "#include"
				by "@" in the o/p  (rather than 'copying out' the input), so 'cmunge'
				tries neither to find nor include the file.   "#include" will be
				restored at the end of 'cmunge', so the o/p file will have exactly
				the same error as the input one!
				--------------------------------------------------------------*/
				while ((c = getchar ()) != '\"'  &&  c != '\n'  &&  c != EOF) {
					switch (c) {
					case  '\\' :
						/*----------------------------------------------*/
						/* if followed by '\n', splice lines (i.e. skip	*/
						/* this char and the '\n'),  otherwise copy the	*/
						/* '\\' to 'buf' (but don't treat it as an	*/
						/* escape character).				*/
						/*----------------------------------------------*/
						if ((c = getchar ()) == '\n')
							break;
							
						*p++ = '\\';
						if (c == EOF) {
							*p = '\0';
							goto  filename_error;
						}
						else {
							ungetc (c, stdin);
							break;
						}
				
					default :
						*p++ = c;
						break;
					}
				}
				*p = '\0';	/* N.B. haven't put final '\"' in 'buf[]'! */
				
				/*}}}*/
				
				if (c != '\"')
					goto  filename_error;
				
				if ( ! replace_filename_by_tmp (filename))
					putchar (PROTECT_LINE_CHAR);	/* a 'system' or 'std' header file */
				
				printf ("%s\"", buf);
				c = getchar ();
				continue;		/* continue processing with last-read char 'c' */
				/*---------------------------------------------------------------*/
				/* the following labels are 'goto' targets from the above code...*/
				/*---------------------------------------------------------------*/
				copy_out :
					/*{{{  copy-out 'buf' & continue*/
					*p = '\0';
					fputs (buf, stdout);
					continue;		/* continue processing with last-read char 'c' */
					/*}}}*/
				
				filename_error :
					/*{{{  write errmsg (missing final "), output <@ "filename>, & continue*/
					
					fputs ("\n*** Error: \'include\' filename not terminated by \":\n", stderr);
					if (ip_filename)
						fprintf (stderr, "%s: ", ip_filename);
					fprintf (stderr, "%s\n", buf);
					
					putchar (PROTECT_LINE_CHAR);
					fputs (buf, stdout);
					continue;		/* outer loop deals with 'c' (== '\n' or EOF) */
					/*}}}*/
				/*}}}*/
			}
			else {
				putchar (c);
				c = getchar ();
			}
			break;
			
		case  ' ' :  case '\t' :
			/*{{{  o/p ' ', and skip subsequent whitespace*/
			if (!start_of_line)
				putchar (' ');
			
			while ((c = getchar ()) == ' ' || c == '\t')
				;			/* skip all consecutive whitespace */
			
			break;		/* continue normal processing with 'c'! */
			/*}}}*/

		case  '\n' :
			/*{{{  o/p '\n', and skip subsequent space & '\n's*/
			putchar ('\n');
			
			start_of_line = TRUE;
			
			while ((c = getchar ()) == ' ' || c == '\t' || c == '\n')
				;		/* skip consecutive whitespace and newlines */
			
			break;			/* continue normal processing with 'c'! */
			/*}}}*/

		case  '\\' :
			/*{{{  if at EOL, splice lines (i.e. skip this & '\n')*/
			/*--------------------------------------------------------------*/
			/* If the next char is '\n', splice this line and the next (i.e.*/
			/* skip the '\\' and '\n').  Otherwise, just output the '//'.	*/
			/*--------------------------------------------------------------*/
			if ((c = getchar ()) == '\n')
				c = getchar ();
			else {
				putchar ('\\');
				start_of_line = FALSE;
			}
			
			break;
			/*}}}*/
			
		default :
			/*{{{  copy-out 'c'*/
			putchar (c);   start_of_line = FALSE;
			c = getchar ();
			break;
			/*}}}*/
		}
	}
}
/*}}}*/

/*{{{  bool  replace_filename_by_tmp (filename) -- OK*/
bool  replace_filename_by_tmp (filename)
/*------------------------------------*/
	char  *filename;
{
	Dir_list  *dp;

	/*------------------------------------------------------*/
	/* don't replace 'filename' if file not found...	*/
	/*------------------------------------------------------*/
	if ( ! find_file (filename))		/* errmsg given */
		return  FALSE;
		
	/*------------------------------------------------------*/
	/* or if it's in a directory in the 'std_incl_list'	*/
	/* (nornally "/usr/include")...				*/
	/*------------------------------------------------------*/
	for (dp = std_incl_list;   dp;   dp = dp->next)
		if ( (strncmp (filename, dp->dir, strlen (dp->dir)) == 0)  &&
		     (*(filename + strlen (dp->dir)) == '/') )
			return  FALSE;

	/*-----------------------------------------*/
	/* Otherwise, replace it, and return TRUE! */
	/*-----------------------------------------*/
	sprintf (filename, "%s%d",  TEMP_FILE_PREFIX,  fileval (filename));

				/* N.B.  'fileval ()' installs 'filename'  */
				/* in 'filetab' if it isn't already there! */
					
	return  TRUE;
}
/*}}}*/
/*{{{  bool  find_file (filename) -- OK*/
bool  find_file (filename)
/*----------------------*/
	char  *filename;
{
	char  *p;
	Dir_list  *dp;
	int  filename_len,  dirname_len;


	if (*filename == '/'  &&  file_exists (filename))
		goto  found;

	filename_len = strlen (filename);

	/*{{{  look for 'filename' in the 'current directory'*/
	/*--------------------------------------------------------------*/
	/* look for 'filename' in the current directory			*/
	/* ('current_dir',  or "." if that is NULL).			*/
	/*--------------------------------------------------------------*/
	if (current_dir) {
		dirname_len = strlen (current_dir);
		p = (char *) malloc (dirname_len + filename_len + 2);
						/* +2 for '/' and '\0' */
		strcpy (p, current_dir);
		*(p + dirname_len) = '/';
		strcpy (p + dirname_len + 1, filename);
		
		if (file_exists (p)) {
			strcpy (filename, p);
			free (p);
			goto  found;
		}
		free (p);
	}
	else if (file_exists (filename))
		goto  found;
	/*}}}*/

	/*{{{  look for it in the directories in the 'usr_incl_list'*/
	/*--------------------------------------------------------------*/
	/* look for 'filename' in the 'include' directory(ies)		*/
	/* specified by '-I' arguments (if any).			*/
	/*--------------------------------------------------------------*/
	for (dp = usr_incl_list;   dp;   dp = dp->next) {
	
		dirname_len = strlen (dp->dir);
		p = (char *) malloc (dirname_len + filename_len + 2);
						/* +2 for '/' and '\0' */
		strcpy (p, dp->dir);
		*(p + dirname_len) = '/';
		strcpy (p + dirname_len + 1, filename);
		
		if (file_exists (p)) {
			strcpy (filename, p);
			free (p);
			goto  found;
		}
		free (p);
	}
	/*}}}*/

	/*{{{  look for it in the directories in the 'std_incl_list'*/
	/*--------------------------------------------------------------*/
	/* finally, look for 'filename' in the 'standard include'	*/
	/* directory(ies).						*/
	/*--------------------------------------------------------------*/
	for (dp = std_incl_list;   dp;   dp = dp->next) {
	
		dirname_len = strlen (dp->dir);
		p = (char *) malloc (dirname_len + filename_len + 2);
						/* +2 for '/' and '\0' */
		strcpy (p, dp->dir);
		*(p + dirname_len) = '/';
		strcpy (p + dirname_len + 1, filename);
		
		if (file_exists (p)) {
			strcpy (filename, p);
			free (p);
			goto  found;
		}
		free (p);
	}
	/*}}}*/

	
	/*------------------------------------------------------*/
	/* 'filename' not found;  give errmsg & return FALSE	*/
	/*------------------------------------------------------*/
	fputs ("\n*** Error: \'include\' file not found:\n", stderr);
	if (ip_filename)
		fprintf (stderr, "%s: ", ip_filename);
	fprintf (stderr, "#include \"%s\"\n", filename);

	return  FALSE;

found :
	/*------------------------------------------------------*/
	/* 'filename' found;  'simplify' it & return TRUE	*/
	/*------------------------------------------------------*/
	simplify_path (filename);
	return   ordinary_file (filename);	/* gives errmsg if not! */
}
/*}}}*/
/*{{{  simplify_path (path) -- OK*/
simplify_path (path)
/*----------------*/
	char  *path;
/*--------------------------------------------------------------*/
/*	Simplify the pathname in 'path' by the following	*/
/* replacements:						*/
/*	dir/..[/]	-->  delete				*/
/*	.[/]		-->  delete				*/
/*	//		-->  /					*/
/*								*/
/* N.B. In the 'simplified' path, all ".." components (if any)	*/
/* are at the start, and there are no "." components.		*/
/*--------------------------------------------------------------*/
{
	register char  *fr,  *to;
	bool   path_simplified = FALSE;
	int    c,  n_named_cmpnts = 0;

	fr = to = path;
	
	if (*fr == '/') {
		fr++;  to++;
	}

	while (*fr) {
		/*{{{  process the next path component*/
		/*--------------------------------------------------------------*/
		/* Process the next path component (P).				*/
		/*								*/
		/* N.B.  We define a 'path component' as *excluding* the	*/
		/* preceding '/' (if any), but including the following '/'	*/
		/* (if any).  At this point, 'fr' and 'to' point to the start	*/
		/* of a path component.						*/
		/*--------------------------------------------------------------*/
		
		if (*fr == '/') {
			/*{{{  P is "/" (i.e. there's a "//" in the pathname).  Skip the '/'.*/
			/*--------------------------------------------------------------*/
			/* P is "/"  (i.e. there's a "//" in the pathname!).  Skip it!	*/
			/*--------------------------------------------------------------*/
			path_simplified = TRUE;
			fr++;
			continue;		/* do next field */
			/*}}}*/
		}
		else if (*fr != '.') {
			/*{{{  P isn't "." or "..", so copy it*/
			/*------------------------------------------------------*/
			/* P is named  (i.e. not "." or  "..").  Copy it.	*/
			/*------------------------------------------------------*/
			n_named_cmpnts++;
			goto  copy_compnt;
			/*}}}*/
		}
		
		switch (*(fr+1)) {
		case '.' :
			c = *(fr+2);			/* the char after ".." */
			if (c  &&  c != '/') {
				/*{{{  P isn't "." or ".." (though starting ".."), so copy it. */
				/*--------------------------------------------------------------*/
				/* P isn't "." or ".." (though it starts ".."), so copy it.	*/
				/*--------------------------------------------------------------*/
				n_named_cmpnts++;
				goto  copy_compnt;
				/*}}}*/
			}
		
			if ( ! n_named_cmpnts) {
				/*{{{  "..[/]", but no prev component to cancel with, so copy it*/
				/*--------------------------------------------------------------*/
				/* P ==  "..[/]",  but either there isn't a preceding path	*/
				/* component or it's also "..", so we can't cancel P with it.	*/
				/* Hence copy P.						*/
				/*--------------------------------------------------------------*/
				goto  copy_compnt;
				/*}}}*/
			}
		
			/*{{{  P is "..[/]":  cancel it with the prev (named) path component*/
			/*------------------------------------------------------------------
			P == "..[/]".  Cancel it with the previous (named) path component
			(i.e. decrement 'to' to point to its start).
			
			N.B. The precise rules for what to cancel are as follows (where
			<EOP> is 'end of path',  <BOP> is 'beginning of path',  and <eps>
			means an 'empty pathname' -- which is replaced by "." at the end):
			
			    P		preceded by	cancel			viz.
			    -		-----------	------			----
									{      dir/../  ->  <eps>
			dir/../		anything	P		{     /dir/../  ->  /
									{ path/dir/../  ->  path/
			
					{ <BOP>		P		        dir/..  ->  <eps>
			dir/.. <EOP>	{ /		P		       /dir/..  ->  /
					{ path/        (P & preceding /)   path/dir/..  ->  path
			
			Notice the complication of the last case (which is handled by the
			'if' statement below).
			------------------------------------------------------------------*/
			
			path_simplified = TRUE;
			n_named_cmpnts--;
			
			for (to -= 2;   to > path && *(to-1) != '/';   to--);
			
			fr += (c ? 3 : 2);	/* points to '\0' or the start of next field */
			
			if ( !c  &&  to > (path+1))
				to--;		/* points to preceding '/' (to be replaced by '\0') */
			
			continue;		/* do next field */
			/*}}}*/
		
		case '/' :
			/*{{{  P is "./":  skip it*/
			/*------------------------------*/
			/* P == "./", so skip it.	*/
			/*------------------------------*/
			path_simplified = TRUE;
			fr += 2;		/* points to start of next field */
			continue;		/* do next field */
			/*}}}*/
		
		case '\0' :
			/*{{{  P is "." (at the end of the path):  skip it*/
			/*--------------------------------------------------------------*/
			/* P == "." (at end of path), so skip it.			*/
			/*								*/
			/* N.B. For an explanation of what to do with an immediately	*/
			/* preceding '/', see the discussion before the code for "..[/]"*/
			/*--------------------------------------------------------------*/
			path_simplified = TRUE;
			fr++;			/* points to the '\0' */
			
			if (to > (path+1))
				to--;		/* points to preceding '/' (to be replaced by '\0') */
			
			continue;		/* i.e. quit 'while' loop */
			/*}}}*/
		
		default :
			/*{{{  P isn't "." or ".." (though starting "."), so copy it*/
			/*--------------------------------------------------------------*/
			/* P isn't "." or ".." (though it starts "."), so copy it.	*/
			/*--------------------------------------------------------------*/
			n_named_cmpnts++;
			break;
			/*}}}*/
		}
		
		copy_compnt :
		
		if (path_simplified) {
			/*{{{  copy 'from' field (up to '/', if any) to 'to' field*/
			/*--------------------------------------------------------------*/
			/* Copy the 'from' field (up to and including a '/', if any) to	*/
			/* the 'to' field.						*/
			/*--------------------------------------------------------------*/
			while (*fr  &&  *fr != '/')
				*to++ = *fr++;
			if (*fr)
				*to++ = *fr++;		/* copy the '/' */
			/*}}}*/
		}
		else {
			/*{{{  as above , but just increment pointers 'fr' & 'to' (as f r== to)*/
			/*--------------------------------------------------------------*/
			/* As above, but just increment the 'fr' and 'to' pointers,	*/
			/* rather than actually copying characters (since fr == to).	*/
			/*--------------------------------------------------------------*/
			while (*fr  &&  *fr != '/')
				fr++;
			if (*fr)
				fr++;
			to = fr;
			/*}}}*/
		}
		/*}}}*/
	}

	if (to == path)		/* (simplified) path is empty! */
		*to++ = '.';

	*to = '\0';
}
/*}}}*/

/*{{{  << 'filetab' handling functions >> -- OK*/
/*{{{  unsigned  hash (filename) -- OK*/
unsigned  hash (filename)
/*---------------------*/
	register char  *filename;
/*------------------------------------------------*/
/*    Compute hash value for string 'filename'    */
/*------------------------------------------------*/
{
	register unsigned  hashval;

	for (hashval = 0;   *filename;   filename++)
		hashval = *filename + 31 * hashval;

	return  hashval % HASHSIZE;
}
/*}}}*/

/*{{{  install_filename (filename, val, processed) -- OK*/
install_filename (filename, val, processed)
/*---------------------------------------*/
	char *filename;
	int   val;		/* > 0 */
	int   processed;	/* a char: 'x' == processed;  ' ' == not */
/*--------------------------------------------------------------*/
/*	Called by 'read_old_dict ()' to install a		*/
/* (filename, val, processed) record, as specified in the	*/
/* 'file dictionary', into 'filetab'.				*/
/*								*/
/* N.B. We assume that the 'filename' isn't already installed.	*/
/*--------------------------------------------------------------*/
{
	unsigned  hashval;
	register Incl_file  *sp;

	hashval = hash (filename);
	
	/*------------------------------------------------------*/
	/* make a new symbol table entry & initialise its 'val'	*/
	/*------------------------------------------------------*/
	sp = (Incl_file *) malloc (sizeof (Incl_file));
	sp->val = val;
	sp->processed = processed;
	
	/*-----------------------------------------------------------*/
	/* make a permanent copy of 'filename' and attach it to 'sp' */
	/*-----------------------------------------------------------*/
	sp->filename = (char *) malloc (strlen (filename) + 1);
						/* +1 for final '\0' */
	strcpy (sp->filename, filename);

	/*----------------------------------------------*/
	/*  link filetab entry into the hash table	*/
	/*----------------------------------------------*/
	sp->next = filetab [hashval];  /* add to front of list */
	filetab [hashval] = sp;

	/*------------------------------------------------------*/
	/*  link the entry to the end of the 'file linked list'	*/
	/*------------------------------------------------------*/
	sp->next_file = NULL;
	if (last_file)
		last_file = last_file->next_file = sp;
	else
		first_file = last_file = sp;	/* first entry! */
}
/*}}}*/
/*{{{  int  fileval (filename) -- OK*/
int  fileval (filename)
/*-------------------*/
	char  *filename;
{
	register Incl_file  *sp;
	int  val;
	unsigned  hashval = hash (filename);

	/*--------------------------------------------------------*/
	/* Search for 'filename' in the filename table, 'filetab' */
	/*--------------------------------------------------------*/
	for (sp = filetab [hashval];  sp;  sp = sp->next)
		if (! strcmp (sp->filename, filename))	/* found */
			return  sp->val;


	/*------------------------------------------------------*/
	/* Not found in the 'filetab', so install it.		*/
	/*------------------------------------------------------*/
	val = ++next_val;

	install_filename (filename, val, UNPROCESSED_USR_FILE);
						/* unprocessed as not	*/
						/* seen before!		*/
	return  val;
}
/*}}}*/



/*}}}*/

/*{{{  bool  file_exists (filename) -- OK*/
bool  file_exists (filename)
/*------------------------*/
	char  *filename;
/*--------------------------------------------------------------*/
/*	Returns 1 if 'filename' exists and is accessible (see	*/
/* below for the interpretation of this) and 0 otherwise.	*/
/*								*/
/* N.B. In the Unix implementation, this function returns 1	*/
/* only if the file exists *and* the directories leading to it	*/
/* can be searched (i.e. have 'x' permission).			*/
/*	Therefore, if any directory leading to the file *cannot**/
/* be searched the function returns 0, even if the file exists.	*/
/* However, in that case the given 'filename' cannot be opened,	*/
/* and therefore cannot be overwritten.				*/
/*	In any other implementation of this function, the	*/
/* important property that must be preserved is that, if the	*/
/* function returns 0, it must be guaranteed that an attempt to	*/
/* open 'filename' will not *delete* or *over-write* a		*/
/* pre-existing file with name 'filename'!			*/
/*								*/
/* N.B. System-dependent features:				*/
/* -------------------------------				*/
/* -- Uses the Unix system call 'access()'.			*/
/*--------------------------------------------------------------*/
{
	return  (access (filename, 0) == 0);
}
/*}}}*/
/*{{{  bool  ordinary_file (filename) -- OK*/
bool  ordinary_file (filename)
/*--------------------------*/
	char  *filename;
{
	struct stat  stat_buf;
	mode_t  file_type;
	
	if (stat (filename, &stat_buf))
		/*----------------------------------------------*/
		/* File not found.  Return FALSE.		*/
		/*						*/
		/* (This shouldn't happen, as this function is	*/
		/* only called after 'filename' has been found	*/
		/* by 'file_exists ()'!)			*/
		/*----------------------------------------------*/
		return  FALSE;

	file_type = stat_buf.st_mode & S_IFMT;
	
	if (file_type == S_IFREG  ||  file_type == S_IFLNK)
		/*----------------------------------------------*/
		/* Ordinary file or symbolic link.  Return TRUE.*/
		/*						*/
		/* N.B. I believe that 'stat' returns info	*/
		/* about the file pointed-to by a symbolic link,*/
		/* so it shouldn't be the latter!)		*/
		/*----------------------------------------------*/
		return  TRUE;

	/*------------------------------------------------------*/
	/* Not an ordinary file!  Give an errmsg & return FALSE.*/
	/*------------------------------------------------------*/
	fputs ("\n*** Error: \'include\' file isn't an ordinary file (it's a ",
	        stderr);

	switch (file_type) {
	case  S_IFDIR :		fputs ("directory", stderr);	break;
	case  S_IFCHR :	case  S_IFBLK :
				fputs ("device file", stderr);	break;
	case  S_IFSOCK :	fputs ("socket", stderr);	break;
	case  S_IFIFO :		fputs ("pipe", stderr);		break;
	}
	fputs ("!):\n", stderr);
	if (ip_filename)
		fprintf (stderr, "%s: ", ip_filename);
	fprintf (stderr, "#include \"%s\"\n", filename);

	return  FALSE;
}
/*}}}*/
/*}}}*/


syntax highlighted by Code2HTML, v. 0.9.1