モダンFortran向けソースファイル自動整形ツールfprettifyの使い方

# 概要
モダンFortranのソースを自動で整形してくれるフォーマッタにfprettifyがあります．オプションで設定を変更できるのですが，説明がイマイチ判りにくかったり，どこが変更されるのかが判りにくかったりします．

サンプルのソースファイルを利用して，fprettifyのオプションの効果を説明します．また，設定ファイルの書き方，VSCodeとの連携，注意点などを紹介します．

# 環境
- Windows 10
- fprettify 0.3.7
- Python 3.8.5 (conda 4.13.0)
- ConfigArgParse 1.2.3
- VSCode 1.68
- Modern Fortran v3.1.0 (VSCode拡張)

Windowsのコマンドプロンプトを利用しているので，プロンプトを`>`で表記しています．

# fprettify
[fprettify](https://github.com/pseewald/fprettify)は，[Patrick Seewald](https://github.com/pseewald)氏が開発している，モダンFortran向けのソースファイル自動整形ツールです．

インデントの幅やスペースの調整のみならず，Fortranの都合で生じる記述のゆれ（キーワード等の大文字/小文字，`program`や`module`内でのインデント，多重ループ内のインデント）や，拡張子の追加にも対応しています．また，fyppのプリプロセッサディレクティブにも対応しています．

## インストール
`pip`または`conda`を利用してインストールできます．

```console
pip install fprettify
```

```console
conda install -c conda-forge fprettify
```

このとき，ConfigArgParseも一緒にインストールされます．ConfigArgParseは，設定ファイルの読み込みに利用されます．
WindowsでAnacondaを使っている場合，fprettifyのインストール先はAnacondaのインストール形態（システムインストールかユーザインストールか）によって変化します．どこにインストールされたか判らない場合は，fprettify.exeで検索してください．

## ソースファイルの整形
fprettifyでソースファイルを整形するには，以下のようにオプションでファイル名を与えて実行します．

```console
fprettify main.f90
```

ここでは，`main.f90`という名前のファイルをオプションとして与えています．fprettifyは，オプション与えたソースファイルを，整形した内容で上書きします．

複数のファイルを同時に整形するには，複数のファイルを指定します．

```console
fprettify main.f90 modules\mod1.f90
```

公式の説明では，カンマでファイル名を区切っていますが，カンマは必要ありません．

## VSCodeとの連携
fprettifyとVSCodeを連携させ，VSCodeでFortranのソースファイルを編集しつつ，保存時にfprettifyで自動整形できます．

fprettifyとVSCodeを連携させるには，

1. Modern Fortran拡張のインストール
2. インストールしたVSCode拡張の設定
3. VSCodeの設定

が必要です．

### Modern Fortran拡張のインストール
VSCodeの拡張機能からでModern Fortranで検索すると見つかります．非常に多機能な拡張で，うまく設定すると自動整形に限らず，Fortranプログラムの開発が非常に楽になります．

### インストールしたVSCode拡張の設定
VSCodeの設定を開き，"fortran"で検索し，左のツリーから拡張機能→Modern Fortranを選択すると設定項目が現れます．`Fortran › Formatting: Formatter`のプルダウンメニューからfprettifyを選択し，fprettifyの**実行ファイルがあるディレクトリのパス**を`Fortran › Formatting: Path`で指定します．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/0820c3af-8195-004b-eca6-b0cfec3f166a.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/209ed5a7-0ea2-99a1-4daf-2b49d7fd8e4e.png)


fprettifyに渡すオプションは，`Fortran › Formatting: Fprettify Args`に記述します．設定の記述にはかなりクセがあり，スペースを区切りとして全て別々の項目として書いていきます．例えば，`--indent 4 --enable-decl --strip-comments --case 1 1 1 1`というオプションを記述するには，`"--indent","4","--line-length","256","--enable-decl","--strip-comments","--case","1","1","1","1"`と区切る必要があります．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/2ef4b2aa-bcbf-e56b-e8cc-4d1c935b8a4b.png)

Modern Fortranの拡張については[この記事](https://qiita.com/implicit_none/items/ee4d3fed0ac6f974d40e)を参考にしてください．

### VSCodeの設定
VSCodeの設定で"format on save"を検索し，Format On Saveのチェックボックスにチェックを入れます．Format On Save Modeはfileとします．
Format On Typeは効きません．

このように設定をすると，VSCodeでFortranのソースファイルを編集・保存した際に，fprettifyによって整形されて保存されるようになります．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/95a040b9-5b8c-3662-5412-fd7df1cde044.png)

## 局所的にfprettifyで整形をしたくない場合
ソースファイルの中で，fprettifyで整形したくない場合には，文末に`!&`を付けるか，整形したくない範囲を`!&<`と`!&>`で囲みます．

例えば，以下のように使います．

```Fortran
laplacian(i,j) = ( (f(i+1,j  ) -2d0*f(i,j) + f(i-1,j  ))/dx**2 & !&
                  +(f(i  ,j+1) -2d0*f(i,j) + f(i  ,j-1))/dy**2)  !&
```

または

```Fortran
!&<
laplacian(i,j) =  (f(i+1,j  ) -2d0*f(i,j) + f(i-1,j  ))/dx**2 &
                 +(f(i  ,j+1) -2d0*f(i,j) + f(i  ,j-1))/dy**2
!&>
```

VSCodeからfprettifyを利用している場合，この機能を利用する際に注意が必要です．書き間違いなどでソース内に`!&>`だけが存在する（対になる`!&<`が無い）場合，**保存時にそのソースファイルの中身が全て消えてしまいます**．焦らず，`Ctrl+Z`などで保存前の状態に戻しましょう．

それ以外にも，マルチステートメント記号`;`と継続行記号`&`が文末にあると，同様の事が起こります．継続行記号の後に改行し，次の行にマルチステートメント記号を書いた時は，特に問題は起こりません．ロギング処理などを1段インデントしたいというような場合に利用できる小ネタです．

```Fortran
    call hoge()
        call logging("invoke hoge") ! これはVSCodeのFortran拡張によってインデントが削除される

    call hoge() ;&
        call logging("invoke hoge") ! 文末に;&があるのでfprettifyは整形できない．VSCodeと併用しているとファイルの中身が消える
    call hoge() &
        ; call logging("invoke hoge") ! インデントは維持される
```

# fprettifyの挙動の制御
fprettifyは，コマンドラインオプションを与えることで，整形の書式や挙動を設定できます．指定できるオプションとその効果を一つずつ説明していきます．

## ファイルとフォルダ構造
fprettifyのオプションとその効果を確認するために，下記のようにファイルを配置します．いくつかのファイルはただ置いてあるだけで，内容に意味はありません．

```
src
├── modules
│   ├── mod1.f90
│   ├── mod2.f08
│   └── type_vector2.f90
└── main.f90
```

