translitの下請けルーチン2015年01月03日 18:22

まだ紹介していないtranslitの下請けルーチンを紹介します。

まずは、length()。これは、文字列の長さを返します。文字列の最後を示すEOSは含みません。

# length.r4 -- compute length of string
      integer function length(str)
      character str(*)

      for (length = 0; str(length + 1) != EOS; length = length + 1)
          ;
      return
      end

Watcom Fortran77版は、下記の通り。

c length.for -- compute length of string
      integer function length(str)
      integer*1 str(*)
      
      length = 1
      while (str(length) .ne. -2) do      ! EOS(-2)
          length = length + 1
      end while
      length = length - 1
      return
      end

xindex()は次の通りです。フラグallbutに従いiindex()とは裏腹の結果を返します。

# xindex -- invert condition return by iindex
      integer function xindex(array,c,allbut,lastto)
      character array(ARB), c
      integer iindex
      integer lastto, allbut
      
      if (c == EOF) 
          xindex = 0
      else if (allbut == NO)
          xindex = iindex(array,c)
      else if (iindex(array,c) > 0)
          xindex = 0
      else
          xindex = lastto + 1
      return
      end

Watcom Fortran77版は、下記の通り。

c xindex -- invert condition return by iindex
      integer function xindex(array,c,allbut,lastto)
      integer*1 array(*),c
      integer allbut
      integer lastto,iindex
      
      if (c .eq. -1) then               ! EOF(-1)
          xindex = 0
      else if (allbut .eq. 0) then      ! NO(0)
          xindex = iindex(array,c)
      else if (iindex(array,c) .gt. 0) then
          xindex = 0
      else
          xindex = lastto + 1
      endif
      return
      end

最後に、error()です。これは、メッセージを表示しプログラムを停止させます。 メッセージは、固定文字列として引き渡す仕様になっているため、一工夫が必要です。

Watcom Fortran77版は、メッセージを表示するremark()を使って作りました。

c error.for -- print message s and stop
      subroutine error(s)
      character s(*)

      call remark(s)
      stop
      end
c remark.for -- print error message
      subroutine remark(s)
      character s(*)                    ! ARB(*)
      integer i
      
      i = 1
      while (s(i) .ne. '.') do
          call putc(ichar(s(i)))
          i = i + 1
      end while
      call putc(46)                     ! PERIOD(46)
      call putc(10)                     ! NEWLINE(10)
      return
      end

ここまでで、必要なパーツがそろいました。モジュールをビルドしライブラリーに登録し、translitを作成してください。 できましたら、早速テストしてみましょう。

C:\Users\Hiroya\Documents\ratfor\fortran\bat>..\exe\translit abc XYZ
abcdefg XYZ
XYZdefg XYZ
a b c X Y Z
X Y Z X Y Z
^Z

C:\Users\Hiroya\Documents\ratfor\fortran\bat>..\exe\translit a-z A
ABCDEFabcdefg
ABCDEFA
ABCDEFabcdefgXYZ
ABCDEFAXYZ
^Z

C:\Users\Hiroya\Documents\ratfor\fortran\bat>..\exe\translit @n #
abcd efg
hijk
^Z
abcd efg#hijk#

C:\Users\Hiroya\Documents\ratfor\fortran\bat>

いかがでしょうか。

さて、translitはフィルターです。ほかのプログラムとつなぎ合わせて使うことができます。

次回は、ファイルの処理をするための基礎ルーチンを紹介します。

外部ファイルの結びつけ、fopen(),fclose(),initfile()2015年01月11日 21:05

Watcom Fortran 77では、外部ファイルと措置番号を実行時に結びつけることができます。 しかし、あまり使い勝手がよいとは、限りません。 装置番号1とファイル"ABC.TXT"を結びつけるには、環境変数を設定する必要があります。

