Feedback for `lfortran fmt` to format Fortran source code

LFortran now has an initial implementation of lfortran fmt, see here for screenshots. Here are the current capabilities as of 0.7.0-132-g35f9ff82:

$ lfortran fmt -h
Format Fortran source files.
Usage: lfortran fmt [OPTIONS] file

Positionals:
  file TEXT REQUIRED     Fortran source file to format

Options:
  -h,--help              Print this help message and exit
  -i                     Modify <file> in-place (instead of writing to stdout)
  --spaces INT=4         Number of spaces to use for indentation
  --indent-unit          Indent contents of sub / fn / prog / mod
  --no-color             Turn off color when writing to stdout

What other options would people like to have so that LFortran could format the source code exactly as you like?

Based on our past discussions, it seems the three major styles are:

  1. Use 2 spaces and indent function bodies:
$ lfortran fmt --spaces 2 --indent-unit integration_tests/program_01.f90 
program program_01
  integer, parameter :: dp=kind(0.d0)
  real :: a
  integer :: i
  print *, "Normal random numbers:"
  do i = 1, 10
    call rand(a)
    print *, a
  end do

contains

  subroutine rand(x)
    real, intent(out) :: x
    logical, save :: first=.true.
    real, save :: u(2)
    real :: r2
    if (first) then
      do
        call random_number(u)
        u = ((2)*(u)) - (1)
        r2 = sum((u)**(2))
        if (((r2)<(1)).and.((r2)>(0))) then
          exit
        end if
      end do
      u = (u)*(sqrt(((-(2))*(log(r2)))/(r2)))
      x = u(1)
    else
      x = u(2)
    end if
    first = .not.(first)
  end subroutine rand

end program program_01
  1. Use 4 spaces, but do not indent function bodies:
$ lfortran fmt --spaces 4 integration_tests/program_01.f90 
program program_01
integer, parameter :: dp=kind(0.d0)
real :: a
integer :: i
print *, "Normal random numbers:"
do i = 1, 10
    call rand(a)
    print *, a
end do

contains

    subroutine rand(x)
    real, intent(out) :: x
    logical, save :: first=.true.
    real, save :: u(2)
    real :: r2
    if (first) then
        do
            call random_number(u)
            u = ((2)*(u)) - (1)
            r2 = sum((u)**(2))
            if (((r2)<(1)).and.((r2)>(0))) then
                exit
            end if
        end do
        u = (u)*(sqrt(((-(2))*(log(r2)))/(r2)))
        x = u(1)
    else
        x = u(2)
    end if
    first = .not.(first)
    end subroutine rand

end program program_01
  1. Use 4 spaces and indent function bodies:
$ lfortran fmt --spaces 4 --indent-unit integration_tests/program_01.f90 
program program_01
    integer, parameter :: dp=kind(0.d0)
    real :: a
    integer :: i
    print *, "Normal random numbers:"
    do i = 1, 10
        call rand(a)
        print *, a
    end do

contains

    subroutine rand(x)
        real, intent(out) :: x
        logical, save :: first=.true.
        real, save :: u(2)
        real :: r2
        if (first) then
            do
                call random_number(u)
                u = ((2)*(u)) - (1)
                r2 = sum((u)**(2))
                if (((r2)<(1)).and.((r2)>(0))) then
                    exit
                end if
            end do
            u = (u)*(sqrt(((-(2))*(log(r2)))/(r2)))
            x = u(1)
        else
            x = u(2)
        end if
        first = .not.(first)
    end subroutine rand

end program program_01

Feel free to comment here or open issues at: https://gitlab.com/lfortran/lfortran/-/issues

I am aware of:

  • #201: not printing parentheses around expressions if not needed
3 Likes

Nice features. What will be interesting:

  • An automatic way to cut a long line
  • Some kind of a tabular format for the variable declaration, so that “intent(…)”, the “::”, the variable names, the “=” (for the initialisation) start at the same column for a given module/type or subroutine.
1 Like

As reference, here are the options of findent:

