Updating projects automatically

Scanning Fortran projects at GitHub, certain flaws appear regularly, for example

  1. Hard-coding kinds with real(8) or worse, using the non-standard real*8
  2. Using source files suffixes such as .f95, .f08 instead of .f90 (which means free-form, not Fortran 90).

There are tools to modernize Fortran code that operate on individual source files. It would be nice if there were a tool that given a project could

  1. Infer how to compile the code (there are still many projects that don’t use FPM).
  2. Look for and correct flaws and create a new project with the corrections. Besides looking for anti-patterns such as real(8) or real*8 with a tool in Python etc., strict compiler options could be used.
  3. Compile the old and new projects and verify that they give the same results (although this is tricky for codes that use RNG without setting the seed).

Even if a tool did only 90% of this and required some further manual work, it would still be useful. I have often raised an issue about file suffixes, and it most cases the authors have changed them to .f90.

We could do this probably in Lfortran, fortls, fprettify without too much work. Specifically language servers have a code action instruction meant exactly for this kind of stuff. I just never got around adding anything of value in them. Also, given that using the correct kind is sometimes a mess (or very verbose) and the general lack of “a Fortran style”, pushed this project quite down in the priority list (for me at least).

1 Like

I personally like real(8), real(4), dble, 1.d0, cmplx(x,y,8) and so on.

So I don’t consider them flaws even though they are not portable.

