I stumbled across the line number article on Wikipedia, and let’s just say it lacked a historical perspective on where line numbers in programming languages came from and how they were used. I ended up writing a history section (adapting some from other Wikipedia articles). I have fond memories of reading my dad’s FORTRAN IV programming manual when I was a kid and later learning about JOSS in History of Programming Languages. (And you have no idea how hard it was for me to not type the name of every programming language in the title of this blog post in UPPERCASE.)

FORTRAN

In Fortran, as first specified in 1956, line numbers were used to define input/output patterns, to specify statements to be repeated, and for conditional branching. For example:[3]

   DIMENSION ALPHA(25), RHO(25)
1) FORMAT(5F12.4)
2) READ 1, ALPHA, RHO, ARG
   SUM = 0.0
   DO 3 I=1, 25
   IF (ARG-ALPHA(I)) 4,3,3
3) SUM = SUM + ALPHA(I)
4) VALUE = 3.14159*RHO(I-1)
   PRINT 1, ARG, SUM, VALUE
   GO TO 2

Like assembler language before it, Fortran did not assume every line needed a label (line number, in this case). Only statements referenced elsewhere required a line number:

  • Line 1 specifies a format pattern for input; the READ command in line 2 and the later PRINT command both reference this line.
  • The DO loop executes line 3.
  • The arithmetic IF statement branches to line 4 on a negative value, to line 3 on zero, and also to line 3 on a positive value.

While the line numbers are sequential in this example, in the very first “complete but simple [Fortran] program” published the line numbers are in the sequence 1, 5, 30, 10, 20, 2.[4]

Line numbers could also be assigned to fixed-point variables (e.g., ASSIGN i TO n) for referencing in subsequent assigned GO TO statements (e.g., GO TO n,(n1,n2,…nm)).

COBOL

In COBOL, line numbers were specified in the first six characters (the sequence number area) of punched cards. This was originally used for facilitating mechanical card sorting to assure intended program code sequence after manual handling. The line numbers were actually ignored by the compiler.

JOSS

In 1963, JOSS made line numbers mandatory for every statement in a program and ordered lines in sequential order. JOSS introduced the idea of a single command line editor that worked both as an interactive language and a program editor. Commands that were typed without a line number were executed immediately, in what JOSS referred to as “direct mode”. If the same line was prefixed with a line number, it was instead copied into the program code storage area, which JOSS called “indirect mode”.

Unlike FORTRAN before it or BASIC after it, JOSS required line numbers to be fixed-point numbers consisting of a pair of two-digit integers separated by a period (e.g., 1.1). The portion of the line number to the left of the period is known as the “page” or “part”, while the portion to the right is known as the “line”; for example, the line number 10.12 refers to page 10, line 12. Branches can target either a page or a line within a page. When the later format is used, the combined page and line is known as a “step”.

Pages are used to define subroutines, which return when the next line is on a different page. For instance, if a subroutine for calculating the square root of a number is in page 3, one might have three lines of code 3.1, 3.2 and 3.3, and it would be called using Do part 3. The code would return to the statement after the Do when it reaches the next line on a different page, for instance, 4.1. There is no need for the equivalent of a RETURN at the end, although if an early return is required, Done accomplishes this. Example:

*Routine to ask the user for a positive value and repeat until it gets one
01.10 Demand X as "Enter a positive value greater than zero".
01.20 Done if X>0.
01.30 To step 1.1

BASIC

Introduced in 1964, Dartmouth BASIC adopted mandatory line numbers, as in JOSS, but made them integers, as in FORTRAN. As defined initially, BASIC only used line numbers for GOTO and GOSUB (go to subroutine, then return). Some Tiny BASIC implementations supported numeric expressions instead of constants, while switch statements were present in different dialects (ON GOTOON GOSUBON ERROR GOTO).

Line numbers were rarely used elsewhere. One exception was allowing the pointer used by READ (which iterated through DATA statements) to be set to a specific line number using RESTORE.

  1 REM RESTORE COULD BE USED IF A BASIC LACKED STRING ARRAYS
  2 DIM M$(9): REM DEFINE LENGTH OF 9 CHARACTERS
  5 INPUT "MONTH #?"; M: IF M<1 OR M>12 THEN 5
  7 RESTORE 10*M: READ M$: PRINT M$
 10 DATA "JANUARY"
 20 DATA "FEBRUARY"
 30 DATA "MARCH"
 ...

In the first editions of Dartmouth BASIC, THEN could only be followed by a line number (for an implied GOTO), not – as in later implementations – by a statement.

The range of valid line numbers varied widely from implementation to implementation, depending on the representation used to store the binary equivalent of the line number (one or two bytes; signed or unsigned). While Dartmouth BASIC supported 1 to 99999, the typical microcomputer implementation supported 1 to 32767 (a signed 16-bit word).

RangeDialect
1 to 254MINOL
1 to 255Tiny BASIC Design Note
2 to 255Denver Tiny BASIC
0 to 999UIUC BASIC
1 to 2045DEC BASIC-8
0 to 32767LLL BASIC, NIBL
1 to 32767Apple I BASICLevel I BASICPalo Alto Tiny BASIC
1 to 65535Altair 4K BASIC, MICRO BASIC 1.3, 6800 Tiny BASIC, Tiny BASIC Extended
1 to 99999Dartmouth BASIC
1 to 999999SCELBAL