~/findent-3.1.7$ findent -h
findent [options]
   Format fortran source.
   Findent reads from STDIN, writes to STDOUT.
   Findent uses various kinds of indentations, see OPTIONS.
   Findent can convert from fixed form to free form and vice versa and
   can supplement END statements, see 'Refactor' below.
   Comment lines with '!' in column one are not indented.
   You can correct findent related indenting errors by
   inserting comment lines: 
    !  findentfix: <fortran statement>
   where <fortran statement> is for example DO, END, WHERE() etcetera.
   Findent will adjust the indentation according to <fortran statement>.
OPTIONS (errors are silently ignored):

  General options:

 	  Below: <n> denotes an unsigned decimal number.
 	         <c> denotes a character.
 	   
 	  In the long options, you can replace '_' with '-'.

-h, --help	: print this text
-H, --manpage	: print man page
--readme	: print some background information
-v, --version	: prints findent version
-q, --query_fix_free	: guess free or fixed, prints 'fixed' or 'free' and exits
--continuation=<c>	:  ' ': (default) do not change continuation characters
 	   '0': create numbered continuation characters
 	   other: use that continuation character
 	   default for conversion from free to fixed is '&'
--include_left=<n>	: (0/1) 1: indent include statements to starting indent (default:0)
-l<n>, --label_left=<n>	: (0/1) 1: move statement labels to start of line (default:1)
 	        (only for free format)
-lastindent, --last_indent	: prints computed indentation of last line
 	        (for usage with vim)
-lastusable, --last_usable	: prints line number of last line usable
 	        as start for indenting(for usage with vim)
-iauto, --input_format=auto	: determine automatically input format (free or fixed)
-ifixed, --input_format=fixed	: force input format fixed
 	  (default: auto)
-ifree, --input_format=free	: force input format free
 	  (default: auto)
-i-, --indent=none	: do not change indent (useful in combination with -R)
-L<n>, --input_line_length=<n>	: use only first <n> characters of each line
 	  default=0: take whole lines
-L<n>g, --input_line_length=<n>g	: same as above, but use gfortran convention
 	  for counting the characters with tabbed lines
 	   example: --input_line_length=72g
-M<n>, --max_indent=<n>	: maximum output indent, default 100, 0: no limit
-ofixed, --output_format=fixed	: force fixed format output
-ofree, --output_format=free	: force free format output
-osame, --output_format=same	: output format same is input format
--openmp=<n>	:  0: do not indent openmp conditionals
 	   1: indent openmp conditionals (default)
 	   NOTE: for free format, the omp sentinel must be '!$ '
-Rr, --refactor_procedures	: refactor procedures and modules: the END line
 	   of a subroutine, program etc. is, if possible, replaced by
 	   'end subroutine <name>' or
 	   'end function <name>' or
 	   'end procedure <name>' or
 	   'end program <name>' or
 	   'end block data <name>' or
 	   'end module <name>' or
 	   'end submodule <name>'
 	   where <name> is the name of the appropriate procedure, subroutine etc.
 	   NOTE1: if the END line contains a continuation the results are undefined
 	   NOTE2: a line like 'end function fun' will be replaced by
 	          'end subroutine sub' if the END line ends 'subroutine sub'
-RR, --refactor_procedures=upcase	: same as -Rr, but 'END SUBROUTINE <name>'
 	  in stead of 'end subroutine <name>' etc.

  Indenting options:

-I<n>, --start_indent=<n>	: starting  indent (default:0)
-Ia, --start_indent=a	: determine starting indent from first line
-i<n>, --indent=<n>	: all       indents except I,c,C,e (default: 3)
-a<n>, --indent_associate=<n>	: ASSOCIATE    indent
-b<n>, --indent_block=<n>	: BLOCK        indent
-d<n>, --indent_do=<n>	: DO           indent
-f<n>, --indent_if=<n>	: IF           indent
-E<n>, --indent_enum=<n>	: ENUM         indent
-F<n>, --indent_forall=<n>	: FORALL       indent
-j<n>, --indent_interface=<n>	: INTERFACE    indent
-m<n>, --indent_module=<n>	: MODULE       indent
-r<n>, --indent_procedure=<n>	: FUNCTION,
 	   SUBROUTINE and PROGRAM indent
