Stringify macro with GNU gfortran
Asked Answered
M

3

8

How can I stringify a preprocessor macro with GNU gfortran? I would like to pass a macro definition to GNU gfortran which will then be used as a string in the code.

Effectively I would like to do this:

program test
implicit none
character (len=:), allocatable :: astring
astring = MYMACRO
write (*, *) astring
end program test

and then build with:

gfortran -DMYMACRO=hello test.F90

I tried creating various macro, for example:

#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)
...
astring = STRINGIFY(MYMACRO)

but this doesn't work with the gfortran preprocessor.

I also tried using a different style of macro:

#define STRINGIFY(x) "x"
...
astring = STRINGIFY(MYMACRO)

but this just creates a string containing the text 'MYMACRO'.

I then tried changing the macro definition to:

-DMYMACRO=\"hello\"

but this caused unrelated problem in the build process.

Thank you for your help

Magill answered 27/7, 2015 at 9:38 Comment(6)
-DMYMACRO=\"hello\" is the right approach. Which problems did it cause?Wanderoo
@VladimirF Escaping the quotes works fine in my example code but in the actual project, some complexities of the build process causes it to fail. Changing the build procedure is not really an option, so I would like to find an alternative method if possible.Magill
Could you be more specific as to why escaping the quotes does not work in the real project? It is hard to provide solutions with an unknown requirement.Tortile
@Tortile Elsewhere in the build process, all the compiler flags are surrounded by quotes and included in a source file. Adding \" around the macro definition causes the compiler to fail.Magill
@VladimirF I updated the question to include #define STRINGIFY(x) "x". I found that it just created a string containing the text 'MYMACRO'.Magill
OK, finally I get where is the difference from the question I linked before. Unfortunately, I don't know the solution.Wanderoo
L
9

Your attempt to use the well-known stringification recipe of the C preprocessor, viz:

#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)

fails for two reasons, each sufficient by itself.

First and simplest, the source file in which you attempt to employ it apparently has the extension .f90. What this extension signifies to gfortran (and to the GCC compiler driver, by any other name) is: Free form Fortran source code that should not be preprocessed. Similarly .f95, .f03 and .f08. If you want gfortran to infer that a source file contains free form Fortran code that must be preprocessed, give it one of the extensions .F90, .F95, .F03 or .F08. See the GCC documentation of these points

Even if you do that simple thing, however, the second reason bites.

The use of the C preprocessor to preprocess Fortran source is as old as C (which though old, is much younger than Fortran). gfortran is obliged not to break ancient working code; so, when it invokes the C preprocessor, it invokes it in traditional mode. The traditional mode of the C preprocessor is the way in which the preprocessor behaved prior to the first standardization of the C language (1989), insofar as that unstandardized behaviour can be pinned down. In traditional mode, the preprocessor does not recognize the stringizing operator '#', which was introduced by the first C Standard. You can verify this by invoking the preprocessor directly like:

cpp -traditional test.c

where test.c contains some attempt to employ the stringification recipe. The attempt fails.

You cannot coax gfortran on its own to work the the stringification recipe.

But there is a workaround. You can invoke cpp directly, unencumbered by traditional mode, to preprocess the Fortran source in which you want the stringification done and relay its output to gfortran. If you already know this and were looking for a gfortran-alone solution you need read no further.

Doing the stringification in your test source this way would look like:

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' '-DMYMACRO=STRINGIFY(hello)' test.f90

The output of that is:

# 1 "test.f90"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.f90"
program test
implicit none
character (len=:), allocatable :: astring
astring = "hello"
write (*, *) astring
end program test

And that output is what you want to to compile. You could accomplish that as well by:

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' \
'-DMYMACRO=STRINGIFY(hello)' test.f90 > /tmp/test.f90 \
&& gfortran -o test /tmp/test.f90

Then you will find that ./test exists and that executing it outputs hello.