```Fortran:main.f90
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = cos(pi/4d0)
    vec%y = sin(pi/4d0)
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

```Fortran:modules\type_vector2.f90
module type_vector2
    use, intrinsic :: iso_fortran_env
    implicit none
    private
    public :: vector2
    public :: clear

    type :: vector2
        real(real64) :: x
        real(real64) :: y
    end type

    interface clear
        procedure :: clear_vector2
    end interface

contains
    subroutine clear_vector2(vec)
        use, intrinsic :: iso_fortran_env
        implicit none
        type(vector2), intent(inout) :: vec
        vec%x = 0d0
        vec%y = 0d0
    end subroutine clear_vector2
end module type_vector2
```

```Fortran:modules\mod1.f90
module mod1
    use, intrinsic :: iso_fortran_env
    implicit none

contains
    subroutine test()
        use, intrinsic :: iso_fortran_env
        implicit none

    end subroutine test
end module mod1
```

```Fortran:modules\mod2.f08
module mod2
    use, intrinsic :: iso_fortran_env
    implicit none

end module mod2
```

## fprettifyのオプション
fprettifyに`-h`あるいは`--help`オプションを付けて実行すると，オプションの一覧とその効果を確認できます[^vscode_h]．

[^vscode_h]: vscode-modern-fortran-formatterのオプションに，`-h`あるいは`--help`を指定してはいけない．

```console
usage: C:\ProgramData\Anaconda3\Scripts\fprettify [-h] [-c CONFIG_FILE]
                                                  [-i INDENT] [-l LINE_LENGTH]
                                                  [-w {0,1,2,3,4}]
                                                  [--whitespace-comma [WHITESPACE_COMMA]]
                                                  [--whitespace-assignment [WHITESPACE_ASSIGNMENT]]
                                                  [--whitespace-decl [WHITESPACE_DECL]]
                                                  [--whitespace-relational [WHITESPACE_RELATIONAL]]
                                                  [--whitespace-logical [WHITESPACE_LOGICAL]]
                                                  [--whitespace-plusminus [WHITESPACE_PLUSMINUS]]
                                                  [--whitespace-multdiv [WHITESPACE_MULTDIV]]
                                                  [--whitespace-print [WHITESPACE_PRINT]]
                                                  [--whitespace-type [WHITESPACE_TYPE]]
                                                  [--whitespace-intrinsics [WHITESPACE_INTRINSICS]]
                                                  [--strict-indent]
                                                  [--enable-decl]
                                                  [--disable-indent]
                                                  [--disable-whitespace]
                                                  [--enable-replacements]
                                                  [--c-relations]
                                                  [--case CASE CASE CASE CASE]
                                                  [--strip-comments]
                                                  [--disable-fypp]
                                                  [--disable-indent-mod] [-d]
                                                  [-s] [-S] [-r] [-e EXCLUDE]
                                                  [-f FORTRAN] [--version]
                                                  [path [path ...]]
以下，各オプションの説明が続く
```

`C:\ProgramData\Anaconda3\Scripts\`は私の環境でfprettifyがインストールされているフォルダです．

全てのオプションには`--`で始まる長いバージョンが用意されているので，そちらを使用します．

## インデントの制御
インデントの幅を制御するには，`--indent`オプションの後にスペースを空けて数字を指定します．

```console
fprettify --indent 2 main.f90
```

<details><summary>main.f90(--indent 2)</summary><div>

```Fortran
program main
  use, intrinsic :: iso_fortran_env
  use :: type_vector2
  implicit none
  real(real64), parameter :: pi = acos(-1d0)

  integer(int32) :: num1, num2!input number 1 and 2
  integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
  real(real64) :: div!division operation

  ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
  type(vector2) :: vec

  write (*, '(a,x)', advance="no") "input num1"
  read *, num1

  !num2 must be greater than 0.
  !repeat do-loop infinitely until num2(>0) is input
  get_num2: do
    write (*, '(a,x)', advance="no") "input num2 (>=0)"
    read *, num2
    if (num2 == 0 .and. num2 < 0) then
      cycle get_num2
    end if
    if (num2 > 0) exit get_num2
  end do get_num2

  add = num1 + num2
  sub = num1 - num2
  mul = num1*num2
  div = dble(num1)/dble(num2)
  neg = -num1

  print '(i0,"+",i0,"=",i0)', num1, num2, add
  print '(i0,"-",i0,"=",i0)', num1, num2, sub
  print '(i0,"*",i0,"=",i0)', num1, num2, mul
  print '(i0,"/",i0,"=",g0)', num1, num2, div
  print '("-(",i0,")=",i0)', num1, neg

  vec = vector2(0d0, 0d0)
  vec%x = cos(pi/4d0)
  vec%y = sin(pi/4d0)
  print *, "hypotenuse=", hypot(vec%x, &
                                vec%y)
  call clear(vec)
end program main
```

</div></details>

fprettifyの標準の幅は**3**なので，`--indent`で幅を指定しない場合，インデントは全てスペース3個分の幅に置き換えられます[^indent]．Fortranでは，インデントの幅を2あるいは4としている人が多いようなので，このオプションは必ず指定することになるでしょう．

以降では，`--indent 4`を指定します．

[^indent]: 2と4で分かれているから間を取って3にしたのではなく，固定形式の名残だと考えられます．固定形式では，1行72文字という制限があり，7文字目から文や関数などを記述するようになっているので，インデント幅を3にすることがありました．インデント幅を3にしておけば，行頭から2回インデントを入れれば7文字目に移動できます．

### インデントを無効化するオプション
`--disable-indent`オプションを付与すると，インデントを無効化できます．
例えば，`--disable-indent`オプションのみを与えると，fpreffity標準のインデント幅に揃えられることを抑制できます．全てのソースファイルでインデント幅が揃っており，インデント幅を変えられたくない時に指定します．

### program，module内のインデントの無効化
`program`~`end program`や，`module`~`end module`に対してインデントをせずに，下記のようにする流儀もあります．

```Fortran
module type_vector2
use, intrinsic :: iso_fortran_env
implicit none
private
public :: vector2
public :: clear

type :: vector2
    real(real64) :: x
    real(real64) :: y
end type

interface clear
    procedure :: clear_vector2
end interface

contains
subroutine clear_vector2(vec)
    use, intrinsic :: iso_fortran_env
    implicit none
    type(vector2), intent(inout) :: vec
    vec%x = 0d0
    vec%y = 0d0
end subroutine clear_vector2
end module type_vector2
```

これを制御するのが`--disable-indent-mod`オプションです．上記の例は

```console
fprettify --indent 4 --disable-indent-mod modules\type_vector2.f90
```

として整形した結果です．`module`~`end module`に対してインデントが行われていませんが，`type`~`end type`や`subroutine`~`end subroutine`に対しては，インデントが行われていることが確認できます．

### 厳密なインデント
Fortranでは，多重の`do`ループを書くときに，`do`ループ内で`do`~`end do`をインデントをしない場合があります．

```Fortran
do k = 1, Nz
do j = 1, Ny
do i = 1, Nx
    f(i, j, k) = ...
