#!/usr/bin/perl
#+
#  Name:
#     makeh

#  Purpose:
#     Generate the contents of the "ast.h" header file.

#  Type:
#     Perl script.

#  Invocation:
#     makeh file_list

#  Description:
#     This script processes private header files used within the AST library
#     in order to extract the public information they contain. This information
#     is produced in a form suitable for use in the public "ast.h" header file,
#     which defines the public interface to the library.

#  Parameters:
#     file_list
#        A space-separated list of the private AST header files whose public
#        information is to be extracted.

#  Result:
#     The contents of the "ast.h" header file are written to standard output.

#  Notes:
#     - This script is specific to the AST library and contains some knowledge
#     of the input file contents.

#  History:
#     10-JAN-2005 (DSB):
#        Added second argument (mode) to the mkdir invocation which creates
#        the temporary directory.
#     2-MAR-2006 (DSB):
#        Check for "config.h" as well as <config.h>
#     6-JAN-2010 (DSB):
#        Add work-around for GCC 4.4.2 problem - the pre-processor seesm to simply
#        throw away backslshes that escape newlines in the input header file. Reported
#        by Bryan Irby at GSFC.
#     27-JUL-2011 (DSB):
#        Include the process ID in the name of the temporary directory and
#        file, so that simultaneous invocations of this script do not trample
#        all over each other.
#-

