FortranでC言語の__FILE__, __LINE__マクロを使う

# 概要
C言語の`__FILE__`, `__LINE__`マクロは非常に有用な情報を提供してくれます．Fortranでもプリプロセスを有効化するコンパイルオプションを付与すれば，これらのマクロを利用できます．

# 使用環境

|コンパイラ|バージョン|
|:--|:--|
|Intel Parallel Studio XE Composer Edition for Fortran|19.0.0.117|
|PGI Visual Fortran for Windows|18.7|
|GNU Fortran|8.1.0 (MinGW-W64)|

# 定義済みマクロ
C/C++言語には，いくつかの定義済みマクロが用意されています．その中でも，`__FILE__`マクロと`__LINE__`マクロは，それが参照されたソースファイルの名前や行番号に展開されるので，プログラムの不整合，例外などの検出に利用できます．

`__FILE__`は文字列，`__LINE__`は整数に展開されます．簡単なプログラムを例示します．

```C:main.c
#include <stdio.h>

int main(void)
{
  printf("An error occurred in %s on line %d\n", __FILE__, __LINE__);
  return 0;
}
```

実行すると，マクロを参照しているファイル名と行番号が正しく得られていることが確認できます．

```console
An error occurred in main.c on line 5
```

# FortranでのCマクロの参照
FortranでC言語のマクロを参照する場合，マクロ名はそのまま利用できます．加えて，コンパイル時にプリプロセスが行われるよう，コンパイルオプションを付与します．拡張子によっては自動でプリプロセスを行ってくれるようですが，そのために拡張子を変更するのは好ましくありません．可能な限りオプションを利用しましょう．

```fortran
program main
    implicit none
    print '(A,I0)', "An error occurred in "//__FILE__//" on line ",__LINE__
end program main
```

Intel, PGI, GNUのFortranコンパイラにおけるオプションを示します．

|コンパイラ|オプション|
|:--|:--|
|Intel |`/fpp`|
|PGI |`-Mpreprocess`もしくは`-Mcpp`|
|GNU Fortran|`-cpp`|

```console
ifort /fpp main.f90
pgfortran -Mpreprocess main.f90
gfortran -cpp -ffree-line-length-none main.f90
```

```console
An error occurred in main.f90 on line 3
```

Intel Fortranのコンパイルオプションは，Windowsの流儀に合わせて`/`で始まっていますが，`-`を用いても問題ありません．PGI Fortranには，`-Mpreprocess`と`-Mcpp`の二つのオプションが用意されていますが，`-Mcpp`は入力ファイルをプリプロセスするだけのようです．

1行あたりの文字数制限はプリプロセス後のソースファイルに対しても課されるので，gfortranはプリプロセスを行う際に`-ffree-line-length-none`オプションも併せて使うことを推奨しています[^gnu]．


`-E`オプションを付与すると，コンパイルは行われず，プリプロセスした結果が標準出力に表示されます．これはすべてのコンパイラで共通です．

```console
> ifort /fpp -E main.f90 /nologo
# 1 "main.f90"
program main
    implicit none
    print '(A,I0)', "An error occurred in "//"main.f90"//" on line ",3
end program main
```
`/nologo`は，コンパイラのコピーライト表示を抑制するオプションです．

```console
> pgfortran -Mpreprocess -E main.f90
# 1 "main.f90"
program main
    implicit none
    print '(A,I0)', "An error occurred in "//"main.f90"//" on line ",3
end program main
```

```console
> gfortran -cpp -E -ffree-line-length-none main.f90
# 1 "main.f90"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.f90"
program main
    implicit none
    print '(A,I0)', "An error occurred in "//"main.f90"//" on line ",3
end program main
```

# __LINE__の文字化
## C言語での使われ方

`__FILE__`, `__LINE__`マクロは非常に便利なのですが，あちこちでエラーメッセージ`print '(A,I0)', "An error occurred in "//__FILE__//" on line ",__LINE__`を書くのは大変です．そのため，エラーメッセージもマクロや関数形式マクロとして定義される場合があります．その際，`__LINE__`を文字列に変換することも，しばしば行われるようです．

`__LINE__`を文字列に変換するために，演算子`#`が用いられます．`#`は単項演算子のように用いられ，引数を文字列として返します．

```c
#define _TOSTRING(NUMBER) #NUMBER
#define TOSTRING(NUMBER) _TOSTRING(NUMBER)
#define AT __FILE__ ":" TOSTRING(__LINE__)

#include <stdio.h>

int main(void)
{
  printf("Fatal error at %s\n", AT);
  return 0;
}
```
```console
Fatal error at main.c:8
```

## Fortranでの再現
Fortranでもマクロはそのまま利用できるので，再現するのは簡単です．

```fortran:main.f90
#define _TOSTRING(NUMBER) #NUMBER
#define TOSTRING(NUMBER) _TOSTRING(NUMBER)
#define AT __FILE__//":"//TOSTRING(__LINE__)

program main
    implicit none
    print '(A)', "Fatal error at "//AT
end program main
```

```console
Fatal error at main.f90:7
```

ただし，このプログラムは**Intel FortranおよびPGI Fortranではコンパイルできますが，gfortranではコンパイルエラーになります**．stackoverflowにその理由や回避策を検討しているスレッドがあります[^sof]．gfortranはプリプロセスの際に`#`演算子を認識しないので，`__LINE__`が展開された際に，`"7"`ではなく`#7`となるためです．


```console
>gfortran -cpp main.f90
main.f90:7:52:

     print '(A)', "Fatal error at"//AT
                                                    1
Error: Syntax error in expression at (1)
```

`cpp`を使ってプリプロセスを試みると，Fortranの文字列結合演算子`//`がC++のコメントと認識されてしまいます．

```console
>cpp -std=c89 main.f90
# 1 "main.f90"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.f90"




program main
    implicit none
    print '(A)', "Fatal error at"main.f90:7:34: error: C++ style comments are not allowed in ISO C90
     print '(A)', "Fatal error at"//AT
                                  ^
main.f90:7:34: error: (this will be reported only once per input file)

end program main
```


`-std=c99`オプションを使うと，`//`以降がコメントと認識され，破棄されます．破棄されないようにオプションを付けても，マクロではなくコメントと認識されているので，展開されません．

```console
>cpp -std=c99 -CC main.f90
# 1 "main.f90"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.f90"




program main
    implicit none
    print '(A)', "Fatal error at"//AT
end program main
```

実際にマクロを使う場面を想定すると，エラーや不整合の内容も併せて出力することになるので，文字列結合ができないのは少々厳しいと思われます．


関数を定義すればなんとかなりますが，`__LINE__`を省略できず，冗長になります．

```fortran
#define FILE __FILE__//":"

program main
    implicit none
    print '(A)', "Fatal error at "//FILE//toString(__LINE__)

    contains
    pure function toString(number) result(string)
        use,intrinsic :: iso_fortran_env
        implicit none
        integer(int32),parameter :: MaxDigits = 10
        integer(int32),intent(in) :: number
        character(MaxDigits) string
        write(string,'(I0)') number
    end function toString
end program main
```

よい解決策をご存じの方はご教示ください．

[^gnu]: https://gcc.gnu.org/onlinedocs/gfortran/Preprocessing-Options.html
[^sof]: https://stackoverflow.com/questions/31649691/stringify-macro-with-gnu-gfortran