      subroutine parser(fhandl,chandl,commnd,params,stat,nline)

C *************************************************
C
C This subroutine reads and parses commands and
C associated parameters from an already-opened file
C with file handle fhandle. Upon each call, a command
C will be read into character variable commnd, and
C a set of parameters (up to a maximum number of stat
C on entry) will be read into the array params. The 
C command/parameters must be of the following syntax:
C
C #<command> { [<param 1>] [<param2>] ... }
C
C The spaces around the braces {} *are* significant.
C This syntax may be split over any number of lines at
C the spaces; more than one space is the same as a single
C space. At any point in the file, if a % character
C is found, the rest of the current line is discarded,
C and a new line is read - this is for comments. Likewise,
C if a ! character is found, the rest of the line is
C written to file handle chandl as an run-time visual
C comment. Once the command and all of the parameters
C are read, parser exits with a positive value in stat
C equal to the number of parameters read. If the end
C of file (EOF) is encountered, a value of 0 is returned
C in stat. If an error occurs, a negative value is 
C returned in stat, the significance of which is as follows:
C
C   -1  --- The command does not begin with a #
C   -2  --- The command is too long to fit in commnd
C   -3  --- The command is not followed by a {
C   -4  --- A spurious { appears in the parameter list
C   -5  --- There are too many (>stat on entry) parameters in the list
C   -6  --- A parameter is too long to fit in params
C   -7  --- EOF was reached whilst reading a parameter list
C   -8  --- There was a FORTRAN error reading from the input file
C
C Started:12/9/95
C Version:0.00
C
C *************************************************

C Turn off implicit typing...

      implicit none

C Declare the passed variables...

      integer fhandl           ! The file handle of the input file
      integer chandl           ! The file handle of the output comment file
      character*(*) commnd     ! The returned command string
      character*(*) params(*)  ! The returned parameters array
      integer stat             ! On entry: the maximum number of parameters allowed
                               ! On exit: the error status value (see above)
      integer nline             ! The number of the line being parsed

C Declare the in-program variables...

      character*(120) cline    ! A character string to hold the current line awaiting parsing
      character*(120) cword    ! A character string holding the word being parsed
      integer sindex           ! The index into cline of the first space
      integer pindex           ! The index into cline of the first % (Percent) char
      integer bindex           ! The index into cline of the first ! (Bang) char
      integer parcnt           ! A counter used whilst reading the parameters

C Set up cline for the first time the subroutine is called.

      data cline /' '/

C Make sure that cline is not corrupted between calls

      save cline

C OK, if cline containts only spaces, read another line from the input file.
C Note that even though we compare cline with ' ', ' ' is expanded out to
C LEN(cline) spaces, and so will properly see if cline is only spaces.
C Also, do any relevent space stripping with sstrip

 100  if(cline.eq.' ') then
        read(fhandl,'(A)',end=9000,err=9200) cline
        call sstrip(cline)
        nline=nline+1
        goto 100
      endif

C Now, see if there are any % chars in the current line - if so, dump the line
C from the % onwards, and loop back to 100 to see if we need a new line...

      pindex=index(cline,'%')

      if(pindex.ne.0) then
        if(pindex.eq.1) then
          cline=' '
          goto 100
        else
          cline=cline(:pindex-1)
        endif
      endif

C Now see if there are any ! chars in the current line - if so, write the line
C from the ! onwards to chandl, chop it off cline, and loop back to 100 to
C see if we need a new line

      bindex=index(cline,'!')

      if(bindex.ne.0) then
        write(chandl,'(A)') cline(bindex:)
        if(bindex.eq.1) then
          cline=' '
          goto 100
        else
          cline=cline(:bindex-1)
        endif
      endif

C OK, now we have done the preprocessing, we can get on with reading in the command.
C First find the index of the first space in cline. If this index is 0, cline
C can contain no spaces and must be all characters. Once we have got the word,
C modifiy cline accordingly, and set up sindex to be the length of the command.
C The modified cline must be sstrip'ed again, because modifying it may reveal leading
C spaces...

      sindex=index(cline,' ')

      if(sindex.eq.0) then
        cword=cline
        cline=' '
        sindex=len(cword)
      else
        cword=cline(1:sindex-1)
        cline=cline(sindex:)
        call sstrip(cline)
        sindex=sindex-1
      endif

C Convert cword to uppercase

      call dtoupp(cword)

C Check that the command begins with a # - if it does not, set up stat to return
C an error.

      if(cword(1:1).eq.'#') then
        cword=cword(2:)
      else
        stat=-1
        return
      endif

C OK, check that the length of cword does not exceed the size of commnd - if it
C does, return an error value in stat, otherwise, put cword into commnd

      if(sindex.gt.len(commnd)) then
        stat=-2
        return
      else
        commnd=cword
      endif

C Great, now we can read the parameters in. We read all of the parameters first using an
C if_then loop. First we reset cword, and then loop around until cword is '}'.
C Also, set parcnt, the parameter counter, to 0

      parcnt=0

      cword=' '

 200  if(cword.ne.'}') then

C Do the usual reading loop...

 300    if(cline.eq.' ') then
          read(fhandl,'(A)',end=9100,err=9200) cline
          call sstrip(cline)
          nline=nline+1
          goto 300
        endif

        pindex=index(cline,'%')


        if(pindex.ne.0) then
          if(pindex.eq.1) then
            cline=' '
            goto 300
          else
            cline=cline(:pindex-1)
          endif
        endif

        bindex=index(cline,'!')

        if(bindex.ne.0) then
          write(chandl,'(A)') cline(bindex:)
          if(bindex.eq.1) then
            cline=' '
            goto 300
          else
            cline=cline(:bindex-1)
          endif
        endif

        sindex=index(cline,' ')

        if(sindex.eq.0) then
          cword=cline
          cline=' '
          sindex=len(cword)
        else
          cword=cline(1:sindex-1)
          cline=cline(sindex:)
          call sstrip(cline)
          sindex=sindex-1
        endif

C We do a little fiddle here - if there is a colon in the word,
C we only dtoupp it up to the colon...

        if(index(cword,':').gt.1) then
          call dtoupp(cword(1:index(cword,':')-1))
        else
          call dtoupp(cword)
        endif

C OK, now we have the appropiate word and have modified cline, we can look at
C the parameter found. If this is the first parameter to be read (checked using
C parcnt), then it *must* be a {. Likewise, if this is *not* the first parameter
C read, then it nust *not* be a {. If the parameter is a }, we ignore it - it
C will be caught at the top of the loop

        if(parcnt.eq.0) then

C Here we check that the first char is a {

          if(cword.ne.'{') then
            stat=-3
            return
          else
            parcnt=1
          endif

C Here we check that { appears nowhere else

        elseif(cword.eq.'{') then
          stat=-4
          return

C Here we quickly do a length check on param, and store it. Also, we check if we are
C about to read too many parameters (the max number of parameters readable is in stat)

        elseif(cword.ne.'}') then

          if(parcnt.gt.stat) then
            stat=-5
            return
          elseif(sindex.gt.len(params(1))) then
            stat=-6
            return
          else
            params(parcnt)=cword
            parcnt=parcnt+1
          endif

C Here we do nothing if cword is }, since it will be picked up at the top of the loop

        else

C Finish the test...

        endif

C Loop back to get the next parameter

        goto 200

      endif    

C OK, now that we have read all of the parameters, we may set up stat with an OK
C return value, and leave the subroutine...

      stat=parcnt-1

      return


C This is the jump in point if an EOF is found when trying to read a command -
C this is 'non-fatal'

 9000 stat=0
      return

C This is the jump-in point if an EOF is found in the middle of reading parameters -
C this is 'fatal'

 9100 stat=-7
      return

C This is the jump-in point if an error is encountered in reading from the file

 9200 stat=-8
      return

C Finish...
 
      end
