Addman
Addman

Reputation: 341

An easy way, how to format output in Fortran

For an older Fortran project, I tried to create a modern logger. It was written object-oriented very similar to python's logging library.

The last thing that bothers me is formating, I will explain.

My logger looks very similar to this library, however, it did not meet my requirements so I wrote my own. Basically my logging library looks something like this:

lg = Logger("super logger")
... other settings ...
lg%log("DEBUG", "A cool message to log", rank)

but with this logger, I lost all of the formatting capabilities of the write statement. Basically in the code string formatting is done by write statements such as:

write (stdout, '(7X,A,f12.6)') "The great number is ", number

So now I have to have two lines of code for every output statement and auxiliary variable:

write (message, '(A,f12.6)') "The great number is ", number
lg%log("DEBUG", message, rank)

which is not very convenient nor nice.

I was trying to do a formatting function, but I guess it is not possible with a variable number of arguments (like in C) e.g:

function fm(fmt, ...) result(message)

I believe I cannot do a C workaround either because when I specify the binding interface I have to specify arguments.

The only solution I have now (except the two-line example) is to overload some method for different variables and create something like streams in c++.

my question is do you have a better idea? Ideally, if you know about a option how to format string in one line.

Upvotes: 2

Views: 893

Answers (2)

roygvib
roygvib

Reputation: 7395

I also use a similar approach (with veryeverie) in my local library, i.e. define X_to_str() for X = integer, real, etc, overload them to to_str(), and use // if necessary. In my case, format is made an optional argument with the default value given in X_to_str(), so that I can just write myfunc( "hello" // to_str(777) ) etc (e.g. with i0 for the default).

Another approach might be to overload // to concatenate string + non-string arguments, like the following (though I've never used this in my codes up to now...)

module str_mod
    implicit none
    interface operator(//)
        module procedure cat_int, cat_real
    end interface

    character(20) :: fmt_int  = "(i0)"
    character(20) :: fmt_real = "(g0.6)"
contains

function cat_int( x, y ) result(res)
    character(*), intent(in)  :: x
    integer, intent(in)       :: y
    character(:), allocatable :: res

    character(20)  sy
    write( sy, fmt_int ) y
    res = trim( x ) // " " // trim(adjustL( sy ))
end
function cat_real( x, y ) result(res)
    character(*), intent(in)  :: x
    real, intent(in)          :: y
    character(:), allocatable :: res

    character(20)  sy
    write( sy, fmt_real ) y
    res = trim( x ) // " " // trim(adjustL( sy ))
end

end module

program main
    use str_mod
    implicit none

    print *, "hello" // 1.23 // " world" // 777
end

Upvotes: 1

veryreverie
veryreverie

Reputation: 2981

You can wrap the conversion to string in a function, like

module io
  
  interface str
    module procedure str_real
  end interface
  
contains
  
  function str_real(input,format) result(output)
    real(dp), intent(in) :: input
    character(*), intent(in) :: format
    character(:), allocatable :: output
    
    ! It's probably possible to do the allocation and re-allocation more cleanly.
    allocate(output(100))
    write(output, format) input
    output = trim(output)
  end function
  
end module

which you can then use with your logger as

lg%log("DEBUG", "The great number is "//str(number,'(f12.6)'), rank)

You'll need separate str functions for each type you want to do this with.

Upvotes: 2

Related Questions