SPAG Version 8 – Re-engineering Fortran Source Code

Re-Structuring Legacy Code – No more GOTOs!

SPAG, the plusFORT restructuring tool, can unscramble ancient spaghetti Fortran code, and convert it to modern structured Fortran 95.  It does this by reorganising code using a number of structured templates:

  • block IFs
  • named DO loops with CYCLE and EXIT
  • replication of individual statements
  • Internal subprograms (new in Version 8)
  • Dispatch loop (new in Version 8)

Version 8, with two new templates, can now eliminate all GOTOs from even the most convoluted legacy code.   

SPAG does not change the meaning of a program, or even the order in which statements are executed; it does change the way the program logic is written down, making it much easier to understand and maintain. Blocks of code are reordered so that logically related sections are physically close, and jumps in control flow are minimised. SPAG may also replicate small code fragments where this improves the re-structured code.

Upgrade Declarations

  • SPAG allows you to switch programs to explicit typing by adding declarations for implicitly defined variables, and adding IMPLICIT NONE. Explicit typing allows your compiler to detect errors which might otherwise remain undetected for years.  This option leaves existing declarations unchanged (apart from re-formatting)
  • Alternatively, SPAG can rewrite the entire declaration section from scratch using Fortran 95 syntax. In this mode, SPAG can:
    •  Include new declarations for implicitly typed variables, and add an IMPLICIT NONE statement.
    • Add INTENT qualifiers to dummy arguments based on how the argument is used.
    • Convert non-standard REAL*8, INTEGER*1 and similar declarations to standard Fortran using KIND definitions in the standard ISO_FORTRAN_ENV module.

Modularization

SPAG can:

  • Convert INCLUDE files and COMMON blocks to MODULEs, and insert USE statements as required.
  • Create MODULEs containing explicit interfaces for all subprograms and insert USE statements as required.
  • SPAG generates symbol tables  which are used by GXCHK to examine the large scale calling structure and data usage within a program.  GXCHK produces reports showing how subprograms and data can be moved into higher level subprograms or MODULEs (see below).

Internalization Report

Cluster Analysis

Code Re-formatting

SPAG contains a powerful code beautifier, with dozens of options controlling spacing, case, labels, indentation, use of CONTINUE etc. You can use SPAG to switch back and forth between the F77 and F95 source forms. But SPAG goes much further:

  • Convert back and forth between old and new source forms, or write output in a style which is compatible with both.
  • SPAG identifies, and optionally removes dead code (statements which could never be executed) and clutter (variables or PARAMETERs which are declared but never used).
  • SPAG provides a simple and safe method for systematically changing the names of symbols within a program.
  • SPAG allows you to specify how upper and lower case should be used to distinguish different types of symbol. For example, local variables may be lower case, PARAMETERs upper case, dummy arguments capitalised etc. (e.g. local , PARAM , Dummyarg, COMmonvar).
  • SPAG can convert between old and new style relational operators.
  • User may insert !-ANCHOR and !-ASIS directives in source code to guide SPAG restructuring and formatting.

There are over 100 configuration options which allow you to customise SPAG output to local conventions and requirements.

Top


Examples

Example 1 – Before
Example 1 – After

Example 2 – Fortran 66
Example 2 – Fortran 77

Example 3 – Before
Example 3 – After

Top


Example 1 – Original Spaghetti Code

      IBON=0
      IF(KON)35,19,35
   19 IF(NSQ-56)24,22,24
   22 IF(LSQ-46)5,28,5
   24 IF(NSQ-55)29,27,29
   27 IF(LSQ-45)5,28,5
   28 IBON=2
      GO TO 5
   29 IF(LSQ-32)30,31,30
   30 IF(LSQ-39)39,31,39
   31 IBON=-5
      GO TO 5
   39 IF(LSQ-35)52,51,52
   52 IF(LSQ-36)5,51,5
   51 IBON=10
      GO TO 5
   35 IF(MARK(NMOVE))36,37,37
   36 IBON=-5
      GO TO 5
   37 IBON=5
    5 end

Top

 


