For our ALGOL 60 sample program we’ll have a quick look at implementing a solution to the Josephus problem.
The problem
Imagine n prisoners standing in a circle, numbered sequentially from 0 to n-1. An executioner walks around the circle, starting at prisoner 0, and kills every kth prisoner, removing them from the circle. As this continues, the circle becomes smaller and smaller; the last prisoner remaining is freed.
An example for n=11 prisoners and k=3. Prisoners 2, 5 and 8 will be killed on the first trip round the circle. On the second trip, it’s goodbye to prisoners 0, 4 and 9. Continuing this leaves prisoner 6 as the survivor.
The task, given any n and k (both which should be greater than zero), find the prisoner who is freed.
See Rosetta Code for a full description and sample code in other languages.
Solution
There’s a fairly simple iterative solution to the main function to determine the last survivor:
'COMMENT' Find surviving prisoner from circle of N;
'COMMENT' after killing each Kth prisoner;
'INTEGER' 'PROCEDURE' JOSEPHUS(N, K);
'VALUE' N, K;
'INTEGER' N, K;
'BEGIN'
'INTEGER' INDEX, PRISONER;
PRISONER := 0;
'FOR' INDEX := 1 'STEP' 1 'UNTIL' N 'DO'
'BEGIN'
JOSEPHUS := PRISONER := MODULUS(PRISONER + K, INDEX);
'END'
'END'
It would seem that we could just use JOSEPHUS as the variable and not need PRISONER but ALGOL only allows return value variables to appear on the left hand side of an expression.
Next, we need to define the MODULUS function to give the remainder after dividing two integers
'COMMENT' Finds the remainder of X and Y for positive values;
'INTEGER' 'PROCEDURE' MODULUS(X, Y);
'VALUE' X, Y;
'INTEGER' X, Y;
'BEGIN'
MODULUS := X - (Y * (X '/' Y));
'END'
Note that we use the integer division operator ÷ (written as '/' in the IBM hardware representation) as the regular division operator / on integers will round either up or down depending on the value.
Finally, for the main program we can call the JOSEPHUS function and output the integer result: we use 41 prisoners and k=3:
'BEGIN'
'COMMENT' Main program;
OUTINTEGER(1, JOSEPHUS(41, 3));
'END'
So writing this was fairly easy. Quoting all keywords still feels unnatural so I normally put them in after writing out the program in full. I had to look up the difference between the two division operators but expressing the main flow was simple. What tripped me up on first compile was the comment syntax: I was using COMMENT abc which the compiler appeared to accept but would sometimes swallow the next line. Fixing this to quote the COMMENT and terminate the line with a semicolon fixed this (eg 'COMMENT' abc;).
Running the solution
Let’s see how this looks when we compile and run the program. Don’t forger to destroy or empty the output object file first otherwise you will get strange results after each recompile.
# $destroy -load
File "-LOAD" has been destroyed.
# $run *algol scards=josephus.al spunch=-load
Execution begins 21:39:02
i
LEVEL 1JUL67 OS ALGOL F DATE FEB 05 1915
SOURCE PROGRAM PAGE 001
SC SOURCE STATEMENT
00000 'BEGIN'
00000 'COMMENT' Finds the remainder of X and Y for positive values;
00000 'INTEGER' 'PROCEDURE' MODULUS(X, Y);
00001 'VALUE' X, Y;
00002 'INTEGER' X, Y;
00003 'BEGIN'
00003 MODULUS := X - (Y * (X '/' Y));
00004 'END'
00004
00004 'COMMENT' Find surviving prisoner from circle of N;
00005 'COMMENT' after killing each Kth prisoner;
00005 'INTEGER' 'PROCEDURE' JOSEPHUS(N, K);
00006 'VALUE' N, K;
00007 'INTEGER' N, K;
00008 'BEGIN'
00008 'INTEGER' INDEX, PRISONER;
00009 PRISONER := 0;
00010 'FOR' INDEX := 1 'STEP' 1 'UNTIL' N 'DO'
00010 'BEGIN'
00010 JOSEPHUS := PRISONER := MODULUS(PRISONER + K, INDEX);
00011 'END'
00011 'END'
00011
00011 'COMMENT' Main program;
00012 OUTINTEGER(1, JOSEPHUS(41, 3));
00013 'END'
i
IDENTIFIER TABLE PAGE 002
PBN SC PBN NAME TYPE DM DSP NAME TYPE DM DSP NAME TYPE DM DSP
SURR PR LN PR LN PR LN
001 00000 000 JOSEPH I P 02 074 MODULU I P 02 070
002 00000 001 MODULU I P 02 070 X I V 020 Y I V 028
003 00005 001 INDEX I 030 JOSEPH I P 02 074 K I V 028
N I V 020 PRISON I 034
i
STORAGE REQUIREMENTS (DECIMAL) PAGE 003
OBJECT MODULE SIZE 840 BYTES.
DATA STORAGE AREA SIZES
PBN BYTES PBN BYTES PBN BYTES PBN BYTES PBN BYTES
001 36 002 48 003 68
Execution terminated 21:39:02 T=0.016
# $run -load+*algollib
Execution begins 21:39:10
+30
END OF ALGOL PROGRAM EXECUTION
Execution terminated 21:39:10 T=0.003
We can see in the compiler identifier table output that although we can use long identifiers they are truncated to six characters.
Final thoughts on ALGOL 60
There’s a lot to like in ALGOL 60: the language definition is more precise than the informal descriptions used before; the block structure and variable scoping; the powerful control statements. As a programmer with a C background it appears much more familiar then FORTRAN.
However it does feel like an interim release rather than a fully developed language; I think this is partially due to its academic origins rather than from a computer company looking to sell its hardware. The different language representations and call-by-name are a bit too clever and the emphasis on machine independence went too far - the lack of any I/O means that no useful program could be portable. Another factor not evident when running MTS today is that the FORTRAN development environment and compilers are much more advanced so for any computationally expensive workload you would have been better off with FORTRAN.
With the experience of implementing compilers in ALGOL 60, the group responsible for the original report looked at what changes should be made for the next version of ALGOL. In the next post I’ll look at one of these, ALGOL W.
As mentioned in the previous post, ALGOL was designed by an international committee of computer scientists: their goal was to create a universal language for expressing algorithms without recourse to hardware specific features.
The result of their discussions was the ALGOL 60 Report, with a slightly corrected version known as the Revised Report released a few years later. This was one of the first attempts to formally specify a language, using Backus-Naur form (JW Backus and Peter Naur were both on the ALGOL committee).
Representations
ALGOL was intended as a universal language, but at the time of its creation there was no universal character set such as Unicode or even ASCII; as a result, the report defines three representations of the language:
A reference language used by the committee in the report to describe ALGOL
Publication language, which modifies the reference language for use in handwritten or published documents
Hardware representations, which modifies the reference language based on the character set of the hardware it is running on.
An example is the exponentiation operator.
In the reference language the upwards facing arrow ↑ is used, eg a ↑ b.
In the publication language, superscript is used for the exponent, eg ab.
In the hardware representation used by IBM, 'POWER' or ** is used, eg a ** b.
One of the most important differences in representation were basic symbols like begin or end. In the reference and publication language these were usually underlines, eg begin. In many hardware representations, including IBM’s, they were enclosed in quotes eg 'BEGIN',
A summary of the differences between the reference language and the IBM hardware representation is shown below; in some cases there is more than once choice for the hardware representation.
The “₁₀” is a scale factor like “E” in the C language, eg 1234 could be expressed as “1.234₁₀3”;
In the rest of this post, I will use the reference language in the body text and the IBM hardware representation in the monospaced examples.
Blocks and statements
ALGOL 60 is free format, with statements separated by semicolons. It is possible to group statements into a block, marked by begin and end and optionally labeled. Comments are started with the keyword comment and ended with a ;; this is not needed at the end of blocks.
'COMMENT' Block example;
'BEGIN'
X: 'BEGIN'
FN1(10);
FN1(20);
'END' this is the end of the X block
'END' and this the unnamed block
Types and variables
ALGOL 60 supports integer, real and Boolean types and variables. There is no restriction in the standard on variable name length, but *ALGOL limits them to six characters. All variables must be declared with their types and variable lifetime is the block. The standard does define a storage class called own, similar to C’s static, but this is not supported by the IBM compiler.
The below will print “3 4 3”:
'BEGIN'
'INTEGER' I, J;
I := 3;
OUTINTEGER(1, I);
'BEGIN'
'INTEGER' I;
'BOOLEAN' STATE;
I := 4;
OUTINTEGER(1, I);
'END'
OUTINTEGER(1, I);
'END'
Multi-dimensional arrays can be declared, with variable bounds:
Strings are supported only as constant parameters to function; it is not possible to declare string variables.
OUTSTRING(1, '('This is a string')');
Expressions
The usual arithmetic expressions are available; it’s possible to include an if/else in such expressions. Unusually, Boolean expressions include operators for implies and equivalent.
'BEGIN'
'INTEGER' X, Y;
'BOOLEAN' A, B;
X := 3;
Y := 4;
A := 'TRUE';
B := 'FALSE';
OUTINTEGER(1, X + Y - ('IF' A 'OR' B 'THEN' 1 'ELSE' 2));
'END'
The above will print 6.
Control statements
As well as the regular goto statement, the switch statement allow selection of a statement label to jump to.
if … then … else can be used for conditional execution. Another if cannot follow the then unless it is contained in a block. to prevent ambiguity of which if then else is attached to.
The for statement is used for repetition: there are three forms:
Procedures support pass by value or pass by name parameters. Pass by value is familiar: a copy is made of the parameter for those identified with value in the procedure preamble. The following will print 42:
Pass by name is similar to pass by reference in C++ or Java in that it allows output to be made to parameters; it also enables Jensen’s device. Take for example the below:
'BEGIN'
'PROCEDURE' INC(A, B);
'INTEGER' A, B;
'BEGIN'
A := A + 1;
B := B + 1;
'END'
'INTEGER' 'ARRAY' X(/10/);
'INTEGER' J;
X(/1/) := 10;
X(/2/) := 20;
J := 1;
INC(J, X(/J/));
OUTINTEGER(1, J);
OUTINTEGER(1, X(/2/));
'END'
This will print 2 (as J has been incremented by the line A := A + 1) and 21 (as X[J] was passed in by name, it was evaluated as X[2] in the line B := B + 1).
Standard functions and input/output
There are a small number of standard functions defined by the language, mostly mathematical (eg ABS, SIGN, SQRT, SIN, LN). The ENTIER function accepts a real number as parameter and returns an integer.
No input/output functions are defined at all in the standard, due to the desire for the language to be device independent and as at the time there was no common agreement of what input/output facilities could be provided across all available hardware. The IBM compiler provides PUT and GET for low level I/O, SYSACT for stream manipulation and a number of functions for input or output of variables, eg ININTEGER, OUTREAL.
In this series of posts we’ll look at ALGOL, one of the foundational languages for modern programming, and the ALGOL 60 implementation on MTS.
The ALGOL family of languages and ALGOL 60
Most programming languages spring from a single person or a company; ALGOL is fairly unique in that it was originally designed by a committee of European and American computer scientists in the mid 1950s. The first version to be widely implemented was ALGOL 60, which is what we will look at here. A large number of improvements to ALGOL 60 were proposed or developed: one, by Niklaus Wirth, became ALGOL W. The next standardised version was ALGOL 68 but this was not widely adopted.
Although it is not in wide use today, ALGOL introduced many influential concepts to programming languages, such as block scoping, so can be considered the ancestor of languages such as Pascal and C.
ALGOL 60 on MTS
The ALGOL compiler provided by IBM for S/360 machines is available on MTS under the name *ALGOL. This follows the ALGOL 60 specification fairly closely. A number of small changes were made to the compiler to make it work under MTS.
There are also ALGOL W and ALGOL 68 compilers on MTS, the former being the most popular version in use at the time of D6.0; I will look at these in later posts.
Prerequisites
No special installation instructions to get ALGOL 60 running - just do the standard D6.0 setup as described in this guide and then sign on as a regular user such as ST01.
Compiling using *ALGOL
*ALGOL will read source from scards (by default *source* ie standard input) and will write object files to spunch. Unlike some other MTS compilers you need to provide a filename for spunch as there is no default.
One gotcha is that *ALGOL will not clear out the file used for writing object code, so you need to run $empty on it before recompiling each time.
Other *ALGOL compilation parameters are listed in MTS Volume 2X.
To run ALGOL 60 programs you need to concatenate the runtime library *ALGOLLIB with the object file. Say your object code is in the file -load then to run it you would do:
# $run -load+*algollib
Hello world
Here’s a simple program to print “Hello, world!” five times.
'BEGIN'
'COMMENT' Hello World program for ALGOL 60;
'INTEGER' I;
'FOR' I := 1 'STEP' 1 'UNTIL' 5 'DO'
'BEGIN'
OUTSTRING(1, '('Hello, world!')');
SYSACT(1, 14, 1);
'END'
'END'
Indentation is not important except to make the program readable. Note the quotation marks around keywords like 'INTEGER' to distinguish them from identifiers: this is known as stropping.
OUTSTRING and SYSACT are not part of ALGOL 60 but are library functions provided by IBM; the SYSACT call here is used to start a new line on the output device.
Here’s a terminal log of how to compile and run the program. This assumes the source code is in file hello.al.
# $run *algol scards=hello.al spunch=-load
i
LEVEL 1JUL67 OS ALGOL F DATE JAN 11 1915
SOURCE PROGRAM PAGE 001
SC SOURCE STATEMENT
00000 'BEGIN'
00000 'COMMENT' Hello World program for ALGOL 60;
00000 'INTEGER' I;
00001 'FOR' I := 1 'STEP' 1 'UNTIL' 5 'DO'
00001 'BEGIN'
00001 OUTSTRING(1, '('Hello, world! ')');
00002 SYSACT(1, 14, 1);
00003 'END'
00003 'END'
i
IDENTIFIER TABLE PAGE 002
PBN SC PBN NAME TYPE DM DSP NAME TYPE DM DSP NAME TYPE DM DSP
SURR PR LN PR LN PR LN
001 00000 000 I I 018
i
STORAGE REQUIREMENTS (DECIMAL) PAGE 003
OBJECT MODULE SIZE 540 BYTES.
DATA STORAGE AREA SIZES
PBN BYTES PBN BYTES PBN BYTES PBN BYTES PBN BYTES
001 48
# $run -load+*algollib
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
END OF ALGOL PROGRAM EXECUTION
If you change the source file and want to recompile, remember to do %empty -load.
Further information
As always, the Wikipedia page provides a good overview of the language’s history and key features.
There is no MTS Volume on ALGOL 60 (there is one on ALGOL W, however). Some information on the *ALGOL compiler and *ALGOLLIB library can be found in the MTS Volume on obsolete commands.
The original IBM documentation for the compiler can be found at Bitsavers.
For our sample program in MAD, let’s look at a classic sorting algorithm, mergesort.
The task
The task description and pseudo-code can be found at Rosetta Code.
The implementation
Let’s write this as two parts: a driver program to set up a vector of integers and the actual function to do mergesort. The mergesort function should be able to take any vector size as input.
The driver
For the driver we’ll just define a vector of integers and call the mergesort function. We could extend this to read the integers from a file if needed. We’ll also need a way to display the vector before and after the sort, which we’ll factor into a separate internal function.
CONSTANT LENGTH = 15
VECTOR VALUES NUMBERS = 9, 12, 3, 1, 6, 8, 2, 5, 14, 13, 11, 7, 10, 4, 0
PRINT COMMENT "Before sort"
DISPLAY.(NUMBERS, LENGTH)
MERGESRT.(0, LENGTH, NUMBERS)
PRINT COMMENT "After sort"
DISPLAY.(NUMBERS, LENGTH)
* Display each value in V
INTERNAL FUNCTION DISPLAY.(V, LEN)
INTEGER V(1), LEN
LOOP FOR I=0, 1, I >= LEN
PRINT RESULTS V(I)
END LOOP
FUNCTION RETURN
END OF FUNCTION
END OF PROGRAM
The mergesort function
The mergesort function is recursive, so we need to use an external function with the RECURSIVE keyword to signal this fact to the MAD compiler so it can set up a dynamic call stack. If we don’t do this, the program will still compile but will die with a protection exception when run.
RECURSIVE EXTERNAL FUNCTION MERGESRT.(LEFT, RIGHT, V)
INTEGER LEFT, RIGHT, V(1)
INTEGER LSTART, LEND, RSTART, REND
* Return if nothing left to sort
IF (RIGHT - LEFT <= 1)
FUNCTION RETURN
END IF
* Define left and right halves
LSTART = LEFT
LEND = (LEFT + RIGHT) / 2
RSTART = LEND
REND = RIGHT
* Recursively sort left and right
MERGESRT.(LSTART, LEND, V)
MERGESRT.(RSTART, REND, V)
* Merge two halves
MERGE.(LSTART, LEND, RSTART, REND, V)
FUNCTION RETURN
* Merge function definition will go here; see below
END OF FUNCTION
END OF PROGRAM
Note that we define V as having length 1: this is to allow us to subscript V, but it’s up to us to manage its actual length.
External functions need an END OF PROGRAM as well, as they are effectively compiled separately from the main routine even if they are in the same file.
Next we need the merge subroutine. As this is private to MERGESRT., we can define it as an internal function of MERGESRT.: the definition can go inside the external function definition as shown above. The code looks like this.
INTERNAL FUNCTION MERGE.(LSTART, LEND, RSTART, REND, V)
INTEGER LSTART, LEND, RSTART, REND, V(1)
INTEGER LLEN, RLEN, I, L, R
POINTER LHALF, RHALF, LHP, RHP
CONSTANT SZ = 4
* Allocate a temp buffer for each half
LLEN = LEND - LSTART
RLEN = REND - RSTART
ALLOCATE (LLEN * SZ)->LHALF
ALLOCATE (RLEN * SZ)->RHALF
* Copy values into temporaries
LHP = LHALF
LOOP FOR I=LSTART, 1, I >= LEND
LHP.IND.INTEGER = V(I)
LHP = LHP + SZ
END LOOP
RHP = RHALF
LOOP FOR I=RSTART, 1, I >= REND
RHP.IND.INTEGER = V(I)
RHP = RHP + SZ
END LOOP
* Merge values back into V
L = 0
R = 0
LHP = LHALF
RHP = RHALF
LOOP FOR I=LSTART, 1, L >= LLEN .OR. R >= RLEN
* If left < right, move left, else move right
IF (LHP.IND.INTEGER < RHP.IND.INTEGER)
V(I) = LHP.IND.INTEGER
L = L + 1
LHP = LHP + SZ
ELSE
V(I) = RHP.IND.INTEGER
R = R + 1
RHP = RHP + SZ
END IF
END LOOP
* Handle leftover values
LOOP WHILE L < LLEN
V(I) = LHP.IND.INTEGER
I = I + 1
L = L + 1
LHP = LHP + SZ
END LOOP
LOOP WHILE R < RLEN
V(I) = RHP.IND.INTEGER
I = I + 1
R = R + 1
RHP = RHP + SZ
END LOOP
RELEASE LHALF
RELEASE RHALF
FUNCTION RETURN
END OF FUNCTION
We need a temporary buffer to hold each half of V while merging. MAD does not allow run time dimensioning of vectors, so we can either make these fixed size or use dynamic memory. I’ve chosen the latter to make the solution more generic: we allocate a chunk of memory which is determined by the size of each half and SZ which is a constant denoting the size of a MAD integer in bytes:
ALLOCATE (LLEN * SZ)->LHALF
We then use pointer arithmetic to read or write integers, eg
RHP.IND.INTEGER = V(I)
RHP = RHP + SZ
At the end of the function we must remember to free the allocated memory with RELEASE.
Putting it together
We’re now ready to compile and run the program.
# $run *gom scards=mergesrt.mad
No errors in MAIN
No errors in MERGESRT
No errors in MAIN
# $run -load
Before sort
NUMBERS(0) = 9
NUMBERS(1) = 12
NUMBERS(2) = 3
...
NUMBERS(12) = 10
NUMBERS(13) = 4
NUMBERS(14) = 0
After sort
NUMBERS(0) = 0
NUMBERS(1) = 1
NUMBERS(2) = 2
...
NUMBERS(12) = 12
NUMBERS(13) = 13
NUMBERS(14) = 14
Final thoughts on MAD
Given that it was first designed at about the same time as FORTRAN, MAD feels much more powerful and flexible to use, while still having an easily understandable syntax, by borrowing ideas from early discussions of ALGOL/IAL. It’s a pity it did not see wider adoption as it might have made FORTRAN programmer’s lives easier.
The introduction to the GOM manual has an interesting section on why it was re-implemented for MTS:
This language is currently known as GOM (for Good Old MAD)
to distinguish it from the ill-fated MAD/1 which was designed as
an extensible language and which, although it provided many
ideas which have been borrowed, was incredibly ugly and never
extended itself into usability.
Why at this point in time resurrect the language? The main
impetus was that a language was needed for writing Grungy Little
Programs and pieces of the operating system. In both cases, the
programs are often machine-dependent, and require matching
previously established data structures and interfaces that are
not “standard”, in the sense of being used by most high-level
languages. Fortran is not usually general enough, and PL1
suffers from its “environment” and in many cases is not general
enough either.
Rather than invent a new language, MAD was chosen as a base
because it provided a happy medium between the insufficiencies
of Fortran and the excesses of PL1, and because the semantic
implications of the various constructs were known. That did
not, however, prevent judicious tailoring of the language to
better fit the current programming practices and problems.
In this post we’ll look at the MAD language (as implemented by GOM on MTS) in more detail. As MAD is quite a large language, compared to FORTRAN or BASIC, I will only go over some of its interesting or unusual features; for a good overview of the language you may want to read the Wikipedia article first.
Complete examples below (those ending with END OF PROGRAM) have been tested using *GOM on MTS D6.0; do note that initial blanks are needed for lines which do not contain statement labels.
Synonyms and abbreviations
There are a number of synonyms for MAD keywords, (eg TRANSFER TO for GO TO, and spaces are not generally needed. For example a goto label X could be expressed as:
GOTO X
GO TO X
TRANSFER TO X
TRANSFERTOX
GOTOX would not work as this is ambiguous, as it could be a references to the variable GOTOX. The TRANSFERTOX works as this is longer than a variable name could be.
Keywords can be abbreviated by taking the first and last letter and inserting a ': for example T'O for TRANSFER TO’. The compiler can optionally produce a listing of the input with these expanded by setting the sprint parameter.
Constant qualification
Constants can be qualified to a particular type or format with a suffix: some examples
1B - Boolean true
100@X - hex integer 100, ie decimal 256.
12@L1 - one byte integer
"HI"@L4 - character string of length 4, right padded with blanks
Statement label vectors
As well as simple labels as seen in FORTRAN, you can have vectors of statement labels. For example, the below will print “Two”:
INTEGER J
J = 2
GOTO L(J)
L(1) PRINT COMMENT "One"
GO TO DONE
L(2) PRINT COMMENT "Two"
GO TO DONE
L(3) PRINT COMMENT "Three"
DONE END OF PROGRAM
Conversions
MAD does implicit conversions by assigning to a variable of different type. You can cast to another type without conversion using .AS.
INTEGER IM, C1, C2
FLOATING POINT F
F = 123.45
IM = F
C1 = F.AS.INTEGER
C2.AS.FLOATING POINT = F
PRINT RESULTS IM, C1, C2
END OF PROGRAM
This will print:
IM = 123 C1 = 1115386675 C2 = 1115386675
Character expressions and strings
Strings are expressed as character vectors; sub-elements of a vector can be addressed using block notation, with ... standing for range, with optional first and last subscript, and | for length. For example:
CHARACTER HELLO(5),H1(3),H2(2)
HELLO(...) = "Hello"
H1(...) = HELLO(2...4)
H2(...) = HELLO(1|2)
PRINT COMMENT H1
PRINT COMMENT H2
END OF PROGRAM
Will print “llo” and “el”.
Conditionals
The conditional statement allows else clauses and there is an implicit block structure for each clause:
INTEGER X
X = 3
IF X > 1 .AND. X <= 3
PRINT COMMENT "Yes"
PRINT RESULTS X
ELSE
PRINT COMMENT "No"
END IF
END OF PROGRAM
Loops
As we saw in the hello world example, the basic loop takes an initial variable, increment and condition which when true will mark the end of the loop. So the below will print “Hello, world!” five times
LOOP FOR I=1, 1, I > 5
PRINT COMMENT "Hello, world!"
END LOOP
END OF PROGRAM
There is also a THROUGH variant which is more like a FORTRAN loop: this will print “Hi” and “There” five times then “Done”.
THROUGH L, FOR I=1,1,I>5
PRINT COMMENT "Hi"
L PRINT COMMENT "There"
PRINT COMMENT "Done"
END OF PROGRAM
and a LOOP WHILE b and LOOP UNTIL b which implement while loops.
Dynamic memory and pointers
Addresses can be referenced with .LOC. and indirection done with .IND. followed by the type (which does not have to be the original type of what is being pointed to). It’s possible to do pointer arithmetic also. The below example will print the result “42”.
INTEGER Q
Q = 0
POINTER P
P = .LOC.Q
P.IND.INTEGER = 42
PRINT RESULTS Q
END OF PROGRAM
A dynamic record defines a simple structure made up of data fields
DYNAMIC RECORD (POINT) X,Y
INTEGER X, Y
To use this you need to point it at an existing memory location or allocate new space with ALLOCATE and free memory with RELEASE. You can then use : to address fields:
DYNAMIC RECORD (POINT) X,Y
INTEGER X, Y
POINTER P
ALLOCATE (POINT)->P
P:X = 10
P:Y = 20
PRINT RESULTS P:Y / P:X
RELEASE P
END OF PROGRAM
The USING keywords allows you to select a pointer, after which references to a record’s fields will be done through that pointer. As an example, the above could be rewritten as:
DYNAMIC RECORD (POINT) X,Y
INTEGER X, Y
POINTER P
ALLOCATE (POINT)->P
USING POINTER P, FOR POINT
X = 10
Y = 20
PRINT RESULTS Y / X
RELEASE P
END OF PROGRAM
The USING will continue until another USING replaces it, or you do STOP USING POINTER.
Functions
MAD supports external (definition can provided at link time) or internal functions. Functions have a number of interesting features:
Names must end with a . so they are recognised by the parser
Optional or variable number of arguments are supported
It’s possible to have multiple entry points into a function
As well as a return value it’s possible to set a return code to signal unusual conditions
A example showing how an internal function can be defined and called, using the return code feature:
INTERNAL FUNCTION NEGATE.(X)
NORMAL MODE IS INTEGER
IF X = 0
FUNCTION RETURN 0, RC=1
ELSE
FUNCTION RETURN - X
END IF
END OF FUNCTION
INTEGER X, X1, R
READ DATA FROM UNIT 5
X1 = NEGATE.(X)->R
IF R = 0
PRINT COMMENT "Zero is still zero"
ELSE
PRINT RESULTS X1
END IF
END OF PROGRAM
Input/output
In the previous example, the READ DATA FROM UNIT 5 requires that you set unit 5 to a FDname on the run command. But how does it know where to put the input? It expects the input format to be a set of name=value pairs separated by commas and terminated by an asterisk, which it will use to set variables. So a sample run of the above program, taking input from the keyboard, would look like: