Ratforプリプロセッサー -- コード生成 "do"2017年06月20日 18:06

Ratforのdo文にであったら、doの限界指定部を取り出して、ラベルL、L+1を 作りだし、

          do L 限界指定部
を出力します。そして、doの終わりに達したら、
        L continue
      L+1 continue
を出力します。ここで、ラベルL+1は、breakに出会ったときの行き先になります。また、ラベルLは、 do分のループの終わりを示すとともに、nextに出会った時の行き先になります。 具体的には、docode()でdo文のはじめを生成します。

docode()のRatofor版は以下の通り。

# docode.r4 -- generate code for beginning of do
      include ratfor.def
      subroutine docode(lab)
      integer lab

      integer labgen
      string(dostr,"do")

      call outtab
      call outstr(dostr)
      call outch(BLANK)
      lab = labgen(2)
      call outnum(lab)
      call outch(BLANK)
      call eatup
      call outdon

      return
      end

WATCOM Fortran77版は以下の通り。

c docode.f -- generate code for beginning of do
      include ratfor.def
      subroutine docode(lab)
      integer lab

      integer labgen
      string(dostr,"do")

      call outtab
      call outstr(dostr)
      call outch(BLANK)
      lab = labgen(2)
      call outnum(lab)
      call outch(BLANK)
      call eatup
      call outdon

      return
      end

eatup()は、まだ取り込んでいない部分を継続行を含めて処理します。

eatup()のRatofor版は以下の通り。

