The ufpp(1) command combines the ability to conditionally select Fortran source code lines for compilation with commonly required file filtering operations. Use at your own risk
The ufpp(1) command is more compatible with Fortran than the C preprocessor cpp(1). The directive syntax for numeric expressions is very similar to FORTRAN 77 INTEGER and LOGICAL expressions.
A detailed description of command options can be generated by using the "-help -html" switches, which were used to generate the document ufpp.1.html.
If you make enhancements or fix bugs I'd appreciate it if you pass them back to me (they must be public-domain).
At a minimum, you need the source code for the program UFPP(1f) and the command-line parsing module M_KRACKEN(3f):
If you want test cases and additional documentation as well, the tar(1) file ufpp.tgz contains everthing required to build and test the program.
This customized pre-processor is derived from the public-domain Lahey pre-processor. Parsing of expressions in the code remains significantly unchanged from the Lahey version; but the rest is essentially a re-write. ufpp(1) compiles with all the Fortran 90+ compilers I have needed it for; including the freely available gfortran(1) compiler (last tested with version 4.5.3).
Assuming you are familiar with the basic behavior of preprocessors such as cpp(1), fpp(1), coco(1), or more powerful macro processors such as m4(1) let's start with a simple input file example:
$! Compile this program and one of the following versions of subroutine SUB1 depending on the value of A $! NOTE: This Fortran source file contains ufpp(1) directives beginning with a dollar("$") character $!------------------ $! set default values for variables tested $IF .NOT.DEFINED(A) $DEFINE A=1 $ENDIF $!------------------ PROGRAM conditional_compile CALL sub1 END PROGRAM conditional_compile $!------------------ $IF A .EQ. 1 ! If A=1 output this version of subroutine sub1 $!------------------ SUBROUTINE sub1 PRINT*, "This is the first SUB1" END SUBROUTINE sub1 $!------------------ $ELSEIF A .EQ. 2 ! If A=2 output this version of subroutine sub1 $!------------------ SUBROUTINE sub1 PRINT*, "This is the second SUB1" END SUBROUTINE sub1 $!------------------ $ELSE ! If A was not 1 or 2 output this version of subroutine sub1 $!------------------ SUBROUTINE sub1 PRINT*, "This is the third SUB1" END SUBROUTINE sub1 $!------------------ $ENDIF $!------------------
Assuming this example is in the file "basic.F90" the output of the command
ufpp a=2 -i basic.F90
PROGRAM conditional_compile CALL sub1 END PROGRAM conditional_compile SUBROUTINE sub1 PRINT*, "This is the second SUB1" END SUBROUTINE sub1
The most unique feature of the ufpp(1) pre-processor as compared to the commonly available C preprocessor cpp(1) is that it combines simple filter operations with traditional pre-processing so that
Additionally, you may find ufpp(1) useful if
A more complicated example follows where code is contained in an HTML document. When the ufpp(1) switch "-html" is used, all lines outside of the lines delimited with the XMP directive are ignored, allowing documentation and code to be contained in one file that can be viewed via a browser. To view the file as HTML see f90.html .
<html> <head> <title> Simple HTML template for use with ufpp(1) </title> <link rel="stylesheet" href="http://www.w3.org/StyleSheets/Core/OldStyle" type="text/css" /> <!-- Chocolate Midnight Modernist Oldstyle Steely Swiss Traditional Ultramarine --> </head> <body> <h1> This is an HTML document that can be used as input to ufpp(1)</h1> <p> If you are comfortable writing simple HTML documents, a simple feature of ufpp(1) allows you to easily maintain documentation, source, and links to external files all together. If you run the ufpp(1) Fortran Preprocessor on this file with the command <pre> ufpp -html -i THISFILE.html -o THISFILE.f90 </pre> the Fortran source is extracted using very simple rules: If lines begin with <xmp> start writing out the lines; quit if </xmp> is encountered. </p> <h2> TEST PROGRAM </h2> <!-- =============================================================== --> <xmp> $if .not.defined(WHICH_VERSION) $define WHICH_VERSION=1 ! Will only compile the first version of subroutine ONE $endif program testit write(*,*)'hello world' call one() call two() end program testit </xmp> <!-- =============================================================== --> <h2> ROUTINE ONE </h2> <p> There are three versions of subroutine ONE(). If WHICH_VERSION is not defined, the "third" version is written to output. If WHICH_VERSION = 1, version "first" is used. If WHICH_VERSION =2 , version "second" is used. </p> <xmp> $IF WHICH_VERSION .EQ. 1 subroutine one() write(*,*)'called one, first version' end subroutine one $ELSEIF WHICH_VERSION .eq. 2 subroutine one() write(*,*)'called one, second version' end subroutine one $ELSE subroutine one() write(*,*)'called one, third version' end subroutine one $ENDIF </xmp> <!-- =============================================================== --> <h2> ROUTINE TWO</h2> <xmp> subroutine two() write(*,*)'called two' end subroutine two </xmp> <!-- =============================================================== --> </body>
In practice Fortran codes rarely need traditional pre-processing. In the past the most common reason for passing Fortran files through a pre-processor was when the code was calling C code or other languages where there was no standard calling interface defined. The ISO_C_BINDING standard now defines a C <--> Fortran interface so there is generally less need to pre-process Fortran source. The second-most common need for pre-processing was to easily use different Fortran extensions in different environments. Since the most common extensions are now standardized (accessing command line arguments, getting date/time information, passing a system command to the operating system, ...) the need for traditional pre-processing has diminished. On the other hand dealing with differences because many compilers are only partially implementing newer Fortran versions has brought back a new need for pre-processing; but that is hopefully a temporary issue.
That being said, pre-processing is occasionally still required (I strongly recommend that you isolate such code by putting the code sections that require pre-processing into small procedures that perform just the non-standard operations, and keep this in the smallest number of files possible).
So I try to avoid the need for traditional pre-processing of Fortran source code whenever possible. So why not just use cpp(1) instead of making ufpp(1)? Even if traditional pre-processing is not required I find ufpp(1)'s filtering capabilities useful as a way to keep related text files (documentation, test files, test programs, test scripts, ...) together in an easily edited and maintained and browse-able form.
If you just want traditional pre-processing capabilities and use a major Linux or Unix distribution you may find existing tools already meet your needs.
Some compilers provide their own "Fortran-safe" pre-processors. The compiler documentation often describes their features; which are usually a subset of the Unix utility cpp(1). They often intentionally do not provide macro expansion or C-style comments (block or in-line); but otherwise look very much like cpp(1). They are often called "fpp" if they can be called as a stand-alone utility. Sun has an open-source version on netlib of their flavor of fpp(1).
The cpp(1) program is designed for C/C++ and is not totally "Fortran- safe". It is, however, a de-facto standard for code pre-processing. Different versions often have switches to reduce the chances of generating unexpected Fortran code. The most common version of cpp(1) works with most Fortran using the following form:
cpp -P -C -traditional MYFILE.F90
The Fortran 95 standard provided an optional standard pre-processor definition. Currently, it seems to rarely be provided with compilers. Dan Nagle has an open-source version with macro expansion called "coco".
The Lahey Fortran site has a pre-processor code in a public-domain repository at http://www.lahey.com/code.htm. It is written in Fortran 77. This is what ufpp(1) was originally derived from. Typically you need to create a small wrapper script to call it from make(1)/cmake(1)/...
Other commonly used pre-processors are
Simply put, instead of pre-processors you can conditionally compile different files.
If you have a directory for each programming environment with identically named procedures in them that are specific to a system you can often avoid pre-processing altogether. Isolate the system-dependent code into small procedures to minimize duplicate code. Usually, the files are of the same name but in in different directories, one per platform. You can often additionally reduce the amount of duplicate code by judicious use of Fortran INCLUDE files. The most common problem with this method is making sure you keep any changes to the procedure parameters consistent across all the versions of the same routine.
A variation that sometimes can be employed is to have different directories with the same filenames in them that are INCLUDE files. You compile for different environments with the -I switch commonly available on Fortran compilers. So if you had directories CRAY and HP that both had the same files in them you could build different versions by entering "f90 -ICRAY ..." or "f90 -IHP ...".
If the system-dependent code is all "standard" code that will compile on all platforms some people recommend placing all the code in a procedure and setting a variable with an INCLUDE to select the proper code (assuming the selection does not cause major performance overhead).
This will make some branches into "dead code"; which many compilers will remove while optimizing the code.
subroutine system_dependent() character(len=10) :: system include "system.h" if(system.eq.'hp')then write(*,*)'do hp stuff' elseif(system.eq.'cray')then write(*,*)'do cray stuff' else write(*,*)'E-R-R-O-R: unknown system' endif end subroutine system_dependent
Assuming you have multiple "system.h" files that include a line like
system='cray'You can build different versions using the -I parameter once again to point to the different INCLUDE files.
I personally do not use this "dead code" method, but have seen it used to good effect.
Of course when all the code can be compiled on all platforms you can write the code without any preprocessing or use of alternate INCLUDE files and provide input at run-time to select the correct branch as well. The difference is that the compiler cannot optimize out the unused branches in that case.
When all the code is standard a good method can be to use pointers to procedures, as is often used to select between different graphics drivers or various mathematical "solvers".