They are, at least to me, `easy on the eye’, which is worth much more when dealing with highly complicated code than religiously following the standard.

In addition, fpt(1), spag(1) are great tools for this.

The compilers themselves should not be overlooked. With flags to apply standards conformance they often flag usage like this, even if they do not automatically change it. Most compiler can employ an implicit IMPLICIT NONE and several can generate interface blocks for procedures without them.

For OS projects where the code is public anyway a rating tool on-line like is available for HTML5 might be nice. It would be a “natural” for an on-line compiler like Compiler Explorer or the fortran-lang playground to also provide reformatting or a rating service as well.

It is arguable whether it should be required, but if such a tool were available on-line it would be worth strongly suggesting it be used on code going into the fpm/fortran repository, along with at least an abstract and index of procedures.

At least one on-line compiler already incorporates fprettify(1), although I do not remember which one off the cuff.

Non-standard usage is perhaps the least contentious to flag. Indenting styles, use of == versus .eq., “end if” versus “endif”, and other formatting-related issues are more a matter of taste.

I would probably flag use of list-directed I/O on production code which is a very personal preference, for instance (a “rule” I break anyway :blush: ).

Every compiler treats real(8) as real(kind(1.0d0)) by default or with a compiler option, so the problem is less portability but flexibility – if you want to change the code to single or quadruple precision you need to replace the 8s that are in declarations but not in other places, which is a hassle.

1 Like

How often do you need to do that though? I’ve used this once or twice for Lapack to compile it for quadruple precision, but there I just used a compiler option, since Lapack doesn’t use “real(dp)”. I did adjust “dp” in the Lapack interface module that I wrote (which was nice, but I could have used a compiler option there too — I did it just so that I can say I actually finally was able to use this feature). I am trying to think if I ever used this feature any other time, I don’t think I did!

It’s useful to write code that would work with multiple precision, but for that you need the new generics.

1 Like

To give an idea of what fpt can do, here is an example from the Linux and Windows distributions:

wumpus.txt  25-Oct-23  John Collins

Modernising Wumpus
==================

wumpus.f is a slightly extended FORTRAN 77 implementation of "Hunt the Wumpus".  To my surprise,
it compiles and runs under gfortran and the game is playable.  The code needs modernisation.
For example, the code which sets up the cave system where the Wumpus lives begins:

      SUBROUTINE PASSAG

      INCLUDE 'ints.inc'

      INTEGER DEST,IPASS(3,20)

      DO 230 I = 1,20
      DO 220 J = 1,3
      IPASS(J,I) = 0
220   CONTINUE
230   CONTINUE
      DO 300 I = 1,3
      N = 20
      J = 0
      DO 280 WHILE (N .GT. 0)
      J = J+1
      IF (IPASS(I,J) .EQ. 0) THEN
240   CONTINUE
      M = N*RAND()
      IF ((M .EQ. 0) .OR. (M .EQ. N)) GOTO 240
      K = 0
      DEST = J
      DO 260 WHILE (K .LT. M)
      DEST = DEST+1
      IF (IPASS(I,DEST) .EQ. 0) K = K+1
260   CONTINUE
      DO 270 L = 1,I-1
      IF (DEST .EQ.IPASS(L,J)) GOTO 240
270   CONTINUE
      IPASS(I,J) = DEST
      IPASS(I,DEST) = J
      N = N-2
      ENDIF
280   CONTINUE
300   CONTINUE
C     WRITE(6,310)(J,(IPASS(I,J),I = 1,3),J=1,20)
310   FORMAT(4I5)
C
      END

There is no indentation, implicit typing, the DO loop control constructs are DO <label> -
<label> CONTINUE, variables are passed between routines in a COMMON block and there are labels
everywhere.

fpt runs on this code with the script modernise_fortran.fsp to produce:

SUBROUTINE passag
!
!
! ****************************************************************************
!
!
        USE fpt_module_kinds
!
        USE module_ints
        IMPLICIT NONE
!
! ****************************************************************************
!
        INCLUDE 'ints.i90'
!
        INTEGER(KIND=ki4)dest,ipass(3,20)
        INTEGER(KIND=ki4) :: i
        INTEGER(KIND=ki4) :: j
        INTEGER(KIND=ki4) :: k
        INTEGER(KIND=ki4) :: l
        INTEGER(KIND=ki4) :: m
        INTEGER(KIND=ki4) :: n
        REAL(KIND=kr4) :: rand
!
        DO i=1,20
           DO j=1,3
              ipass(j,i)=0
           ENDDO
        ENDDO
        DO i=1,3
           n=20
           j=0
           DO WHILE (n>0)
              j=j+1
              IF (ipass(i,j)==0) THEN
240              CONTINUE
                 m=n*rand()
                 IF ((m==0) .OR. (m==n)) THEN
                    GOTO 240
                 ENDIF
                 k=0
                 dest=j
                 DO WHILE (k<m)
                    dest=dest+1
                    IF (ipass(i,dest)==0) THEN
                       k=k+1
                    ENDIF
                 ENDDO
                 DO l=1,i-1
                    IF (dest==ipass(l,j)) THEN
                       GOTO 240
                    ENDIF
                 ENDDO
                 ipass(i,j)=dest
                 ipass(i,dest)=j
                 n=n-2
              ENDIF
           ENDDO
        ENDDO
!
        DO i=1,20
           DO j=1,3
              passage(j,cave_alias(i))=cave_alias(ipass(j,i))
           ENDDO
        ENDDO
!       WRITE(6,310)(j,(pass(i,j),i = 1,3),j=1,20)
!
END

The text of the script modernise_fortran.fsp is:


! modernise_fortran.fsp  20-May-24  John Collins

! Please edit this file to customise the changes for your system

! File handling
! =============
! We assume that the top-level directory structure is:
!
!   project_base_directory
!   !   original_source
!   !   !   directories containing the code
!   !   modified_source
!   !   !   empty directories matching original_source
!   !   fpt_output
!   !   !   empty directories matching original_source
!
! If your directory structure is different (and it may be) comment-out the following
! Set a default for new files (if any)
% output directory "../fpt_output"
% keep directories
% edit output file names: replace "original_source" by "fpt_output"
% edit output file names: replace "modified_source" by "fpt_output"
! Note - specify the input file name extensions in the list of input files
% primary output file name extension: ".f90"
% include output file name extension: ".i90"
% overwrite changed files

! modified_source contains files changed by hand. Look in modified source for changed files
% check modified source

! Code changes
! ============
% specify implicit none
% specify numeric kinds
% change do continue to do enddo
% change if to if-then
% remove labels from enddo statements
% remove labels from executable statements
% change relational operators to symbolic form
! Choose a number (or comment-out)
% remove format statements used fewer than 3 times
% change common to module
% make makefile
! Suppress the diagnostics which mark these changes
% suppress diagnostic 2255 4243 4495

! Code formatting
! ===============
% free format
% no column format

! Choose numbers appropriate for your system
% output code line length: 130
% page width: 132
% write continuation character in column 88

! Choose between:
!!! % lower case keywords
% upper case keywords
!!! % lower case intrinsics
% upper case intrinsics
% lower case symbols
!!! % upper case symbols
!!! % lower case parameters
!!! % upper case parameters
% default case parameters
% lower case kind tags
!!! % upper case kind tags
!!! % default case kind tags
!!! % lower case exponent characters
% upper case exponent characters
! Optionally:
% space before "::"
% space after "::"
% space before "="
% space after "="

! End of modernise_fortran.fsp

2 Likes

Apologies! The block formatting thinks that the fsp file is Fortran and coloured the keywords :smile:

You can add a txt after the triple back-quote for block formatting to make it a single color (plaintext).

1 Like