I have seen a lot of preprocessing. I use the prep(1) preprocessor myself but
I have encountered some unusual ātricksā along the way. One of the more
unusual ones is having multiple file types (C, Fortran, docs, ā¦) in a single file
and then having a makefile use commands to separate the parts like
cpp -D_MD_CODE FILE|pandoc >doc.html
cpp -D_C_CODE FILE FILE.c
cpp -D_F_CODE FILE FILE.f90
or have little associated files like to extract the C part a little file āFILE.cā:
#define _C_CODE
#include "FILE"
because of a strong preference to keep everything related to a procedure in
a single file. The file then looks something like this ā¦
#ifdef _MD_CODE_
This is my documentation written
in markdown.
#endif
#ifdef _C_CODE_
/*
Here are some C procedures that are used along with ISO_C_BINDING
by the Fortran code
*/
#endif
#ifdef _F_CODE_
!! Fortran code
#endif
Less exotic are things like having
- #include files that turn debugging on and off with custom print macros
- to be used for templating where the common code is in an include file and
type definitions are in the main file
- predefined constants, although now done relatively easily with a module instead
- abbreviations used for commonly used lines or to standardize usage
- defining macros using a standard convention that are pre-defined by various
platforms in an ad-hoc manner so they are easier and more reliably used
- just for the ability to have free-format blocks of comments
- to provide metadata in a central file
A little example of templating with preprocessing and include files
! combined with #include for templating
subroutine a_32()
integer,parameter :: wp=kind(0.0)
#include "a"
end subroutine a_32
subroutine a_64()
integer,parameter :: wp=kind(0.0d0)
#include "a"
end subroutine a_64
Here is an attempt at showing some of the uses I have made of cpp(1) in the past
or have seen along the way. These are illustrations, not advocacy!
/* pick one */
#undef DEBUGON
#define DEBUGON
/* Constants */
#define __PI__ 3.141592653589793238462643383279502884197169399375105820974944592307d0
/* Debug I/O */
#ifdef DEBUGON
#define DEBUG print '(*(g0,":"))',"<DEBUG>",__TIMESTAMP__,"FILE",__FILE__,"LINE",__LINE__
#else
#define DEBUG !
#endif
/* Abbreviations */
#define __ISO_TYPES__ use,intrinsic :: iso_fortran_env, only : int8, int16, int32, int64, real32, real64, real128
#define __ISO_IO__ use,intrinsic :: iso_fortran_env, only : iostat_eor, iostat_end, stdin=>input_unit, stderr=>error_unit, stdout=>output_unit
#define __ISO_COMPILER__ use,intrinsic :: iso_fortran_env, only : compiler_options, compiler_version
#define __ISO__ use,intrinsic :: iso_fortran_env, only : sp=>real32,dp=>real64
/*Standardize pre-defined macros*/
#define _UNIX 1
#define _MS_WINDOWS 2
#define _APPLE 3
#define _CYGWIN 4
#define _UNKNOWN 999
#ifdef __unix
#define __IFON__ _UNIX
#endif
#ifdef _WIN32
#define __IFON__ _MSWINDOWS
#endif
#ifdef __APPLE__
#define __IFON__ _APPLE
#endif
#ifdef __CYGWIN__
#define __IFON__ _CYGWIN
#endif
#ifndef __IFON__
#define __IFON__ _UNKNOWN
#endif
/* Standardize pre-defined compiler ID */
#define __INTEL_COMP 1
#define __GFORTRAN_COMP 2
#define __NVIDIA_COMP 3
#define __NAG_COMP 4
#define __UNKNOWN_COMP 9999
#define REAL128 0
#ifdef __INTEL_COMPILER
# define __COMPILER__ __INTEL_COMP
#elif __GFORTRAN__ == 1
# define __COMPILER__ __GFORTRAN_COMP
#elif __NVCOMPILER
# define REAL128 1
# define __COMPILER__ __NVIDIA_COMP
#else
# define __COMPILER__ __UNKNOWN_COMP
# warning NOTE: UNKNOWN COMPILER
#endif
/* Metadata */
#define PROGRAM_NAME "printenv"
#define VERSION "1.0.0"
#define AUTHORS "David MacKenzie", "Richard Mlynarik"
#define LICENSE "MIT"
program testit
/*
It is a lot easier to maintain free-format text blocks
than it is to maintain Fortran comments with a exclamation
in front of every line, even if your favorite editor
supports editing Fortran comment blocks
*/
__ISO_TYPES__
__ISO_IO__
__ISO__
implicit none
real :: A=100.0
! constants
real,parameter :: PI= __PI__
! compiled on __IFON__
#if __IFON__ == _APPLE
write(*,*)'An Apple a Day keeps Dr. DOS away!'
#endif
#if __IFON__ == _UNIX
write(*,*)'Well, that takes some brass!'
#endif
#if __IFON__ == _CYGWIN
write(*,*)'An application or an OS?'
#endif
#if __IFON__ == _UNKNOWN
write(*,*)'Where am I?'
#endif
DEBUG , "got here!"
! print more debug values
DEBUG ,"A",A
write(*,*) __IFON__
write(*,*) __COMPILER__
end program testit
The Fortran Wiki shows commands for several compilers that dump a list
of the pre-defined macros. Pre-processing is generally made much more
powerful by predefined macros but it is very easy to miss how to display
them and there are no standard ones, so each compiler has different ones.
The most common use in the past that is now generally replaced by using
modules was to define COMMON blocks in #include files. COMMON blocks are
very prone to getting out of sync when maintained separately at each point
they are used. Putting the definitions in an #include file or defining them
as decks in UPDATE allowed you to keep just one definition of the COMMON.
Without that ātrickā a LOT of bugs were caused by COMMON definitions changing
in one reference but not in another.
There are so many subtle differences between the various cpp and fpp
commands out there that I made my own a long time ago; so I am a bit
rusty with cpp. It is not needed nearly as much as in the past to isolate
platform-specific code, which is a good thing. And upcoming Fortran changes
to support templating will hopefully reduce that more; but just about the
time you think pre-processing is not needed a change comes along that
ends up requiring it.
There is a standalone single-file version of prep(1) in the standalone/
directory of the github repository where the pre-processor I use is at
for alternative ideas about preprocessing for those not brave enough to
use m4(1) ā¦