find -- 文型の照合2015年07月26日 07:47

findは、指定された照合パターンの文字列と入力行を照合し、条件に当てはまったら書き出します。照合パターンの 文字列は、次のように指定します。

  • "%"は、行頭を示します。
  • "$"は、行末、NEWLINEを示します。
  • "?"は、任意の文字一文字を示します。
  • "!"は、次の文字以外の文字を示します。ただし、NEWLINEは除きます。
  • 文字の類は、"["と"]"で囲まれた文字です。囲まれた文字のどれかを示します。省略記法も使えます。
  • "*"は、任意の文字の0回以上の繰り返しを示します。このような文型を閉包(クロージャー)と呼びます。

findでは、この文型照合パターンpatに特別な記号(符号)を挿入し、管理します。閉包以外については、以下のようにします。

  • BOL 行頭
  • EOL 行末
  • ANY 一文字
  • CHAR 文字そのもの
  • CCL 文字の類の始まり
  • NCCL "!"で始まる文字の類の始まり。
  • CLOSURE 閉包の始まり。

パターン"%[!x]?[0-9]x$"は、照合パターンpatには、次のように展開されます。

BOL NCCL 1 x ANY CCL 10 0 1 2 3 4 5 6 7 8 9 CHAR x EOL
         -           --
         ^           ^
         続く文字数  続く文字数 

findのメインルーチンは、まず、getpat()で照合パターンpatを作り、次に、match()で文型の照合をします。

RATFOR版は、下記の通り。

# find.r4 -- find pattern in text
      character arg(MAXARG),lin(MAXLINE+1),pat(MAXPAT)
      integer getarg,getlin,getpat,match

      if (getarg(1,arg,MAXARG) == EOF)
          call error('usage: find pattern.')
      if (getpat(arg,pat) == ERR)
          call error('illigal pattern.')

      while (getlin(lin,STDIN) != EOF)
          if (match(lin,pat) == YES)
              call putlin(lin,STDOUT)
      stop
      end

WATCOM Fortran 77版は下記の通り。

c find.for -- find pattern in text
      integer*1 arg(81),lin(81+1),pat(81) ! MAXARG(81) MAXLINE(81) MAXPAT(81)
      integer getarg,getlin,getpat,match

      call initfile()

      if (getarg(1,arg,81) .eq. -1) then  ! MAXARG(81) EOF(-1)
          call error('usage: find pattern.')
      end if
      if (getpat(arg,pat) .eq. -1) then ! ERR(-1)
          call error('illigal pattern.')
      end if

      while (getlin(lin,5) .ne. -1) do  ! STDIN(5) EOF(-1)
          if (match(lin,pat) .eq. 1) then ! YES(1)
              call putlin(lin,6)        ! STDOUT(6)
          end if
      end while
      stop
      end

match()は、行単位で照合パターンと一致する部分があるかを調べます。

RATFOR版は下記の通り。

# match.r4 -- find mach anywhere on line
      integer function match(lin,pat)
      character lin(MAXLINE+1),pat(MAXPAT)
      integer amatch
      integer i

      for (i = 1; lin(i) != EOS; i = i + 1)
          if (amatch(lin(i),i,pat) > 0) {
              match = YES
              return
              }
      match = NO
      return
      end

WATCOM Fortran77版は下記の通り。

c match.for -- find mach anywhere on line
      integer function match(lin,pat)
      integer*1 lin(81+1),pat(81)      ! MAXLINE(81) MAXPAT(81)
      integer amatch
      integer i

      i = 1
      while (lin(i) .ne. -2) do         ! EOS(-2)
          if (amatch(lin,i,pat) .gt. 0) then
              match = 1                 ! YES(1)
              return
          end if
          i = i + 1
      end while
      match = 0                         ! NO(0)
      return
      end

ここで、amatch()は、照合パターンが検査対象行のどこで一致したを返す。一致点がなかった場合は0を返す。 さて、照合パターンには、複数の閉包を持たすことができるので、文型の照合はすべての閉包について行う 必要がある。閉包の展開は、getpat()にて行うが、照合のコストが少なくなるよう、照合パターンを作成する。 すなわち、getpat()では、展開できるものはすべて展開してしまい照合パターンを作成する。amatch()は、このような方針で 展開した照合パターンについて照合を行う。amatch()の方針は、閉包一つ一つを最長一致の原則で調べ上げる。 最長一致で調べ上げるには、閉包単位でamach()を呼び出しながら処理することになる。amatch()でamatch()を呼び出す、 すなわち、再帰的な手続きになるが、FORTRANでは再帰的呼び出しはない。

再帰版の下書きは次の通り。