Example 1 – After Processing by SPAG

      ibon = 0
      IF ( kon.NE.0 ) THEN
         IF ( mark(nmove).LT.0 ) THEN
            ibon = -5
         ELSE
            ibon = 5
         ENDIF
      ELSEIF ( nsq.NE.56 ) THEN
         IF ( nsq.NE.55 ) THEN
            IF ( lsq.EQ.32 ) THEN
               ibon = -5
            ELSEIF ( lsq.EQ.39 ) THEN
               ibon = -5
            ELSEIF ( lsq.EQ.35 ) THEN
               ibon = 10
            ELSEIF ( lsq.EQ.36 ) THEN
               ibon = 10
            ENDIF
         ELSEIF ( lsq.EQ.45 ) THEN
            ibon = 2
         ENDIF
      ELSEIF ( lsq.EQ.46 ) THEN
         ibon = 2
      ENDIF
      END

Top


Example 2 – Original Fortran 66.

This subroutine picks off digits from an integer and branches depending on their value.

      SUBROUTINE OBACT(TODO)
      INTEGER TODO,DONE,IP,BASE
      COMMON /EG1/N,L,DONE
      PARAMETER (BASE=10)
   13 IF(TODO.EQ.0) GO TO 12
      I=MOD(TODO,BASE)
      TODO=TODO/BASE
      GO TO(62,42,43,62,404,45,62,62,62),I
      GO TO 13
   42 CALL COPY
      GO TO 127
   43 CALL MOVE
      GO TO 144
  404 N=-N
   44 CALL DELETE
      GO TO 127
   45 CALL PRINT
      GO TO 144
   62 CALL BADACT(I)
      GO TO 12
  127 L=L+N
  144 DONE=DONE+1
      CALL RESYNC
      GO TO 13
   12 RETURN
      END

Top


Example 2 – Fortran 77 Version.

In addition to restructuring, SPAG has renamed some variables, removed the unused variable IP, inserted declarations, and used upper and lower case to destinguish different types of variable.

      SUBROUTINE OBACT(Todo)
      IMPLICIT NONE
C*** Start of declarations inserted by SPAG
      INTEGER act , LENgth , NCHar
C*** End of declarations inserted by SPAG
      INTEGER Todo , DONe , BASE
      COMMON /EG1   / NCHar , LENgth , DONe
      PARAMETER (BASE=10)
 100  IF ( Todo.NE.0 ) THEN
         act = MOD(Todo,BASE)
         Todo = Todo/BASE
         IF ( act.EQ.1 .OR. act.EQ.4 .OR.
     &        act.EQ.7 .OR. act.EQ.8 .OR.
     &        act.EQ.9 ) THEN
            CALL BADACT(act)
            GOTO 200
         ELSEIF ( act.EQ.2 ) THEN
            CALL COPY
            LENgth = LENgth + NCHar
         ELSEIF ( act.EQ.3 ) THEN
            CALL MOVE
         ELSEIF ( act.EQ.5 ) THEN
            NCHar = -NCHar
            CALL DELETE
            LENgth = LENgth + NCHar
         ELSEIF ( act.EQ.6 ) THEN
            CALL PRINT
         ELSE
            GOTO 100
         ENDIF
         DONe = DONe + 1
         CALL RESYNC
         GOTO 100
      ENDIF
 200  RETURN
      END

Top


Example 2 – Fortran 95.

SPAG has used DO WHILE, SELECT CASE, EXIT and CYCLE. No GOTOs or labels remain. The COMMON block has been replaced by a MODULE, and all declarations rewritten using Fortran 95 syntax.

      SUBROUTINE OBACT(Todo)
      USE C_EG1
      IMPLICIT NONE
!
!*** Start of declarations rewritten by SPAG
!
! PARAMETER definitions
!
      INTEGER , PARAMETER :: BASE = 10
!
! Dummy arguments
!
      INTEGER,INTENT(INOUT) :: Todo
!
! Local variables
!
      INTEGER :: act
!
!*** End of declarations rewritten by SPAG
!
      DO WHILE ( Todo/=0 )
         act = MOD(Todo,BASE)
         Todo = Todo/BASE
         SELECT CASE (act)
         CASE (1,4,7,8,9)
            CALL BADACT(act)
            EXIT
         CASE (2)
            CALL COPY
            LENgth = LENgth + NCHar
         CASE (3)
            CALL MOVE
         CASE (5)
            NCHar = -NCHar
            CALL DELETE
            LENgth = LENgth + NCHar
         CASE (6)
            CALL PRINT
         CASE DEFAULT
            CYCLE
         END SELECT
         DONe = DONe + 1
         CALL RESYNC
      ENDDO
      END SUBROUTINE OBACT

 

Example 3 – Dispatch Loops and Internal SUBROUTINES

