In this series of posts we’ll look at the Pittsburgh Interpretative Language, or PIL, a simple interpreted language that can be used to do calculations and build small programs interactively.
The Pittsburgh Interpretative Language
PIL was developed at the University of Pittsburgh for the System/360 in the late 60s. It was based on one of the first interpreted languages JOSS that originated at the RAND Corporation in 1963. PIL improves on JOSS by providing improved debugging capabilities and error reporting.
The design goals for PIL were, according to MTS Volume 12:
PIL is oriented toward problem-solving, with program development and debugging facilities having highest priority. For the beginning user, PIL was designed to be clear, unambiguous, and hence, easily learned. For the experienced programmer, the language offers increased flexibility with statement structure and expanded capabilities for the solution of non-numeric problems. For the researcher, PIL reduces the amount of time and effort that must be expended in problem solving.
PIL can be used as a simple desktop calculator with variables
* set a = 3
* set b = 4
* type the square root of (a*a + b*b)
the square root of (a*a + b*b) = 5.0
It can also be used to build simple programs interactively that can then be run:
* 1.01 demand a
* 1.02 demand b
* 1.03 type the square root of (a*a + b*b)
* do part 1
a = ?_ 3
b = ?_ 4
the square root of (a*a + b*b) = 5.0
PIL on MTS
PIL on MTS is based on the second version of PIL, PIL/2. It was modified to integrate well with MTS files and system services.
Prerequisites
No special installation instructions to get this language running - just do the standard D6.0 setup as described in this guide and then sign on as a regular user such as ST01.
Running a program using *PIL
Running the command *PIL on MTS will start the PIL interpreter. It is intended to be used in an interactive way where you enter commands and see the output directly. It can be used in batch mode by feeding commands into source but this is not the intended mode of operation. Inside the interpreter, programs and data can be loaded and saved with the load and save as statements.
Hello world
Let’s see how to run a simple program to print ‘Hello, world!’ five times using PIL.
# $run *pil
# Execution begins 09:56:53
PIL/2: Ready
* for i = 1 to 5: type "Hello, world!"
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
* stop
# Execution terminated 09:59:06 T=0.004
After starting PIL, enter the for statement at the * prompt. Output is shown immediately after the command is entered. Type stop to return to MTS.
In the next post we’ll look at the language in more detail.
Further information
MTS volume 12 has a tutorial and reference for the PIL language and describes how it is integrated with MTS.
The video pictured at the top of this post, 2 4 Using the Michigan Terminal System 6 01, shows simple operation of PIL on a teletypewriter, starting from 21 minutes into the video.
For the final post in this series, let’s write a real program in RATFOR and FLECS and see how they compare with the original FORTRAN. We’ll be implementing the reverse-primes emirp program we did before.
FLECS version
C FLECS PROGRAM TO DISPLAY EMIRPS
C
C *** TEST IF A NUMBER IS PRIME ***
LOGICAL FUNCTION PRIME(N)
INTEGER N
C DEAL WITH NUMBERS <= 3
IF (N .LE. 1) GOTO 200
IF (N .EQ. 2 .OR. N .EQ. 3) GOTO 100
C CHECK IF DIVISIBLE BY 2 OR 3
IF (MOD(N,2) .EQ. 0) GOTO 200
IF (MOD(N,3) .EQ. 0) GOTO 200
C SEE IF DIVISIBLE BY 5, 7, ..., UP TO APPROX SQRT(N)
DO (I=5,999999,2)
IF (I*I .GT. N) GOTO 100
IF (MOD(N,I) .EQ. 0) GOTO 200
FIN
100 PRIME = .TRUE.
RETURN
200 PRIME = .FALSE.
RETURN
END
C
C *** REVERSE AN INTEGER'S DIGITS ***
INTEGER FUNCTION REVRSE(N)
INTEGER N
INTEGER M,R
C M IS COPY OF N FROM WHICH WE TAKE DIGITS
C R IS REVERSED DIGITS
M = N
R = 0
C LOOP UNTIL NO MORE DIGITS
UNTIL (M .LT. 1)
C TAKE LAST DIGIT FROM M AND APPEND TO R
R = R * 10
R = R + MOD(M, 10)
M = M / 10
FIN
REVRSE = R
RETURN
END
C
C *** TEST IF AN INTEGER IS AN EMIRP ***
LOGICAL FUNCTION EMIRP(N)
INTEGER N
C EXTERNAL FUNCTIONS
INTEGER REVRSE
LOGICAL PRIME
C R CONTAINS REVERSED DIGITS OF N
INTEGER R
R = REVRSE(N)
C N AND R MUST BOTH BE PRIME AND NOT THE SAME VALUE
IF (N .NE. R)
IF (PRIME(N))
IF (PRIME(R))
EMIRP = .TRUE.
RETURN
FIN
FIN
FIN
EMIRP = .FALSE.
RETURN
END
C
C *** DISPLAY AN INTEGER ***
SUBROUTINE SHOW(N)
INTEGER N
WRITE(6,50) N
50 FORMAT(I10)
RETURN
END
C
C
C *** MAIN ENTRY POINT ***
C I IS COUNT OF EMIRPS FOUND
C N IS NUMBER TO TEST
C EXTERNAL FUNCTION
LOGICAL EMIRP
INTEGER I,N
TEST-1
TEST-2
TEST-3
STOP
C
C *** SHOW FIRST 20 EMIRPS ***
TO TEST-1
N = 0
I = 0
WHILE (I .LT. 20)
N = N + 1
IF (EMIRP(N))
CALL SHOW(N)
I = I + 1
FIN
FIN
FIN
C
C *** SHOW EMIRPS BETWEEN 7,700 AND 8,000 ***
TO TEST-2
DO (N=7700,8000)
IF (EMIRP(N)) CALL SHOW(N)
FIN
FIN
C
C *** SHOW 10,000TH EMIRP ***
TO TEST-3
N = 0
DO (I=1,10000)
REPEAT UNTIL (EMIRP(N)) N = N + 1
FIN
CALL SHOW(N)
FIN
C
END
Apart from the FORMAT specification and the PRIME function we’ve eliminated all line numbers. PRIME could be written without line numbers but with the multiple paths out of the function that would need their own RETURN I think it’s better this way.
The internal procedures come in handy, eliminating the need for subroutines for TEST1-3, though this does make N and I global which makes me a little uneasy if this was a larger program.
We use the block structure often, with UNTIL, WHILE and REPEAT ... UNTIL; this simplifies code, though without indentation it’s a little hard to follow; the output of the preprocessor is useful here to show what it thinks the indentation should be, for example:
86 TO TEST-1
87 . N = 0
88 . I = 0
89 . WHILE (I .LT. 20)
90 . . N = N + 1
91 . . IF (EMIRP(N))
92 . . . CALL SHOW(N)
93 . . . I = I + 1
94 . . ...FIN
95 . ...FIN
96 ...FIN
The compiler diagnostics also helped a lot with catching errors with missing FINs.
RATFOR
Now let’s try writing the RATFOR version.
######################################################################
# Ratfor program to display emirps
######################################################################
######### Test if a number is prime #########
logical function prime(n)
integer n # Number to test
# Deal with numbers <= 3
if (n < 1) goto 200
if (n == 2 | n == 3) goto 100
# Check if divisible by 2 or 3
if (mod(n,2) == 0) goto 200
if (mod(n,3) == 0) goto 200
# See if divisible by 5, 7, ..., up to approx sqrt(n)
for (i = 5; i < 1000000; i = i + 2) {
if (I*I > n) goto 100
if (mod(n,i) == 0) goto 200
}
100 prime = .true.
return
200 prime = .false.
return
end
######### Reverse an integer's digits #########
integer function revrse(n)
integer n # Number to reverse
integer m # Copy of n from which we take digits
integer r # Reversed digits
m = n
r = 0
while (m >= 1) {
# Take last digit from m and append to r
r = r * 10
r = r + mod(m, 10)
m = m / 10
}
revrse = r
return
end
######### Test if an integer is an emirp #########
logical function emirp(n)
integer n # Number to test
integer revrse # External function
logical prime # External function
integer r # Reversed digits of n
r = revrse(n)
emirp = .false.
# n and r must both be prime and not the same value
if (n .ne. r & prime(n) & prime(r)) {
emirp = .true.
}
return
end
######### Display an integer #########
subroutine show(n)
integer n
write(6,50) n
50 format(i10)
return
end
######### Show first 20 emirps #########
subroutine test1
logical emirp # External function
integer i # Count of emirps found
integer n # Number to test
n = 0
for (i = 1; i <= 20; i = i + 1) {
repeat {
n = n + 1
} until (emirp(n))
call show(n)
}
return
end
######### Show emirps between 7,700 and 8,000 #########
subroutine test2
logical emirp # External function
integer n # Number to test
for (n = 7700; n <= 8000; n = n + 1) {
if (emirp(n)) {
call show(n)
}
}
return
end
######### Show 10,000th emirp #########
subroutine test3
logical emirp # External function
integer i # Count of emirps found
integer n # Number to test
n = 0
for (i = 1; i <= 10000; i = i + 1) {
repeat {
n = n + 1
} until (emirp(n))
}
call show(n)
return
end
######### Main entry point #########
call test1
call test2
call test3
stop
end
I feel right at home with the braces and the C style for loops, though I miss the increment operator ++. prime would be much better if I could just return (.true.) but that does not work on the version of RATFOR on MTS so we keep the line numbers and gotos.
With the above, plus the free form input (which was supported on MTS FORTRAN anyway) and the operators like < it was easy to write. However, I got precisely zero diagnostics from the RATFOR preprocessor, with all my typos caught by the FORTRAN compiler, from which I’d have to find the problem in the original source. Easy enough in a small program but would be painful in larger ones.
Final thoughts
RATFOR and FLECS both make writing FORTRAN easier and more pleasant at the cost of an extra step in the development process, and I found both succeed at that. RATFOR is clearer and easier to get started with (especially coming from a C background today); the implementation is almost aggressively simple, as the authors admit in their paper, and I wonder how well it would scale for writing larger programs. FLECS has a more robust implementation but a more diffuse design, such as two versions of switch; features like printing a neatly indented output would certainly help on MTS or its contemporaries but the language lacks the cosmetic features that make RATFOR easier to read.
Neither are much used today; FORTRAN 77 and beyond took some of these ideas and built them into the core language. The idea of translating a richer language into a widely used but less expressive language is still alive though: think of Coffeescript or Typescript producing Javascript.
Further information
Full source code for these programs can be found on github.
From the UM Computing Center Newsletter, Volume 5 Number 14, 24 September 1975, via Google Books. Proposal 2) seems to indicate a different preprocessor was being considered for UM as well as FLECS, I wonder if this was RATFOR or something else?
Hi and welcome back. Today let’s continue our exploration of RATFOR and FLECS by comparing the language features they add to vanilla FORTRAN. The quotes below are from the RATFOR paper and FLECS manual, links to which are provided at the end of this post. Code samples for FORTRAN and FLECS are shown in upper case, RATFOR in lower case.
Design
RATFOR attempts to retain the merits of FORTRAN (universality, portability, efficiency) while hiding the worst FORTRAN inadequacies. The language is FORTRAN except for two aspects - [control flow and syntactic sugar] … Throughout, the design principle which has determined what should be in RATFOR and what should not has been RATFOR doesn’t know any FORTRAN.
RATFOR focuses on control flow - if statements, blocks, looping - and cosmetics such as free form input, comments and other features that make FORTRAN more pleasant to write. By not knowing any FORTRAN, the design limits what features can be made available but also keeps it simple to implement and reduces the temptation to change FORTRAN into a different language altogether.
FLECS is a language extension of FORTRAN which has additional control mechanisms . These mechanisms make it easier to write FORTRAN by eliminating much of the clerical detail associated with constructing FORTRAN programs. FLECS is also easier to read and comprehend than FORTRAN.
FLECS also tries ti improve FORTRAN’s control statements, taking ideas from several different languages including Pascal and Lisp. It has less cosmetic additions than RATFOR but adds the concept of internal procedures and includes features in the translator that help the programmer see the structure of their program.
Structure
RATFOR allows blocks of statements to be introduced within braces where FORTRAN would only allow a single statement. The fixed column format in classic FORTRAN is relaxed so any indentation is allowed. Multiple statements can appear on the same line if they are separated by semicolons.
if (x > 100) {
call error(x)
err = 1; return
}
FLECS also has blocks which extend from the start of a control statement to the keyword FIN. It retains the fixed formatting of FORTRAN but prints a nicely indented view of the program when translating. So the example above would be entered as this in FLECS:
IF (X .GT. 100)
CALL ERROR(X)
ERR = 1
FIN
and the translator would print
IF (X .GT. 100)
. CALL ERROR(X)
. ERR = 1
...FIN
This is useful when entering programs via cards where it is difficult to get indentation right.
It’s possible to have a single statement after a control structure in which case the FIN is not needed:
IF (X .GT. 100) CALL ERROR(X)
RATFOR comments are introduced with # and apply from that point to the end of the line, less restrictive than C in FORTRAN and FLECS which must be in the first column.
% will stop RATFOR processing the rest of the line, passing it through to FORTRAN directly. FLECS will look for a FLECS statement in column 7 and if found will translate the line; if not found it will pass through the whole line to FORTRAN.
Textual substitution
RATFOR allows constants to be set with define SYMBOL VALUE; any use of SYMBOL in the RATFOR program will be replaced with VALUE in the generated FORTRAN program.
include FILE will insert a copy of FILE at that point in the program, just like C’s #include.
Operators
RATFOR allows the now-familiar symbols <, <=, !=, | etc to be used instead of .LT., .LE., .NE., .OR. etc. FLECS retains the FORTRAN operators.
Strings
Text in RATFOR programs in single or double quotes is converted to FORTRAN nH strings. Backslash escapes the next character. FLECS keeps FORTRAN strings.
Conditionals
FORTRAN has a simple iF statement where only one statement can be executed if the condition is true. RATFOR extends this by allowing else and nested ifs. An else clause is attached to the nearest if.
if (x > 0) {
if (x > 10)
write(6, 1) x
else
write(6, 2) x
else
weite(6, 3)
FLECS has IF and for negative tests UNLESS. It also has WHEN … ELSE for a single positive and negative test.
The switch statement added in RATFOR looks like C but does not have break; the switch is exited after each case or default is executed. FLECS’s equivalent is SELECT, so comparing the two:
switch (x) {
case 1: y=3
case 2, 3: y=5
default y=0
}
FLECS has CONDITIONAL which looks a lot like LISP’s cond:
CONDITIONAL
(X.LT.-5.0) U = U+W
(X.LE.1.0) U = U+W+Z
(X.LE.10.5) U = U-Z
(OTHERWISE) U = 0
FIN
Looping
The FORTRAN DO loop has to have a line number marking the point where the loop will restart:
DO 10 i = 1, n
x(i) = 0.0
y(i) = 0.0
z(i) = 0.0
10 CONTINUE
RATFOR replaces this with a block:
do i = 1, n {
x(i) = 0.0
y(i) = 0.0
z(i) = 0.0
}
It also allows break to exit a loop early and next to restart the loop like C’s continue. It can be followed by an integer to say how many levels to apply, so break 2 would move out of a two level do statement immediately.
RATFOR also adds a while and for statement that look like C’s - these allow immediate exit from the statement if the condition is true on entry, unlike in FORTRAN DO where the statement is always executed at least once (in the IBM implementation at least) and the conditional is tested at the end of the statement. A version of C’s do … while is provided as repeat … until.
The FLECS equivalent for the above do loop would be:
DO (I = 1, N)
X(I) = 0.0
Y(I) = 0.0
Z(I) = 0.0
FIN
FLEC’s WHILE construct is similar to RATFOR’s, with the conditional tested before the loop starts. By using REPEAT WHILE the body of the loop is executed at least once and the test made at the end of the loop. UNTIL can be used instead of WHILE in both cases to indicate that the loop ends when the conditional becomes true
X = 0
UNTIL (X.EQ.5)
X = X + 1
FIN
Return
To return a value from a function in FORTRAN and FLECS you must assign a value to the name of the function:
INTEGER FUNCTION DECREMENT(I)
INTEGER I
DECREMENT = I - 1
RETURN
END
In the RATFOR paper it says you can give return a value:
integer function decrement(i)
integer i
return (i-1)
end
However, note this is not supported in the version supplied with MTS - it will just pass through such a return statement causing an error from the FORTRAN compiler.
Internal procedures
FLECS allows a group of statements to be defined as a procedure with TO which can then be called by giving its name. No parameters are passed - it uses global variables to communicate. The below example will print 5.
INTEGER X
X = 1
INCREMENT-IT
DOUBLE-AND-INCREMENT
WRITE(6,50) X
STOP
50 FORMAT(I10)
TO INCREMENT-IT X = X + 1
TO DOUBLE-AND-INCREMENT
X = X * 2
INCREMENT
FIN
END
Procedure names must include at least one hyphen and recursion is not allowed.
Operation
RATFOR runs as a simple translator, taking a RATFOR input file and producing a FORTRAN output file that must then be fed to the FORTRAN compiler. FLECS, as modified at UM, will both translate and call the FORTRAN compiler, producing machine code output that can be run directly.
Error handling
RATFOR will catch some errors, such as missing closing braces, but will otherwise delegate problems with the program to the FORTRAN compiler to catch, as it does not understand FORTRAN syntax. This could be difficult to trace back to the source of the error as the FORTRAN compiler would show the error in the generated FORTRAN, not the RATFOR original.
FLECS will find syntax errors and remove them from the program, allowing translation to continue at the cost of possibly causing further errors; it will not move on to compilation in this case.
Implementation
Not surprisingly given its authors’ roots, RATFOR was originally written in around 1000 lines of C using yacc. The authors say it took less than a week to implement. As C was not widely available in the mid 70’s, a version of RATFOR in RATFOR was produced that would generate around 2500 lines of basic FORTRAN so it could be used anywhere.
The FLECS implementation comes in at around 2200 lines of FLECS and took around six months to develop according to comments in the source code.
Further information
See Kernighan’s RATFOR paper or the FLECS User’s Manual (in component 673/22; I’ve uploaded a copy here) for more information on the languages.
Most programmers will agree that FORTRAN is an unpleasant language to
program in, yet there are many occasions when they are forced to use
it.
From the introduction to ‘RATFOR — A Preprocessor for a Rational FORTRAN’ by Brian W. Kernighan
FORTRAN was the lingua franca for mainframe programmers in the 1960s and 1970s, but as Kernighan states it’s not always easy to program in - the main reasons are lack of good control structures and the fixed line format. As a result, a number of preprocessors were developed that translated enhanced code down to plain FORTRAN that could then be compiled anywhere a compiler was available.
In this series of posts, we’ll look at two preprocessors available on MTS: RATFOR and FLECS. MTS also had OVERDRIVE, but this is not available on D6.0 due to copyright reasons.
Prerequisites
No special installation instructions to get these preprocessors running - just do the standard D6.0 setup as described in this guide and then sign on as a regular user such as ST01.
RATFOR
RATFOR was developed by Brian Kernighan at Bell Telephone Labs in 1974; its syntax was (not surprisingly) inspired by the C programming language, with keywords like for, while and until. It was used as the language for examples in Software Tools and became one of the most popular preprocessors in use. Versions are still available today that run on Unix systems.
Preprocessing using *RATFOR
The version on MTS is called *RATFOR and takes a RATFOR program as input on scards and writes FORTRAN source to spunch. The generated file can then be compiled with *FTN.
Hello world
Here’s a terminal log of how to compile and run a simple hello world program in RATFOR. This assumes the source code is in file hello.r.
# $list hello.r
1 # *** Simple hello world program ***
2 #
3 integer i
4 for (i = 0; i < 5; i = i + 1)
5 {
6 write(6, 200)
7 }
8 stop
9 200 format("Hello, world!")
10 end
# $run *ratfor scards=hello.r spunch=-hello.f
Execution begins 21:56:08
Execution terminated 21:56:08 T=0.004
# #list -hello.f
1 INTEGERI
2 CONTINUE
3 I=0
4 23000 IF(.NOT.(I.LT.5))GOTO 23002
5 WRITE(6,200)
6 23001 I=I+1
7 GOTO 23000
8 23002 CONTINUE
9 STOP
10 200 FORMAT(13HHello, world!)
11 END
# $run *ftn scards=-hello.f spunch=-load
Execution begins 21:56:36
No errors in MAIN
Execution terminated 21:56:36 T=0.008
# #run -load
Execution begins 21:56:39
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Execution terminated 21:56:39 T=0.001
FLECS
FLECS was written in the early 1970s by Terry Beyer at the University of Oregon. It provides a smaller set of control structures that RATFOR but the syntax is closer to FORTRAN. Keywords include IF...THEN...ELSE and CONDITIONAL and multi-line statements are supported. It does not appear to have been used much past the introduction of FORTRAN77, but a a version is still available today for HPUX.
Compiling using UNSP:FLX
At the time D6.0 was released, FLECS was unsupported at UM so is available as the file FLX in UNSP:. The preprocessor does not used scards and spunch; instead, all parameters need to be passed in to par. Unlike RATFOR, FLECS can call the FORTRAN compiler directly to generate object code. In the listing below, PAR=SOURCE=hello.fl,P=*SINK*,FTNSOURCE,LOAD=-load would read source from hello.fl, print diagnostics to *SINK including the FORTRAN source generated, and write compiled output to -load.
Hello world
Here’s a terminal log of how to compile and run a simple hello world program in FLECS. This assumes the source code is in file hello.fl.
# $list hello.fl
1 C *** SIMPLE HELLO WORLD PROGRAM ***
2 C
3 DO (I = 1,5)
4 WRITE (6,20)
5 FIN
6 STOP
7 20 FORMAT(13H HELLO, WORLD)
8 END
# $run UNSP:FLX PAR=SOURCE=hello.fl,P=*SINK*,FTNSOURCE,LOAD=-load
Execution begins 21:47:21
FFI(CT206)
(FLECS VERSION 22.38) MTS Version CT155 21:47:21 JAN 21, 1916 Page 1
MTS Line# Indented Source Listing...
1 C *** SIMPLE HELLO WORLD PROGRAM ***
2 C
3 DO (I = 1,5)
4 . WRITE (6,20)
5 ...FIN
6 STOP
7 20 FORMAT(13H HELLO, WORLD)
8 END
0.001 seconds CPU time used. Translation rate is 480000 lines per CPU minute.
There were NO MAJOR ERRORS and NO MINOR ERRORS in the above module.
No preprocessor errors in module 1.
MICHIGAN TERMINAL SYSTEM FORTRAN G(21.8) MAIN 01-21-16 21:47:21 PAGE P001
0001 DO 99998 I = 1,5 3.000
0002 WRITE (6,20) 4.000
0003 99998 CONTINUE 5.000
0004 STOP 6.000
0005 20 FORMAT(13H HELLO, WORLD) 7.000
0006 END 8.000
*OPTIONS IN EFFECT* ID,EBCDIC,SOURCE,NOLIST,NODECK,LOAD,NOMAP
*OPTIONS IN EFFECT* NAME = MAIN , LINECNT = 57
*STATISTICS* SOURCE STATEMENTS = 6,PROGRAM SIZE = 344
*STATISTICS* NO DIAGNOSTICS GENERATED
No errors in MAIN
NO STATEMENTS FLAGGED IN THE ABOVE COMPILATIONS.
Execution terminated 21:47:21 T=0.018
# $run -load
Execution begins 21:47:31
HELLO, WORLD
HELLO, WORLD
HELLO, WORLD
HELLO, WORLD
HELLO, WORLD
Execution terminated 21:47:31 T=0.001
Further information
The Wikipedia article on RATFOR has a basic introduction to language features and the history of its development. Kernighan’s paper on RATFOR goes into more detail on the language.
Not much appears to exist on the Internet describing FLECS, but the D6.0 MTS tapes does include the complete User’s Manual (in component 673/22) and the interface to MTS.
MTS Volume 6 describes the FORTRAN compilers on MTS, which are needed to compile the RATFOR preprocessor’s output.
Let’s implement a simple program in SNOBOL on MTS to print today’s date in different formats.
The problem
The problem is quite simple: take today’s date and display it in ISO format (eg 2015-10-11) and a human readable format (eg Sunday, October 11, 2015). Further details and implementations in other languages can be found on Rosetta Code.
Getting today’s date
There’s a built in function in SNOBOL to return the date as an eight character string. Interestingly, the SNOBOL 4 Programming Language says this returns ‘MM/DD/YY’ but on MTS it returns ‘MM-DD-YY’. Let’s get this and store in a variable.
NOW = DATE()
Breaking the date into components
We take the date and extract the month, day and year using SNOBOL’s pattern matching facility and assign it to variables DAY, MONTH, and YEAR.
PART = SPAN("0123456789")
SEP = "-"
NOW (PART . MONTH) SEP (PART . DAY) SEP (PART . YEAR)
Y2K strikes again
The value returned from DATE() has a two digit year, so let’s assume we are running this in the 21st century.
CENTURY = 2000
CYEAR = YEAR + CENTURY
Displaying in ISO format
So displaying the date in ISO format is now simply a case of concatenating the day, month and four digit year and then outputting it.
ISO = CYEAR SEP MONTH SEP DAY
OUTPUT = ISO
Day of the week
We now turn to the human readable form but there is a slight problem - we need to know which day of the week it is, eg Monday, and there is no facility in SNOBOL to calculate this. There may be a MTS external library we could call to get this, but instead we will use Gauss’ algorithm to derive the day as a number from 0 (Sunday) to 6 (Saturday).
* GYEAR is the 4 digit year, unless Jan or Feb then subtract 2
* GMONTH is MONTH-2 modulus 12, Jan is 11, Feb is 12
GT(MONTH, 2) :S(G1)F(G2)
G1 GYEAR = CYEAR
GMONTH = MONTH - 2 :(GX)
G2 GYEAR = CYEAR - 1
EQ(MONTH, 1) :S(G3)F(G4)
G3 GMONTH = 11 :(GX)
G4 GMONTH = 12 :(GX)
GX WDAY = REMDR(DAY, 7)
* Calculate the month term
MT = (2.6 * GMONTH) - 0.2
* Add the month term - the 0.00005 is needed due to lack of FP precision
WDAY = WDAY + REMDR(CONVERT(MT + 0.00005, 'INTEGER'), 7)
WDAY = WDAY + 5 * REMDR(REMDR(GYEAR, 4), 7)
WDAY = WDAY + 4 * REMDR(REMDR(GYEAR, 100), 7)
WDAY = WDAY + 6 * REMDR(REMDR(GYEAR, 400), 7)
WDAY = REMDR(WDAY, 7)
Month and day names
We will need a way of translating a month and day number into a name, eg January or Monday. SNOBOL’s arrays can be used for this. Note that the DAYS array is indexed from 0 to 6 instead of 1 to 7.
# $run *snobol4 5=date.sn
# Execution begins 21:09:05
SNOBOL4 (VERSION 3.10, APRIL 1, 1973)
(MTS IMPLEMENTATION MAY 1, 1975)
0 SYNTACTIC ERROR(S) IN SOURCE PROGRAM
2015-10-11
Sunday, October 11, 2015
NORMAL TERMINATION AT LEVEL 0
LAST STATEMENT EXECUTED WAS 53
SNOBOL4 STATISTICS SUMMARY
38 MS. COMPILATION TIME
1 MS. EXECUTION TIME
41 STATEMENTS EXECUTED, 0 FAILED
21 ARITHMETIC OPERATIONS PERFORMED
1 PATTERN MATCHES PERFORMED
0 REGENERATIONS OF DYNAMIC STORAGE
0 READS PERFORMED
2 WRITES PERFORMED
0.02 MS. AVERAGE PER STATEMENT EXECUTED
# Execution terminated 21:09:05 T=0.045
Final thoughts on SNOBOL
Using SNOBOL feels close to modern scripting languages such as Perl, Python or Ruby. I really like the pattern matching facilities where you can do things like EXPR = TERM | *EXPR OP TERM which is much more powerful than regular expressions; I don’t think there is any modern language that has this built in. The lack of control flow processing apart from GOTO is annoying; later versions of the language such as SPITBOL addressed this. I imagine that it was also rather slow when running on a mainframe, especially as it had to be compiled each time it was run.
I don’t think SNOBOL is much in use today, but the maintainer of SPITBOL is still active.