# amatch.r4 a recursive version to handle closures (pseudo-code)
      integer function amatch(lin,from,pat)
      
      offset = from  # next unexamined input character
      for (j = 1; pat(j) != EOS; j = j + patsiz(pat,j))
          if (pat(j) == CLOSURE) { # a closure entry
              j = [繰り返される文型のありか]
              for (i = offset; lin(i) != EOS; )  # match as many
                  if (omatch(lin,i,pat,j) == NO) # as possible
                      break
                  # i now points to character that make us fail
                  # try to match rest of pattern against rest of input
                  # shrink the closure by 1 after each failure
              for (j = [次の文型のありか]; i >= offset; i = i - 1) {
                  k = amatch(lin,i,pat(j))
                  if (k > 0) # successful match of rest pattern
                      break
                  }
                  offset = k
                  break
              }
          else if (omatch(lin,offset,pat,j) == NO) { non-closure
              amatch = 0
              return # failure on non-closure
              }
          # else omatch succeded
     amatch = offset
     return
仕方がないので、ループで実現することを考える。そのための追加情報を照合パターンに入れ込む。

追加情報は、以下のようになる。

pat(i+0)   [型]      閉包の場合はCLOSURE
pat(i+1)   COUNT     文型の繰り返し回数
pat(i+2)   PREVCL    前の閉包の位置
pat(i+3)   START     入力行上の文型照合開始位置

以上のことを加味して、amatch()をまとめる。実際の部分部分の照合は、omatch()が行う。

RATFOR版は以下の通り。

# amatch.r4 (non recursive) -- lock foramatch starting at lin(from)
      integer function amatch(lin,from,pat)
      character lin(MAXLINE+1),pat(MAXPAT)
      integer omatch,patsiz
      integer from,i,j,offset,stack

      stack = 0
      offset = from # next unexamined input character
      for (j = 1; pat(j) != EOS; j = j + patsiz(pat,j))
          if (pat(j) == CLOSURE) { # a closure entry
              stack = j
              for (i = offset; lin(i) != EOS; ) # match as many as
                  if (omatch(lin,i,pat) == NO)  # possible
                      break
              pat(stack + COUNT) = i - offset
              pat(stack + START) = offset
              offset = i # character that made us fail
              }
          else if (omatch(lin,offset,pat,j) == NO) { # non-closure
              for ( ; stack > 0; stack = pat(stack + PREVCL))
                  if (pat(stack + count) > 0)
                      break
              if (stack <= 0) { # stack is empty
                  amatch = 0    # return failure
                  return
                  }
              pat(stack + COUNT) = pat(stack + COUNTT) - 1
              j = stack + CLOSIZE
              offset - pat(stack + START) + pat(stack + COUNT)
              }
          # else omatch succeeded
      amatch = offset
      return # success
      end

WATCOM Fortran77版は以下の通り。

c amatch.for (non recursive) -- lock foramatch starting at lin(from)
      integer function amatch(lin,from,pat)
      integer*1 lin(81+1),pat(81)       ! MAXLINE(81) MAXPAT(81)
      integer from,i,j,offset,stack
      integer omatch,patsiz

      stack = 0
      offset = from                     ! next unexamined input character
      j = 1
      while (pat(j) .ne. -2) do         ! EOS(-2)
          if (pat(j) .eq. 42) then      ! CLOSURE(42 '*')
              stack = j
              j = j + 4                 ! CLOSIZE(4) step over CLOSURE
              i = offset
              while (lin(i) .ne. -2) do ! EOS(-2) ! match as many as possible
                  if (omatch(lin,i,pat,j) .eq. 0) then ! NO(0)
                      exit
                  end if 
              end while
              pat(stack+1) = i - offset ! COUNT(1)
              pat(stack+3) = offset     ! START(3)
              offset = i                ! character that made us fail
          else if (omatch(lin,offset,pat,j) .eq. 0) then  ! non-closure NO(0)
              while (stack .gt. 0) do
                  if (pat(stack+1) .gt. 0) then ! COUNT(1)
                      exit
                  end if
                  stack = pat(stack+2)  ! PREVCL(2)
              end while
              if (stack .le. 0) then    ! stack is empty
                  amatch = 0            ! return failure
                  return
              end if
              pat(stack+1) = pat(stack+1) - 1 ! COUNT(1)
              j = stack + 4                   ! CLOSIZE(4)
              offset = pat(stack+3) + pat(stack+1) ! START(3) COUNT(1)
          else
              ! else omach succeeded
          end if 
          j = j + patsiz(pat,j)
      end while
      amatch = offset
      return ! success
      end

下請けルーチンpatsiz()は、照合パターンの大きさを返す。

RATFOR版は下記の通り。

# patsiz.r4 -- returns size of pattern entry at pat(n)
      integer function patsiz(pat,n)
      character pat(MAXPAT)
      integer n

      if (pat(n) == CHAR)
          patsiz = 2
      else if (pat(n) == BOL | pat(n) == EOL | pat(n) == ANY)
          patsiz = 1
      else if (pat(n) == CCL | pat(n) == NCCL)
          patsiz = pat(n + 1) + 2
      else if (pat(n) == CLOSURE) # optioal
          patsiz = CLOSIZE
      else
          call error('in patsiz: can not happen.')
      return
      end

WATCOM Fortran77版は下記の通り。

