# PIL - Roman numerals

In the final part of this series, let's create a real program in PIL.

## The problem

We will implement arabic to roman number conversion from Rosetta Code.

The algorithm we're going to use is similar to the one used there for BASIC:

• Have a table of all distinct roman numbers ordered by size, including the -1 variants like IV. So roman(0) = “M”, roman(1) = “CM”, roman(2) = “D” etc.
• Have another table with the same indices for their arabic equivalents. arabic(0) = 1000, arabic(1) = 900, arabic(2) = 500 etc.
• Loop through each index. For each, if the input value is greater than the value of the arabic table at that value, accumulate the roman equivalent at the end of the output string and decrease the input value by the arabic amount. Keep doing this until the remaining input value is smaller than the arabic number.
• So for input 2900 the steps would be
• index 0, output -> “M”, input -> 1900
• index 0, output -> “MM” , input -> 900
• index 1, output -> “MMCM”, input -> 0 and end

## The solution

As PIL is an interpreted language I'll show a lightly reformatted transcript of my session as I build up the program in separate parts (and make mistakes along the way). Let's get started!

``````# \$run *pil
# Execution begins   20:09:19
``````

### The tables

First we need to set up the tables for arabic numbers in part 1. I will use the `number` command so that PIL prompts me with line numbers followed by an underscore automatically.

``````*number 1, 0.01
&*1.0  _arabic(0) = 1000
&*1.01 _arabic(1) = 900
&*1.02 _arabic(2) = 500
&*1.03 _arabic(3) = 400
&*1.04 _arabic(4) = 100
&*1.05 _arabic(5) = 90
&*1.06 _arabic(6) = 50
&*1.07 _arabic(7) = 40
&*1.08 _arabic(8) = 10
&*1.09 _arabic(9) = 9
&*1.10 _arabic(10) = 5
&*1.11 _arabic(11) = 4
&*1.12 _arabic(12) = 1
&*1.13 _\$unnumber
``````

The `unnumber` command exits numbered line prompting mode. It needs to be prefixed with `\$` to be executed immediately rather than be entered as part of the program.

Let's run that immediately so we can check it looks correct

``````*do part 1
*type arabic
arabic(0) =  1000.0
arabic(1) =  900.0
arabic(2) =  500.0
arabic(3) =  400.0
arabic(4) =  100.0
arabic(5) =  90.0
arabic(6) =  50.0
arabic(7) =  40.0
arabic(8) =  10.0
arabic(9) =  9.0
arabic(10) =  5.0
arabic(11) =  4.0
arabic(12) =  1.0
``````

We can then do the same for the roman numbers.

``````*number 2, 0.01
&*2.0 _roman(0) = "M"
&*2.01 _roman(1) = "CM"
&*2.02 _roman(2) = "D"
&*2.03 _roman(3) = "CD"
&*2.04 _roman(4) = "C"
&*2.05 _roman(5) = "XC"
&*2.06 _roman(6) = "L"
&*2.07 _roman(7) = "XL"
&*2.08 _roman(8) = "X"
&*2.09 _roman(9) = "IX"
&*2.1 _roman(10) = "V"
&*2.11 _roman(11) = "IV"
&*2.12 _roman(12) = "I"
&*2.13 _\$unnumber
*do part 2
``````

### The main loop

Let's now make the main loop to convert the number. We'll do it in three parts, first the loop over the indices. I put in some comments fir the function.

``````*number 5, 0.01
&*5.0 _* Main entry point to arabic -> roman converter
&*5.01 _* Input: a (arabic number to convert)
&*5.02 _* Output: r (roman number equivalent of a)
&*5.03 _for i = 0 to 12: do part 6
&*5.04 _done
&*5.05 _\$unnumber
``````

Next, the loop for each arabic number. We can use a `for` with a dummy variable and the `while` controlling how often it is run.

``````*number 6, 0.01
&*6.0 _for j = 0 while a >= arabic(i): do part 7
&*6.01 _done
&*6.02 _\$unnumber
``````

Finally, in part 7 build up the roman number string and decrease the arabic number.

