FortranでJavaのScannerクラスみたいなやつを作る

# 背景
## Fortranで競技プログラミングをする際に...
競技プログラミングでは入力を読み込む必要があります.
面倒な入力形式が稀によくあります.

### 例えば...
- https://atcoder.jp/contests/abc157/tasks/abc157_e
の入力の一部は
```text
(略)
4
1 1 a
2 1 2
2 2 30
1 2 z
```
のように, 3列目の型が文字か数値です.
これらを Q回読み込む必要があります.
`do` ループで回したいのですが, 数値でも文字でも読み込める型は文字列しかないです.
そのため, 文字列で読み込む → 数値に変換するという手間が掛かります.
Fortran の `read` 文はトークンを読み込むことは(おそらく)できないため, このような入力の処理は面倒です.
なので, Java 言語の `Scanner` クラスみたいなものを定義して, トークン毎に文字を読み込みたいです.

# Javaの[Scannerクラス](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Scanner.html)
Java の `Scanner` クラスを使うと, 上の入力は
```java
// (略)
// 大体こんな感じ(コンパイルできるか試していません)
Scanner sc = new Scanner(System.in);
int q = sc.nextInt();
for (int i = 0; i < q; ++i) {
  int t = sc.nextInt();
  if (t == 1) {
    int iq = sc.nextInt();
    String s = sc.next();
    // 処理
  } else { // t = 2
    int lq = sc.nextInt();
    int rq = sc.nextInt();
    // 処理
  }
}
```
のように, `System.in` から空白区切りで数字や文字列を読み込むことができます.
これを Fortran で実現したいです.

# 数値計算的な用途は...ない?
例えば, 数値計算で入力を用意する場合は, 入力の方を自分で決められるため, Fortranで扱い易い形式にすればよいです.
なので, 数値計算においては `scanner` クラスの需要はないと思います.


# 作ってみよう

マイ競プロライブラリにも `src/scanner_m.f90` を追加しました.

https://github.com/osada-yum/Fortran_competitive_library

## ソースコード全体
ソースコードは以下の折りたたみの中身の通りです.

<details><summary> scanner_m.f90 (全体) </summary>

```scanner_m.f90
module scanner_m
  use, intrinsic :: iso_fortran_env
  implicit none
  private
  public scanner
  integer(int32), parameter, public :: max_bufsize = ishft(1_int32, 20) ! ~ 10**6
  !> type(scanner): For reading token like Scanner class in Java.
  !> The default delimiter is space (" ").
  !> If delimiter is space (" "), regard `iostat_eor` as space.
  type :: scanner
     private
     integer(int32) :: pos_ = 0, unit_ = input_unit
     character :: delim_ = " "
     logical :: is_eof_ = .false.
     character(len=max_bufsize) :: buffer_
   contains
     !> setting function
     procedure, pass :: use_delim => use_delim_scanner
     !> read function
     procedure, pass, private :: read_until_delim => read_until_delim_scanner
     !> next functions
     procedure, pass :: next => next_scanner
     generic :: next_int => next_int8, &
          & next_int16, &
          & next_int32, &
          & next_int64
     procedure, pass :: next_int8 => next_int8_scanner
     procedure, pass :: next_int16 => next_int16_scanner
     procedure, pass :: next_int32 => next_int32_scanner
     procedure, pass :: next_int64 => next_int64_scanner
     generic :: next_real => next_real32, &
          & next_real64
     procedure, pass :: next_real32 => next_real32_scanner
     procedure, pass :: next_real64 => next_real64_scanner
     !> state functions
     procedure, pass :: is_eof => is_eof_scanner
  end type scanner
  interface scanner
     module procedure construct_scanner
  end interface scanner
contains
  !> construct_scanner: Constructor for `type(scanner)`.
  !> @param unit An unit of input device.
  !> @return A object of scanner type.
  pure type(scanner) function construct_scanner(unit) result(res)
    integer(int32), intent(in) :: unit
    if (unit == output_unit) &
         & error stop "Error: Scanner does not use `output_unit`."
    if (unit == error_unit) &
         & error stop "Error: Scanner does not use `error_unit`."
    res%unit_ = unit
  end function construct_scanner
  !> use_delim_scanner: Modify delimiter.
  !> @param this `this%delim_` changes.
  !> @param delimiter A character of delimiter.
  pure subroutine use_delim_scanner(this, delimiter)
    class(scanner), intent(inout) :: this
    character, intent(in) :: delimiter
    this%delim_ = delimiter
  end subroutine use_delim_scanner
  !> read_until_delim_scanner: Read from `this%unit_` until reaching `this%delim_`.
  !> @param this `this%buffer_(1:this%pos_)` represents token splited by `this%delim_`.
  impure subroutine read_until_delim_scanner(this)
    class(scanner), intent(inout) :: this
    character :: c
    integer(int32) :: iostat
    if (this%pos_ /= 0) return
    read_until_delim: do
       read (this%unit_, '(a1)', advance="no", iostat=iostat) c
       select case (iostat)
       case (iostat_eor)
          if (this%delim_ == " ") exit
          this%pos_ = this%pos_ + 1
          this%buffer_(this%pos_:this%pos_) = achar(10) !> newline code.
          cycle
       case (iostat_end)
          this%is_eof_ = .true.
          exit
       case (0)
          !> do nothing.
       case default
          write (error_unit, *) iostat
          error stop "Error: Unknown iostat"
       end select
       if (c == this%delim_) then
          if (this%pos_ /= 0) exit
          cycle !> The contiguous `this%delim_`.
       end if
       ! write(error_unit, *) c, this%pos_, iostat, iostat_eor, iostat_end
       this%pos_ = this%pos_ + 1
       this%buffer_(this%pos_:this%pos_) = c
    end do read_until_delim
  end subroutine read_until_delim_scanner
  !> next_scanner: Return token as `character(len=:)` through `res`.
  !> @param this this%pos_ reset 0.
  !> @param res A character of token splited by `this%delim_`.
  impure subroutine next_scanner(this, res)
    class(scanner), intent(inout) :: this
    character(len=:), intent(inout), allocatable :: res
    character(len=:), allocatable :: tmp
    call this%read_until_delim()
    allocate (tmp, source=this%buffer_(1:this%pos_))
    call move_alloc(from=tmp, to=res)
    this%pos_ = 0
  end subroutine next_scanner
  !> next_int8_scanner: Return token as `integer(int8)` through `res`.
  !> @param this this%pos_ reset 0.
  !> @param res An `integer(int8)` of token splited by `this%delim_`.
  impure subroutine next_int8_scanner(this, res)
    class(scanner), intent(inout) :: this
    integer(int8), intent(inout) :: res
    call this%read_until_delim()
    read (this%buffer_(1:this%pos_), *) res
    this%pos_ = 0
  end subroutine next_int8_scanner
  !> next_int16_scanner: Return token as `integer(int16)` through `res`.
  !> @param this this%pos_ reset 0.
  !> @param res An `integer(int16)` of token splited by `this%delim_`.
  impure subroutine next_int16_scanner(this, res)
    class(scanner), intent(inout) :: this
    integer(int16), intent(inout) :: res
    call this%read_until_delim()
    read (this%buffer_(1:this%pos_), *) res
    this%pos_ = 0
  end subroutine next_int16_scanner
  !> next_int32_scanner: Return token as `integer(int32)` through `res`.
  !> @param this this%pos_ reset 0.
  !> @param res An `integer(int32)` of token splited by `this%delim_`.
  impure subroutine next_int32_scanner(this, res)
    class(scanner), intent(inout) :: this
    integer(int32), intent(inout) :: res
    call this%read_until_delim()
    read (this%buffer_(1:this%pos_), *) res
    this%pos_ = 0
  end subroutine next_int32_scanner
  !> next_int64_scanner: Return token as `integer(int64)` through `res`.
  !> @param this this%pos_ reset 0.
  !> @param res An `integer(int64)` of token splited by `this%delim_`.
  impure subroutine next_int64_scanner(this, res)
    class(scanner), intent(inout) :: this
    integer(int64), intent(inout) :: res
    call this%read_until_delim()
    read (this%buffer_(1:this%pos_), *) res
    this%pos_ = 0
  end subroutine next_int64_scanner
  !> next_real32_scanner: Return token as `real(real32)` through `res`.
  !> @param this this%pos_ reset 0.
  !> @param res An `integer(real32)` of token splited by `this%delim_`.
  impure subroutine next_real32_scanner(this, res)
    class(scanner), intent(inout) :: this
    real(real32), intent(inout) :: res
    call this%read_until_delim()
    read (this%buffer_(1:this%pos_), *) res
    this%pos_ = 0
  end subroutine next_real32_scanner
  !> next_real64_scanner: Return token as `real(real64)` through `res`.
  !> @param this this%pos_ reset 0.
  !> @param res An `integer(real64)` of token splited by `this%delim_`.
  impure subroutine next_real64_scanner(this, res)
    class(scanner), intent(inout) :: this
    real(real64), intent(inout) :: res
    call this%read_until_delim()
    read (this%buffer_(1:this%pos_), *) res
    this%pos_ = 0
  end subroutine next_real64_scanner
  !> is_eof_scanner: Return is reaching EOF of input file or not.
  !> @param this Read one token.
  !> @return A logical value if reaching EOF or not.
  impure logical function is_eof_scanner(this) result(res)
    class(scanner), intent(inout) :: this
    call this%read_until_delim()
    res = this%is_eof_
  end function is_eof_scanner
end module scanner_m
```

</details>

## 型を定義する

<details open><summary> scanner_m.f90 (型定義) </summary>

