41 min read

> *"Any fool can write code that a computer can understand. Good programmers write code that humans can

Prerequisites

  • 2
  • 3
  • 4
  • 5
  • 6
  • 8

Learning Objectives

  • Read fixed-form FORTRAN 77 source correctly, accounting for the column rules (labels in 1–5, continuation in 6, comment `C` in 1) that free-form Fortran abandoned.
  • Explain what a `COMMON` block and `BLOCK DATA` unit do, why they are a global untyped memory overlay, and how a mismatched declaration silently corrupts data.
  • Recognize `EQUIVALENCE` as deliberate memory aliasing, state the concrete hazards it creates, and name its modern replacements.
  • Decode the legacy control-flow zoo — `GO TO`, computed `GOTO`, arithmetic `IF`, statement functions, and `ENTRY` — and map each onto the structured construct that replaced it.
  • Interpret implicit typing (the `I`–`N` integer rule), `DATA` initialization, and Hollerith constants when you meet them in the wild.
  • Read an unfamiliar ~150-line FORTRAN 77 program — the `PLATE` Jacobi relaxation kernel — without panic, reconstruct what it computes, and predict its output.

Chapter 17: Reading FORTRAN 77 — Fixed-Form, COMMON Blocks, EQUIVALENCE, and the Archaeology of Old Code

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler, Refactoring: Improving the Design of Existing Code (1999)

Overview

Sooner or later a file with a .f extension lands on your desk. It was written before you were born, it computes something the organization depends on and cannot afford to get wrong, and — this is the part that makes people panic — it looks nothing like the Fortran you have learned so far. Its lines begin in the seventh column. Its variables are named T, N, IJK, and are declared nowhere. Whole blocks of memory are shared between routines by a mechanism called COMMON that no one has explained to you, two different arrays occupy the same bytes on purpose, and the flow of control is stitched together with numbered GO TOs that leap up and down the page. You could be forgiven for concluding that this is a different language.

It is not. It is your language, forty years younger — FORTRAN 77, the classic dialect that most of the world's validated scientific code is still written in. This chapter opens Part IV, the part of the book that turns you from someone who can write modern Fortran into someone who can also read the old kind and modernize it — a skill that, as the Part IV introduction argues, will earn your keep in almost any organization sitting on a mountain of legacy code. And the first move in modernizing old code is never to change it. It is to read it: to reconstruct, calmly and systematically, what it does and why, before you touch a single line. That reading is what this chapter teaches.

We approach FORTRAN 77 the way an archaeologist approaches a dig — not with contempt for the people who came before, but with curiosity about the constraints they worked under and respect for what they built. Every strange feature you are about to meet was a reasonable answer to a real 1970s problem: no dynamic memory, punch cards with eighty columns, compilers that fit in a few kilobytes. Understanding why the old code looks the way it does is most of what it takes to read it fluently.