``````*number 7, 0.01
&*7.0 _r = r + roman(i)
&*7.01 _a = a - arabic(i)
&*7.02 _done
&*7.03 _\$unnumber
``````

Let's see what these look like now.

``````*type part 5, part 6, part 7

5.0    * Main entry point to arabic -> roman converter
5.01   * Input: a (arabic number to convert)
5.02   * Output: r (roman number equivalent of a)
5.03   for i = 0 to 12: do part 6
5.04   done

6.0    for j = 0 while a >= arabic(i): do part 7
6.01   done

7.0    r = r + roman(i)
7.01   a = a - arabic(i)
7.02   done
``````

### Trying it out

We can set up the input number in `a` then call part 5 to convert. The output should go into `r`.

``````*a = 13
*do part 5
Error at step 7.0: r = ?
``````

Ah, `r` is not initialised so cannot be appended to. We can patch part 5 and try again.

``````*5.025 r = ""
*do part 5
*type r
r = "XIII"
*type a
a =  0.0
``````

Great! There is a side effect though, the input value in `a` is wiped out as PIL does not have local variables.

Thinking about it, we are relying on the tables being initialised when we run part 5. We should really make it stand-alone by calling part 1 and 2 first.

``````*5.026 do part 1
*5.027 do part 2
``````

### Making it interactive

We should have a way to prompt for a number and then display the conversion.

``````*number 10, 0.01
&*10.0 _demand a
&*10.01 _do part 5
&*10.02 _type r
&*10.03 _\$unnumber

*do part 10
& a = ? _1992
r = "MCMXCII"
``````

### Unit tests!

It may be anachronistic, but we should have some unit tests to see if the conversion works. First let's define a unit test handler in part 20 that takes the arabic number in `a`, the expected result in `rExpected` and then checks this matches.

``````*number 20, 0.01
&*20.0 _do part 5
&*20.01 _if r = rExpected, then type "OK", r; else type "ERROR', r, rExpected
Error at step 20.01: SYMBOLIC NAME TOO LONG
&*20.02 _if r = re, then type "OK", r; else type "ERROR", r, re
&*20.03 _done
&*20.04 _\$unnumber
``````

`rExpected` is too long for a variable number so we use a shorter name instead, `re`.

Let's test the tester out.

``````*re = "XLII"
*a = 42
*do part 20
Error at step 20.01: SYMBOLIC NAME TOO LONG
``````

Ah, the bad line is still there, so delete that and try again.

``````*delete step 20.01
*do part 20
ERROR
r = ""
re = "XLII"
``````

Wait, that's not right, why is the output in `r` blank?

`````` *type r
r = ""
*type a
a =  0.0
``````

Oh OK, `a` is clobbered. Let's set it up again.

``````*a = 42
*do part 5
*type r
r = "XLII"
*do step 20.02
OK
r = "XLII"
*do step 20.02
OK
r = "XLII"
*type part 20

20.0    do part 5
20.02   if r = re, then type "OK", r; else type "ERROR", r, re
20.03   done

*a = 42
*re = "XLII"
*do part 20
OK
r = "XLII"
``````

That fixed it. Try the error case.

``````*a = 42
*re = "XXX"
*do part 20
ERROR
r = "XLII"
re = "XXX"
``````

With that done, set up the tests.

``````*number 21, 0.01
&*21.0 _a = 2009
&*21.01 _re = "MMIX"
&*21.02 _do part 20
&*21.03 _a = 1666
&*21.04 _re = "MDCLXVI"
&*21.05 _do part 20
&*21.06 _a = 3888
&*21.07 _re = "MMMDCCCLXXXVIII"
&*21.08 _do part 20
&*21.09 _done
&*21.1 _\$unnumber
``````

And run them.

``````*do part 21
OK
r = "MMIX"
OK
r = "MDCLXVI"
OK
r = "MMMDCCCLXXXVIII"
``````

All green. However we did not test all cases such as zero, negative numbers, non-integral numbers etc.

To confirm the program is all done and we are not relying on anything in the environment, save it to disk, quit and come back into PIL and try re-running.