-s<n>, --indent_select=<n>	: SELECT       indent
-t<n>, --indent_type=<n>	: TYPE         indent
-w<n>, --indent_where=<n>	: WHERE        indent
-x<n>, --indent_critical=<n>	: CRITICAL     indent
--indent_changeteam=<n>     	: CHANGE TEAM  indent
-C-, --indent_contains=restart, 	: restart indent after CONTAINS
-k<n>, --indent_continuation=<n>	: continuation indent except   
 	    for lines starting with '&'
 	       free to free only
-k-, --indent_continuation=none	: continuation lines not preceded
 	    by '&' are untouched
 	       free to free only
  	: next defaults are: all - all/2
-c<n>, --indent_case=<n>	: CASE      negative indent
-C<n>, --indent_contains=<n>	: CONTAINS  negative indent
-e<n>, --indent_entry=<n>	: ENTRY     negative indent
 	   
Dependencies:
--deps	: output dependency information only, other flags are ignored.
 	  This can be used to generate a dependencies file for usage with make(1).
 	   The format of this information:
 	   Fortran source      ->        findent output
 	    include "file1"  ->        inc file1
 	    #include "file2" ->        cpp file2
 	    #include <file3>   ->        std file3
 	    ??include 'file4'  ->        coc file4
 	    use module1        ->        use module1
 	    submodule(m) subm  ->        use m
 	                                 mod m:subm
 	    module module2     ->        mod module2
--makefdeps	: outputs a sh(1) script that serves as a an example
 	   to generate dependencies for use in make(1).
1 Like

Some of my favorite additional features of the two apps I typically use other
than basic indenting are cleanup of whitespace. See

https://sourceforge.net/projects/findent
https://github.com/pseewald/fprettify

  • It is good to get rid of tab characters and trailing spaces at a minimum.
  • An option to collapse multiple blank lines
  • toggle enddo <==> end do, same for goto, endif, elseif, …
  • removing//adding a space around +, =, ==,
  • ([; changing comma to comma-space and vice-versa
  • setting margins and redoing continued and long lines to fit
  • interface/Plugin for vim and (put your favorite editor here)
  • putting & at end and beginning of a continued line (and others would want the
    sometimes-redundant & removed)
  • output of a colorized HTML version similiar to what is generated with
    github markup “```fortran” syntax, or what indent(1), vim(1) and emacs(1)
    autoformatting, …does.

See https://fortranwiki.org/fortran/show/Tools and add your favorites there.
So far, I do not think this sight has a tool list or it is harder to find.
Some kind of better index is needed here. Far easier to find recent changes
and categories on the Fortran Wiki so far.

1 Like

Thanks @gardhor, @ivanpribec and @urbanjost for excellent feedback and ideas.

It seems all of the proposed features can be added to LFortran.

The hard ones to implement are those that preserve original user formatting that does not have semantic meaning. For example, if the user adds a blank line and we want to preserve it. The same with comments (which we obviously want to preserve) or any other white space.

Currently the AST only contains things that have semantic meaning. So I think we should add two more AST nodes: Comment and BlankLine (can be repeated). The formatter can then choose to preserve these if requested. It would still lose all other white space (and also whether keywords are uppercase or lowercase or mixed), but it seems most people would be ok with that, since we want to format all other white space automatically.

The alternative is to create a parser that preserves everything (roundtrippable): it can convert to AST and back to source code that is equal to the original. The C# compiler does it. I am a bit worried it would slow the parser down for regular compiling.

So I’ll experiment with adding BlankLine and Comment which might be enough to make it usable for a lot of people. And then we’ll see.

See https://fortranwiki.org/fortran/show/Tools and add your favorites there.
So far, I do not think this sight has a tool list or it is harder to find.
Some kind of better index is needed here. Far easier to find recent changes
and categories on the Fortran Wiki so far.

Good point. CCing @milancurcic and @lkedward on this. I think we discussed having a “tools” section on our website to collect such tools and allow people to add more.