Top

 

Before Restructuring
      FUNCTION IUNITS(JDIF,KDIF,JU,KU,IP)
      INTEGER IP,IUNITS,JDIF,JU,KDIF,KU
      IUNITS=1
      IF(JDIF)20,30,40
20    JU=-1
      IF(KDIF)50,60,70
30    JU=0
      KU=KDIF/IABS(KDIF)
      IP=4
      RETURN
40    JU=1
      IF(KDIF)70,60,50
50    IF(JDIF-KDIF)100,51,100
51    KU=JU
      IP=3
      RETURN
60    KU=0
      IP=4
      RETURN
70    IF(JDIF+KDIF)100,71,100
71     KU=-JU
      IP=3
      RETURN
100   IUNITS=0
      RETURN
    END

Top

 

spag iunits.for 31=1 (Dispatch Loops) spag iunits.for 31=2 (Internal Subroutines)
   FUNCTION iunits(Jdif,Kdif,Ju,Ku,Ip)
      IMPLICIT NONE
      INTEGER Ip , iunits , Jdif , Ju , Kdif , Ku
      INTEGER :: spag_nextblock_1
      spag_nextblock_1 = 1
      SPAG_DispatchLoop_1: DO
         SELECT CASE (spag_nextblock_1)
         CASE (1)
            iunits = 1
            IF ( Jdif<0 ) THEN
               Ju = -1
               IF ( Kdif<0 ) THEN
               ELSEIF ( Kdif==0 ) THEN
                  spag_nextblock_1 = 2
                  CYCLE SPAG_DispatchLoop_1
               ELSE
                  spag_nextblock_1 = 3
                  CYCLE SPAG_DispatchLoop_1
               ENDIF
            ELSEIF ( Jdif==0 ) THEN
               Ju = 0
               Ku = Kdif/iabs(Kdif)
               Ip = 4
               RETURN
            ELSE
               Ju = 1
               IF ( Kdif<0 ) THEN
                  spag_nextblock_1 = 3
                  CYCLE SPAG_DispatchLoop_1
               ENDIF
               IF ( Kdif==0 ) THEN
                  spag_nextblock_1 = 2
                  CYCLE SPAG_DispatchLoop_1
               ENDIF
            ENDIF
            IF ( Jdif/=Kdif ) THEN
               iunits = 0
               RETURN
            ELSE
               Ku = Ju
               Ip = 3
               RETURN
            ENDIF
         CASE (2)
            Ku = 0
            Ip = 4
            RETURN
         CASE (3)
            IF ( Jdif+Kdif/=0 ) THEN
               iunits = 0
            ELSE
               Ku = -Ju
               Ip = 3
               RETURN
            ENDIF
            EXIT SPAG_DispatchLoop_1
         END SELECT
      ENDDO SPAG_DispatchLoop_1
   END FUNCTION iunits
   FUNCTION iunits(Jdif,Kdif,Ju,Ku,Ip)
      IMPLICIT NONE
      INTEGER Ip , iunits , Jdif , Ju , Kdif , Ku
      iunits = 1
      IF ( Jdif<0 ) THEN
         Ju = -1
         IF ( Kdif<0 ) THEN
         ELSEIF ( Kdif==0 ) THEN
            CALL spag_block_1
            RETURN
         ELSE
            CALL spag_block_2
            RETURN
         ENDIF
      ELSEIF ( Jdif==0 ) THEN
         Ju = 0
         Ku = Kdif/iabs(Kdif)
         Ip = 4
         RETURN
      ELSE
         Ju = 1
         IF ( Kdif<0 ) THEN
            CALL spag_block_2
            RETURN
         ENDIF
         IF ( Kdif==0 ) THEN
            CALL spag_block_1
            RETURN
         ENDIF
      ENDIF
      IF ( Jdif/=Kdif ) THEN
         iunits = 0
         RETURN
      ELSE
         Ku = Ju
         Ip = 3
         RETURN
      ENDIF
   CONTAINS
      SUBROUTINE spag_block_1
         Ku = 0
         Ip = 4
         RETURN
      END SUBROUTINE spag_block_1
      SUBROUTINE spag_block_2
         IF ( Jdif+Kdif/=0 ) THEN
            iunits = 0
         ELSE
            Ku = -Ju
            Ip = 3
            RETURN
         ENDIF
      END SUBROUTINE spag_block_2
   END FUNCTION iunits

Top