文字列の変換 -- translit2014年12月07日 12:12

コマンドラインの引数がとれるようになったので、引数を使ったプログラムを 紹介します。 文字列の置き換えプログラムtranslitです。

translitは、標準入力から読み込んだ文字をコマンドラインの引数の指示に従い変換します。たとえば、
translit x y
は、文字"x"を文字"y"に変換します。また、
translit xy yx
は、文字"x"を文字"y"に、文字"y"を文字"x"に変換します。さらには、
translit a-z A-Z
は、英小文字を英大文字に変換します。"a-z"は、"abcdefghijklmnopqrstuvwxyz"の略記です。
translit a-zA-Z a
とすれば、英文字列を文字"a"一文字に押しつぶします。また、
translit 0-9 n
とすれば、数字列を文字"n"一文字に押しつぶします。
translit 0-9
とすれば、数字列を削除することになります。もう少し拡張します。
translit !a-z -
とすれば、英小文字以外は、改行も含め、"-"に変換されます。

脱出文字"@"も使えます。"@n"とすれば改行を意味し、"@t"は、タブです。"@"自身は"@@"と表現できます。
translit @n N
とすれば、改行が文字"N"に変換され、
translit @t T
とすれば、タブが文字"T"に変換されます。

このように、translitは一種のフィルターです。

translitのメイン部分は下記の通り。

# translit.r4 -- map characters
      character getc
      character arg(MAXARR), c, from(MAXSET), to(MAXSET)
      integer getarg, length, makset, xindex
      integer allbut, collap, i, lastto

      allbut = NO
      if (getarg(1, arg, MAXARR) == NO)
          call error('usage: translit from to.')
      else if (arg(1) == NOT)
          allbut = YES
          if (makset(arg, 2, from, MAXSET) == NO)
              call error('from: too large.')
      else 
          allbut = NO
          if (makset(arg, 1, from, MAXSET) == NO)
              call error('from: too large.')

      if (getarg(2,arg,MAXARR) == NO)
          to(1) = EOS
      else if (makset(arg, 1, to, MAXSET) == NO)
          call error('to: too large.')

      lastto = length(to)
      if (length(from) > lastto | allbut == YES)
          collap = YES
      else
          collap = NO

      repeat {
          i = xindex(from, getc(c), allbut, lastto)
          if (collap == YES & i >= lastto & lastto > 0) { # collapse
              call putc(to(lastto))
              repeat
                  i = xindex(from, getc(c), allbut, lastto)
              until (i < lastto)
              }
          if (c == EOF) { 
              call putc(EOF)
              break
              }
          if (i > 0 & lastto > 0)       # translate
              call putc(to(i))
          else if (i == 0)              # copy
              call putc(c)
                                        # else delete
          }
      stop
      end

プログラムの前半は、コマンドラインの引数から、変換ルールの文字列を作成することに費やされています。 後半は、標準入力からEOFを読みとるまで、変換して標準出力へ書き出します。

Watcom Fortran77版は、下記の通り。

c translit.for -- map characters
      program translit
      integer*1 getc
      integer*1 arg(100), c, from(200), to(200) ! MAXARR(100) MAXSET(00)
      integer getarg, length, makset, xindex
      integer allbut, collap, i, lastto

      allbut = 0                        ! NO(0)
      if (getarg(1, arg, 100) .eq. 0) then ! MAXARR(100) NO(0)
          call error('usage: translit from to.')
      else if (arg(1) .eq. 33) then     ! NOT(33)
          allbut = 1                    ! YES(1)
          if (makset(arg, 2, from, 200) .eq. 0) then ! MAXSET(200) NO(0)
              call error('from: too large.')
          end if
      else 
          allbut = 0                    ! NO(0)
          if (makset(arg, 1, from, 200) .eq. 0) then ! MAXSET(200) NO(0)
              call error('from: too large.')
          end if
      end if

      if (getarg(2, arg, 100) .eq. 0) then ! MAXARR(100)
          to(1) = -2                    ! EOS
      else if (makset(arg, 1, to, 200) .eq. 0) then ! MAXSET(200) NO(0)
          call error('to: too large.')
      end if

      lastto = length(to)
      if (length(from) .gt. lastto .or. allbut .eq. 1) then ! YES(1)
          collap = 1                    ! YES(1)
      else
          collap = 0                    ! NO(0)
      end if

      loop
          i = xindex(from, getc(c), allbut, lastto)
          if (collap .eq. 1
     1        .and. i .ge. lastto .and. lastto .gt. 0) then ! collapse
              call putc(to(lastto))
              loop
                  i = xindex(from, getc(c), allbut, lastto)
              until (i .lt. lastto)
          end if
          if (c .eq. -1) then           ! EOF(-1)
              call putc(-1)             ! EOF(-1)
              exit
          end if
          if (i .gt. 0 .and. lastto .gt. 0) then  ! translate
              call putc(to(i))
          else if (i .eq. 0) then       ! copy
              call putc(c)
          end if                        ! else delete
      end loop
      stop
      end

