3

Suppose I have a text file with potentially very long lines of text, for example

short
short
reeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooonnnnggg
short as well
short

I can write a simple program which reads this file:

program main
implicit none
integer, parameter        :: BIG_NUMBER = 400
integer                   :: lun
character(len=BIG_NUMBER) :: line
integer                   :: istat

open(newunit = lun, file = 'myfile')

do
   read (lun, '(A)', iostat = istat) line
   if (istat /= 0) exit
end do

end program main

This only supports text files, where all lines are not longer than 400 characters. In C, pointers are used and a similar program would automatically support any text file.

How can I rewrite the sample program in such a way that it can read lines of all lengths?

Wauzl
  • 941
  • 4
  • 20
  • 2
    You could use formatted stream access and read character after character and place them into a stack or queue. When you encounter the line end character allocate an appropriately long character variable and copy the content of the stack. – Vladimir F Героям слава Jan 12 '16 at 14:48
  • See also [this](http://stackoverflow.com/questions/31084087/using-a-deferred-length-character-string-to-read-user-input). – Wauzl Jan 13 '16 at 08:46

2 Answers2

7

You can use non-advancing input to read successive chunks of the line into a buffer, and then combine each chunk together to form the complete line in a deferred length character variable. For example:

subroutine get_line(lun, line, iostat, iomsg)
  integer, intent(in)           :: lun
  character(len=:), intent(out), allocatable :: line
  integer, intent(out)          :: iostat
  character(*), intent(inout)   :: iomsg

  integer, parameter            :: buffer_len = 80
  character(len=buffer_len)     :: buffer
  integer                       :: size_read

  line = ''
  do
    read ( lun, '(A)',  &
        iostat = iostat,  &
        iomsg = iomsg,  &
        advance = 'no',  &
        size = size_read ) buffer
    if (is_iostat_eor(iostat)) then
      line = line // buffer(:size_read)
      iostat = 0
      exit
    else if (iostat == 0) then
      line = line // buffer
    else
      exit
    end if
  end do
end subroutine get_line
IanH
  • 21,026
  • 2
  • 37
  • 59
  • 1
    Note to myself: Don't mind the syntax highlighting, `//` is NOT the beginning of a comment but rather string concatenation. – Wauzl Jan 12 '16 at 20:20
  • Do we use implicit reallocation of `line`? – Wauzl Jan 12 '16 at 20:22
  • 2
    Yes, but for deferred length allocatable character variables all compilers with significant F2008 capability support that by default. – IanH Jan 12 '16 at 21:12
  • This is a really nice subroutine, however if you're interested in efficiency for REALLY long lines you should at the very least make buffer_len a lot larger, say 4k. Or even better, increase the size of the buffer by a multiplicative factor for each iteration in order to make the cost of the reallocation+copying logarithmic. But that is perhaps unneeded complexity unless you REALLY need it.. – janneb Jan 14 '16 at 08:27
1

Never seek another solution when you have one at hand, unless it has some limitations. Since you can do it in C and with all the interoperability stuff between C and Fortran, I suggest you write a small part of the program in C to handle the reading. The C part can be as simple as a set of 2 functions and few global variables:

  1. a function to query the size of the next line that actually reads the next line and returns its size to fortran, in addition to a EOF flag
  2. a function to get the next line data that returns the line data to fortran.

If you are using getline for example, the global variables will be the File object, the line buffer and the buffer size. In addition, you can add two other functions to be called by fortran to open and close the file, or you can handle all of that in the query function.

innoSPG
  • 4,588
  • 1
  • 29
  • 42