( $#ARGV >= 0 ) || Usage();

#  Test whether we need to include config.h in the result.
$need_config_h = 0;

# Location of source files (makefile variable $(srcdir)).
# This is most typically '.', but can be different.
$srcdir = '.';

while ( $ARGV[0] =~ /^-/ ) {
   if ( $ARGV[0] eq '-s' ) {
      shift @ARGV;
      ( $#ARGV >= 0 ) || Usage();
      $srcdir = $ARGV[0];
      shift @ARGV;
   } else {
      Usage();
   }
}

#  Create a scratch directory.
$tmpdir="/tmp/makeh-$$.tmp";
unless ( -d $tmpdir && -w $tmpdir ) {
    if ( -e $tmpdir ) {
        die "Temp $tmpdir exists, but is unwritable or is not a directory\n";
    }
    mkdir $tmpdir, 0777 ||
        die "Failed to create temporary directory $tmpdir\n";
}

#  Open each input file.
foreach $file ( @ARGV ) {
   protect_copy_file( $file );
}


#  Open a pipe to a script (in the current directory) which runs the C
# preprocessor and direct its output to a scratch file.
open( CC, "| ./ast_cpp >/tmp/ast-$$.h" ) ||
                                die "Can't open pipe to C preprocessor (cpp)";

if ( $need_config_h ) {
# Include this directory's config.h, unescaped, in the output.  We
# need to pay attention to the values in this file, but don't want
# them unexpanded in the final ast.h.
    chomp($cwd = `pwd`);
    print(CC "#include \"$cwd/config.h\"\n");
}

#  Before including each file, write an underlined heading in the form of
#  C comments (with comment characters suitably protected so that they will
#  be passed unchanged by cpp).
foreach $file ( @ARGV ) {
   $comment = $file;
   $comment =~ s|^.*/||;
   $comment =~ s|.h$||;
   print( CC "/_* " . $comment . ". *_/\n" );
   $comment =~ s/./=/g;
   print( CC "/_* " . $comment . "= *_/\n" );

#  Write #include "xxx.h" lines to cpp so that it includes (and
#  preprocesses) each of the temporary files created above in turn.
   $tmp_file = $file;
   $tmp_file =~ s|^.*/||;
   printf(CC "#include \"%s/%s\"\n", $tmpdir, $tmp_file);
};

#  Close the pipe to cpp.
close( CC ) || die "C preprocessor (cpp) error";

#  Remove the temporary directory and the files it contains.
print( stderr `rm -r $tmpdir` );

#  Write the output preamble.
print(
'#if !defined(AST_INCLUDED)
#define AST_INCLUDED
/*
*+
*  Name:
*     ast.h

*  Purpose:
*     Define the public C interface to the AST library.

*  Language:
*     ANSI C

*  Type of Module:
*     C header file.

*  Description:
*     This file defines the public C interface to the AST library. It contains
*     all the type definitions, function prototypes, macro definitions, etc.
*     needed to use the library.

*  Copyright:
*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
*     Research Councils

*  Licence:
*     This program is free software: you can redistribute it and/or
*     modify it under the terms of the GNU Lesser General Public
*     License as published by the Free Software Foundation, either
*     version 3 of the License, or (at your option) any later
*     version.
*     
*     This program is distributed in the hope that it will be useful,
*     but WITHOUT ANY WARRANTY; without even the implied warranty of
*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*     GNU Lesser General Public License for more details.
*     
*     You should have received a copy of the GNU Lesser General
*     License along with this program.  If not, see
*     <http://www.gnu.org/licenses/>.

*  Authors:
*     DSB: D.S. Berry (STARLINK)
*     RFWS: R.F. Warren-Smith (STARLINK)
*     {enter_new_authors_here}

*  History:
*     ' );

#  Add the current date at this point.
( $sec, $min, $hour, $mday, $mon, $year ) = localtime;
print( $mday, '-',
       ( 'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
         'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC' )[ $mon ], '-',
       ( $year > 95 ? 1900 : 2000 ) + $year );

print( ' (makeh):
*        Original version, generated automatically from the internal header
*        files by the "makeh" script.
*     {enter_changes_here}
*-
*/

/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
#ifndef __GNUC__
#  define  __attribute__(x)  /*NOTHING*/
#endif

' );

#  Open the scratch file created above and read it.
$space = 0;
open( TEMP, "</tmp/ast-$$.h" );
while( <TEMP> ) {

#  Remove the underscores from the protected lines and macros.
   s/^_#include(\s)/#include$1/;
   s/^_#define(\s)/#define$1/;
   s/___LINE__/__LINE__/g;
   s/___FILE__/__FILE__/g;

#  Also un-protect protected comments.
   s|/_\*|/*|g;
   s|\*_/|*/|g;

#  Convert multiple blank lines (cpp creates lots of these) into single blank
#  lines.
   if ( /^\s*$/ ) {
      $space = 1;
   } else {

#  Remove additional unwanted lines that cpp creates.
      if ( ! /^# \d+/ ) {
         if ( $space ) { print( "\n" ) };
         $space = 0;

#  Output the lines we want to keep.
         print;
      }
   }
}

#  Write closing output lines.
print(
'
#endif
' );

#  Close and remove the scratch file.
close( TEMP );
unlink "/tmp/ast-$$.h";


sub protect_copy_file {
   my $file = shift;

   open( INCLUDE, "$srcdir/$file" )
       || die "Can't open input file " . $srcdir/$file;

#  Inicate we have no deferred text to write out.
   $total = "";

#  Open an output file with the same name in the temporary directory.
   $tmp_file = $file;
   $tmp_file =~ s|^.*/||;
   open( TEMP, ">$tmpdir/$tmp_file" );

#  Read the input file and detect "#include <...>" lines, extracting the name
#  of the header file being included.
line: while ( <INCLUDE> ) {
#  Omit any config.h includes, and note that we saw this
      if (/^\s*\#\s*include\s+<config.h>/ ||
          /^\s*\#\s*include\s+"config.h"/) {
          $need_config_h = 1;
          next line;
      }

      if ( ( $header ) = /^\#include\s+<(.+)>/ ) {

#  If this system header file has already been included, ignore it and go on to
#  the next input line.
         next line if $done{ $header }++;

#  Otherwise, protect the #include with an underscore to prevent the file
#  actually being included.
         s/^/_/;
      }

#  Also protect "#define ..." lines, to prevent macro substitution being
#  performed by the C preprocessor. Do not do this to lines of the form
#  "#define XXX_INCLUDED" because these are still needed to determine which
#  AST header files get included.
      if ( /^\#define\s/ ) {
         if ( ! /^\#define\s+\w*_INCLUDED/ ) { s/^/_/ };
      }

#  Similarly add underscores to protect standard C macros.
      s/__LINE__/___LINE__/g;
      s/__FILE__/___FILE__/g;

#  Some pre-processors (e.g. GCC 4.4.2) seem to simply throw away trailing
#  backslashes used to concatenate consecutive lines, producing two
#  independent lines in the output rather than one. This completely fouls
#  up the resulting header file. To avoid this, we concatenate such lines
#  explicitly, before writing them out to the temporary output file.
#  If the current line ends with an escaped newline, remove the escape
#  character and newline, and concatenate it with any previously deferred
#  lines.
      if( /^(.*)\\\s*$/ ) {
         $total .= $1;

#  If the current line does not end with an escaped newline, concatenate it
#  with any previously deferred lines, and write the total string out. Then
#  reset the total string to indicate we have no deferred text.
      } else {
         $total .= $_;
         print TEMP $total;
         $total = "";
      }
   }

#  Close each file when done.
   close( INCLUDE );
   close( TEMP );
}

sub Usage {
   print STDERR "$0 [-s srcdir] file...\n";
   exit (1);
}