You can eliminate the intermediate temporary file with further refinement. Your F90 source code will compile as F95, as the latter is conservative of the former. So you can take advantage of the fact that GCC will compile source piped to its standard input if you tell it what language you are piping, using its -x language option. The Fortran dialects that you may specify in this way are f77, f77-cpp-input, f95 and f95-cpp-input, where the -cpp-input prefix denotes that the source is to be preprocessed and its absence denotes that it is not. Thus

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' \
'-DMYMACRO=STRINGIFY(hello)' test.f90 |  gfortran -x f95 -o test -

works as well as the previous solution, minus the temporary file, and emits the innocuous warning:

Warning: Reading file '<stdin>' as free form

(Note and retain the final - on the commandline. That is what tells gfortran to compile the standard input.). The meaning of -x f95 brings the additional economy that the source, which is preprocessed by cpp, is not preprocessed again by the compiler.

The use of the option -std=c89 when invoking cpp calls for a cautionary explanation. It has the effect of making cpp conform to the earliest C Standard. That is as close to -traditional as we can get while still availing of the #-operator, on which the stringification recipe depends, But with it you embrace the possibility of breaking some Fortran code if you preprocess it this way; otherwise gfortran itself would not enforce -traditional. In the case of your test program, you could safely omit -std=c89, allowing cpp to conform to the default C standard when it was built. But if you permit it or direct it to conform to -std=c99 or later, then the standard will mandate recognition of // as the beginning of a one-line comment (as per C++), by which any line of Fortran that contains the concatenation operator will be truncated at the first occurrence.

Naturally, if you use are using make or another build system to build the code in which you want the stringified macros, you will have a way of telling the build system what actions constitute compiling a given class of compilable files. For any Fortran source file fsrc you wanted to compile with a stringification preamble, the actions to specify would be in the vein:

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' \
'-DMYMACRO=STRINGIFY(hello)' fsrc.f90 | gfortran -x f95 -c -o fsrc.o -
Lunetta answered 31/7, 2015 at 19:4 Comment(1)
Thank you for writing such a comprehensive answer. In response to your first point, that was just a typo on my part -- I was using the .F90 suffix rather than .f90. I have edited the question to reflect this. On the second point, you have answered my question with "You cannot coax gfortran on its own to work the the stringification recipe". It looks like I am either going to have to find a different way to do what I want, or convince the project owner to modify their build procedure. The remainder of your second answer gives me a solution to offer. Thanks again.Magill
M
3

Although this is an old and answered question, I wanted to achieve macro stringification in gfortran without changing the default preprocessor or build process. I found that the preprocessor would do what I want, as long as there was no initial quotation mark on the line, therefore the desired stringification can be achieved by breaking lines with ampersands:

astring = "&
&MYMACRO"

A caveat is that this really only works with the traditional preprocessor, and for examples breaks with intel ifort compiler, which is too smart to fall for this trick. My current solution is to define separate stringification macros for gfortran as:

#ifdef __GFORTRAN__
# define STRINGIFY_START(X) "&
# define STRINGIFY_END(X) &X"
#else /* default stringification */
# define STRINGIFY_(X) #X
# define STRINGIFY_START(X) &
# define STRINGIFY_END(X) STRINGIFY_(X)
#endif

program test
implicit none
character (len=:), allocatable :: astring
astring = STRINGIFY_START(MYMACRO)
STRINGIFY_END(MYMACRO)
write (*, *) astring
end program test

It looks really ugly, but it does get the job done.

Manisa answered 21/9, 2017 at 10:50 Comment(1)
Great trick! Although it's a little cumbersome to use, it's the only real solution for big projects where changing the build process is out of the question.Chladek
T
-2

Removing the quotes around x in STRINGIFY solved the problem for me:

#define STRINGIFY(x) x   ! No quotes here
program test
    implicit none
    character (len=:), allocatable :: astring
    astring = STRINGIFY(MYMACRO)
    write(*,*), astring
end program test

Compiled with

gfortran -cpp -DMYMACRO=\"hello\" test.f90

The -cpp option enables the preprocessor for all extensions.

Thicken answered 14/9, 2016 at 14:45 Comment(1)
Well yes, but the point is in the # operator to concatenate two strings. The macro you show does literally nothing.Wanderoo

© 2022 - 2024 — McMap. All rights reserved.