end do
end do
end do
```

fprettifyでは，このような多重ループがあってもインデントしませんが，`--strict-indent`オプションを付与すると，下記のようにインデントするようになります．

```Fortran
do k = 1, Nz
    do j = 1, Ny
        do i = 1, Nx
            f(i, j, k) = ...
        end do
    end do
end do
```

### fyppのブロック内のインデント
fprettifyはfypp (Python powered Fortran metaprogramming)のブロックもインデントにも対応しています．

fyppでは`#:if`~`#:endif`でブロックを記述します．fprettifyは，そのブロック内を1段インデントしますが，`--disable-fypp`オプションを付与すると，fyppのブロックに対するインデントをしなくなります．

- 入力（[fyppリポジトリ](https://github.com/aradi/fypp)より引用）

```Fortran
    #:if DEBUG > 0
        print *, "Some debug information"
    #:endif
```

- `--indent 4 --whitespace 0`

```Fortran
    #:if DEBUG > 0
        print*,"Some debug information"
    #:endif
```

- `--indent 4 --whitespace 0 --disable-fypp`

```Fortran
    #:if DEBUG > 0
    print*,"Some debug information"
    #:endif
```

## 空白の制御
fprettifyのコマンドライン引数のおよそ半分は，空白の制御を行うためのオプションです．細々と指定するのが面倒，あるいはそこまでこだわりがない場合は，`--whitespace`オプションである程度まとめて制御できます．

`--whitespace`オプションには，5個のプリセットが用意されており，`--whitespace`の後にスペースを空けてその番号を指定します．

プリセットの番号と，入れられる空白には下記表の対応があります．

|プリセット番号|空白が入る場所|
|:--:|:--|
|0|文法上空白が必要な場所<br>セミコロンの後ろ<br>継続行記号`&`の前<br>`then`の前|
|1|0で空白が入る場所<br>算術演算子以外の演算子（代入，関係，論理演算子）の前後<br>`print`,`read`文の名前と書式の間（書式が`*`の場合）<br>カンマの後ろ<br>`end`と構文名の間<br>文の名前とそれに続く括弧の間|
|2|1で空白が入る場所<br>`+`, `-`の前後|
|3|2で空白が入る場所<br>`*`, `/`の前後|
|4|3で空白が入る場所<br>派生型の成分を指定する`%`の前後|

fprettifyは2を標準にしており，`--whitespace`を指定しない場合は，`--whitespace 2`を付与した場合と同じように空白が挿入，あるいは削除されます．

プリセット番号を変えながら，どのように整形されるかを見てみます．

<details><summary>main.f90 (--indent 4 --whitespace 0)</summary><div>

```Fortran
program main
    use,intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64),parameter :: pi=acos(-1d0)

    integer(int32) :: num1,num2!input number 1 and 2
    integer(int32) :: add,sub,mul,neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write(*,'(a,x)',advance="no") "input num1"
    read*,num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write(*,'(a,x)',advance="no") "input num2 (>=0)"
        read*,num2
        if(num2==0.and.num2<0) then
            cycle get_num2
        endif
        if(num2>0) exit get_num2
    enddo get_num2

    add=num1+num2
    sub=num1-num2
    mul=num1*num2
    div=dble(num1)/dble(num2)
    neg=-num1

    print '(i0,"+",i0,"=",i0)',num1,num2,add
    print '(i0,"-",i0,"=",i0)',num1,num2,sub
    print '(i0,"*",i0,"=",i0)',num1,num2,mul
    print '(i0,"/",i0,"=",g0)',num1,num2,div
    print '("-(",i0,")=",i0)',num1,neg

    vec=vector2(0d0,0d0)
    vec%x=cos(pi/4d0)
    vec%y=sin(pi/4d0)
    print*,"hypotenuse=",hypot(vec%x, &
                               vec%y)
    call clear(vec)
endprogram main
```

</div></details>

`--whitespace 0`を付けた場合でも，`use`文や型宣言のセミコロンの前後に空白が存在しています．これは空白が入れられた訳ではなく，fprettifyが当該箇所を整形しないようになっているため，元のソースファイルにあった空白が残っているだけです．
括弧と`then`の間にはスペースが入ります．

<details><summary>main.f90 (--indent 4 --whitespace 1)</summary><div>

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1+num2
    sub = num1-num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = cos(pi/4d0)
    vec%y = sin(pi/4d0)
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

</div></details>

<details><summary>main.f90 (--indent 4 --whitespace 2)</summary><div>

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = cos(pi/4d0)
    vec%y = sin(pi/4d0)
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

</div></details>


<details><summary>main.f90 (--indent 4 --whitespace 3)</summary><div>

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1 * num2
    div = dble(num1) / dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = cos(pi / 4d0)
    vec%y = sin(pi / 4d0)
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

</div></details>

<details><summary>main.f90 (--indent 4 --whitespace 4)</summary><div>

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1 * num2
    div = dble(num1) / dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec % x = cos(pi / 4d0)
    vec % y = sin(pi / 4d0)
    print *, "hypotenuse=", hypot(vec % x, &
                                  vec % y)
    call clear(vec)
end program main
```

</div></details>
<br>

個人的な感覚では，標準の**2**で問題ないと思います．`%`前後の空白はやり過ぎだと思いますし，算術演算子の前後に空白が無いのも，見にくいと思います．また，結合の強さを空白で表すために，`+`や`-`の前後には空白を入れ，`*`と`/`の間には空白を入れないという書き方も普通にされているので，2で違和感はありません．

### 個別の空白の制御
個別に空白を制御したい場合，`--whitespace`だけでなく，個別の空白制御用のオプションを利用します．

#### `--whitespace-comma`
`--whitespace-comma true`とすると，カンマの後ろに空白が入る．
`--whitespace-comma false`とすると，カンマの後ろの空白が削られる．

`main.f90`の中で，いくつか関係のありそうな箇所を取り出して見てみます．

- 入力

```Fortran
    integer(int32) :: num1, num2!input number 1 and 2
    write (*, '(a,x)', advance="no") "input num1"
    read *, num1
    print '(i0,"+",i0,"=",i0)', num1, num2, add
    vec = vector2(0d0, 0d0)
```

- `--indent 4 --whitespace 0 --whitespace-comman false`

```Fortran
    integer(int32) :: num1,num2!input number 1 and 2
    write(*,'(a,x)',advance="no") "input num1"
    read*,num1
    print '(i0,"+",i0,"=",i0)',num1,num2,add
    vec=vector2(0d0,0d0)
```

- `--indent 4 --whitespace 0 --whitespace-comman true`

```Fortran
    integer(int32) :: num1, num2!input number 1 and 2
    write(*, '(a,x)', advance="no") "input num1"
    read*,num1
    print '(i0,"+",i0,"=",i0)', num1, num2, add
    vec=vector2(0d0, 0d0)
```

変数宣言における各変数の区切り，関数や文の引数の区切りとなるカンマの後ろに空白が入っています．一方で，`read`文の書式と変数を区切るカンマ（`read*,num1`）と，書式の各項目を区切るカンマ(`'(i0,"+",i0,"=",i0)'`)の後ろには空白は入りません．書式は文字列として取り扱われるので，文字列を整形しないのは望ましい挙動ですが，`read`文の書式と変数を区切るカンマの後ろに空白を入れない理由はよくわかりません．

fprettifyのヘルプには，セミコロンの後ろの空白も制御できるように書かれていますが，セミコロンの後ろの空白は，どのようなオプションを指定しても必ず入るようです．

#### `--whitespace-assignment`
`--whitespace-assignment true`とすると，代入演算子の前後に空白が入る．
`--whitespace-assignment false`とすると，代入演算子の前後の空白が削られる．

- 入力

```Fortran
    add = num1 + num2
    print '(i0,"+",i0,"=",i0)', num1, num2, add
```

- `--indent 4 --whitespace 0 --whitespace-assignment false`

```Fortran
    add=num1+num2
    print '(i0,"+",i0,"=",i0)',num1,num2,add
```

- `--indent 4 --whitespace 0 --whitespace-assignment true`

```Fortran
    add = num1+num2
    print '(i0,"+",i0,"=",i0)',num1,num2,add
```

代入演算子の前後に空白が入れられますが，文字列の`"="`の前後には空白は入りません．

#### `--whitespace-relational`
`--whitespace-relational true`とすると，関係演算子の前後に空白が入る．
`--whitespace-relational false`とすると，関係演算子の前後の空白が削られる．

- 入力

```Fortran
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
```

- `--indent 4 --whitespace 0 --whitespace-relational false`

```Fortran
        if(num2==0.and.num2<0) then
            cycle get_num2
        endif
        if(num2>0) exit get_num2
```

- `--indent 4 --whitespace 0 --whitespace-relational true`

```Fortran
        if(num2 == 0.and.num2 < 0) then
            cycle get_num2
        endif
        if(num2 > 0) exit get_num2
```

#### `--whitespace-logical`
`--whitespace-logical true`とすると，論理演算子の前後に空白が入る．
`--whitespace-logical false`とすると，論理演算子の前後の空白が削られる．

- 入力

```Fortran
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
```

- `--indent 4 --whitespace 0 --whitespace-logical false`

```Fortran
        if(num2==0.and.num2<0) then
            cycle get_num2
        endif
        if(num2>0) exit get_num2
```

- `--indent 4 --whitespace 0 --whitespace-logical true`

```Fortran
        if(num2==0 .and. num2<0) then
            cycle get_num2
        endif
        if(num2>0) exit get_num2
```

#### `--whitespace-plusminus`
`--whitespace-plusminus true`とすると，`+`と`-`演算子の前後に空白が入る．
`--whitespace-plusminus false`とすると，`+`と`-`の前後の空白が削られる．

- 入力

```Fortran
    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1
```

- `--indent 4 --whitespace 0 --whitespace-plusminus false`

```Fortran
    add=num1+num2
    sub=num1-num2
    mul=num1*num2
    div=dble(num1)/dble(num2)
    neg=-num1
```

- `--indent 4 --whitespace 0 --whitespace-plusminus true`

```Fortran
    add=num1 + num2
    sub=num1 - num2
    mul=num1*num2
    div=dble(num1)/dble(num2)
    neg=-num1
```

減算を行う演算子としての`-`の前後には空白が入ります．一方で，符号を反転する演算子としての`-`の前後には空白は入りません．

#### `--whitespace-multdiv`
`--whitespace-multdiv true`とすると，`*`と`/`演算子の前後に空白が入る．
`--whitespace-multdiv false`とすると，`*`と`/`の前後の空白が削られる．

- 入力

```Fortran
    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1
```

- `--indent 4 --whitespace 0 --whitespace-multdiv false`

```Fortran
    add=num1+num2
    sub=num1-num2
    mul=num1*num2
    div=dble(num1)/dble(num2)
    neg=-num1
```

- `--indent 4 --whitespace 0 --whitespace-multdiv true`

```Fortran
    add=num1+num2
    sub=num1-num2
    mul=num1 * num2
    div=dble(num1) / dble(num2)
    neg=-num1
```

ここでは例示していませんが，冪乗の演算を行う`**`の前後に空白を入れることは，fprettifyではできません．

#### `--whitespace-print`
`--whitespace-print true`とすると，`print`, `read`の文の名前と`*`の間に空白が入る．
`--whitespace-print false`とすると，`print`, `read`の文の名前と`*`の間の空白が削られる．書式を指定している場合は，空白は削られない．

- 入力

```Fortran
    write (*, '(a,x)', advance="no") "input num1"
    read *, num1
    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
```

- `--indent 4 --whitespace 0 --whitespace-print false`

```Fortran
    write(*,'(a,x)',advance="no") "input num1"
    read*,num1
    print '(i0,"+",i0,"=",i0)',num1,num2,add
    print*,"hypotenuse=",hypot(vec%x, &
                               vec%y)
```

- `--indent 4 --whitespace 0 --whitespace-print true`

```Fortran
    write(*,'(a,x)',advance="no") "input num1"
    read *, num1
    print '(i0,"+",i0,"=",i0)',num1,num2,add
    print *, "hypotenuse=",hypot(vec%x, &
                                 vec%y)
```

`--whitespace-print false`を付与すると，`print *`, `read *`において，文の名前と`*`の間の空白が削られていることが判ります．一方で，`print '(i0,"+",i0,"=",i0)'`のように書式を指定している時は，文の名前と書式の間の空白は削られません．これは文法上の制約です．

`--whitespace-print true`を付与すると，`print`文，`read`文において，文の名前と`*`の間に空白が入ります．`write`文は`write *`という書き方ができないので，整形対象外です．`write`とそれに続く括弧の間の空白を制御するには，`--whitespace-intrinsics`オプションを利用します．

#### `--whitespace-type`
`--whitespace-type true`とすると，派生型の成分を指定する`%`の前後に空白が入る．
`--whitespace-type false`とすると，派生型の成分を指定する`%`の前後の空白が削られる．

- 入力

```Fortran
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
```

- `--indent 4 --whitespace 0 --whitespace-type false`

```Fortran
    print*,"hypotenuse=",hypot(vec%x, &
                               vec%y)
```

- `--indent 4 --whitespace 0 --whitespace-type true`

```Fortran
    print*,"hypotenuse=",hypot(vec % x, &
                               vec % y)
```

#### `--whitespace-intrinsics`
`--whitespace-intrinsics true`とすると，`if`, `write`, `open`, `close`など，文の名前とそれに続く括弧の間に空白が入る．
`--whitespace-intrinsics false`とすると，文の名前とそれに続く括弧の間の空白が削られる．

- 入力

```Fortran
    write (*, '(a,x)', advance="no") "input num1"
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
```

- `--indent 4 --whitespace 0 --whitespace-intrinsics false`

```Fortran
   write(*,'(a,x)',advance="no") "input num1"
        if(num2==0.and.num2<0) then
            cycle get_num2
        endif
```

- `--indent 4 --whitespace 0 --whitespace-intrinsics true`

```Fortran
    write (*,'(a,x)',advance="no") "input num1"
        if (num2==0.and.num2<0) then
            cycle get_num2
        end if
```

`--whitespace-intrinsics true`とすると，`write`文，`if`文において，文の名前とそれに続く括弧の間に空白が入っていることが確認できます．`open`, `close`, `allocate`, `deallocate`等も同様です．

また，ヘルプには書かれていませんが，構文ブロックの終わりを示す`end`の後ろにも空白が入ります．`end if`だけでなく，`end program`, `end module`, `end subroutine`, `end function`, `end do`, `end block`等全てが整形対象になります．

#### `--enable-decl`
`--enable-decl`を引数に与えることで，`use`文や変数宣言のセミコロン`::`を整形の対象とする．
`--whitespace 0 --enable-decl`の場合は`::`前後の空白が削除され，`--whitespace 1 --enable-decl`では`::`前後に空白が入る．

- 入力

```Fortran
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    real(real64), parameter :: pi = acos(-1d0)
    integer(int32) :: num1, num2!input number 1 and 2
```

- `--indent 4 --whitespace 0`

```Fortran
    use,intrinsic :: iso_fortran_env
    use :: type_vector2
    real(real64),parameter :: pi=acos(-1d0)
    integer(int32) :: num1,num2!input number 1 and 2
```

- `--indent 4 --whitespace 0 --enable-decl`

```Fortran
    use,intrinsic::iso_fortran_env
    use::type_vector2
    real(real64),parameter::pi=acos(-1d0)
    integer(int32)::num1,num2!input number 1 and 2
```

- `--indent 4 --whitespace 1 --enable-decl`

```Fortran
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)
    integer(int32) :: num1, num2!input number 1 and 2
