1#! @PERL@ -w
2# -*- Mode: perl; -*-
3#
4# f77tof90 indir outdir [ Makefile-template [Make-Append] ]
5# For each file in indir/*.[fF], create a corresponding file in outdir
6# with .f90/.F90, and with any include "mpif.h" replaced with use mpi
7# It also changes to the new comment style, because some compilers
8# use the comment style to choose other features of the language
9#
10# We also allow the file name to be modified to help out Windows, since
11# programs in a project need to have distinct names
12#
13$indir = $ARGV[0];
14$outdir = $ARGV[1];
15$makeTemplate = $ARGV[2];
16$makeAppend   = $ARGV[3];
17$convertToFreeForm    = 1;
18$convertToNewComments = 1;
19# Including a newline variable allows us to handle Unix and DOS source files
20$newline = "\n";
21
22%replaceInclude = ( 'iodisp' => 'integer (kind=MPI_OFFSET_KIND) disp',
23		    'ioaint' => 'integer (kind=MPI_ADDRESS_KIND) aint',
24		    'iooffset' => 'integer (kind=MPI_OFFSET_KIND) offset',
25		    'type1aint' => 'integer (kind=MPI_ADDRESS_KIND) aint',
26		    'typeaints' => 'integer (kind=MPI_ADDRESS_KIND) aint, aintv(max_asizev)',
27		    'attr1aints' => 'integer (kind=MPI_ADDRESS_KIND) extrastate, valin, valout, val',
28		    'attraints' => 'integer (kind=MPI_ADDRESS_KIND) extrastate, valin, valout, val',
29		    'addsize' => 'integer (kind=MPI_ADDRESS_KIND) asize',
30                    'add1size' => 'integer (kind=MPI_ADDRESS_KIND) asize',
31	    );
32
33%excludePrograms = ();
34$debugReplace = 0;
35$reportSkipped = 0;
36# --------------------------------------------------------------------------
37# Check the input arguments
38if ($indir eq "" || $outdir eq "") {
39    print STDERR "Usage: f77tof90 indir outdir [ makefile-template ]\n";
40    exit 1;
41}
42if ( ! -d $indir) {
43    print STDERR "Input directory $indir does not exist\n";
44    exit 1;
45}
46if (! -d $outdir) {
47    print STDERR "Output directory $outdir does not exist\n";
48    exit 1;
49}
50# --------------------------------------------------------------------------
51
52
53# --------------------------------------------------------------------------
54opendir( DIR, "$indir" );
55my @filelist = ();
56while ($file = readdir(DIR)) {
57    # Extract the extension
58    if ($file =~ /^(.*)\.([^\.]*)$/) {
59	$name = $1;
60	$ext  = $2;
61	# Special handling for C files, if any
62	if ($ext eq "c") {
63	    my $name90 = $name;
64	    $name90 =~ s/f/f90/g;
65	    &ConvertCFile( "$indir/$file", "$outdir/$name90.c.new" );
66	    &ReplaceIfDifferent( "$outdir/$name90.c",
67				 "$outdir/$name90.c.new" );
68	    next;
69	}
70	# Skip if the file isn't a Fortran source file
71	if ($ext ne "f" && $ext ne "F") { next; }
72	&ConvertToF90( "$indir/$file", "$outdir/${name}90.${ext}90.new" );
73	&ReplaceIfDifferent( "$outdir/${name}90.${ext}90",
74			     "$outdir/${name}90.${ext}90.new" );
75	$filelist[$#filelist+1] = $file;
76    }
77}
78closedir( DIR );
79
80# &CreateMakefile( "filelist", $outdir );
81if (defined($makeTemplate) && $makeTemplate ne "" &&
82    -s "$indir/$makeTemplate") {
83    &ConvertMakefile( $indir, $outdir, $makeTemplate );
84    if (defined($makeAppend) && -s "$outdir/$makeAppend") {
85	# If there is a makeAppend in the output directory, then
86	# append that to the generated makefile
87	&AppendFile( "$outdir/$makeAppend", "$outdir/$makeTemplate.new" );
88    }
89    &ReplaceIfDifferent( "$outdir/$makeTemplate",
90			 "$outdir/$makeTemplate.new" );
91}
92if (-s "$indir/testlist" || -s "$indir/testlist.in") {
93    # We allow both testlist.in and testlist as source files;
94    # testlist.in gets priority
95    my $filename = "testlist";
96    if (-s "$indir/testlist.in") {
97	$filename = "testlist.in";
98    }
99    &ConvertTestlist( $indir, $outdir, $filename );
100
101    if (-s "$outdir/testlist.ap") {
102	&AppendFile( "$outdir/testlist.ap", "$outdir/$filename.new" );
103    }
104    &ReplaceIfDifferent( "$outdir/$filename", "$outdir/$filename.new" );
105}
106
107exit 0;
108
109# -----------------------------------------------------------------------------
110
111sub ConvertToF90 {
112    my $infile = $_[0];
113    my $outfile = $_[1];
114
115    open (INF, "<$infile" ) || die "Could not open $infile\n";
116    open (OUTF, ">$outfile" ) || die "Could not open $outfile\n";
117
118    print OUTF "! This file created from $infile with f77tof90\n";
119    my $lastLine = "";
120    my $firstline = 1;
121    while (<INF>) {
122	if (/\r/) { $newline = "\r\n"; }
123	# Remove any end-of-line characters
124	s/[\r\n]*//g;
125        # The implicit none must not come before the use mpi statement,
126	# but in F77, it must come before the include mpif.h statement.
127	# Rather than try and fix this, rely on the F77 versions to
128	# catch undeclared variables
129        if (/[Ii][Mm][Pp][Ll][Ii][Cc][Ii][Tt]\s+[Nn][Oo][Nn][Ee]/) { next; }
130	if (/^(\s*)include\s+[\'\"]mpif\.h/) {
131	    $_ = "$1use mpi";
132	}
133	# Allow the insertion of Fortran 90 only statements, such as
134	# interface definitions
135        if (/^CF90/) {
136	    s/^CF90/    /;
137	}
138	# Since we use interface statements for the error handlers,
139	# remove their external declaration
140	if (/^\s+external myerrhanfunc/) {
141	    s/^\s/!/;
142	}
143	if ($convertToNewComments) {
144	    s/^C/!/;
145	    s/^c/!/;
146	}
147	# Update the special includes that are used to provide
148	# address or offset sized types with ones the use the
149	# Fortran90 KIND style
150	if (/^(\s*)include\s+[\'\"]([\/\.\w]+)\.h[\"\']/) {
151	    my $leading     = $1;
152	    my $includename = $2;
153	    if (defined($replaceInclude{$includename})) {
154		$_ = $leading . $replaceInclude{$includename} . "\n";
155	    }
156	}
157
158	# We need to handle the special case of the program
159	# name in spawn commands
160	if (/(.*)\"([\.\/\w]*spawn[^\"]*)\"(.*)/) {
161	    my $before = $1;
162	    my $name = $2;
163	    my $after = $3;
164	    $_ = $before . "\"" . $name . "90" . "\"" . $after;
165	}
166
167	# We could also detect continuations in column six and
168	# convert to free-form input by holding one line back.
169	if ($convertToFreeForm) {
170	    if (/^     \S(.*)/) {
171		$leftover = $1;
172		# This line contains a continuation marker
173		# Add a continuation marker to the previous line if
174		# it doesn't already have one
175		if (! ($lastline =~ /\&\s*$/) ) {
176	  	    $lastline .= " &";
177		}
178		$_ = "      \&$leftover";
179	    }
180	}
181	print OUTF "$lastline$newline" if (! $firstline);
182	$firstline = 0;
183	$lastline = $_;
184    }
185    print OUTF "$lastline$newline";
186
187    close (INF);
188    close (OUTF);
189}
190
191#
192# A very simple routine for creating a version of a C file that refers
193# to F90 instead of F77.
194sub ConvertCFile {
195    my $infile = $_[0];
196    my $outfile = $_[1];
197
198    open (INF, "<$infile" ) || die "Could not open $infile\n";
199    open (OUTF, ">$outfile" ) || die "Could not open $outfile\n";
200
201    print OUTF "/* This file created from $infile with f77tof90 */\n";
202    while (<INF>) {
203	if (/\r/) { $newline = "\r\n"; }
204	# Remove any end-of-line characters
205	s/[\r\n]*//g;
206	# replace F77 with F90, mostly for CPP tests, except for name
207	# mapping
208	if (! /F77_NAME/) {
209	    s/F77/F90/g;
210	}
211	print OUTF "$_$newline";
212    }
213
214    close (INF);
215    close (OUTF);
216}
217
218# Create a makefile from a template.  Replace @EXECS@ with the programs
219# in the filelist.
220# CreateMakefile( "filelist", $outdir )
221sub CreateMakefile {
222    my $filelist = $_[0];
223    my $outdir   = $_[1];
224
225    print STDERR "This function is not implemented\n";
226    return 0;
227}
228
229#
230# Take an existing makefile and perform the following transformations:
231# .f -> .f90, .F -> .F90
232# Others as necessary
233# ConvertMakefile( indir, outdir, filename )
234# By providing the filename, we can accept Makefile, Makefile.in, Makefile.ap,
235# Makefile.sm, or even nonstandard names such as buildscript.
236sub ConvertMakefile {
237    my ($indir, $outdir, $filename) = @_;
238    %excludePrograms = ();
239
240    open( INF, "<$indir/$filename" ) || die "Cannot open $indir/$filename\n";
241    open( OUTF, ">$outdir/$filename.new" ) || die "Cannot open $outdir/$filename.new\n";
242    print OUTF "# This $filename generated automatically by f77tof90\n";
243    print OUTF "# from $indir/$filename.  DO NOT EDIT\n";
244    while (<INF>) {
245        # First, check for sources that are not present.  These
246	# may be derived files (see f77/io for an example).  For now,
247	# we'll skip these
248	if (/^(\w+)_SOURCES\s*=\s*(\w+\.f)/) {
249	    my $sourcebase = $1;
250	    my $sourcename = $2;
251	    if (! -s "$indir/$sourcename") {
252		print "Skipping source file $indir/$sourcename because it is not present\n" if $reportSkipped;
253		$excludePrograms{$sourcebase} = 1;
254		next;
255	    }
256	}
257        # convert program names from foof.f to foof90.f90
258        s/f_SOURCES/f90_SOURCES/g;
259	if (/f\.f/) {
260	    s/f\.f/f90.f90/g;
261	}
262	else {
263	    # Move files to f90
264	    s/\.f/.f90/g;
265	}
266	s/mtestf\.o/mtestf90.o/;
267	s/\.F/.F90/g;
268	s/f77/f90/g;
269	s/F77/F90/g;
270	# Update any per-program LDADD values
271        s/f_LDADD/f90_LDADD/g;
272	#
273	# Handle special cases:
274	# Force the c2f2c test to use the f90 compiler
275	s/c2f2cf90_SOURCES.*/c2f2cf90_SOURCES = c2f2cf90.f90 c2f902c.c/;
276	s/c2f2ciof90_SOURCES.*/c2f2ciof90_SOURCES = c2f2ciof90.f90 c2f902cio.c/;
277#	s/c2f2ciof90_LDADD/c2f2cfio90_LDADD/g;
278	s/c2f2cwinf90_SOURCES.*/c2f2cwinf90_SOURCES = c2f2cwinf90.f90 c2f902cwin.c/;
279
280	if (/EXTRA_PROGRAMS/) {
281	    s/allocmemf/allocmemf90/;   # allocmemf test is special
282	}
283	# Handle the special case of C programs (used for f2d/c2f testing)
284	if (/(\w+)_SOURCES(\s*=\s*)(\w+)\.c\s*$/) {
285	    my $progname = $1;
286	    my $spacing  = $2;
287	    my $name     = $3;
288	    $name =~ s/f/f90/;
289	    $progname =~ s/f/f90/;
290	    $_ = "$progname" . "_SOURCES" . $spacing . $name . ".c\n";
291	}
292
293	# Eventually need some way to update directory paths (particularly
294	# relative ones) and add F90 compile rules when not present.
295	print OUTF $_;
296    }
297
298    close( INF );
299    close( OUTF );
300    # The check on a file change is handled in the routine that calls this
301    # because we may append to this file first.
302}
303
304# Append infile to the end of inout file
305#( infile, inoutfile )
306sub AppendFile {
307    my $infile = $_[0];
308    my $outfile = $_[1];
309
310    open( INA, "<$infile" ) || die "Cannot open $infile\n";
311    open( OUTA, ">>$outfile" ) || die "Cannot open $outfile\n";
312    while (<INA>) {
313        print OUTA $_;
314    }
315    close(INA);
316    close(OUTA);
317}
318
319#
320# Replace old file with new file only if new file is different
321# Otherwise, remove new filename
322sub ReplaceIfDifferent {
323    my ($oldfilename,$newfilename) = @_;
324    my $rc = 1;
325    if (-s $oldfilename) {
326	$rc = system "cmp -s $newfilename $oldfilename";
327	$rc >>= 8;   # Shift right to get exit status
328    }
329    if ($rc != 0) {
330	print STDERR "Replacing $oldfilename\n";
331	if ($debugReplace && -s $oldfilename) {
332	    print STDERR "Differences are:";
333	    system "diff $newfilename $oldfilename";
334	}
335	# The files differ.  Replace the old file
336	# with the new one
337	if (-s $oldfilename) {
338	    unlink $oldfilename;
339	}
340	rename $newfilename, $oldfilename ||
341	    die "Could not replace $oldfilename";
342    }
343    else {
344	unlink $newfilename;
345    }
346}
347
348# Change the names of the tests.  Remove any that were skipped from the
349# Makefile.  Check for a  testlist.in before testlist
350sub ConvertTestlist {
351    my ($indir, $outdir, $filename) = @_;
352
353    open( INF, "<$indir/$filename" ) || die "Cannot open $indir/$filename\n";
354    open( OUTF, ">$outdir/$filename.new" ) || die "Cannot open $outdir/$filename.new\n";
355    print OUTF "# This file generated by f77tof90\n";
356    while (<INF>) {
357	if (/^(\w+)\s/) {
358	    my $sourcebase = $1;
359	    if (defined($excludePrograms{$sourcebase})) { next; }
360	}
361	if (/^(\w+f)\s+(.*)/) {
362	    $_ = $1 . "90 "  . $2 . "\n";
363	}
364	elsif (/^c2fmult(\w*)\s+(.*)/) {
365	    # This is a special case for programs that are not Fortran
366	    # programs but are part of the Fortran tests; principly, these
367	    # are the tests of MPI handle conversion
368	    # note the \w* instead of \w+; this allows us to match both
369	    # c2fmult.c and c2fmultio.c
370	    $_ = "c2f90mult$1 $2\n";
371	}
372	elsif (/^\@ALLOCMEMF\@/) {
373	    # This is a special case for an optional feature (using
374	    # Cray-style pointers for MPI_Alloc_mem).
375	    $_ = "\@ALLOCMEMF90\@\n";
376	}
377	print OUTF $_;
378    }
379    close INF;
380    close OUTF;
381
382}
383