set 1=ABC.TXT
この方法だと、使っている装置番号を知っておく必要があります。あまり、賢くないやり方です。 別の方法を考えます。コマンドラインのパラメーターにファイル名を指定する方法を考えてみます。
program ABC.TXT
この方法は便利です。プログラム内部で使用されている装置番号を知る必要はありませんが、 外部ファイルと装置番号結びつける仕組みが必要です。その仕組みがWatcom Fortran 77にあり、それはopen文です。
open(unit=uid, file=fname, action=act, err=99)
    uid : 装置番号
    fname : ファイル名(character型の文字列)
    act : 'READ','WRITE'
    99 : ERRORが起きたときにジャンプする先
このままでは、使いにくいので一枚、皮をかぶせます。fopen()です。
c fopen.for -- connect intenal file descripter and external file
      integer function fopen(uid, fn, act)
      integer uid
      integer*1 fn(*), act
      integer i
      character*261 cfn                 ! MAXNAME(261)
      character*5 cact                  ! READ WRITE

      include 'files.fi'

      if (act .eq. 82) then             ! READ(LETR)
          cact = 'READ'
      else if (act .eq. 87) then        ! WRITE(LETW)
          cact = 'WRITE'
      else                              ! error
           uid = -1                     ! ERR(-1)
           fopen = -1                   ! ERR(-1)
           return
      end if

      call is2cs(fn,cfn,261)            ! MAXNAME(261) convert integer string to character string

      i = 1
      while (i .le. 20) do              ! MAXFIELS(20)
          if (finuse(i) .eq. 0) then    ! NOUSE(0)
              open(unit=i, file=cfn, action=cact, err=99)
              finuse(i) = 1             ! INUSE(1)
              uid = i
              fopen = i
              if (act .eq. 82) then     ! READ(LETR)
                  flastc(i) = 81        ! MAXCARD+1(81)
                  fbuf(i,81) = 10       ! NEWLINE(10)
                  fbuf(i,82) = -2       ! EOS(-2)
                  fmode(i) = 82         ! READ(LETR)
              else if (act .eq. 87) then ! WRITE(LETW)
                  flastc(i) = 0
                  fmode(i) =87          ! WRITE(LETW)
              end if
              return
          endif
          i = i + 1
      end while
      
   99 continue
      uid = -1                          ! ERR(-1)
      fopen = -1                        ! ERR(-1)
      return
      end

includeは、Watcom Fortran 77の機能で、'files.fi'をコンパイル時に、読み込みます。

この中で、is2cs()は、integer*1の文字列をcharacter型の 文字列に変換するものです。

c is2cs -- copy integer string to character string
      subroutine is2cs(is,cs,maxsiz)
      integer*1 is(*)
      character cs(maxsiz)
      integer maxsiz
      character char
      integer i

      i = 1                             ! clear character string
      while (i .le. maxsiz) do
           cs(i) = ' '
           i = i + 1
      end while

      i = 1
      while (is(i) .ne. -2) do          ! EOS(-2)
          if (i .ge. maxsiz) then       ! MAXNAME(261)
              exit
          end if
          cs(i) = char(is(i))
          i = i + 1
      end while
      return
      end

files.fiは下記の通りです。

c files.fi -- file interface common valiables
      common /files/finuse,fbuf,flastc,fmode
      integer finuse(20)                ! inuse flag               MAXFILES(20)
      integer*1 fbuf(20,82)             ! I/O buffer               MAXFILES(20) MAXLINE(81)+1
      integer flastc(20)                ! characters in I/O buffer MAXFILES(20)
      integer*1 fmode(20)               ! READ/WIRTE flag          MAXFILES(20)
  • finuse(i) : 装置番号iが使用中ならばINUSE、そうでなければNOUSE
  • fbuf(i,82) : i番目の装置の入出力バッファー
  • flastc(i) : i番目の装置の次の読み出し文字位置、または、次の書き出し位置
  • fmode(i) : i番目の装置が入出力モード

fopen()の逆で、ファイルを切り離すfclose()を示します。これは、Watcom Fortran 77のclose()に皮をかぶせたものです。 uid=5,6を除外しているのは、標準入力と標準出力だからです。

c fclose.for -- disconnect internal filedescripter and extenal file
      subroutine fclose(uid)
      integer uid

      include 'files.fi'

      if (.not. ((uid .eq. 5) .or. (uid .eq. 6))) then
          if (fmode(uid) .eq. 87) then  ! WRITE(LETW)
              call fputc(uid,-1)        ! flush buffer by put EOF
          end if
          close(unit=uid, status='keep')
          finuse(uid) = 0               ! NOUSE(0)
          uid = 0
      end if
      return
      end