```

`--whitespace 0`を付与しただけでは，`::`前後の空白は整形されません．`--whitespace 0`に`--enable-decl`を追加すると，`::`前後の空白が削除されています．

#### `--whitespace-decl`
`--whitespace-decl true`とすると，`::`の前後に空白が入る．
`--whitespace-decl false`とすると，`::`の前後の空白が削られる．
`--enable-decl`と同時に利用する必要がある．

- 入力

```Fortran
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    real(real64), parameter :: pi = acos(-1d0)
    integer(int32) :: num1, num2!input number 1 and 2
```

- `--indent 4 --whitespace-decl false`

```Fortran
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    real(real64), parameter :: pi = acos(-1d0)
    integer(int32) :: num1, num2!input number 1 and 2
```

- `--indent 4 --enable-decl --whitespace-decl false`

```Fortran
    use, intrinsic::iso_fortran_env
    use::type_vector2
    real(real64), parameter::pi = acos(-1d0)
    integer(int32)::num1, num2!input number 1 and 2
```

- `--indent 4 --whitespace 0 --enable-decl --whitespace-decl true`

```Fortran
    use,intrinsic :: iso_fortran_env
    use :: type_vector2
    real(real64),parameter :: pi=acos(-1d0)
    integer(int32) :: num1,num2!input number 1 and 2
