LoginSignup
17
15

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

Last updated at Posted at 2021-03-27

概要

モダン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は,Patrick Seewald氏が開発している,モダンFortran向けのソースファイル自動整形ツールです.

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

インストール

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

pip install fprettify
conda install -c conda-forge fprettify

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

ソースファイルの整形

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

fprettify main.f90

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

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

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で指定します.


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"と区切る必要があります.

Modern Fortranの拡張についてはこの記事を参考にしてください.

VSCodeの設定

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

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

局所的にfprettifyで整形をしたくない場合

ソースファイルの中で,fprettifyで整形したくない場合には,文末に!&を付けるか,整形したくない範囲を!&<!&>で囲みます.

例えば,以下のように使います.

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)  !&

または

!&<
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段インデントしたいというような場合に利用できる小ネタです.

    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
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
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
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
modules\mod2.f08
module mod2
    use, intrinsic :: iso_fortran_env
    implicit none

end module mod2

fprettifyのオプション

fprettifyに-hあるいは--helpオプションを付けて実行すると,オプションの一覧とその効果を確認できます1

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オプションの後にスペースを空けて数字を指定します.

fprettify --indent 2 main.f90
main.f90(--indent 2)
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

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

以降では,--indent 4を指定します.

インデントを無効化するオプション

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

program,module内のインデントの無効化

program~end programや,module~end moduleに対してインデントをせずに,下記のようにする流儀もあります.

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オプションです.上記の例は

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

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

厳密なインデント

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

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オプションを付与すると,下記のようにインデントするようになります.

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のブロックに対するインデントをしなくなります.

    #:if DEBUG > 0
        print *, "Some debug information"
    #:endif
  • --indent 4 --whitespace 0
    #:if DEBUG > 0
        print*,"Some debug information"
    #:endif
  • --indent 4 --whitespace 0 --disable-fypp
    #:if DEBUG > 0
    print*,"Some debug information"
    #:endif

空白の制御

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

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

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

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

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

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

main.f90 (--indent 4 --whitespace 0)
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

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

main.f90 (--indent 4 --whitespace 1)
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
main.f90 (--indent 4 --whitespace 2)
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
main.f90 (--indent 4 --whitespace 3)
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
main.f90 (--indent 4 --whitespace 4)
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

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

個別の空白の制御

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

--whitespace-comma

--whitespace-comma trueとすると,カンマの後ろに空白が入る.
--whitespace-comma falseとすると,カンマの後ろの空白が削られる.

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

  • 入力
    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
    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
    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とすると,代入演算子の前後の空白が削られる.

  • 入力
    add = num1 + num2
    print '(i0,"+",i0,"=",i0)', num1, num2, add
  • --indent 4 --whitespace 0 --whitespace-assignment false
    add=num1+num2
    print '(i0,"+",i0,"=",i0)',num1,num2,add
  • --indent 4 --whitespace 0 --whitespace-assignment true
    add = num1+num2
    print '(i0,"+",i0,"=",i0)',num1,num2,add

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

--whitespace-relational

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

  • 入力
        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
        if(num2==0.and.num2<0) then
            cycle get_num2
        endif
        if(num2>0) exit get_num2
  • --indent 4 --whitespace 0 --whitespace-relational true
        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とすると,論理演算子の前後の空白が削られる.

  • 入力
        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
        if(num2==0.and.num2<0) then
            cycle get_num2
        endif
        if(num2>0) exit get_num2
  • --indent 4 --whitespace 0 --whitespace-logical true
        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とすると,+-の前後の空白が削られる.

  • 入力
    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1
  • --indent 4 --whitespace 0 --whitespace-plusminus false
    add=num1+num2
    sub=num1-num2
    mul=num1*num2
    div=dble(num1)/dble(num2)
    neg=-num1
  • --indent 4 --whitespace 0 --whitespace-plusminus true
    add=num1 + num2
    sub=num1 - num2
    mul=num1*num2
    div=dble(num1)/dble(num2)
    neg=-num1

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