次回からは、translitの下請けルーチンを紹介します。

translitの下請けルーチン2014年12月13日 22:14

transllitの下請けルーチンは、複数ありますし、 また、それらのルーチンの下請けルーチンがあります。

まずは、makeset()。変換文字列fromとtoを作成するのに使います。

# makset.r4 -- make set from array(k) in set
      integer function makset(array, k, set, size)
      integer k, size
      character array(ARB), set(size)
      integer addset
      integer i, j

      i = k
      j = 1
      call filset(EOS, array, i,set, j, size)
      makset = addset(EOS, set, j, size)
      return
      end

Watcom Fortran77版は、下記の通り。

c makset.for -- make set from array(k) in set
      integer function makset(array, k, set, size)
      integer k, size
      integer*1 array(*), set(size)     ! ARB(*)
      integer addset
      integer i, j

      i = k
      j = 1
      call filset(-2, array, i, set, j, size) ! EOS(-2)
      makset = addset(-2, set, j, size) ! EOS(-2)
      return
      end

addset()は、文字列setのjの位置に文字を追加できれば追加します。 やっていることは、単純ですが、モジュールとしてまとめることで、 プログラムの見通しを良くするのに一役買っています。

# addset.r4 -- put c inset(j) if it fits, increment j
      integer function addset(c, set, j, maxsiz)
      character c, set(maxsiz)
      integer j, maxsiz

      if (j > maxsiz)
          addset = NO                    ! NO(0)
      else
          set(j) = c
          j = j + 1
          addset = YES                   ! YES(1)
      return
      end

Watcom Fortran77版は、下記の通り。

c addset.for -- put c inset(j) if it fits, increment j
      integer function addset(c, set, j, maxsiz)
      integer*1 c, set(maxsiz)
      integer j, maxsiz

      if (j .gt. maxsiz) then
          addset = 0                    ! NO(0)
      else
          set(j) = c
          j = j + 1
          addset = 1                    ! YES(1)
      endif
      return
      end

filset()は、略記法を考慮して、変換文字列を作り出します。

# filset.r4 -- expand set at array(i) into set(j), stop at delm
      subroutine filset(delim, array, i, set, j, maxset)
      character delim, array(ARB), set(maxset), esc
      integer i, j, maxset
      integer addset, iindex
      integer junk
      string lowalf "abcdefghijklmnopqrstuvwxyz"
      string upalf "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      string digits "0123456789"

      for ( ; array(i) != delim & array(i) != EOS; i = i + 1)
          if (array(i) .eq. ESCAPE)
              junk = addset(esc(array, i), set, j, maxset)
          else if (array(i) != DASH)
              junk = addset(array(i), set, j, maxset)
          else if (j <= 1 | array(i+1) == EOS)
              junk = addset(DASH, set, j, maxset) # set literal '-'
          else if (iindex(digits, set(j-1)) > 0)
              call dodash(digits, array, i, set, j, maxset)
          else if (iindex(lowalf, set(j-1)) > 0)
              call dodash(lowalf, array, i, set, j, maxset)
          else if (iindex(upalf, set(j-1)) > 0)
              call dodash(upalf, array, i, set, j, maxset)
          else
              junk = addset(DASH, set, j, maxset)
       return
       end

Watcom Fortran77版は、下記の通り。

