概要
モダン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の都合で生じる記述のゆれ(キーワード等の大文字/小文字,programやmodule内でのインデント,多重ループ内のインデント)や,拡張子の追加にも対応しています.また,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を連携させるには,
- Modern Fortran拡張のインストール
- インストールしたVSCode拡張の設定
- 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
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
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
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
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 typeやsubroutine~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のブロックに対するインデントをしなくなります.
- 入力(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方言の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]
このように不揃いに書く必要性はないので,実用上はいずれかの書式に揃えた方が良いでしょう.
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.f90とmodules\mod2.f08は整形対象から除外されず,またfortran=[f90,f08]が機能しないのでmodules\mod2.f08は整形の対象になりません(fprettifyが整形対象とする拡張子にf08が追加されない).そのため,modules\mod1.f90, modules\type_vector2.f90が整形されます.
まとめ
モダンFortran用のソース自動整形ツールfprettifyを紹介しました.
人はミスをします.人力でソースを整形せずに,ツールに頼れる所はどんどん頼っていきましょう.
-
vscode-modern-fortran-formatterのオプションに,
-hあるいは--helpを指定してはいけない. ↩ -
2と4で分かれているから間を取って3にしたのではなく,固定形式の名残だと考えられます.固定形式では,1行72文字という制限があり,7文字目から文や関数などを記述するようになっているので,インデント幅を3にすることがありました.インデント幅を3にしておけば,行頭から2回インデントを入れれば7文字目に移動できます. ↩
-
fprettifyのヘルプでは,Cスタイルと称されていますが,FortranにはCと互換性がない関係演算子(
/=)があるので,Cスタイルという表現は正しくないように思います. ↩ -
エディタにシンタックスハイライトや補完などの機能がなくても,キーワードや組込の手続き名を識別するためと考えられます. ↩
-
vscode-modern-fortran-formatterのオプションに,
-S,--silent,--no-report-errorsを指定してはいけない. ↩ -
vscode-modern-fortran-formatterのオプションに,
-r,--recursiveを指定してはいけない. ↩