# eatup.r4 -- process rest of statement; interpret continuations
      include ratfor.def
      subroutine eatup
      character gtoken
      character ptoken(MAXTOK),t,token(MAXTOK)
      integer nlpar

      nlpar = 0
      repeat `
          t = gtoken(token,MAXTOK)
          if (t == SEMICOL | t == NEWLINE)
              break
          if (t == LBRACE) {
              call pbstr(token)
              break
              }
          if (t == RBRACE | t == EOF) {
              call synerr('unexpected brace or EOF.')
              call pbstr(token)
              break
              }
          if (t == COMMA) {
              if (gtoken(ptoken,MAXTOK) != NEWLINE)
                  call pbstr(ptoken)
              }
          else if (t == LPAREN)
              nlpar = nlpar + 1
          else if (t == RPAREN)
              nlpar = nlpar - 1
          call outstr(token)
          } until (nlpar < 0)
      if (nlpar != 0)
          call synerr('unbalanced parentheses.')
      return
      end

WATCOM Fortran77版は以下の通り。

c eatup.f -- process rest of statement; interpret continuations
      include ratfor.def
      subroutine eatup
      integer*1 gtoken
      integer*1 ptoken(MAXTOK),t,token(MAXTOK)
      integer nlpar

      nlpar = 0
      loop
          t = gtoken(token,MAXTOK)
          if ((t .eq. SEMICOL) .or. (t .eq. NEWLINE)) then
              exit
          end if
          if (t .eq. LBRACE) then
              call pbstr(token)
              exit
          end if
          if ((t .eq. RBRACE) .or. (t .eq. EOF)) then
              call synerr('unexpected brace or EOF.')
              call pbstr(token)
              exit
          end if
          if (t .eq. COMMA) then
              if (gtoken(ptoken,MAXTOK) .ne. NEWLINE) then
                  call pbstr(ptoken)
              end if  
          else if (t .eq. LPAREN) then
              nlpar = nlpar + 1
          else if (t .eq. RPAREN) then
              nlpar = nlpar - 1
          end if
          call outstr(token)
      until (nlpar .lt. 0)
      if (nlpar .ne. 0) then
          call synerr('unbalanced parentheses.')
      end if
      return
      end

doの終わりのコードは、dostat()で生成します。

dostat()のRatofor版は以下の通り。

# dostat.r4 -- generate code for end of do statement
      subroutine dostat(lab)
      integer lab

      call outcon(lab)
      call outcon(lab+1)
      return
      end

WATCOM Fortran77版は以下の通り。

c dostat.f -- generate code for end of do statement
      subroutine dostat(lab)
      integer lab

      call outcon(lab)
      call outcon(lab+1)
      return
      end

Ratforプリプロセッサー -- コード生成 "if"と"if-else"2017年06月07日 15:41

"if"文に出会ったら、2つのラベル、LとL+1を生成し、

          if(.not(条件)) goto L
を出力します。それは、ifcode()で行ない、ラベルを返します。

ifcode()のRatofor版は以下の通り。

# ifcode.r4 -- generate initial code for if
      subroutine ifcode(lab)
      integer lab
      integer labgen

      lab = labgen(2)
      call ifgo(lab)
      return
      end

WATCOM Fortran77版は以下の通り。

c ifcode.f -- generate initial code for if
      subroutine ifcode(lab)
      integer lab
      integer labgen

      lab = labgen(2)
      call ifgo(lab)
      return
      end

ifgo()は、

          if(.not.(条件)) goto lab
を作り出します。

ifgo()のRatofor版は以下の通り。

# ifgo.r4 -- generate "if (.not. (...)) goto lab"
      include ratfor.def
      subroutine ifgo(lab)
      integer lab
      string ifnot "if (.not. "

      call outtab           # get to column 7
      call outstr(ifnot)    # "if (.not. "
      call balpar           # collect and output condition
      call outch(RPAREN)    # ")"
      call outch(BLANK)     # " "
      call outgo(lab)       # "goto lab"
      return
      end

WATCOM Fortran77版は以下の通り。

c ifgo.f -- generate "if (.not. (...)) goto lab"
      include ratfor.def
      subroutine ifgo(lab)
      integer lab
      integer*1 ifnot(11)
      data ifnot(1)/LETi/
      data ifnot(2)/LETf/
      data ifnot(3)/BLANK/
      data ifnot(4)/LPAREN/
      data ifnot(5)/PERIOD/
      data ifnot(6)/LETn/
      data ifnot(7)/LETo/
      data ifnot(8)/LETt/
      data ifnot(9)/PERIOD/
      data ifnot(10)/BLANK/
      data ifnot(11)/EOS/

      call outtab                       ! get to column 7
      call outstr(ifnot)                ! "if (.not. "
      call balpar                       ! collect and output condition
      call outch(RPAREN)                ! ")"
      call outch(BLANK)                 ! " "
      call outgo(lab)                   ! "goto lab"
      return
      end

elseの開始、ifまたはelse ifの終了は、

          goto L + 1
        L continue
を作り出します。ここでLはスタックに積んであったラベルです。

elseif()のRatofor版は以下の通り。

# elseif.f -- generate code for end of if before else
      subroutine elseif(lab)
      integer lab

      call outgo(lab+1)
      call outcon(lab)
      return
      end

WATCOM Fortran77版は以下の通り。

c elseif.f -- generate code for end of if before else
      subroutine elseif(lab)
      integer lab

      call outgo(lab+1)
      call outcon(lab)
      return
      end

labgen()は、必要な数だけのラベルを生成します。ラベルは、50000からとします。

labgen()のRatofor版は以下の通り。

# labgen.r4 -- generates n consecutive labels, return first one
      integer function labgen(n)
      integer n
      integer label
      data label/50000/

      labgen = label
      label = label + n
      return
      end

WATCOM Fortran77版は以下の通り。

c labgen.f -- generates n consecutive labels, return first one
      integer function labgen(n)
      integer n
      integer label
      data label/50000/

      labgen = label
      label = label + n
      return
      end

balpar()は、条件を(複数行に渡っているのもまとめて)取り出し、出力します。 また、cnvop()で、演算子"<"、"<="、">"、">="、"=="、"!="、"&"、"|"を ".lt."、".le."、".gt."、".ge."、".eq."、".ne."、".and."、".or."に変換します。

balpar()のRatofor版は以下の通り。

# balpar.r4 -- copy balanced paren string
      include ratfor.def
      subroutine balpar
      character gtoken
      character t,token(MAXTOK)
      integer nlpar
      integer iindex
      string opcode "=> 0)
              call cnvop(token,opstr)   # convert logical operator
              call outstr(opstr)
          else
              call outstr(token)
          } until (nlpar <= 0)
      if (nlpar != 0)
          call synerr('missing parenthesis in condition.')
      return
      end

WATCOM Fortran77版は以下の通り。

c balpar.f -- copy balanced paren string
      include ratfor.def
      subroutine balpar
      integer*1 gtoken
      integer*1 opstr(MAXTOK),t,token(MAXTOK)
      integer nlpar

      if (gtoken(token,MAXTOK) .ne. LPAREN) then
          call synerr('missing left paren.')
          return
      end if
      call outstr(token)
      nlpar = 1
      loop
          t = gtoken(token,MAXTOK)
          if ((t .eq. SEMICOL) .or. (t .eq. LBRACE) .or.
     1        (t .eq. RBRACE) .or. (t .eq. EOF)) then
              call pbstr(token)
              exit
          end if
          if (t .eq. NEWLINE) then      ! delete newlines
              token(1) = EOS
          else if (t .eq. LPAREN) then
              nlpar = nlpar + 1
          else if (t .eq. RPAREN) then
              nlpar = nlpar - 1
          ! else nothing special
          end if
          if (islgop(token(1)) .eq. YES) then
              call cnvop(token,opstr)   ! convert logical operator
              call outstr(opstr)
          else
              call outstr(token)
          endif
      until (nlpar .le. 0)
      if (nlpar .ne. 0) then
          call synerr('missing parenthesis in condition.')
      end if
      return
      end

cnvop()のRatofor版は以下の通り。

# cnvop.r4 -- convert logical oprator to FORTRAN style string
      include ratfor.def
      subroutine cnvop(token,opstr)
      integer*1 token(MAXTOK),opstr(MAXTOK)
      integer*1 ntoken(MAXTOK),t
      integer*1 gtoken

      string opeq " .eq. "
      string opne " .en. "
      string opgt " .gt. "
      string opge " .ge. "
      string oplt " .lt. "
      string ople " .le. "
      string opnot " .not. "
      string opand " .and. "
      string opor " .or. "

      t = gtoken(ntoken,MAXTOK)
      if (t .eq. OPEQUAL) then
          if (token(1) .eq. OPEQUAL) then
              call scopy(opeq,1,opstr,1)
          else if (token(1) .eq. OPGTHAN) then
              call scopy(opge,1,opstr,1)
          else if (token(1) .eq. OPLTHAN) then
              call scopy(ople,1,opstr,1)
          else if (token(1) .eq. OPNOT) then
              call scopy(opne,1,opstr,1)
          end if
      else
          call pbstr(ntoken)
          if (token(1) .eq. OPGTHAN) then
              call scopy(opgt,1,opstr,1)
          else if (token(1) .eq. OPLTHAN) then
              call scopy(oplt,1,opstr,1)
          else if (token(1) .eq. OPNOT) then
              call scopy(opnot,1,opstr,1)
          else if (token(1) .eq. OPAND) then
              call scopy(opand,1,opstr,1)
          else if (token(1) .eq. OPOR) then
              call scopy(opor,1,opstr,1)
          end if
      end if
      return
      end

WATCOM Fortran77版は以下の通り。

c cnvop.f -- convert logical oprator to FORTRAN style string
      include ratfor.def
      subroutine cnvop(token,opstr)
      integer*1 token(MAXTOK),opstr(MAXTOK)
      integer*1 ntoken(MAXTOK),t
      integer*1 gtoken

      integer*1 opeq(7)
      data opeq(1)/BLANK/
      data opeq(2)/PERIOD/
      data opeq(3)/LETe/
      data opeq(4)/LETq/
      data opeq(5)/PERIOD/
      data opeq(6)/BLANK/
      data opeq(7)/EOS/
      integer*1 opne(7)
      data opne(1)/BLANK/
      data opne(2)/PERIOD/
      data opne(3)/LETn/
      data opne(4)/LETe/
      data opne(5)/PERIOD/
      data opne(6)/BLANK/
      data opne(7)/EOS/
      integer*1 opgt(7)
      data opgt(1)/BLANK/
      data opgt(2)/PERIOD/
      data opgt(3)/LETg/
      data opgt(4)/LETt/
      data opgt(5)/PERIOD/
      data opgt(6)/BLANK/
      data opgt(7)/EOS/
      integer*1 opge(7)
      data opge(1)/BLANK/
      data opge(2)/PERIOD/
      data opge(3)/LETg/
      data opge(4)/LETe/
      data opge(5)/PERIOD/
      data opge(6)/BLANK/
      data opge(7)/EOS/
      integer*1 oplt(7)
      data oplt(1)/BLANK/
      data oplt(2)/PERIOD/
      data oplt(3)/LETl/
      data oplt(4)/LETt/
      data oplt(5)/PERIOD/
      data oplt(6)/BLANK/
      data oplt(7)/EOS/
      integer*1 ople(7)
      data ople(1)/BLANK/
      data ople(2)/PERIOD/
      data ople(3)/LETl/
      data ople(4)/LETe/
      data ople(5)/PERIOD/
      data ople(6)/BLANK/
      data ople(7)/EOS/
      integer*1 opnot(8)
      data opnot(1)/BLANK/
      data opnot(2)/PERIOD/
      data opnot(3)/LETn/
      data opnot(4)/LETo/
      data opnot(5)/LETt/
      data opnot(6)/PERIOD/
      data opnot(7)/BLANK/
      data opnot(8)/EOS/
      integer*1 opand(8)
      data opand(1)/BLANK/
      data opand(2)/PERIOD/
      data opand(3)/LETa/
      data opand(4)/LETn/
      data opand(5)/LETd/
      data opand(6)/PERIOD/
      data opand(7)/BLANK/
      data opand(8)/EOS/
      integer*1 opor(7)
      data opor(1)/BLANK/
      data opor(2)/PERIOD/
      data opor(3)/LETo/
      data opor(4)/LETr/
      data opor(5)/PERIOD/
      data opor(6)/BLANK/
      data opor(7)/EOS/

      t = gtoken(ntoken,MAXTOK)
      if (t .eq. OPEQUAL) then
          if (token(1) .eq. OPEQUAL) then
              call scopy(opeq,1,opstr,1)
          else if (token(1) .eq. OPGTHAN) then
              call scopy(opge,1,opstr,1)
          else if (token(1) .eq. OPLTHAN) then
              call scopy(ople,1,opstr,1)
          else if (token(1) .eq. OPNOT) then
              call scopy(opne,1,opstr,1)
          end if
      else
          call pbstr(ntoken)
          if (token(1) .eq. OPGTHAN) then
              call scopy(opgt,1,opstr,1)
          else if (token(1) .eq. OPLTHAN) then
              call scopy(oplt,1,opstr,1)
          else if (token(1) .eq. OPNOT) then
              call scopy(opnot,1,opstr,1)
          else if (token(1) .eq. OPAND) then
              call scopy(opand,1,opstr,1)
          else if (token(1) .eq. OPOR) then
              call scopy(opor,1,opstr,1)
          end if
      end if
      return
      end

ここまでに出てきた、下層の下請けルーチン、outtab()、outstr()、outch()、outgo()は、 以下の通りです。

outtab()のRatofor版は以下の通り。

# outtab.r4 -^- get past column 6
      include ratfor.def
      subroutine outtab

      include coutln.ri

      while (outp .lt. 6) do
          call outch(BLANK)
      end while
      return
      end

WATCOM Fortran77版は以下の通り。

c outtab.f -^- get past column 6
      include ratfor.def
      subroutine outtab

      include coutln.fi

      while (outp .lt. 6) do
          call outch(BLANK)
      end while
      return
      end

outstr()のRatofor版は以下の通り。

# outstr.r4 -- output string
      include ratfor.def
      subroutine outstr(str)
      character str(ARB)
      integer i,j
      character c

      for (i = 1; str(i) != EOS; i = I + 1) {
          c = str(i)
          if (c != SQUOTE & c != DQUOTE)
              call outch(c)
          else {
              i = i + 1
              for (j = i;str(j) != c;j = j + 1) # find end
                  ;
              call outnum(j-i)
              call outch(LETH)
              for ( ; i < j; i = i + 1)
                  call outch(str(i))
              }
      return
      end

WATCOM Fortran77版は以下の通り。

c outstr.f -- output string
      include ratfor.def
      subroutine outstr(str)
      integer*1 str(ARB)
      integer i,j
      integer*1 c

      i = 1
      while (str(i) .ne. EOS) do
          c = str(i)
          if ((c .ne. SQUOTE) .and. (c .ne. DQUOTE)) then
              call outch(c)
          else
              i = i + 1
              j = i                     ! find end
              while (str(j) .ne. c) do
                  j = j + 1
              end while
              call outnum(j-i)
              call outch(LETH)
              while (i .lt. j) do
                  call outch(str(i))
                  i = i + 1
              end while
          end if 
          i = i + 1
      end while
      return
      end

outch()のRatofor版は以下の通り。

# outch.r4 -- put one character into output buffer
      include ratfor.def
      subroutine outch(c)
      character c
      integer i

      include coutln.ri

      if (outp >= 72 ) # continution card
          call outdon
          for (i = 1;  i < 6; i = i + 1)
              outbuf(i) = BLANK
          outbuf(6) = STAR
          outp = 6
      outp = outp + 1
      outbuf(outp) = c
      return
      end

WATCOM Fortran77版は以下の通り。

c outch.f -- put one character into output buffer
      include ratfor.def
      subroutine outch(c)
      integer*1 c
      integer i

      include coutln.fi

      if (outp .ge. 72 ) then  ! continution card
          call outdon
          i = 1
          while (i .lt. 6) do
              outbuf(i) = BLANK
              i = i + 1
          end while
          outbuf(6) = STAR
          outp = 6
      end if
      outp = outp + 1
      outbuf(outp) = c
      return
      end

outgo()のRatofor版は以下の通り。

# outgo.r4 -- output "goto n"
      include ratfor.def
      subroutine outgo(n)
      integer n

      string(goto,"goto")

      call outtab
      call outstr(goto)
      call outch(BLANK)
      call outnum(n)
      call outdon
      return
      end

WATCOM Fortran77版は以下の通り。

c outgo.f -- output "goto n"
      include ratfor.def
      subroutine outgo(n)
      integer n

      string(goto,"goto")

      call outtab
      call outstr(goto)
      call outch(BLANK)
      call outnum(n)
      call outdon
      return
      end

Ratforプリプロセッサー -- 構文解析2017年05月25日 16:50

Ratforで書かれたプログラム文は、parse()で解析され、lex()が返す綴り別に それがコードの始まりの時は、コード生成ルーチンを呼び出し、スタックに綴りの種類と 名札が積まれる。コードの終わりの時は、unstak()が呼ばれスタックから取りおろしをします。 綴りは、defineやmacroのように、initkw()でテーブルにセットします。

parse()のRatofor版は以下の通り。

# parse.r4 -- parse Ratfor source program
      include ratfor.def
      subroutine parse
      character lexstr(MAXTOK)
      character lex, token
      integer lab,sp
      integer labval(MAXSTACK)
      character lextyp(MAXSTACK)

      call initkw                       # install keywords in table
      sp = 1
      lextyp(1) = EOF
      for (token = lex(lexstr); token != EOF; token = lex(lexstr) ) {
          if (token == LEXIF)
              call ifcode(lab)
          else if (token == LEXDO)
              call docode(lab)
          else if (token == LEXWHILE)
              call whilec(lab)
          else if (token == LEXFOR)
              call forcod(lab)
          else if (token == LEXREPEAT)
              call repcod(lab)
          else if (token == LEXUNTIL) {
              if (lextyp(sp) == LEXREPEAT)
                  call synerr('illegal until.')
              }
          else if (token == LEXSTRING)
              call strngc
          else if (token == LEXDIGITS)
              call labelc(lexstr)
          else if (token == LEXELSE) {
              if (lextyp(sp) == LEXIF)
                  call elseif(labval(sp))
              else
                  call synerr('illegal else.')
              }
          if (token == LEXIF | token == LEXELSE
              | token == LEXWHILE | token == LEXDO
              | token == LEXDIGITS | token == LBRACE
              | token == LEXFOR | token == LEXREPEAT) {
              sp = sp + 1
              if (sp > MAXSTACK)
                  call synerr('stack overflow in paser.')
              lextyp(sp) = token
              labval(sp) = lab
              }
          else }
              if (token == RBRACE)
                  if (lextyp(sp) == LBRACE)
                      sp = sp - 1
                  else
                      call synerr('illegal right brace.')
              else if (token == LEXOTHER)
                  call otherc(lexstr)
              else if (token == LEXBREAK | token == LEXNEXT)
                  call brknxt(sp,lextyp,labval,token)
              token = lex(lexstr)       # peek at next token
              call pbstr(lexstr)
              call unstak(sp,lextyp,labval,token)
              }
          }
      if (sp != 1)
          call synerr('unexpected EOF.')
      return
      end

WATCOM Fortran77版は以下の通り。

c parse.f -- parse Ratfor source program
      include ratfor.def
      subroutine parse
      integer*1 lexstr(MAXTOK)
      integer*1 lex,lextyp(MAXSTACK),token
      integer lab,labval(MAXSTACK),sp

      call initkw                       ! install keywords in table
      sp = 1
      lextyp(1) = EOF
      token = lex(lexstr)
      while (token .ne. EOF) do
          if (token .eq. LEXIF) then
              call ifcode(lab)
          else if (token .eq. LEXDO) then
              call docode(lab)
          else if (token .eq. LEXWHILE) then
              call whilec(lab)
          else if (token .eq. LEXFOR) then
              call forcod(lab)
          else if (token .eq. LEXREPEAT) then
              call repcod(lab)
          else if (token .eq. LEXUNTIL) then
              if (lextyp(sp) .ne. LEXREPEAT) then
                  call synerr('illegal until.')
              end if
          else if (token .eq. LEXSTRING) then
              call strngc
          else if (token .eq. LEXDIGITS) then
              call labelc(lexstr)
          else if (token .eq. LEXELSE) then
              if (lextyp(sp) .eq. LEXIF) then
                  call elseif(labval(sp))
              else
                  call synerr('illegal else.')
              end if
          end if
          if ((token .eq. LEXIF) .or. (token .eq. LEXELSE) .or.
     1        (token .eq. LEXWHILE) .or. (token .eq. LEXDO) .or.
     2        (token .eq. LEXDIGITS) .or. (token .eq. LBRACE) .or.
     3        (token .eq. LEXFOR) .or. (token .eq. LEXREPEAT)) then
              sp = sp + 1
              if (sp .gt. MAXSTACK) then
                  call synerr('stack overflow in paser.')
              end if
              lextyp(sp) = token
              labval(sp) = lab
          else
              if (token .eq. RBRACE) then
                  if (lextyp(sp) .eq. LBRACE) then
                      sp = sp - 1
                  else
                      call synerr('illegal right brace.')
                  end if
              else if (token .eq. LEXOTHER) then
                  call otherc(lexstr)
              else if (token .eq. LEXBREAK .or. token .eq. LEXNEXT) then
                  call brknxt(sp,lextyp,labval,token)
              end if
              token = lex(lexstr)       ! peek at next token
              call pbstr(lexstr)
              call unstak(sp,lextyp,labval,token)
          end if
          token = lex(lexstr)
      end while
      if (sp .ne. 1) then
          call synerr('unexpected EOF.')
      end if

      return
      end

initkw()は、見出し語をinstal()を使って登録します。

Ratofor版は以下の通り。

# initkw.f -- install Ratfor keyword
      include ratfor.def
      subroutine initkw
      string(ifst,"if")
      character iftyp(2)
      data iftyp(1)/LEXIF/
      data iftyp(2)/EOS/
      string(elsest,"else")
      character elstyp(2)
      data elstyp(1)/LEXELSE/
      data elstyp(2)/EOS/
      string(whilst,"while")
      character whityp(2)
      data whityp(1)/LEXWHILE/
      data whityp(2)/EOS/
      string(forst,"for")
      character fortyp(2)
      data fortyp(1)/LEXFOR/
      data fortyp(2)/EOS/
      string(repest,"repeat")
      character reptyp(2)
      data reptyp(1)/LEXREPEAT/
      data reptyp(2)/EOS/
      string(untist,"until")
      character unttyp(2)
      data unttyp(1)/LEXUNTIL/
      data unttyp(2)/EOS/
      string(dost,"do")
      character dotyp(2)
      data dotyp(1)/LEXDO/
      data dotyp(2)/EOS/
      string(breast,"break")
      character bretyp(2)
      data bretyp(1)/LEXBREAK/
      data bretyp(2)/EOS/
      string(nextst,"next")
      character nextyp(2)
      data nextyp(1)/LEXNEXT/
      data nextyp(2)/EOS/
      integer*1 strtyp(2)
      data strtyp(1)/LEXSTRING/
      data strtyp(2)/EOS/

      call instal(ifst,iftyp)
      call instal(elsest,elstyp)
      call instal(whilst,whityp)
      call instal(forst,fortyp)
      call instal(repest,reptyp)
      call instal(untist,unttyp)
      call instal(dost,dotyp)
      call instal(breast,bretyp)
      call instal(nextst,nextyp)
      call instal(strst,strtyp)
      return
      end

WATCOM Fortran77版は以下の通り。

c initkw.f -- install Ratfor keyword
      include ratfor.def
      subroutine initkw
      string(ifst,"if")
      integer*1 iftyp(2)
      data iftyp(1)/LEXIF/
      data iftyp(2)/EOS/
      string(elsest,"else")
      integer*1 elstyp(2)
      data elstyp(1)/LEXELSE/
      data elstyp(2)/EOS/
      string(whilst,"while")
      integer*1 whityp(2)
      data whityp(1)/LEXWHILE/
      data whityp(2)/EOS/
      string(forst,"for")
      integer*1 fortyp(2)
      data fortyp(1)/LEXFOR/
      data fortyp(2)/EOS/
      string(repest,"repeat")
      integer*1 reptyp(2)
      data reptyp(1)/LEXREPEAT/
      data reptyp(2)/EOS/
      string(untist,"until")
      integer*1 unttyp(2)
      data unttyp(1)/LEXUNTIL/
      data unttyp(2)/EOS/
      string(dost,"do")
      integer*1 dotyp(2)
      data dotyp(1)/LEXDO/
      data dotyp(2)/EOS/
      string(breast,"break")
      integer*1 bretyp(2)
      data bretyp(1)/LEXBREAK/
      data bretyp(2)/EOS/
      string(nextst,"next")
      integer*1 nextyp(2)
      data nextyp(1)/LEXNEXT/
      data nextyp(2)/EOS/
      integer*1 strst(7)
      data strst(1)/LETs/
      data strst(2)/LETt/
      data strst(3)/LETr/
      data strst(4)/LETi/
      data strst(5)/LETn/
      data strst(6)/LETg/
      data strst(7)/EOS/
      integer*1 strtyp(2)
      data strtyp(1)/LEXSTRING/
      data strtyp(2)/EOS/

      call instal(ifst,iftyp)
      call instal(elsest,elstyp)
      call instal(whilst,whityp)
      call instal(forst,fortyp)
      call instal(repest,reptyp)
      call instal(untist,unttyp)
      call instal(dost,dotyp)
      call instal(breast,bretyp)
      call instal(nextst,nextyp)
      call instal(strst,strtyp)
      return
      end

unstak()は、スタックに最後に積まれた綴りに応じた構文の終わりのコードを生成し、 スタックから取り下ろします。

unstak()のRatofor版は以下の通り。

# unstack.f -- unstack at end of statment
      include ratfor.def
      subroutine unstak(sp,lextyp,labval,token)
      integer sp
      integer labval(MAXSTACK)
      character token
      character lextyp(MAXSTACK)

      for ( ; sp > 1; sp = sp -1) {
          if (lextyp(sp) == LBRACE)
              break
          if ((lextyp(sp) == LEXIF) & (token == LEXELSE))
              break
          if (lextyp(sp) == LEXIF)
              call outcon(labval(sp))
          else if (lextyp(sp) == LEXELSE) {
              if (sp > 2)
                  sp = sp - 1
              call outcon(labval(sp)+1)
              }
          else if (lextyp(sp) == LEXDO)
              call dostat(labval(sp))
          else if (lextyp(sp) == LEXFOR)
              call forsta(labval(sp))
          else if (lextyp(sp) == LEXREPEAT)
              call repats(labval(sp))
          else if (lextyp(sp) == LEXWHILE)
              call whiles(labval(sp))
          }
      return
      end

WATCOM Fortran77版は以下の通り。

c unstack.f -- unstack at end of statment
      include ratfor.def
      subroutine unstak(sp,lextyp,labval,token)
      integer labval(MAXSTACK),sp
      integer*1 lextyp(MAXSTACK),token

      while (sp .gt. 1) do
          if (lextyp(sp) .eq. LBRACE) then
              exit
          end if
          if ((lextyp(sp) .eq. LEXIF) .and. (token .eq. LEXELSE)) then
              exit
          end if
          if (lextyp(sp) .eq. LEXIF) then
              call outcon(labval(sp))
          else if (lextyp(sp) .eq. LEXELSE) then
              if (sp .gt. 2) then
                  sp = sp - 1
              end if
              call outcon(labval(sp)+1)
          else if (lextyp(sp) .eq. LEXDO) then
              call dostat(labval(sp))
          else if (lextyp(sp) .eq. LEXFOR) then
              call forsta(labval(sp))
          else if (lextyp(sp) .eq. LEXREPEAT) then
              call repats(labval(sp))
          else if (lextyp(sp) .eq. LEXWHILE) then
              call whiles(labval(sp))
          end if
          sp = sp - 1
      end while
      return
      end

Ratforのメインプリグラムはとても簡単です。

Ratofor版は以下の通り。

# ratfor.r4 -- main program for Ratfor
      program ratfor
      call initfile
      call parse
      stop
      end

WATCOM Fortran77版は以下の通り。

c ratfor.f -- main program for Ratfor
      program ratfor
      call initfile
      call parse
      stop
      end

Ratforプリプロセッサー -- 字句解析2017年05月13日 15:55

Ratforで書かれたプログラムからtokenを切り出すのは、defineやmacroと同様であるが、 引用符の通り扱いに注意が必要である。引用符は、ほとんどの場合ペアで使われ、その間に NEWLINEが入ることはない。この点に配慮する必要がある。

Ratofor版は以下の通り。

# gtoken.f -- get token for ratfor
      include ratfor.def
      character function gtoken(lexstr,toksiz)
      integer toksiz
      character lexstr(toksiz)
      character ngetc,type
      integer i
      character c

      include cline.ri

      while (ngetc(c) != EOF)
          if ((c != BLANK) & (c != TAB))
              break
      call putbak(c)
      for(i = 1;i < toksiz-1;i = i +1) {
          gtoken = type(ngetc(lexstr(i)))
          if ((gtoken != LETTER) & (gtoken != DIGIT))
              break
          }
      if (i >= toksiz-1)
          call synerr('token too long.')
      if (i > 1)
          call putbak(lexstr(i))
          lexstr(i) = EOS
          gtoken = ALPHA
      else if ((lexstr(1) == SQUOTE) | (lexstr(1) == DQUOTE))
          for (i = 2;ngetc(lexstr(i)) != lexstr(1)) {
              if (lexstr(i) == NEWLINE) {
                  call synerr('missing quote.')
                  lexstr(i) = lexstr(1)
                  call putbak(NEWLINE)
                  break
                  }
              }
      else if (lexstr(1) == SHARP) { # strip comments
          while (ngetc(lexstr(1)) != NEWLINE)
              ;
          gtoken = NEWLINE
          }
      lexstr(i+1) = EOS
      if (lexstr(1) == NEWLINE)
          linect = linect + 1
      return
      end

WATCOM Fortran77版は以下の通り。

c gtoken.f -- get token for ratfor
      include ratfor.def
      integer*1 function gtoken(lexstr,toksiz)
      integer toksiz
      integer*1 lexstr(toksiz)
      integer*1 ngetc,type
      integer i
      integer*1 c

      include cline.fi

      while (ngetc(c) .ne. EOF) do
          if ((c .ne. BLANK) .and. (c .ne. TAB)) then
              exit
          end if
      end while
      call putbak(c)
      i = 1
      while (i .lt. toksiz-1) do
          gtoken = type(ngetc(lexstr(i)))
          if ((gtoken .ne. LETTER) .and. (gtoken .ne. DIGIT)) then
              exit
          end if
          i = i + 1
      end while
      if (i .ge. toksiz-1) then
          call synerr('token too long.')
      end if
      if (i .gt. 1) then
          call putbak(lexstr(i))
          lexstr(i) = EOS
          gtoken = ALPHA
      else if (lexstr(1) .eq. SQUOTE .or. lexstr(1) .eq. DQUOTE) then
          i = 2
          while (ngetc(lexstr(i)) .ne. lexstr(1)) do
              if (lexstr(i) .eq. NEWLINE) then
                  call synerr('missing quote.')
                  lexstr(i) = lexstr(1)
                  call putbak(NEWLINE)
                  exit
              end if
              i = i + 1
          end while
      else if (lexstr(1) .eq. SHARP) then ! strip comments
          while (ngetc(lexstr(1)) .ne. NEWLINE) do
              ! nothing to do
          end while
          gtoken = NEWLINE
      end if
      lexstr(i+1) = EOS
      if (lexstr(1) .eq. NEWLINE) then
          linect = linect + 1
      end if
      return
      end

gtoken()の下請けルーチンsynerr()はsyntax errorを出力する。errorは、行番号とともに出力されるが、 行番号linectは、必要なモジュールで共有できるように、共通領域clineにおいている。

synerr()のRatofor版は以下の通り。

# synerr -- report Ratfor syntax error
      include ratfor.def
      subroutine synerr(msg)
      character msg(MAXLINE)
      character lc(MAXLINE)
      integer itoc
      integer junk

      include cline.ri

      call remark('error at line.')
      junk = itoc(linect,lc,MAXLINE)
      call putlin(lc,ERROUT)
      call fputc(ERROUT,COLON)
      call remark(msg)
      return
      end

WATCOM Fortran77版は以下の通り。

c synerr -- report Ratfor syntax error
      include ratfor.def
      subroutine synerr(msg)
      character msg(MAXLINE)
      integer*1 lc(MAXLINE)
      integer itoc
      integer junk

      include cline.fi

      call remark('error at line.')
      junk = itoc(linect,lc,MAXLINE)
      call putlin(lc,ERROUT)
      call fputc(ERROUT,BLANK)
      call remark(msg)
      return
      end

clineのRatofor版は以下の通り。

# cline.ri
      common /cline/linect
      integer linect
      data linect/1/

WATCOM Fortran77版は以下の通り。

c cline.fi
      common /cline/linect
      integer linect
      data linect/1/

gtoken()で切り出されたtokenは、次のlex()に引き継がれ解析される。 ここでも字句の判定に、lookup()を使用する。

lex()のRatfor版は以下の通り。

# lex.r4- return lexical type of token
      include ratfor.def
      character function lex(lexstr)
      character lexstr(MAXTOK)
      character gtoken
      integer alldig,lookup
      character ltype(2)

      while (gtoken(lexstr,MAXTOK) == NEWLINE)
          ;
      lex = lexstr(1)
      if (lex == EOF | lex == SEMICOL | lex == LBRACE | lex == RBRACE))
          return
      if (alldig(lexstr)==YES)
          lex = LEXDIGITS
      else if(lookup(lexstr,ltype) == YES)
          lex = ltype(1)
      else
          lex = LEXOTHER
      return
      end

WATCOM Fortran77版は以下の通り。

c lex.f- return lexical type of token
      include ratfor.def
      integer function lex(lexstr)
      integer*1 lexstr(MAXTOK)
      integer*1 gtoken
      integer alldig,lookup
      integer*1 ltype(2)

      while (gtoken(lexstr,MAXTOK) .eq. NEWLINE) do
          ! nothing to do
      end while
      lex = lexstr(1)
      if ((lex .eq. EOF) .or. (lex .eq. SEMICOL) .or.
     1    (lex .eq. LBRACE) .or. (lex .eq. RBRACE)) then
          return
      end if
      if (alldig(lexstr)) then
          lex = LEXDIGITS
      else if(lookup(lexstr,ltype) .eq. YES) then
          lex = ltype(1)
      else
          lex = LEXOTHER
      end if
      return
      end

lex()の下請けルーチンalldig()は、tokenが数字列か否かを判定する。

alldig()のRatfor版は以下の通り。

# alldig.r4 -- return YES if str is all digis
      include ratfor.def
      integer function alldig(s)
      character s(ARB)
      character type
      character i

      alldig = NO
      if (s(1) == EOS)
          return
      for (i = 1; s(i) != EOS; i = i +1)
          if (type(s(i)) != DIGIT)
              return
      alldig = YES
      return
      end

WATCOM Fortran77版は以下の通り。

c alldig -- return YES if str is all digis
      include ratfor.def
      integer function alldig(s)
      integer*1 s(ARB)
      integer*1 type
      integer i

      alldig = NO
      if (s(1) .eq. EOS) then
          return
      end if
      i = 1
      while (s(i) .ne. EOS) do
          if (type(s(i)) .ne. DIGIT) then
              return
          end if
          i = i + 1
      end while
      alldig = YES
      return
      end

Ratforプリプロセッサー -- RatforからFortranへの変換2017年04月29日 09:51

ここまで、Ratforで書かれたプログラムを手作業でWATCOM Fortran 77でコンパイル できるよう修正してきた。これから、RatforのコードからFortran IVのレベルのコードに変換する プリプロセッサーを紹介する。

これまでに作成したツールとプリプロセッサーを使って、 Ratforで書かれたプログラムを実行可能なプログラムにする手順は、 以下のようになる。

          ,--------------,
          |    Ratfor    |
          | 原プログラム |
          '--------------'
                 |
                 V           .------------.
             [include]<------| Ratfor.def |
                 |           '------------'
                 V
              [macro]
                 |
                 V
              [ratfor]
                 |
                 V
          [Watcom Fortran77]
                 |
                 V
           .------------.
           | 実行可能な |
           { プログラム |
           '------------'

ところで、Ratfor言語の定義をBNFで記述すると以下のようになる。

          プログラム : 文
                     | プログラム 文
          文         : if ( 条件 ) 文
                     | if ( 条件 ) 文 else 文
                     | while ( 条件 ) 文
                     | for ( 初期設定; 条件; 再設定 ) 文
                     | repeat 文
                     | repeat 文 until ( 条件 )
                     | do 限界指定部 文
                     | 数字の列 文
                     | break
                     | next
                     | { プログラム }
                     | その他

Ratfor言語は、10種類程度の文からなっており、"その他"とはRatforが知らない文でFortranの 文が該当する。Ratforは、"if"や"while" などの制御構造を持った文をFortranの"if","go to","continue"などを 使って、制御構造を作りだすのである。

文の変換規則(コード変換規則)は、以下のようになる。

"if" 文

          if ( 条件 ) 文
は、
          if (.not. ( 条件 )) goto L
             文
        L continue

"if--else" 文

          if ( 条件 ) 文1 else 文2
は、
          if (.not. ( 条件 )) goto L
             文1
          go to L1
        L continue
             文2
       L1 continue

"while" 文

          while ( 条件 ) 文
は、
          continue
        L if (.not. ( 条件 )) go to L1
              文
              go to L
       L1 continue

"for" 文

          for ( 初期設定; 条件; 再設定 ) 文
は、
          continue
          初期設定
        L if (.not. ( 条件 )) goto L2
             文
       L1    continue
             再設定
             goto L
       L2    continue

"repeat" 文

          repeat 文
は、
          continue
        L continue
              文
       L1     continue
              go to L
       L2 continue

"repeat-until" 文

          continue
        L continue
             文
       L1    continue
             if (.not. ( 条件 )) go to L
       L2 continue

"do" 文

          do 限界指定部 文
は、
          do L 限界指定部
              文
       L1 continue
        L continue

"break"文は、"while"、"for"、"repeat"、"repeat-until"、"do"のループから抜け出すのに使用できる。 すなわち、ループの次の文に制御が移る。

"next"文は、ループの残りをジャンプして次の繰り返しに制御を移す。 "while"、"repeat-until"、"do"の各ループでは、条件判定部に、 "for"では、再設定に、"repeat"では、ループ本体の先頭に、それぞれ制御が移る。

論理式に使う演算子"<"や"&"等は、変換される。

          Ratforの演算子    Fortranの演算子
          --------------    ---------------
                >                .gt.
                >=               .ge.
                <                .lt.
                <=               .le.
                ==               .eq.
                !=               .ne.
                !                .not.
                &                .and.
                |                .or.

引数付きマクロ処理 -- 機能改善2017年04月21日 13:54

完成した"macro"を使ってみて、以下の不具合が見つかった。

  1. マクロの外で、'や"で囲まれた文字列(たいていの場合文字列定数)に含まれるマクロ名も置換されてしまう。
  2. マクロ"string"は、英数字しか展開できない。
  3. マクロ"string"の下請けマクロ"len","str"が、プログラム中の変数とぶつかる可能性が多々ある。

このうち2に関しては、Ratforプリプロセッサーに組み込むこととし、 1は、macroのメインルーチンを手直しして対応し、3は、マクロ名を手直しして対応する。

macroの、メインルーチンの修正点は、マクロの外を処理している最中に "や'が出現したら、対応する"や'が出現するまで読み込み、出力する。ただし、 対応する"や'が出現しない場合は、先頭の"だけを出力する。

RATFOR版は、以下の通り。

# macro.r4 -- expand macros with arguments
      program macro
      character gettok
      character defn(MAXDEF),t,token(MAXTOK)
      integer lookup,push
      integer ap,argstk(ARGSIZE),callst(CALLSIZE),i,nlb,plev(CALLSIZE)

      string balp "()"
      string defnam "define"
      character deftyp(2)
      data deftyp(1)/DEFTYPE/,deftyp(2)/EOS/
      string incnam "incr"
      character inctype(2)
      data inctyp(1)/INCTYPE/,inctyp(2)/EOS/
      string subnam "substr"
      character subtype(2)
      data subtyp(1)/SUBTYPE/,subtyp(2)/EOS/
      string ifnam "ifelse"
      character iftype(2)
      data iftyp(1)/IFTYPE/,iftyp(2)/EOS/
      string udfnam "ifelse"
      character udftype(2)
      data udftyp(1)/UDFTYPE/,udftyp(2)/EOS/
      string ifdnam "ifdef"
      character ifdtype(2)
      data ifdtyp(1)/IFDTYPE/,ifdtyp(2)/EOS/

      include cmacro.fi

      call initfile
      call inittbl
      call initbuf
      call instal(defnam,deftyp)
      call instal(incnam,inctyp)
      call instal(subnam,subtyp)
      call instal(ifnam,iftyp)
      call instal(udfnam,udftyp)
      call instal(ifdnam,ifdtyp)
      cp = 0
      ap = 1
      ep = 1
      for (t = gettok(token,MAXTOK); t !=  EOF; t = gettok(token,MAXTOK)) {
          if (t == ALPHA) {
              if (lookup(token,defn) == NO)
                  call puttok(token)
              else {                    # defined; put it in eval stack
                  cp = cp + 1
                  if (cp > CALLSIZE) then
                      call error('call stack overflow.')
                  callst(cp) = ap
                  ap = push(ep,argstk,ap)
                  call puttok(defn)     # stack definition
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  call puttok(token)    # stack name
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  t = gettok(token,MAXTOK) # peek at next
                  call pbstr(token)
                  if (t != LPAREN)      # add ( ) if not present
                      call pbstr(balp)
                  plev(cp) = 0
                  }
              }
          else if (t == LBRACK) {       # strip one level of [ ]
              nlb = 1
              repeat {
                  t = gettok(token,MAXTOK)
                  if (t == LBRACK)
                      nlb = nlb + 1
                  else if (t == RBRACK) {
                      nlb = nlb - 1
                      if (nlb == 0)
                          break
                      }
                  else if (t == EOF)
                      call error('EOF in string.')
                  call puttok(token)
                  }
          else if (cp == 0) {           # not in a macro at all
              if (token(1) == SQUOTE ! token(1) == DQUOTE) {
                  for (i = 2; ngetc(token(i)) != token(1); i = i + 1) {
                      if (token(i) == NEWLINE) {
                          token(i+1) = EOS
                          call pbstr(token(2))
                          i = 1
                          break
                          }
                      }
                  token(i+1) = EOS
                  }
              call puttok(token)
              }
          else if (t == LPAREN)
              if (plev(cp) > 0)
                  call puttok(token)
              plev(cp) = plev(cp) + 1
          else if (t == RPAREN) {
              plev(cp) = plev(cp) - 1
              if (plev(cp) .gt. 0)
                  call puttok(token)
              else {                    # end of argument list
                  call putchr(EOS)
                  call eval(argstk,callst(cp),ap-1)
                  ap = callst(cp)       # pop eval stack
                  ep = argstk(ap)
                  cp = cp - 1
                  }
          else if ((t == COMMA) $ (plev(cp) == 1)) {
              call putchr(EOS)
              ap = push(ep,argstk,ap)
              }
          else
              call puttok(token)
          }
      if (cp != 0)
          call error('unexpected EOF.')
      stop
      end

WATCOM Fortran 77版は以下の通り。

c macro.f -- expand macros with arguments
      include ratfor.def
      program macro
      integer*1 gettok,ngetc
      integer*1 defn(MAXDEF),t,token(MAXTOK)
      integer lookup,push
      integer ap,argstk(ARGSIZE),callst(CALLSIZE),i,nlb,plev(CALLSIZE)

      integer*1 balp(3)
      data balp(1)/LPAREN/
      data balp(2)/RPAREN/
      data balp(3)/EOS/

      integer*1 defnam(7)
      data defnam(1)/LETd/
      data defnam(2)/LETe/
      data defnam(3)/LETf/
      data defnam(4)/LETi/
      data defnam(5)/LETn/
      data defnam(6)/LETe/
      data defnam(7)/EOS/
      integer*1 deftyp(2)
      data deftyp(1)/DEFTYPE/
      data deftyp(2)/EOS/

      integer*1 incnam(5)
      data incnam(1)/LETi/
      data incnam(2)/LETn/
      data incnam(3)/LETc/
      data incnam(4)/LETr/
      data incnam(5)/EOS/
      integer*1 inctyp(2)
      data inctyp(1)/INCTYPE/
      data inctyp(2)/EOS/

      integer*1 subnam(7)
      data subnam(1)/LETs/
      data subnam(2)/LETu/
      data subnam(3)/LETb/
      data subnam(4)/LETs/
      data subnam(5)/LETt/
      data subnam(6)/LETr/
      data subnam(7)/EOS/
      integer*1 subtyp(2)
      data subtyp(1)/SUBTYPE/
      data subtyp(2)/EOS/

      integer*1 ifnam(7)
      data ifnam(1)/LETi/
      data ifnam(2)/LETf/
      data ifnam(3)/LETe/
      data ifnam(4)/LETl/
      data ifnam(5)/LETs/
      data ifnam(6)/LETe/
      data ifnam(7)/EOS/
      integer*1 iftyp(2)
      data iftyp(1)/IFTYPE/
      data iftyp(2)/EOS/

      integer*1 udfnam(6)
      data udfnam(1)/LETu/
      data udfnam(2)/LETn/
      data udfnam(3)/LETd/
      data udfnam(4)/LETe/
      data udfnam(5)/LETf/
      data udfnam(6)/EOS/
      integer*1 udftyp(2)
      data udftyp(1)/UDFTYPE/
      data udftyp(2)/EOS/

      integer*1 ifdnam(6)
      data ifdnam(1)/LETi/
      data ifdnam(2)/LETf/
      data ifdnam(3)/LETd/
      data ifdnam(4)/LETe/
      data ifdnam(5)/LETf/
      data ifdnam(6)/EOS/
      integer*1 ifdtyp(2)
      data ifdtyp(1)/IFDTYPE/
      data ifdtyp(2)/EOS/

      include cmacro.fi

      call initfile
      call inittbl
      call initbuf
      call instal(defnam,deftyp)
      call instal(incnam,inctyp)
      call instal(subnam,subtyp)
      call instal(ifnam,iftyp)
      call instal(udfnam,udftyp)
      call instal(ifdnam,ifdtyp)
      cp = 0      ! current call stack pointer
      ap = 1      ! next free position in argstk
      ep = 1      ! next free position in evalst
      t = gettok(token,MAXTOK) 
      while (t .ne. EOF) do
          if (t .eq. ALPHA) then
              if (lookup(token,defn) .eq. NO) then
                  call puttok(token)
              else                          ! defined; put it in eval stack
                  cp = cp + 1
                  if (cp .gt. CALLSIZE) then
                      call error('call stack overflow.')
                  end if
                  callst(cp) = ap
                  ap = push(ep,argstk,ap)
                  call puttok(defn)         ! stack definition
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  call puttok(token)        ! stack name
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  t = gettok(token,MAXTOK)  ! peek at next
                  call pbstr(token)
                  if (t .ne. LPAREN) then   ! add ( ) if not present
                      call pbstr(balp)
                  end if
                  plev(cp) = 0
              end if
          else if (t .eq. LBRACK) then      ! strip one level of [ ]
              nlb = 1
              loop
                  t = gettok(token,MAXTOK)
                  if (t .eq. LBRACK) then
                      nlb = nlb + 1
                  else if (t .eq. RBRACK) then
                      nlb = nlb - 1
                      if (nlb .eq. 0) then
                          exit
                      end if
                  else if (t .eq. EOF) then
                      call error('EOF in string.')
                  end if
                  call puttok(token)
              end loop
          else if (cp .eq. 0) then          ! not in a macro at all
              if (token(1) .eq. SQUOTE .or. token(1) .eq. DQUOTE) then
                  i = 2
                  while (ngetc(token(i)) .ne. token(1)) do
                      if (token(i) .eq. NEWLINE) then
                          token(i+1) = EOS
                          call pbstr(token(2))
                          i = 1
                          exit
                      end if
                      i = i + 1
                  end while
                  token(i+1) = EOS
              end if
              call puttok(token)
          else if (t .eq. LPAREN) then
              if (plev(cp) .gt. 0) then
                  call puttok(token)
              end if
              plev(cp) = plev(cp) + 1
          else if (t .eq. RPAREN) then
              plev(cp) = plev(cp) - 1
              if (plev(cp) .gt. 0) then
                  call puttok(token)
              else                         ! end of argument list
                  call putchr(EOS)
                  call eval(argstk,callst(cp),ap-1)
                  ap = callst(cp)          ! pop eval stack
                  ep = argstk(ap)
                  cp = cp - 1
              end if
          else if ((t .eq. COMMA) .and. (plev(cp) .eq. 1)) then
              call putchr(EOS)
              ap = push(ep,argstk,ap)
          else
              call puttok(token)
          end if
          t = gettok(token,MAXTOK)
      end while

      if (cp .ne. 0) then
          call error('unexpected EOF.')
      end if

      stop
      end

マクロ"string"の下請けマクロ"len","str"は、以下のように変更した。

define(00length00,[ifelse($1,,0,[incr(00length00(substr($1,2)))])])
define(string,[integer*1 $1(00length00(substr($2,2)))
00string00($1,substr($2,2),0)
      data $1(00length00(substr($2,2)))/EOS/
])
define(00string00,[ifelse($2,",,      data $1(incr($3))/[LET]substr($2,1,1)/
[00string00($1,substr($2,2),incr($3))])])

引数付きマクロ処理 -- 機能拡張(2)2017年04月14日 19:53

組み込み関数"ifelse"の実装は以下の通り。

RATFOR版

# doif.f -- select one of two arguments
      include ratfor.def
      subroutine doif(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer equal
      integer a2,a3,a4,a5

      include cmacro.ri

      if (j-i < 5)
          return

      a2 = argstk(i + 2)
      a3 = argstk(i + 3)
      a4 = argstk(i + 4)
      a5 = argstk(i + 5)

      if (equal(evalst(a2),evalst(a3)) == YES)
          call pbstr(evalst(a4))
      else
          call pbstr(evalst(a5))
      return
      end

WATCOM Fortran 77版は以下の通り。

c doif.f -- select one of two arguments
      include ratfor.def
      subroutine doif(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer equal
      integer a2,a3,a4,a5

      include cmacro.fi

      if (j-i .lt. 5) then
          return
      end if

      a2 = argstk(i + 2)
      a3 = argstk(i + 3)
      a4 = argstk(i + 4)
      a5 = argstk(i + 5)

      if (equal(evalst(a2),evalst(a3)) .eq. YES) then
          call pbstr(evalst(a4))
      else
          call pbstr(evalst(a5))
      end if
      return
      end

組み込み関数"incr"は、引数を数値化して、下請けルーチンpbnum()で+1し、文字列化して入力に戻す。"incr"の実装は 以下の通り。

RATFOR版

# doinc.r4 -- increment argument by 1
      include ratfor.def
      subroutine doincr(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer ctoi
      integer k

      include cmacro.ri

      k = argstk(i+2)
      call pbnum(ctoi(evalst,k)+1)
      return
      end

WATCOM Fortran 77版は以下の通り。

c doinc.f -- increment argument by 1
      include ratfor.def
      subroutine doinc(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer ctoi
      integer k

      include cmacro.fi

      k = argstk(i+2)
      call pbnum(ctoi(evalst,k)+1)
      return
      end

下請けルーチンpbnum()は以下の通り。

RATFOR版は以下の通り。

# pbnum.r4 -- convert number to string, push back on input
      include ratfor.def
      subroutine pbnum(n)
      integer n
      integer mod
      integer m,num
      string digits "0123456789"

      num = n
      repeat {
          m = mod(num,10)
          call putbak(digits(m+1))
          num = num/10
          } until (num == 0)
      return
      end

WATCOM Fortran 77版は以下の通り。

c pbnum.f -- convert number to string, push back on input
      include ratfor.def
      subroutine pbnum(n)
      integer n
      integer mod
      integer m,num
      integer*1 digits(11)
      data digits(1)/LET0/
      data digits(2)/LET1/
      data digits(3)/LET2/
      data digits(4)/LET3/
      data digits(5)/LET4/
      data digits(6)/LET5/
      data digits(7)/LET6/
      data digits(8)/LET7/
      data digits(9)/LET8/
      data digits(10)/LET9/
      data digits(11)/EOS/

      num = n
      loop
          m = mod(num,10)
          call putbak(digits(m+1))
          num = num/10
      until (num .eq. 0)
      return
      end

組み込み関数"substr"の実装は、以下の通り。

RATFOR版は以下の通り。

# dosub.r4 -- select substring
      include ratfor.def
      subroutine dosub(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer ctoi,length,min
      integer ap,fc,k,nc

      include cmacro.ri

      if (j-i < 3)
          return
      if (j-i < 4)
          nc = MAXTOK
      else {
          k = argstk(i+4)
          nc = ctoi(evalst,k)           # number of characters
          }
      k = argstk(i+3)                   # origin
      ap = argstk(i+2)                  # target string
      fc = ap + ctoi(evalst,k) - 1      # first char of substring
      if ((fc >= ap) & (fc < ap+length(evalst(ap)))) { # subarrays
          k = fc + min(nc,length(evalst(fc))) - 1
          for ( ; k >= fc ; k = k -1)
              call putbak(evalst(k))
          }
      return
      end

WATCOM Fortran 77版は以下の通り。

c dosub.f -- select substring
      include ratfor.def
      subroutine dosub(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer ctoi,length,min
      integer ap,fc,k,nc

      include cmacro.fi

      if (j-i .lt. 3) then
          return
      end if
      if (j-i .lt. 4) then
          nc = MAXTOK
      else
          k = argstk(i+4)
          nc = ctoi(evalst,k)      ! number of characters
      end if
      k = argstk(i+3)              ! origin
      ap = argstk(i+2)             ! target string
      fc = ap + ctoi(evalst,k) - 1 ! first char of substring
      if ((fc .ge. ap) .and. (fc .lt. ap+length(evalst(ap)))) then ! subarrays
          k = fc + min(nc,length(evalst(fc))) - 1
          while (k .ge. fc) do
              call putbak(evalst(k))
              k = k - 1
          end while
      end if
      return
      end

組み込み関数"undef"の実装は、以下の通り。

RATFOR版

# doudf.f -- undefine macro
      include ratfor.def
      subroutine doudf(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer a2

      include cmacro.r4

      if (j-i .lt. 1)
          return

      a2 = argstk(i + 2)
      call uninst(evalst(a2))
      return
      end

WATCOM Fortran 77版は以下の通り。

c doudf.f -- undefine macro
      include ratfor.def
      subroutine doudf(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer a2

      include cmacro.fi

      if (j-i .lt. 1) then
          return
      end if

      a2 = argstk(i + 2)
      call uninst(evalst(a2))
      return
      end

組み込み関数"ifdef"の実装は、以下の通り。

RATFOR版

# doifd.r4 -- define if macro is defined
      include ratfor.def
      subroutine doifd(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer lookup
      integer a2,a3,a4
      character junk(MAXDEF)

      include cmacro.fi

      if (j-i .lt. 4)
          return

      a2 = argstk(i + 2)
      a3 = argstk(i + 3)
      a4 = argstk(i + 4)
      if (lookup(evalst(a2),junk) .eq. YES)
          call pbstr(evalst(a3))
      else
          call pbstr(evalst(a4))
      return
      end

WATCOM Fortran 77版は以下の通り。

# doifd.r4 -- define if macro is defined
      include ratfor.def
      subroutine doifd(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer lookup
      integer a2,a3,a4
      character junk(MAXDEF)

      include cmacro.fi

      if (j-i .lt. 4)
          return

      a2 = argstk(i + 2)
      a3 = argstk(i + 3)
      a4 = argstk(i + 4)
      if (lookup(evalst(a2),junk) .eq. YES)
          call pbstr(evalst(a3))
      else
          call pbstr(evalst(a4))
      return
      end

マクロの応用を紹介する。

文字列の長さを返す"len"。再帰的定義になっているが、込み入ってない。

          define(len,[ifelse($1,,0,[incr(len(substr($1,2)))])])

文字列を定義する"string"。

           string(name,"STRING")

とすると

           integer name(6)
           data name(1)/LETS/
           data name(2)/LETT/
           data name(3)/LETR/
           data name(4)/LETN/
           data name(5)/LETG/

           data name(6)/EOS/

と展開される。"string"の定義は、以下の通り。

          define(string,[integer $1(len(substr($2,2)))
          str($1,substr($2,2),0)
                data $1(len(substr($2,2)))/EOS/
          ])

下請けルーチンの"str"は、以下の通り。

          define(str,[ifelse($2,",,      data $1(incr($3))/[LET]substr($2,1,1)/
          [str($1,substr($2,2),incr($3))])])

macro.r4中の

      string(balp,"()")
は、うまく展開できない。

引数付きマクロ処理 -- 機能拡張(1)2017年04月14日 12:39

マクロが動くようになったので、バッチファイル"fim.bat"を作成し、マクロの展開に 使用する。

          @echo off
          rem fim.bat
          cd ..\src
          ..\exe\include < %1.f | ..\exe\macro > %1.for
          cd ..\bat

ここで、いくつかの有用な組み込み関数を追加する。追加する組み込み関数は、以下の通り。

  • ifelse(a,b,c,d) -- aとbが文字列として等しければ、cをそうでなければ、dを返す。
  • incr(a) -- aに+1した値を返す。
  • substr(a,b,c) -- 文字列aのb文字目から、c文字を返す。cがなければ、文字列の最後まで返す。
  • undef(a) -- マクロaを削除する。
  • ifdef(a,b,c) -- マクロaが定義されていれば、bをそうでなければ、cを返す。

簡単な例を以下に示す。

          define(EOF,-1)
          define(EOS,-2)
          define(MAXCARD,80)
          define(MAXLINE,[incr(MAXCARD)]) -- MAXLINEは81になる。
          define(FOO,0)
          define(STR1,ABCDE)
          define(STR2,12345)
          ifdef([BAR],STR1,STR2) -- BARは定義されていないので、"12345"が返る。
          ifdef([FOO],STR1,STR2) -- FOOは定義されているので、"ABCDE"が返る。
          substr(STR1,3,2) -- "CD"が返る。
          substr(STR2,3) -- "345"が返る。
          undef([FOO]) -- FOOを削除する。"[]"が必要である。
          define(compare,[ifelse($1,$2,YES,NO)]) -- 2つの引数が等しければ、YESを そうでなければNOを返すマクロ"compare"を定義する。

追加する組み込み関数のそれぞれの処理は、eval()の中で各処理ルーチンを呼び出す。新しいeval()は、以下の通り。

RATFOR版

# eval.r4 - expand args i through j: evaluate builtin or push back defn
      subroutine eval(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer iindex,length
      integer argno,k,m,n,t,td
      include cmacro.ri
      string digits "0123456789"

      t = argstk(i)
      td = evalst(t)
      if (td == DEFTYPE)
          call dodef(argstk,i,j)
      else if (td == INCTYPE)
          call doinc(argstk,i,j)
      else if (td == SUBTYPE)
          call dosub(argstk,i,j)
      else if (td == IFTYPE)
          call doif(argstk,i,j)
      else if (td == UDFTYPE)
          call doudf(argstk,i,j)
      else if (td == IFDTYPE) {
          call doifd(argstk,i,j)
          }
      else {
          for (k = t + length(evalst(t)) - 1; k > t); k = k - 1)
              if (evalst(k-1) != ARGFLAG)
                  call putbak(evalst(k))
              else {
                  argno = iindex(digits,evalst(k)) - 1
                  if (argno >= 0) {
                      n = i + argno + 1
                      m = argstk(n)
                      call pbstr(evalst(m))
                      }
                  k = k - 1     # skip over $
                  }
          if (k == t)           # do last character
              call putbak(evalst(k))
          }
      return
      end

WATCOM Fortran 77版は以下の通り。

c eval.f - expand args i through j: evaluate builtin or push back defn
      include ratfor.def
      subroutine eval(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer iindex,length
      integer argno,k,m,n,t,td,junk
      include cmacro.fi

      integer*1 digits(11)
      data digits(1)/LET0/
      data digits(2)/LET1/
      data digits(3)/LET2/
      data digits(4)/LET3/
      data digits(5)/LET4/
      data digits(6)/LET5/
      data digits(7)/LET6/
      data digits(8)/LET7/
      data digits(9)/LET8/
      data digits(10)/LET9/
      data digits(11)/EOS/

      t = argstk(i)
      td = evalst(t)
      if (td .eq. DEFTYPE) then
          call dodef(argstk,i,j)
      else if (td .eq. INCTYPE) then
          call doinc(argstk,i,j)
      else if (td .eq. SUBTYPE) then
          call dosub(argstk,i,j)
      else if (td .eq. IFTYPE) then
          call doif(argstk,i,j)
      else if (td .eq. UDFTYPE) then
          call doudf(argstk,i,j)
      else if (td .eq. IFDTYPE) then
          call doifd(argstk,i,j)
      else
          k = t + length(evalst(t)) - 1
          while (k .gt. t) do
              if (evalst(k-1) .ne. ARGFLAG) then
                  call putbak(evalst(k))
              else
                  argno = iindex(digits,evalst(k)) - 1
                  if (argno .ge. 0) then
                      n = i + argno + 1
                      m = argstk(n)
                      call pbstr(evalst(m))
                  end if
                  k = k - 1       ! skip over $
              end if
              k = k - 1
          end while
          if (k .eq. t) then
              call putbak(evalst(k))
          end if
      end if
      return
      end

メインルーチンmacroでは、追加した組み込み関数を登録する必要がある。

RATFOR版は以下の通り。

# macro.r4 -- expand macros with arguments
      program macro
      character gettok
      character defn(MAXDEF),t,token(MAXTOK)
      integer lookup,push
      integer ap,argstk(ARGSIZE),callst(CALLSIZE),nlb,plev(CALLSIZE)

      string balp "()"
      string defnam "define"
      character deftyp(2)
      data deftyp(1)/DEFTYPE/,deftyp(2)/EOS/
      string incnam "incr"
      character inctype(2)
      data inctyp(1)/INCTYPE/,inctyp(2)/EOS/
      string subnam "substr"
      character subtype(2)
      data subtyp(1)/SUBTYPE/,subtyp(2)/EOS/
      string ifnam "ifelse"
      character iftype(2)
      data iftyp(1)/IFTYPE/,iftyp(2)/EOS/
      string udfnam "ifelse"
      character udftype(2)
      data udftyp(1)/UDFTYPE/,udftyp(2)/EOS/
      string ifdnam "ifdef"
      character ifdtype(2)
      data ifdtyp(1)/IFDTYPE/,ifdtyp(2)/EOS/

      include cmacro.fi

      call initfile
      call inittbl
      call initbuf
      call instal(defnam,deftyp)
      call instal(incnam,inctyp)
      call instal(subnam,subtyp)
      call instal(ifnam,iftyp)
      call instal(udfnam,udftyp)
      call instal(ifdnam,ifdtyp)
      cp = 0
      ap = 1
      ep = 1
      for (t = gettok(token,MAXTOK); t !=  EOF; t = gettok(token,MAXTOK)) {
          if (t == ALPHA) {
              if (lookup(token,defn) == NO)
                  call puttok(token)
              else {                    # defined; put it in eval stack
                  cp = cp + 1
                  if (cp > CALLSIZE) then
                      call error('call stack overflow.')
                  callst(cp) = ap
                  ap = push(ep,argstk,ap)
                  call puttok(defn)     # stack definition
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  call puttok(token)    # stack name
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  t = gettok(token,MAXTOK) # peek at next
                  call pbstr(token)
                  if (t != LPAREN)      # add ( ) if not present
                      call pbstr(balp)
                  plev(cp) = 0
                  }
              }
          else if (t == LBRACK) {       # strip one level of [ ]
              nlb = 1
              repeat {
                  t = gettok(token,MAXTOK)
                  if (t == LBRACK)
                      nlb = nlb + 1
                  else if (t == RBRACK) {
                      nlb = nlb - 1
                      if (nlb == 0)
                          break
                      }
                  else if (t == EOF)
                      call error('EOF in string.')
                  call puttok(token)
                  }
          else if (cp == 0)             # not in a macro at all
              call puttok(token)
          else if (t == LPAREN)
              if (plev(cp) > 0)
                  call puttok(token)
              plev(cp) = plev(cp) + 1
          else if (t == RPAREN) {
              plev(cp) = plev(cp) - 1
              if (plev(cp) .gt. 0)
                  call puttok(token)
              else {                    # end of argument list
                  call putchr(EOS)
                  call eval(argstk,callst(cp),ap-1)
                  ap = callst(cp)       # pop eval stack
                  ep = argstk(ap)
                  cp = cp - 1
                  }
          else if ((t == COMMA) $ (plev(cp) == 1)) {
              call putchr(EOS)
              ap = push(ep,argstk,ap)
              }
          else
              call puttok(token)
          }
      if (cp != 0)
          call error('unexpected EOF.')
      stop
      end

WATCOM Fortran 77版は以下の通り。

c macro.f -- expand macros with arguments
      include ratfor.def
      program macro
      integer*1 gettok
      integer*1 defn(MAXDEF),t,token(MAXTOK)
      integer lookup,push
      integer ap,argstk(ARGSIZE),callst(CALLSIZE),nlb,plev(CALLSIZE)

      integer*1 balp(3)
      data balp(1)/LPAREN/
      data balp(2)/RPAREN/
      data balp(3)/EOS/

      integer*1 defnam(7)
      data defnam(1)/LETd/
      data defnam(2)/LETe/
      data defnam(3)/LETf/
      data defnam(4)/LETi/
      data defnam(5)/LETn/
      data defnam(6)/LETe/
      data defnam(7)/EOS/
      integer*1 deftyp(2)
      data deftyp(1)/DEFTYPE/
      data deftyp(2)/EOS/

      integer*1 incnam(5)
      data incnam(1)/LETi/
      data incnam(2)/LETn/
      data incnam(3)/LETc/
      data incnam(4)/LETr/
      data incnam(5)/EOS/
      integer*1 inctyp(2)
      data inctyp(1)/INCTYPE/
      data inctyp(2)/EOS/

      integer*1 subnam(7)
      data subnam(1)/LETs/
      data subnam(2)/LETu/
      data subnam(3)/LETb/
      data subnam(4)/LETs/
      data subnam(5)/LETt/
      data subnam(6)/LETr/
      data subnam(7)/EOS/
      integer*1 subtyp(2)
      data subtyp(1)/SUBTYPE/
      data subtyp(2)/EOS/

      integer*1 ifnam(7)
      data ifnam(1)/LETi/
      data ifnam(2)/LETf/
      data ifnam(3)/LETe/
      data ifnam(4)/LETl/
      data ifnam(5)/LETs/
      data ifnam(6)/LETe/
      data ifnam(7)/EOS/
      integer*1 iftyp(2)
      data iftyp(1)/IFTYPE/
      data iftyp(2)/EOS/

      integer*1 udfnam(6)
      data udfnam(1)/LETu/
      data udfnam(2)/LETn/
      data udfnam(3)/LETd/
      data udfnam(4)/LETe/
      data udfnam(5)/LETf/
      data udfnam(6)/EOS/
      integer*1 udftyp(2)
      data udftyp(1)/UDFTYPE/
      data udftyp(2)/EOS/

      integer*1 ifdnam(6)
      data ifdnam(1)/LETi/
      data ifdnam(2)/LETf/
      data ifdnam(3)/LETd/
      data ifdnam(4)/LETe/
      data ifdnam(5)/LETf/
      data ifdnam(6)/EOS/
      integer*1 ifdtyp(2)
      data ifdtyp(1)/IFDTYPE/
      data ifdtyp(2)/EOS/

      include cmacro.fi

      call initfile
      call inittbl
      call initbuf
      call instal(defnam,deftyp)
      call instal(incnam,inctyp)
      call instal(subnam,subtyp)
      call instal(ifnam,iftyp)
      call instal(udfnam,udftyp)
      call instal(ifdnam,ifdtyp)
      cp = 0      ! current call stack pointer
      ap = 1      ! next free position in argstk
      ep = 1      ! next free position in evalst
      t = gettok(token,MAXTOK) 
      while (t .ne. EOF) do
          if (t .eq. ALPHA) then
              if (lookup(token,defn) .eq. NO) then
                  call puttok(token)
              else                          ! defined; put it in eval stack
                  cp = cp + 1
                  if (cp .gt. CALLSIZE) then
                      call error('call stack overflow.')
                  end if
                  callst(cp) = ap
                  ap = push(ep,argstk,ap)
                  call puttok(defn)         ! stack definition
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  call puttok(token)        ! stack name
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  t = gettok(token,MAXTOK)  ! peek at next
                  call pbstr(token)
                  if (t .ne. LPAREN) then   ! add ( ) if not present
                      call pbstr(balp)
                  end if
                  plev(cp) = 0
              end if
          else if (t .eq. LBRACK) then      ! strip one level of [ ]
              nlb = 1
              loop
                  t = gettok(token,MAXTOK)
                  if (t .eq. LBRACK) then
                      nlb = nlb + 1
                  else if (t .eq. RBRACK) then
                      nlb = nlb - 1
                      if (nlb .eq. 0) then
                          exit
                      end if
                  else if (t .eq. EOF) then
                      call error('EOF in string.')
                  end if
                  call puttok(token)
              end loop
          else if (cp .eq. 0) then          ! not in a macro at all
              call puttok(token)
          else if (t .eq. LPAREN) then
              if (plev(cp) .gt. 0) then
                  call puttok(token)
              end if
              plev(cp) = plev(cp) + 1
          else if (t .eq. RPAREN) then
              plev(cp) = plev(cp) - 1
              if (plev(cp) .gt. 0) then
                  call puttok(token)
              else                         ! end of argument list
                  call putchr(EOS)
                  call eval(argstk,callst(cp),ap-1)
                  ap = callst(cp)          ! pop eval stack
                  ep = argstk(ap)
                  cp = cp - 1
              end if
          else if ((t .eq. COMMA) .and. (plev(cp) .eq. 1)) then
              call putchr(EOS)
              ap = push(ep,argstk,ap)
          else
              call puttok(token)
          end if
          t = gettok(token,MAXTOK)
      end while

      if (cp .ne. 0) then
          call error('unexpected EOF.')
      end if

      stop
      end

引数付きマクロ処理(一部修正)2017年01月26日 21:27

マクロの定義に、引数が使えるようになると、利便性が非常に向上する。簡単な例を 示す。まずは、マクロの定義は、以下のようになる。マクロgetc,putcの定義の中、$1が マクロの引数にである。引数は、$1から$9までである。

          define(STDIN,5)
          define(STDOUT,6)
          define(getc,getch(STDIN,$1))
          define(putc,putch(STDOUT,$1))

プログラム中では、以下のように、記述する。

          c = getc(c)
          call putc(c)

これが展開されると、以下のようになる。

          c = getch(5,c)
          call putch(6,c)

もう少し長いマクロの例を以下に示す。

          define(BLANK,32)
          define(TAB,9)
          define(skipbl,while($1($2) == BLANK | $1($2) == TAB)
               $2 = $2 + 1)

プログラム中では、

          skipbl(s,i)

展開されると、

          while(s(i) == 32 | s(i) == 9)
              i = i + 1

読み込み中にマクロに出会ったら、引数も含めてマクロ評価用スタックに積む。 引数の中にマクロ呼び出しがあったら、新しいマクロ評価用スタック領域を取り、 スタックに積む。そして、マクロを完全に評価して、入力に送り返す。そして、元の マクロの評価を続ける。

マクロ評価用スタックevalstは配列で表現され、マクロの名前、定義型、 引数が入る。一方、配列argstkは、evalstに格納された文字列の場所の 位置を示す。いくつものモジュールで共通の用いられるevalstは以下の通り。

RATFOR版は、

# cmacro.ri
      common /cmacro/cp,ep,evalst(EVALSIZE)
      integer cp       # current call stack pointer
      integer ep       # next free position in evalst
      character evalst # evaluation stack

WATCOM fortran 77版は、

! cmacro.fi
      common /cmacro/cp,ep,evalst(EVALSIZE)
      integer cp       ! current call stack pointer
      integer ep       ! next free position in evalst
      integer*1 evalst ! evaluation stack

このマクロでは、マクロや組み込み操作は出現したとき、 その場で全て展開することになっているので、それではまずいことがある。 たとえば、defをdefineの同義語として定義したいとき、

      define(def,define($1,$2))

とすれば良さそうだが、うまくいかない。まず、マクロ名"def"が、評価用スタックに積まれる。 次に、置き換え文字列"define($1,$2)"が評価されてしまい、"def"に対応する置き換え文字列が 空となってしまう。 これでは、目的を達成できないので、"["と"]"でくくられた範囲は、評価を遅らせる仕組みを 付け加える。

      define(def,[define($1,$2)])
      def(ABC,DEF)

とすると

      ABC

は、変換されて、

      DEF

となる。実は、引数なしのマクロプログラムのソースは、defineを通せない。 プログラム中のマクロ定義ではない"define"文字列がマクロの定義と 見間違えられてしまうのである。

引数なしのマクロには、"()"がつかない、これを特別扱いしないように、 "()"がついていないマクロに出会ったら、"()"を入力に送り返し、あたかも"()"が つぃているかのように振る舞わせる。

以上を踏まえた、引数付きマクロのRATFOR版は、以下の通り。

# macro.r4 -- expand macros with arguments
      program macro
      character gettok
      integer*1 defn(MAXDEF),t,token(MAXTOK)
      integer lookup,push
      integer ap,argstk(ARGSIZE),callst(CALLSIZE),nlb,plev(CALLSIZE)

      string balp "()"
      string defnam "define"
      character deftyp(2)
      data deftyp(1)/DEFTYPE/,deftyp(2)/EOS/

      include cmacro.fi

      call initfile
      call inittbl
      call instal(defnam,deftyp)
      cp = 0
      ap = 1
      ep = 1
      for (t = gettok(token,MAXTOK); t !=  EOF; t = gettok(token,MAXTOK)) {
          if (t == ALPHA) {
              if (lookup(token,defn) == NO)
                  call puttok(token)
              else {                    # defined; put it in eval stack
                  cp = cp + 1
                  if (cp > CALLSIZE) then
                      call error('call stack overflow.')
                  callst(cp) = ap
                  ap = push(ep,argstk,ap)
                  call puttok(defn)     # stack definition
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  call puttok(token)    # stack name
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  t = gettok(token,MAXTOK) # peek at next
                  call pbstr(token)
                  if (t != LPAREN)      # add ( ) if not present
                      call pbstr(balp)
                  plev(cp) = 0
                  }
              }
          else if (t == LBRACK) {       # strip one level of [ ]
              nlb = 1
              repeat {
                  t = gettok(token,MAXTOK)
                  if (t == LBRACK)
                      nlb = nlb + 1
                  else if (t == RBRACK) {
                      nlb = nlb - 1
                      if (nlb == 0)
                          break
                      }
                  else if (t == EOF)
                      call error('EOF in string.')
                  call puttok(token)
                  }
          else if (cp == 0)             # not in a macro at all
              call puttok(token)
          else if (t == LPAREN)
              if (plev(cp) > 0)
                  call puttok(token)
              plev(cp) = plev(cp) + 1
          else if (t == RPAREN) {
              plev(cp) = plev(cp) - 1
              if (plev(cp) .gt. 0) then
                  call puttok(token)
              else {                    # end of argument list
                  call putchr(EOS)
                  call eval(argstk,callst(cp),ap-1)
                  ap = callst(cp)       # pop eval stack
                  ep = argstk(ap)
                  cp = cp - 1
                  }
          else if ((t == COMMA) $ (plev(cp) == 1)) {
              call putchr(EOS)
              ap = push(ep,argstk,ap)
              }
          else
              call puttok(token)
          }
      if (cp != 0)
          call error('unexpected EOF.')
      stop
      end

WATCOM fortran 77版は、

! macro.f -- expand macros with arguments
      include ratfor.def
      program macro
      integer*1 gettok
      integer*1 defn(MAXDEF),t,token(MAXTOK)
      integer lookup,push
      integer ap,argstk(ARGSIZE),callst(CALLSIZE),nlb,plev(CALLSIZE)

      integer*1 balp(3)
      data balp(1)/LPAREN/
      data balp(2)/RPAREN/
      data balp(3)/EOS/

      integer*1 defnam(7)
      data defnam(1)/LETd/
      data defnam(2)/LETe/
      data defnam(3)/LETf/
      data defnam(4)/LETi/
      data defnam(5)/LETn/
      data defnam(6)/LETe/
      data defnam(7)/EOS/

      integer*1 deftyp(2)
      data deftyp(1)/DEFTYPE/
      data deftyp(2)/EOS/
      include cmacro.fi

      call initfile
      call inittbl
      call instal(defnam,deftyp)
      cp = 0      ! current call stack pointer
      ap = 1      ! next free position in argstk
      ep = 1      ! next free position in evalst
      t = gettok(token,MAXTOK) 
      while (t .ne. EOF) do
          if (t .eq. ALPHA) then
              if (lookup(token,defn) .eq. NO) then
                  call puttok(token)
              else                          ! defined; put it in eval stack
                  cp = cp + 1
                  if (cp .gt. CALLSIZE) then
                      call error('call stack overflow.')
                  end if
                  callst(cp) = ap
                  ap = push(ep,argstk,ap)
                  call puttok(defn)         ! stack definition
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  call puttok(token)        ! stack name
                  call putchr(EOS)
                  ap = push(ep,argstk,ap)
                  t = gettok(token,MAXTOK)  ! peek at next
                  call pbstr(token)
                  if (t .ne. LPAREN) then   ! add ( ) if not present
                      call pbstr(balp)
                  end if
                  plev(cp) = 0
              end if
          else if (t .eq. LBRACK) then      ! strip one level of [ ]
              nlb = 1
              loop
                  t = gettok(token,MAXTOK)
                  if (t .eq. LBRACK) then
                      nlb = nlb + 1
                  else if (t .eq. RBRACK) then
                      nlb = nlb - 1
                      if (nlb .eq. 0) then
                          exit
                      end if
                  else if (t .eq. EOF) then
                      call error('EOF in string.')
                  end if
                  call puttok(token)
              end loop
          else if (cp .eq. 0) then          ! not in a macro at all
              call puttok(token)
          else if (t .eq. LPAREN) then
              if (plev(cp) .gt. 0) then
                  call puttok(token)
              end if
              plev(cp) = plev(cp) + 1
          else if (t .eq. RPAREN) then
              plev(cp) = plev(cp) - 1
              if (plev(cp) .gt. 0) then
                  call puttok(token)
              else                         ! end of argument list
                  call putchr(EOS)
                  call eval(argstk,callst(cp),ap-1)
                  ap = callst(cp)          ! pop eval stack
                  ep = argstk(ap)
                  cp = cp - 1
              end if
          else if ((t .eq. COMMA) .and. (plev(cp) .eq. 1)) then
              call putchr(EOS)
              ap = push(ep,argstk,ap)
          else
              call puttok(token)
          end if
          t = gettok(token,MAXTOK)
      end while

      if (cp .ne. 0) then
          call error('unexpected EOF.')
      end if

      stop
      end

下請けルーチンputtok()のRATFOR版は、以下の通り。

# puttok.r4 -- put a token either on output or into evaluation stack
      subroutine puttok(str)
      character str(MAXTOK)
      integer i

      for (i = 1; str(i) != EOS; i = i + 1)
          call putchr(str(i))
      return
      end

WATCOM fortran 77版は、

! puttok.f -- put a token either on output or into evaluation stack
      include ratfor.def
      subroutine puttok(str)
      integer*1 str(MAXTOK)
      integer i

      i = 1
      while (str(i) .ne. EOS) do
          call putchr(str(i))
          i = i + 1
      end while
      return
      end

下請けルーチンputchr()のRATFOR版は、以下の通り。

# putchr -- put single char on output or into eveluation stack
      subroutine putchr(c)
      character c
      include cmacror.ri

      if (cp == 0)
          call putc(c)
      else {
          if (ep > EVALSIZE)
              call error('eveluation stack overflow.')
          evalst(ep) = c
          ep = ep + 1
          }
      return
      end

WATCOM fortran 77版は、

! putchr -- put single char on output or into eveluation stack
      include ratfor.def
      subroutine putchr(c)
      integer*1 c
      include cmacro.fi

      if (cp .eq. 0) then
          call putc(c)
      else
          if (ep .gt. EVALSIZE) then
              call error('eveluation stack overflow.')
          end if
          evalst(ep) = c
          ep = ep + 1
      end if
      return
      end

下請けルーチンeval()のRATFOR版は、以下の通り。

# eval.r4 - expand args i through j: evaluate builtin or push back defn
      subroutine eval(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer iindex,length
      integer argno,k,m,n,t,td
      include cmacro.ri
      string digits "0123456789"

      t = argstk(i)
      td = evalst(t)
      if (td == DEFTYPE)
          call dodef(argstk,i,j)
      else {
          for (k = t + length(evalst(t)) - 1; k > t); k = k - 1)
              if (evalst(k-1) != ARGFLAG)
                  call putbak(evalst(k))
              else {
                  argno = iindex(digits,evalst(k)) - 1
                  if (argno >= 0) {
                      n = i + argno + 1
                      m = argstk(n)
                      call pbstr(evalst(m))
                      }
                  k = k - 1     # skip over $
                  }
          if (k == t)           # do last character
              call putbak(evalst(k))
          }
      return
      end

WATCOM fortran 77版は、

! eval.f - expand args i through j: evaluate builtin or push back defn
      include ratfor.def
      subroutine eval(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer iindex,length
      integer argno,k,m,n,t,td
      include cmacro.fi

      integer*1 digits(11)
      data digits(1)/LET0/
      data digits(2)/LET1/
      data digits(3)/LET2/
      data digits(4)/LET3/
      data digits(5)/LET4/
      data digits(6)/LET5/
      data digits(7)/LET6/
      data digits(8)/LET7/
      data digits(9)/LET8/
      data digits(10)/LET9/
      data digits(11)/EOS/

      t = argstk(i)
      td = evalst(t)
      if (td .eq. DEFTYPE) then
          call dodef(argstk,i,j)
      else
          k = t + length(evalst(t)) - 1
          while (k .gt. t) do
              if (evalst(k-1) .ne. ARGFLAG) then
                  call putbak(evalst(k))
              else
                  argno = iindex(digits,evalst(k)) - 1
                  if (argno .ge. 0) then
                      n = i + argno + 1
                      m = argstk(n)
                      call pbstr(evalst(m))
                  end if
                  k = k - 1       ! skip over $
              end if
              k = k - 1
          end while
          if (k .eq. t) then
              call putbak(evalst(k))
          end if
      end if
      return
      end

下請けルーチンdodef()のRATFOR版は、以下の通り。

# dodef.rf -- install definition in table
      subroutine dodef(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer a2,a3
      include cmacro.ri

      if (j-i .gt. 2) {
          a2 = argstk(i+2)
          a3 = argstk(i+3)
          call instal(evalst(a2),evalst(a3))  # subarrays
          }
      return
      end

WATCOM fortran 77版は、

! dodef.f -- install definition in table
      include ratfor.def
      subroutine dodef(argstk,i,j)
      integer i,j,argstk(ARGSIZE)
      integer a2,a3
      include cmacro.fi

      if (j-i .gt. 2) then
          a2 = argstk(i+2)
          a3 = argstk(i+3)
          call instal(evalst(a2),evalst(a3))  ! subarrays
      end if
      return
      end

文書整形 -- 出力の右揃えなど2016年12月14日 18:13

現在の出力は、行の右端が不揃いになる。これを解消するのに、 putwrd()を修正する。この中のspread()は、語の間の空白を調整し一行の中に、語を 割り付ける。

RATFORでは

# putwrd.r4 -- put a word in outbuf; include margin justification
      subroutine putwrd(wrdbuf)
      character wrdbuf(INSIZE)
      integer length,width
      integer last,llval,nextra,w
      include cout.fi
      include cparam.fi

      w = width(wrdbuf)
      last = length(wrdbuf) + outp + 1  ! new end of outbuf
      llval = rmval - tival
      if ((outp > 0) & (outw+w > llval | last >= MAXOUT)) { # too big
          last = last - outp            # remember end of wrdbuf
          nextra = llval - outw + 1
          call spread(outbuf,outp,nextra,outwds)
          if ((nextra > 0) & (outwds > 1))
               outp = outp + nextra

          call brk                      # flush previous line
          }
      call scopy(wrdbuf,1,outbuf,outp+1)
      outp = last
      outbuf(outp) = BLANK              # blank between words
      outw = outw + w + 1               # 1 for blank
      outwds = outwds + 1
      return
      end

WATCOM fortran 77では、

c putwrd.f -- put a word in outbuf; include margin justification
      subroutine putwrd(wrdbuf)
      integer*1 wrdbuf(82)              ! INSIZE(82)
      integer length,width
      integer last,llval,nextra,w
      include cout.fi
      include cparam.fi

      w = width(wrdbuf)
      last = length(wrdbuf) + outp + 1  ! new end of outbuf
      llval = rmval - tival
      if ((outp .gt. 0) .and.
     1    ((outw+w .gt. llval) .or. (last .ge. 74))) then ! MAXOUT(74)  too big
          last = last - outp            ! remember end of wrdbuf
          nextra = llval - outw + 1
          call spread(outbuf,outp,nextra,outwds)
          if ((nextra .gt.0) .and. (outwds .gt. 1)) then
               outp = outp + nextra
          end if
          call brk                      ! flush previous line
      end if
      call scopy(wrdbuf,1,outbuf,outp+1)
      outp = last
      outbuf(outp) = 32                 ! BLANK(32)       blank between words
      outw = outw + w + 1               ! 1 for blank
      outwds = outwds + 1
      return
      end

spread()は、以下の通り

RATFORでは

# spread.r4 -- spread words to justify right margin
      subroutine spread(buf,outp,nextra,outwds)
      character buf(MAXOUT)
      integer outp,nextra,outwds
      integer min
      integer dir,i,j,nb,ne,nholes
      data dir/0/

      if ((nextra <= 0) | (outwds <= 1))
          return
      dir = 1 - dir                     # reverce previouse direction
      ne = nextra
      nholes = outwds - 1
      i = outp - 1
      j = min(MAXLINE - 2, i + ne)      # leave room for NEWLINE, EOS
      while (i < j) {
          buf(j) = buf(i)
          if (buf(i) == BLANK) {
              if (dir == 0)
                  nb = (ne - 1) / nholes + 1
              else
                  nb = ne / nholes
              ne = ne - nb
              nholes = nholes - 1
              for ( ; nb > 0; nb = nb - 1) {
                  j = j - 1
                  buf(j) = BLANK
                  }
              }
          i = i - 1
          j = j - 1
          }
      return
      end

WATCOM fortran 77では、

c spread.for -- spread words to justify right margin
      subroutine spread(buf,outp,nextra,outwds)
      integer*1 buf(74)                 ! MAXOUT(74)
      integer outp,nextra,outwds
      integer min
      integer dir,i,j,nb,ne,nholes
      data dir/0/

      if ((nextra .le. 0) .or. (outwds .le. 1)) then
         return
      end if
      dir = 1 - dir                     ! reverce previouse direction
      ne = nextra
      nholes = outwds - 1
      i = outp - 1
      j = min(74-2, i+ne)               ! MAXOUT(74) -2 for leave room for NEWLINE, EOS

      while (i .lt. j) do
          buf(j) = buf(i)
          if (buf(i) .eq. 32) then      ! BLANK(32)
              if (dir .eq. 0) then
                  nb = (ne - 1) / nholes + 1
              else
                  nb = ne / nholes
              end if
              ne = ne - nb
              nholes = nholes - 1
              while (nb .gt. 0) do
                  j = j - 1
                  buf(j) = 32           ! BLANK(32)
                  nb = nb - 1
              end while
          end if
          i = i - 1
          j = j - 1
      end while
      return
      end

中央そろえは、center()で行う。実際は、一時字下げの値を調節する。

RATFORでは

# center.r4 -- center a line by setting tival
      subroutine center(buf)
      character buf(ARB)
      integer width,max
      include cparam.fi

      tival = max((rmval + tival - width(buf)) / 2, 0)
      return
      end

WATCOM fortran 77では、

c center.f -- center a line by setting tival
      subroutine center(buf)
      integer*1 buf(9999)               ! ARB(9999)
      integer width,max
      include cparam.fi

      tival = max((rmval + tival - width(buf)) / 2, 0)
      return
      end

下線は、書き出し文字とBACKSPACE、UNDERLINEを組み合わせ作り出す。 実際は、underl()で書き出し文字列を作り出す。

RATFORでは

# underl.r4 -- underline a line
      subroutine underl(buf,tbuf,size)
      character buf(size),tbuf(size)
      integer size
      integer type
      integer i,j,t

      j = 1
      for (i = 1; buf(i) != NEWLINE & j < size- 1; i = i + 1) {
          tbuf(j) = buf(i)
          j = j + 1
          if (buf(i) != BLANK & buf(i) != TAB & buf(i) != BACKSPACE) {
              tbuf(j) = BACSPACE
              tbuf(j+1) = UNDERLINE
              j = j + 2
              }
          }
      tbuf(j) = NEWLINE
      tbuf(j+1) = EOS
      call scopy(tbuf, 1, buf, 1)          # copy it back to buf
      return
      end

WATCOM fortran 77では、

c underl.for -- underline a line
      subroutine underl(buf,tbuf,size)
      integer*1 buf(size),tbuf(size)
      integer size
      integer i,j,t

      j = 1
      i = 1
      while ((buf(i) .ne. 10) .and. (j .lt. size-1)) do ! NEWLINE(10)
          tbuf(j) = buf(i)
          j = j + 1
          if ((buf(i) .ne. 32)          ! BLANK(32)
     1        .and. (buf(i) .ne. 9)     ! TAB(9)
     2        .and. (buf(i) .ne. 8)) then ! BACKSPACE(8)
              tbuf(j)   = 8             ! BACKSPACE(8)
              tbuf(j+1) = 95            ! UNDERLINE(95)
              j = j + 2
          end if
          i = i + 1
      end while
      tbuf(j)   = 10                    ! NEWLINE(10)
      tbuf(j+1) = -2                    ! EOS(-2)
      call scopy(tbuf,1,buf,1)          ! copy it back to buf
      return
      end

ここまで出てきた新機能を追加するには、text()を修正する必要がある。 text()の最終版は、以下の通り。

RATFORでは

# text.r4 -- process text lines (final version)
      subroutine text(inbuf)
      character inbuf(INSIZE), wrdbuf(INSIZE)
      integer getword
      integer i
      include cparam.ri

      if (inbuf(1) == BLANK | inbuf(1) == NEWLINE)
         call leadbl(inpuf)             # move left, set tival
      if (ulval > 0) {                  # underlining
          call underl(inbuf,wrdbuf,INSIZE)
          ulval = ulval - 1
          }
      if (ceval > 0) {                  # centering
          call center(inbuf)
          call put(inbuf)
          ceval = ceval - 1
          }
      else if (inbuf(1) == NEWLINE)     # all blank line
         call put(inbuf)
      else if (fill == NO)              # unfiled text
         call put(inbuf)
      else                              # filled text
         for (i = 1;getwrd(inbuf,i,wrdbuf)>0; )
             call putwrd(wrdbuf)
      return
      end

WATCOM fortran 77では、

c text.f -- process text lines (final version)
      subroutine text(inbuf)
      integer*1 inbuf(82), wrdbuf(82)   ! INSIZE(82) INSIZE(82)
      integer getwrd
      integer i
      include cparam.fi

      if (inbuf(1) .eq. 32 .or. inbuf(1) .eq. 10) then ! BLANK(32) NEWLINE(10)
         call leadbl(inpuf)             ! move left, set tival
      end if
      if (ulval .gt. 0) then            ! underlining
          call underl(inbuf,wrdbuf,INSIZE)
          ulval = ulval - 1
      end if
      if (ceval .gt. 0) then            ! centering
          call center(inbuf)
          call put(inbuf)
          ceval = ceval - 1
      else if (inbuf(1) .eq. 10) then   ! all blank line
         call put(inbuf)
      else if (fill .eq. 0) then        ! unfiled text NO(0)
         call put(inbuf)
      else                              ! filled text
         i = 1
         while (getwrd(inbuf,i,wrdbuf) .gt. 0 ) do
             call putwrd(wrdbuf)
         end while
      end if
      return
      end