/*{{{ 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