``````*create "roman.pil"
FILE "ROMAN.PIL" IS CREATED
*save as "roman.pil", all parts
SAVE COMPLETED
*stop
# Execution terminated   18:51:16  T=0.279

# \$run *pil
# Execution begins   18:51:37
*do part 10
& a = ?  _42
r = "XLII"
*do part 21
OK
r = "MMIX"
OK
r = "MDCLXVI"
OK
r = "MMMDCCCLXXXVIII"
*stop
``````

### The complete listing

``````*type all parts

1.0    arabic(0) = 1000
1.01   arabic(1) = 900
1.02   arabic(2) = 500
1.03   arabic(3) = 400
1.04   arabic(4) = 100
1.05   arabic(5) = 90
1.06   arabic(6) = 50
1.07   arabic(7) = 40
1.08   arabic(8) = 10
1.09   arabic(9) = 9
1.1    arabic(10) = 5
1.11   arabic(11) = 4
1.12   arabic(12) = 1

2.0    roman(0) = "M"
2.01   roman(1) = "CM"
2.02   roman(2) = "D"
2.03   roman(3) = "CD"
2.04   roman(4) = "C"
2.05   roman(5) = "XC"
2.06   roman(6) = "L"
2.07   roman(7) = "XL"
2.08   roman(8) = "X"
2.09   roman(9) = "IX"
2.1    roman(10) = "V"
2.11   roman(11) = "IV"
2.12   roman(12) = "I"

5.0    * Main entry point to arabic -> roman converter
5.01   * Input: a (arabic number to convert)
5.02   * Output: r (roman number equivalent of a)
5.025  r = ""
5.026  do part 1
5.027  do part 2
5.03   for i = 0 to 12: do part 6
5.04   done

6.0    for j = 0 while a >= arabic(i): do part 7
6.01   done

7.0    r = r + roman(i)
7.01   a = a - arabic(i)
7.02   done

10.0    demand a
10.01   do part 5
10.02   type r

20.0    do part 5
20.02   if r = re, then type "OK", r; else type "ERROR", r, re
20.03   done

21.0    a = 2009
21.01   re = "MMIX"
21.02   do part 20
21.03   a = 1666
21.04   re = "MDCLXVI"
21.05   do part 20
21.06   a = 3888
21.07   re = "MMMDCCCLXXXVIII"
21.08   do part 20
21.09   done
``````

## Final thoughts

JOSS is a simple but well designed language - it's easy to pick up, has a carefully chosen set of features and does the job it's supposed to do well. Compared to BASIC it seems much more intuitive as a simple language for non-specialists who want to get numeric calculations done quickly. The lack of functions and local variables, plus the heavily interactive nature of the language makes it harder to write larger programs, but given the first version was running in 1963 it's quite an impressive feat of engineering.

PIL, the version of JOSS implemented on MTS, improves the usability of the original language, eg by not requiring a period at the end of each statement. There is enough integration with the operating system to make it usable. It would be interesting to know what type of use it got at UM.

Several languages were inspired by JOSS, including FOCAL on PDP-8s. It's also one of the influences on MUMPS, which is still in use today.

## Further information

Full source code for this program can be found on github.

# PIL - Language features

In the last post we saw some of the history of PIL and how to run it on MTS. We'll now take a closer look at the features of PIL. Examples shown can be entered directly into PIL after starting it with `\$run *pil`.

## Direct mode

PIL starts up in direct mode, where statements entered are immediately executed when you press `RETURN`. `*` is used by PIL as the prompt to enter input. You can use the `TYPE` statement and simple arithmetic expressions to make PIL act as a calculator:

``````* type 22 * 2
22 * 2 =  44.0
* type 2 ** 16
2 ** 16 =  65536.0
``````

On MTS, PIL is case-insensitive for keywords and lines can optionally end with a period. Errors are immediately reported, usually starting with `Eh?`.

``````* TYPE 2+2.
2+2 =  4.0
* TYPE
Eh? IMPROPERLY FORMED STATEMENT
``````

