Sometimes all we want to do is read a line of text from a file. But let’s make it more interesting and use recursion. The following Fortran module readlines
reads lines from a text file. The read
statement uses non-advancing I/O to read a small piece of the! current line in a file. If the end-of-line is reached, the file pointer is moved to the start of the next line. It uses a recursive subroutine piecemeal_readline()
to do all the hard graft. In this case it reads chunks of text, 10 characters in length, and appends them to the line
. When the end-of-line is encountered, the line is returned.
module readlines
use iso_fortran_env
implicit none
contains
subroutine readline(lun, line, success)
integer, intent(in) :: lun
character(len=:), allocatable, intent(out) :: line
logical, intent(out) :: success
character(len=0) :: newline
success = .true.
call piecemeal_readline(newline)
contains
recursive subroutine piecemeal_readline(newline)
character(len=*), intent(in) :: newline
character(len=10) :: piece
integer :: ierr, sz
read(lun, '(a)', advance='no', size=sz, iostat=ierr) piece
if (ierr /= 0 .and. ierr /= iostat_eor) then
allocate(character(len=len(newline)):: line)
line = newline
success = .false.
return
endif
! End of line?
if (sz >= len(piece)) then
call piecemeal_readline(newline // piece)
else
allocate(character(len=len(newline)+sz):: line)
line = newline // piece(1:sz)
success = .true.
endif
end subroutine piecemeal_readline
end subroutine readline
end module readlines
Here is a passage to test what is happening (from Sense and Sensibility, Jane Austen):
I am excessively fond of a cottage; there is always so much comfort, so much elegance about them. And I protest, if I had any money to spare, I should buy a little land and build one myself, within a short distance of London, where I might drive myself down at any time, and collect a few friends about me and be happy. I advise everybody who is going to build, to build a cottage.
Basically let’s look at what happens at Line 34, which determines whether the end of a line has been encountered. If we process just the first line of the above text, using string chunks 10 in length, we get the following if the if
statement is true:
I am exces I am excessively fon I am excessively fond of a cot I am excessively fond of a cottage; ther I am excessively fond of a cottage; there is alway I am excessively fond of a cottage; there is always so much I am excessively fond of a cottage; there is always so much comfort, s I am excessively fond of a cottage; there is always so much comfort, so much
Basically, because it is not end-of-line, the current piece
of text is appended to newline
, which is then used as input to the recursive call of piecemeal_readline()
. Each recursive call adds a chuck onto the line of text. When the end of line is encountered, the other part of the if
statement is processed, returning the line as a string to the calling program. Here is a sample program that uses the module readlines
.
program dynamo
use readlines
implicit none
character(*), parameter :: filename = 'passage2.txt'
integer :: lun
logical :: ios
character (len=:), allocatable :: str
open(unit=lun, file=filename, status='old', action='read')
ios = .true.
do while (ios .eqv. .true.)
call readline(lun,str,ios)
if (ios .eqv. .true.) then
print*, str
print*, "Length = ", len(str)
endif
enddo
close(lun)
end program dynamo
Here is the program running with the passage from above (in passage4.txt
):
I am excessively fond of a cottage; there is always so much comfort, so much Length = 77 elegance about them. And I protest, if I had any money to spare, I should buy Length = 78 a little land and build one myself, within a short distance of London, where I Length = 79 might drive myself down at any time, and collect a few friends about me and be Length = 79 happy. I advise everybody who is going to build, to build a cottage. Length = 68