背景
コマンドライン引数を得る方法として,Fortranではcommand_argument_count()
関数で引数の数を取得した上でget_command_argument()
サブルーチンで特定のコマンドライン引数値を得ることができます.この方法はシンプルでよいですが,コマンドライン引数一覧をオブジェクトとして保持することはできず,特定のコマンドライン引数を得るためには毎回これらの手続を呼ぶ必要があります.
一方で,Pythonでは,sys.argv()
関数によりコマンドライン引数一覧をリストとして得られます.そのため,コマンドライン引数一覧を頻繁に参照する場合にはより便利です.
そこで,Fortranでもコマンドライン引数をリストとして得るべく,実装しました.
リストクラスの作成
複数のデータ要素をもつ1次元配列はリストと呼ばれます.リストの各要素には1つのインデックスが対応しており,インデックスを参照することで要素を受け取ることができます.
Fortranでリストを実装する方法は幾通りもありえますが,ここでは要素を文字型配列として持つ方法を採用します.
まず,リストの要素をlist_content_
型の派生型として定義します.
type :: List_content_
character(:),allocatable :: char
end type
次に,リスト要素の配列をメンバーにもつリストList_
型配列を作成します.
併せて,初期化用サブルーチンとゲッターを定義しておきます.
type :: List_
type(List_content_), allocatable :: content(:)
contains
procedure, public :: new => new_list_listclass
procedure, public :: get => get_list_content_listclass
end type
また,コマンドライン引数取得用の関数も定義しておきます.
interface argv
module procedure argv_get_cmd_args_as_list
end interface
以下が完全な実装です.
module ListClass
use iso_fortran_env
implicit none
type :: List_content_
character(:),allocatable :: char
end type
type :: List_
type(List_content_), allocatable :: content(:)
contains
procedure, public :: new => new_list_listclass
procedure, public :: get => get_list_content_listclass
end type
interface argv
module procedure argv_get_cmd_args_as_list
end interface
contains
! #####################################################
pure subroutine new_list_listclass(this, length)
class(List_), intent(inout) :: this
integer(int32), intent(in) :: length
integer(int32) :: i
if (allocated(this%content)) then
deallocate (this%content)
end if
allocate (this%content(length))
do i = 1, length
this%content(i)%char = ""
end do
end subroutine
! #####################################################
! #####################################################
pure function get_list_content_listclass(this, idx) result(ret)
class(List_), intent(in) :: this
integeR(int32), intent(in) :: idx
character(:), allocatable :: ret
if (allocated(this%content))then
ret = this%content(idx)%char
elseif (.not. allocated(this%content)) then
ret = ""
return
end if
end function
! #####################################################
! #####################################################
function argv_get_cmd_args_as_list() result(ret)
type(List_) :: ret
integer(int32) ::i, n, length, status
character(:),allocatable :: this_arg
!character(:),allocatable ::line
n = command_argument_count()
call ret%new(n)
do i = 1,n
call get_command_argument(i,length=length,status=status)
if(allocated(this_arg))then
deallocate(this_arg)
endif
allocate(character(length)::this_arg)
call get_command_argument(i,this_arg,status=status)
ret%content(i)%char = this_arg
enddo
end function
! #####################################################
end module ListClass
用例
第一引数,第二引数を取得してみます.
program main
use ListClass
implicit none
type(List_) :: args
args = argv()
print *, args%get(1)
print *, args%get(2)
end program main
./a.out hello world
と実行すると,無事,以下の出力を得ます.
$ ./a.out hello world
hello
world
また,コマンドライン引数を単発で取得する場合にも,ゲッターをユーザー定義演算子化すると,やや明快になります.
interface operator(.get.)
module procedure get_element_of_listclass
end interface
! #####################################################
function get_element_of_listclass(this_list,idx) result(ret)
character(:),allocatable :: ret
type(List_),intent(in) :: this_list
integer(int32),intent(in) :: idx
ret = ""
if(allocated(this_list%content) )then
if (size(this_list%content) >= idx )then
ret = this_list%content(idx)%char
endif
endif
end function
! #####################################################
以下のように実行できます.
program main
use ListClass
implicit none
print *, argv() .get. 1
print *, argv() .get. 2
end program main
出力は同じく以下の通りとなります.
$ ./a.out hello world
hello
world
まとめと拡張
簡単な派生型を作ることで,コマンドライン引数のリストのようなものを得られるようにしました.
ここで,
public :: assignment(=)
interface assignment(=)
module procedure assign_real64, assign_int32, ...
end interface
のようにして,文字型配列を浮動小数点数型,整数型等に変換できるようにしておくと,よりリストに近いものになります.
また,簡単な拡張でsplit
等,文字列とリストを相互に変換することもでき,pythonライクな使用感が得られます.
Appendix
1ファイルにまとめたコードはこちらになります.
module ListClass
use iso_fortran_env
implicit none
!type :: List_content_
! character(:),allocatable :: char
!end type
type :: List_content_
character(:),allocatable :: char
end type
type :: List_
type(List_content_), allocatable :: content(:)
contains
procedure, public :: new => new_list_listclass
procedure, public :: get => get_list_content_listclass
end type
interface argv
module procedure argv_get_cmd_args_as_list
end interface
interface operator(.get.)
module procedure get_element_of_listclass
end interface
contains
! #####################################################
pure subroutine new_list_listclass(this, length)
class(List_), intent(inout) :: this
integer(int32), intent(in) :: length
integer(int32) :: i
if (allocated(this%content)) then
deallocate (this%content)
end if
allocate (this%content(length))
do i = 1, length
this%content(i)%char = ""
end do
end subroutine
! #####################################################
! #####################################################
pure function get_list_content_listclass(this, idx) result(ret)
class(List_), intent(in) :: this
integeR(int32), intent(in) :: idx
character(:), allocatable :: ret
if (allocated(this%content))then
ret = this%content(idx)%char
elseif (.not. allocated(this%content)) then
ret = ""
return
end if
end function
! #####################################################
! #####################################################
function argv_get_cmd_args_as_list() result(ret)
type(List_) :: ret
integer(int32) ::i, n, length, status
character(:),allocatable :: this_arg
!character(:),allocatable ::line
n = command_argument_count()
call ret%new(n)
do i = 1,n
call get_command_argument(i,length=length,status=status)
if(allocated(this_arg))then
deallocate(this_arg)
endif
allocate(character(length)::this_arg)
call get_command_argument(i,this_arg,status=status)
ret%content(i)%char = this_arg
enddo
end function
! #####################################################
! #####################################################
function get_element_of_listclass(this_list,idx) result(ret)
character(:),allocatable :: ret
type(List_),intent(in) :: this_list
integer(int32),intent(in) :: idx
ret = ""
if(allocated(this_list%content) )then
if (size(this_list%content) >= idx )then
ret = this_list%content(idx)%char
endif
endif
end function
! #####################################################
end module ListClass
program main
use ListClass
implicit none
type(List_) :: args
args = argv()
print *, args%get(1)
print *, args%get(2)
print *, argv() .get. 1
print *, argv() .get. 2
end program main