! \section{F95 to \TeX converter} ! ! The \texttt{f95totex} program converts free-format Fortran programs ! with \TeX-coded comments into typesettable \LaTeX\ form, with the ! actual program lines formatted using the \texttt{listings} package. ! The program lines are numbered according to the position in the ! F95 file, counting comment lines. ! \module{filespecs} ! This module contains the relevant numbers and data for the input and ! output files. module filespecs implicit none ! Maximum line length integer, parameter :: MAXLEN = 200 ! Input and output file numbers. integer :: infile integer :: outfile end module ! \module{texfileparts} ! This module handles writing of the various parts of the \TeX\ output file. module texfileparts use filespecs implicit none ! The current file state is tracked via a set of modes. At present, there are ! only two modes, \TeX\ output and F95 output. (Line skipping before F95 code ! is handled by leaving the blank lines in \TeX\ mode; listings skips blank ! lines after F95 code.) integer, parameter :: texmode = 0 integer, parameter :: f95mode = 1 integer :: currentmode ! The current line number in the F95 file is tracked in a module variable. integer :: currentlinenumber contains ! \subroutine{writeheader} ! The header is written as a single preplanned lump. subroutine writeheader(outfile) integer, intent(in) :: outfile ! We use the standard \texttt{article} class, with enlarged margins. The ! \texttt{amsmath} package is available for in-comment mathematics. The ! actual program listings are set with the \texttt{listings} package. ! The \texttt{textcomp} package is needed for upright single quotes. write(outfile,'(A)') "\documentclass[twoside]{article}" write(outfile,'(A)') "\usepackage{geometry}" write(outfile,'(A)') "\geometry{left=1in,right=1in,top=1in,bottom=1in}" write(outfile,'(A)') "\usepackage{amsmath}" write(outfile,'(A)') "\usepackage{textcomp}" write(outfile,'(A)') "\usepackage{listings}" ! The default Fortran 95 language file does not include strings delimited ! by single-quote characters. We define a new language spec that includes ! them. write(outfile,'(A)') "\lstdefinelanguage{MyFortran}[95]{Fortran}%" write(outfile,'(A)') " {morestring=[d]'}" ! All ``actual code'' listings are set in the \texttt{standardcode} style, ! as defined here. write(outfile,'(A)') "\lstdefinestyle{standardcode}{" write(outfile,'(A)') " language=MyFortran," write(outfile,'(A)') " columns=flexible," write(outfile,'(A)') " xleftmargin=36pt," write(outfile,'(A)') " numbers=left," write(outfile,'(A)') " numberstyle={\tiny}," write(outfile,'(A)') " identifierstyle={\itshape}," write(outfile,'(A)') " stringstyle={\ttfamily}," write(outfile,'(A)') " texcl=true," write(outfile,'(A)') " upquote=true," write(outfile,'(A)') " commentstyle={}" write(outfile,'(A)') "}" ! Definitions for program element macros. (This could be improved.) write(outfile,'(A)') "\newcommand{\module}[1]" write(outfile,'(A)') " {\section{Module: \texttt{#1}}}" write(outfile,'(A)') "\newcommand{\program}[1]" write(outfile,'(A)') " {\section{Program: \texttt{#1}}}" write(outfile,'(A)') "\newcommand{\subroutine}[1]" write(outfile,'(A)') " {\subsection{Subroutine: \texttt{#1}}}" write(outfile,'(A)') "\newcommand{\function}[1]" write(outfile,'(A)') " {\subsection{Function: \texttt{#1}}}" ! We define \verb|\code| as a short command for inline code. Also, we ! define a \verb|\var| command for variables only, which can be included in ! end-of-line comments without causing problems by nesting listings code. ! For variables, the only objectionable character is the underscore, which ! we handle by making it active -- which causes it to call the \verb|\_| ! macro that already exists. The standard indirection is required to set ! the catcode before the argument is processed. write(outfile,'(A)') "\newcommand{\code}{\lstinline[style=standardcode]}" write(outfile,'(A)') "\newcommand{\var}{\bgroup\catcode`\_=13\relax\dovar}" write(outfile,'(A)') "\newcommand{\dovar}[1]{\itshape #1\egroup}" ! A test of var: \var{x_yz} and math: $x^2 + y^2$ in code comments. ! Page headings, using the standard \texttt{headings} style. write(outfile,'(A)') "\pagestyle{headings}" ! And, finally, the beginning of the document. write(outfile,'(A)') "\begin{document}" ! Set current state as appropriate for the beginning of a new F95 file. call startf95file(outfile) end subroutine ! \subroutine{switchtotex} ! Switch from F95 mode to \TeX\ mode. subroutine switchtotex(outfile) integer, intent(in) :: outfile ! \texttt{lstlisting} environments should only be closed if they are ! currently open. We insert a blank line after the listing in order ! to start a new paragraph, and set the new paragraph to ! \verb|\noindent|. ! ! (Note that we have to break up the \texttt{lstlisting} name in ! order to avoid triggering the end of a listing when typesetting this ! program!) if (currentmode == f95mode) then write(outfile,'(A)') "\end{lst" // "listing}" write(outfile,'(A)') "" write(outfile,'(A)') "\noindent" end if currentmode = texmode end subroutine ! \subroutine{switchtof95} ! Switch from \TeX\ mode to F95 mode. subroutine switchtof95(outfile) integer, intent(in) :: outfile ! \texttt{lstlisting} environments should only be started if we are ! not currently in one. if (currentmode /= f95mode) then write(outfile,'(A)') "\begin{lstlisting}[%" write(outfile,'(A,I,A)') " firstnumber={", currentlinenumber, "}," write(outfile,'(A)') " style=standardcode]" end if currentmode = f95mode end subroutine ! \subroutine{writefooter} ! Finishes out the document. subroutine writefooter(outfile) integer, intent(in) :: outfile ! Close out any open \texttt{lstlisting} environment. call switchtotex(outfile) ! End the document. write(outfile,'(A)') "\end{document}" end subroutine ! \subroutine{startf95file} ! Resets the file state as appropriate for a new F95 file. subroutine startf95file(outfile) integer, intent(in) :: outfile ! Close any open \texttt{lstlisting} environment. call switchtotex(outfile) ! Reset the file number counter. currentlinenumber = 0 ! Write a header for the new file, if appropriate. (Not yet implemented.) end subroutine ! \subroutine{dof95line} ! Takes in a line from the F95 file and processes it as appropriate. subroutine dof95line(outfile, programline) integer, intent(in) :: outfile character (len=MAXLEN), intent(in) :: programline integer :: i ! Increment the current line number. currentlinenumber = currentlinenumber + 1 ! If the line is blank, we should just print it out without switching ! mode. if (len_trim(programline) == 0) then write(outfile,'(A)') "" ! Check for a commented \TeX-mode line, and print out the comment line ! without the preceeding comment character. elseif (programline(1:2) == '! ') then call switchtotex(outfile) write(outfile,'(A)') trim(programline(3:MAXLEN)) ! If neither of the above are true, the line is a line of code, and ! should be treated appropriately. ! ! This may eventually include extra parsing to check for module and ! function definitions and the like, and include automatic section ! headers and such for those. else call switchtof95(outfile) write(outfile,'(A)') trim(programline) endif end subroutine end module ! \module{commandline\_filenames} ! This module inspects the command line for input and output filenames, ! and returns them. module commandline_filenames ! Use the Intel Fortran POSIX routines (for command-line arguments) use ifposix use filespecs implicit none ! We define the filename variables as module variables, for ! simplicity (so they don't have to be declared twice). integer :: n_infiles character (len=MAXLEN), dimension(:), allocatable :: infilenames character (len=MAXLEN) :: outfilename ! File unit numbers for currently connected input and output files. integer :: unit_infile, unit_outfile contains ! \subroutine{getfilenames} ! Gets the file names from the command line. subroutine getfilenames character (len=MAXLEN) :: currentarg integer :: arglen, ierror integer :: i, i_currentarg ! Check that there are arguments, and set unit numbers to the standard ! input and output if not. if (ipxfargc() == 0) then n_infiles = 1 unit_infile = 5 unit_outfile = 6 return end if i_currentarg = 1 ! Get the first argument. call pxfgetarg(i_currentarg, currentarg, arglen, ierror) ! Check for whether the first argument is a ``-o'' flag, and retrieve ! the file name from the following argument. if (currentarg(1:2) == '-o') then ! Error checking: the argument should be simply ``-o'', with nothing ! after. if (arglen>2) then write(*,*) " Unknown option: " // trim(currentarg) stop end if ! Retrieve outfile name. call pxfgetarg(i_currentarg + 1, outfilename, arglen, ierror) if (ierror > 0) then write(*,*) " Error obtaining output file name." stop end if i_currentarg = i_currentarg + 2 ! Append ``.tex'' to outfile name if it has no extensions. if (index(outfilename, ".") == 0) then outfilename = trim(outfilename) // ".tex" end if ! Alternately, set outfilename to blank, for filling in later. else outfilename = "" end if ! Get count of input file names n_infiles = ipxfargc() - i_currentarg + 1 allocate(infilenames(n_infiles)) ! Read input file names from the command line. do i=1,n_infiles call pxfgetarg(i_currentarg, infilenames(i), arglen, ierror) i_currentarg = i_currentarg + 1 ! Append ``.f90'' to infile name if it has no extensions. if (index(infilenames(i), ".") == 0) then infilenames(i) = trim(infilenames(i)) // ".f90" end if end do ! If outfilename is undefined, define it from the first infile name. if (len_trim(outfilename) == 0) then outfilename = infilenames(1) outfilename = outfilename(1:index(outfilename,'.',back=.true.)-1) // ".tex" end if ! At this point, we have an outfile; we may or may not have an infile. We set ! the unit numbers appropriately. if (n_infiles > 0) then unit_infile = 10 else unit_infile = 5 endif unit_outfile = 11 end subroutine ! \function{connectinputfile} ! Connects the $i$th input file name to a file handle, and returns the handle. ! If $i$ is zero, this function simply closes the file. function connectinputfile(i_infile) result(u_infile) integer, intent(in) :: i_infile integer :: u_infile ! If the infile unit is 5 (standard input), we do nothing but return the unit ! number. if (unit_infile == 5) then u_infile = 5 return end if ! We close the input file handle. (If it's not open, this is a no-op.) close(unit_infile) ! Open the new infile, if within range, and return the unit number; else, ! return zero. if ((i_infile > 0) .AND. (i_infile <= n_infiles)) then open (unit_infile, file = infilenames(i_infile), & action = 'read', status = 'old') u_infile = unit_infile else u_infile = 0 end if end function ! \function{connectoutputfile} ! Connects the output file name to a file handle, and returns the handle. function connectoutputfile result(u_outfile) integer :: u_outfile ! If the outfile unit is 6 (standard output), we do nothing but return the unit ! number. if (unit_outfile == 6) then u_outfile = 6 return end if ! We close the output file handle. (If it's not open, this is a no-op.) close(unit_outfile) ! Open the new outfile and return the unit number. open (unit_outfile, file = outfilename, & action = 'write', status = 'replace') u_outfile = unit_outfile end function end module ! \program{f95totex} ! The main program handles opening the input and output files, and ! reading the individual lines and passing them to \texttt{dof95line}. program f95totex use filespecs use texfileparts use commandline_filenames implicit none character (len=MAXLEN) :: programline integer :: i ! Collect filenames from the command line. call getfilenames ! Open output file, as appropriate. outfile = connectoutputfile() ! Start \TeX file. call writeheader(outfile) ! Loop over input files. do i=1,n_infiles infile = connectinputfile(i) call startf95file(outfile) ! Loop until end of input file, reading lines and processing them. 10 continue read(infile, '(A)', end=20) programline call dof95line(outfile, programline) goto 10 20 continue ! end of file. enddo ! Finish \TeX file. call writefooter(outfile) end program