In this chapter, you will learn to:

  • Read fixed-form source correctly — the column rules for labels, continuation, and comments that govern every line of a .f file, and that trip up every newcomer exactly once.
  • Understand COMMON and BLOCK DATA: how FORTRAN 77 shared state between routines by overlaying memory, and why the compiler could not protect you from a mismatch.
  • See EQUIVALENCE for what it is — two names deliberately pointing at the same bytes — and know the hazards that make it the single most dangerous construct in the language.
  • Decode the legacy control-flow zoo: plain GO TO, the computed GOTO, the arithmetic IF, the statement function, and ENTRY, each paired with the modern construct that retired it.
  • Handle implicit typing (the rule that a name's first letter sets its type), DATA statements, and the fossil that is the Hollerith constant.
  • Put it all together by reading a real ~150-line FORTRAN 77 program end to end — the PLATE heat-plate relaxation kernel that you will modernize, step by step, in the next two chapters.

Learning Paths

How to read this chapter by track. - 🔧 Legacy ("I inherited old code") — this entire chapter is written for you; read every section closely, and treat §17.6 and the Project Checkpoint as the reason you bought the book. This is your home turf. - 📖 Standard — read straight through. Even if you never write a COMMON block, you will read one, and knowing the whole language — including its past — is what "knowing Fortran" means. - 🔬 Scientist ("my Python is too slow") — you can defer this part until you actually meet a .f file, but skim §17.2 and §17.6 now: the numerical kernel you will one day inherit looks exactly like the PLATE code toured here. - ⚡ HPC ("I need parallel code") — the tuned kernels at the heart of many HPC codes are legacy FORTRAN 77. Read §17.1–17.3 so the fixed-form, COMMON-heavy sources you will profile in Part VII hold no surprises.

Throughout, the recurring theme of this part sits underneath everything: legacy code is not a burden, it is an inheritance. The strangeness is not incompetence; it is history. Read it as history and it becomes legible.


17.1 Fixed-Form Source: Why Column Seven Matters

The first shock of an old .f file is visual: the code does not start at the left margin. It starts one tab-stop in, and if you retype it flush left it stops compiling. This is not a style convention you may ignore. It is fixed-form source, the layout FORTRAN inherited from the punch card, and the columns carry meaning.

Definition (fixed-form source). The source layout of FORTRAN 77 and earlier, in which each line is divided into fixed character columns with reserved meanings: columns 1–5 hold an optional statement label (a number), column 6 is the continuation marker, columns 7–72 hold the statement itself, and columns 73–80 are ignored (historically the punch card's sequence number). A C or * in column 1 makes the entire line a comment. This is the counterpart to the free-form source introduced with Fortran 90 (and used everywhere else in this book), where statements may begin in any column. A .f (or .for) file is fixed-form; a .f90 file is free-form.

Picture the eighty columns of a punch card, because that is literally where these rules come from:

Column:  1         2         3    4       5    6      7 ... 72        73 ... 80
         |----- label -----|     |   continuation |----- statement -----|  |sequence|
         1 2 3 4 5                6                7 ...................72   73......80
C   a C or * in column 1 makes the whole line a comment
      X = 1.0            <- statement starts in column 7 (six leading blanks)
100   Y = X + 1.0        <- label 100 sits in columns 1-5; statement in 7+
     &   + 2.0           <- nonblank in column 6 continues the PREVIOUS line

Read that ruler carefully, because four rules fall out of it and they explain almost every "why won't this compile" moment you will have with old code.

Rule 1 — statements live in columns 7 through 72. The six leading blanks you see on every ordinary line are not decoration; columns 1–6 are reserved, so the statement body must begin at column 7. That is the "one tab-stop in" you noticed.

Rule 2 — a label goes in columns 1–5. A statement label is a number (1 to 99999) that names a line so that a GO TO, a DO, or a FORMAT can refer to it. It sits left of the statement, in the label field. Labels need not be ordered or contiguous; they are just names that happen to be numbers.

Rule 3 — column 6 is the continuation column. If a statement is too long for one line (remember, column 72 is the wall), you break it and put any nonblank character in column 6 of the next line to say "this continues the statement above." The choice of character is free — old codes use &, +, ., or the digits 1, 2, 3 to number a long continuation. A blank or a zero in column 6 means "this is a new statement," which is the normal case.

Rule 4 — a C or * in column 1 is a comment. There is no ! end-of-line comment in strict FORTRAN 77 (that came with Fortran 90); a comment is a whole line flagged in column 1. You will see rivers of C-comments in old code, often in a banner style.

Here is a small, complete, authentic fixed-form program. Note the leading blanks, the label 10, and that everything stays inside column 72:

C     AREA -- circle area, in genuine fixed-form FORTRAN 77.
      PROGRAM AREA
      PI = 3.14159
      R  = 2.0
      A  = PI * R * R
      WRITE (*,10) A
10    FORMAT (1X, 'AREA = ', F8.4)
      STOP
      END
$ gfortran -std=legacy area.f -o area && ./area
 AREA =  12.5664

The -std=legacy flag is how you tell gfortran "this is old code; accept its obsolete features and read it as fixed-form." (The compiler also switches to fixed-form automatically from the .f extension.) We hand-check the output: $\pi r^2 = 3.14159 \times 4 = 12.56636$, which F8.4 prints as 12.5664, right-justified in eight columns, after the one leading space from 1X. Nothing about the computation is strange; only the layout is.

📜 From History. Why columns at all? Because FORTRAN was designed to be punched onto cards, one statement per card, and the card reader delivered each card as eighty fixed columns. Columns 73–80 were left for a sequence number so that if you dropped your card deck on the floor, you could sort it back into order. That is not a metaphor — it is why column 72 is the end of the statement to this day. When you meet a bizarre constraint in old code, ask what physical object it was shaped by; the answer is usually illuminating and occasionally poignant.

🔧 Modern vs Legacy: The same program, free-form, is what you have been writing since Chapter 2:

fortran ! Modern, free-form: no column rules, no labels, an end-of-line comment with ! program area implicit none real :: pi = 3.14159, r = 2.0 print '(a, f8.4)', 'area = ', pi * r * r end program area

The statement starts where you like, the comment rides on the same line with !, and there is no label and no FORMAT line to jump to. Free-form did not add power — the two programs compute the same thing — it removed a set of tripwires. Modern Fortran is a modern language, and this is the first and most visible piece of evidence.

⚠️ Common Pitfall: the invisible column-6 character. The nastiest fixed-form bug is a stray character that lands in column 6 by accident (a misplaced tab, a comment marker one column off), turning a line you meant as a fresh statement into a continuation of the line above — or vice versa. If an old file gives a baffling syntax error, count columns with your editor's ruler before you suspect anything cleverer. Nine times in ten it is a column, not a concept.

🔄 Check Your Understanding

  1. In fixed-form source, what is in columns 1–5, what is in column 6, and where does the statement body begin?
  2. A line reads * DIFFUSION TERM with the * in column 6 (not column 1). Is it a comment? What is it?
  3. Why can a strict FORTRAN 77 statement never extend past column 72?
Answers 1. Columns 1–5 hold an optional statement *label* (a number); column 6 is the *continuation* marker (nonblank = "continue the previous line"); the statement body occupies columns 7–72. 2. It is **not** a comment — a comment needs the `C` or `*` in **column 1**. With the `*` in column 6, this line is a *continuation* of the statement above it, and `DIFFUSION TERM` will be spliced onto that statement, almost certainly causing a syntax error. This is exactly the column-6 trap. 3. Because the source came from eighty-column punch cards, with columns 73–80 reserved for a sequence number; the standard fixed the statement field at columns 7–72, and anything past 72 is ignored.

17.2 COMMON Blocks and BLOCK DATA: Global State by Memory Overlay

FORTRAN 77 had no modules (Chapter 8), no derived types, and — crucially — no way for two subroutines to share a variable by naming it. If STEP and OUTPUT both needed to see the same temperature array, the language offered exactly one tool: the COMMON block, which let them share the same memory rather than the same name.

Definition (COMMON). A COMMON block is a named (or unnamed "blank") region of memory that several program units may each declare and thereby share. Written COMMON /name/ list-of-variables, it establishes a run of storage; every program unit that declares a COMMON block of the same name is given access to that same storage, associating its listed variables with the block by position, not by name. It is FORTRAN 77's mechanism for global, shared state — the ancestor of the module variable, and far more dangerous.

The phrase to burn in is by position, not by name. When two routines both say COMMON /GRID/ ..., the compiler lays their variables over the same bytes in the order listed. The names need not match. The types need not match. Nothing is checked.

C     Routine A's view of the shared block.
      SUBROUTINE STEP
      COMMON /STATE/ TEMP, FLUX, NCELL
      REAL TEMP, FLUX
      INTEGER NCELL
C     ... uses TEMP, FLUX, NCELL ...
      END

C     Routine B's view of the SAME bytes -- different names, and no one checks.
      SUBROUTINE OUTPUT
      COMMON /STATE/ A, B, M
      REAL A, B
      INTEGER M
C     ... A is TEMP, B is FLUX, M is NCELL -- if you got the order right.
      END

Both routines refer to the same three words of memory. In STEP the first word is called TEMP; in OUTPUT the very same word is called A. As long as every routine lists the block in the same order with the same types, this works — it is how every large FORTRAN 77 program shared its data. But the safety net is entirely in the programmer's head.

🐛 Find the Bug. Two routines declare the same COMMON block. What goes wrong, and why does the compiler stay silent?

```fortran SUBROUTINE SOLVE COMMON /BLK/ X, Y, N ! REAL, REAL, INTEGER ... END

  SUBROUTINE PRINTIT
  COMMON /BLK/ X, N, Y          ! REAL, INTEGER, REAL  <-- reordered!
  ...
  END

```

Diagnosis The two declarations disagree about the order of the block. In SOLVE the second word is the real Y and the third is the integer N; in PRINTIT the second word is read as the integer N and the third as the real Y. So PRINTIT reads the bits of a real number and interprets them as an integer, and vice versa — producing nonsense, with no crash and no warning. FORTRAN 77 associates COMMON by position, and the compiler compiles each routine separately, so it never sees the two declarations side by side to notice they conflict. This is not a contrived bug; it is the single most common way large COMMON-based codes were silently corrupted, and it is why keeping the declarations in sync spawned the INCLUDE habit below.

Because the same block had to be declared identically in file after file, programmers kept the declaration in one file and pasted it in everywhere with an INCLUDE line (INCLUDE 'state.inc'), a raw textual insertion. The result was FORTRAN 77's actual mechanism for building large programs: one global pool of untyped memory, held together by copied-and-pasted text.

BLOCK DATA: initializing the global pool

There is one thing a plain COMMON declaration cannot do: give its variables initial values. A DATA statement (§17.5) may not initialize a COMMON variable inside an ordinary subroutine — the standard forbids it, because it would be ambiguous which routine "owns" the initialization. FORTRAN 77 solved this with a dedicated, bodiless program unit whose only job is to hold those initial values.

C     BLOCK DATA: the only place a named COMMON block may be initialized.
      BLOCK DATA SETCON
      COMMON /PARAMS/ TOL, MAXIT
      DATA TOL /1.0E-5/, MAXIT /1000/
      END

A BLOCK DATA unit has no PROGRAM, SUBROUTINE, or FUNCTION header and contains no executable statements — only COMMON, type, and DATA declarations. It runs no code; it simply tells the linker "stamp these initial values into the /PARAMS/ block before the program starts." You will see one, off in a corner of an old code, doing exactly this and nothing else.

🔗 Connection. You already met the cure for all of this in Chapter 8. The module replaces the COMMON block with one typed, compiler-checked declaration that every unit uses by name; the module variable replaces the shared word of memory; a module parameter or an initializer replaces the BLOCK DATA unit. Where COMMON associates by position and checks nothing, use associates by name and checks everything. Converting COMMON to a module is step three of the eight-step modernization recipe in Chapter 18, and it is the step that stops a code from being able to corrupt itself.

🔧 Modern vs Legacy: The /PARAMS/ block and its BLOCK DATA, done the modern way, collapse into a handful of lines with no overlay and no separate initialization unit:

fortran ! Modern: a module owns the parameters, typed and initialized in one place. module params use, intrinsic :: iso_fortran_env, only: dp => real64 implicit none real(dp), parameter :: tol = 1.0e-5_dp integer, parameter :: maxit = 1000 end module params

There is no BLOCK DATA, because the initialization lives with the declaration; there is no positional association, because use params, only: tol, maxit imports the entities by name; and if a routine misuses maxit as a real, the compiler says so. One authoritative definition, checked everywhere it is used.

🚪 Threshold Concept — COMMON is one global pool of untyped memory. The mental model that unlocks every old code is this: in FORTRAN 77, a large program is a single shared expanse of bytes that any routine may reach into and reinterpret according to whatever it happened to declare, with no compiler anywhere able to check that two routines agree about what lives there. Modern Fortran replaced that with components that each own their state behind a checked interface. Once you see a COMMON block as a window onto shared raw memory — not as a tidy list of variables — the behavior of legacy code stops being mysterious. Every strange aliasing bug, every "how did that variable change?", traces back to this one design.


17.3 EQUIVALENCE: Deliberate Memory Aliasing and Its Dangers

If COMMON shares memory between routines, EQUIVALENCE shares it within one — it tells the compiler that two or more variables in the same routine occupy the same storage, on purpose.

Definition (EQUIVALENCE). An EQUIVALENCE statement, written EQUIVALENCE (a, b), declares that the named variables (or array elements) share the same memory location: writing one changes the other, because they are the same bytes. It was used chiefly to save memory — a scarce resource in the 1970s — by letting arrays that were never live at the same time reuse one buffer, and to reinterpret a region of storage as a different shape or type (an array as a flat vector, say). It is FORTRAN 77's tool for deliberate aliasing — the very thing modern Fortran works hard to forbid, because forbidding it is what makes Fortran fast.

There are two flavors, and they map onto two very different intentions.

Memory reuse. Two large scratch arrays that are never needed simultaneously can be laid over the same buffer to halve the program's footprint:

C     WORK and SCRATCH share one buffer -- only legal because the code
C     never needs both alive at once. Get that wrong and you have a bug.
      DIMENSION WORK(1000), SCRATCH(1000)
      EQUIVALENCE (WORK(1), SCRATCH(1))

Reshaping and reinterpreting. An array can be aliased as a flat vector so it can be swept with a single loop, or (worse) a real can be overlaid on an integer to inspect its bits:

C     Alias the 2-D field T as a 1-D vector TFLAT for a single-loop scan.
      DIMENSION T(5,5), TFLAT(25)
      EQUIVALENCE (T(1,1), TFLAT(1))
C     Now TFLAT(K) IS T stored column-major: TFLAT(1)=T(1,1), TFLAT(2)=T(2,1)...

That second use is genuinely handy — and it depends on knowing Fortran's column-major storage order (the lesson of Chapter 5): TFLAT(1) is T(1,1), TFLAT(2) is T(2,1), and so the flat index marches down the first column, then the next. If you mis-picture the layout, the alias silently reads the wrong cells.

Why is EQUIVALENCE the most dangerous construct in the language? Three reasons, each concrete:

  • A write through one name changes the other, invisibly. Code that reads SCRATCH after some distant routine wrote WORK gets whatever WORK left behind — a data dependency with no syntactic trace. The bug lives in the absence of a connection you can see.
  • Type overlays are non-portable and treacherous. Overlaying a REAL on an INTEGER to read the bits assumes a particular size and representation; move to a machine where a REAL and an INTEGER differ in size and the alias corrupts memory.
  • It defeats the optimizer. The compiler's freedom to reorder loads and stores rests on knowing that distinct names are distinct memory. EQUIVALENCE breaks that promise by hand, so aliased code cannot be optimized as aggressively — you pay in speed for the bytes you saved.

🔧 Modern vs Legacy: Every legitimate use of EQUIVALENCE has a safe, typed modern replacement, and you will tabulate them in Chapter 19:

Legacy intent (EQUIVALENCE) Modern replacement
Reuse one buffer for two scratch arrays Two allocatable arrays — allocate and deallocate around use
Alias a 2-D array as a flat vector reshape, or an array-pointer with contiguous
Reinterpret a REAL's bits as an INTEGER the intrinsic transfer(source, mold) — explicit and typed

The transfer intrinsic deserves the spotlight: where EQUIVALENCE silently overlays bytes and hopes, transfer(x, 0) says in the code "reinterpret the bits of x as the type of 0," checked and portable. The intent that was implicit and dangerous becomes explicit and safe.

⚠️ Common Pitfall — do not "fix" EQUIVALENCE by deleting it. When you meet an EQUIVALENCE in old code, resist the urge to strip it out on sight. First find out which intent it serves — memory reuse, reshaping, or bit reinterpretation — because each maps to a different modern construct, and choosing the wrong one changes the program's behavior. Reading correctly precedes rewriting correctly; that is the whole ethic of this part.

🔄 Check Your Understanding

  1. In one sentence, what does EQUIVALENCE (A, B) assert about A and B?
  2. Given DIMENSION T(5,5), TFLAT(25) with EQUIVALENCE (T(1,1), TFLAT(1)), which element of T is TFLAT(7)? (Fortran is column-major.)
  3. Which modern intrinsic replaces the "reinterpret these bits as another type" use of EQUIVALENCE?
Answers 1. That `A` and `B` occupy the *same* memory — they are two names for one storage location, so writing one writes the other. 2. `TFLAT(7)` is `T(2,2)`. Column-major means the flat index runs down column 1 first (`TFLAT(1..5)` = `T(1,1)..T(5,1)`), then down column 2 (`TFLAT(6..10)` = `T(1,2)..T(5,2)`), so index 7 is the second element of column 2, `T(2,2)`. 3. `transfer(source, mold)` — it reinterprets the bits of `source` as the type of `mold`, explicitly and portably.

17.4 The Control-Flow Zoo: GO TO, Computed GOTO, Arithmetic IF, Statement Functions, ENTRY

Modern Fortran's block constructs — if … end if, do … end do, select case — did not exist in early FORTRAN. Control flow was built almost entirely from labels and jumps, and reading old code means recognizing the handful of jump-based idioms and mentally translating each into the structured construct that replaced it (the ones you learned in Chapter 4).

The plain GO TO. The workhorse. GO TO 100 transfers control to the statement labeled 100, anywhere in the routine. A loop was a GO TO back to the top with an IF to break out; a two-way branch was an IF guarding a GO TO around the else-part. Read a cluster of GO TOs by finding the labels they target and asking "is this jumping back (a loop) or forward (an escape or a branch)?"

C     Legacy: a convergence loop built from a label and a GO TO.
      ITER = 0
100   CONTINUE
      ITER = ITER + 1
C     ... do a sweep, compute DMAX ...
      IF (DMAX .LT. TOL) GO TO 200
      IF (ITER .LT. MAXIT) GO TO 100
200   CONTINUE

That is a do while wearing a disguise. The modern equivalent says the same thing without a single label:

! Modern: the same loop, structured. No labels, no GO TO.
iter = 0
do
  iter = iter + 1
  ! ... do a sweep, compute dmax ...
  if (dmax < tol) exit
  if (iter >= maxit) exit
end do

The computed GOTO. A multi-way branch driven by an integer index.

Definition (computed GOTO). A control statement of the form GO TO (L1, L2, L3), K that transfers control to the label in the list selected by the integer K: to L1 if K is 1, L2 if K is 2, and so on. If K is less than 1 or greater than the number of labels, control falls through to the next statement. It is FORTRAN 77's integer-indexed multi-way branch — the ancestor of select case.

C     Legacy: dispatch on an integer boundary-condition code.
      GO TO (10, 20, 30), IBC
10    CONTINUE
C        ... fixed-temperature (Dirichlet) boundary ...
      GO TO 99
20    CONTINUE
C        ... insulated (Neumann) boundary ...
      GO TO 99
30    CONTINUE
C        ... periodic boundary ...
99    CONTINUE

The modern reading is a select case, which is safer in every way — it labels its cases meaningfully, it cannot fall through by accident, and it handles the out-of-range value explicitly:

! Modern: the same dispatch, self-documenting and bounds-safe.
select case (ibc)
case (1)   ! fixed-temperature (Dirichlet)
  ! ...
case (2)   ! insulated (Neumann)
  ! ...
case (3)   ! periodic
  ! ...
case default
  error stop 'unknown boundary code'
end select

The arithmetic IF. The oldest branch of all, and the strangest to modern eyes: a three-way jump on the sign of an expression.

C     Legacy: IF (expr) neg, zero, pos -- jump on the sign of expr.
      IF (DMAX - TOL) 200, 100, 100
C     negative (DMAX<TOL) -> label 200 (converged);
C     zero or positive    -> label 100 (keep iterating).

IF (e) L1, L2, L3 jumps to L1 when e is negative, L2 when e is zero, and L3 when e is positive. It reads as a puzzle until you decode it as "branch on sign(e)," and then it is just a clumsy select case on three outcomes. The arithmetic IF was declared obsolescent by the standard long ago; modern code writes an ordinary if (dmax < tol) then … else … end if.

The statement function. A one-line function defined inline, in the declaration section of a routine.

Definition (statement function). A statement function is a single-statement function definition written among a routine's declarations, of the form name(args) = expression. It is local to the routine that defines it, its arguments are dummy names, and it is expanded like a formula wherever it is called. It was the lightweight way to name a small recurring expression — the ancestor of the modern internal procedure — and it is now obsolescent.

C     Legacy: a statement function for the 4-neighbour Jacobi average
C     (this is the exact one in the PLATE kernel of Section 17.6).
      AVG(TL,TR,TB,TA) = 0.25D0 * (TL + TR + TB + TA)
C     ... later, in the sweep ...
      TNEW(I,J) = AVG(T(I-1,J), T(I+1,J), T(I,J-1), T(I,J+1))

A statement function must sit after the declarations and before the first executable statement, and it can be only one expression long. When you meet one, read it as a named formula. The modern replacement is an internal procedure (a containsed function, from Chapter 6) — which may span many lines, carries intent and a checked interface, and can be pure:

! Modern: the statement function promoted to a pure internal function.
contains
  pure function avg(w, e, s, n) result(m)
    real(dp), intent(in) :: w, e, s, n
    real(dp) :: m
    m = 0.25_dp * (w + e + s + n)
  end function avg

ENTRY. The rarest fossil, and worth recognizing precisely because it confuses people. An ENTRY statement gives a subprogram a second name and entry point, sharing the routine's body and local variables. A single SUBROUTINE might have three ENTRY points, so that CALL INIT, CALL STEP, and CALL FINISH all landed in different parts of one tangled routine that shared state through its locals.

C     Legacy: one routine, multiple entry points sharing local state.
      SUBROUTINE SOLVER
      SAVE COUNT
      COUNT = 0
      RETURN
      ENTRY BUMP
      COUNT = COUNT + 1
      RETURN
      END

The modern reading is almost always "this should have been a module with several procedures sharing a private module variable" — exactly the step_counter pattern from Chapter 8. ENTRY is obsolescent; you will rarely write it, but you must be able to recognize it, because a routine with multiple entry points does not behave like the single-entry procedures you are used to.

🐍 Python Comparison. Every construct in this section is a jump — a goto in spirit — and Python, tellingly, has no goto at all, on the same principle Fortran itself adopted in 1990: structured constructs (if, for, while, match) express every sane control pattern more clearly than labels and jumps do. Reading old FORTRAN is partly an exercise in recovering the structured intent that the jumps obscure. When you find yourself drawing arrows between GO TOs and their labels to see the loops, you are reverse-engineering the do loop the author could not yet write.

🔗 Connection. Chapter 4 built the modern replacements for every construct here, and its history boxes pointed forward to this moment: the do … exit that replaces the GO TO loop, the select case that replaces the computed GOTO and the arithmetic IF. Reading legacy control flow is mostly the inverse map — recognizing the old idiom and naming the new construct it becomes.

🔄 Check Your Understanding

  1. GO TO (30, 10, 20), K — to which label does control go when K is 2? What happens when K is 5?
  2. Rewrite the arithmetic IF statement IF (X - Y) 5, 6, 6 as a modern if construct.
  3. Where in a routine must a statement function be defined, and what modern construct replaces it?
Answers 1. When `K` is 2 it goes to the *second* label in the list, `10`. When `K` is 5 — out of range for a three-label list — control *falls through* to the statement after the computed `GOTO`. 2. `if (x < y) then` → (the `5` branch) `else` → (the combined `6` branch, since both zero and positive go to `6`) `end if`. In words: one branch for `x < y`, the other for `x >= y`. 3. A statement function must appear after the declaration statements and before the first executable statement of its routine. It is replaced by an **internal procedure** — a function defined after `contains`, with `intent` on its arguments and a checked interface.

17.5 Implicit Typing, DATA, and the Hollerith Fossil

Look again at the AREA program in §17.1 and you will notice something that would fail to compile in every other chapter of this book: not one variable is declared. PI, R, and A appear from nowhere and are real numbers. This is implicit typing, and it is the default you have been shielded from by implicit none since Chapter 2.

Definition (implicit typing). The FORTRAN convention by which an undeclared variable's type is determined by the first letter of its name: names beginning with I, J, K, L, M, or N are INTEGER; names beginning with any other letter (AH, OZ) are REAL. The rule is often summarized "I through N are integers" — a mnemonic reinforced by the first two letters of INteger. A variable simply comes into existence, typed by its initial, the first time it is used. Modern Fortran disables this with implicit none (Chapter 2), forcing an explicit declaration for every variable.

The rule is not arbitrary — it matches mathematical habit, where i, j, k, m, n are the classic loop counters and array sizes, and everything else is a real quantity. In a short, careful routine it even reads cleanly. But it is a trap with two sharp edges.

The first edge is the silent typo. If you write TEMP in one place and TEPM in another, implicit typing does not complain that TEPM is undeclared — it creates a brand-new real variable called TEPM, initialized to garbage, and your temperature quietly fails to propagate. There is no error, only a wrong answer. This is the single strongest argument for implicit none, and it is why the modern language made the shield available.

The second edge is accidental integer arithmetic. Because IN names are integers, a quantity you meant as a real — a step count reused as a divisor, say — can be silently integer, and integer division truncates (Chapter 3). A name like N holding what should be a fraction gives 1/N = 0, not 0.01, with no warning.

C     Legacy hazard: with implicit typing, N is INTEGER (starts with N),
C     so 1/N is INTEGER DIVISION -> 0, not 0.01. Nothing warns you.
      N = 100
      DT = 1.0 / N
C     DT is 1.0/100 done as real (1.0 is real), so this one is OK -- but
C     STEP = 1 / N   would be integer 1/100 = 0. The danger is real and quiet.

⚠️ Common Pitfall — the implicit SAVE, hiding nearby. Implicit typing has a cousin that catches people even in modern code. In a FORTRAN 77 routine, a local variable given an initial value in its declaration was implicitly SAVEd — it kept its value between calls. Old code leaned on this; a routine would initialize a counter "once" and rely on it persisting. When you read (or modernize) such a routine, that persistence is part of its behavior, and dropping it silently changes the result. We dissect the modern version of this trap — the accidental implicit save inside a procedure — in Chapter 13; here, simply flag every "initialized once" local as stateful.

DATA statements. FORTRAN 77 initialized variables not in the declaration (that came with Fortran 90) but with a separate DATA statement, which pairs a list of variables with a list of constants.

C     DATA: initialize variables with a parallel list of constant values.
      DATA TTOP, TBOT /0.0, 100.0/
      DATA (EDGE(I), I=1,3) /3*0.0/       ! implied-DO; "3*0.0" = three 0.0's

Two idioms in that snippet repay knowing. The values between slashes correspond position-by-position to the variables before them. The 3*0.0 is a repeat count — shorthand for 0.0, 0.0, 0.0 — and the (EDGE(I), I=1,3) is an implied-DO, a compact loop that lists EDGE(1), EDGE(2), EDGE(3). Modern Fortran folds all of this into the declaration: real :: ttop = 0.0, tbot = 100.0 and real :: edge(3) = 0.0.

Hollerith constants. Finally, the oldest fossil you will encounter — the way FORTRAN represented text before it had a character type at all (the CHARACTER type arrived only with FORTRAN 77 itself, so pre-77 code, and much early-77 code, predates it).

A Hollerith constant is written nHtext — a count n, the letter H, then exactly n characters — naming a string of text by its length. 5HHELLO is the five characters HELLO. Named for Herman Hollerith, whose punch-card tabulator seeded IBM, it stored text in numeric or INTEGER variables and appeared mainly in FORMAT statements and DATA lists.

C     Legacy: a FORMAT using a Hollerith constant for its label text.
10    FORMAT (1X, 13HTEMPERATURE =, F8.2)
C     Modern equivalent uses a quoted character string:
C     print '(a, f8.2)', 'temperature =', t

You will almost never need to write Hollerith, but you must be able to read it: when you see 13H followed by exactly thirteen characters wedged into a FORMAT, that is a text label, nothing more. Miscount the characters and the following comma vanishes into the string — a classic way old FORMATs broke.

🔧 Modern vs Legacy: The whole of this section, in one comparison. Legacy: undeclared variables typed by their first letter, initialized by a distant DATA statement, with text smuggled through Hollerith counts. Modern: implicit none, every variable declared with an explicit type and kind, initialized where it is declared, text in quoted character strings. The modern version is longer by a few declarations and safer by an entire category of bug. That trade — a little more typing for a lot more checking — is the through-line of modernization, and the reason modern Fortran is a modern language.


17.6 Reading Old Code Without Panicking: A Guided Tour of PLATE

Everything so far has been vocabulary. Now we read a whole program — the way you will read the real thing — using a method you can apply to any legacy source. Meet PLATE, a compact FORTRAN 77 kernel (about a hundred lines, in four program units) that solves for the steady-state temperature of a square metal plate. It is the code you will modernize in Chapter 18 and tabulate in Chapter 19; read it well here and those chapters become edits, not excavations.

First, the physics in one breath, because reading numerical code is far easier when you know what it is trying to compute. A time-dependent temperature field obeys the heat equation $\frac{\partial u}{\partial t} = \alpha \nabla^2 u$ (the equation behind this book's whole project). When the plate reaches steady state, nothing changes with time, so $\frac{\partial u}{\partial t} = 0$ and the equation collapses to Laplace's equation, $\nabla^2 u = 0$. On a grid, the five-point stencil turns that into a beautifully simple rule: each interior temperature equals the average of its four neighbours,

$$ T_{i,j} = \tfrac{1}{4}\left( T_{i-1,j} + T_{i+1,j} + T_{i,j-1} + T_{i,j+1} \right). $$

Jacobi relaxation enforces that rule by brute repetition: replace every interior point with the average of its neighbours from the previous sweep, over and over, until the field stops changing. (The full finite-difference story — and why "from the previous sweep" matters — is Chapter 24's; here we only need to recognize the pattern in the code.)

Here is the whole program — about a hundred lines across four program units. Do not read it line by line yet; first just look at its shape. (This is the exact code you will modernize in Chapter 18; it is in this chapter's code/plate-legacy.f.)

C=====================================================================
C  PLATE -- steady-state temperature of a square plate.
C           2-D Laplace equation, solved by Jacobi relaxation.
C           FORTRAN 77, fixed-form.  *** LEGACY CODE -- do not imitate. ***
C           Compile:  gfortran -std=legacy plate-legacy.f -o plate-legacy
C=====================================================================
      PROGRAM PLATE
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      PARAMETER (NMAX = 21)
      COMMON /GRID/ T(NMAX,NMAX), N
C     Grid size (a 4x4 test plate; interior is 2x2).
      N = 4
      CALL SETBC
      CALL RELAX
      CALL OUTPT
      STOP
      END
C
      SUBROUTINE SETBC
C     Boundary conditions: top edge hot, other three edges cold.
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      PARAMETER (NMAX = 21)
      COMMON /GRID/ T(NMAX,NMAX), N
      DATA THOT, TCOLD / 100.0D0, 0.0D0 /
      DO 20 J = 1, N
        DO 10 I = 1, N
          T(I,J) = 0.0D0
   10   CONTINUE
   20 CONTINUE
      DO 30 I = 1, N
        T(I,1) = TCOLD
        T(I,N) = TCOLD
   30 CONTINUE
      DO 40 J = 1, N
        T(N,J) = TCOLD
   40 CONTINUE
      DO 50 J = 1, N
        T(1,J) = THOT
   50 CONTINUE
      RETURN
      END
C
      SUBROUTINE RELAX
C     Jacobi relaxation with a GOTO convergence loop.
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      PARAMETER (NMAX = 21)
      COMMON /GRID/ T(NMAX,NMAX), N
      DOUBLE PRECISION TNEW(NMAX,NMAX)
      DATA TOL, MAXIT / 1.0D-6, 1000 /
C     Statement function: four-neighbour average.
      AVG(TL,TR,TB,TA) = 0.25D0 * (TL + TR + TB + TA)
      ITER = 0
   60 CONTINUE
      ITER = ITER + 1
      DMAX = 0.0D0
C     One Jacobi sweep: TNEW from the OLD T (interior only).
      DO 80 J = 2, N-1
        DO 70 I = 2, N-1
          TNEW(I,J) = AVG(T(I-1,J), T(I+1,J), T(I,J-1), T(I,J+1))
          DIFF = ABS(TNEW(I,J) - T(I,J))
          IF (DIFF .GT. DMAX) DMAX = DIFF
   70   CONTINUE
   80 CONTINUE
      DO 100 J = 2, N-1
        DO 90 I = 2, N-1
          T(I,J) = TNEW(I,J)
   90   CONTINUE
  100 CONTINUE
      IF (DMAX .GT. TOL .AND. ITER .LT. MAXIT) GO TO 60
      WRITE (6,'(A,I5,A)') ' converged in ', ITER, ' iterations'
      RETURN
      END
C
      SUBROUTINE OUTPT
C     Print the interior temperatures, one grid row per line.
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      PARAMETER (NMAX = 21)
      COMMON /GRID/ T(NMAX,NMAX), N
      DO 120 I = 2, N-1
        WRITE (6,'(20F9.4)') (T(I,J), J = 2, N-1)
  120 CONTINUE
      RETURN
      END

The five-step reading method

Now the method — the same one whether the code is 150 lines or 150,000.

Step 1: read the comments and the shape, not the logic. Before decoding any statement, harvest what the author told you. The banner says "steady-state temperature… Jacobi relaxation." The routine names — SETBC, RELAX, OUTPT — announce a three-phase structure: set the boundary conditions, relax to steady state, output. In thirty seconds, without reading one executable line, you know this program imposes edge temperatures on a plate, relaxes the interior, and prints the result. That is most of the battle.

Step 2: find the shared state. In a COMMON-based code, the data model lives in the COMMON blocks. Here there is just one, /GRID/ T(NMAX,NMAX), N — the temperature field T and the working grid size N — and it is declared identically in all four program units. That consistency is what makes the sharing safe, and it is the first thing you would check if you suspected a bug. Two details reward a second look. NMAX is a PARAMETER fixing the array's storage size (21) while N is the working size (4) — the classic FORTRAN 77 "over-dimension a fixed array" pattern, since there were no allocatable arrays. And IMPLICIT DOUBLE PRECISION (A-H,O-Z) at the top of every unit means all those undeclared reals (T, THOT, TOL, DMAX, …) are double precision — a common way old codes got 64-bit arithmetic without declaring a kind. Note also what is not here: no BLOCK DATA, because the constants TOL, MAXIT, THOT, TCOLD are local variables initialized by DATA inside their routines — BLOCK DATA is only needed to initialize COMMON.

Step 3: identify the loops and the exit. In RELAX, the label 60 with the GO TO 60 near the bottom is a loop, and its single exit is the line IF (DMAX .GT. TOL .AND. ITER .LT. MAXIT) GO TO 60: it jumps back only while not yet converged and iterations remain, so control falls through to the WRITE when either condition fails. Mentally, that is do … if (dmax <= tol .or. iter >= maxit) exit … end do. The labeled DO 80 / 70 CONTINUE and DO 100 / 90 CONTINUE nests are ordinary counted loops: the first is the sweep over the interior (2 to N-1, skipping the fixed edges), the second copies the new values back.

Step 4: decode the kernel. The statement function AVG names the four-neighbour average, and the line TNEW(I,J) = AVG(T(I-1,J), T(I+1,J), T(I,J-1), T(I,J+1)) is exactly the Laplace stencil above — the new field TNEW computed from the old field T, which is what makes it Jacobi and not in-place Gauss–Seidel. The separate TNEW array, copied back into T after the whole sweep, is the tell. The DIFF/DMAX lines track the largest change in the sweep, the convergence measure. You have now found the mathematical heart of the program, and it is four lines long.

Step 5: trace one concrete step to confirm. Never trust a reading you have not tested against a number. SETBC sets the top row (T(1,J)) hot at 100, the other three edges cold at 0, and zeros the interior. On this 4×4 grid the interior is just the four cells (2,2), (2,3), (3,2), (3,3). Trace the first sweep by hand: cell (2,2) averages its old neighbours T(1,2)=100 (hot edge), T(3,2)=0, T(2,1)=0, T(2,3)=0, giving $\tfrac14(100) = 25$; cell (3,2), one row further from the heat, averages four zeros and stays 0. So after sweep 1 the largest change is 25 — a number you can verify with a calculator.

Where does it converge? By symmetry the two upper interior cells are equal (call the value $a$) and the two lower cells equal ($b$). Steady state means each equals the average of its neighbours: $a = \tfrac14(100 + b + 0 + a)$ and $b = \tfrac14(a + 0 + 0 + b)$, i.e. $3a - b = 100$ and $a = 3b$. Substituting gives $8b = 100$, so $b = 12.5$ and $a = 37.5$ — every value a dyadic fraction, exact in double precision. OUTPT prints only the interior (2 to N-1), so you see a 2×2 block, and the loop reports the sweep count on the way:

$ gfortran -std=legacy plate-legacy.f -o plate-legacy && ./plate-legacy
 converged in    25 iterations
   37.5000  37.5000
   12.5000  12.5000

That output is not a lucky guess — it is the steady state you just derived by hand, which is precisely why this small, symmetric problem was chosen for a teaching kernel: a reader can confirm the archaeology against an exact answer. (Why 25 sweeps? From an all-zero interior the max change is 25 after sweep 1 and then halves every sweep — a spectral radius of exactly $\tfrac12$ on this grid — so it first drops below the $10^{-6}$ tolerance on sweep 25.) When you inherit a real code with no such luxury, Step 5 becomes "find or build a case whose answer you know" — the seed of the regression test you will write in Chapter 18.

💡 Intuition: Reading legacy code is not linear reading; it is triage. Comments and structure first, data model second, control flow third, the numerical kernel fourth, a concrete trace last. Panic comes from trying to understand line 1 before line 2; calm comes from understanding the shape before any line at all. You did not need to know FORTRAN 77 to find the four-line heart of PLATE — you needed a method.

🔄 Check Your Understanding

  1. Which array in RELAX is the give-away that the method is Jacobi rather than in-place Gauss–Seidel, and why?
  2. In RELAX, which single statement forms the exit of the convergence loop, and which construct would replace the whole 60 … GO TO 60 structure in modern Fortran?
  3. OUTPT loops I and J from 2 to N-1. On the 4×4 grid, how many numbers does it print, and why does it skip the first and last rows and columns?
Answers 1. `TNEW` — the *separate new-field* array. Jacobi computes every interior point from the neighbours' **old** values in `T`, writing results into `TNEW`, then copies `TNEW` back into `T` after the whole sweep; Gauss–Seidel would update `T` in place, using new values as soon as they appear, and would need no `TNEW`. The `T(I,J) = TNEW(I,J)` copy-back loop confirms it is Jacobi. 2. The line `IF (DMAX .GT. TOL .AND. ITER .LT. MAXIT) GO TO 60` is the loop: it repeats *while* not converged and iterations remain, and falls through when either fails. Modern Fortran replaces the `60 CONTINUE … GO TO 60` structure with a bare `do … end do` and an `if (dmax <= tol .or. iter >= maxit) exit`. 3. It prints the `2 × 2 = 4` interior values (a 2×2 block). It skips the first and last rows and columns because those are the *fixed boundary* temperatures set by `SETBC` — they are inputs, not part of the computed solution, so only the relaxed interior is reported.

Project Checkpoint

Your running heat solver takes a side quest here, one that spans Chapters 17–19. You have just met the FORTRAN 77 PLATE kernel; over the next two chapters you will modernize it into clean Fortran and tabulate every move you make. This chapter's checkpoint is the one that makes the rest possible: read PLATE until you own it.

Concretely, do three things and commit them beside your heat-solver/ project in a new folder, heat-solver/legacy/:

  1. Save and compile the kernel. Type the PLATE listing from §17.6 into plate-legacy.f (or take it from this chapter's code/ directory) and build it with gfortran -std=legacy plate-legacy.f -o plate-legacy. Confirm it prints converged in 25 iterations and the interior 37.5000 / 12.5000. Getting a legacy build working — the -std=legacy flag, the fixed-form .f extension — is itself a skill you will reuse on every old code you inherit.

  2. Annotate it. In a copy of the file, add a modern !-style comment beside each legacy construct naming what it is and what will replace it: IMPLICIT DOUBLE PRECISION"implicit none + real(dp) declarations"; COMMON /GRID/ T, N"a grid module (or an assumed-shape argument)"; the 60 … GO TO 60 loop → "do … exit"; AVG(...) ="internal pure function"; DATA THOT, TCOLD"declaration initializers"; the over-dimensioned T(NMAX,NMAX)"an allocatable array sized to N". (Notice what is absent: PLATE uses no EQUIVALENCE — a reminder that the modernization recipe is a menu, not a fixed march; you apply the steps a given code actually needs.) This annotated file is your migration map for Chapter 18.

  3. Pin the reference output. Run the kernel and save its output to legacy/expected.txt. That file is the regression reference: in Chapter 18 you will modernize the code one step at a time and, after each step, check that its output still matches expected.txt. Because every value here is a dyadic fraction (37.5, 12.5) computed with no rounding, this migration can be checked bit-for-bit — a case where "still correct" is completely unambiguous.

That is the whole checkpoint: no new solver code, but a legacy kernel you can read, build, and trust, plus the annotated map and reference output that turn Chapter 18 from a rewrite into a safe, checkable refactor. This is the ethic of the part made practical — never rewrite what you can refactor, and never refactor what you have not first read and pinned down.


Summary

Reading FORTRAN 77 is a bounded skill: a fixed vocabulary of old constructs, each with a known modern counterpart, plus a method for approaching an unfamiliar file calmly.

Legacy construct What it is Modern counterpart
fixed-form source columns 1–5 label, 6 continuation, 7–72 statement, C/* comment free-form; ! comments
COMMON /blk/ shared memory, associated by position, unchecked a module with typed variables (Ch. 8)
BLOCK DATA the only unit that may initialize named COMMON a module parameter or initializer
INCLUDE 'x.inc' raw textual paste to keep COMMON in sync use module, only: …
EQUIVALENCE two names share the same bytes (aliasing) allocatable, reshape, transfer
GO TO label unconditional jump do/exit/cycle, if
computed GOTO GO TO (l1,l2,…), k — jump by integer index select case
arithmetic IF IF (e) l1,l2,l3 — jump on sign(e) if … else if … end if
statement function one-line inline function in the declarations internal pure function (Ch. 6)
ENTRY a second entry point sharing a routine's body/state separate module procedures
implicit typing first letter sets the type; IN are INTEGER implicit none + explicit declarations (Ch. 2)
DATA statement initialize via parallel variable/value lists declaration initializer
Hollerith nHtext text named by length, pre-CHARACTER quoted character strings

The two rules worth memorizing. (1) In fixed-form, columns matter: label 1–5, continuation 6, statement 7–72, comment C in column 1 — count columns before you suspect anything deeper. (2) A COMMON block is shared raw memory associated by position, so a reordered or retyped declaration corrupts data silently; the module fixed this by associating by name and checking every access.

The reading method (any legacy file). Comments and structure first → the COMMON data model second → loops and their exits third → the numerical kernel fourth → one hand-traced concrete step last. Understand the shape before any single line.

Compile flag introduced: -std=legacy — accept obsolete features and read fixed-form; the way you build an inherited .f file.

Project piece added: not solver code, but the read, built, annotated, and output-pinned PLATE kernel — the legacy side quest you will modernize in Chapter 18.

Spaced Review

Two threads reach back into the foundations, because reading legacy control flow and legacy state is the mirror image of the modern constructs you already know — Chapter 4 (control flow) and Chapter 8 (modules).

  1. (Ch. 4) The PLATE kernel's convergence loop is 100 CONTINUE … IF (DMAX .LT. TOL) GO TO 200 … GO TO 100. Write the equivalent modern loop using do, exit, and an if, and say why the modern version needs no statement labels.

    Answer`do; iter = iter + 1; … ; if (dmax < tol) exit; if (iter >= maxit) exit; end do`. The modern `do … end do` construct *is* the loop — `end do` marks where to jump back to and `exit` marks where to leave — so there is nothing for a label to name. Labels existed only because the old `GO TO` had to name a target line; structured constructs make the target implicit.

  2. (Ch. 4) A legacy routine dispatches with GO TO (10, 20, 30), MODE. Which modern construct replaces a computed GOTO, and what does it add that the computed GOTO lacked?

    Answer`select case (mode)` with `case (1)`, `case (2)`, `case (3)`, and a `case default`. It adds a named, self-documenting branch per value, a `default` for out-of-range values (the computed `GOTO` silently falls through), and no possibility of accidental fall-through — the safety points from Chapter 4's `select case` section.

  3. (Ch. 8) PLATE shares its grid through COMMON /GRID/ T(NMAX,NMAX), N, declared identically in all four routines. How does a module make this both safer and shorter, and what replaces the requirement to repeat the declaration everywhere?

    AnswerA module declares `t` and `n` *once*, with explicit types the compiler checks; every routine gains access with a single `use grid` (or `use grid, only: …`), associating the entities **by name**, not by position. There is no repeated `COMMON` line to keep in sync across the four units, so the whole class of "reordered/retyped declaration corrupts data" bugs disappears — the payoff of Chapter 8's module concept.

  4. (Ch. 8) In FORTRAN 77 a named COMMON block could only be initialized in a BLOCK DATA unit. Where does that initialization go in modern Fortran, and why is no separate unit needed?

    AnswerIt goes right where the entity is declared in the module — as a `parameter` (for constants like `tol` and `maxit`) or a declaration initializer. No separate unit is needed because a module variable's declaration and its initial value live together and are owned unambiguously by the module, removing the ambiguity that forced FORTRAN 77 to invent `BLOCK DATA`.

What's Next

You can now read FORTRAN 77 — the columns, the COMMON blocks, the aliasing, the jumps, the implicit types — and you have a real kernel, PLATE, that you have read down to its four-line Jacobi heart and pinned to a known-correct output. That reading was the hard part; what follows is the payoff. Chapter 18 takes PLATE through an eight-step modernization recipe — implicit none, free-form, COMMON to module, intent, assumed-shape arrays, structured control, retiring EQUIVALENCE (where a code has it), and adding error handling — transforming it into clean modern Fortran without changing the answer it computes, checked at every step against the reference output you saved here. Legacy code is not a burden; it is an inheritance — and you are about to improve the engineering while preserving the science. Let's modernize it.