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).
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.
Examples
Example 1 – Before
Example 1 – After
Example 2 – Fortran 66
Example 2 – Fortran 77
Example 3 – Before
Example 3 – After
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
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
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
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
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
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 |
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 |