/* Listing 1: */ 

#define EXPORT_ON
#define EXPORT_OFF

#define export
#define local	static

/* Listing 2: */

%{
/*
 *  c2hscan.l: lex input-file for header-file-generator c2h
 *
 *  Author: Stephan Mertens, sm@coconet.de
 *
 *  See file c2h.c for further copyright notes
 */
#include <string.h>
enum { NONE, EXPO, DECL, INIT }; /* states of finite automaton */
static int s = NONE;
static char lookaside[4096];     /* buffer */
%}

%%
^EXPORT_ON        switch (s) {
                  case NONE:
                       fputc ('\n', yyout);
                       s = EXPO;
                       break;
                  default:
                       lexerror ("unexpected keyword 'EXPORT_ON'");
                       break;
                  }
^EXPORT_OFF       switch (s) {
                  case EXPO:
                       fputc ('\n', yyout);
                       fputc ('\n', yyout);
                       s = NONE;
                       break;
                  default:
                       lexerror ("unexpected keyword 'EXPORT_OFF'");
                       break;
                  }
"/*"              switch (s) {
                  case EXPO:
                       fputs ("/*", yyout);
                       break;
                  default:
                       comment ("*/");
                       break;
                  }
export            switch (s) {
                  case NONE:
                       s = DECL;
                       strcpy (lookaside, "extern");
                       break;
                  case EXPO:
                       /* attention: "export" in EXPORT-regime */
                       fputs (yytext, yyout);
                       break;
                  default:
                       lexerror ("unexpected keyword 'export'");
                       break;
                  }
";"               switch (s) {
                  case DECL: case INIT:
                       s = NONE;
                       fputs (lookaside, yyout);
                       fputs (";\n\n", yyout);
                       break;
                  case EXPO:
                       fputc (';', yyout);
                       break;
                  default:
                       break;
                  }
(\n|\t|" ")*"="   switch (s) {
                  case DECL:
                       s = INIT;
                       break;
                  case EXPO:
                       fputs (yytext, yyout);
                       break;
                  default:
                       break;
                  }
"{"               switch (s) {
                  case INIT:
                       skipchrs ('{', '}');
                       break;
                  case EXPO:
                       fputc ('{', yyout);
                       break;
                  case DECL:
                       strcpy (lookaside, "");
                       s = NONE;
                       break;
                  default:
                       break;
                  }
","               switch (s) {
                  case INIT:
                       s = DECL;
                       /* fall through */
                  case DECL:
                       if (stradd (lookaside, ",", sizeof(lookaside)) != 0)
                         lexerror ("internal buffer overflow");
                       break;
                  case EXPO:
                       fputc (',', yyout);
                  default:
                       break;
                  }
"("               switch (s) {
                  case DECL:
                       s = NONE;
                       strcpy (lookaside, "");
                       break;
                  case INIT:
                       skipchrs ('(', ')');
                       break;
                  case EXPO:
                       fputc ('(', yyout);
                       break;
                  default:
                       break;
                  }
"["[^,^\n]*"]"    switch (s) {
                  case DECL:
                       treatarray();
                       break;
                  case EXPO:
                       fputs (yytext, yyout);
                       break;
                  default:
                       break;
                  }
\n|.              switch (s) {
                  case DECL:
                       if (stradd (lookaside, yytext, sizeof(lookaside)) != 0)
                         lexerror ("internal buffer overflow");
                       break;
                  case EXPO:
                       fputs (yytext, yyout);
                       break;
                  default:
                       break;
                  }
%%

#include <stdio.h>
#include <varargs.h>

extern int next_file (); /* must be provided by c2h */
extern char *filename;   /* dto. */

/* is called each time yylex encounters EOF */
int yywrap ()
{
   if (next_file () == -1) return 1;
   else return 0;
}

/* ignores input up to next occurence of 'match' */
comment (match)
   char *match;
{
   int c;
   char *c_p;
   for (c_p = match; *c_p != 0; ) {
      c = input();
      if (c == EOF) {
         lexerror("end of file in comment");
         break;
      }
      if (c != *c_p++) {
         c_p = match;
         if (c == *c_p)
            c_p++;
         }
      }
}