--whitespace-multdiv

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

  • 入力
    add = num1 + num2
    sub = num1 - num2
    mul = num1*num2
    div = dble(num1)/dble(num2)
    neg = -num1
  • --indent 4 --whitespace 0 --whitespace-multdiv false
    add=num1+num2
    sub=num1-num2
    mul=num1*num2
    div=dble(num1)/dble(num2)
    neg=-num1
  • --indent 4 --whitespace 0 --whitespace-multdiv true
    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の文の名前と*の間の空白が削られる.書式を指定している場合は,空白は削られない.

  • 入力
    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
    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
    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とすると,派生型の成分を指定する%の前後の空白が削られる.

  • 入力
    print *, "hypotenuse=", hypot(vec%x, &
                                  vec%y)
  • --indent 4 --whitespace 0 --whitespace-type false
    print*,"hypotenuse=",hypot(vec%x, &
                               vec%y)
  • --indent 4 --whitespace 0 --whitespace-type true
    print*,"hypotenuse=",hypot(vec % x, &
                               vec % y)

--whitespace-intrinsics

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

  • 入力
    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
   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
    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では::前後に空白が入る.

  • 入力
    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
    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
    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
    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と同時に利用する必要がある.

  • 入力
    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
    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
    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
    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も併せて付与することで,クラシカルな関係演算子がモダンな方に置き換えられるようになります.

  • 入力
        if (num2 == 0 .and. num2 .lt. 0) then
            cycle get_num2
        end if
        if (num2 .gt. 0) exit get_num2
  • --indent 4 --enable-replacements
        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オプションと共に指定することで,クラシカルな関係演算子をモダンな関係演算子に置き換えます3

  • 入力
        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
        if (num2 == 0 .and. num2 < 0) then
            cycle get_num2
        end if
        if (num2 > 0) exit get_num2

大文字小文字の変換

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

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

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

fprettify --case 1 2 0 0 main.f90

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

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

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

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

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

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

  • 入力
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

入力は,上で示したソースと同じです.

  • --indent 4 --case 2 0 0 0
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

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

  • --indent 4 --case 0 2 0 0
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

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

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

  • --indent 4 --case 0 0 2 0 --enable-replacements
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 .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

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

  • --indent 4 --case 0 0 0 2
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

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

コメント前のスペース

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

  • 入力
    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
    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
    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文字を超えていたので,コメントアウトしていました.

  • 入力
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で処理しようとすると,警告が出力されます.また,当該行は整形されず,カンマの後ろの空白が削除されていません.

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
    !以下省略
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行目が整形されるようになります.

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オプションを付与すると,メッセージの出力が抑制されます5

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

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

ソース内に!&>だけを書いて整形すると,VSCodeの出力コンソールに以下のようなエラーメッセージが出力されます.

--stdout

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

--diff

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

コンソール出力(--indent 4 --whitespace 0 --line-length 256 --diff)
--- 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

--recursive

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

  • .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を実行します.

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以下の全てのソースファイルを整形するには,以下のようにオプションを付けて実行します.

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

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

Fortranでは規格で拡張子を決めていませんが,個人的にはgfortran方言のf03f08は不要で,固定形式の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
src>fprettify --config-file fprettify.rc --recursive .

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

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

標準の設定ファイル

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

src>fprettify --recursive .

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

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.f90modules\mod2.f08は整形対象から除外されず,またfortran=[f90,f08]が機能しないのでmodules\mod2.f08は整形の対象になりません(fprettifyが整形対象とする拡張子にf08が追加されない).そのため,modules\mod1.f90, modules\type_vector2.f90が整形されます.

まとめ

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

  1. vscode-modern-fortran-formatterのオプションに,-hあるいは--helpを指定してはいけない.

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

  3. fprettifyのヘルプでは,Cスタイルと称されていますが,FortranにはCと互換性がない関係演算子(/=)があるので,Cスタイルという表現は正しくないように思います.

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

  5. vscode-modern-fortran-formatterのオプションに,-S, --silent, --no-report-errorsを指定してはいけない.

  6. vscode-modern-fortran-formatterのオプションに,-r, --recursiveを指定してはいけない.

17
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15