If the last character entered on the line is `-` then input will continue on the next line before it is executed. The prompt will change to `&` to show this continuation. `*` at the start of line can be used to make comments.

`````` * type 1 + 2 + 3 +-
& 5 + 6
1 + 2 + 3 +5 + 6 =  17.0
* * comment here
*
``````

## Variables and types

Variables can be introduced by the optional keyword `SET` followed by a variable name and an assignment.

``````* set a = 2
* b = 3
* type a, b, a+b
a =  2.0
b =  3.0
a+b =  5.0
``````

PIL understands three types: numerical, which are stored as floating point values, character strings and Boolean values.

``````* a = 1 / 3
* b = "PIL"
* c = The True
* type a, b, c
a =  0.3333333
b = "PIL"
c = The True
``````

Booleans constants are ‘The True’ or ‘The False’ - something I've not seen in any other language. Strings can be up to 255 characters long and can be entered with single or double quotes as delimiters. Floats have 7 digits of precision and can be entered with exponential notation. Volume 12 mentions type 9.999999e64 as the maximum value but on the version I'm running it seems 62 is the maximum exponent.

``````* type 9.999999e62
9.999999e62 =  9.999999E+62
* type 9.999999e63
Eh? EXPONENT OUT OF RANGE
``````

Variable names are up to 8 characters long, are case sensitive and are distinguished from keywords, as this silly example shows.

``````* set set = 1
* set SET = 2
* type set, SET
set =  1.0
SET =  2.0
``````

Arrays are allowed with any number of dimensions, though once set the number of dimensions cannot be changed.

``````* x(1, -2) = 3
* x(3, 44.0) = 4
* type x
x(1,-2) =  3.0
x(3,44) =  4.0
* x(3,4,5) = 42
Eh? ??-
``````

## Expressions

Arithmetic expressions work mostly as expected. The absolute value can be taken by surrounding an expression with `|`; exponentiation is done with `**`. Functions for arithmetic operations such as square root, cosine, log etc generally have a short and long form and do not need parentheses unless needed to resolve ambiguities.

``````* type 1 + |-41|
1 + |-41| =  42.0
* type the square root of 9
the square root of 9 =  3.0
* type sqrt of 16
sqrt of 16 =  4.0
* type sqrt of 25+1
sqrt of 25+1 =  6.0
* type sqrt of (25+1)
sqrt of (25+1) =  5.09902
``````

There are functions for min/max, random numbers and extracting parts of a number, as well as special functions to get time (in 300ths of seconds since midnight), date, cpu/elapsed time and storage used.

``````* type the min of (1,2,3)
the min of (1,2,3) =  1.0
* type the time, the date, the elapsed time, the cpu time
the time =  1.963908E+07
the date =  17133.0
the elapsed time =  7.819461E+07
the cpu time =  464.0
* type the total size, the size
the total size =  188.0
the size =  165.0
``````

Boolean functions are similar to other languages, with `#` standing in for logical or.

``````* type 2 >= 3
2 >= 3 = The False
* type 2 \$lt 3
2 \$lt 3 = The True
* type the true # the false
the true # the false = The True
* type the true & the false
the true & the false = The False
``````

Character expressions include length, case conversion, comparison and extraction.

``````* type the l of "abc"
the l of "abc" =  3.0
* type the upper of "abC"
the upper of "abC" = "ABC"
* type the first 3 characters of "abcde"
the first 3 characters of "abcde" = "abc"
* type 2 \$fc "abcde" + "X" + 1 \$lc "abcde"
2 \$fc "abcde" + "X" + 1 \$lc "abcde" = "abXe"
* type "aaa" > "AAA"
"aaa" > "AAA" = The False
``````

Finally, the type of an expression can be found and run time evaluation performed.

``````* type the mode of 42, the mode of the true, the mode of "abc"
the mode of 42 =  1.0
the mode of the true =  2.0
the mode of "abc" =  3.0
* type the value of "2*21"
the value of "2*21" =  42.0
``````

## Control flow

There's an `IF` statement with an optional `ELSE` clause. The `IF` and `ELSE` can be omitted but the punctuation is required.