/* aborts printing error-message and -location */
lexerror(va_alist) va_dcl
{
   va_list va;
   char *fmt;
   va_start(va);
   fmt = va_arg(va, char *);
   if (yylineno)
      fprintf(stderr, "c2h: file %s line %d: ", filename, yylineno);
   vfprintf(stderr, fmt, va);
   fprintf(stderr, "\n");
   va_end(va);
   abort ();
}


/* ignores input up to next matching delimiter 'to' */
int skipchrs (from, to)
	char from, to;
{
	int count = 1;
	int c;
	while (count) {
		c = input();
		if (c == EOF) lexerror ("unexpected EOF encountered");
		if (c == from) ++count;
		if (c == to) --count;
	}
	return 1;
}

/* processes vector indices in declaration */
int treatarray ()
{
	char *c_p;
	if (stradd (lookaside, "[]", sizeof (lookaside)) != 0)
    lexerror ("internal buffer overflow");
	if (strrchr (yytext, '[') != yytext) {
		/* multidimensional array */
		c_p = strchr (yytext, ']') + 1;
    if (stradd (lookaside, c_p, sizeof (lookaside)) != 0)
      lexerror ("internal buffer overflow");
	};
	return 1;
}

/* appends 'tail' to 'head' if 'head+tail' will not exceed 'max_length'-1 */
int stradd (head, tail, max_length)
  char *head, *tail;
  size_t max_length;
{

  if (strlen (head) + strlen (tail) < max_length) {
    strcat (head, tail);
    return 0;
  }
  else return -1;
}

/* Listing 3: */

/*
 * c2h.c: a program to extract header-files from C-sources

 * Copyright (c) 1993 by CoCoNet GmbH Duesseldorf
 *
 * Author: Stephan Mertens, sm@coconet.de
 *
 * The only restrictions on the use of this program is that it is not
 * used for monetary gain, and that this copyright notice be left intact.
 *
 * This program may be freely used to extract code at
 * proprietary/corporate sites, as long as the actual source
 * is not used within any proprietary code.
 *
 * The generated output (header) files are distributable in any
 * manner desired as they are based on the scanned source code,
 * not this program.
 *
 * CoCoNet GmbH disclaims all warranties, expressed or implied, including
 * but not limited to, warranties of merchantability and fitness for a
 * particular purpose with respect to defects in the code or the
 * documentation. In no event shall CoCoNet GmbH be liable for any loss of
 * profit or any commercial damage, including but not limited to special,
 * incidental, consequential, or other damages.
 *
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#define SELF "c2h"
#define  VER "2.0"

/* maximum number of command line arguments */
#define MAXOPT 128

/* tool to extract function-declarations */
char CEXTRACT[] = "cextract";
/* CEXTRACT: options which are followed by a parameter */
char  OPTIONS[] = "HIqY";

/* command line for calling CEXTRACT */
char cmdline[1024];

/* list of .c-files to be processed */
char *list_of_files[MAXOPT];
int the_file, n_files;
char *filename = NULL; /* points to name of current input-file */


/* name of temporary output-file */
char tmpfile_name [L_tmpnam];
/* name of actual output-file */
char *outfile_name = NULL;


/* files and functions for parser */
extern FILE *yyin, *yyout;
extern yylex();

#define FILESEPARATOR '/'


/* compares content of 2 files. Files must be opened to read by caller
   and are closed by this function. Returns 0 if files are identical, -1
   otherwise */