```scanner_m.f90
  integer(int32), parameter, public :: max_bufsize = ishft(1_int32, 20) ! ~ 10**6
  type :: scanner
     private
     integer(int32) :: pos_ = 0, unit_ = input_unit
     character :: delim_ = " "
     logical :: is_eof_ = .false.
     character(len=max_bufsize) :: buffer_
   contains
     !> setting function
     procedure, pass :: use_delim => use_delim_scanner
     !> read function
     procedure, pass, private :: read_until_delim => read_until_delim_scanner
     !> next functions
     procedure, pass :: next => next_scanner
     generic :: next_int => next_int8, &
          & next_int16, &
          & next_int32, &
          & next_int64
     procedure, pass :: next_int8 => next_int8_scanner
     procedure, pass :: next_int16 => next_int16_scanner
     procedure, pass :: next_int32 => next_int32_scanner
     procedure, pass :: next_int64 => next_int64_scanner
! (略)
  end type scanner
```

</details>

- 型の中の `character(len=max_bufsize) :: buffer_` 配列にトークンを保持します.
- $10^6$ 程度読み込めれば大丈夫でしょう.
- あとは色々定義します.
- `read_until_delim_scanner` はトークンを読み込むルーチンです.
- ちなみに `next_int` たちは `fypp` を使って楽しています.
```fortran:fypp
#:set SCANNER_INTEGER_KINDS = ["int8", "int16", "int32", "int64"]
     generic :: next_int => ${", &\n& ".join(map(lambda k: f"next_{k}", SCANNER_INTEGER_KINDS))}$
#:for kind in SCANNER_INTEGER_KINDS
     procedure, pass :: next_${kind}$ => next_${kind}$_scanner
#:endfor
```

## トークンを読み込む

<details open><summary> scanner_m.f90 (read_until_delimiter) </summary>

```scanner_m.f90
  impure subroutine read_until_delim_scanner(this)
    class(scanner), intent(inout) :: this
    character :: c
    integer(int32) :: iostat
    if (this%pos_ /= 0) return
    read_until_delim: do
       read (this%unit_, '(a1)', advance="no", iostat=iostat) c
       select case (iostat)
       case (iostat_eor)
          if (this%delim_ == " ") exit
          this%pos_ = this%pos_ + 1
          this%buffer_(this%pos_:this%pos_) = achar(10) !> newline code.
          cycle
       case (iostat_end)
          this%is_eof_ = .true.
          exit
       case (0)
          !> do nothing.
       case default
          write (error_unit, *) iostat
          error stop "Error: Unknown iostat"
       end select
       if (c == this%delim_) then
          if (this%pos_ /= 0) exit
          cycle !> The contiguous `this%delim_`.
       end if
       ! write(error_unit, *) c, this%pos_, iostat, iostat_eor, iostat_end
       this%pos_ = this%pos_ + 1
       this%buffer_(this%pos_:this%pos_) = c
    end do read_until_delim
  end subroutine read_until_delim_scanner
```

</details>

- `read (this%unit_, '(a1)', advance="no", iostat=iostat) c` で1文字だけ読み込めます.
- `iostat` で改行(`iostat_eor`)と読み込み終わり(`iostat_end`)を検知できます.
- 区切りが `" "` の場合は改行も区切りとしています.

1文字を `this%delim_` を読み込むまで繰り返すだけです.

## やってみる

<details><summary> test_scanner_m.f90 </summary>

```test_scanner_m.f90
program part_of_ABC157e
  use, intrinsic :: iso_fortran_env
  use scanner_m
  implicit none
  integer(int32) :: q
  type(scanner) :: sc
  integer(int32) :: i
  read (input_unit, *) q
  sc = scanner(input_unit)
  do i = 1, q
     block
       integer(int32) :: types
       call sc%next_int(types)
       select case (types)
       case (1)
          block
            integer(int32) :: i
            character(len=:), allocatable :: c
            call sc%next_int(i)
            call sc%next(c)
            write (output_unit, '(*(g0, 1x))') types, i, c
          end block
       case (2)
          block
            integer(int32) :: l, r
            call sc%next_int(l)
            call sc%next_int(r)
            write (output_unit, '(*(g0, 1x))') types, l, r
          end block
       end select
     end block
  end do
end program Part_Of_ABC157e
```
</details>

```bash
$ gfortran scanner_m.f90 test_scanner.f90 && ./a.out <<< "4             
1 1 a 
2 1 2 
2 2 30
1 2 z"
1 1 a
2 1 2
2 2 30
1 2 z
```

ちゃんと読み込めています.

- `sc = scanner(input_unit)` で初期化します.
- `read` した後に `scanner` クラスで読み込んでも平気そうです.
  + 逆は, `sc` の `buffer_` に文字列が読み込まれている可能性があるため危険です.

# まとめ
Fortran はトークン毎に文字列を読み込める可能性を秘めています.
ユーザはインターフェースだけ知っていれば良いので楽そうです.
`fypp` を使うと楽できます.
面倒なことはクラスに処理させましょう.