Fortran Wiki
ufpp

The uufp(1) program is a pre-processor that can be used to conditionally output lines from input files to generate a Fortran source file. The source is in the public domain. It is not cpp(1)-compatible.

  • it is written in Fortran
  • directives are compatible with Fortran 77 (case insensitive, expressions use the syntax of Fortran 77 INTEGER or LOGICAL expressions)
  • user documentation is generated by the program -help option (see the HELP procedure in the code).
  • if you make enhancements please feel free to incorporate them into this source
  • Requires the additional module M_kracken
  • An extended personally maintained alternate version is also available as part of a collection of routines for building and maintaining Fortran code with a CLI (Command Line interface).
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!  @(#) FORTRAN preprocessor
!  Originally based on public-domain FPP preprocessor from Lahey Fortran Code Repository : http://www.lahey.com/code.htm
!  Use at your own risk.
!  John S. Urban ; last updated 20130611
!===================================================================================================================================
!     Requires:
!         M_kracken  Fortran module for parsing command line arguments.
!                    See "http://www.urbanjost.altervista.org/LIBRARY/libGPF/arguments/krackenhelp.html".
!===================================================================================================================================
   module M_fpp                                                      !@(#) module used by UFPP(1) program

   integer,parameter           ::  num=128                           ! num         - number of named values allowed
   integer,parameter           ::  line_length=1024                  ! line_length - allowed length of input lines
   integer,parameter           ::  var_len=31                        ! var_len     - allowed length of variable names
   integer,parameter           ::  nestl_max=20                      ! nestl_max   - maximum nesting level of conditionals

   logical,save                :: condop(0:nestl_max)                ! condop      - flag to keep track of previous write flags
   data condop(0:nestl_max) /.true.,nestl_max*.false./

   integer                     :: numdef=0                           ! numdef      - number of defined variables in dictionary

   logical                     :: write=.true.                       ! write       - indicates whether lines should be processed
   integer                     :: nestl=0                            ! nestl       - count of if/elseif/else/endif nesting level

   character(len=line_length)  :: source                             ! source      - original source file line
   character(len=line_length)  :: message                            ! message     - message to build for stopping program

   character(len=var_len)      :: defval(num)                        ! defval      - variable values  in variable dictionary
   character(len=var_len)      :: defvar(num)                        ! defvar      - variables in variable dictionary

   logical                     :: dc                                 ! dc          - flag to determine write flag

   integer                     :: iin(50)=0
   integer                     :: iline_number(50)=0
   integer                     :: iocount=0
   integer                     :: iototallines=0
   character(len=line_length)  :: files(50)
   integer                     :: iout                               ! output unit

   integer                     :: inc_count=0
   character(len=line_length)  :: inc_files(50)

   contains
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine cond                                         !@(#) process conditional directive assumed to be in SOURCE "$verb..."
   implicit none

   character(len=255),external :: upperstr                 ! function to convert a string to uppercase
   character(len=line_length)  :: line                     ! directive line with leading prefix removed
   logical,save                :: eb=.false.               !
   integer,save                :: noelse=0                 !
   integer                     :: istart
   integer                     :: itrim
!-----------------------------------------------------------------------------------------------------------------------------------
   line=source(2:)                                         ! remove leading prefix from directive line
   line=upperstr(line)                                     ! convert line to uppercase
   call nospace(line)                                      ! remove spaces from directive
   if (index(line,'!').ne.0) then                          ! if directive contains an exclamation a comment is present
      line=line(:index(line,'!')-1)                        ! trim trailing comment from directive
   endif
   select case(line(:2))                                   ! process directive based on first two characters
   case('  ')                                              ! entire line is a comment
   case('DE')                                              ! input is a DEFINE directive
      if (write) call define(line)                         ! only process DEFINE if not skipping data lines
   case('UN')                                              ! input is an UNDEF directive
      if (write) call undef(line)                          ! only process UNDEF if not skipping data lines
   case('IF')                                              ! input is an IF directive
      call if(line,noelse,eb)                              !
   case('PR')                                              ! input is a PRINTENV directive
      call printenv(line)                                  !
   case('EL')                                              ! input is an ELSE/ELSEIF directive
      call else(line,noelse,eb)                            !
   case('EN')                                              ! input is an ENDIF directive
      call endif(noelse,eb)                                !
   case('IN')                                              ! input is an INCLUDE directive. Filenames can be case sensitive
      istart=index(upperstr(source),'INCLUDE')             ! find INCLUDE in original source
      if(istart.ne.0)then                                  ! trim $INCLUDE from line
         line=source(istart+7:)
         itrim=index(line,'!')                             ! trim trailing comment, if any
         if(itrim.ne.0)then
            line(itrim-1:)='  '
         endif
         call nospace(line)
         call include(line,50+iocount)                     !
      else
         write(message,'("*ufpp* FATAL - MISSPELLED INCLUDE:",a)')trim(source)
         call stop_ufpp()
      endif
   case('SH')                                              ! input is a DEBUG directive
      call debug()                                         !
   case default
      write(message,'(''*ufpp* FATAL - UNKNOWN COMPILER DIRECTIVE:'',a)') trim(SOURCE)
      call stop_ufpp()
   end select
   end subroutine cond
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine nospace(line)                                !@(#) remove all blanks from input string
   implicit none
   character(len=*)            ::  line                    ! remove spaces from this string and return it
   character(len=line_length)  ::  temp                    ! buffer to build output in
   integer                     ::  ipos                    ! position to place next output character at
   integer                     ::  i                       ! counter to increment from beginning to end of input string
!-----------------------------------------------------------------------------------------------------------------------------------
   ipos=0
   temp=' '
   do i=1,len(line)                                        ! increment from first to last character of the input line
      if (ichar(line(i:i)).eq.32) cycle                    ! if a blank is encountered skip it
      ipos=ipos+1                                          ! increment count of non-blank characters found
      if(ipos.gt.line_length)then                          ! of all of input cannot be stored in output stop
         write(message,'(''*ufpp* FATAL - INPUT LINE TOO LONG.'',a)') trim(line)
         call stop_ufpp()
      endif
      temp(ipos:ipos)=line(i:i)                            ! store non-blank character in output
   enddo
   line=temp                                               ! replace original string with output
   end subroutine nospace
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine define(line)                                  !@(#) process "DEFINE variablename[=expression]" directive
   implicit none
   character(len=line_length)  ::  line                     ! packed uppercase working copy of input line with leading $ removed

   character(len=line_length)  ::  temp                     ! scratch
   integer                     :: iequ                      ! location of "=" in the directive, if any
   integer                     :: j                         ! index thru variable dictionary to see if variable is already defined
!-----------------------------------------------------------------------------------------------------------------------------------
! CHECK COMMAND SYNTAX
   if(line(1:6).ne.'DEFINE')then                            ! check verb really is DEFINE and find rest of directive
      write(message,'(''*ufpp* FATAL - EXPECTED "DEFINE". FOUND:'',a)') trim(source)
      call stop_ufpp()
   else
      line=line(7:)                                         ! trim off directive verb DEFINE
   endif
   iequ=index(line,'=')                                     ! find "=" in "variable_name=expression" if any
   if (line(1:1).eq.' '.or.iequ.eq.len_trim(line)) then     ! no variable name in packed string or string after = is null
      write(message,'(''*ufpp* FATAL - INCOMPLETE STATEMENT:'',a)') trim(source)
      call stop_ufpp()
   endif
   if (iequ.gt.var_len+1) then                              ! variable name too long
      write(message,'(''*ufpp* FATAL - MISPELLING OR NAME LENGTH EXCEEDS '',i5,''CHARACTERS:'',a)') var_len, trim(source)
      call stop_ufpp()
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
! OBTAIN VARIABLE NAME
   numdef=numdef+1                                          ! increment number of defined variables
   if (iequ.eq.0) then                                      ! if no = then variable assumes value of 1
      defvar(numdef)=line                                   ! store variable name from line with no =value string
      line='1'                                              ! set string to default value
   else                                                     ! =value string trails name on directive
      defvar(numdef)=line(:iequ-1)                          ! store variable nanme from line with =value string
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
! CHECK VARIABLE NAME
   call name(defvar(numdef))                                ! check that variable name is composed of allowed characters

   if (numdef.ne.1) then                                    ! test for redefinition of defined name
      do j=1,numdef-1
         if (defvar(numdef).eq.defvar(j)) then
            write(message,'(''*ufpp* FATAL - REDEFINITION OF DEFINED NAME INVALID:'',a)') trim(source)
            numdef=numdef-1
            call stop_ufpp()
         endif
      enddo
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   temp=line(iequ+1:)                                       ! get expression
   call parens(temp)                                        !
   if (iequ.eq.0) then
      line=temp
   else
      line=line(:iequ)//temp
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   call math(line,iequ+1,len_trim(line))
   call doop(line,iequ+1,len_trim(line))
   call logic(line,iequ+1,len_trim(line))
   call getval(line,iequ+1,len_trim(line),defval(numdef))
!-----------------------------------------------------------------------------------------------------------------------------------
   end subroutine define
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
FUNCTION GetDateTimeStr() RESULT(s)                                  !@(#) Function to write date and time into returned screen
! -----------------------------------------------------------------
! PURPOSE - Return a string with the current date and time
   IMPLICIT NONE
   CHARACTER(LEN=*),PARAMETER         :: MONTH='JanFebMarAprMayJunJulAugSepOctNovDec'
   CHARACTER(LEN=*),PARAMETER         :: FMT = '(I2.2,A1,I2.2,I3,A3,I4)'
   CHARACTER(LEN=15)                  :: s
   INTEGER,DIMENSION(8)               :: v
!-------------------------------------------------------------------
   CALL DATE_AND_TIME(VALUES=v)
   WRITE(s,FMT) v(5), ':', v(6), v(3), MONTH(3*v(2)-2:3*v(2)), v(1)
END FUNCTION GetDateTimeStr
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine printenv(line)                                  !@(#) process "PRINTENV variablename" directive
   implicit none
   character(len=line_length)  ::  line                       ! packed uppercase working copy of input line with leading $ removed
   character(len=line_length)  ::  varvalue                   ! value of environmental variable
!-----------------------------------------------------------------------------------------------------------------------------------
! CHECK COMMAND SYNTAX
   if(line(1:8).ne.'PRINTENV')then                            ! check verb really is PRINTENV
      write(message,'(''*ufpp* FATAL - EXPECTED "PRINTENV". FOUND:'',a)') trim(source)
      call stop_ufpp()
   else
      line=line(9:)                                           ! trim off directive verb PRINTENV
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   select case(line)                                                          ! process directive based on variable name
   case('UFPP_DATE')
      write(iout,'("      UFPP_DATE=""",a,"""")')GetDateTimeStr()
   case('UFPP_FILE')
      write(iout,'("      UFPP_FILE=""",a,"""")')trim(files(iocount))     ! assumes filename does not have " characters
   case('UFPP_LINE')
      !write(iout,'("      UFPP_LINE=""",i11,"""")')iline_number(iocount)  ! assumes want this as a string and not a number
      write(iout,'("      UFPP_LINE=",i11)')iline_number(iocount)  ! assumes want this as a number
   case('')
      write(message,'(''*ufpp* FATAL - NO VARIABLE NAME ON "PRINTENV":'',a)') trim(SOURCE)
      call stop_ufpp()
   case default
      call get_environment_variable(line,varvalue)
      if(varvalue.eq.'')then
         write(message,'(''*ufpp* FATAL - NO VARIABLE VALUE FOUND FOR "PRINTENV":'',a)') trim(SOURCE)
         call stop_ufpp()
      endif
      write(iout,'(a)')trim(varvalue)
   end select
   end subroutine printenv
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine name(line)                                                 !@(#) test for legal variable name
   implicit none
   character(len=*)       :: line
   integer                :: i
!-----------------------------------------------------------------------------------------------------------------------------------
   if (line(1:1).lt.'A'.or.line(1:1).gt.'Z')then                         ! variable names start with a-z
      write(message,'(''*ufpp* FATAL - VARIABLE NAME DOES NOT START WITH ALPHAMERIC (OR GENERAL SYNTAX ERROR):'',a)') trim(source)
      call stop_ufpp()
   endif

   if(len_trim(line).gt.var_len)then
      write(message,'(''*ufpp* FATAL - VARIABLE NAME EXCEEDS '',i5,'' CHARACTERS:'',a)') var_len,trim(source)
      call stop_ufpp()
   endif

   do i=2,len_trim(line)                                                 ! name uses $  _ and letters (A-Z) digits (0-9)
      if(line(i:i).ne.'$'.and.line(i:i).ne.'_'.and.     &
      & (line(i:i).lt.'A'.or.line(i:i).gt.'Z').and.     &
      & (line(i:i).lt.'0'.or.line(i:i).gt.'9')) then
         write(message,'(''*ufpp* FATAL - VARIABLE NAME CONTAINS UNALLOWED CHARACTER (OR GENERAL SYNTAX ERROR):'',a)') trim(source)
         call stop_ufpp()
      endif
   enddo

   end subroutine name
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine getval(line,ipos1,ipos2,value)                    !@(#) get value from dictionary for given variable name or return input
   implicit none

   character(len=var_len),intent(out)      :: value                         ! returned variable name
   integer,intent(in)                      :: ipos1                         ! beginning position of variable name in LINE
   integer,intent(in)                      :: ipos2                         ! ending position of variable name in LINE
   character(len=line_length),intent(in)   :: line                          ! current(maybe partial) directive line

   character(len=line_length)              :: temp                          ! copy of substring being examined
   integer                                 :: i
   integer                                 :: ivalue
!-----------------------------------------------------------------------------------------------------------------------------------
   temp=line(ipos1:ipos2)                                                   ! place variable name/value substring into TEMP

   if (temp(1:1).eq.' ')then                                                ! did not find expected variable name or value
      write(message,'(''*ufpp* FATAL - INCOMPLETE STATEMENT.'',a)') trim(SOURCE)
      call stop_ufpp()
   endif

   if (temp(1:1).ge.'A'.and.temp(1:1).le.'Z') then                          ! appears to be a variable name (not number or logical)

     value=temp
     do i=1,numdef                                                          ! find defined parameter in dictionary
        if (defvar(i).eq.value)exit
     enddo
     if (i.gt.numdef)then                                                   ! unknown variable name
        write(message,'(''*ufpp* FATAL - UNDEFINED PARAMETER IN GETVAL(3f):'',a)') trim(source)
        call stop_ufpp()
     endif
     value=defval(i)                                                        ! (trusted) value for variable name found in dictionary
     return
   else                                                                     ! not a variable name, try as a value
     read(temp(1:11),'(i11)',err=3) ivalue                                  ! try string as a numeric integer value
     write(value,'(i11)') ivalue                                            ! write numeric value into VALUE
     return                                                                 ! successfully return numeric VALUE

3    continue                                                               ! failed to read numeric value
     value=temp                                                             ! test TEMP as a logical
     if (value.ne.'.FALSE.'.and.value.ne.'.TRUE.')then                      ! if here, value should be a logical
        write(message,'(''*ufpp* FATAL - SYNTAX ERROR.'',a)') trim(source)
        call stop_ufpp()
     endif
                                                                            ! value is ".TRUE." or ".FALSE."
   endif

   if(temp(1:1).ge.'A')then
      write(message,'(''*ufpp* FATAL - $DEFINE VALUE MUST BE AN INTEGER OR LOGICAL CONSTANT.'',a)') trim(source)
      call stop_ufpp()
   endif

   end subroutine getval
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine undef(line)                                  !@(#) process UNDEFINE directive
   character(len=line_length)  :: line                     ! directive with no spaces, leading prefix removed, and all uppercase
   integer                     :: ifound                   ! subscript for location of variable to delete
!-----------------------------------------------------------------------------------------------------------------------------------
! CHECK VERB
   if(line(1:8).ne.'UNDEFINE')then                         ! check that verb is UNDEFINE
      write(message,'(''*ufpp* FATAL - DIRECTIVE MUST START WITH "UNDEFINE":'',a)')trim(source)
      call stop_ufpp()
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
! REMOVE VARIABLE IF FOUND IN VARIABLE NAME DICTIONARY
   line=line(9:)                                           ! remove leading UNDEFINE so just uppercase trimmed variable name remains

   if (len_trim(line).eq.0) then                           ! if no variable name
      write(message,'(''*ufpp* FATAL - INCOMPLETE STATEMENT:'',a)')trim(source)
      call stop_ufpp()
   endif

   ifound=-1                                               ! initialize subscript for variable name to be searched for to bad value
   do i=1,numdef                                           ! find defined variable to be undefined by searching dictionary
      if (defvar(i).eq.line)then                           ! found the requested variable name
         ifound=i                                          ! record the subscript that the name was located at
         exit                                              ! found the variable so no longer any need to search remaining names
      endif
   enddo

   if (ifound.lt.1) then                                   ! variable name not found
      return                                               ! quietly ignore unknown name (or syntax error!)
   endif

   do j=ifound,numdef-1                                    ! remove variable name and value from list of variable names and values
     defvar(j)=defvar(j+1)                                 ! replace the value to be removed with the one above it and then repeat
     defval(j)=defval(j+1)
   enddo

   numdef=numdef-1                                         ! decrement number of defined variables

   end subroutine undef
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine if(line,noelse,eb)                           !@(#)  process IF and ELSEIF directives
   logical                    ::  eb
   character(len=var_len)     ::  value
   character(len=line_length) ::  line
!-----------------------------------------------------------------------------------------------------------------------------------
   line=line(3:)                                           ! reduce line to just the expression
   noelse=0
   write=.false.

   nestl=nestl+1                                           ! increment IF nest level
   if (nestl.gt.nestl_max) then
      write(message,'(''*ufpp* ABORT - "IF" BLOCK NESTING TOO DEEP, LIMITED TO '',i4,'' LEVELS:'',a)')nestl_max, trim(source)
      call stop_ufpp()
   endif

   FIND_DEFINED: do                                        ! find and reduce all DEFINED() functions to ".TRUE." or ".FALSE."
      if (index(line,'DEFINED(').ne.0) then                ! find a DEFINED() function
         call ifdef(line,index(line,'DEFINED('))           ! reduce DEFINED() function that was found
         call nospace(line)                                ! remove any spaces from rewritten expression
         cycle                                             ! look for another DEFINED() function
      endif
      exit                                                 ! no remaining DEFINED() functions so exit loop
   enddo FIND_DEFINED

   call parens(line)
   if (index(line,'.').eq.0) then                          ! if line should be a variable only
      if (line(1:1).ge.'A'.and.line(1:1).le.'Z') then      ! check that variable name starts with a valid character
         call name(line)                                   ! check that line contains only a legitimate variable name
         value=line(:var_len)                              ! set VALUE to variable name
         do i=1,numdef                                     ! find variable in variable dictionary
            if (defvar(i).eq.value) exit
         enddo
         if (i.gt.numdef) then                             ! if failed to find variable name
            write(message,'(''*ufpp* FATAL - UNDEFINED PARAMETER IN IF(3f):'',a)') trim(source)
            call stop_ufpp()
         endif
         read(defval(i),'(l4)',iostat=ios) dc              ! convert variable value to a logical
         if(ios.ne.0)then
            write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED.''),a') trim(source)
            call stop_ufpp()
         endif
      else                                                 ! this should have been a variable name
         write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED:'',a)') trim(source)
         call stop_ufpp()
      endif
   else                                                    ! a period is present in the expression so it needs evaluated
      call eval(line)                                      ! evaluate line
   endif
   if (.not.dc.or..not.condop(nestl-1).or.eb)then
      return                                               ! check to make sure previous IF was true
   endif
   condop(nestl)=.true.
   write=condop(nestl)
   end subroutine if
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine ifdef(line,ipos1)                            !@(#) process
   character(len=line_length)    :: line
   character(len=line_length)    :: newl

   character(len=var_len)        ::  ifvar
!----------------------------------------------------------------------------------------------------------------------------------
   newl=line(ipos1+7:)
   if (len_trim(newl).eq.1.or.index(newl,')').eq.0.or. index(newl,')').eq.2)then
      write(message,'(''*ufpp* FATAL - INCOMPLETE STATEMENT.''),a') trim(SOURCE)
      call stop_ufpp()
   endif
   if (index(newl,')').gt.33)then
      write(message,'(''*ufpp* FATAL - MISSPELLING OR NAME LENGTH EXCEEDS '',i5,'' CHARACTERS.'',a)') var_len,trim(source)
      call stop_ufpp()
   endif
   ifvar= newl(2:index(newl,')')-1)
   if (newl(2:2).lt.'A'.or.newl(2:2).gt.'Z')then
      write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED.'',a)') trim(source)
      call stop_ufpp()
   endif

   do i=3,index(newl,')')-1
      IF (NEWL(I:I).NE.'$'.AND.NEWL(I:I).NE.'_'.AND.(NEWL(I:I).LT.'A' &
       &  .OR.NEWL(I:I).GT.'Z').AND.(NEWL(I:I).LT.'0'                 &
       &  .or.newl(i:i).gt.'9')) then
         write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED.'',a)') trim(source)
         call stop_ufpp()
      endif
   enddo

   dc=.false.
   line(ipos1:ipos1+6+index(newl,')'))='.FALSE.'

   do i=1,numdef
     if (defvar(i).eq.ifvar) then
       dc=.true.
       line(ipos1:ipos1+6+index(newl,')'))='.TRUE.'
       return
     endif
   enddo

   end subroutine ifdef
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine else(line,noelse,eb)                         !@(#) process else and elseif
   logical eb
   character(len=line_length)  line                        ! line        -
!-----------------------------------------------------------------------------------------------------------------------------------
   if (noelse.eq.1.or.nestl.eq.0) then                     ! test for else instead of elseif
      WRITE(message,'(''*ufpp* FATAL - MISPLACED $ELSE OR $ELSEIF DIRECTIVE:'',A)') trim(SOURCE)
      call stop_ufpp()
   endif
   if (len_trim(line).eq.4) noelse=1
   if (.not.condop(nestl-1)) return                        ! if was true so ignore else
     eb=.false.
   if (condop(nestl)) then
       eb=.true.
       write=.false.
   elseif (len_trim(line).ne.4) then                      ! elseif detected
     nestl=nestl-1                                        ! decrease if level because it will be incremented in subroutine if
     line=line(5:)
     call if(line,noelse,eb)
   else                                                   ! else detected
     condop(nestl)=.true.
     write=.true.
   endif
   end subroutine else
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine endif(noelse,eb)                             !@(#) process ENDIF directive
   logical                       :: eb
!-----------------------------------------------------------------------------------------------------------------------------------
   nestl=nestl-1                                           ! decrease if level
   if(nestl.lt.0)then
      write(message,'(''*uffp* FATAL - MISPLACED $ENDIF DIRECTIVE:'',a)') trim(source)
      call stop_ufpp()
   endif

   noelse=0                                                ! reset else level
   eb=.not.condop(nestl+1)
   write=.not.eb
   condop(nestl+1)=.false.

   if (nestl.eq.0) then
         write=.true.
         eb=.false.
   endif

   end subroutine endif
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine parens(line)                                 !@(#) find subexpressions in parenthesis and process them
   character(len=line_length)  line                        ! line        -
!-----------------------------------------------------------------------------------------------------------------------------------
   TILLDONE: do
      if (index(line,')').ne.0) then          ! closing parens found
         do i=index(line,')'),1,-1            ! find inner most set of parens
            if (line(i:i).eq.'(') exit
         enddo
         if (i.eq.0) then
            write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED:'',a)') trim(source)
            call stop_ufpp()
         endif
         call math(line,i+1,index(line,')')-1)
         call doop(line,i+1,index(line,')')-1)
         call logic(line,i+1,index(line,')')-1)
         if (i.eq.1.and.index(line,')').eq.len_trim(line)) then      ! rewrite line after no more parens
            line=line(i+1:index(line,')')-1)
         elseif (i.eq.1) then                                             ! rewrite line after first set of parens
            line=line(2:index(line,')')-1)//line(index(line,')')+1:)
         elseif (index(line,')').eq.len_trim(line)) then                 ! rewrite line after last set of parens on line

            if (line(i+1:i+1).eq.'-'.and.index('*/+-',line(i-1:i-1)).ne.0) then
               do j=i-2,1,-1
                  if (index('*/+-',line(j:j)).ne.0) exit
               enddo
               if (j.eq.i-2) then
                  write(message,'(''*ufpp* 1**(-1) NOT IMPLEMENTED YET'')')
                  call stop_ufpp()
               endif

               select case (index('*/+-',line(i-1:i-1)))
               case(1,2)
                  if (j.eq.0) then
                     line='-'//line(:i-1)//line(i+2:index(line,')')-1)
                  else
                     line=line(:j)//'(-'//line(j+1:i-1)//line(i+2:index(line,')'))
                  endif
               case(3)
                  line=line(:i-2)//'-'//line(i+2:index(line,')')-1)
               case(4)
                  line=line(:i-2)//'+'//line(i+2:index(line,')')-1)
               case default
               end select
            else
               line=line(:i-1)//line(i+1:index(line,')')-1)
            endif
         elseif (line(i+1:i+1).eq.'-'.and.index('*/+-',line(i-1:i-1)).ne.0) then
            do j=i-2,1,-1
               if (index('*/+-',line(j:j)).ne.0) exit
            enddo
            if (j.eq.i-2) then
               write(message,'(''*ufpp* 1**(-1) NOT IMPLEMENTED YET'')')
               call stop_ufpp()
            endif

            select case (index('*/+-',line(i-1:i-1)))
            case(1,2)
               if (j.eq.0) then
                  line='-'//line(:i-1)//line(i+2:index(line,')')-1)//line(index(line,')')+1:)
               else
                  line=line(:j)//'(-'//line(j+1:i-1)//line(i+2:index(line,')'))//line(index(line,')')+1:)
               endif
            case(3)
               line=line(:i-2)//'-'//line(i+2:index(line,')')-1)//line(index(line,')')+1:)
            case(4)
               line=line(:i-2)//'+'//line(i+2:index(line,')')-1)//line(index(line,')')+1:)
            case default
            end select
         else
            line=line(:i-1)//line(i+1:index(line,')')-1)//line(index(line,')')+1:)
         endif
      call nospace(line)
      cycle TILLDONE
   elseif (index(line,'(').ne.0) then
      write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED:'',a)') trim(source)
      call stop_ufpp()
   endif
   exit
   enddo TILLDONE
   end subroutine parens
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine math(line,ipos1,ipos2)                             !@(#)
   character(len=line_length)      :: line
   character(len=line_length)      :: newl
!-----------------------------------------------------------------------------------------------------------------------------------
   newl=line(ipos1:ipos2)
   i=1

   do
      j=index(newl(i:),'.')
      if (j.ne.0.and.j.ne.1) then
         call domath(newl(i:j+i-2),j-1)
         i=i+j
      elseif (j.eq.1) then
         i=i+1
      else
         call domath(newl(i:),ipos2-i+1)
         exit
      endif
   enddo

   line(ipos1:ipos2)=newl
   call nospace(line)

   end subroutine math
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine domath(line,ipos2)                           !@(#) reduce integer expression containing  +-/* and ** operators
   character(len=*)                          :: line

   character(len=11)                         :: temp
   character(len=line_length)                :: newl
   character(len=2),save                     :: ops(3)= (/'**','*/','+-'/)
   integer                                   :: i
   integer                                   :: j
   integer                                   :: loc
   integer                                   :: minus1
!-----------------------------------------------------------------------------------------------------------------------------------
   if (ipos2.eq.0) return
   loc=0
   j=0
   minus1=1
   newl=line(:ipos2)
   OVERALL: do numop=1,3                         ! check **, then */, then +-
      TILLDONE: do                               ! keep doing reduction of current operators
        i=index(newl,ops(numop))                 ! find location in input string where operator string was found
        if (numop.ne.1) then                     ! if not the two-character operator ** check for either operator of current group
          i=index(newl,ops(numop)(1:1))          ! find  first operator of group, if present
          j=index(newl,ops(numop)(2:2))          ! find second operator of group, if present
          i=max(i,j)                             ! find right-most operator, if any
          if (i*j.ne.0) i=min(i,j)               ! if at least one operator is present find left-most
        endif
        IF (I.EQ.0) cycle OVERALL                ! did not find these operators

        LEN=1                                    ! operator length
        IF (NUMOP.EQ.1) LEN=2
        IF (I.EQ.len_trim(NEWL)) then            ! if operator is at end of string
           WRITE(message,'(''*uffp* FATAL - INCOMPLETE STATEMENT. OPERATOR (**,/,*,+,-) AT STRING END:'',a)') trim(SOURCE)
           call stop_ufpp()
        endif
        IF (I.EQ.1.AND.NUMOP.NE.3) then          ! if operator at beginning of string and not +-
           WRITE(message,'(''*ufpp* FATAL - SYNTAX ERROR. OPERATOR (**,*,/) NOT ALLOWED TO PREFIX EXPRESSION:'',a)') trim(SOURCE)
           call stop_ufpp()
        endif
        if (.not.(i.eq.1.and.numop.eq.3)) then   ! if processing +- operators and sign at beginning of string skip this
           if (index('*/+-',newl(i-1:i-1)).ne.0.or.index('*/+-',newl(i+len:i+len)).ne.0) then
              write(message,'(''*ufpp* FATAL - SYNTAX ERROR IN DOMATH(3f):'',a)') trim(source)
              call stop_ufpp()
           endif
        endif

        i1=0
        if (.not.(i.eq.1.and.numop.eq.3)) then
           do j=i-1,1,-1
             if (index('*/+-.',newl(j:j)).eq.0) cycle
             exit
           enddo
           if (.not.(j.eq.i-1.and.j.ne.1))then
              i1=get_integer_from_string(newl,j+1,i-1)
           endif
        endif
        do l=i+len_trim(ops(numop)),len_trim(newl)
          if (index('*/+-.',newl(l:l)).eq.0) cycle
          exit
        enddo

        i2=get_integer_from_string(newl,i+len,l-1)

        if (numop.eq.1) then
          i1=i1**i2*minus1
        else
           select case (index('*/+-',newl(i:i)))
           case(1)
              i1=i1*i2*minus1
           case(2)
	      if(i2.eq.0)then
                 write(message,'(''*ufpp* FATAL - DIVIDE BY ZERO:'',a)') trim(source)
                 call stop_ufpp()
	      endif
              i1=i1/i2*minus1
           case(3)
           if (i1.ne.0) then
             i1=i1*minus1+i2
           else
             i1=i1+i2*minus1
           endif
           case(4)
              if (i1.ne.0) then
                i1=i1*minus1-i2
              else
                i1=i1-i2*minus1
              endif
           case default
              write(message,'(''*ufpp* FATAL - INTERNAL PROGRAM ERROR:'',a)') trim(source)
              call stop_ufpp()
           end select
        endif

        if (i1.le.0) then
          if (j.eq.i-1.and.j.ne.1) then
            minus1=-1
            i1=abs(i1)
            loc=j+1
            newl(j+1:j+1)=' '
            l=l-1
            call nospace(newl)
          elseif (i.eq.1.and.numop.eq.3) then
            minus1=-1
            i1=abs(i1)
            loc=i
            newl(j:j)=' '
            l=l-1
            j=j-1
            call nospace(newl)
          else
            minus1=1
          endif
        else
          minus1=1
        endif
        write(temp,'(i11)') i1
        call nospace(temp)
        if (j.eq.0.and.l.gt.len_trim(newl)) then
          newl=temp(:len_trim(temp))
          cycle overall
        elseif (j.eq.0) then
          newl=temp(:len_trim(temp))//newl(l:)
        elseif (l.gt.len_trim(newl)) then
          newl=newl(:j)//temp(:len_trim(temp))
        else
          newl=newl(:j)//temp(:len_trim(temp))//newl(l:)
        endif
	if(i1.lt.0)then  ! if i1 is negative, could produce +-
	   call change_all(newl,'c@+-@-@')
	endif
      enddo TILLDONE
   enddo OVERALL

   if (minus1.eq.-1.and.(loc.eq.0.or.loc.eq.1)) then
      newl='-'//newl
   elseif (minus1.eq.-1.and.loc.ne.1) then
      newl=newl(:loc-1)//'-'//newl(loc:)
   endif

   line(:ipos2)=newl(:len_trim(newl))

   end subroutine domath
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine doop(line,ipos1,ipos2)                       !@(#) find VAL.OP.VAL strings and reduce to .TRUE. or .FALSE.

   character(len=4),save       ::  ops(6) = (/'.EQ.','.NE.','.GE.','.GT.','.LE.','.LT.'/)
   character(len=var_len)      :: val1
   character(len=var_len)      :: val2
   character(len=7)            :: temp

   character(len=line_length)  :: newl                           !
   character(len=line_length)  :: line                           !
!-----------------------------------------------------------------------------------------------------------------------------------
   newl=line(ipos1:ipos2)
   CHECK_EACH_OP_TYPE: do i=1,6
      FIND_MORE_OF: do
         dc=.false.
         if (index(newl,ops(i)).ne.0) then                       ! found current operator looking for
            do j=index(newl,ops(i))-1,1,-1
               if (newl(j:j).eq.'.') then
                  exit
               endif
            enddo
            call getval(newl,j+1,index(newl,ops(i))-1,val1)
            do k=index(newl,ops(i))+4,len_trim(newl)
               if (newl(k:k).eq.'.')then
                  exit
               endif
            enddo
            call getval(newl,index(newl,ops(i))+4,k-1,val2)
            select case(i)                                       ! determine truth
            case(1)                                              ! .eq.
               if (val1.eq.val2) dc=.true.
            case(2)                                              ! .ne.
               if (val1.ne.val2) dc=.true.
            case(3)                                              ! .ge.
               if (val1.ge.val2) dc=.true.
            case(4)                                              ! .gt.
               if (val1.gt.val2) dc=.true.
            case(5)                                              ! .le.
               if (val1.le.val2) dc=.true.
            case(6)                                              ! .lt.
               if (val1.lt.val2) dc=.true.
            case default
            end select
            temp='.FALSE.'
            if (dc) temp='.TRUE.'
            call rewrit(newl,temp(:len_trim(temp)),j,j,k,k)
            call nospace(newl)
            cycle
         endif
         exit
      enddo FIND_MORE_OF
   enddo CHECK_EACH_OP_TYPE
   if (ipos1.ne.1) then
      line=line(:ipos1-1)//newl(:len_trim(newl))//line(ipos2+1:)
   else
      line=newl(:len_trim(newl))//line(ipos2+1:)
   endif
   call nospace(line)
   end subroutine doop
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   logical function trufal(line,ipos1,ipos2)                 ! @(#) convert variable name or .TRUE./.FALSE. to a logical value
   implicit none
   character(len=line_length),intent(in) :: line             ! line containing string to interpret as a logical value
   integer,intent(in)                    :: ipos1            ! starting column of substring in LINE
   integer,intent(in)                    :: ipos2            ! ending column of substring in LINE

   character(len=var_len)                :: value            ! substring to extract from LINE
   integer                               :: i                ! loop counter
   integer                               :: ios              ! error code returned by an internal READ
   integer                               :: ifound           ! index in dictionary at which a variable name was found, or -1
!-----------------------------------------------------------------------------------------------------------------------------------
   trufal=.false.                                            ! initialize return value
   value=line(ipos1:ipos2)                                   ! extract substring from LINE to interpret
   ifound=-1                                                 ! flag if successfully converted string, or index variable naem found

   select case (value)                                       ! if string is not a logical string assume it is a variable name
   case ('.FALSE.','.F.')
      ifound=0                                               ! set flag to indicate a good value has been found
      trufal=.false.                                         ! set appropriate return value
   case ('.TRUE.','.T.')
      ifound=0                                               ! set flag to indicate a good value has been found
      trufal=.true.                                          ! set appropriate return value
   case default                                              ! assume this is a variable name, find name in dictionary
      do i=1,numdef
         if (defvar(i).eq.value) then                        ! found variable name in dictionary
            ifound=i                                         ! record index in diction where variable was found
            exit
         endif
      enddo

      if (ifound.eq.-1) then                                 ! if not a defined variable name stop program
         write(message,'(''*ufpp* FATAL - UNDEFINED PARAMETER.'',a)') trim(source)
         call stop_ufpp()
      endif

      read(defval(ifound),'(l4)',iostat=ios) trufal          ! try to read a logical from from the value for the variable name

      if(ios.ne.0)then                                       ! not successful in reading string as a logical value
            write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED.'',a)') trim(source)
            call stop_ufpp()
      endif

   end select

   if (ifound.lt.0) then                                     ! not a variable name or string '.TRUE.' or '.FALSE.'
      write(message,'(''*ufpp* FATAL - CONSTANT LOGICAL EXPRESSION REQUIRED:'',a)') trim(source)
      call stop_ufpp()
   endif

   end function trufal
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine logic(line,ipos1,ipos2)           !@(#) process .OP. operator strings
   character(len=*)             :: line
   integer,intent(in)           :: ipos1, ipos2

   logical                      :: one, two
   character(len=7)             :: temp
   character(len=line_length)   :: newl
   character(len=6),save        :: ops(5)= (/'.NOT. ','.AND. ','.OR.  ','.EQV. ','.NEQV.'/)
!-----------------------------------------------------------------------------------------------------------------------------------
   NEWL=LINE(IPOS1:IPOS2)
   LEN1=0
   LEN2=0
   ONE=.FALSE.
   LOOP: DO I=1,3
20      continue
        LEN=5
        IF (I.EQ.3) LEN=4
        IF (INDEX(NEWL,OPS(I)(:len_trim(OPS(I)))).EQ.0) cycle
        I1=INDEX(NEWL,OPS(I)(:len_trim(OPS(I))))-1
        J=I1+1
        LEN1=0
        IF (I.NE.1) then
           OUTER: DO J=I1,1,-1
             INNER: DO K=1,5
                LEN1=5
                IF (K.EQ.3) LEN1=4
                IF (INDEX(NEWL(J:I1),OPS(K)(:len_trim(OPS(K)))).NE.0) exit OUTER
             enddo INNER
           enddo OUTER
           IF (J.EQ.0) LEN1=1
           ONE=TRUFAL(NEWL,J+LEN1,I1)
        endif
!-------------------------------------------------------------------------
        OUT: DO L=I1+LEN,len_trim(NEWL)
          IN: DO K=1,5
             LEN2=5
             IF (K.EQ.3) LEN2=4
             IF (INDEX(NEWL(I1+LEN:L),OPS(K)(:len_trim(OPS(K)))).NE.0) exit OUT
          enddo IN
        enddo OUT
!-------------------------------------------------------------------------
        IF (L.GT.len_trim(NEWL)) LEN2=0
        TWO=TRUFAL(NEWL,I1+LEN+1,L-LEN2)
        !-------------------------------------
        select case(i)
        !-------------------------------------
        case(1)
           dc=.not.two
        !-------------------------------------
        case(2)
           dc=one.and.two
        !-------------------------------------
        case(3)
           dc=one.or.two
        !-------------------------------------
        case default
           write(message,*)'*ufpp* internal error'
           call stop_ufpp()
        end select
        !-------------------------------------
        temp='.FALSE.'
        if (dc) temp='.TRUE.'
        call rewrit(newl,temp(:len_trim(temp)),j,j+len1-1,l,l-len2+1)
        goto 20
   enddo LOOP
   TILLDONE: do
      ieqv=index(newl,'.EQV.')
      ineqv=index(newl,'.NEQV')
      if (ieqv*ineqv.eq.0.and.ieqv.ne.ineqv) then
        iop=max(ieqv,ineqv)
      elseif (ieqv.ne.0) then
        iop=min(ieqv,ineqv)
      elseif (ipos1.eq.1) then
        line=newl(:len_trim(newl))//line(ipos2+1:)
        return
      else
        line=line(:ipos1-1)//newl(:len_trim(newl))//line(ipos2+1:)
        return
      endif
      len=5
      if (index(newl,'.EQV.').ne.iop) len=6
      do j=iop-1,1,-1
         if (newl(j:j+1).eq.'V.') exit
      enddo
      if (j.eq.0) len1=1
      one=trufal(newl,j+len1,iop-1)
      do l=iop+len,len_trim(newl)
         if (newl(l:l+1).eq.'.E'.or.newl(l:l+1).eq.'.N') exit
      enddo
      if (l.gt.len_trim(newl)) len2=0
      two=trufal(newl,iop+len,l+len2)
      dc=one.eqv.two
      if (len.ne.5) dc=one.neqv.two
      temp='.FALSE.'
      if (dc) temp='.TRUE.'
      call rewrit(newl,temp(:len_trim(temp)),j,j+len1-1,l,l-len2+1)
   enddo TILLDONE
   end subroutine logic
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine eval(line)                                   !@(#) evaluate math expression to .TRUE. or .FALSE.
   character(len=line_length)     :: line
   character(len=7)               :: value
!-----------------------------------------------------------------------------------------------------------------------------------
   call parens(line)
   call math(line,1,len_trim(line))
   call doop(line,1,len_trim(line))
   call logic(line,1,len_trim(line)) 
   value=line(1:7)

   if (value.ne.'.TRUE.'.and.value.ne.'.FALSE.') then
      write(message,'(''*ufpp* FATAL - value neither true or false:'',a,'' when evaluating  '',a)') trim(value), trim(source)
      call stop_ufpp()
   endif

   read(value,'(l4)') dc

   end subroutine eval
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   function get_integer_from_string(line,ipos1,ipos2) !@(#) read integer value from line(ipos1:ipos2)
                                                      ! assume string is a variable name or an integer value
   implicit none
   character(len=*),intent(in)  :: line                             ! string containing substring to read an integer value from
   integer,intent(in)           :: ipos1                            ! lower bound of substring in input line to convert
   integer,intent(in)           :: ipos2                            ! upper bound of substring in input line to convert
   character(len=var_len)       :: value                            ! the substring
   integer                      :: i                                ! index of variable dictionary where variable name is stored
   integer                      :: ios                              ! I/O error value to check to see if internal reads succeeded
   integer                      :: get_integer_from_string                             ! integer value to return if string is converted successfully
!-----------------------------------------------------------------------------------------------------------------------------------
   if (line(ipos1:ipos1).ge.'A'.and.line(ipos1:ipos1).le.'Z') then  ! not a number, now assumed to  be a variable name
      value= line(ipos1:ipos2)                                      ! extract substring that is assumed to be a variable name
      i=-1                                                          ! this will be index where variable name is found in dictionary
      do i=1,numdef                                                 ! scan variable dictionary for the variable name
        if (defvar(i).eq.value) exit
      enddo
      if (i.gt.numdef.or.i.lt.0)then                                ! if variable name not found in dictionary, stop
        write(message,'(''*ufpp* FATAL - UNDEFINED PARAMETER:'',a)') trim(source)
        call stop_ufpp()
      endif
      read(defval(i),'(i11)',iostat=ios) get_integer_from_string                       ! read integer value from the value associated with name
      if(ios.ne.0)then                                              ! failed reading integer from value, stop
        write(message,'(''*ufpp* FATAL - MUST BE INTEGER:'',a)') trim(source)
        call stop_ufpp()
      endif
   else                                                             ! input is not a variable name, assume it represents an integer
      read(line(ipos1:ipos2),'(i11)',iostat=ios) get_integer_from_string               ! try to read integer value from input string
      if(ios.ne.0)then                                              ! failed to convert the string to an integer, so stop
        write(message,'(''*ufpp* FATAL - MUST BE INTEGER:'',a)') trim(source)
        call stop_ufpp()
      endif
   endif                                                            ! return integer value
   end function get_integer_from_string
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine rewrit(line,temp,j,j1,l,l1)                           !@(#)

   character temp*(*)
   character(len=line_length)  line
!-----------------------------------------------------------------------------------------------------------------------------------

   if (j.eq.0.and.l.gt.len_trim(line)) then      ! done
      line=temp
   elseif (j.eq.0) then                          ! first item
      line=temp//line(l1:)
   elseif (l.gt.len_trim(line)) then             ! last item
      if (j1.ne.0) then
         line=line(:j1)//temp
      else
         line=temp
      endif
   else                                          ! middle item
        line=line(:j1)//temp//line(l1:)
   endif
   end subroutine rewrit
!===================================================================================================================================
   subroutine debug()                                                  !@(#) process $SHOW command or state output when errors occur
!-----------------------------------------------------------------------------------------------------------------------------------
   write(*,'(a)')'!==============================================================================='
   write(*,'("! *ufpp* CURRENT STATE ...")')
   write(*,'("! *ufpp*    TOTAL LINES READ=",i11)')iototallines        ! write number of lines read
   write(*,'("! *ufpp*    CONDITIONAL_NESTING_LEVEL=",i4)')nestl       ! write nesting level

   write(*,'(a)')'! *ufpp* VARIABLES:'
   do i=1,numdef                                                       ! print variable dictionary
      write(*,'("! *ufpp*    ! ",a," ! ",a)'),defvar(i),defval(i)      ! write variable and corresponding value
   enddo

   write(*,'(a)')'! *ufpp* OPEN FILES:'
   write(*,'(a)')'! *ufpp*    ! xxxx ! UNIT ! LINE NUMBER ! FILENAME'
   do i=1,iocount                                                      ! print file dictionary
      write(*,'("! *ufpp*    ! ",i4," ! ",i4," ! ",i11," ! ",a)')i,iin(i),iline_number(i),trim(files(i)) ! write table of open files
   enddo

   write(*,'(a)')'!==============================================================================='
   end subroutine debug
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine include(line,iunit)                         ! add file to input file list
   implicit none
   character(len=line_length),intent(in) :: line
   integer                               :: iunit
   integer                               :: ios
!-----------------------------------------------------------------------------------------------------------------------------------
   if(iunit.eq.5.or.line.eq.'@')then      ! assume this is stdin
      iocount=iocount+1
      iin(iocount)=5
      files(iocount)=line
      return
   endif

   call findit(line)

   open(unit=iunit,file=trim(line),iostat=ios,status='old',action='read')
   if(ios.ne.0)then
      write(message,'("*uffp* FATAL - FAILED OPEN OF INPUT FILE(",i4,"):",a)') iunit, trim(line)
      call debug()
      call stop_ufpp()
   else
      iocount=iocount+1
      if(iocount.gt.size(iin))then
         write(message,'(''*uffp* FATAL - INPUT FILE NESTING TOO DEEP:'',a)') trim(source)
         call stop_ufpp()
      endif
      iin(iocount)=iunit
      files(iocount)=line
      iline_number(iocount)=0
   endif

   end subroutine include
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine findit(line)     !@(#) look for filename in search directories if name does not exist and return modified name
   implicit none
   character(len=line_length)            :: line
   character(len=line_length)            :: filename
   logical                               :: file_exist
   integer                               :: i
   integer                               :: iend_dir

   inquire(file=trim(line), exist=file_exist)

   if(file_exist)then
      return
   endif

   do i=1,inc_count
      iend_dir=len_trim(inc_files(i))
      if(inc_files(i)(iend_dir:iend_dir).ne.'/')then
         filename=inc_files(i)(:iend_dir)//'/'//trim(line)
      else
         filename=inc_files(i)(:iend_dir)//trim(line)
      endif
      inquire(file=trim(filename), exist=file_exist)
      if(file_exist)then
         line=filename
	 return
      endif
   enddo

   write(message,'(''*uffp* FATAL - MISSING INPUT FILE:'',a)') trim(source)
   call stop_ufpp()

   end subroutine findit
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine opens()                                               !@(#)  use expression on command line to  open input files
   use M_kracken, only: delim, sget                                 ! load command argument parsing module

   integer,parameter                     :: n=50                    ! maximum number of tokens to look for
   character(len=line_length)            :: array(n)                ! the array to fill with tokens
   character(len=1)                      :: dlim=' '                ! string of single characters to use as delimiters

   integer                               :: icount                  ! how many tokens are found
   integer                               :: ibegin(n)               ! starting column numbers for the tokens in INLINE
   integer                               :: iterm(n)                ! ending column numbers for the tokens in INLINE
   integer                               :: ilen                    ! is the position of last non-blank character in INLINE
!-----------------------------------------------------------------------------------------------------------------------------------
   call delim(sget('cmd_i'),array,n,icount,ibegin,iterm,ilen,dlim) ! break command argument cmd_i into single words
   ivalue=50  ! starting file unit to use
   do i=icount,1,-1
      source='$include '//trim(array(i))  ! for messages
      call include(array(i),ivalue)
      ivalue=ivalue+1
   enddo

!   If ARRAY(N) fills before reaching the end of the line the routine stops.
!   Check "if(iend(icount) .eq. ilen)" to see if you got to the end.

   end subroutine opens
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine includes()                                            !@(#)  use expression on command line to  get include directories
   use M_kracken, only: delim, sget                                 ! load command argument parsing module

   integer,parameter                     :: n=50                    ! maximum number of tokens to look for
   character(len=1)                      :: dlim=' '                ! string of single characters to use as delimiters
   integer                               :: ibegin(n)               ! starting column numbers for the tokens in INC_FILES
   integer                               :: iterm(n)                ! ending column numbers for the tokens in INC_FILES
   integer                               :: ilen                    ! is the position of last non-blank character in INC_FILES
!-----------------------------------------------------------------------------------------------------------------------------------
   ! inc_files is the array to fill with tokens
   ! inc_count is the number of tokens found
!-----------------------------------------------------------------------------------------------------------------------------------
   call delim(sget('cmd_I'),inc_files,n,inc_count,ibegin,iterm,ilen,dlim) ! break command argument cmd_I into single words
   end subroutine includes
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine defines()                                             !@(#)  use expressions on command line to define variables
   use M_kracken, only: delim, sget                                 ! load command argument parsing module
   integer,parameter                     :: n=128                   ! maximum number of tokens to look for
   character(len=line_length)            :: array(n)                ! the array to fill with tokens
   character(len=1)                      :: dlim=' '                ! string of single characters to use as delimiters

   integer                               :: icount                  ! how many tokens are found
   integer                               :: ibegin(n)               ! starting column numbers for the tokens in INLINE
   integer                               :: iterm(n)                ! ending column numbers for the tokens in INLINE
   integer                               :: ilen                    ! is the position of last non-blank character in INLINE
!-----------------------------------------------------------------------------------------------------------------------------------
   call delim(sget('cmd_oo'),array,n,icount,ibegin,iterm,ilen,dlim) ! break command argument CMD_OO into single words
   do i=1,icount
      source='$define '//trim(array(i))
      call cond()
   enddo

!   If ARRAY(N) fills before reaching the end of the line the routine stops.
!   Check "if(iend(icount) .eq. ilen)" to see if you got to the end.

   end subroutine defines
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   subroutine stop_ufpp                   !@(#) write MESSAGE to stderr
   USE ISO_FORTRAN_ENV, ONLY : ERROR_UNIT ! access computing environment ; Standard: Fortran 2003
   implicit none
   write(ERROR_UNIT,'(a)')trim(message)
   call debug()
   stop
   end subroutine stop_ufpp
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
      SUBROUTINE change_all(CDUM,CSTRNG) ! CHANGE A CHARACTER STRING
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
!@(#) CHANGE A CHARACTER STRING LIKE XEDIT CHANGE OR C COMMAND
!     CDUM CONTAINS LINE TO BE CHANGED
!     CSTRNG CONTAINS THE COMMAND CHANGING THE STRING(LESS THE COUNT PARAM)
!
!     THIS ROUTINE DOES NOT ALLOW FOR SEPARATORS ON THE CHANGE COMMAND
!     (...) OR .NOT.CONTAINING (---).
!
!     THE COMMAND MUST BE OF THE FORM:
!      C/STRING1/STRING2/    OR CW/STRING1/STRING2/  (CHANGE IN WINDOW)
!     WHERE / MAY BE ANY CHARACTER OTHER THAN W OR BLANK, WHICH IS NOT
!     INCLUDED IN STRING1 OR STRING2
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      CHARACTER*(*) CDUM,CSTRNG             ! USE STRING LENGTH
      PARAMETER (MAXSCR=255)                ! MAXIMUM SCRATCH LENGTH
      CHARACTER*(MAXSCR) STR1,STR2,DUM1     ! SCRATCH STRING BUFFERS
      ML=1                                  ! ML SETS THE LEFT  MARGIN
      MR=len(CDUM)                          ! MR SETS THE RIGHT MARGIN
      LMAX=MIN0(LEN(CDUM),MAXSCR)           ! MAX LENGTH OF NEW STRING
      LCDUM=JULEN(CDUM)                     ! GET NON-BLANK LENGTH OF LINE
!     CRACK THE DIRECTIVES LINE
      STR1(:)=' '                           ! INITIALIZE STRINGS
      STR2(:)=' '                           ! INITIALIZE STRINGS
      DUM1(:)=' '                           ! INITIALIZE STRINGS
      LDIR=JULEN(CSTRNG)                    ! FIND LAST CHARACTER IN DIRECTIVE
      ID=2
      IF(CSTRNG(2:2).EQ.'W')ID=3            ! CHECK FOR WINDOW OPTION
      ID1=ID+1                              ! DELIMITER CHARACTER + 1
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      IF(LDIR.LT.3+ID)then                  ! IF BAD DIRECTIVE
         message='*change* bad directive'
         call stop_ufpp()
      endif
      IF(CSTRNG(ID:ID).NE.CSTRNG(LDIR:LDIR))then
         message='*change* unmatched delimiters'
         call stop_ufpp()
      endif
      IDEL=INDEX(CSTRNG(ID1:LDIR-1),CSTRNG(ID:ID))    ! FIND MID DELIM
      IF(IDEL.EQ.0)then                               ! IF NO MID DELIM
         message='*change* missing middle delimiter'
         call stop_ufpp()
      endif
      IF(IDEL.GT.1)STR1=CSTRNG(ID1:IDEL+ID-1)         ! STRING TO BE CHANGED
      LS1=IDEL-1                                      ! STRING OF STRING TBC
      IF(IDEL+ID.LT.LDIR-1)STR2=CSTRNG(IDEL+ID1:LDIR-1) ! NEW STRING
      LS2=LDIR-IDEL-ID1                               ! LENGTH OF NEW STRING
      IF(LS2.GT.0)THEN
         IF(INDEX(STR2(:LS2),CSTRNG(ID:ID)).NE.0)then ! EXTRA DELIMITER
            message='*change* extra delimiter'
            call stop_ufpp()
	 endif
      ENDIF
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
!     DIRECTIVES HAVE BEEN CRACKED, NOW IMPLEMENT
      IF(ID.EQ.2)THEN                       ! NO WINDOW
         IL=1                               ! IL TO LEFT MARGIN
         IR=LMAX                            ! IR TO RIGHT MOST ALLOWED
      ELSE                                  ! IF WINDOW IS SET
         IL=ML                              ! USE LEFT MARGIN
         IR=MIN0(MR,LMAX)                   ! USE RIGHT MARGIN OR RIGHT MOST
      ENDIF                                 ! END OF WINDOW SETTINGS
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      IF(IL.EQ.1)THEN                       ! IF LEFT MARGIN IS 1
         DUM1(:)=' '                        ! BEGIN WITH A BLANK LINE
      ELSE                                  ! IF LEFT MARGIN NOT 1
         DUM1=CDUM(:IL-1)                   ! BEGIN WITH WHAT'S BELOW MARGIN
      ENDIF
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      IF(LS1.EQ.0)THEN                      ! c//str2/ means insert str2 at beginning of line (or left margin)
         ICHAR=LS2 + LCDUM
         IF(ICHAR.GT.LMAX)then
            message='*change* new line will be too long'
            call stop_ufpp()
	 endif
         IF(LS2.GT.0)THEN
            DUM1(IL:)=STR2(:LS2)//CDUM(IL:LCDUM)
         ELSE
            DUM1(IL:)=CDUM(IL:LCDUM)
         ENDIF
         CDUM(1:LMAX)=DUM1(:LMAX)
         IER=1                              ! Made one change. Actually, c/// should maybe return 0
         RETURN
      ENDIF
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      IER=0
      ICHAR=IL
      IC=IL
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      do
         IND=INDEX(CDUM(IC:),STR1(:LS1))+IC-1
         IF(IND.EQ.IC-1.OR.IND.GT.IR) exit
         IER=IER+1
         IF(IND.GT.IC)THEN
            LADD=IND-IC
            IF(ICHAR-1+LADD.GT.LMAX)then
               message='*change* new line will be too long'
               call stop_ufpp()
   	 endif
            DUM1(ICHAR:)=CDUM(IC:IND-1)
            ICHAR=ICHAR+LADD
         ENDIF
         IF(ICHAR-1+LS2.GT.LMAX)then
            message='*change* new line will be too long'
            call stop_ufpp()
         endif
         IF(LS2.NE.0)THEN
            DUM1(ICHAR:)=STR2(:LS2)
            ICHAR=ICHAR+LS2
         ENDIF
         IC=IND+LS1
      enddo
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      IF(IER.EQ.0)RETURN
      LADD=LCDUM-IC
      IF(ICHAR+LADD.GT.LMAX)then
         message='*change* new line will be too long'
         call stop_ufpp()
      endif
      if(ic.lt.len(cdum))then
         DUM1(ICHAR:)=CDUM(IC:max(ic,LCDUM))
      endif
      CDUM=DUM1(:LMAX)
      END SUBROUTINE change_all
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine help()
implicit none
!-----------------------------------------------------------------------------------------------------------------------------------
! This documentation is a combination of the original Lahey documentation of fpp(1) from
! "LAHEY FORTRAN REFERENCE MANUAL"; Revision C, 1992;
! examination of the code and documentation of the features subsequently added to the program.
!-----------------------------------------------------------------------------------------------------------------------------------
write(*,'(a)')'ufpp(1)                       User Commands                            ufpp(1)  '
write(*,'(a)')'                                                                                '
write(*,'(a)')'NAME                                                                            '
write(*,'(a)')'   ufpp - preprocess FORTRAN source files (Lahey Compiler Style)                '
write(*,'(a)')'                                                                                '
write(*,'(a)')'SYNOPSIS                                                                        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   ufpp [define_list]                                                           '
write(*,'(a)')'        [-i input_file(s)]                                                      '
write(*,'(a)')'        [-o output_file]                                                        '
write(*,'(a)')'        [-I include_directories]                                                '
write(*,'(a)')'        [-prefix character]                                                     '
write(*,'(a)')'        [-version]                                                              '
write(*,'(a)')'        [-help]                                                                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   define_list                                                                  '
write(*,'(a)')'       A list of variable names and optional expressions used to                '
write(*,'(a)')'       define variables before file processing commences, delimited by spaces.  '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -i input_files                                                               '
write(*,'(a)')'       The default input file is stdin. Filenames are space-delimited.          '                                        '
write(*,'(a)')'       In a list, @ represents stdin.                                           '                                        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -o output_file                                                               '
write(*,'(a)')'       The default output file is stdout.                                       '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -I include directories                                                       '
write(*,'(a)')'       The directories to find files in that appear on $INCLUDE directives.     '                                        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -prefix prefix_character                                                     '
write(*,'(a)')'       The default directive prefix character is "$". "#" may be specified      '
write(*,'(a)')'       as an alternate prefix.                                                  '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -help                                                                        '
write(*,'(a)')'       Display documentation and exit                                           '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -version                                                                     '
write(*,'(a)')'       Display version and exit                                                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'DESCRIPTION                                                                     '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   By default the  stand-alone  pre-processor  ufpp(1)  will  interpret lines   '
write(*,'(a)')'   with "$" in column one, and will output a file containing no "$" lines.      '
write(*,'(a)')'   Other input is conditionally written to the output file based on the         '
write(*,'(a)')'   directives encountered in the input.                                         '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Compiler  directives are specified by a "$" in column one, followed by a     '
write(*,'(a)')'   keyword. The syntax and parsing rules of the text following the "$"  are     '
write(*,'(a)')'   essentially   like   FORTRAN   source, eg., spaces (blanks) are ignored,     '
write(*,'(a)')'   upper and lower case are equivalent.   However,   expressions   do   not     '
write(*,'(a)')'   need  to  be enclosed in parentheses.                                        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   The  syntax  for  the  control  lines  "$DEFINE", "$UNDEFINE", "$INCLUDE",   '
write(*,'(a)')'   "$IF", "$ELSE",  "$ELSEIF",  and "$ENDIF", is as follows:                    '
write(*,'(a)')'                                                                                '
write(*,'(a)')'     $DEFINE   variable_name[=expression]                 [! comment ]          '
write(*,'(a)')'     $UNDEFINE variable_name                              [! comment ]          '
write(*,'(a)')'     $PRINTENV environment_variable_name                  [! comment ]          '
write(*,'(a)')'     $INCLUDE  filename                                   [! comment ]          '
write(*,'(a)')'     $IF       <constant LOGICAL expression>              [! comment ]          '
write(*,'(a)')'               < sequence of FORTRAN source statements>                         '
write(*,'(a)')'     [$ELSEIF  <constant LOGICAL expression>              [! comment ]          '
write(*,'(a)')'               < sequence of FORTRAN source statements>]                        '
write(*,'(a)')'     [$ELSE                                               [! comment ]          '
write(*,'(a)')'               < sequence of FORTRAN source statements>]                        '
write(*,'(a)')'     $ENDIF                                               [! comment ]          '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   An  exclamation character on a valid directive begins an in-line comment     '
write(*,'(a)')'   that is terminated by an end-of-line.                                        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Each  of the control lines delineates a block of FORTRAN source.  If the     '
write(*,'(a)')'   expression following the $IF is ".TRUE.",  then  the  lines  of  FORTRAN     '
write(*,'(a)')'   source  following  are  output.  If  it  is  ".FALSE.", and an $ELSEIF       '
write(*,'(a)')'   follows, the expression is evaluated and treated the same as the $IF. If     '
write(*,'(a)')'   the  $IF  and  all  $ELSEIF expressions are ".FALSE.", then the lines of     '
write(*,'(a)')'   source following the $ELSE are output. A matching $ENDIF ends the            '
write(*,'(a)')'   conditional block.                                                           '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Any  LOGICAL  expression  composed  of  integer constants,  parameters       '
write(*,'(a)')'   names operators, and the DEFINED function is valid if valid on a $IF and     '
write(*,'(a)')'   $ELSEIF.                                                                     '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Logical operators are ".NOT.",".AND.",".OR.",".EQV.", and ".NEQV."; and      '
write(*,'(a)')'   ".EQ.",".NE.",".GE.",".GT.",".LE.", and ".LT.", and                          '
write(*,'(a)')'   "+,"-","*","/","(",")" and "**".                                             '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Constant parameters e defined from the point they are uncountered in a       '
write(*,'(a)')'   $DEFINE directive until program termination unless explicitly                '
write(*,'(a)')'   undefined with a $UNDEFINE directive.                                        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   ------------------------------------                                         '
write(*,'(a)')'   $DEFINE <parameter> [= <expression>]                                         '
write(*,'(a)')'   ------------------------------------                                         '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   A $DEFINE may appear anywhere in a source file. If the value is ".TRUE."     '
write(*,'(a)')'   or ".FALSE." then the  parameter  is  of  type  LOGICAL,  otherwise  the     '
write(*,'(a)')'   parameter  is  of  type  INTEGER and the value must be an INTEGER. If no     '
write(*,'(a)')'   value is supplied, the parameter is of type INTEGER  and  is  given  the     '
write(*,'(a)')'   value 1. The DEFINED() parameter is NOT valid in a $DEFINE directive.        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   ----------------                                                             '
write(*,'(a)')'   DEFINED function                                                             '
write(*,'(a)')'   ----------------                                                             '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Syntax:                                                                      '
write(*,'(a)')'                                                                                '
write(*,'(a)')'     DEFINED (<parameter>)                                                      '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   If  <parameter>  has been defined at that point in the source code, then     '
write(*,'(a)')'   the function value is ".TRUE.", otherwise it is  ".FALSE.".  A  name  is     '
write(*,'(a)')'   defined  only  if  it has appeared in the source previously in a $DEFINE     '
write(*,'(a)')'   directive. The names used in compiler directives are district from names     '
write(*,'(a)')'   in  the  FORTRAN  source, which means that "a" in a $DEFINE and "a" in a     '
write(*,'(a)')'   FORTRAN source statement are totally unrelated.  The DEFINED() function      '
write(*,'(a)')'   is valid only on a $IF or $ELSEIF directive.                                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'     Example:                                                                   '
write(*,'(a)')'                                                                                '
write(*,'(a)')'            Program test                                                        '
write(*,'(a)')'     $IF .NOT. DEFINED (inc)                                                    '
write(*,'(a)')'            INCLUDE ''comm.inc''                                                '
write(*,'(a)')'     $ELSE                                                                      '
write(*,'(a)')'            INCLUDE ''comm2.inc''                                               '
write(*,'(a)')'     $ENDIF                                                                     '
write(*,'(a)')'            END                                                                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   The file,  "comm.inc"  will  be INCLUDEd in the source if the parameter,     '
write(*,'(a)')'   "inc", has not been previously defined, while INCLUDE  "comm2.inc"  will     '
write(*,'(a)')'   be  included in the source if "inc" has been previously defined. This is     '
write(*,'(a)')'   useful for setting up a default inclusion.                                   '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   $define A=1                                                                  '
write(*,'(a)')'   $define B=1                                                                  '
write(*,'(a)')'   $define C=2                                                                  '
write(*,'(a)')'   $if ( A + B ) / C .eq. 1                                                     '
write(*,'(a)')'      (a+b)/c is one                                                            '
write(*,'(a)')'   $endif                                                                       '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -------------------                                                          '
write(*,'(a)')'   $UNDEFINE directive                                                          '
write(*,'(a)')'   -------------------                                                          '
write(*,'(a)')'   A  symbol  defined  with  $DEFINE  can  be  removed  with  the $UNDEFINE     '
write(*,'(a)')'   directive, whose syntax is:                                                  '
write(*,'(a)')'                                                                                '
write(*,'(a)')'     $UNDEFINE <parameter>                                                      '
write(*,'(a)')'                                                                                '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  --------------                                                                '
write(*,'(a)')'  $PRINTENV NAME                                                                '
write(*,'(a)')'  --------------                                                                '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  If the name of an uppercase environmental variable is given the value         '
write(*,'(a)')'  of the variable will be placed in the output file. If the value is a          '
write(*,'(a)')'  null string or if the variable is undefined output will be stopped.           '
write(*,'(a)')'  This allows the system shell to generate code lines. This is usually          '
write(*,'(a)')'  used to pass in information about the compiler environment. For               '
write(*,'(a)')'  example:                                                                      '
write(*,'(a)')'  # If the following command were executed in the bash(1) shell...              '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  export STAMP="      write(*,*)''COMPILED ON:`uname -s`;AT `date`''"             '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  the environmental variable STAMP would be set to something like               '
write(*,'(a)')'                                                                                '
write(*,'(a)')'     write(*,*)''COMPILED ON:Eureka;AT Wed, Jun 12, 2013  8:12:06 PM''            '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  A version number would be another possibility                                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'     export VERSION="      program_version=2.1"                                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  Special predefined variable names are:                                        '
write(*,'(a)')'                                                                                '
write(*,'(a)')'     UFPP_DATE  ->      UFPP_DATE="12:58 14Jun2013"                             '
write(*,'(a)')'  Where code is assumed to have defined UFPP_DATE as CHARACTER(LEN=15)          '
write(*,'(a)')'     UFPP_FILE  ->      UFPP_FILE="current filename"                            '
write(*,'(a)')'  Where code is assumed to have defined UFPP_FILE as CHARACTER(LEN=1024)        '
write(*,'(a)')'     UFPP_LINE  ->      UFPP_LINE=    nnnnnn                                    '
write(*,'(a)')'  Where code is assumed to have defined UFPP_FILE as INTEGER                    '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  ---------                                                                     '
write(*,'(a)')'  Examples:                                                                     '
write(*,'(a)')'  ---------                                                                     '
write(*,'(a)')'  ----------------------------------------------------------------------------- '
write(*,'(a)')'  Simple usage:                                                                 '
write(*,'(a)')'  -------------                                                                 '
write(*,'(a)')'  $DEFINE a=1                                                                   '
write(*,'(a)')'  C                                                                             '
write(*,'(a)')'  C Will only compile the first version of SUB1.                                '
write(*,'(a)')'  C                                                                             '
write(*,'(a)')'        PROGRAM conditional compile                                             '
write(*,'(a)')'        CALL sub1                                                               '
write(*,'(a)')'        END                                                                     '
write(*,'(a)')'  C                                                                             '
write(*,'(a)')'  C                                                                             '
write(*,'(a)')'  $IF a .EQ. 1                                                                  '
write(*,'(a)')'         SUBROUTINE sub1                                                        '
write(*,'(a)')'         PRINT*, "This is the first SUB1"                                       '
write(*,'(a)')'         END                                                                    '
write(*,'(a)')'  C                                                                             '
write(*,'(a)')'  $ELSE                                                                         '
write(*,'(a)')'         SUBROUTINE sub1                                                        '
write(*,'(a)')'         PRINT*, "This is the second SUB1"                                      '
write(*,'(a)')'         END                                                                    '
write(*,'(a)')'  C                                                                             '
write(*,'(a)')'  $ENDIF                                                                        '
write(*,'(a)')'  ----------------------------------------------------------------------------- '
write(*,'(a)')'  Define variables on command line:                                             '
write(*,'(a)')'  ---------------------------------                                             '
write(*,'(a)')'  ufpp HP SIZE=64bit -i hp_directives.dirs @ < test.F90 >test_out.f90           '
write(*,'(a)')'                                                                                '
write(*,'(a)')'  defines variables HP and SIZE as if the expressions had been on a $DEFINE     '
write(*,'(a)')'  and reads file "hp_directives.dirs" and then stdin.  Unix redirection puts    '
write(*,'(a)')'  test.F90 onto stdin and redirects output to test_out.f90                      '
write(*,'(a)')'  ----------------------------------------------------------------------------- '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   -----------                                                                  '
write(*,'(a)')'   LIMITATIONS                                                                  '
write(*,'(a)')'   -----------                                                                  '
write(*,'(a)')'   $IF  constructs  can  be nested up to eight levels deep. Note that using     '
write(*,'(a)')'   more than two levels typically makes input files less readable.              '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Input files                                                                  '
write(*,'(a)')'       o lines are limited to 1024 columns. Text past column 1024 is ignored.   '
write(*,'(a)')'       o files already opened cannot be opened again.                           '
write(*,'(a)')'       o a maximum of 50 files can be nested by $INCLUDE                        '
write(*,'(a)')'       o filenames cannot contain spaces on the command line.                   '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   Variable names                                                               '
write(*,'(a)')'       o cannot be redefined unless first undefined.                            '
write(*,'(a)')'       o are limited to ',var_len,' characters.                                        '
write(*,'(a)')'       o must start with a letter (A-Z).                                        '
write(*,'(a)')'       o are composed of the letters A-Z, digits 0-9 and _ and $.               '
write(*,'(a)')'       o 128 variable names may be defined at a time.                                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   ---------------------------------------------                                '
write(*,'(a)')'   Major cpp(1) features not present in ufpp(1):                                '
write(*,'(a)')'   ---------------------------------------------                                '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   There is no option to specify directories to search for included files.      '
write(*,'(a)')'   The FORTRAN INCLUDE statement can be used instead in  cases  where           '
write(*,'(a)')'   the included file does not contain preprocessing directives.                 '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   There are no predefined preprocessor symbols. Use a directive input file     '
write(*,'(a)')'   instead.                                                                     '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   This program does not provide  string  (macro)  substitution  in  output     '
write(*,'(a)')'   lines.  See cpp(1) and m4(1) and related utilities if macro expansion is     '
write(*,'(a)')'   required.                                                                    '
write(*,'(a)')'                                                                                '
write(*,'(a)')'   While   cpp(1)  is the de-facto standard for preprocessing Fortran code,     '
write(*,'(a)')'   Part 3  of  the  Fortran  95  standard  (ISO/IEC  1539-3:1998)   defines     '
write(*,'(a)')'   Conditional    Compilation,     but   it  is   (currently) not    widely     '
write(*,'(a)')'   supported (See coco(1)).                                                     '
write(*,'(a)')'--------------------------------------------------------------------------------'
write(*,'(a)')'   -----------                                                                  '
write(*,'(a)')'   BUGS                                                                         '
write(*,'(a)')'   -----------                                                                  '
write(*,'(a)')'   Expressions that return negative numbers are not handled properly under      '
write(*,'(a)')'   all conditions                                                               '
write(*,'(a)')'$define name = 44                                                               '
write(*,'(a)')'$define a = 22                                                                  '
write(*,'(a)')'$define c = name + a                                                            '
write(*,'(a)')'$define d = a + a - name                                                        '
write(*,'(a)')'# PRODUCES:                                                                     '
write(*,'(a)')'# *ufpp* FATAL - SYNTAX ERROR:$define d = a + a - name                          '
write(*,'(a)')'--------------------------------------------------------------------------------'
end subroutine help
!===================================================================================================================================
end module M_fpp
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   program ufpp                                            !@(#) preprocessor for Fortran/FORTRAN source code
   !implicit none
   !--------------------------------------------------------
   use M_fpp
   !use M_fpp, only : line_length, source, write, nestl, iin, iocount, iototallines, iline_number, message
   ! LINE_LENGTH   current input line
   ! SOURCE        allowed length of input lines
   ! WRITE         flag whether current data lines should be written
   ! NESTL         nesting level for $IF/$ELSEIF/$ELSE/$ENDIF
   ! IIN()
   ! IOCOUNT
   ! LINE_LENGTH
   !--------------------------------------------------------
   use M_kracken, only: kracken, lget, rget, iget, sget, retrev, sgetl
   character(len=line_length) :: in_filename=''            ! input filename, default is stdin
   character(len=line_length) :: out_filename=''           ! output filename, default is stdout
   character(len=1)   :: prefix                            ! directive prefix character

   character(len=line_length)  :: line                     ! working copy of input line
!-----------------------------------------------------------------------------------------------------------------------------------
   call kracken('cmd','-i -I -o -prefix $ -help .false. -version .false.') !  define command arguments, default values and crack command line
   in_filename = sget('cmd_i')                                             !  get values from command line
   out_filename = sget('cmd_o')
   prefix = sget('cmd_prefix')
!-----------------------------------------------------------------------------------------------------------------------------------
   if(lget('cmd_version'))then                            ! if version switch is present display version name and exit
      write(*,'(a)') 'UFPP: Fortran Pre-processor version 1.4: 20130618'
      stop
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   if(lget('cmd_help'))then                                ! if help switch is present display help and exit
      call help()
      stop
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   call defines()                                          ! define named variables declared on the command line
   call includes()                                         ! define include directories supplies on command line
!-----------------------------------------------------------------------------------------------------------------------------------
   if(in_filename.eq.'')then                               ! open input file
      call include(in_filename,5)
   else
      call opens()
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   if(out_filename.eq.'')then                              ! open output file
      IOUT=6
   else
      IOUT=60
      open(unit=60,file=out_filename,iostat=ios,action='write')
      if(ios.ne.0)then
         write(message,'(a)')'*ufpp* FATAL - FAILED TO OPEN OUTPUT FILE:'//out_filename(:len_trim(out_filename))
         call stop_ufpp()
      endif
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   READLINE: do                                            ! read loop to read input file
      read(iin(iocount),'(a)',end=7) line
      iototallines=iototallines+1
      iline_number(iocount)=iline_number(iocount)+1
      if (line(1:1).eq.prefix) then                        ! prefix must be in column 1 for conditional compile directive
         source=line                                       ! make directive line available globally
         call cond()                                       ! process directive
      elseif (write) then                                  ! if last conditional was true then write line
         write(iout,'(a)') trim(line)                      ! write data line
      endif
      cycle
7     continue                                             ! end of file encountered on input
      if(iin(iocount).ne.5)then
         close(iin(iocount),iostat=ios)
      endif
      iocount=iocount-1
      if(iocount.lt.1)exit
   enddo READLINE
!-----------------------------------------------------------------------------------------------------------------------------------
   if (nestl.ne.0) then                                    ! check to make sure all if blocks are closed
      write(message,'(''*ufpp* FATAL - $IF BLOCK NOT CLOSED.'')')
      call stop_ufpp()
   endif
   end program ufpp
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
   character(len=*) function upperstr(linei)          !@(#)upperstr: return copy of string converted to uppercase 1996, John S. Urban
   use M_fpp, only : message, source, stop_ufpp
   implicit none
   character(len=*),intent(in) :: linei               ! input string to convert to uppercase

   intrinsic ichar, char, len
   integer                     :: inlen               ! number of characters in trimmed input string
   integer                     :: i10                 ! counter to increment through input and output string
   integer                     :: ilet                ! current character being converted represented using ASCII Decimal Equivalent
!-----------------------------------------------------------------------------------------------------------------------------------
   inlen=len_trim(linei)                              ! number of characters to convert to uppercase
   upperstr=' '                                       ! initialize output string to all blanks

   if(inlen.gt.len(upperstr))then                     ! make sure there is room to store the output characters
      write(message,'(a)')'*ufpp* FATAL - OUTPUT TOO LONG TO CONVERT TO UPPERCASE:'//trim(source)
      call stop_ufpp()
   endif

   do i10=1,inlen,1                                   ! loop through each character in input string
      ilet=ichar(linei(i10:i10))                      ! current character in input to convert to output converted to ADE
      if( (ilet.ge.97) .and. (ilet.le.122))then       ! lowercase a-z in ASCII is 97 to 122; uppercase A-Z in ASCII is 65 to 90
         upperstr(i10:i10)=char(ilet-32)              ! convert lowercase a-z to uppercase A-Z
      else
         upperstr(i10:i10)=linei(i10:i10)             ! character is not a lowercase a-z, just put it in output
      endif
   enddo
   end function upperstr
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!===================================================================================================================================

category: code