'files.fi'にある変数を、fopen(),fclose()などを使う前に初期化する必要があります。初期化 モジュールinitfile()を作成します。標準入力と標準出力は事前にオープンする必要がないため、 読み出し位置、書き出し位置の初期値をここで設定します。

c initfile.for -- setup file manage array funit
      subroutine initfile()
      integer i

      include 'files.fi'

      i = 1
      while (i .le. 20) do              ! MAXFILES(20)
          finuse(i)  = 0                ! NOUSE(0)
          i = i + 1
      end while

      finuse(5) = 1                     ! INUSE(1) for STDIN
      flastc(5) = 81                    ! lastc of read buffer
      fbuf(5,81) = 10                   ! NEWLINE(10)
      fbuf(5,82) = -2                   ! EOS(-2)
      fmode(5) = 82                     ! READ(LETR)
      finuse(6) = 1                     ! INUSE(1) for STDOUT
      flastc(6) = 0                     ! lastc of write buffer
      fmode(6) = 87                     ! WRITE(LETW)
      return
      end

これらをコンパイルするのに、fc.batに修正が必要です。includeするファイルをサーチする場所を指定するオプションを 追加します。

@echo off
rem fc2.for
wfc386 ..\src\%1.for /INCPATH=..\src
move ..\bat\%1.obj ..\obj

外部ファイルの入出力、fputc(),fgetc()2015年01月19日 22:30

前回作成したファイルのオープン、クローズルーチンと併せて使用する、ファイル読み書きルーチンを示します。

はじめに、一文字書き出しルーチンfputc()です。putc()を拡張しています。

c fputc.for (extended version) -- put character on file
      subroutine fputc(u,c)
      integer i, u
      integer*1 c

      include 'files.fi'

      if (c .eq. -1 .and. flastc(u) .eq. 0) then
          return                        ! buffer is empty, nothing to do
      end if
      if (flastc(u) .ge. 80 .or. c .eq. 10 .or. c .eq. -1) then ! MAXCARD(80) NEWLINE(10) EOF(-1)
          write(u,10) (fbuf(u,i),i=1,flastc(u))
   10     format(80a1)                  ! MAXCARD(80)
          flastc(u) = 0
      end if
      if (.not. (c .eq. 10 .or. c .eq. -1)) then ! NEWLINE(10) EOF(-1)
          flastc(u) = flastc(u) + 1
          fbuf(u,flastc(u)) = c
      end if
      return
      end

次に、一文字読み込みルーチンfgetc()です。これは、getc()を拡張しています。

c fgetc.for -- (extended version) get character from unit u
      integer*1 function fgetc(u,c)
      integer u
      integer*1 c
      integer col, i

      include 'files.fi'

      flastc(u) = flastc(u) + 1
      if (flastc(u) .ge. 80 .or. fbuf(u,flastc(u)) .eq. -2) then ! MAXCARD(80) EOS(-2)
          read(u,10,end=9) (fbuf(u,i),i=1,80) ! MAXCARD(80)
   10     format(80a1)                  ! MAXCARD(80)
          flastc(u) = 1
          col = 80
          while (fbuf(u,col) .eq. 32) do ! BALNK(32)
              col = col - 1
          end while
          fbuf(u,col+1) = 10            ! NEWLINE(10)
          fbuf(u,col+2) = -2            ! EOS(-2)
      endif
      c = fbuf(u,flastc(u))
      fgetc = fbuf(u,flastc(u))
      return
    9 continue
      c = -1                            ! EOF(-1)
      fgetc = -1                        ! EOF(-1)
      return
      end

ここで、問題が生じました。fgetc(5,c)とgetc(c)が交互に呼ばれたらどうなるでしょうか。また、fputc(6,c)とputc(c)が、 交互に呼ばれたらどうなるでしょうか。悲劇が起きます。原因は、どこにあるでしょうか。原因究明は次回に致します。