int filecompare (f1, f2)
   FILE *f1, *f2;
{
   struct stat stat1;
   struct stat stat2;
   int result;
   result = fstat (fileno (f1), &stat1);
   assert (result == 0);
   result = fstat (fileno (f2), &stat2);
   assert (result == 0);
   if (stat1.st_size != stat2.st_size) result = -1;
   else {
      /* files are of same size. Compare byte by byte */
      char buffer1[512], buffer2[512];
      int i, n1, n2;
      result = 0;  /* default */
      do {
         n1 = fread (buffer1, sizeof (char), sizeof (buffer1), f1);
         n2 = fread (buffer2, sizeof (char), sizeof (buffer2), f2);
         if (n1 != n2) {
            result = -1;
            break;
         }
         for (i = 0; i < n1; i++) {
            if (buffer1[i] != buffer2[i]) {
               result = -1;
               break;
            }
         }
      } while ((result == 0) && (n1*n2 > 0));
   }
   fclose (f1);
   fclose (f2);
   return result;
}


/* standard-function 'rename' extended to work across distinct
   file-systems */
int file_rename (from_path, to_path)
   char *from_path, *to_path;
{
   if (rename (from_path, to_path) == 0) return 0;
   else {
      FILE *f_in, *f_out;
      f_in = fopen (from_path, "r");
      if (f_in == NULL) return -1;
      f_out = fopen (to_path, "w");
      if (f_out == NULL) {
         fclose (f_in);
         return -1;
      }
      else {
         /* all files open, copy byte by byte */
         char buffer[1024];
         int n;
         while ((n = fread (buffer, sizeof(char), sizeof(buffer), f_in)) > 0)
            fwrite (buffer, sizeof(char), n, f_out);
         fclose (f_in);
         fclose (f_out);
         remove (from_path);
         return 0;
      }
   }
}



/* writes head of header-file */
write_head (fp, fname)
   FILE *fp;
   char *fname;
{
   fprintf (fp, "/*******************************************************\n\n");
   fprintf (fp, "  WARNING: This file was generated automatically by %s.\n",
            SELF);
   fprintf (fp, "           Manual editing not recommended.\n\n");
   fprintf (fp, "*******************************************************/\n\n");
   if (fname != NULL) {
      char head_def[20];
      char *c_p;
      c_p = strrchr (fname, FILESEPARATOR);
      if (c_p == NULL) strncpy (head_def, fname, sizeof (head_def) - 1);
      else strncpy (head_def, c_p + 1, sizeof (head_def) - 1);
      *(head_def + sizeof(head_def) - 1) = '\0';
      /* replace '.' by '_' */
      while ((c_p = strchr (head_def, '.')) != NULL) *c_p = '_';
      fprintf (fp, "\n#ifndef _%s_\n#define _%s_\n", head_def, head_def);
   }
}

/* writes end of header-file */
write_tail (fp, fname)
   FILE *fp;
   char *fname;
{
   if (fname != NULL) {
      fprintf (fp, "\n\n#endif\n\n");
   }
}


/* Opens item 'the_file' of 'list_of_files' as input-file   */
/* and increments 'the_file'. Returns -1 if 'list_of_files' */
/* is exhausted, 0 otherwise                                */
int next_file ()
{
   if (the_file >= n_files) return -1;
   if (the_file > 0) {
      /* close previous input-file */
      fclose (yyin);
   }
   yyin = fopen (list_of_files[the_file], "r");
   filename = list_of_files[the_file];
   if (yyin == NULL) {
      fprintf (stderr, "%s: can't open file '%s'\n", SELF,
               list_of_files[the_file]);
      exit (-1);
   }
   fprintf (yyout, "\n\n\n/* export from file %s */\n\n",
            list_of_files[the_file]);
   the_file++;
   return 0;
}