c patsiz.for -- returns size of pattern entry at pat(n)
      integer function patsiz(pat,n)
      integer*1 pat(81)                 ! MAXPAT(81)
      integer n

      if (pat(n) .eq. 97) then          ! CHAR(97 'a')
          patsiz = 2
      else if ((pat(n) .eq. 37)         ! BOL(37 '%')
     1    .or. (pat(n) .eq. 36)         ! EOL(36 '$')
     2    .or. (pat(n) .eq. 63)) then   ! ANY(63 '?')
          patsiz = 1
      else if ((pat(n) .eq. 91)         ! CCL(91 '[')
     1    .or. (pat(n) .eq. 110)) then  ! NCCL(110 'n')
          patsiz = pat(n + 1) + 2
      else if (pat(n) .eq. 42 ) then    ! optional CLOSURE(42 '*')
          patsiz = 4                    ! COLSIZE(4)
      else
          call error('in patsiz: can not happen.')
      end if
      return
      end

下請けルーチンomatch()は、文型一つ分の照合をする。

RATFOR版は下記の通り。

# omatch.r4 -- try tomatch a single pattern at pat(j)
      integer function omatch(lin,i,pat,j)
      character lin(MAXLINE+1),pat(MAXPAT)
      integer locate
      integer bump,i,j

      omatch = NO
      if (lin(i) == EOS)
          return
      bump = -1
      if (pat(j) == CHAR) {
          if (lin(i) == pat(j+1))
              bump = 1
          }
      else if (pat(j) == BOL) {
          if (i == 1)
              bump = 0
          }
      else if (pat(j) == ANY) {
          if (lin(i) != NEWLINE)
              bump = 1
          }
      else if (pat(j) == EOL) {
          if (lin(i) == NEWLINE)
              bump = 0
          }
      else if (pat(j) == CCL) {
          if(locate(lin(i),pat,j + 1) == YES)
              bumo = 1
          }
      else if (pat(j) == NCCL) {
          if (lin(i) != NEWLINE & locate(lin(i),pat,j + 1) == NO)
              bump = 1
          }
      else
          call error('in omatch: can not happen.')
      if (bump >= 0) {
          i = i + bump
          omatch = YES
          }
      return
      end

WATCOM Fortran77版は下記の通り。

c omatch.for -- try tomatch a single pattern at pat(j)
      integer function omatch(lin,i,pat,j)
      integer*1 lin(81+1),pat(81)      ! MAXLINE(81) MAXPAT(81)
      integer i,j,locate,bump

      omatch = 0                        ! NO(0)
      if (lin(i) .eq. -2) then          ! EOS(-2)
          return
      end if

      bump = -1
      if (pat(j) .eq. 97) then          ! CHAR(97 'a')
          if (lin(i) .eq. pat(j+1)) then
              bump = 1
          end if
      else if (pat(j) .eq. 37) then     ! BOL(37 '%')
          if (i .eq. 1) then
              bump = 0
          end if
      else if (pat(j) .eq. 63) then     ! ANY(63 '?')
          if (lin(i) .ne. 10) then      ! NEWLINE(10)
               bump = 1
          end if
      else if (pat(j) .eq. 36) then     ! EOL(36 '$')
          if (lin(i) .eq. 10) then      ! NEWLINE(10)
              bump = 0
          end if
      else if (pat(j) .eq. 91) then     ! CCL(91 '[')
          if (locate(lin(i),pat,j+1) .eq. 1) then ! YES(1)
              bump = 1
          end if
      else if (pat(j) .eq. 110) then    ! NCCL(110 'n')
          if ((lin(i) .ne. 10)          ! NEWLINE(10)
     1        .and. (locate(lin(i),pat,j+1)) .eq. 0) then ! NO(0)
              bump = 1
          end if
      else
          call error('in omatch: can not happen.')
      end if
      if (bump .ge. 0) then
          i = i + bump
          omatch = 1                    ! YES(1)
      end if
      return
      end

omatch()の下請けルーチンlocate()は、文字が文字の類に該当するかどうかを調べる。

RATFOR版は以下の通り。

# locate.r4 -- look for c in char class at pat(offser)
      integer function locate(c,pat,offset)
      character c,pat(MAXPAT)
      integer i,offset
      # size of class is at pat(offset), characters follow

      for (i = offset + pat(offset); i > offset; i = i - 1)
          if (c == pat(i)) {
              locate = YES
              return
          }
      locate = NO
      return
      end

WATCOM Fortran77版は下記の通り。

c locate.for -- look for c in char class at pat(offser)
      integer function locate(c,pat,offset)
      integer*1 c,pat(81)               ! MAXPAT(81)
      integer i,offset
      ! size of class is at pat(offset), characters follow

      i = offset + pat(offset)
      while (i .gt. offset) do
          if (c .eq. pat(i)) then
              locate = 1                ! YES(1)
              return
          end if
          i = i - 1
      end while
      locate = 0                        ! NO(0)
      return
      end