Fortranでテキストデータを丸ごと読み込んで保持する

# 何をするの
[オブジェクト指向Fortran](https://qiita.com/cometscome_phys/items/f87080286c6fdf72e49a)を使って、テキストファイルを読み込んでそれを変数として保持するコードを書きます。Fortran2008までの機能を使います。

## 使ったコンパイラ　
- gcc version 13.2.0 (Homebrew GCC 13.2.0) 


# はじめに

Fortranを使って数値計算を行う際、何らかのインプットファイルを読み込んで使用することはあるかと思います。例えば、[namelist](https://cometscome.github.io/JuliaFromFortran/build/chapter2/02/#namelist)を使えば簡単にパラメータを読み込むことができます。しかし、namelistは読み込む時点で読み出し用の変数が定義されていなければならず、後で読み込んだり必要なものだけ読み込んだりするというときに不便な場合があります。そして、様々なシミュレーションソフトウェアでは独自のテキストファイルをインプットとして使っています。

例えば、分子動力学ソフトウェア[PIMD](https://ccse.jaea.go.jp/software/PIMD/index.jp.html)のフォーマットは
```:input.dat
<input_style>
NEW

<method>
STATIC

<ipotential>
EAM

<iboundary>
1
 33.25917740   0.00000000   0.00000000
  0.00000000  33.25917740   0.00000000
  0.00000000   0.00000000  33.25917740

```
のようなもので、<と>で囲まれたタグでパラメータが定義されています。

このようなテキストデータを、最初の時点で読み込んでおき、一つの変数として使いやすい形に取り回せるようにすることを目的とします。

# コード
テキストデータを読み込んで保持するために構造体を定義しておきます。
```fortran
    type type_textdata
        integer(int64):: numlines
        character(len=maxlength),dimension(:),allocatable::data
        contains
        procedure:: print => print_text
        procedure::search_tag
        procedure::get_tagdata
    end type
```
[こちら](https://qiita.com/cometscome_phys/items/f87080286c6fdf72e49a)で説明をしていますが、```contains```以下の記述がクラスに所属する関数やサブルーチンを意味しています。

全体のコードを先に示しておきます。モジュールとして、
```fortran
module mod_textdata
    use iso_fortran_env
    integer(int64),parameter::maxlength = 200
    type type_textdata
        integer(int64):: numlines
        character(len=maxlength),dimension(:),allocatable::data
        contains
        procedure:: print => print_text
        procedure::search_tag
        procedure::get_tagdata
    end type

    interface type_textdata
        module procedure read_file
    end interface type_textdata

    contains

    type(type_textdata) function read_file(filename) result(textdata)
        implicit none
        character(len=*),intent(in)::filename
        integer(int8)::iounit,ios
        integer(int64)::count = 0
        integer(int64)::i
        character(len=maxlength)::line
        open(newunit=iounit,file=filename,status='old')
        do
            read(iounit,'(A)',iostat=ios) line
            if(ios /= 0) exit
            count = count + 1
        end do
        close(iounit)
        write(*,*) "numlines", count
        textdata%numlines = count
        allocate(textdata%data(count))
        open(newunit=iounit,file=filename,status='old')
        do i=1,count
            read(iounit,'(A)',iostat=ios) line
            textdata%data(i) = line
        end do
        close(iounit)
    end

    subroutine print_text(self)
        implicit none
        class(type_textdata),intent(in)::self
        integer(int64)::i
        do i=1,self%numlines
            write(*,*) trim(self%data(i))
        end do
    end subroutine print_text

    integer(int64) function search_tag(self,tag) result(position)
        implicit none
        class(type_textdata),intent(in)::self
        character(len=*),intent(in)::tag
        integer(int32)::tag_length
        integer(int64)::i,k
        position = 0
        tag_length = len_trim(tag)
        write(*,*) tag_length
        loop:&
        do i=1,self%numlines
            k = index(self%data(i)(1:tag_length), tag(1:tag_length))
            if (k .ge. 1) then 
                position = i
                exit loop
            end if
        end do loop
        write(*,*) position,k
    end function search_tag

    subroutine get_tagdata(self,tag,tagdata,numlines)
        implicit none
        class(type_textdata),intent(in)::self
        character(len=*),intent(in)::tag
        character(len=maxlength),intent(out)::tagdata(:)
        integer(int64),intent(out)::numlines
        integer(int64)::position,numtagdatalines,i,count
        tagdata = ""
        numlines = 0
        position = self%search_tag(tag)
        if (position == 0) return

        numtagdatalines = ubound(tagdata,1)
        count = 0

        loop:&
        do i=position+1,position+numtagdatalines-1
            if (self%data(i)(1:1) == "<") then
                write(*,*) "error: next tag is detected.",self%data(i)
                exit loop
            end if
            if (i > self%numlines) exit loop
            if (self%data(i) /= "") then
                count = count + 1
                tagdata(count) = self%data(i)
            end if
        end do loop
        numlines = count
    end subroutine get_tagdata
end
```
を定義し、テストのコードとして、
```fortran
program main
    use iso_fortran_env
    use mod_textdata,only:type_textdata,read_file
    implicit none
    type(type_textdata)::textdata
    character(len=100)::filename
    integer(int64)::position,numlines,i
    character(len=200)::tagdata(10)


    write(*,*) "reading data"
    filename = "input.dat"
    textdata = type_textdata(filename)
    call textdata%print()
    position =  textdata%search_tag("<method>")
    call textdata%get_tagdata("<method>",tagdata,numlines)
    write(*,*) "<method>: num. of lines is ",numlines
    do i=1,numlines
        write(*,*) trim(tagdata(i))
    end do
    call textdata%get_tagdata("<iboundary>",tagdata,numlines)
    write(*,*) "<iboundary>: num. of lines is ",numlines
    do i=1,numlines
        write(*,*) trim(tagdata(i))
    end do
end program
```
を用意します。インプットファイル名は```input.dat```としました。

## 解説
```data```は200の長さの文字列のallocatableな配列です。```read_file```でテキストデータの行数を取得し、配列の長さを定義しています。

```search_tag```は任意の文字列が現れる行数を見つけるもので、なければ0が返ります。```get_tagdata```は<>で囲まれているデータのブロックを取り出しているもので、読んでいただければわかるかと思います。

```textdata%get_tagdata```はオブジェクト指向による文法で、```get_tagdata```というサブルーチンを呼び出しています。

```open(newunit=iounit,file=filename,status='old')```の```newunit```はFortran2008の機能で、使われていない装置番号を割り当ててくれます。


## 実行結果

コードを実行すると、

```
./a.out          
 reading data
 numlines                   14
 <input_style>
 NEW
 
 <method>
 STATIC
 
 <ipotential>
 EAM
 
 <iboundary>
 1
  33.25917740   0.00000000   0.00000000
   0.00000000  33.25917740   0.00000000
   0.00000000   0.00000000  33.25917740
           8
                    4                    1
           8
                    4                    1
 error: next tag is detected.<ipotential>                                                                                                                                                                                            
 <method>: num. of lines is                     1
 STATIC
          11
                   10                    1
 <iboundary>: num. of lines is                     4
 1
  33.25917740   0.00000000   0.00000000
   0.00000000  33.25917740   0.00000000
   0.00000000   0.00000000  33.25917740
```
となり、ちゃんと読み込めていることがわかります。