archive.michigan-terminal-system.org is currently down, so I’ve replaced links on this site with links to archive.org. Here is a recent snapshot of the site and here is a copy of the MTS distribution that can be easily downloaded.
Please leave a comment or email me if anything is still broken.
In this single part post, we’ll take a quick look at PL360, a low level programming language targeted at the System/360.
PL360
PL360 was initially designed by Niklaus Wirth at Stanford in the late 1960s. He was working on a successor to ALGOL and was unsatisfied with the choices of implementation languages: assembler or FORTRAN. Quoting from his original paper, he wanted a tool which would:
(1) allow full use of the facilities provided by the 360 hardware, (2) provide convenience in writing and correcting programs, and (3) encourage the user to write in a clear and comprehensible style.
As a result, he and his team came up with PL360. As the name suggests, the language is tied to the architecture of the System/360, providing direct access to opcodes like STC and the need to specify which registers to use. It also contains high level features like IF ... THEN conditionals and WHILE ... DO loops, plus syntax to make common tasks easier, eg R1 := R1 + R2 + R3.
PL360 on MTS
PL360 on MTS seems to date back to at least distribution 1.0. The version was have on the latest distribution 6.0 has the following note in the drive file listing under component 108:
This version of PL360 is based on one received from Stanford in late 1977 and installed at UM in January, 1978. The latest versions are distributed by UBC as part of the SPIRES distribution.
PL360 is installed on MTS as *PL360. The compiler takes input from SCARDS and writes an object file to SPUNCH. For example:
$RUN *PL360 SCARDS=input SPUNCH=object
At run time, to use the standard I/O library you need to add *PL360LIB to the $RUN command, ie:
$RUN object+*PL360LIB
Note that documentation linked below mentions that some procedures in the standard library are not available on MTS, such as VALTOBCD which converts a number to an EBCDIC representation.
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.
Hypotenuse program
Our sample program will calculate the hypotenuse of a right angled triangle: this is taken from the PL360 textbook linked below. The original version used VALTOBCD etc which is not available, so we’ll supply the input values in the main program and use the MTS debugger to view the results.
The program uses Newton’s methods to take square roots R'' = ((X/R') + R')/2:
where R’ is an approximation for the root of X, and R’’ is the next approximation. When the difference between R’ and R’’ becomes very small, R’’ is the assumed root of X. The first approximation of R’ is taken to be X with its exponent cut in half. Thus, the first approximation for the root of 128 would be 8. Likewise, 1/8 would be the approximation for 1/128. (128 = #80)
The procedure to calculate square roots looks like below. The format is mostly unchanged; I’ve added line numbers in the first two columns which would not be present in the actual program text.
1 PROCEDURE SQRT (R14); IF F01 > 0L THEN
2 |-- THIS PROCEDURE TAKES THE SQUARE ROOT OF THE VALUE IN F01 --|
3 BEGIN LONG REAL TEMPCELL; |-- TEMPORARY STORAGE --|
4 LONG REAL REGISTER DIFF SYN F67, |-- TEMPORARY --|
5 APROX1 SYN F23, APROX2 SYN F45; |-- REGISTERS --|
6 TEMPCELL := F01; R1 := R1-R1;
7 |-- COMPUTE HALF THE ORIGINAL EXPONENT, EXCESS 64 NOTATION --|
8 IC(R1,TEMPCELL); R1 := R1 - #40S SHRA 1 + #40S;
9 STC(R1,TEMPCELL); APROX2 := TEMPCELL; DIFF := 2L;
10 WHILE DIFF > 10'_6L DO |-- FIND SQRT OF F01 --|
11 BEGIN APROX1 := APROX2; |-- NEW APPROXIMATION FROM OLD --|
12 APROX2 := F01/APROX1 + APROX1 / 2L;
13 DIFF := APROX2 - APROX1; DIFF := ABS DIFF;
14 END; F01 := APROX2; |-- REPLACE F01 BY SQRT OF F01 --|
15 END;
The procedure implicitly takes its input and returns output to floating point register 0. R14 is used to denote the register containing the address to jump to on return.
The IF statement will guard against negative input values, skipping the whole procedure unless the input is positive.
The body of the iF starts on line 3 with BEGIN. It creates temporary storage TEMPCELL and provides synonyms for floating point registers to make the code clearer.
Cutting the exponent is done by taking the byte containing the exponent (the ‘characteristic’ in IBM’s language) out of the floating point value (with IC), shifting it right one bit (which divides it by two) and then storing it back into the floating point value. Why is #40S subtracted before the shift and then added back again? I had to look at the ‘Principles of Operation’ for this: turns out characteristic is biased by +64, ie hex 40.
We then use a WHILE ... DO statement to loop the approximation calculation until the difference is less then 10^-6. Note that precedence is strictly left to right, so we can write the approximation calculation on line 12 in that way.
To calculate the hypotenuse, we need to sum the squares of the two sides and call SQRT. This can be easily done:
Note the END is terminated with a period to denote this is the main program.
Compiling and running the program
We compile the program as follows:
$run *PL360 SCARDS=hyp.pl3 SPUNCH=hyp.l
The output shows a listing and a cross-reference table, and ends with NO ERRORS DETECTED so we are good.
To test, we run this under the debugger and print out the floating point registers at the end with DUMP FRS:
$debug hyp.l
Ready
run
User program return.
Ready
dump frs
Floating Point Registers
Hex Decimal
FR0 : 41500000 00000000 5.
FR2 : 41500000 02C4F885 5.00000001031688268
FR4 : 41500000 00000000 5.
FR6 : 3A2C4F88 50000000 .103168826814936665E-07
stop
FR0 contains 5, so it looks like it worked!
Final thoughts
PL360 occupies a unique slot in the set of languages on the System/360: tied closely to the architecture but operating at a higher level then assembler. Apart from Wirth’s work, it did enjoy some use for other applications, eg the SPIRES database system, but was not widely adopted on MTS or other systems.
MTS CCMEMO 75 has an overview of the language and its implementation on MTS.
Copies of the “PL360 Reference Manual” formerly distributed by Stanford can be found on the Internet, eg here. A textbook based on this manual can be found here.
Full source code for this program can be found on github.
For the final post in this series, let’s write a real program in PLUS. Rather than working on a Rosetta Code problem, I’ll use this opportunity to implement a useful program, a version of Unix grep for MTS.
Grep
Grep is a program from Unix to find text in files. The first version was written in around 1974 in PDP-11 assembly language, and is available on pretty much every Unix derived system today. I don’t believe there is an equivalent for MTS - the MTS File Editor does have a rich pattern matching language but is a bit unwieldy to use from the command line.
The requirements for the program are:
input a pattern to search for, which should support some kind of regular expression to match arbitrary text
input a file or list of files to search in
compare the pattern to the contents of the files and print any lines that match.
follow MTS conventions for calling the program.
Pattern matching
The first problem is how can we match patterns? There is no regular expression library like regex in Unix. Looking through Volume 3, on page 366.3 there is a set of routines that look promising:
Three system subroutines, PATBUILD, PATMATCH, and PATFREE, are
available for implementing $FILESTATUS-like pattern-matching capabili-
ties from user programs.
PATBUILD will build a pattern from an input string. The input string
may be of any length and may specify a file name or a generic string.
For example:
File names: “2CYB:data?”, “?.doc”, “2ABC:?”
Generic strings: “Bill R?”, “in the state of ?”
PATMATCH will compare an input string against the pattern built by
PATBUILD. The input string may be of any length.
PATFREE will free the storage used to build the pattern.
Although this is more suited for file wildcard expansion than general text search, we’ll use this as a quick way to get the program running.
The PLUS Source Library
Definition
does not mention these routines, but looking through the actual contents of *PLUS.SOURCELIB there are definitions available. For example for patbuild:
%Include(Numeric_Types);
type Patbuild_Procedure_Type is
procedure
reference parameters Patstring is value unknown,
Strlen is value Integer,
Patworkaddr is pointer to unknown,
optional reference parameters Flags is value bit(32),
Ccid is value character(4),
Chars is value character(2),
end;
/* Values used in Flags word */
constant Patbuild_Filename is '00000000',
Patbuild_Nonfilename is '00000001',
Patbuild_Ignore_Case is '00000000',
Patbuild_Dont_Ignore_Case is '00000004';
Driving the pattern matcher
Let’s define a data structure to be used by our grep program.
global Grep
/* The pat subroutines need a working area. */
type Pattern_Work_Area_Storage_Type is bit(32);
type Pattern_Work_Area_Type is pointer to Pattern_Work_Area_Storage_Type;
constant Pattern_Work_Area_Size is Byte_Size(Pattern_Work_Area_Storage_Type);
constant Wildcard_Character is "?";
type Pattern_Struct is
record
Work_Area is Pattern_Work_Area_Type,
Error_Message is Varying_String,
Match_Count is Integer
end record;
end global Grep;
And also some procedures, using pass-by-reference for structs and strings to avoid copying.
procedure Grep_Initialize is
procedure
reference parameters
Pattern is Pattern_Struct,
Pattern_Expression is Varying_String
result
Grep_Initialize_Result is Boolean
end;
procedure Grep_Match is
procedure
reference parameters
Pattern is Pattern_Struct,
Query_Text is Varying_String
result
Match_Result is Boolean
end;
procedure Grep_Terminate is
procedure
reference parameter
Pattern is Pattern_Struct
end;
Grep_Initialize will take our data structure and a pattern expression input by the user. It will fill out the data structure and return a Boolean to show if it worked or not. If it failed, the Error_Message field in the struct will be set to an appropriate message.
Grep_Match will apply the pattern to a line of query text and return a Boolean stating whether there was a match or not. It will update the Match_Count member in the structure so we can keep track of how many results we get.
Finally, Grep_Terminate will free up the memory used by the system.
Grep_Initialize in detail
Let’s look at the implementation of Grep_Initialize.
definition Grep_Initialize;
Pattern.Match_Count := 0;
Pattern.Error_Message := "";
Pattern.Work_Area := Getspace(0, Pattern_Work_Area_Size);
if Length(Pattern_Expression) = 0
then
Pattern.Error_Message := "Missing pattern in PAR field";
return with False;
end if;
/* Add leading and trailing wildcards to match anywhere
on a line. */
variable Match_In_Line is Varying_String;
Match_In_Line := Wildcard_Character || Pattern_Expression ||
Wildcard_Character;
variable Len is Integer;
variable Text_Ptr is pointer to Fixed_String;
Get_String_Text_And_Length(Match_In_Line, Text_Ptr, Len);
variable Return_Code is Return_Code_Type;
Patbuild(Text_Ptr@, Len, Pattern.Work_Area, Pattern_Flags,
return code Return_Code);
/* Any error code greater than this is unrecoveable. */
constant Patbuild_Error_Code_Threshold is 8;
if Return_Code > Patbuild_Error_Code_Threshold
then
Pattern.Error_Message := "Error constructing pattern";
return with False;
end if;
return with True;
end Grep_Initialize;
It first starts by initializing the Pattern structure we passed in. The work area is a piece of memory needed by the system pattern routines so this is allocated. It then checks the pattern expression provided to see if it is not empty. If OK, it adds the wildcard ? to the start and end of the expression, so we can match text anywhere on a line.
We are nearly ready to call Patbuild to initialize the pattern. However, our pattern expression is a Varying_String and Patbuild requires a location in memory for the underlying string and a length. To get this, we define a macro Get_String_Text_And_Length that does some type casting to get access to the members of Varying_String:
macro Get_String_Text_And_Length
/* Given a Varying_String, get the address of the underlying
Fixed_String and copy the length to an Integer variable.
This is needed for the calls to system subrountines
like Patbuild below. */
parameters are V_String, Text_Ptr, Len;
equate V_String_Struct to V_String as Varying_String_Structure_Type;
equate Text to V_String_Struct.Varying_String_Text as Fixed_String;
Text_Ptr := Address(Text);
Len := Length(V_String);
end macro;
The macro encapsulates the details of accessing these members and allows us to use this in other parts of the program, but avoids the cost of a procedure call as the code will be inlined to the current function.
We can now call Patbuild using the return code Return_Code parameter to get the system routine result code. Volume 3 states this can fail under some cases, so we check this code and return an appropriate Boolean.
Grep_Match and Grep_Terminate
The procedure to do the actual matching is then simple:
definition Grep_Match;
variable Len is Integer;
variable Text_Ptr is pointer to Fixed_String;
Get_String_Text_And_Length(Query_Text, Text_Ptr, Len);
variable Return_Code is Return_Code_Type;
Patmatch(Text_Ptr@, Len, Pattern.Work_Area, return code Return_Code);
if Return_Code = 0
then
Pattern.Match_Count +:= 1;
return with True;
end if;
return with False;
end Grep_Match;
Grep_Terminate just calls Patfree.
Calling the Grep routines
The Main procedure can now be constructed by
reading the PAR parameter from the command line to get the pattern expression
calling Grep_Initialize
loop, reading lines from SCARDS, matching and printing results
print a final count of matches
definition Main;
variable Msg is pointer to Stream_Type;
variable Pattern is Pattern_Struct;
variable Pattern_Expression is Varying_String;
variable Line is Varying_String;
variable Return_Code is Return_Code_Type;
Msg := Message_Initialize();
Pattern_Expression := Trim(Par);
if not Grep_Initialize(Pattern, Pattern_Expression)
then
Message(Msg, "<v></>", Pattern.Error_Message);
else
/* Read lines from SCARDS and print any that match. */
cycle
Scards_Varying(Line, Return_Code);
if Return_Code > 0
then
exit;
end if;
if Grep_Match(Pattern, Line)
then
Sprint_Varying(Line);
end if ;
end cycle;
Message(Msg, "<i> match(es) found</>", Pattern.Match_Count);
end if;
Grep_Terminate(Pattern);
Message_Terminate(Msg);
end Main;
Compiling and running the program
The program can be compiled with $run *PLUS SCARDS=grep.plus SPUNCH=grep
# $run grep scards=*PLUS.SOURCELIB par=patmatch
# Execution begins 16:45:30
PATMATCH 30300
PATMATCH_PROCEDURE_TYPE 30400
/begin Patmatch
%Include(Patmatch_Procedure_Type);
procedure Patmatch is system Patmatch_Procedure_Type
/begin Patmatch_Procedure_Type
type Patmatch_Procedure_Type is
7 match(es) found
# Execution terminated 16:45:31 T=0.232
Matching across several files:
# $run grep scards=hello.plus+grep.plus par=title
# Execution begins 16:47:19
%Title := "Hello World Program";
%Title := "Find text in files";
2 match(es) found
# Execution terminated 16:47:19 T=0.006
Areas for improvement
By using the pattern routines provided by the MTS system, we have produced a basic grep program in 166 lines of code. There are, however, several ways we could improve it.
The program only supports case-insensitive matching. This is a by-product of the way the PAR command line is passed to the program in upper case. It would be nice to search multiple files, such as all files in a catalog, without having to use concatenation. It would also be good to show which file and line was matched.
One way to support this would be to use the PAR command line to pass a file specification, eg PLUS:?, and use SCARDS to pass in the text to be searched. The file specification could be expanded to a list of files, and by reading from SCARDS we could supply a mixed case pattern expression.
Another area to improve would be the pattern matching. As you can see from the run of grep on a large file, it’s not that fast. We also don’t provide as rich a pattern matching language as POSIX regexes. Looking at the source code for the pattern matching routines, they do a simple scan of the text - which is reasonable for the purpose they were designed for, to match filenames - but for a more complete implementation we’d want to look at writing our own regex library using something like the Boyer-Moore algorithm to improve the search time.
One last crazy idea. MTS has a PLUS compiler that produced PDP-11 object code, *PLUS11. What if we compiled grep.plus to run on the PDP_11, bringing grep back home? Unfortunately the pattern system routines like patbuild aren’t available on the PDP-11, but it’s only a matter of implementation…
Update 28 June 2020: according to the comment from Jeff Ogden, it is possible to get access to the contents of the parameter field without case conversion by using the PARSTR subroutine documented on page 366.1 of Volume 3.
Any other suggestions for improvements? Please post in the comments.
Final thoughts on PLUS
I really enjoyed using PLUS. The language feels like C with a more Pascal-ly syntax, balancing power with expressiveness. The integration with MTS is extensive, and if you want to extend MTS further it would be an ideal choice.
Further information
Full source code for these programs can be found on github.
There’s an interesting post on a FreeBSD mailing list by the original author of GNU grep on why it is so fast.
The MTS File Editor pattern matching language is described from page 159 onwards in Volume 18 of the MTS documentation set.
Let’s look in more detail at some of the features of the PLUS language.
Format
PLUS is free format and case-insensitive, so the variable count is the same as COUNT. Comments are the same as PL/I and C, ie /* ... /*. Statements are separated by semicolons. Input files to the compiler contain procedures and global definitions so they can be compiled separately.
Variables and types
Variables must be defined before use and given a type. They can then be assigned to with :=. Numeric types must be given a range, for example:
variable Count is (1 to 5);
variable Temperature is (-100 to 100);
Count := 1;
Temperature := -100;
Type definitions can be names with type and multiple definitions can be done on the same line:
type Temperature_Type is (-100 to 100);
variable Current_Temp is Temperature_Type;
variables High_Temp, Low_Temp are Temperature_Type;
The third line above is an example of multiple definitions on the same line; the variables and are keywords are just syntactical sugar for the singular forms.
Fixed string types include the length of the string, variable strings contain a min/max length. So in the below, My_Name is varying. Strings can be concatenated with ||.
variable My_Name is character(50);
variables First_Name, Last_Name are character(1 to 25);
First_Name := "Jane";
Last_Name := "Doe";
My_Name := First_Name || Last_Name;
Bit types allow precise control over the amount of memory used:
variable Full_Word is bit(32);
variable Byte is bit(8);
There are type attributes such as aligned or packed for further fine-grained control.
Identifiers are like C enums:
variable Device is (Reader, Punch, Printer);
There are also real and set types defined in the language but not fully implemented - so you can create a real and assign to it but not do arithmetic.
Arrays have an index (which can be non-numeric) and a value
variable Lookup is array character(1) of character(8);
variable Matrix is array (1 to 10) of array (1 to 10) of Temperature_Type;
The size of the array must be determined at compile time. Elements can be accessed via round brackets.
Pointers work similarly to C pointers but use the suffix @ for de-referencing and the built-in procured Address for assignment.
variable Ptr is pointer to Count_Type;
type Count_Type is (1 to 10);
variable x is Count_Type;
x := 5;
Ptr := Address(x);
Ptr@ +:= 1;
Note that the type of what the pointer is accessing (here Count_Type) can be defined after it is introduced. Note also the +:= form to increment a value; here the underlying variable x pointed to by Ptr is updated to be 6.
Record types define structures.
type My_Record is
record
a is (1 to 1),
b is character(50)
end;
There are also variant records which allow something similar to C’s unions.
equate allows aliasing of variables or variable expressions.
Procedures
Procedures need to be declared before they are defined. They can also be treated as types.
/* Pull in definition of Integer */
%Include(Numeric_Types);
type Arithmetic_Procedure is
procedure
parameters
x is Integer,
y is Integer
result
answer is Integer
end;
/* Declaration */
procedure Plus is Arithmetic_Procedure;
/* Definition */
definition Plus;
answer := x + y;
end Plus;
/* ... */
variable x is Integer;
x := Plus(2, 3);
Parameters are passed by value by default; you can define them as reference parameter to pass a reference in instead, and the compiler will behind the scenes pass a pointer and de-reference it in the procedure.
Expressions and control structures
The usual range of arithmetic and logical expressions are supported. There is no separate Boolean type but anything non zero is counted as true.
For conditionals, there is if ... then ... elseif ... else... end if and select ... case ... end select.
The cycle statement starts a infinite loop that can be broken out of
using exit or restarted with repeat, which can be used to write
loops. An example from the tutorial:
variables Count, Return_Code are Integer,
String is Varying_String;
Count := 0;
/* Loop reading and writing records, counting number copied. */
cycle
Scards_Varying(String, Return_Code);
if Return_Code ¬= 0
then
/* Terminate loop when no more input */
exit
end if;
Spunch_Varying(String);
Count := Count + 1
end cycle;
A more restrictive do statement allows single variable incrementing or decrementing, like we saw in the Hello World program in part 1.
begin ... end can be used to start a new block with its own scoped variables, and supports exit and break like cycle.
Constants, macros and compiler directives
Compile time constants can be defined as follows
constant Zero_Temp is 0;
and can be used in type definitions as well as variable initialisation.
Macros provide a procedure like interface and copy the text of the macro into the current scope.
Compiler directives start with %. We’ve seen %Title in part 1 and $Include above; other directives control things like compiler listing output and extra code checks.
Input/output and the standard library
There are only a few built in procedures in PLUS; most functionality is provided by the standard source library *PLUS.SOURCELIB. This include I/O routines like Sprint_String or Scards_Varying which respectively send a fixed string to the output device and read a varying string from the input device.
A more complex, printf style procedure called Message can be used for string printing.
Message(Msg, "<hi> items found at <v></>", count, location);
This will print the halfword integer and varying strings included in the parameter list. The </> prints a new line. (I think the resemblance to HTML-like tags is coincidental, as PLUS predates even SGML of which HTML was a subset).
The standard source library also includes many routines to call MTS system procedures so you can control devices, file attributes directly from PLUS.
The PLUS language holds a special place in the history of MTS - let’s look at it in more detail.
PLUS
PLUS was created by Alan Ballard and Paul Whaley of the University of British Columbia in the 1970s, based on language work done for a previous experimental OS called SUE. According to the HELP pages for PLUS:
PLUS is superficially quite different from Sue or Pascal; however the underlying language semantics are really very similar. Users familiar with the programming language C will also recognize much of its structure and semantics in PLUS.
PLUS on MTS
PLUS was used heavily at MTS sites as a system development language, as it provides a structured programming environment easier to use than lower level languages, while still allowing access to machine level features in a way a more generic language such as Pascal did not. The implementation also came with a large source library that made writing complex programs easier. It could generate object code both for the System/360 and both PDP/11 and Motorola 68000 systems.
The compiler was written in PLUS and the source can be found on the distribution tapes under component 1042.
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.
Compiling and running a program
The *PLUS compiler takes its input from scards and writes output to spunch, like many other language translators.
$run *PLUS scards=hello.plus spunch=-load
Hello world
Let’s show this in action. Assume we have a program in the file hello.plus, let’s look how we’d list, compile and run it.
# $list hello.plus
1 %Title := "Hello World Program";
2
3 %Include(Sprint_String, Main);
4
5 definition Main;
6 variable Count is Integer;
7 do Count := 1 to 5
8 Sprint_String("Hello, world!");
9 end do;
10 end Main;
# $run *plus scards=hello.plus spunch=-load
# Execution begins 07:26:15
Hello World Program User REL. Fri May 3/19 07:26:16 Page 1
1. 1 |%Include(Sprint_String, Main); hello.plus
No messages for global String_Types.
Global size 0 bytes.
No messages for global Numeric_Types.
Global size 0 bytes.
****************************************************************************************
* *
* procedure Main *
* parameter is Par (ref). *
* result is Rc. *
* external name is "MAIN ". *
* *
****************************************************************************************
24810. 1 |definition Main;
6. 2 | variable Count is Integer;
7. 3 | do Count := 1 to 5
8. 1 | Sprint_String("Hello, world!");
9. 25 | end do;
10. 26 |end Main;
No messages for procedure Main.
215 bytes code generated; 424 bytes stack required.
String_Types Numeric_Types Main Mts_Io_Types Main Page 1
Plus/370 Compilation -- University of Michigan -- Version 28/16 User REL. Fri May 3/19 07:26:16 Page 2
C r o s s R e f e r e n c e
+------------------------------------------------+ Sprint_String macro 1:8 1:4
| References are of form page:coordinate,... | Sprint_Varying macro 1:7 1:7
| | Standard_String_Length
| Codes used are: | constant 1:2 1:3,4,5
| D - procedure definition | Str macro parm 1:8 1:23,24
| S - store reference | Str macro parm 1:9 1:6
| @ - pointer dereferenced | String_Length_Type type 1:3 1:8
| A - address taken | String_Types global 1:1
| E - equated or opened | Transmitted_Length field 1:8
| | Varying_String type 1:5 1:5
| * indicates procedures and globals with errors| Varying_String_Length field 1:8
+------------------------------------------------+ Varying_String_Structure_Type
type 1:6
+-------------------------------------------------------------+ Varying_String_Text field 1:9
| Symbol Usage Decl References |
+-------------------------------------------------------------+
Actual_Length field 1:8
Buff parameter 1:15
Count variable 1:2 1:3S
Fdub field 1:17
Fixed_String type 1:4 1:9
Integer type 1:6 1:15,2,19
Len parameter 1:16
Len variable 1:22 1:23S,24A
Line variable 1:5 1:6S,23,24A
Lio_Number field 1:19
Liounit field 1:15
Lnum parameter 1:18
Main procedure 1:17 1:1D
Main_Procedure_Type type 1:12 1:17
Maximum_Integer constant 1:4 1:5,6
Maximum_Length field 1:8
Maximum_Short_Integer constant 1:2 1:3,7
Minimum_Integer constant 1:5 1:6
Minimum_Short_Integer constant 1:3 1:7
Mods parameter 1:17
Mts_Fdub_Kind constant 1:10 1:16
Mts_Fdub_Type type 1:2 1:17
Mts_Io_Extended_Modifiers_Type
type 1:5
Mts_Io_Length_Type type 1:6
Mts_Io_Modifiers_Type type 1:4
Mts_Io_Types global 1:1
Mts_Io_Unit_Type type 1:11
Mts_Line_Number_Type type 1:3 1:18
Mts_Lio_Number_Kind constant 1:10 1:18
Mts_Liounit_Kind constant 1:10 1:14
Mts_Unit_Kind_Type type 1:10 1:13
Notification result 1:19
Numeric_Types global 1:1
Par parameter 1:14
Rc result 1:15
Short_Integer type 1:7 1:8,16,22
Sprint procedure 1:21 1:24
Sprint_Procedure_Type type 1:13 1:21
Page 2
Plus/370 Compilation -- University of Michigan -- Version 28/16 User REL. Fri May 3/19 07:26:16 Page 3
The following libraries were specified for this compilation:
*PLUS.SOURCELIB 9 members included.
Total code generated: 215 (bytes)
Total warning messages: 0
Total error messages: 0
# Execution terminated 07:26:15 T=0.075
# $run -load
# Execution begins 07:26:23
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
# Execution terminated 07:26:23 T=0.001
In the next article, we’ll look at the features of PLUS in more detail.
Further information
A paper describing Project SUE can be found at the ACM website.