c filset.for -- expand set at array(i) into set(j), stop at delm
      subroutine filset(delim,array,i,set,j,maxset)
      integer*1 delim, array(*), set(maxset), esc ! ARB(*)
      integer i, j, maxset
      integer addset, iindex
      integer junk
      integer*1 lowalf(27), upalf(27), digits(11)
      data lowalf/'a','b','c','d','e','f','g','h','i','j','k','l','m',
     1        'n','o','p','q','r','s','t','u','v','w','x','y','z',-2/ ! EOS(-2)
      data upalf/'A','B','C','D','E','F','G','H','I','J','K','L','M',
     1        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',-2/ ! EOS(-2)
      data digits/'0','1','2','3','4','5','6','7','8','9',-2/ ! EOS(-2)

      while ((array(i) .ne. delim) .and. (array(i) .ne. -2)) do ! EOS(-2)
          if (array(i) .eq. 64) then    ! ESCAPE(64)
              junk = addset(esc(array,i),set,j,maxset)
          else if (array(i) .ne. 45) then  ! DASH(45)
              junk = addset(array(i),set,j,maxset)
          else if ((j .le. 1) .or. (array(i+1) .eq. -2)) then  ! EOS(-2)
              junk = addset(45, set, j, maxset) ! DASH(45)        ! set literal '-'
          else if (iindex(digits, set(j-1)) .gt. 0) then
              call dodash(digits, array, i, set, j, maxset)
          else if (iindex(lowalf, set(j-1)) .gt. 0) then
              call dodash(lowalf, array, i, set, j, maxset)
          else if (iindex(upalf, set(j-1)) .gt. 0) then
              call dodash(upalf, array, i, set, j, maxset)
          else
              junk = addset(45, set, j, maxset) ! DASH(45)
          endif
          i = i + 1
       end while
       return
       end

filset()の下請けルーチン、addset()、iindex()はすでに紹介してあります。

esc()は、脱出記号を処理します。具体的には、次の通り。

# esc.r4 -- map array(i) into escaped character if appropriate
      character function esc(array,i)
      character array(ARB)
      integer i
      
      if (array(i) != ESCAPE)
          esc = array(i)
      else if (array(i+1) == EOS) # @ not special at end
          esc = ESCAPE
      else {
          i = i + 1
          if (array(i) == LETN)
              esc = NEWLINE
          else if (array(i) == LETT)
              esc = TAB
          else
              esc = array(i)
          }
      return
      end

Watcom Fortran77版は、下記の通り。

c esc.for -- map array(i) into escaped character if appropriate
      integer*1 function esc(array,i)
      integer*1 array(*)                ! ARB(*)
      integer i
      
      if (array(i) .ne. 64) then        ! ESCAPE(64 @)
          esc = array(i)
      else if (array(i+1) .eq. -2) then ! EOS(-2)
          esc = 64                      ! ESCAPE(@)
      else
          i = i + 1
          if (array(i) .eq. 110) then   ! LETN(110)
              esc = 10                  ! NEWLINE(10)
          else if (array(i) .eq. 116) then ! LETT(116)
              esc = 9                   ! TAB(9)
          else
              esc = array(i)
          end if
      end if
      return
      end

dodash()は、略記を処理します。ここでもaddset()、esc()をうまく使っています。内容は次の通り。

# dodash.r4 -- expand array(i-1)-array(i+1) into set(j)... from valid
      subroutine dodash(valid, array, i, set, j, maxset)
      character valid(ARB), array(ARB), set(maxset)
      integer i, j, maxset
      integer addset, junk, iindex, limit, k
      character esc

      i = i + 1
      j = j - 1
      limit =iindex(valid,esc(array,i))
      for (k = iindex(valid, set(j)); k <= limit; k = k + 1)
          junk = addset(valid(k), set, j, maxset)
      return
      end

Watcom Fortran77版は、下記の通り。

c dodash.for -- expand array(i-1)-array(i+1) into set(j)... from valid
      subroutine dodash(valid, array, i, set, j, maxset)
      integer*1 valid(*), array(*), set(maxset) ! ARB(*)
      integer i, j, maxset
      integer addset, junk, iindex, limit, k
      integer*1 esc

      i = i + 1
      j = j - 1
      limit =iindex(valid,esc(array,i))
      k = iindex(valid,set(j))
      while (k .le. limit) do
          junk = addset(valid(k),set,j,maxset)
          k = K + 1
      end while
      return
      end

次回は、まだ説明していない、下請けルーチンを紹介し、translitを完成させます。