```

`--whitespace-decl false`だけを指定しても`::`前後の空白は削除されませんが，`--enable-decl`も同時に与えることで，`::`前後の空白が削除されます．

#### 空白の整形の無効化
`--disable-whitespace`オプションを付与すると，空白の整形を無効化できます．例えば，インデント幅のみを変更したい場合，`--indent 2 --disable-whitespace`とします．

## 関係演算子
### `--enable-replacements`
`--enable-replacements`オプションを付与すると，モダンな（Fortran 90以降の）関係演算子が，クラシカルな（FORTRAN 77の）関係演算子に置き換えられます．

|クラシカル|モダン|
|:--:|:--:|
|`.lt.`|`<`|
|`.le.`|`<=`|
|`.gt.`|`>`|
|`.ge.`|`>=`|
|`.eq.`|`==`|
|`.ne.`|`/=`|

fprettifyのヘルプには相互変換であるかのように書かれてありますが，試した限りではモダンな関係演算子がクラシカルな方へ置き換えられるだけでした．ただし，次で説明する`--c-relations`も併せて付与することで，クラシカルな関係演算子がモダンな方に置き換えられるようになります．

- 入力

```Fortran
        if (num2 == 0 .and. num2 .lt. 0) then
            cycle get_num2
        end if
        if (num2 .gt. 0) exit get_num2
```

- `--indent 4 --enable-replacements`

```Fortran
        if (num2 .eq. 0 .and. num2 .lt. 0) then
            cycle get_num2
        end if
        if (num2 .gt. 0) exit get_num2
```

### `--c-relations`
`--enable-replacements`オプションと共に指定することで，クラシカルな関係演算子をモダンな関係演算子に置き換えます[^cstyle]．

- 入力

```Fortran
        if (num2 == 0 .and. num2 .lt. 0) then
            cycle get_num2
        end if
        if (num2 .gt. 0) exit get_num2