main (argc, argv)
   int  argc;
   char *argv[];
{
   int i;
   char c;
   int compare = 0;

   if (argc < 2) {
      fprintf (stderr, "%s: no arguments specified.\n", SELF);
      return -1;
   }

   if (argc > MAXOPT-1) {
      fprintf (stderr, "%s: too many arguments specified.\n", SELF);
      return -1;
   }

   /* parse command line and set up arguments for CEXTRACT */
   sprintf (cmdline, "%s ", CEXTRACT);
   n_files = 0;
   for (i = 1; i < argc; i++) {
      /* option or filename? */
      if (*argv[i] == '-') {
         /* current argument == option */
         c = *(argv[i]+1);
         if ((c == 'o') || (c == 'O')) {
            /* options specifying output-file */
            if (strlen (argv[i]) > 2) outfile_name = argv[i]+2;
            else {
               /* filename separated by blank */
               if (i == argc - 1) {
                  fprintf (stderr, "%s: missing filename after -o.\n", SELF);
                  return -1;
               }
               i++;
               outfile_name = argv[i];
            }
         }
         else if ((c == 'c') || (c == 'C')) {
            /* compare new output file with old one */
            compare = 1;
         }
         else {
            /* option passed to CEXTRACT */
            if (strlen (cmdline) + strlen (argv[i]) >= sizeof (cmdline) - 2) {
               fprintf (stderr, "%s: command line too long.\n", SELF);
               return -1;
            }
            strcat (cmdline, argv[i]);
            strcat (cmdline, " ");
            if ((strchr (OPTIONS, c) != NULL) && (strlen (argv[i]) <= 2)) {
               /* option with separated argument */
               if (i == argc - 1) {
                  fprintf (stderr, "%s: missing argument after %s.\n",
                           SELF, argv[i]);
                  return -1;
               }
               i++;
               if (strlen (cmdline) + strlen (argv[i]) >= sizeof (cmdline) - 2) {
                  fprintf (stderr, "%s: command line too long.\n", SELF);
                  return -1;
               }
               strcat (cmdline, argv[i]);
               strcat (cmdline, " ");
            }
         }
      }
      else {
         /* current argument == filename */
         list_of_files[n_files] = argv[i];
         n_files++;
         if (strlen (cmdline) + strlen (argv[i]) >= sizeof (cmdline) - 2) {
            fprintf (stderr, "%s: command line too long.\n", SELF);
            return -1;
         }
         strcat (cmdline, argv[i]);
         strcat (cmdline, " ");
      }
   }
   if (n_files == 0) {
      fprintf (stderr, "%s: no input-files specified.\n", SELF);
      return -1;
   }

   if (outfile_name == NULL) yyout = stdout;
   else {
      tmpnam (tmpfile_name);
      yyout = fopen (tmpfile_name, "w");
      if (yyout == NULL) {
         fprintf (stderr, "%s: can't open tmp-file.\n", SELF);
         return -1;
      }
      /* redirect output of CEXTRACT to tmp-file */
      if (strlen (cmdline) + strlen (tmpfile_name) >= sizeof (cmdline) - 3) {
         fprintf (stderr, "%s: command line too long.\n", SELF);
         return -1;
      }
      strcat (cmdline, ">>");
      strcat (cmdline, tmpfile_name);
   }

   /* start writing to output stream */
   write_head (yyout, outfile_name);

   /* parse input-files */
   the_file = 0;
   next_file ();
   yylex ();

   /* stop writing to output stream */
   if (yyout != stdout) fclose (yyout);

   /* call CEXTRACT */
   system (cmdline);

   /* write end of output stream */
   if (outfile_name == NULL) yyout = stdout;
   else {
      yyout = fopen (tmpfile_name, "a");
      assert (yyout != NULL);
   }
   write_tail (yyout, outfile_name);
   if (yyout != stdout) fclose (yyout);

   /* update h-file if necessary */
   if (yyout != stdout) {
      FILE *f_p;
      f_p = fopen (outfile_name, "r");
      if (f_p != NULL) {
         /* outfile exists. Update? */
         if (compare != 0) {
            yyout = fopen (tmpfile_name, "r");
            assert (yyout != NULL);
            if (filecompare (yyout, f_p) == 0) {
               /* no update necessary */
               remove (tmpfile_name);
            }
            else {
               /* update necessary */
               remove (outfile_name);
               file_rename (tmpfile_name, outfile_name);
            }
         }
         else {
            /* replace always */
            fclose (f_p);
            remove (outfile_name);
            file_rename (tmpfile_name, outfile_name);
         }
      }
      else {
         /* no outfile present */
         file_rename (tmpfile_name, outfile_name);
      }
   }

   return 0;
}