``````* if 1 < 2, then type 'yes'; else type 'no'
yes
* if 1 > 2, type 'yes'; type 'no'
no
``````

For loops have a range or an increment and an optional clause to terminate.

``````* for i = 1 to 5: type i ** 2
i ** 2 =  1.0
i ** 2 =  4.0
i ** 2 =  9.0
i ** 2 =  16.0
i ** 2 =  25.0
* for i = 1 by 2 to 10: type i ** 2
i ** 2 =  1.0
i ** 2 =  9.0
i ** 2 =  25.0
i ** 2 =  49.0
i ** 2 =  81.0
* for i = 1 by 2 while i < 20: type i
i =  1.0
i =  3.0
i =  5.0
i =  7.0
i =  9.0
i =  11.0
i =  13.0
i =  15.0
i =  17.0
i =  19.0
``````

## Indirect mode

Lines entered that start with a number are treated as stored instructions, broken down by part and step, that can be run later. Here we define a program in part 1 consisting of four steps.

``````* 1.0 i = 5
* 1.1 type i
* 1.2 i = i * i
* 1.3 type i
``````

This can then be run with `DO`, which will execute all steps in a part.

``````* do part 1
i =  5.0
i =  25.0
``````

Variables set in a program remain after execution is completed and it is possible to run a single step at a time.

``````* type i
i =  25.0
* do step 1.2
* do step 1.3
i =  625.0
``````

Steps can call other parts with `DO`: execution will resume after the part is finished. It's also possible to transfer execution with `TO` which will not return. `DONE` will return from the current part.

``````* type part 2, part 3

2.0    i = 3
2.1    do step 3
2.2    i = 5
2.3    do step 3
2.4    i = 7
2.5    to step 3
2.6    type 'not reached'

3.0    type i
3.1    done
3.2    type 'also not reached'

* do part 2
i =  3.0
i =  5.0
i =  7.0
``````

The above also shows it is possible to list out programs with `TYPE PART`. You can see all entered parts with `TYPE ALL PARTS`. With `TYPE ALL STUFF` you will see all variables and parts defined.

`DELETE` can be used to remove a step or a variable definition.

## I/O and system access

We've seen `TYPE` used to display variables. You can prompt for a value to be entered with `DEMAND`

``````* delete part 1
* 1.0 demand i
* 1.1 i = i * 10
* 1.2 type i
* do part 1
i = ?_ 3
i =  30.0
``````

There is also a formatting facility for input and output that is covered in the manual.

Programs can be saved to disk for future list with `SAVE`. This takes a file name (which must already exist) and what to save. For example:

``````save as 'x.pil', all stuff
``````

will save all parts and variables to `x.pil`. File format is plain text so it can be edited outside of PIL if needed. `LOAD 'x.pil'` will then load the file back into PIL.

The implementation of PIL on MTS includes access to system facilities such as file creation and device access. For example, `CREATE 'x.pil'` will create a new file.

In the next post we'll see how to construct a larger PIL program.

## Further information

MTS volume 12 has a complete reference to PIL as implemented on MTS.

If you can find a copy of the book ‘History of Programming Languages’, edited by Richard Wexelblat, this has a great section about JOSS talking about its design principles.

# PIL - Introduction Screenshot of a teletypewriter running PIL, from 2 4 Using the Michigan Terminal System 6 01.

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
* 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.

The Pitt time-sharing system for the IBM system 360: Two year's experience. describes the operation of PIL at the University of Pittsburgh.

JOSS: Introduction To A Helpful Assistant is a very readable paper from the RAND Corporation on the language that inspired PIL.

The source code and implementation notes for PIL can be found in component 566 on tape 6.0T2 in the D6.0 distribution.

Source code for the hello world program can be found on github.

# RATFOR & FLECS - Emirp primes

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 `goto`s.

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.

# RATFOR & FLECS - Language Features 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
}
``````
``````      SELECT (X)
(1) Y=3
(2) Y=5
(3) Y=5
(OTHERWISE) Y=0
FIN
``````

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.

← Previous  Next →