```

- `--indent 4 --enable-replacements --c-relations`

```Fortran
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
```

[^cstyle]: fprettifyのヘルプでは，Cスタイルと称されていますが，FortranにはCと互換性がない関係演算子（`/=`）があるので，Cスタイルという表現は正しくないように思います．

## 大文字小文字の変換
FORTRAN 77まではアルファベットの大文字しか使えませんでしたが，Fortran 90以降小文字も使えるようになりました．それでも，キーワードや組込の手続き名を大文字で書く流儀があります[^case]．

[^case]: エディタにシンタックスハイライトや補完などの機能がなくても，キーワードや組込の手続き名を識別するためと考えられます．

fprettifyはこのような要望にも対応しており，`--case`オプションでキーワード等の大文字↔小文字の変換が可能です．

`--case`オプションは4個のパラメータを取り，それらのパラメータは，0, 1, 2のいずれかを指定します．`--case`オプションは，例えば次のように利用します．

```console
fprettify --case 1 2 0 0 main.f90
```

パラメータとして指定する0, 1, 2によって，動作が変化します．

|パラメータ|動作|
|:--:|:--|
|0|何もしない（ソースファイルの状態を維持）|
|1|小文字に変換|
|2|大文字に変換|

また，パラメータの番号と，その番号によって変換される対象は下記の通りです．

|パラメータの番号|対象|
|:--:|:--|
|1番目|キーワード<br>`program`, `print`, `read`, `write`, `if`, `do`等|
|2番目|組込のモジュール名および手続き名|
|3番目|論理演算子（`.and., .or., .not.`）<br>クラシカルな関係演算子（`.lt., .le., .gt., .ge., .eq., .ne.`）|
|4番目|組込の定数，精度を指定する指数記号（eやd）|

詳しくは後述しますが，この`--case`オプションを設定ファイルに記述する際には注意が必要です．fprettifyの標準の設定ファイル（.fprettify.rc）に記述しても反映されませんが，`--config-file`オプションで設定ファイルを指定すると，有効になります．

本記事のサンプルプログラムは，全て小文字で書いてあるので，`--case`のパラメータとして`2`を与えた結果について示します．

- 入力

<details><summary>main.f90</summary><div>

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = cos(pi/4d0)
    vec%y = sin(pi/4d0)
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

</div></details>

入力は，上で示したソースと同じです．
<br>

- `--indent 4 --case 2 0 0 0`

<details><summary>main.f90</summary><div>

```Fortran
PROGRAM main
    USE, INTRINSIC :: iso_fortran_env
    USE :: type_vector2
    IMPLICIT NONE
    REAL(real64), PARAMETER :: pi = acos(-1d0)

    INTEGER(int32) :: num1, num2!input number 1 and 2
    INTEGER(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    REAL(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    TYPE(vector2) :: vec

    WRITE (*, '(a,x)', advance="no") "input num1"
    READ *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: DO
        WRITE (*, '(a,x)', advance="no") "input num2 (>=0)"
        READ *, num2
        IF (num2 == 0 .and. num2 < 0) THEN
            CYCLE get_num2
        END IF
        IF (num2 > 0) EXIT get_num2
    END DO get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1

    PRINT '(i0,"+",i0,"=",i0)', num1, num2, add
    PRINT '(i0,"-",i0,"=",i0)', num1, num2, sub
    PRINT '(i0,"*",i0,"=",i0)', num1, num2, mul
    PRINT '(i0,"/",i0,"=",g0)', num1, num2, div
    PRINT '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = cos(pi/4d0)
    vec%y = sin(pi/4d0)
    PRINT *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    CALL clear(vec)
END PROGRAM main
```

</div></details>

1番目のパラメータに`2`を与えると，キーワードが全て大文字になります．これが一番見た目に与える影響が大きいと思われます．
<br>

- `--indent 4 --case 0 2 0 0`

<details><summary>main.f90</summary><div>

```Fortran
program main
    use, intrinsic :: ISO_FORTRAN_ENV
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = ACOS(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = DBLE(num1)/DBLE(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = COS(pi/4d0)
    vec%y = SIN(pi/4d0)
    print *, "hypotenuse=", HYPOT(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

</div></details>

2番目のパラメータは，組込のモジュール（`iso_fortran_env`等）や手続きの名前（`sin`, `cos`, `hypot`, `dble`等）を置き換えます．ユーザ定義のモジュールである`type_vector2`や，ユーザ定義関数`clear`は置き換えられません．また，`vector2()`はFortranの機能によって自動的に作成される関数ですが，大文字には置き換えられていません．

どの程度組込手続きを利用しているかにもよりますが，影響はさほど大きくありません．使うとすれば，1番目（キーワード）と同時に指定するくらいでしょうか．
<br>


- `--indent 4 --case 0 0 2 0 --enable-replacements`

<details><summary>main.f90</summary><div>

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 .EQ. 0 .AND. num2 .LT. 0) then
            cycle get_num2
        end if
        if (num2 .GT. 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0d0, 0d0)
    vec%x = cos(pi/4d0)
    vec%y = sin(pi/4d0)
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

</div></details>

3番目のパラメータは，組込の関係演算子や論理演算子を置き換えます．モダンな関係演算子を用いている場合は，このパラメータはほとんど影響がないように思われます．
<br>


- `--indent 4 --case 0 0 0 2`

<details><summary>main.f90</summary><div>

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(REAL64), parameter :: pi = acos(-1D0)

    integer(INT32) :: num1, num2!input number 1 and 2
    integer(INT32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(REAL64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec

    write (*, '(a,x)', advance="no") "input num1"
    read *, num1

    !num2 must be greater than 0.
    !repeat do-loop infinitely until num2(>0) is input
    get_num2: do
        write (*, '(a,x)', advance="no") "input num2 (>=0)"
        read *, num2
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2
    end do get_num2

    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1

    print '(i0,"+",i0,"=",i0)', num1, num2, add
    print '(i0,"-",i0,"=",i0)', num1, num2, sub
    print '(i0,"*",i0,"=",i0)', num1, num2, mul
    print '(i0,"/",i0,"=",g0)', num1, num2, div
    print '("-(",i0,")=",i0)', num1, neg

    vec = vector2(0D0, 0D0)
    vec%x = cos(pi/4D0)
    vec%y = sin(pi/4D0)
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
    call clear(vec)
end program main
```

</div></details>

4番目のパラメータは，組込の定数を置き換えます．このとき，リテラルの精度を指定する指数記号も置き換えられます．どちらかというと，組込でないユーザ指定の定数の大文字↔小文字変換の方が，需要があるように思います．

## コメント前のスペース
`--strip-comments`を付与すると，文の後ろに書かれたコメントの前に空白が入ります．
単独行に書かれたコメントに対しては，何も行われません．

- 入力

```Fortran
    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation
    !num2 must be greater than 0.
```

- `--indent 4 --whitespace 4`

```Fortran
    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation
    !num2 must be greater than 0.
```

- `--indent 4 --whitespace 4 --strip-comments`

```Fortran
    integer(int32) :: num1, num2 !input number 1 and 2
    integer(int32) :: add, sub, mul, neg !add, subtruct, multiply, negate operations
    real(real64) :: div !division operation
    !num2 must be greater than 0.
```

文の末尾に，変数名（`num2`, `neg`, `div`）との間に空白を置かずにコメントを書いています．`--whitespace 4`を指定しても，変数名とコメントの間に空白は入りませんが，` --strip-comments`も追加すると，変数名とコメントの間に空白が入れられます．

一方で，空白しかない行（空白行）では，何も行われません．また，コメント記号`!`とコメントの間にも空白は入りません．

## fprettifyの実行に関係するオプション
### `--line-length`
fprettifyが処理できる1行の長さ（文字数）を指定するオプションです．`--line-length`の後に，空白を開けて文字数を指定します．指定しない場合は，132文字が標準の文字数として扱われ，132文字を超える行は処理されません．

入力ファイルの11行目は132文字を超えていたので，コメントアウトしていました．

- 入力

```Fortran
program main
    use, intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64), parameter :: pi = acos(-1d0)

    integer(int32) :: num1, num2!input number 1 and 2
    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    ! integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec
    !以下省略
```

- `--indent 4 --whitespace 0`

アンコメントしてfprettifyで処理しようとすると，警告が出力されます．また，当該行は整形されず，カンマの後ろの空白が削除されていません．

```Fortran
program main
    use,intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64),parameter :: pi=acos(-1d0)

    integer(int32) :: num1,num2!input number 1 and 2
    integer(int32) :: add,sub,mul,neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec
    !以下省略
```

```console
WARNING: File main.f90, line 11
    auto indentation failed due to chars limit, line should be split (limit: 132)
```

コマンドラインからfprettifyを実行する場合は，当該行は整形されないだけですが，VSCodeでこの状況になると，当該行のインデントが削除されてしまいます．

- `--indent 4 --whitespace 0 --line-length 256`

`--line-length`に続けて1行の上限文字数を指定すると，警告の出ていた11行目が整形されるようになります．

```Fortran
program main
    use,intrinsic :: iso_fortran_env
    use :: type_vector2
    implicit none
    real(real64),parameter :: pi=acos(-1d0)

    integer(int32) :: num1,num2!input number 1 and 2
    integer(int32) :: add,sub,mul,neg!add, subtruct, multiply, negate operations
    real(real64) :: div!division operation

    integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1,longlonglonglonglonglonglonglonglonglonglong_name_variable2
    type(vector2) :: vec
    !以下省略
```

### `--silent`もしくは`--no-report-errors`
`--line-length`の項目で見たように，fprettifyでは，問題が生じた場合，画面にメッセージを出力します．
`--silent`もしくは`--no-report-errors`オプションを付与すると，メッセージの出力が抑制されます[^vscode_S]．

[^vscode_S]: vscode-modern-fortran-formatterのオプションに，`-S`, `--silent`, `--no-report-errors`を指定してはいけない．

fprettifyでは，このオプションはエディタとの統合を見据えて設けられています．実際に，Vimからfprettifyを利用する場合は，このオプションを`.vimrc`に記述するよう説明されています．

一方，vscode-modern-fortran-formatter拡張を介してVSCodeから利用する場合，**このオプションは指定しない**ように記されています．VSCodeの場合，出力コンソールに警告を表示する機能があり，vscode-modern-fortran-formatterもその機能を利用しているためだと思われます．

ソース内に`!&>`だけを書いて整形すると，VSCodeの出力コンソールに以下のようなエラーメッセージが出力されます．
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/59cc1f75-8e3b-0ef7-5e15-ee4f9a6617a0.png)

### `--stdout`
ソースファイルを上書きせずに，整形結果を標準出力に出力します．エラーが生じている場合は，エラーメッセージが先に出力されたのち，標準出力に整形結果が出力されます．

### `--diff`
ソースファイルを上書きせずに，整形結果をstdoutに出力します．その際，整形前に`-`，整形結果に`+`が付けられます．

<details><summary>コンソール出力(--indent 4 --whitespace 0 --line-length 256 --diff)</summary><div>

```console
--- main.f90
+++ main.f90
@@ -1,46 +1,46 @@
 program main
-    use, intrinsic :: iso_fortran_env
+    use,intrinsic :: iso_fortran_env
     use :: type_vector2
     implicit none
-    real(real64), parameter :: pi = acos(-1d0)
+    real(real64),parameter :: pi=acos(-1d0)

-    integer(int32) :: num1, num2!input number 1 and 2
-    integer(int32) :: add, sub, mul, neg!add, subtruct, multiply, negate operations
+    integer(int32) :: num1,num2!input number 1 and 2
+    integer(int32) :: add,sub,mul,neg!add, subtruct, multiply, negate operations
     real(real64) :: div!division operation

-    integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1, longlonglonglonglonglonglonglonglonglonglong_name_variable2
+    integer(int32) :: longlonglonglonglonglonglonglonglonglonglong_name_variable1,longlonglonglonglonglonglonglonglonglonglong_name_variable2
     type(vector2) :: vec

-    write (*, '(a,x)', advance="no") "input num1"
-    read *, num1
+    write(*,'(a,x)',advance="no") "input num1"
+    read*,num1

     !num2 must be greater than 0.
     !repeat do-loop infinitely until num2(>0) is input
     get_num2: do
-        write (*, '(a,x)', advance="no") "input num2 (>=0)"
-        read *, num2
-        if (num2 == 0 .and. num2 < 0) then
+        write(*,'(a,x)',advance="no") "input num2 (>=0)"
+        read*,num2
+        if(num2==0.and.num2<0) then
             cycle get_num2
-        end if
-        if (num2 > 0) exit get_num2
-    end do get_num2
+        endif
+        if(num2>0) exit get_num2
+    enddo get_num2

-    add = num1 + num2
-    sub = num1 - num2
-    mul = num1*num2
-    div = dble(num1)/dble(num2)
-    neg = -num1
+    add=num1+num2
+    sub=num1-num2
+    mul=num1*num2
+    div=dble(num1)/dble(num2)
+    neg=-num1

-    print '(i0,"+",i0,"=",i0)', num1, num2, add
-    print '(i0,"-",i0,"=",i0)', num1, num2, sub
-    print '(i0,"*",i0,"=",i0)', num1, num2, mul
-    print '(i0,"/",i0,"=",g0)', num1, num2, div
-    print '("-(",i0,")=",i0)', num1, neg
+    print '(i0,"+",i0,"=",i0)',num1,num2,add
+    print '(i0,"-",i0,"=",i0)',num1,num2,sub
+    print '(i0,"*",i0,"=",i0)',num1,num2,mul
+    print '(i0,"/",i0,"=",g0)',num1,num2,div
+    print '("-(",i0,")=",i0)',num1,neg

-    vec = vector2(0d0, 0d0)
-    vec%x = cos(pi/4d0)
-    vec%y = sin(pi/4d0)
-    print *, "hypotenuse=", hypot(vec%x, &
-                                  vec%y)
+    vec=vector2(0d0,0d0)
+    vec%x=cos(pi/4d0)
+    vec%y=sin(pi/4d0)
+    print*,"hypotenuse=",hypot(vec%x, &
+                               vec%y)
     call clear(vec)
-end program main
+endprogram main
```

</div></details>

###  `--recursive`

`--recursive`オプションを付与し，ファイルの代わりにフォルダパスを続けて指定することで，指定したフォルダとサブフォルダにあるファイルを一括して整形するためのオプションです[^vscode_r]．このとき，fprettifyは以下の拡張子を持つファイルを整形します．

[^vscode_r]: vscode-modern-fortran-formatterのオプションに，`-r`, `--recursive`を指定してはいけない．

- `.f`, `.F`
- `.for`, `.FOR`
- `.ftn`, `.FTN`
- `.f90`, `.F90`
- `.f95`, `.F95`
- `.f03`, `.F03`
- `.fpp`, `.FPP`

上でも示したように，以下のようにファイルが配置されている状況を考えます．

```
src
├── modules
│   ├── mod1.f90
│   ├── mod2.f08
│   └── type_vector2.f90
└── main.f90
```

`src`フォルダ以下のファイルを整形するには，例えば`src`で以下のようにオプションを付けてfprettifyを実行します．

```console
src>fprettify --indent 4 --recursive .
```

`.`はカレントフォルダを表すので，`src`とそのサブフォルダにあるファイルを対象とし，`main.f90`, `modules\mod1.f90`, `module\type_vector2.f90`が整形されます．`modules\mod2.f08`は，拡張子がfprettifyの整形対象ではないので，整形されません．

処理する拡張子を追加したい場合は，`--fortran`オプションを利用します．また，fprettifyで整形したくないフォルダやファイルがある場合は，`--exclude`オプションで対象を指定します．

### `--fortran`
`--fortran`に続けて拡張子を指定することで，その拡張子をfprettifyの処理の対象とします．`--fortran`はかなり厄介なオプションで，指定された拡張子でfprettifyが処理の対象としている拡張子のリストを**上書き**します．

つまり，`--fortran f08`とすると，**拡張子`.f08`を持つファイルのみが整形対象になっていまいます**．`--fortran f90 f08`のように複数のパラメータを取ることができないので，`--fortran f90 --fortran f08`のように複数回オプションを記述する必要があります．

上の例で，`src`以下の全てのソースファイルを整形するには，以下のようにオプションを付けて実行します．

```console
src>fprettify --indent 4 --fortran f90 --fortran f08 --recursive .
```

後述しますが，設定ファイルに記述する場合は，複数のパラメータを取ることができます．

Fortranでは規格で拡張子を決めていませんが，個人的にはgfortran方言の`f03`や`f08`は不要で，固定形式の`f`，自由形式の`f90`で十分だと考えています．また，Fortran規格を厳格に使い分けている人もほぼいないと思われますし，規格に沿っているかの検査は`-std=f2008`等のコンパイルオプションで行うべきでだと考えています．

### `--exclude`
`--recursive`オプションを付けて実行した際に，整形を除外するファイルやフォルダを指定します．

- `--recursive . --fortran f90 --fortran f08 --exclude m*`とすると，`m`から始まる全てのファイル，フォルダが除外されます．そのため，本記事の例ではどのファイルも整形されません．（`modules`フォルダが除外されるので，`modules\type_vector2.f90`も整形対象になりません）

- `--recursive . --fortran f90 --fortran f08 --exclude m*.f??`とすると，`main.f90`, `modules\mod1.f90`, `modules\mod2.f08`が除外され，`modules\type_vector2.f90`のみが整形対象になります．

`--exclude`も複数のオプションを指定できないので，複数の条件を記述する際は，`--exclude mod1.f90 --exclude mod2.f90`などとオプションを複数回記述する必要があります．

### `--config-file`
オプションが記述された設定ファイルを読み込むためのオプションです．
設定ファイルはConfigArgParseを利用しているので，様々な書き方（`key value`, `key: value`, `key=value`）が可能です．例えば，`--indent 4 --line-length 256 --whitespace 0 --enable-decl --whitespace-decl false --case 1 1 1 1 --fortran f90 --fortran f08 --exclude mod1.f90 --exclude mod2.f08`は以下のように設定ファイルに書くことができます．

```
indent: 4
line-length 256
whitespace=0
--enable-decl
whitespace-decl=false
case [1,1,1,1]
fortran: [f90,f08]
exclude=[mod1.f90, mod2.f08]
```

このように不揃いに書く必要性はないので，実用上はいずれかの書式に揃えた方が良いでしょう．

```:fprettify.rc
indent=4
line-length=256
whitespace=0
enable-decl
whitespace-decl=false
case=[1,1,1,1]
fortran=[f90,f08]
exclude=[mod1.f90, mod2.f08]
```

設定ファイル名に`fprettify.rc`と名前を付けて`src`に置き，その設定ファイルを参照して整形するには，例えば以下のようにfprettifyを実行します．

```
src
├── modules
│   ├── mod1.f90
│   ├── mod2.f08
│   └── type_vector2.f90
├── main.f90
└── fprettify.rc
```

```console
src>fprettify --config-file fprettify.rc --recursive .
```

このとき，`main.f90`, `modules\type_vector2.f90`が整形されます．

設定ファイルとコマンドラインで同じオプションを指定した場合は，**コマンドラインの入力が優先されます**．

#### 標準の設定ファイル
`.fprettify.rc`という名前の設定ファイルがユーザディレクトリ（Windowsでは`C:\Users\ユーザ名`，Unix系OSでは`~`）あるいはソースと同じディレクトリに存在する場合，その設定ファイルを自動的に読み込んでくれます．
ただし，複数のパラメータを指定するオプションは，上手く読み込まれません．上で作成した`fprettify.rc`を`.fprettify.rc`に変更し，

```console
src>fprettify --recursive .
```

として実行した場合，`case=[1,1,1,1]`, `fortran=[f90,f08]`, `exclude=[mod1.f90, mod2.f08]`は正しく機能しません．しかし，`--config-file`オプションを用いて`.fprettify.rc`を指定して読み込むと，正しく機能します．

```console
src>fprettify --config-file .fprettify.rc --recursive .
```

`.fprettify.rc`がソースファイルと同じディレクトリにない場合は，その親ディレクトリも検索し，そこに`.fprettify.rc`があれば，それを読み込んでくれます．

```
src>cd modules
modules>fprettify --recursive .
```

としてfprettifyを実行すると，`modules`の親ディレクトリ`src`にある`.fprettify.rc`を読み込み，その設定を利用して`modules\mod1.f90`, `modules\type_vector2.f90`を整形します．

ただし，先述したとおり，`.fprettify.rc`を自動で読み込む場合には，複数のパラメータを指定するオプションはうまく機能しません．`exclude=[mod1.f90, mod2.f08]`が機能しないので，`modules\mod1.f90`と`modules\mod2.f08`は整形対象から除外されず，また`fortran=[f90,f08]`が機能しないので`modules\mod2.f08`は整形の対象になりません（fprettifyが整形対象とする拡張子に`f08`が追加されない）．そのため，`modules\mod1.f90`, `modules\type_vector2.f90`が整形されます．

# まとめ
モダンFortran用のソース自動整形ツールfprettifyを紹介しました．
人はミスをします．人力でソースを整形せずに，ツールに頼れる所はどんどん頼っていきましょう．