(This page is under construction.)

Table of Contents

Introduction to XPL/I

Keep in mind that the Virtual AGC Project is devoted to onboard guidance software used aboard space vehicles such as those of the Apollo and Space Shuttle projects, thus most of what I have to say on the topic of XPL is from that point of view.  But that doesn't mean it isn't applicable for general purposes as well.

For the purpose of thinking about XPL, what you need to know to follow the discussion herein is this:
Well, that's certainly a mouthful of words!  The point is that having an XPL compiler is just one item — though an important one! — in a chain of things that are helpful in the process of resurrecting PASS for modern audiences. XPL used to be a computer language (see the Wikipedia article) in the 1960's and 1970's that was defined by a book called A Compiler Generator, by William M. McKeeman, James J. Horning, and David B. Wortman.  Today, the language is deader than ancient Sumerian.  Worse, the internals of the language relied heavily on the fact that the compiled XPL programs would run on IBM System/360 mainframes.  While you can write an XPL compiler that will run on a different type of computer system, or to compile programs that will run on a different kind of computer system, doing so in a way that such programs, as-is, would produce the same results is problematic. In practical terms, the book is the sole documentation for the standard XPL language, and is available neither for free, nor in digital form online.  For brevity, I'll refer to that book from now on simply as McKeeman.

Alas, it's even a bit more complicated than what I just said.  For one thing, Intermetrics did not write its HAL/S compiler in standard XPL as defined by McKeeman.  Rather, they extended the language with new features, occasionally changing the existing features in an incompatible way.  From now on, I'll refer to Intermetrics's variant of XPL as XPL/I to distinguish it from the standard.

In short, if you actually had a compiler for standard XPL that you could use on (say) a Windows, Mac OS, or Linux computer, it would most likely neither compile most XPL/I programs, nor would most of those compiled programs run correctly afterwards if you were able to do so.  Consider the following brief example of a program:
declare x fixed, y fixed, z fixed;
x(0) = 1;
x(1) = 2;
x(2) = 3;
output = x(0) || ' ' || x(1) || ' ' || x(2);
output = x || ' ' || y || ' ' || z;
What is this little example supposed to do?  First, it declares 3 variables of the fixed datatype; fixed means that x, y, and z are 32-bit signed integers.  It then proceeds to use x as if it were a three-element array, though x wasn't declared that way, assigning values to each of its elements.  In other words, the program assumes that x(0) corresponds to x, x(1) corresponds to y, and x(2) corresponds to z.  Finally, the program "prints out" both the elements of x (as an array) and the values of x (as a non-array), y, and z.  Naturally, those two lines of printout are identical.

In case you're wondering, we could have used z(-1) as an equivalent to y, or z(-2) as x.  For that matter, we could have used this feature to read or write areas of memory in which no variables at all had been declared.  Or if we had declared variables of some other datatype than FIXED, we could have used this same trick to access them as if they were FIXED.
Aside: When I originally wrote about the example above, it was intended as a warning about XPL/I extensions to XPL, since there is no statement in McKeeman that partakes in this subscripting sloppiness. But no!  Subscripting sloppieness is allowed in standard XPL after all.  I belatedly I noticed the following comment on p. 137 of McKeeman:  "Assignments to subscripted variables are not checked against the array bounds; thus every memory location is accessible through subscripting," although admittedly it takes a bit of imagination to claim that this ratifies the example given above.  More compelling is the source code:  The central feature of McKeeman is an Appendix containing the entire source code for their XPL compiler, XCOM, and if you dig into it deeply enough you do find places where XCOM itself does make casual use of sloppy subscripting.  And worse!  For example, there is a PROCEDURE in XCOM, ERROR(MSG,SEVERITY), whose purpose is to print error messages.  In ERROR, the parameter SEVERITY (a scalar) is used with a subscript, SEVERITY(-1), in what the authors' comments call "A PORNOGRAPHIC WAY OF OBTAINING THE RETURN ADDRESS [of ERROR]".  Well, the comment is certainly worth a laugh!  And the idea behind it was to allow ERROR to print the address at which the error had occurred, which indeed makes some sense.  I would question its sensibility, however.  Sense and sensibility don't always go together.
Aside:  Incidentally, Daniel Weaver has written an XPL-to-C translator.  The first thing anyone suggests to me when this topic is discussed is "Why don't you just use it?"  The subscripting sloppiness inherent in actual usage of XPL/I is one good reason.  As you might expect, Daniel's translator cannot compile the XPL/I example program above.  And why in the world would anybody expect it to?  Here's a fun printout of the very-sensible error messages you get if you try to do so:
XPL to C language translator -- version 1.1
2 |x(0) = 1; |
|
*** Error, Identifier is not an array (detected at line 5741 in xcom). ***
3 |x(1) = 2; |
|
*** Error, Identifier is not an array (detected at line 5741 in xcom). ***
*** Last previous error was detected on line 2. ***
4 |x(2) = 3; |
|
*** Error, Identifier is not an array (detected at line 5741 in xcom). ***
*** Last previous error was detected on line 3. ***
5 |output = x(0) || ' ' || x(1) || ' ' || x(2); |
|
*** Error, Identifier is not an array (detected at line 5741 in xcom). ***
*** Last previous error was detected on line 4. ***
5 |output = x(0) || ' ' || x(1) || ' ' || x(2); |
|
*** Error, Identifier is not an array (detected at line 5741 in xcom). ***
*** Last previous error was detected on line 5. ***
5 |output = x(0) || ' ' || x(1) || ' ' || x(2); |
|
*** Error, Identifier is not an array (detected at line 5741 in xcom). ***
*** Last previous error was detected on line 5. ***
6 cards containing 7 statements were compiled.
6 errors (0 severe) were detected.
The last detected error was on line 5.

Aside:  Daniel has also pointed out that aside from his own compiler, Dave Bodenstab wrote an XPL compiler for FreeBSD, which Daniel himself has ported to Linux.  I have not tried it as of this writing.

Aside:  The other suggestion I receive with a fair degree of regularity is, "Why not just run all of this IBM 360 software in a simulator like Hercules, and be done with it?  Problem solved!"  Well, for one thing, we don't have Intermetrics's XPL compiler, so we can't run it on an IBM 360 simulation.  Lacking that, we can't build Intermetrics's HAL/S compiler in such a way that it could be run on an IBM 360 simulation.  But if we had the HAL/S compiler and it could compile the PASS flight software to IBM 360 object code, why don't we just run that on an IBM 360 simulator?  Well ... tell me how to integrate Hercules into a spacecraft simulator like Orbiter?  And tell me how to motivate any of you to learn how to use Hercules in addition to the already very steep learning curve you face with the flight software?  If you can answer those questions for me, I'll admit you've got a pretty good idea!  With that said, there's definitely merit in emulating an IBM 360, if not necessarily emulating a full IBM 360 system.   I'll come back to this point later
"Subscript sloppiness", as I call it, is just one example of the difficulties.  To be fair, I'll admit that it's hard to make a full list of all the difficulties of XPL or all of the differences between XPL and XPL/I, because neither is McKeeman a full and accurate description of XPL, nor is Intermetrics documentation a full description of XPL/I.  Nor did Intermetrics use the term "XPL/I", simply calling their language "XPL", making it hard to be entirely sure what's supposed to be different between the two languages and what's not. 

By the way, I recognize that it's very dangerous to make claims about what other people haven't accomplished, so you have to take it with a grain of salt when I say that you're not going to find any existing compiler for Windows, Mac, or Linux that can compile and run XPL/I programs.  If you find out that I don't know what I'm talking about, by all means let me know! 
Aside: In point of fact, I do know of another XPL/I compiler currently under development, though not yet fully ready for use.  It's actually an XPL/I to C++ translator.  Indeed, it was a discussion with the author of that compiler, Don Schmidt, prior to the start of his own work, which motivated me to look into this topic to begin with.
With that said, as far as I can tell, if you want to compile Intermetrics's HAL/S compiler, you're going to have to rely on a newly-written XPL compiler having XPL/I support baked into it from the ground up, rather than hoping to somehow tack XPL/I capabilities onto some existing XPL compiler.  And that's what's discussed below.  I call the new compiler XCOM-I, for XCOM (the name of McKeeman's original XPL compiler) and "I" (for "Intermetrics").

Like Daniel Weaver's XPL translator, XCOM-I is actually an XPL/I-to-C translator, though it is entirely new and is not based on Daniel's work nor on any of the original XPL compilers. In most cases, XCOM-I should work for standard XPL programs too, but not always.
Aside: And in case you wonder, XCOM-I does translate the sample XPL/I program given above to C without error; the C program it creates also compiles without errors; and if you run the compiled C program you get what you might expect:
PAGE 1


1 2 3
1 2 3

However ... there's one final complication:  Another thing the original Intermetrics authors did in the XPL/I source code they wrote was to intersperse it (thankfully, sparingly!) with code written in IBM System/360 Basic Assembly Language (BAL).  Alas, XCOM-I cannot translate BAL into anything meaningful.  But XCOM-I does provide hooks so that you can patch in replacement code, written in C, for the embedded BAL code.  Those hooks will be discussed later on.

Processing a Program Written in XPL, Using XCOM-I

Despite the title of this section, there's actually no difference between how to build and run XPL/I programs than XPL programs.  Once you know how to build and run an XPL program, you'll automatically know how to build and run an XPL/I program. (Oh, there's an extra command-line switch you can use to specify that you really, truly want XPL rather than XPL/I, namely --xpl, but all it does is to remove some reserved words and runtime-library functions specific to XPL/I, allowing your XPL program to use those words as names of variables or procedures. Which surprisingly, does turn out to be necessary occasionally.)

With that said, the XPL/I source code available to us dwarfs the XPL source code we have for any standard XPL programs we have.  Moreover, those XPL/I programs are much larger and more complex than the XPL programs.  This means that there are differences in how the source code for the two is organized and maintained.  In that sense we find differences in how to deal with the two.  That's why I concentrate at first on compiling standard XPL programs, and defer some discussion of XPL/I compilation until later.

Installation of XCOM-I

XCOM-I is a program written in the Python 3 language.  To install XCOM-I on your system, you simply need to copy the folder called "XCOM-I" from the Virtual AGC software repository onto your computer, and presumably add that directory to your PATH if it's not already there.  Running XCOM-I requires Python 3.7 or later, but I don't think it requires additional installation of any Python modules not present in a normal Python distribution. 

Compiling the C programs created by running XCOM-I using the simplest variation of the instructions given below assumes that the GNU gcc C compiler and the GNU make program are also installed on your computer.  Using alternatives to those is possible, probably easily, and will be briefly discussed, but I have no way to personally survey every possible combination of development tools.

Compiling and Running XPL Programs

There are several standard XPL programs present in the folder XCOM-I/Tests/.  It would be a mistake to think any of them are user-friendly.  But they do illustrate the principles involved.
Aside: You'd also be very mistaken to imagine that XCOM-I itself is user-friendly.  I fear that there will be a lot of work involved before the error messages XCOM-I spits out upon occasion can be mistaken for anything other than spit.  Until then, try not to make any errors in your XPL programming.
Aside:  Throughout this discussion, I use the Linux/Mac convention that the symbol '/' is used to separate the components of a filename and the path to the folder containing it.  Windows uses the separator '\' instead, so in some places you may find that you need to replace '/' by '\'.

For the sake of discussion, suppose we wish to compile and run the sample program called Example-6.18.6.xpl.  The first step is to use XCOM-I to translate Example-6.18.6.xpl into C source code:

cd XCOM-I
XCOM-I.py Tests/Example-6.18.6.xpl
This will create a sub-folder called Example-6.18.6/ of the current working folder, and put all of the files of the C translation into that folder. 
Aside:  XCOM-I.py has various allowable command-line options, though none were needed in the invocation shown above.  You can see a list of XCOM-I.py's command-line options with the command "XCOM-I.py --help".
I won't bore you with a lengthy description of the C files output by XCOM-I, but it might be mildly instructive to glance briefly at a couple of them.  For this example, the principal outputs are the files RANDOM.c and main.c, and here you can see a comparison of the original XPL file (left) to the C translation of them (right):
/* This is example XPL program 6.18.6 from McKeeman p. 157.
The book only provides PROCEDURE RANDOM, which is transcribed as-is.
The top-level code that exercises RANDOM is new. */

RANDOM:
procedure(range) fixed;
/* Returns a random integer in the range 0 to range - 1 */

declare range fixed, rbase fixed initial(1),
rmult literally '671297325';

rbase = rbase * rmult;

return shr(shr(rbase, 16) * range, 16);

end RANDOM;

declare i;

do i = 1 to 100;
output = RANDOM(100000);
end;

eof

/*
File RANDOM.c generated by XCOM-I, 2024-04-16 08:46:47.
*/

#include "runtimeC.h"
#include "procedures.h"

int32_t
RANDOM(void)
{

// rbase = rbase * rmult; (2)
{
int32_t numberRHS = xmultiply(getFIXED(8), 671297325);
putFIXED(8, numberRHS);
}
// return shr(shr(rbase, 16) * range, 16); (3)
return SHR(xmultiply(SHR(getFIXED(8), 16), getFIXED(4)), 16);
}

/*
File main.c generated by XCOM-I, 2024-04-16 08:46:47.
XPL/I source-code file used: Example-6.18.6.xpl.
To build the program from the command line, using defaults:
cd Example-6.18.6/
make
View the Makefile to see different options for the `make`
command above. To run the program:
Example-6.18.6 [OPTIONS]
Use `Example-6.18.6 --help` to see the available OPTIONS.
*/

#include "runtimeC.h"
#include "procedures.h"

/*
Memory Map:
Address (Hex) Data Type Variable
------------- --------- --------
0 (000000) FIXED I
4 (000004) FIXED RANDOMxRANGE
8 (000008) FIXED RANDOMxRBASE
*/

int
main(int argc, char *argv[])
{

if (parseCommandLine(argc, argv)) exit(0);

// do i = 1 to 100; (0)
{
int32_t from0, to0, by0;
from0 = 1;
to0 = 100;
by0 = 1;
for (putFIXED(0, from0);
getFIXED(0) <= to0;
putFIXED(0, getFIXED(0) + by0)) {
// output = RANDOM(100000); (1)
{
int32_t numberRHS = ( putFIXED(4, 100000), RANDOM() );
string_t stringRHS;
strcpy(stringRHS, fixedToCharacter(numberRHS));
OUTPUT(0, stringRHS);
}
}
} // End of DO for-loop block

if (LINE_COUNT)
printf("\n"); // Flush buffer for OUTPUT(0) and OUTPUT(1).
return 0; // Just in case ...
}

The comparison, I think, is both tantalizing and mysterious.  Tantalizing, because you can almost see how it works.  And mysterious, because you can't quite see it.  But I digress!

The next step, of course, is to compile the C programs.  If your setup is like mine, namely a Linux system with gcc as the default C compiler, then compilation can be accomplished like this, assuming that you're still in the XCOM-I/ working directory:
make -C Example-6.18.6
This produces an executable program within the Example-6.18.6/ folder that's also called Example-6.18.6.

But the world being what it is, you most likely don't have a setup like mine, so some alterations in the instructions above may be needed in your situation.  For one thing, your C compiler may not be gcc.  Actually, the default assumption isn't that the compiler is gcc, but rather that the shell's environment variable CC holds the name of the compiler.  But if that's not true, or if you're not using gcc, then it's a trifle harder to accomplish the compilation. 
Note: I've tried to make XCOM-I use C that's pretty generic, in order to avoid the very problem of being limited in the choice of C compilers.  But there are some C features used, such as "designated initializers", which I believe aren't quite as commonly supported.
For example, suppose you use clang instead of gcc.  Just change the build commands to read:
cd XCOM-I
make -C Example-6.18.6 CC=clang

Or if you're on Windows — and who isn't, really? — then you'd want to add the filename extension ".exe" to your executable.  That could be done like so:
cd XCOM-I
make -C Example-6.18.6 TARGET=Example-6.18.6.exe

Aside:  The Makefile itself lists other possible alterations, any or all of which can be used in combination with the others.  For example, one thing you may encounter if using a compiler other than gcc, or even a different version of gcc, is an increased likelihood of seeing compile-time warnings that I don't see myself.  Even if such warnings don't affect your ability to compile and run the software, they may be annoying and hence you might like to use compiler switches to turn them off.  For example, with clang 15, I see irritating (and incidentally, incorrect!) warnings about "dangling elses".  Those warning messages are supposedly (though not actually) disabled with:
make -C Example-6.18.6 CC=clang EXTRA="-Wdangling-else"
Finally, you can now run the now-fully-compiled program:
Example-6.18.6/Example-6.18.6
What this particular sample program does is just to print 100 random numbers, so that's what should happen.  (I say they're "random", but the seed for the random-number generator is hard-coded and always the same as 1, so you'll get the same 100 numbers every time you run the program.)
Aside: Just like XCOM-I, the compiled application also has a variety of command-line options that may affect how it runs.  None of them are really applicable to this particular sample program, but you could see them with the command "Example-6.18.6/Example-6.18.6 --help".
If you want to see a much meatier sample program than Example-6.18.6 that's written in standard XPL, you can look instead at the ANALYZER program (Tests/ANALYZER.xpl). 
Aside:  I originally found the source code presented for ANALYZER online, and don't actually know if it's precisely the same source code as given in McKeeman.  At least, not without much more-detailed checking than I have any desire to do.  I have definitely seen some differences from the book, but suspect that the book may have errors (gasp!) due to typesetting process.  Whether there are differences not accounted for by typesetting, I cannot say.
If you have a copy, McKeeman (see Section 7.1, Chapter 10, and Appendix 6) provides reasonably-detailed operating instructions and theory of operation for the program.  Basically, ANALYZER is used for analyzing a given BNF grammar and printing a report about it.  Thus McKeeman recommends ANALYZER for developing BNF grammars and debugging them.  There's an associated data file as well, SKELETON.xpl, which contains the BNF description for a very simple language (called "SKELETON", of course) that's the basis for some of the discussion in the book.  The steps for compiling and running ANALYZER with SKELETON.bnf as input are just what you'd expect from the discussion earlier:
cd XCOM-I
XCOM-I.py --xpl Tests/ANALYZER.xpl make -C ANALYZER ANALYZER/ANALYZER <Tests/SKELETON.bnf
If you're interested, you can compare the report on the SKELETON grammar produced by the steps above, versus the report printed in Figure 7.1.6 of McKeeman in 1969.  You will notice slight differences between the two, but not (I think) anything substantive.  Remember that the report printed in the book would have been manually typeset, always with the possibility of human error, and with editorial decisions having been made to affect the aesthetics and publishing cost, possibly to the detriment of literal 100% accuracy.
Aside: Reducing discussion of ANALYZER to just whether or not it works the same today as it did back in 1970 is doing it a injustice, because ANALYZER has interesting capabilities in its own right.  If you look at the reports I mentioned above, you'll notice that one thing ANALYZER includes in these reports is a large section consisting of XPL DECLARE statements.  These DECLARE statements, if plugged into the template XPL code provided elsewhere in McKeeman, are what's needed to create a compiler (written in XPL, of course) for the grammar being analyzed.  In other words, the title of the book (A Compiler Generator) isn't a misnomer.   This XPL code in the report isn't incredibly useful as-is, because it's formatted in a manner that's not immediately compilable.  However, ANALYZER also allows you to "punch" separate punch-cards that do contain immediately-compilable XPL.  More on that in a moment. 
One of the legacy XPL programs provided for demonstration purposes is actually a compiler for the SKELETON language, though calling it a "compiler" is a bit of a stretch, since all it does is analyze statements in the SKELETON language for correctness, and doesn't produce any object code.  The idea behind the SKELETON program was that since it's such a small program (~300 lines of XPL), it would be easy to experiment with.  The SKELETON language itself is very bare-bones, in that it only has assignment statements in it, and those assignment statements have the form IDENTIFIER = EXPRESSION;.  Identifiers follow the same rules in XPL, except that lower-case letters aren't supported.  There are no declarations, no arrays, no strings, and "expressions" are basically whatever normal arithmetical expressions you can form from just identifiers, literal decimal numbers, and the tokens + - * / ( and ).  Anyway, you can compile and run it like so:
cd XCOM-I
XCOM-I.py --xpl Tests/SKELETON.xpl make -C SKELETON
SKELETON/SKELETON
SKELETON being relatively uninteresting, the Tests/ folder also contains a similar but more-complex BNF grammar for XPL itself.  Analyzing the XPL grammar with ANALYZER is very slightly more complex than analyzing the SKELETON grammar was. That's because the XPL grammar file used as input for ANALYZER contains a directive which the SKELETON grammar did not.  The culprit is $PUNCH, which directs ANALYZER to output the XPL code it generates to a "punch device".  Technically, that means a file has to be "attached" to the punch device, or else the data has nowhere to go.  But by default ANALYZER doesn't attach any file to the punch device.  On an IBM System/360, this would have been handled by means of separately-provided Job Control Language (JCL), and in particular by DD statements in the JCL.  Naturally, we have no JCL in our XCOM-I based system, but the equivalent to JCL would be ANALYZER's command-line switches. The command-line switches for simulating DD statements are --ddi and --ddo.  Putting that all together, we can analyze the XPL grammar, with the punch device attached to a file called PUNCH.txt, using the following command:
ANALYZER/ANALYZER --ddo=2,PUNCH.txt <Tests/XPL.bnf
Or since ANALYZER accepts the input grammar it analyzes on "device 0" (attached by default to stdin, which is why we usually pipe in the input via <), we could even run it instead as:
ANALYZER/ANALYZER --ddi=0,Tests/XPL.bnf --ddo=2,PUNCH.txt <Tests/XPL.bnf
For a still meatier sample program, we can turn to the source code of the original XPL compiler itself, XCOMXCOM was itself written in XPL, so perhaps we can compile it using XCOM-I.  Like ANALYZER, the source code for XCOM is in McKeeman (Appendix 3), but also like ANALYZER, the source code we have is something I found online, and I cannot guarantee it's identical to the book.  Having compiled XCOM, we'll have the dubious pleasure of being able to use it to compile standard XPL programs and produce object code in IBM System/360 machine code. 

As usual, we compile XCOM itself like so:
cd XCOM-I
XCOM-I.py --xpl Tests/XCOM.xpl make -C XCOM
But then compiling an XPL program using XCOM involves a little JCL DD trickery, similar to some we saw above: There's an extra file which needs to be attached as an input.  That's because when XCOM compiles an XPL file, it expects also always to need the source code for something called COMPACTIFY.  For XPL code (vs XPL/I), that happens to be contained in a file called XPL.LIBRARY.xpl, so we have to attach that file. Also, XCOM wants to use several "random-access files" for storing object code and what not, and we have to attach those files.  (Refer to the FILE entry in the section on XPL's built-in library functions.)
XCOM uses a hard-coded constant to determine the record sizes for these random-access files, which happens to have the default value of DISKBYTES=3600.  Compiling an XPL program called (say) "Program.xpl" with XCOM could thus look something like this:
XCOM/XCOM --ddi=0,Tests/Program.xpl --ddi=2,XPL.LIBRARY.xpl --raf=B,3600,1,Program.obj --raf=B,3600,2,Program.dat --raf=B,3600,3,Program.str
Just to reiterate, the object code produced by this process will be useless to you, since it will consist of IBM System/360 machine code.  (Or will it?  We'll talk about that a little later.)  But that doesn't detract from it as a demonstration of XCOM-I.  McKeeman provides source code for two sample XPL programs, Example-6.18.1 and Example-6.18.2, which we duplicate in the Tests/ folder.  The former demonstrates object-code production, while the latter demonstrates error handling.  The complete XCOM reports for these programs are given in the book as well, so it's possible to compare the original reports from McKeeman's XCOM, including the interspersed IBM System/360 assembly language produced by the compiler, versus the corresponding reports created by our newly-compiled XCOM:

XPL Source-Code File
Compiled by XCOM for
A Compiler Generator (1970)
Compiled by XCOM-I.py for
The Virtual AGC Project (2024)
Tests/Example-6.18.1.xpl
Report
Report
Tests/Example-6.18.2.xpl
Report
Report
Aside:  Incidentally, McKeeman's XCOM has plenty of quirks specific to it, quite aside from any quirks XPL more-generally may have as a computer language.  I mention this just in case you become excited about using XCOM and start writing new programs for it!  Here are a few I've noticed:

Emulating the IBM 360 CPU

As I mentioned earlier, it has often been suggested me that since so many of these legacy XPL programs assume that they are running on an IBM 360 system, and since legacy XPL compilers like McKeeman's XCOM and Intermetrics's HAL/S-FC can produce IBM 360 object code, then emulating an IBM 360 on which to run that software might be a good idea. Alas, a full-scale IBM System/360 emulator such as Hercules does not jibe with Virtual AGC's project goals very well. For one thing, the Space Shuttle's flight computers were what are typically referred to as "embedded systems" that provided real-time control of flight systems, whereas the System/360 was instead a mainframe computer that read punch-cards, executed batch jobs and made printouts. Even considered as a stop-gap measure, just used temporarily to see if a program compiled from XPL source code works at all, Hercules is still not a very good fit with Virtual AGC, because you'd have to navigate the learning curve of how to operate an IBM System/360 computer. Which is fine, if that's your interest, but not so fine if your interest is in ancient flight computers rather than ancient mainframe computers!

But that's not to say that emulating the IBM 360 CPU is a worthless idea! To the contrary, if you had a lightweight emulator, it's entirely possible it could be used or adapted to achieve all of Virtual AGC's goals.

As it happens, there is an available candidate for such lightweight IBM 360 CPU emulator. This emulator, called sim360, was written by Daniel Weaver, who I've mentioned earlier as also being the author of a compiler for standard XPL. You can find the source code for sim360 archived as the XCOM-I/sim360-source/ folder in the Virtual AGC software repository, but the official place to get the most up-to-date version is Dan's own site. It's disguised on Dan's site as a Pascal compiler, but don't be confused: There's an IBM 360 CPU emulator in there as well!

Assuming you're working from Virtual AGC's archived copy, the first thing you have to do is to build sim360 itself as follows:

cd XCOM-I/sim360-source/simulate
make clean all

This will result in the executable, sim360 itself, being created in the XCOM-I/sim360/simulate/ folder. But you'll probably want it to be in your PATH. You can just copy sim360 itself into the PATH if you like; it no longer needs any of the other files in XCOM-I/sim360-source/.

To actually run an IBM 360 program in sim360, you'll need an IBM 360 load file for it. In the context of Virtual AGC, there are two ways to get such a load file:

Those facts being what they are, let's run through the complete process of compiling a simple XPL program into an IBM 360 load file, and then running that file with sim360. Here's our simple program:

OUTPUT = 'Hello, world!';
EOF

It doesn't get much simpler than that! Well, actually it does, because I've already put this file, as HELLO.xpl, into the XCOM-I/Tests/ folder for you. Assuming that XCOM has itself been moved somewhere into your PATH, we can use the instructions at the end of the preceding section to compile HELLO.xpl:

cd XCOM-I
XCOM --ddi=0,Tests/HELLO
.xpl --ddi=2,Tests/XPL.LIBRARY.xpl --raf=B,3600,1,HELLO.obj --raf=B,3600,2,HELLO.dat --raf=B,3600,3,HELLO.str

Assuming that step completed with no errors, our IBM 360 load file is now HELLO.obj. Let's run it:

sim360 -o0ET stdout HELLO.obj

And here's the output from that emulated run:

Hello, world!
Sim360 normal exit

The command-line switches for sim360 probably look mysterious, but they're not so bad. You can see a full list of command-line options with the command "sim360 --help", but what the mysterious -o0ET switch means is this: text from OUTPUT(0) (that's where the "o" and the "0" come from) are going to be in EBCDIC (that's the "E") and we want sim360 to translate it (that's the "T") to ASCII when it's sent to stdout (that's the ... well, you get the idea).

This was a very simple example, but I think the potential of the method is obvious.

Compiling and Running HAL/S-FC

(Most of what's described in this section is not yet functional, but I'm writing it while working it out so that my memory is fresh.)

The examples in the preceding section are all very well and good, but the Holy Grail is compilation via XCOM-I of HAL/S-FC itself, and subsequently being able to successfully run HAL/S-FC.  Just as there was a big leap in complexity from being able to compile ANALYZER (~1500 lines of XPL) to being able to compile XCOM (~4200 lines of XPL), there's a much larger leap to being able to compile HAL/S-FC (>30,000 lines of XPL).  The steps for doing so are also somewhat more complex.

To compile HAL/S-FC, I'd suggest switching over to the working directory where the HAL/S-FC source code actually is stored, and compiling it there, which looks like this:
cd .../virtualagc/yaShuttle/"Source Code"/PASS.REL32V0/PASS1.PROCS
make
The reason that looks so simple is that I took the trouble to hide the complexity the additional complexity I mentioned above by creating a Makefile in that folder.   HAL/S-FC actually consists of 7 passes, each with its own, standalone executable, and the instructions above (if they succeed), will thus create 7 new programs in that folder:  PASS1, FLO, OPT, AUX, PASS2, PASS3, and PASS4. You may or may not want to move these into your PATH.  The reason you might not want to move them into your PATH is that a number of applications written in XPL use this kind of naming pattern, and have program names like PASS1, PASS2, and PASS3.

Actually running HAL/S-FC to compile some XPL/I source code is also tricky, because the 7 passes are run in succession, with each pass passing data into the next pass either explicitly via files, or else via COMMON memory.  It's thus necessary not merely to run the passes in the correct order, but to use all of the command-line settings that attach files with the expected names.

TBD

A Primer for Standard XPL and Intermetrics XPL/I

It is a truth universally acknowledged that there is no satisfactory introductory information available concerning programming in XPL.  Or at least, it would be universally acknowledged if anybody had ever heard of XPL and wanted to use it for anything. 

The best you can do, generally speaking, is to purchase a used copy of McKeeman (i.e., A Compiler Generator).  If you do, you'll find a book that's densely packed with information, but that information is the source code for an XPL compiler (written in XPL), lots of BNF descriptions of the language, lots of theory on how to write a compiler-generator program, and very little of direct interest to a programmer who wants to come up to speed quickly on how to write or understand a program written in XPL. Not to mention the fact — though I am mentioning it! — that some of the most-critical counter-intuitive information is buried in easy-to-miss, easy-to-misunderstand comments made in passing, rather than as big, bold-face warnings.   And as a bonus, the book provides an index of almost no use at all to a newby XPL programmer.  Besides which, most online information about XPL, in my experience, is a simple abridgement or other rehashing of A Compiler Generator, and adds little extra of value in a tutorial sense, since it's almost never written by anybody actually working with XPL.  With that said, you may find some useful online information in a couple of places:

And as for Intermetrics XPL/I ... well, from a tutorial perspective, it's orders of magnitude worse. Perhaps later, non-surviving documentation did a better job.  Enough said!

Taking all of that into account, it might be reasonable to provide a full tutorial here how to write XPL or XPL/I programs.  Perhaps I'll do that sometime.  It turns out that that's easier said than done, since as you may have noticed, simplification for beginners is not really my personal strong suit. Which is ironic, considering my strong criticism of A Compiler Generator above! For now, I'll just cover some of the basics and quirks of the language(s). Send in suggestions for improvement, if you like; I'm sure I can use them somehow to make the discussion even worse.

The Basics

Character Set

The most basic characteristic of a language is the character set in which the language is expressed.  Neither McKeeman nor Intermetrics specifies the character set.  I've given it a lot of thought, and my conclusion is that the originally-supported character set was:

<space>
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9
_ % + - * . / | & = < > # @ $ , ; : ( ) ' " ! ?
¢ ¬

You'll notice that several characters common today were not originally supported, including:

` ~ ^ [ ] { }

Contrariwise, there are two characters (¢ and ¬) that don't exist at all in the 7-bit ASCII character set that's the common core for the character sets typically used today when writing software source code.  Therefore, when working with XCOM-I, we use the characters ~ and ^ interchangeably with ¬ (but prefer ~).  Similarly, we use ` in preference to ¢.  These substitutions allow us to completely translate the original XPL or XPL/I source code back-and-forth between the original EBCDIC and ACII without any loss of information, and without insisting that you adopt any specific "locale" like ISO-8859-15 or UTF-8 for your computer that's not optimal for your other (non-XPL'ing) activities.  With that said, I hope that you could use ¢ and ¬ in your XPL/I source code, if you insist on doing so, but I do not guarantee it.

Aside: All previously-existing XPL or XPL/I source code I've found, or HAL/S source code for that matter, has already substituted ~ or ^ for ¬ anyway. And indeed, some early HAL/S documentation suggests this very substitution. I suspect that's because some IBM printers at the time printed ~ in place of ¬. But whatever the explanation, the substitutions I'm suggesting are not exactly daring in their originality. As far as the ¢ symbol is concerned, it's not actually used in active XPL or XPL/I code, as such, but can be used in program comments to toggle various compiler options on and off, or in principle could appear within quoted strings.

Case Sensitivity

XPL programs are not case-sensitive, except in so far as the contents of quoted strings are concerned.  E.g., lower-case or mixed-case symbols are treated as being identical to their upper-case correspondents, but quoted strings are case-sensitive.

Source-Code Formatting

Input to an XPL or XPL/I program (via the INPUT built-in function) is expected to conform to computer punch-card-like conventions. I.e., input lines are always exactly 80 characters long, and XCOM-I enforces this by truncating or right-padding input lines as necessary. If the input lines are longer than 80 columns physically — say, because they have punch-card sequence numbers in columns 81-88 — the extra columns are stripped off.

As for the source-code proper, other than being confined to columns 1-80, it is entirely free-form. I.e., line breaks are ignored; several statements may exist on the same input card, or conversely, a single statement may be split across multiple cards. Even though XPL CHARACTER strings are limited to 256 characters, there is seemingly no limit to the length of an XPL statement.

There are hints in the error messages of McKeeman's XCOM (and I believe, in A Compiler Generator text) that some contemporary computer systems may have treated column 1 specially, perhaps accepting some kind of non-blank control characters there. If so, it was a issue outside XCOM proper and is irrelevant to XCOM-I. However, you do find that legacy XPL source code does often begin in column 2 rather than column 1, and I suspect that this hypothetical column-sensitivity is the reason for it.

Regarding this column-1 ambiguity, however, there is the practical question of what to do when a long quoted string is split across multiple cards. Does column 1 belong to the string or not? In XCOM-I, I take my cue from McKeeman's source code for XCOM in this matter: In spite of the fact that XCOM's source code generally avoids column 1 in all other cases, column 1 does belong to any multi-line quoted strings.

Identifiers

Identifiers cannot begin with a digit, but otherwise consist of any sequence of upper- or lower-case alphanumeric characters, or any of the characters @ _ # $.  For example, @_26$8ab# is a legal name for a variable.

Identifiers cannot exceed 256 characters in length.

Datatypes, Declarations, and Literals

The Basic XPL Datatypes

There are only three basic datatypes:

Aside: The method for storing CHARACTER data described above leaves no room for 0-length "empty" strings. But the XPL and XPL/I languages do allow for empty strings: p. 207 of McKeeman tells us that an empty-string is represented by a string-descriptor with the value 0x00000000, with no extra memory allocation for the non-existent "data" of the string. This isn't ambiguous, by the way. While 0x00000000 technically appears to be a descriptor for a 1-byte string whose data is located at address 0x000000, in fact address 0x000000 would always have been outside of the block of memory dedicated for storage of EBCDIC string data, rendering a descriptor of 0x00000000 unusable under the normal interpretation.

The storage formats in memory duplicate those that would have been expected on an IBM System/360 computer, within the limits of my ability to infer what those formats were.  While the storage formats are not significant in abstract terms, they'll be seen to be quite significant in dealing with certain aspects of HAL/S-FC's source code, such as its so-called "virtual memory" system, and indeed I think it would be impossible to run HAL/S-FC unless these underlying IBM 360 storage formats were used.

You'll notice that there is no floating-point datatype, a fact which will be discussed in some detail later.

The three basic datatypes can also be incorporated into single-dimension arrays. Multi-dimensional arrays are not allowed.  There is no maximum array length beyond the available memory.  Array indices start at 0.  Indices (or "subscripts") are enclosed in parentheses, as in A(3) or B(N)
Aside: It's easy to become confused and to imagine (incorrectly!) that you can treat a CHARACTER variable (as opposed to an array of CHARACTER variables) as an array itself, in order to access its individual characters.  You cannot!  In fact, the XPL language does not provide any syntactical means to access individual characters of a string.  For that, you must rely on built-in functions provided by the runtime library.  The most-direct method is to use the BYTE function, which can either retrieve the EBCDIC numerical encoding of an individual character in a CHARACTER variable, or else to store a new EBCDIC numerical value at a given position in a CHARACTER variable.  Thus if we had a CHARACTER variable C which held the value 'HELLO!', then BYTE(C, 3) would return 211 (the EBCDIC encoding for the letter 'L'), while the assignment statement "BYTE(C, 3) = 198;" would change the contents of C to 'HELFO' since 198 is the EBCDIC code for the letter 'F'.  That sounds cumbersome, since very few of us have memorized the EBCDIC table, but it's really not.  You generally don't have to look up the EBCDIC encoding for anything, because you would actually have programmed operations such as this as "BYTE(C, 3) = BYTE('F');".  Another, less-generally-useful method would be to use the built-in SUBSTR function to retrieve a specific character position as a new CHARACTER object of length 1.
The BIT(N) datatype is actually quite problematic.  It would be reasonable to assume that since BIT(N) seems to represent support for collections of bits, then XPL should provide some syntactical sugar for reading the values of these bits or modifying them.  For BIT(1) through BIT(32), you'd be somewhat correct, in that XPL largely treats these as being interchangeable with integers (i.e., FIXED), and automatically converts them back and forth between FIXED.  The runtime library's built-in logical-shift-left and logical-shift-right functions (SHL and SHR) work just as easily with them as with the FIXED, as do the logical operators &, |, and ¬ (~), and the relational operators <, >, =, !=, >=, and <=.  Swell!

But once you advance to BIT(32) through BIT(2048), you are cruelly disappointed.   XPL provides no built-in methods of working with this data, beyond the ability to initialize BIT(N) variables with data when they're declared.  (More on that later.)  As far as actually using this data for anything, there are no built-in means to do anything at all with that data.  While you can manipulate the data by cobbling together various runtime-library memory-access functions, McKeeman gives none of the technical information about the storage format that would allow you to do so, in so far as I was able to discover.  What do I mean about cobbling together memory-access functions?  Well, suppose that you want to access bit 43 of a BIT(86) variable called B.  First, you must know where the data for B is located in memory.  There's a function that can tell you that.  Then you must know which byte in that block contains bit 43.  There's no function that tells you that, but if you do know it somehow then there's a function that gives you the value of a byte at that address.  Then you must know which bit in the byte corresponds to bit 43.  Again, there's no function for that, but if you know it, then you can use library functions like SHL or SHR, probably in conjunction with a logical operator like &, to isolate the value of that bit.  If you wanted to change the value of that bit, different but similar awfulness is involved.

But doing any of that requires that you have intimate knowledge of how such BIT data is packed into memory.  It's hard to write unambiguously about these matters, but I'll try to do so using the following concepts:  An n-bit value, when written out in human-readable form has a leftmost bit and a rightmost bit; meanwhile, a block of m bytes in memory has a byte that's at the lowest address, a byte at the highest address, and within each byte has a most-significant bit and a least-significant bit.  With those ideas in mind, here's how XPL packs BIT data into memory:
Or to put it concisely if ambiguously, short bit strings are right justified, while long bit strings are left justified.
Aside:  Since I say that the bit-packing is undocumented, what's my justification for claiming that what I said just above is true?  The short answer is trial-and-error!  The longer answer is that one of the intermediate milestones in trying to get XCOM-I to the point of being able to compile the original source code for HAL/S-FC was first to be able to compile McKeeman's original XCOM and run it with a verifiably correct result.  But I couldn't get it to work!  After messing with it for days on end, I eventually got the answer in a dream, and then experimented with a couple of different bit-packing schemes before finally getting XCOM to run properly.  The packing scheme I describe above is the one that worked.
Aside: If all that wasn't bad enough, there's also a trap waiting for you if you're already used to doing bit manipulations with logical operators and shifts in other computer languages.  This trap is in the behavior of conditional tests in XPL's IF and DO WHILE statements.  Conditional tests in these statements depend only on the least significant bit; i.e., it as if any conditional test involves an extra "& 1" operation that you can't see.  Thus if you wanted to detect (say) that bit 3 of the BIT(5) variable A was set, a statement like "IF A & 8 THEN ...;" wouldn't help you at all, since the implicit "& 1" in the conditional would cause the test always to fail!  You would instead need to use a shift-right operation, such as "IF SHR(A, 3) THEN ...;".
But enough of these measly BIT-based frustrations!

Variables in general are supposedly strictly typed, and every variable used must have an associated declaration statement, though we've already seen examples of the casual way XPL/I treats XPL's strict typing with subscripting sloppiness.

Here are a few examples of declarations of variables, both scalar and array:
DECLARE F FIXED, C CHARACTER, B BIT(5);
DECLARE FS(10) FIXED, CS(10) CHARACTER, BS(10) BIT(5);

These are pretty self-explanatory in most ways, so I won't dissect them for you in detail.  What's perhaps most confusing is that the three arrays declared here (FS, CS, and BS) each have eleven elements in them, because the number 10 in their declarations is not the number of elements, but rather the highest legal index.  As mentioned above, indices start at 0, so the total number of elements in each is 11.
Aside: Standard XPL, à la McKeenan, doesn't allow expressions when expressing array sizes, whereas XPL/I does allow them.  For example, the following is fine in XPL/I but is a no-no in XPL:
DECLARE BUFFER(3600-1) BIT(8);
This example will be continued in the next section, where it will make a little more sense, but this is actually the kind of thing you might want to do.

There are additional attributes which can be applied to such declarations, of which the most important is probably INITIAL.  This attribute allows you to supply an initial value for the variable, such as:
DECLARE F FIXED INITIAL(22), F2 FIXED INITIAL("22"), F3 FIXED INITIAL("(8) 22");
DECLARE C CHARACTER INITIAL('Hello!');
DECLARE B BIT(5) INITIAL("(1) 10100");
DECLARE FS(10) FIXED INITIAL(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);

It's important to understand that the initializer has its affect only at compile-time, and is not applied at run-time.  That means that if you declare variables within PROCEDUREs, they're not reinitialized each time the PROCEDURE is executed.

You may have been confused by the initializers shown above for the variables F2, F3, and B, since they naively appear to be strings instead of numbers; but the naive interpretation is wrong.  Which brings up the nature of literals in XPL:
These numeric literals are the only syntactical reason that the double-quote character (") appears in XPL source code.

Another important attribute is LITERALLY. It's not strictly related to declaration of variables, even though appearing in DECLARE statements and so it's discussed in the next section instead.

XPL/I ARRAY and BASED Data

XPL/I provides a separate kind of statement which can declare arrays, which syntactically differs only in that the keyword ARRAY is used in place of the keyword DECLARE, and in that it can only be used for arrays of FIXED or BIT, and not for CHARACTER.
Aside: For the original XPL/I compiler, I believe there was a distinction in the way ARRAY variables were stored in memory vs DECLARE variables.  At the present time, I don't see this distinction as being operationally significant, so XCOM-I treats the two keywords identically.  This is subject to change, if I discover my thinking was in error.  As, unfortunately, I often do.
More significantly, XPL/I adds an additional kind of datatype that it calls a BASED variable.  These are basically pointers to arrays of FIXED, BIT, or CHARACTER.  Note that I said "pointers to arrays" rather than "arrays of pointers".  By changing the address stored in the BASED variable's pointer, you can instantly interpret an entirely different chunk of memory as the array.  Moreover, besides the basic types just mentioned, the BASED variable can point to an array of "records", where each "record" is a collection of the basic datatypes.  I.e., a record can hold any combination of FIXED, BIT, or CHARACTER fields, or arrays thereof.  Using the BASED mechanism, XPL/I can thus mimic both pointers and primitive types of structures, neither of which is available in XPL proper.  I say that the structures are "primitive", because BASED variables cannot themselves be fields of BASED variables, hence only structures that are a single-level deep are available. 

Here are a couple of examples of declarations of BASED variables:
BASED FB FIXED;
BASED RB RECORD:
F FIXED,
C CHARACTER,
A(10) BIT(5),
END;

Although BASED variables are always (or almost always) arrays, you'll note that the declarations of FB and RB don't indicate any dimensional information.  That's because no memory for them, other than for the pointer, is allocated at compile-time.  Space is instead explicitly allocated at runtime by user code.  Thus XCOM-I has knowledge of the size of each array entry, but not of the number of elements in the array. 
(Some of the material covered in this inset discussion of "dope vectors" has not yet been implemented in XCOM-I.)

When I said that a BASED is a "pointer to an array", I was glossing over the fact that to be useful a BASED must track a lot more information about the BASED than just its data's location in memory.  In fact, a BASED is stored as a 28-byte structure plus the separately-positioned data for the array.  The 28-byte structure is referred to as a "dope vector".   In other words, if you had a BASED called (say) MYBASED and you executed the built-in function ADDR(MYBASED), it would return the address of the dope vector for MYBASED.  As usual, the HAL/S-FC documentation and source code do not actually provide any useful facts about this setup, but various factoids can be inferred from HAL/S-FC source code, to a greater or lesser degree of confidence, and here are my feeble inferences about the fields of dope vectors:
  1. FIXED pointer giving the address of the actual data.
  2. BIT(16) giving the size in bytes of each array entry.
  3. BIT(16) giving the number of "descriptors". I'm not clear on what that means, but perhaps is the number of non-constant CHARACTER values appearing per record of the BASED. I could see that as being useful if the implementation of (say) garbage collection involved searching the entire BASED looking for CHARACTER data, since you could more-easily know to stop searching once they had all been found.
  4. FIXED giving the total number of array entries for which space has been allocated.
  5. FIXED giving the total number of array entries actually used so far.
  6. FIXED. I think that perhaps all of the dope vectors form a linked list, and that this is the address of the next dope vector in the list, presumably in order of increasing pointers. The list is terminated when a value of 0 is found. Thus the process of finding usable holes in memory — or at least the portion of memory dedicated to BASED variables (vs character strings) — is just the process of searching this linked list.
  7. FIXED. It appears to me that this field supplies some properties of the BASED in the form of bit fields. It is laid out as follows:
  8. BIT(16) of purpose TBD. It is referred to as "global factor".
  9. BIT(16) is referred to as "group factor". As far as I can see, all uses of this are commented out in HAL/S-FC, so perhaps it ended up being unused.
The XCOM-I implementation mimics this dope-vector structure, though only the fields I've highlighted in green are significant in XCOM-I ... which is fortunate, since they're the only ones I imagine I understand somewhat.

With that discussion in mind, in understanding some of the things that need to happen with BASED variables in actual XPL/I source code (and in particular, in HAL/S-FC), let's consider various space-management macros and/or procedures used:

I'd note that as long as effective versions of these macros are provided, along with a working COMPACTIFY, it doesn't really matter if the dope-vector properties are implemented or not.

User code that initially allocates free memory — let's say 25 records to start with — for a BASED variable is typically a two-step process that looks something like this:
BASED MYVARIABLE FIXED;
...
RECORD_CONSTANT(MYVARIABLE, 25, MOVEABLE); /* OR UNMOVEABLE */
RECORD_USED(
MYVARIABLE) = RECORD_ALLOC(MYVARIABLE);
Or, if you knew that you were going to need more elements later, you might allocate a bit extra, for example:
BASED MYBASED FIXED;
...
ALLOCATE_SPACE(
MYBASED, 30);
RECORD_USED(
MYBASED) = 25;
Having allocated the space for it, you can now use MYBASED just like any other array of FIXED, such as in assignments like "MYBASED(27) = 6;" or "X = MYBASED(N) + 12;". 

To actually increase the number of elements later, you'd do something like this:
NEXT_RECORD(MYBASED);
This will increment RECORD_USED(MYBASED) by 1 — if possible while still keeping it below RECORD_ALLOC(MYBASED) — or else will reallocate and possibly move MYBASED into a larger space, if possible.

In a more-complex case, we might have a BASED RECORD variable:
BASED MYNEWBASED RECORD:
F FIXED,
C CHARACTER,
A(9) FIXED,
END;
...
RECORD_CONSTANT(
MYNEWBASED, 30, MOVEABLE);
RECORD_USED(
MYNEWBASED) = 25;
Accessing MYNEWBASE requires the dotted style often used these days for accessing fields of structures or classes.  Some examples include:
MYNEWBASED(6).F = 12;
MYNEWBASED(10).C = 'XPL is where it is at!';
MYNEWBASED(20).A(6) = 15;
X = MYNEWBASED(6).F;

and so on.

LITERALLY and Macros

Another attribute that can appear in DECLARE statements is the LITERALLY attribute.  Here's an example:
DECLARE ARRAYTOP LITERALLY '255';
DECLARE MYARRAY(ARRAYTOP) FIXED;

Notice that ARRAYTOP has no datatype assigned to it.  That's because its declaration is not actually the declaration of a variable called "ARRAYTOP", but rather of a macro of that name.  Wherever the identifier ARRAYTOP is encountered subsequently, it's simply replaced literally by the string 255, now unquoted:
DECLARE MYARRAY(255) FIXED;
This clarifies an example of a commonly-desirable declaration I gave in the preceding section, which in this section would be expressed as:
DECLARE RECSIZE LITERALLY '3600';
DECLARE BUFFER(RECSIZE-1) BIT(8);

As noted in the preceding section, standard XPL's grammar wouldn't allow an expression (like RECSIZE-1) in that context, so this particular convenience in making declarations is only available in XPL/I.

Macro expansions — not macro declarations (I hope!) — can be nested, so you can do things like this if you want:
DECLARE DEVICE LITERALLY '6', DECLARE OUT LITERALLY 'OUTPUT(DEVICE)';
OUT = 'My message';

This expands to
OUTPUT(6) = 'My message';
Macros can expand to portions of statements, as the ones above have, or to multiple statements, such as
DECLARE MYBLOCK LITERALLY 'DO; X=1; Y=X+3; END';
...
IF X=7 THEN;
MYBLOCK;

which expands to:
IF X=7 THEN;
DO; X=1; Y=X+3; END;

Macros can also have arguments.  Consider the following:
DECLARE MYMAC(2) LITERALLY '%1% = %2%';
This declaration means that MYMAC has 2 arguments, and that when the macro is expanded, the first argument will replace %1% and the second argument will replace %2%.  Thus "MYMAC(X, 3 * Y)" expands to "X = 3 * Y".
Warning:  As with macros in any other computer language, this can quickly get out of hand.  XCOM-I, for example, won't detect recursive, endlessly-expanding macros.  There's also no guarantee when multiple macros are in play that XCOM-I will necessarily expand macros in the same order that XCOM would have.  Neither McKeeman nor Intermetrics documentation makes any mention of what that ordering should be.

Warning:  The scope of macro definitions is also different in XPL vs XPL/I.  In XPL, macro definitions don't respect any nested scopes they appear in; i.e., any macro definition will simply remain in effect until the end of the source code.  In XPL/I, macro definitions remain in effect only until the end of the procedure in which they're defined, including embedded procedures.  In neither case does a macro definition have any effect on source code prior to it.

Logical Expressions

XPL's logical operators are &, |, and ~ (¬), for "and", "or", and "not", respectively. The documentation in A Compiler Generator is maddeningly unclear as to what these operators do. True, table 6.8.1 calls them "logical and", "logical or", and "logical complement", but the word "logical" isn't defined ... just as my sloppy usage of the word "logical" at the beginning of this paragraph makes no distinction. Which leaves open a few loopholes that have to be closed up. The issues which we must understand are:

In case you're not in the mood for a technical discussion of the matter, I'll give you the short answer up front, and having read that, you can proceed to the long discussion if you want:

As for how I came up with this "information", there are several places we can look for guidance in guessing the answers. For one thing, according to McKeeman's account, the XPL language was derived from the PL/I language, so we can look at PL/I documentation and hope that it applies to XPL. Of course, we can look at the source code for McKeeman's XPL compiler (XCOM) as listed in the book, and see if there are any hints there. Or we can even examine the IBM 360 object code that XCOM generates for these operators. (All the while wondering how things came to this, that we have to resort to lame measures like consulting object code to figure out the basic features of the language?)

As far as PL/I is concerned, IBM's PL/I Language Reference (2017) tells us on p. 66 that for the &, |, and ¬ operators, "bit operations are performed on a bit-by-bit basis". As far as object code produced by XCOM is concerned, McKeeman (p. 150) shows an example in which object code for the expression "SHL(K,1) & SHR(I,J)" is produced, and we do find that it simply uses the IBM 360 NR ("And Logical") instruction:

Although I had to consult more than one IBM assembly-language manual to find the answer to the seemingly-simple question of what NR does, IBM's z/Architecture Principles of Operation (p. A-8) does tell us that the NR (and its cousins N, NC, and NI) are indeed bitwise operations.

Aside: Figure B-2, "Instructions Arranged by Mnemonic" of the latter document is very helpful in trying to decipher such listings of IBM 360 object code.

Short-circuiting is a natural consideration for strictly bipolar operands and operators, but is a bit trickier to consider once we've concluded that the logical operators operate bitwise rather than in a bipolar fashion. Certainly the object-code example from A Compiler Generator that was mentioned in the preceding paragraph shows no signs at all of short circuiting: Both of the operands of the & operator in that example are evaluated, with no attempt at checking the value of the first operation before proceeding to the second one. On the other hand, that example of object-code generation by XCOM happens to be for an assignment statement rather than for the conditional expression of an IF, DO WHILE, or DO UNTIL. Perhaps the evaluation of a conditional expression might be very different in those contexts. One reason to believe that it might be different is that the final result of a conditional expression is masked to just the least-significant bit, and thus (eventually) is indeed a bipolar value; i.e., even if all of the bits were involved in the computation, all but one of them is discarded in the end, so perhaps the extra bits are discarded at the beginning rather than at the end of the computation, even though it's more work to do so. Moreover, the PL/I Language Reference document mentioned earlier does cover short-circuit evaluation (see p. 245), and it says that short-circuiting is only in the context of the conditional of an IF statement (versus assignment statements). Plus, even then the short-circuiting occurs only in certain special circumstances, such as the leading operand being a BIT(1) literal or constant variable, which leads one to believe that the value of the leading operand has to be determined to be 0 or 1 at compile-time rather than at run-time for the short-circuiting to occur.

Unfortunately, the example of object-code generation in McKeeman doesn't show us how an IF statement would compile. But as we saw earlier, we have been able to use XCOM-I to create a working copy of McKeeman's XCOM, so we can make our own example of IF, compile it with XCOM and see! Imagine we have the following ridiculous little XPL program:

 DECLARE I FIXED;
DO I = 1 TO 10;
IF (I * I) & (100 - I * I) THEN OUTPUT = 'hello';
END;
DO I = 1 TO 10;
IF 0 & (100 - I * I) THEN OUTPUT = 'hello';
END;
EOF

Aside: By the way, compiling an XPL program with McKeeman's XCOM is a bit more constrained than compiling a similar program with XCOM-I. For one thing, to avoid an irritating if harmless warning message, the EOF token must be present at the end of the source code, whereas XCOM-I doesn't care. For another, even though XPL is case-insensitive other than inside of quoted strings, and even though all of the XPL source code in A Compiler Generator is printed in lower case, XCOM will in fact choke on any XPL source code that isn't fully upper case. Go figure!

Compiling this silly program with XCOM, and pulling just the relevant portion of the XCOM's report gives us the following IBM 360 object code for the conditional expressions of the IFq statements:
  20 |    IF (I * I) & (100 - I * I) THEN OUTPUT = 'hello';                           |  1314 C7 = 10.
1314: CODE = L 1,1340(0,11)
1318: CODE = M 0,1340(0,11)
1322: CODE = L 2,1340(0,11)
1326: CODE = L 3,1340(0,11)
1330: CODE = MR 2,2
1332: CODE = LA 2,100(0,0)
1336: CODE = SR 2,3
1338: CODE = NR 1,2
1340: CODE = N 1,164(0,11)
...
24 | IF 0 & (100 - I * I) THEN OUTPUT = 'hello'; | 1400
1400: CODE = L 1,1340(0,11)
1404: CODE = M 0,1340(0,11)
1408: CODE = LA 2,100(0,0)
1412: CODE = SR 2,1
1414: CODE = N 2,1300(0,1048571)
1418: CODE = N 2,164(0,11)
...

Not shown above is that the symbol table tells us variable I is stored at address 1340(11), which is why all of the 1340(0,11)'s appear above. I don't understand IBM 360 assembly language, but what I think the code probably does is:

But whether or not my interpretation is 100% correct, at least in this example there's no evidence of short-circuiting. The 2nd IF in particular is pretty shocking. Perhaps there's supposed to be some subsequent optimization I'm not aware of that would have cleaned it up.

COMMON Memory

XPL/I also introduces the notion of COMMON memory, not present in XPL.  The notion behind COMMON memory is that a very large application program like HAL/S-FC won't be loaded entirely in memory at once, but will instead be run as a sequence of "passes". 

Thus, HAL/S-FC isn't a single application program, but rather a set of them:  PASS1, FLO, OPT, AUX, PASS2, PASS3, and PASS4.  Each of these applications is loaded, run, and unloaded from memory, in succession.

But!  Each of these application programs may receive some kind of input data or state data from the preceding application program, and transmit output data or state data to the next application program in succession.  In XPL/I's visualization, some of that data is passed in files.  But other of that data is instead just assumed to remain in computer memory, unchanged from whatever the preceding application has left behind.  The term XPL/I applies to this leftover memory is COMMON.  It's formalized when you explicitly declare variables as being in COMMON.  Variables declared to be in COMMON are not initialized by an XPL/I program, but are simply assumed to already contain the data needed.  On the other hand, variables not declared COMMON are up for grabs, and no assumption can be made about their initial contents other than whatever initialization their declarations explicitly provide.

Syntactically, COMMON data is declared in XPL/I by three methods:

Another distinction is that CHARACTER variables cannot declared in COMMON, though CHARACTER variables can appear as fields in COMMON BASED RECORD variables. XCOM-I actually relaxes this restriction.

It's not documented anywhere, as far as I know, but I would assume that there was originally an expectation that each cooperating application running in succession needed to declare COMMON in exactly the same way, using exactly the same ordering of variables and the same datatypes. XCOM-I relaxes this restriction as well.

Of course, XCOM-I makes no effort at all to pass COMMON data from one application to another using actual memory. Rather, each XPL/I application program compiled by XCOM-I optionally (depending on its command-line options) can load a file of data into its COMMON area, and automatically writes out its COMMON area into a file upon termination. By using the --commoni and --commono command-line switches of the application, a close degree of control can be exercised over which previously-saved COMMON blocks, if any, are passed to which application programs.

Memory Model for a Compiled XPL Program

The theoretical memory space available at runtime for a compiled XPL program is 224=16,777,216 bytes in size, although the Wikipedia article on IBM System/360 tells us that the actual physical maximum was only 8 MB. 

For the original XPL and XPL/I compilers, the lowest portion and highest portion of this (theoretical) 16 MB space was dedicated to the executable code for the program and the data used by it.  The middle of the area was used for the program's data.  But in the XCOM-I framework, all of the program code is stored elsewhere, thus the entire 16MB space can be dedicated just for the variables actually DECLARE'd in the XPL source code ... plus those few elements of data which the operating system needs to communicate to the program, such as run-time program options.

The memory nevertheless still needs to be partitioned into blocks dedicated to specific types of data, in order to facilitate management of dynamic data like CHARACTER or BIT(n>32) strings and BASED variables.  I try to follow the same partitioning scheme as used by the original compilers, at least roughly.  The broad outline of this partitioning scheme is seen in the diagram to the right. 

The two topmost memory blocks are problematic, in the sense that they consist of allocations that may change dynamically over the course of execution of the program.  As the dynamic data grows or shrinks or move around within the parent memory block during program execution, it may cause "holes" of unallocated memory to appear.  The holes in memory don't directly cause a problem, but the ever-decreasing range of contiguous free memory at the end of the one of these dynamic data blocks may cause a problem eventually. 

As far as the dynamic-data block reserved for CHARACTER and BIT(n>32) data is concerned, the garbage-collection procedure called COMPACTIFY is called whenever allocable memory in that block runs out, to remove the holes by repacking the memory space, at the same time correcting all descriptors, and thus increasing the size of contiguous free memory.  Though COMPACTIFY was considered a "built-in" runtime-library function in McKeeman XPL, there was actually XPL source code for it which had to be included along with the XPL program being compiled. 
Aside: The COMPACTIFY source code just mentioned was in the file XPL.LIBRARY.xpl for McKeeman's XCOM, and in the file HALINCL/SPACELIB.xpl for Intermetrics's XPL/I compiler.  Both the McKeeman and Intermetrics XPL compilers automatically included their appropriate library file without the need for any explicit directives within the XPL source code of the program being compiled.  (Presumably there was JCL to specify the particular library file.)  COMPACTIFY and any of the other contents of the library file were processed first; therefore, all of its declarations were available to the XPL source code of the program being compiled.  And all of the variables declared in the library files precede the variables declared in the XPL source code in memory.
In that sense, COMPACTIFY was really a customizable user-supplied PROCEDURE merely masquerading as a built-in of the runtime library.  We have both of those versions of the XPL source code for COMPACTIFY.  They are very different.

(The struck-out stuff below cannot be correct, and must be rethought.  Character strings in BASED variables will not work correctly otherwise.)
Aside: The Intermetrics version of COMPACTIFY is seemingly uncompilable.  Not just in XCOM-I, but in any compiler.  It inexplicably references a number of variables which are nowhere declared in the library source code.  I can only suppose that these undeclared variables undocumented built-ins of the compiler.  Either that, or else the source code I've received for it is for some reason irreparably broken.  Whatever the case, without additional (or any!) documentation, I rate the Intermetrics library as no longer functional.
Because of technical difficulties I've had with the Intermetrics library, XCOM-I takes what we might think of as a combined approach to implementing memory management:
  • When compiling an XPL program with XCOM-I, you should use the McKeeman library, XPL.LIBRARY.xpl.
  • When compiling an XPL/I program with XCOM-I, you should use the library XPLI.LIBRARY.xpl, which is basically the Intermetrics library with McKeeman's COMPACTIFY replacing Intermetrics's COMPACTIFY.
The highest memory region — i.e., the dope-vector dynamic memory block reserved for contents of BASED variables — naturally did not exist in XPL, since BASED variables are an XPL/I extension.  Nor was memory management of the BASED variables handled by COMPACTIFY, but rather by a set of macros and functions discussed in the section on BASED memory.   Those are defined in XPLI.LIBRARY.xpl.
Addresses
Description
Region Number
0xFFFFFF



Data pointed to by "dope vectors" of
BASED



6
FREELIMIT



FREEPOINT


FREEBASE


Data pointed to by "string descriptors" of CHARACTER or BIT(>32)

5


BASED
dope vectors

4


CHARACTER
or BIT(>32) string descriptors

3


FIXED and BIT(≤32) variables for non-COMMON

2


FIXED and BIT(≤32) variables for COMMON

1
0x000000
Data supplied by MONITOR(13) and MONITOR(23)

0

Structure of an XPL Program vs XPL/I

An XPL program consists of any sequence of XPL statements, followed by the token EOF.  In particular:

Note:  The original XPL compiler, which was called XCOM, performed a single pass.  It required that the declaration of any particular identifier as an object (such as a variable) had to precede the use of that identifier, although there were provisions for making a forward declaration for a PROCEDURE, so that the PROCEDURE could be used before it was defined.  XCOM-I relaxes this requirement.

Each of the sample programs I've encountered in standard XPL so far has been contained in a single relatively-small file. For example, ANALYZER has a little over 1500 lines of source code, while XCOM has a little over 4200 lines.

In contrast, the XPL/I source code for Intermetrics's HAL/S compiler HAL/S-FC has over 120,000 lines of source code spread across over 600 files, though any individual pass of the compiler has no more than around 35,000 lines. This huge size, along with the huge difference from programs in standard XPL, necessitates different methods for managing that source-code base, and some of those methods are reflected by compiler directives embedded within the source code. Insofar as HAL/S-FC and its related applications are concerned, the top-level source-code file (##DRIVER.xpl) for each application always contains all of the necessary directives for compiling the other source-code files needed, in the correct order, so in using XCOM-I to compile these applications you don't need to worry about any file other than ##DRIVER.xpl itself.

Aside: Well, the comment about ##DRIVER.xpl isn't exactly right. Any XPL or XPL/I program will expect that there's a separate "library file" containing source code for the COMPACTIFY procedure, but the XPL/I source code for the program won't explicitly include the library file. That's the compiler's responsibility.

Aside: Due to the lack of relevant Intermetrics documentation, what I'm about to describe is not only speculative on my part, but also represents certain pragmatic compromises that I don't believe literally existed in Intermetrics' XPL compiler or development procedures. But if it will work for us using XCOM-I on HAL/S-FC and if there are no other lurking XPL/I programs that we need to worry about, why complain?

Compiler directives in XPL/I are comments or comment-like constructions which aren't documented in McKeeman and have no other obvious purpose.  The specifics are covered by the subsections below.

Compiler Directive Type:  /?c ... XPL/I source code ... ?/

This type of compiler directive is a conditional inclusion of source code, similar to the C language's #if c.  Here, c is supposed to be an upper-case alphabetical letter that represents the particular condition that needs to be "true".  All conditions, A through Z, are by default "false".  You make condition c "true" by using XCOM-I's --cond=c command-line switch.

Interpretations of the possible conditions are entirely at the user's disposal; i.e., they vary by the particular source-code being compiled by XCOM-I.  In the particular case of the XPL/I source code for the HAL/S-FC program, there are 4 possible conditions c that I'm aware of:

XCOM-I Command-Line Switch
Interpretation
--cond=P
HAL/S-FC will be specialized for compiling the Space Shuttle's Primary Flight Software (PFS).
--cond=B HAL/S-FC will be specialized for compiling the Space Shuttle's Backup Flight Software (BFS).
Note:  Either --cond=P or --cond=B must be used, but not both at the same time.
--cond=A Produce debugging output related to memory management of BASED variables.
--cond=C Produces debugging output related to actions by the COMPACTIFY procedure.

Aside: This implies that you don't just compile HAL/S-FC once to get a HAL/S compiler that works for all HAL/S programs. Rather, you compile HAL/S-FC twice, once to get a version of the HAL/S compiler that works for the primary flight software, and once to get a version of the compiler that works for the backup flight software.

Compiler Directive Type:  /%INCLUDE module %/

This type of compiler directive inserts an entire XPL/I source-code file, module.xpl, at the current point.  It is used for importing COMMON-block declarations or macros which are used identically by all source-code files.  By default, the included module is taken from the folder ../HALINCL/.  As far as I know this covers every use in HAL/S-FC source code.  However, if necessary, XCOM-I has a command-line option (--include=folder) which can be used to change the folder containing the modules.

Compiler Directive Type:  /* ...comment... $%module */

This is a variant of the /%INCLUDE module ...comment... %/ directive, which acts the same way, and for which my comments are otherwise the same.

Compiler Directive Type:  /**MERGE module procedure */

This is yet another directive for including an XPL/I source-code file in the current XPL/I source-code file, but it differs from the other include-directives described above in that instead of importing definitions used in common by multiple source-code files, it instead is typically used for importing the source code for a single PROCEDURE.  As above, module.xpl is the source-code file to include, while procedure is the name of the PROCEDURE contained in that file.  In point of fact, XCOM-I simply ignores the procedure name.
Aside: Procedure names don't match the filenames, usually, because the naming conventions for System/360 files were severely limited vs identifiers in XPL.  Thus the filenames were normalized, truncated forms of the procedure names.
Also, module.xpl is expected to be within the same folder as the source-code file being compiled; no other folders are searched for it, and there are no command-line switches to alter this behavior.

PROCEDUREs, RETURNs, and Their Peculiarities

In a view from a height, an XPL program consists of PROCEDURE definitions and of code that uses those definitions.

A procedure definition looks something like this:

label:
PROCEDURE(... parameter list ...) ReturnType;
DECLARE ... for the parameters ...;
DECLARE ... for local variables ...;

... code ...;
END label;

A lot of this is optional. Thus while the initial label: is required (since it's the name of the procedure), the label at the end of the definition is optional, and is really there only for readability purposes. If the procedure needs no parameters, then the parameter list, including its enclosing parentheses, is omitted. If the procedure returns no value, then ReturnType is omitted; if present, it is one of the basic non-subscripted datatypes FIXED, BIT(n), or CHARACTER. Each parameter in the parameter list must have a declaration within the body of the procedure, and while those declarations don't technically have to precede the declarations of the local variables as shown above, it was apparently customary to do so.

PROCEDURE definitions in the source code can be nested, to any desired depth. PROCEDURE definitions and DO...END blocks provide the program with a hierarchical structure of "scopes".  The hierarchical structure is provided by the parent/child relationships among the scopes. Variables are accessible within the scope in which they're declared, along with any descendant scopes. If a variable is declared in more than one scope of the hierarchy, the one in the innermost enclosing scope is the one that is applicable. Note, though, that variables can be declared only at the global level or in a procedure, and not within DO...END blocks.
Warning:  As usual, there are traps.  As I mentioned above, PROCEDURE definitions can be embedded within other PROCEDURE definitions, and are accessible within that PROCEDURE or descendant scopes.  Can a PROCEDURE be defined within a DO...END block?  It can!  But (trap!) it is accessible anywhere in the enclosing PROCEDURE or that PROCEDURE's descendants, and is not limited to the DO...END block in which it was defined.  (For example, the COMMENT_BRACKET procedure within HAL/S-FC's OUTPUTWR.xpl.)

A PROCEDURE may be invoked in two different ways.  If it returns a value via a RETURN statement, it can be used in an arithmetical expression or a string expression.  If it doesn't return a value, or if it does return a value and you simply want to ignore the value, a CALL statement can be used to invoke the PROCEDURE but to discard any returned value.

Important:  All variables local to a PROCEDURE definition retain their values after the PROCEDURE returns.  If the PROCEDURE is re-executed, those local variables retain the values they previously had in the prior invocation of the PROCEDURE.  The values of those local variables, though retained, are inaccessible to code outside of the PROCEDURE, because the compiler enforces scopes of variables. (In C code, this would be the same thing as saying that every local variable of every function is automatically declared as static.)

Very important: Any parameters at the end of the calling list of a PROCEDURE can be omitted from when calling the PROCEDURE, and if omitted, they retain the same values as the last time the PROCEDURE was invoked or the values previously assigned to those parameters from within the PROCEDURE itself!  (In essence, this is like saying that parameters of a PROCEDURE are not passed to the PROCEDURE, but rather that they are just aliases for some set of global variables dedicated to the PROCEDURE.)  That's so weird that we need to see an example.  Consider the following XPL PROCEDURE definition, and CALLs to it:

weirdo: 
procedure(x, y, z);
declare (x, y, z) fixed; /* Declare x,y,z as integers */
output = x || ' ' || y || ' ' || z; /* Print out x,y,z */
x = 29;
y = y + 1;
end weirdo;

call weirdo(1, 2, 3);
call weirdo(4, 5);
call weirdo(6);
call weirdo();
call weirdo;

The five calls successively print out the following:

1 2 3
4 5 3
6 6 3
29 7 3
29 8 3

PROCEDUREs cannot be recursive, either directly or indirectly.

Taking these facts altogether, XCOM-I implements both parameters and local variables of PROCEDUREs essentially as global variables in they way they are stored:  i.e., each parameter and each local variable of each PROCEDURE has its own static address (in the global memory model), assigned at compile time and unchanging thereafter.  The compiler enforces the logical scoping of these variables.

Regarding the RETURN statement, McKeeman explains that it is used to exit from a PROCEDURE and optionally to return a value. Furthermore, the calling code can either use that return value or else ignore it. Which makes perfect sense. But as usual, there are some documented and undocumented peculiarities to the RETURN statement as well:

Blocks and Loops

Compound statements in XPL are groupings of simple statements (such as assignments or if-then-else statements) enclosed within a DO ... END block:

DO ...;
... simple statements ...
END;

There are five different kinds of DO ... END blocks. First, there is a mere grouping:

DO;
... simple statements ...
END;

Then there are 3 different kinds of loops:

DO COUNTER = START TO END [BY STEP];
... simple statements ...
END;

DO WHILE CONDITION;
... simple statements ...
END;

DO UNTIL CONDITION;
... simple statements ...
END;

Note that DO UNTIL is new in XPL/I and is not present in standard XPL.

In these loops, COUNTER, START, END, and the optional STEP are all integers. STEP defaults to 1, but must be positive. START, END, and STEP may be expressions, but if so they are evaluated only a single time, at the start of the loop, and are not reevaluated thereafter. CONDITION, on the other hand, is an expression evaluated on each loop; it is treated as "true" if its least-significant bit is 1, or "false" if its least-significant bit is 0.

The fifth kind of DO ... END block is:

DO CASE EXPRESSION;
STATEMENT0;
STATEMENT1;
STATEMENT2;
...
END;

The EXPRESSION must evaluate to an integer. If 0, then STATEMENT0 is executed; if 1, then STATEMENT1 is executed; and so on. At most, a single statement is executed, and there is no "fall through" from one statement to the next. If the EXPRESSION is negative or beyond the number of available statements, McKeeman tells us that "a random jump is executed". In XCOM-I, no statement is executed under those circumstances, and control passes to the next statement after the END.

ESCAPE and REPEAT

The ESCAPE and REPEAT keywords appear to be undocumented XPL/I features not present in standard XPL. Unfortunately, from the available material I can't think of any way to be sure what they do, so I can only speculate.

ESCAPE appears in two different forms:

It should be noted that the HAL/S language has the keyword EXIT, which also has these two forms. (See Ryer, p. 5-12.) EXIT has the following behavior in HAL/S:

Until a more-plausible explanation comes along, my assumption is that ESCAPE in XPL/I has the same behavior as HAL/S's EXIT.

For example, consider the following XPL/I code:

...
MYBLOCK:
DO ...
    ...
    DO ...
        ...
        ESCAPE; /* Escape #1 */
        ...
        ESCAPE MYBLOCK; /* Escape #2 */
        ...
    END;
      /* Escape #1 comes here! */
    ...
END;
/* Escape #2 comes here! */
...
REPEAT also appears in those same two forms:
And again, there's a REPEAT keyword in HAL/S as well.  In this case, though, from examining the actual usage in HAL/S-FC source code, I believe that while the keyword has a similar behavior in HAL/S and XPL/I, there is nevertheless a distinction between them.  Here's what I believe the behavior is in XPL/I:
Aside:  The way my XPL/I implementation is different from HAL/S is that in HAL/S, REPEAT (without a label) goes to the beginning of the innermost enclosing loop (DO WHILE or DO UNTIL or DO I = X TO Y) rather than the innermost enclosing DO ... END.  Which makes sense, since that's what you'd normally want.
Note:  Neither ESCAPE nor REPEAT accepts a label attached to an arbitrary statement.  It must be a label attached to a DO ... END block, and it must at some level enclose the ESCAPE or REPEAT statement itself.

Counter Value After Normal Loop Termination

For XPL code such as
DO I = 1 to 100;
...
END;

it appears to be undocumented what value I holds after the loop ends.  Not all computer languages handle this in the same way.  My considered opinion is that the loop counter holds the value at which the condition for continuation fails: in this example, 101.  Or, if the loop is broken prematurely (as with ESCAPE from the preceding section), then the value the counter held when the ESCAPE occurred is retained.
Aside:  In Python, for example, the counter for an equivalent loop would have the value 100 rather than 101 after normal termination of the loop.

Built-In Runtime-Library Functions

Standard XPL has a variety of so-called "built-ins", comprising runtime-library functions callable from XPL code. Some of these bullt-ins can appear on either the right-hand or left-hand side of assignments, and some have to be CALL'd like user-defined PROCEDUREs. XPL/I has roughly the same built-ins, plus-or-minus a few, mostly (but not entirely) defined to have the same functionality. The compiler recognizes these built-ins, and there is no need for them to be declared in any way prior to use. Since these built-ins were mostly written originally in IBM System/360 basic assembly language, the runtime library supplied with XCOM-I has been entirely written in C, without any reference to the original runtime-library source code.

The list below is from McKeeman (p. 140-142), with some alterations due to XPL/I, and some hopefully-helpful notes from me. The parameter descriptions in the list below identify the datatypes of parameters by the following convention:

I suppose I should make it clear that the XCOM-I environment is not precisely like that envisaged in the original XPL language as confined to an IBM 360 runtime environment, and as such, built-in functions don't work exactly the same way either. What's described here is how the XCOM-I runtime library's functions corresponding to the original built-in functions work.

Library Function
Description From Original Documentation
Additional Notes
ABS(NE)
This function returns the absolute value of NE.  (Note: "80000000", the maximum negative number, has no representable absolute value and returns "7FFFFFFF", the maximum positive number.)
(XPL/I only.)
ADDR(V) A function with numeric value which is the (at most) 24-bit absolute address of the variable (subscripted or not) V.  Mostly used in conjunction with COREWORD or COREBYTE.
If the variable has a subscript, then the returned address is that of a specific array element, rather than of the beginning of the array.  For a CHARACTER or long BIT string (i.e., for BIT(33) through BIT(2048) datatypes), the returned address is that of the descriptor for the variable, and if you want to find the actual data, you then must first fetch the value of the descriptor and then massage it further.  For XPL/I's BASED variables (see IR-182-1 p. 13-3), using the unsubscribed name of the variable for V will return the address in memory where the pointer to the BASED variable's data is stored; whereas adding the subscript (0) to V returns the address of the variable's data itself.
BYTE(DE,NE)
A function with numeric value given by the NEth 8 bits of the string described DE.
By "string", McKeeman means either CHARACTER data or BIT data.  As far as BIT data is concerned, this function works for any data-width; i.e., for BIT(1) through BIT(2048), and not just for "long" BIT string.  However, there's some difference between how CHARACTER and BIT data is treated.  BIT data is retrieved from memory as-is, whereas CHARACTER data is transparently translated between EBCDIC encoding (in memory) vs ASCII encoding (for manipulation) by the software.  The latter behavior is specific to XCOM-I, and wouldn't have been needed by the original XCOM, since back then, all CHARACTER data was encoded in EBCDIC all the time, whether or not it resided in "memory".  Moreover, if you try to do so, it is possible to fool XCOM-I's BYTE function into thinking an area of memory is a different datatype than it really is, thus defeating the translation mechanism ... so please don't try to do that.

Note that BYTE can appear either in an expression (such as on the right-hand side of an assignment), in which case it returns a value as just described, or else on the left-hand side of an assignment, in which case it modifies the value stored in memory.  McKeeman recommends not using BYTE on the left-hand side of an assigment, due to the possibility of unintended consequences.

The documentation does not explain what's supposed to happen if NE<0 or NE>=len(DE).  Alas, that's not a theoretical question, because such uses of BYTE really appear in legacy XPL code.  Given the sloppy subscripting accepted by XPL, my guess is that BYTE just grabs whatever happens to reside wherever NE leads it.  Unlike the usual sloppy subscripting in XPL code, this would almost certainly be a mistake by the programmer, because the location of string data in memory is dynamically assigned and liable to change during the course of execution, so the programmer can't really know reliable what lies beyond the bounds of a string.  Thus my guess is that when NE is out of bounds for the string size, it's probably a mistake on the part of the program that was never detected because it never produced any error messages.
Aside: For example, it happens in the SCAN procedure of XCOM, in the vicinity of line 835 at this writing,
CP = 1;                                                          
DO WHILE BYTE(TEXT, CP) = BYTE(' ') & CP <= TEXT_LIMIT;         
CP = CP + 1;                                                 
END;  
                                                          
where TEXT happens to be a string with LEN(TEXT)=1 containing a single blank space, and TEXT_LIMIT=0.  Here, BYTE(TEXT,CP) is out of bounds on the very first iteration of the loop.  In C, if the operands of the & operator were reversed, then BYTE(TEXT,CP) would never be executed, and the problem would be avoided.  But if that kind of optimization of conditionals is a feature of XPL, it's not a feature that's mentioned anywhere to my knowledge, and XCOM-I does not attempt it.
Whether or not that's a true in interpretation, in XCOM-I the BYTE function returns a value of 0 (corresponding to an EBCDIC NUL) if NE is out of bounds, or else silently does nothing at all if on the left-hand side of an assignment.
BYTE(DE)
Same as BYTE(DE,0)
CLOCK_TRAP
Not supported in XCOM. And it's not supported in XCOM-I either.
COMPACTIFY
A procedure called automatically to repack the free storage area for string data when it is exhausted. Calls can be triggered by ||, INPUT, number-to-string conversions, or an explicit call:
CALL COMPACTIFY;
The variables FREEBASE, FREELIMIT, FREEPOINT, DESCRIPTOR, and NDESCRIPT are used by COMPACTIFY.
The "free-storage area" is where the data for CHARACTER and long BIT-string variables is stored.  As a program which involves string variables executes, the strings tend to change size and move around in the free-storage area, thus causing unused "holes" in memory to develop.  COMPACTIFY repacks the area so as to remove the holes.
Aside:  While long BIT-strings (>32 bits) are technically similar to CHARACTER strings, they differ in the fact that the amount of storage they require is determined at compile-time and does not change after that.  The original McKeeman and Intermetrics compilers therefore stored them outside of the free-storage area — or at least in the very lowest addresses of the free-string area in order to save any execution time that would otherwise have been wasted during allocation of memory or garbage allocation.  XCOM-I is not so picky, since even the most feeble modern computer will be approximately 14.3926 gazillion times faster than an IBM 360 was.  XCOM-I thus saves (me) a little effort by grouping the long bit strings with the character strings in memory, at an immeasurably-small cost in execution time.
XPL/I's BASED variables are stored in a separate memory region and do not participate in garbage collection by COMPACTIFYSee the earlier discussion of this.
COREBYTE(NE)
A byte array identical to the IBM System/360 memory.  The subscript is the absolute byte address of the byte selected.  COREBYTE may be used on either side of the replacement operator (i.e., =).
The function returns a FIXED value, if used in an expression, but only the least-significant 8 bits contain the value.  Similarly, if used on the left-hand side of an assignment, it receives a FIXED value from the right-hand side, but only stores the least-significant 8 bits at the specified memory address.  The XCOM-I version of this function performs no EBCDIC translation as BYTE (see above) does, so it does not expect the data in memory to be CHARACTER data.  Whether this will turn out to be an issue remains to be seen.
COREWORD(NE)
Like COREBYTE, except the subscript corresponds to the word address in memory.  Thus an assignment to COREBYTE(4) can change COREWORD(1).
Since XPL has only a 24-bit address space, only the least-significant 3 bytes of the "4-byte word address" NE are used.  According to IR-182-1 (p. 13-3), this function differs in XPL/I as follows:
According to "A COMPILER GENERATOR", NE is a word index, or word-aligned address. However, in the Intermetrics version, NE must be a byte address, and the user must himself guarantee that the lower-most two bits are 0's (full word aligned).
I think that what's being implied by this cryptic comment is that McKeeman's COREWORD in XPL worked like this:
  • COREWORD(0) consists of COREBYTE(0) through COREBYTE(3).
  • COREWORD(1) consists of COREBYTE(4) through COREBYTE(7).
  • COREWORD(2) consists of COREBYTE(8) through COREBYTE(11).
  • and so on.

whereas Intermetrics's COREWORD in XPL/I worked like this:

  • COREWORD(0) consists of COREBYTE(0) through COREBYTE(3).
  • COREWORD(1) is illegal.
  • COREWORD(2) is illegal.
  • COREWORD(3) is illegal.
  • COREWORD(4) consists of COREBYTE(4) through COREBYTE(7).
  • COREWORD(5) is illegal.
  • COREWORD(6) is illegal.
  • COREWORD(7) is illegal.
  • COREWORD(8) consists of COREBYTE(8) through COREBYTE(11).
  • COREWORD(9) is illegal.
  • and so on.

To my way of thinking, McKeeman's description doesn't say what IR-182-1 says it says, and unfortunately, McKeeman's clarification that "an assignment to COREBYTE(4) can change COREWORD(1)", does not in fact clarify it very much either.

XCOM-I conforms to the latter (Intermetrics) usage, with the exception that addresses like 1, 2, 3, 5, 6, 7, 9, ... are perfectly fine: COREWORD(1) consists of COREBYTE(1) through COREBYTE(4), and so on. Which conforms just fine to the description in McKeeman as well, if not necessarily to McKeeman's actual usage.

Aside:  In point of fact, COREWORD is not used in any software written in standard XPL that's available to me, so I cannot determine empirically which of these choices (if either) is correct.  If it is somehow discovered later that McKeeman's XCOM really does need to behave in the manner Intermetrics claimed that it did, then XCOM-I's --xpl command-line switch will be extended to select between the two behaviors.  But I don't intend to waste any effort implementing that until/unless I have enough evidence to justify it.  For now, it's a moot point.
DATE
A function with the numeric value of the date, coded as
(day of year) + 1000 * (year - 1900)
McKeeman doesn't inform us of anything so mundane as the time zone to which this relates.  XCOM-I uses UTC.
DATE_OF_GENERATION
A word variable initialized with the value of DATE during compilation of the program being run.
Unfortunately, there's a slight bug in XCOM-I which I haven't found any satisfying way to resolve, which is that DATE_OF_GENERATION uses the local time-zone of the C compilation rather than UTC as DATE (see above) does.
DESCRIPTOR(NE)
The description of the NEth string as a numeric value.
I believe that this cryptic description is referring to the fact that in the way memory is allocated for the variables declared by XCOM, the 32-bit string descriptors for all CHARACTER and BIT(n) (n>32) variables appear consecutively in memory.  By symbolically labeling the very first of those descriptors as DESCRIPTOR, and treating DESCRIPTOR as of the FIXED datatype, DESCRIPTOR becomes an easily-accessible array of all the string descriptors.  This is useful, for example, for memory-management procedures like COMPACTIFY.  The number of elements of the array is given by the built-in NDESCRIPT (see below).
EXIT
A procedure
CALL EXIT;
which causes an abnormal exit form XPL execution.
In fact, XCOM-I models this a C-language exit(1).
FILE(NE1,NE2)
An array-valued pseudovariable for manipulation of random-access bulk storage.  Examples of its use are
DECLARE BUFF(3600) BIT(8);
BUFF = FILE(I, J);
FILE(I-1, J) = BUFF;

One record is transferred into or out of the buffer array by the assignments shown above.  FILE cannot appear on both sides of the same assignment. 
I've actually truncated McKeeman's description of FILE, because it is so long and so much of it is specific to IBM System/360, while being quite irrelevant to us.  The key facts are these:
  • "Random access files" may be attached to the running program. 
  • Once attached, they are identified by a "device number" that ranges from 1 to 9. 
  • Each attached random-access file may be for input-only, output-only, or both. 
  • Each of the attached files consists of records of a fixed size.
  • Different files may have different record sizes.
  • These random-access files, and the associated FILE operations, are completely distinct from the "sequential files" (numbered 0 through 9) accessed via the INPUT and OUTPUT functions (see below).
  • Record sizes typically do not change during program execution. (But see the continued comments below.)
By default, no random-access files are attached to programs.  Originally (back in 1970), they were attached at runtime via Job Control Language (JCL) DD cards.  But with programs compiled via XCOM-I, files are attached at program startup by using the program's command-line switches.  The relevant switch is --raf, and multiple instances can be used on the same command line.
--raf=I,R,N,F
The parameters of the --raf switch are:
  • I is either a literal I (for "input"), a literal O (for "output"), or a literal B (for "both input and output"). Note that for O, an empty file will always be created by the program; for B, if there's an existing file of the specified name, then it will be used, but an empty file will be created otherwise.
  • R is the record size associated with the file. 3600 and 7200 are typical values, but not the only ones in use. They must match the expectations of the program's XPL code for using the data.
  • N is the device number, from 1 through 9, to which the file should be attached.
  • F is the relative or absolute path to the file.

In spite of what I said above, the XPL/I documentation (IR-182-1, p. 13-5) does describe one way to change the record size of a random-access file once the program has begun operation, and that is via a call to MONITOR(4,R,N) (see below), where R and N have the same meanings as for --raf. It also tells us that such a call must precede the first use of FILE(N). XCOM-I relaxes this restriction, though it seems that the original restriction should be treated as very good advice to follow. Note, by the way, that the call to MONITOR(4,R,N) has no way to distinguish between input files and output files, so its record-size manipulation is applied simultaneously to input file N and output file N, if they differ.

FREEBASE
A word variable containing the absolute address of the top of constant strings and thus the bottom of the repackable area.
In XCOM-I, this is implemented as a function rather than a variable, and is not directly modifiable by user code. 

What the description from the original documentation is trying to say is that most kinds of variables — such as FIXED or BIT(1) types, or descriptors for CHARACTER or long BIT-string types, or arrays thereof, or pointers to BASED variables — are stored in memory at low addresses determined at compile time, and remain unchanged in address thereafter.  In contrast, data for CHARACTER variables can grow or shrink or move at runtime.  FREEBASE is the boundary between those regions of memory.

In XCOM-I, data allocated at runtime via the MONITOR(6,...) or MONITOR(19,...) function (see below), sometimes for storing data of for BASED variables, also appears above the FREEBASE boundary.
FREELIMIT
A word variable containing the absolute address of the last usable byte in the string-data area.
I believe that the description lies, and that FREELIMIT is the first unusable byte beyond the string-data area, rather than the last usable byte in it.

In XPL, all memory beyond FREELIMIT is unused by the program.  In XPL/I, the data for BASED variables is stored at FREELIMIT and above.  However, the memory-management technique for is to push FREELIMIT downward in order to make room for BASED variables.  So for either XPL or XPL/I, the initial value of FREELIMIT is the highest memory location used by the program.  XCOM-I sets the initial value of FREELIMIT at "FFFC00" (i.e., 1024 bytes below the top of physical memory), in order to leave a small amount of space for COMPACTIFY to use as scratch memory.
FREEPOINT
A word variable containing the absolute address of the next free byte in the string-data area.  When FREEPOINT passes FREELIMIT, COMPACTIFY must be called.
In other words, at any given time, only the memory region between FREEPOINT and FREELIMIT is available for dynamically-allocating new free memory for BASED variables or CHARACTER variables.  as the program continues to execute, dynamic memory allocation and subsequent memory-freeing may cause FREEPOINT to steadily creep upward, and the area from FREEBASE to FREEPOINT to become increasingly fully of unusable holes.  Eventually, COMPACTIFY will be automatically called, repacking the data to eliminate the holes, and moving FREEPOINT downward in memory once again.
INLINE(NE1,...)
Inserts arbitrary IBM System/360 machine code directly into the instruction stream.
I've replaced McKeeman's lengthy description with a short synopsis, because the applicability of the original functionality has change drastically in the 55 years (as of this writing) since A Compiler Generator was written.

XCOM-I does not support the originally-described functionality as-is.  Instead, you can use
CALL INLINE(DE);
to perform the conceptually-similar task of inserting an arbitrary string of C-language code directly into the instruction stream.  If the string contains newline characters ('\n'), then a single INLINE can insert multiple lines of C code.  (But recall, an XPL string can be no longer than 256 characters.)

More-importantly, in XCOM-I, there is also a different mechanism for using legacy CALL INLINE statements to patch lengthy C-language replacements for the original BAL code represented by the INLINEs into the instruction stream, while leaving the original XPL/I source code unchanged.  This important but far from automatic mechanism is the topic of a later section.

Thankfully, no available legacy standard XPL program contains any CALL INLINE statements, so the difficulties presented by them are limited to XPL/I code such as HAL/S-FC.
INPUT(NE)
A function with descriptor value specifying the next record on input file NE.
I've truncated McKeeman's rather roundabout description, because I don't find it very informative as-is to more-modern readers.

What McKeeman is trying to say is that any given program can have several files attached to it for so-called sequential input.  You can think of these files as consisting of lines of text, and you can read them, one text-line at a time, by using the INPUT built-in function.  For the original McKeeman XCOM (or the Intermetrics version of XCOM), the text in the sequential files would have been encoded in EBCDIC, but for use with XCOM-I they are encoded in ASCII.

Because XPL comes from the era and computing environment it did, it expects input to be supplied on computer punch cards.  As such, it expects lines of input to actually be 80 columns wide, even if they need to be padded by spaces to do so.  Not all XPL programs have problems with lines of other lengths have a problem with lines of a different length, but some do.  Notably, the original XCOM is one of the programs that behaves incorrectly unless it gets its way.  Because of that, XCOM-I's built-in INPUT function transparently truncates lines to 80 columns or pads them 80 columns as necessary.


There are up to 9 input files attached to the program, numbered, 0 through 9 — yes, I know that's 10 files, but just keep reading — and you access them via those "device numbers".  For example,
DECLARE CHARACTER C;
C = INPUT(5);

reads a single line from sequential file number 5 into the variable C.
Important: These "sequential files" and the INPUT mechanism are completely separate from the "random-access files" described in FILE's notes earlier!
By default, there is a single sequential file attached to the program, though it is attached to both device number 0 and device number 1.  The default attached file is stdin, and thus any text piped into the program via a redirector (<) on the program's command line will be available on both INPUT(0) and INPUT(1).
Aside:  In case you're wondering, I don't think that the reason two separate devices are associated with stdin is very deep or interesting.  I imagine it's just kind of symmetric to the fact that OUTPUT(0) and OUTPUT(1) (see below) are both attached by default to stdout.  In situations like this I'm tempted to quote Emerson — "a foolish consistency is the hobgoblin of little minds" — but I suspect the decision was pragmatic rather than foolish.
There's no explanation or obvious provision in the documentation for what happens when an end-of-file is reached, and XCOM-I makes no particular provision for it either.  I suppose it must depend on the particular program.  For example, some XPL programs assume that a blank line terminates a file.  Other programs may look for special patterns, such as the string "EOF".  XCOM-I returns an empty string for reads past the end of the file.

Additional input files can be attached via the program's command-line switch --ddi:
--ddi=N,F
Here, F is the pathname to the file, and N is the device number to which it should be attached.  By the way, if you want to debug your program once it has been translated by XCOM-I to C and then the C has been compiled to an exectuable, I've sometimes found piping input into the program via a command-line redirector (<) to be somewhat problematic.  It's handy in those situations to override the default attachment of stdin to device 0 by using --ddi=0,F instead of piping. 

There is no way to change the attachments once the program is running, although there are MONITOR calls (see below) which can close attached files at runtime.

In XPL/I, there's an additional alternative, in which Partioned Data Set (PDS) files can be attached for use by INPUT instead of sequential files. A PDS is partitioned into sections, each with its own 8-character identifying name (right-padded with blanks if necessary).  An attempted read past the end of a partition returns an empty string.

Only one partition of the PDS is available via INPUT at any given time, but
F = MONITOR(2, N, NAME);
(see below) can be used at any time to switch device N to the partition whose name is contained in the string called NAME.  The value returned, F, is 0 if the switch was successful, or 1 if there was no such partition.

A PDS is thus similar to a folder of sequential files, with the partition names corresponding to filenames within the folder. 

By default, no there is no PDS attached to programs.  However, a PDS can be attached for input via the program's command-line switch --pdsi:
--pdsi=N,F
Because of PDS similarity to folders, that's how they're implemented in XCOM-I. So F should be the pathname of a folder, and the partitions should be ASCII text files within the folder.  N, of course, is the device number on which the attachment is to be made.  However, no specific partition is selected for reading until the necessary MONITOR(2, N, NAME) call has been made.
INPUT
Same as INPUT(0)

INTERRUPT_TRAP
Not supported in XCOM.
Not supported in XCOM-I.
LENGTH(DE)
A function with the numeric value equal to the number of characters in the string denoted by the parameter.

LINE_COUNT
This function returns the number of lines which have been printed on the SYSPRINT file since the last page eject.
(XPL/I only.)  "SYSPRINT" refers to OUTPUT(0) and OUTPUT(1) (see below).
LINK
This procedure performs the functions necessary to exit the current program phase and pass control to the next phase on the PROGRAM DD sequence, preserving COMMON data and any other dynamically allocated space which has not been deallocated.
(XPL/I only.)  This refers to the notion that a sequence of XPL/I programs is being run via Job Control Language (JCL), with each program passing data to the next program in sequence.  However, this LINK built-in is specific to the original computing environment, and performs no function in XCOM-I, which has a different mechanism sharing data between program passes.  Refer to the extensive discussion concerning COMMON memory.
MONITOR(NE1,...)
Calls the "submonitor".
The "submonitor" is a separate program from whatever XPL or XPL/I program is being compiled and run, providing some kine of system-specific functionality.  In so far as McKeeman is concerned, the submonitor's functionality and even it calling sequence was unknowable, and thus the McKeeman XCOM system simply discarded all calls to the submonitor.

Not so with XPL/I: There is an extensive set of functions provided by the submonitor, and the XPL/I program HAL/S-FC uses them extensively as well, so XCOM-I needs to support them.  Most (I hope!) but not all submonitor functions are known from their descriptions in section 13.3 of IR-182-1.  Besides which, HAL/S-FC source code contains the source code for MONITOR (written in IBM 360 Basic Assembly Language), from which additional information can be obtained.

Because of the complexity of MONITOR, an entire separate section is devoted to explaining it.
MONITOR_LINK
A fixed array which can be used for transmission of information between a program and the submonitor.
(See Table 8.4.6 in McKeeman.)  I'm not presently aware of any need to implement this in XCOM-I.  However, there are MONITOR functions (see above) whose operations remain a mystery, so there's a possibility that that could change.
NDESCRIPT(NE)
A word variable containing the upper bound in the array DESCRIPTOR (see above) of the descriptions.
See DESCRIPTOR above.
OUTPUT(NE)
This is the analog of INPUT (see above) but for outputting textual data rather than inputting it.
Most of the comments concerning INPUT (see above) are directly applicable to OUTPUT, or else are analogous in a very obvious way, so I'll try to confine my remarks to the clear differences between the two.

The most common use of OUTPUT is to "print" to the "SYSPRINT" device.  Unlike the original XCOM computing environment, in which SYSPRINT was an actual printer, in XCOM-I SYSPRINT by default refers to stdout.  And by default, stdout is attached to both OUTPUT(0) and OUTPUT(1). But there are differences between the two!  Use of OUTPUT(0) is simpler, and therefore more common, but OUTPUT(1) is more flexible.  Let's start with OUTPUT(0).

Output statements via OUTPUT(0) might look something like this:
OUTPUT(0) = 'Hello, world!';
OUTPUT(0) = 'Hello' || ',' || ' ' || 'world' || '!';
OUTPUT(0) = 'This is OUTPUT statement number ' || 3;
OUTPUT(0) = 25;

Thus we can print any character strings or numbers we like, and can concatenate them using the string-concatenation operator (||), with XCOM-I doing the work of automatically converting numbers to strings where necessary.

Each use of OUTPUT(0) results in another physical line being printed.  When printing messages with OUTPUT, since it is expected that the lines are being output to a physical printer, the system keeps track of the number of lines being printed, and automatically inserts page breaks in the output once a page has been filled up.  At the tops of pages, a page number and optional headings and subheadings are also printed.  The headings and subheadings, though, are simply whatever has been previously set up, and you can't change them or otherwise influence them using OUTPUT(0).

With OUTPUT(1), on the other hand, there's quite a bit of additional functionality.  For example, it isn't necessarily true that each use of OUTPUT(1) prints another line.  The first character of each OUTPUT(1) isn't directly printed, but instead is a "carriage-control character" (or "ANSI control character"), as follows:
  • ' ' — Single-space the line and print. This is the same behavior as OUTPUT(0): i.e., any line you print to OUTPUT(0), you could instead prefix it with a space character and send it to OUTPUT(1) instead.
  • '0' — Double-space the line and print.
  • '-' — Triple-space the line and print.
  • '+' — Do not space the line at all, but print. In other words, prints but doesn't advance to the next line. It behaves like an ASCII carriage-return character ('\r'). For a physical printer back in the day, this would allow you to overprint the line upon the next OUTPUT. Unfortunately, for stdout on most computers, we can't emulate such overprinting in any meaningful way. Thus in XCOM-I, the behavior is indistinguishable from the "single space and print" behavior.
  • '1' — Form feed. I.e., advance to the top of the next page, regardless of how many left-over lines remain on the current page. In XCOM-I, this is accomplished by embedding the ASCII form-feed character ('\l') in the output.
  • 'H' — Heading line. This doesn't print anything, but takes the remainder of the line and sets it as the heading for subsequent pages.
  • '2' — Subheading line. This doesn't print anything, but takes the remainder of the line and sets it as the subheading for subsequent pages.
If you experiment with OUTPUT(1), and even to a certain extent with OUTPUT(0), you may become very confused, because various things won't work as you expect.  Or at least, I was very confused at first.  To get past this confusion, you need to grasp the following
Very important point:  For the computer systems most people are familiar with today, when we print a line of text, we expect that line of text to end with an implicit advance to the next line; i.e., with a "newline" character, often signified in programming languages by '\n'.  Whereas in System/360 (and therefore in XPL or XPL/I), the implicit newline occurs prior to the text being printed by the OUTPUT command.  I.e., today we typically ask ourselves "what happens at the end of the line"", whereas for XPL you need to ask "what happens at the beginning of the line?".
Similarly to INPUT, you can attach additional sequential output files (F) on device numbers (N),
--ddo=N,F
Or for Partitioned Data Sets,
--pdso=N,F
For selecting partitions of a PDS, you don't use the same MONITOR call as for INPUT, but instead use:
F = MONITOR(1, N, NAME);
OUTPUT commands targets for a PDS don't immediately write data to the physical PDS.  Rather, the data being output is buffered in memory until the MONITOR(1, N, NAME) call occurs, and the data is then written out to the selected partition in its entirety.  The return value (F) is 0 if the partition is new (i.e., if it didn't previously exist in the PDS), while it is 1 if the partion previously existed but has now been overwritten with entirely new contents.
OUTPUT
The same as OUTPUT(0).

PARM_FIELD
This function returns a character string which contains the entire parameter specification coded on the PARM= option on the EXEC card.  If no PARM is specified, a null string will be returned.
(XPL/I only.)  A program of any complexity generally has a number of options selectable at runtime.  For XPL/I programs like HAL/S-FC, the mechanism for selecting such options was originally a Job Control Language (JCL) card such as:
PARM='SYTSIZE=1800,REFSIZE=20000,LISTING2,$I,$V,$U,$W'
Thus, PARM_FIELD would have had the value 'SYTSIZE=1800,REFSIZE=20000,LISTING2,$I,$V,$U,$W' in such a case.

In XCOM-I, PARM_FIELD is implemented as a function rather than a variable, but nevertheless returns data of the kind described.  Of course, there is no JCL supplying such parameters, but the compiled program nevertheless has a command-line option that does the same job:
--parm='SYTSIZE=1800,REFSIZE=20000,LISTING2,$I,$V,$U,$W'
Aside: Depending on your operating system, some trickery may be involved in correctly forming such a command-line option.  In Linux or (I suppose) Mac OS, the command shell expects the dollar sign ($) to indicate that the value of an environment variable is desired.  In other words, if the string in the command-line option shown above had no quotes, then (for example) $I would be replaced by the value of the environment-variable I ... probably a blank!  And the same thing would happen if the option were enclosed in double-quotes.  But by using single-quotes, we defeat that substitution, and $I is reported in PARM_FIELD literally as $I.  Alternate tricks to defeating substitution could include using the backslash ('\') escape character in front of all dollar signs ($). 

Aside: It has been decades since I used Windows in any serious way, so I don't really know whether there's any similar problem with it.  Probably not.

RECORD_WIDTH(V)

(XPL/I only.)  IR-182-1 doesn't mention it, but it appears to me that RECORD_WIDTH is a new built-in in XPL/I which when given the symbolic name of a BASED variable returns the variable's record size (in bytes).  In HAL/S-FC's XPL/I source code (the only available example of use of RECORD_WIDTH), I find it used only for a couple of BASED RECORD variables, which makes sense according to the naming, but I have no way to tell just from those few examples what the actual applicability was.  In XCOM-I, it will work for any BASED variable, RECORD or not.
SET_LINELIM(NE)
This procedure establishes the number of lines which will be printed on the SYSPRINT file before an automatic page eject and header line will be printed.
(XPL/I only.)  See OUTPUT(NE).
SHL(NE1,NE2)
A function with the numeric value given by shifting the value of NE1 left (logical shift, zeroes appear in the least significant bit position) the number of positions indicated by the value of NE2.
One point I found confusing for quite a while is that you'd suppose that logical shifts would operate on BIT variables, and particularly on long-BIT variables, for which XPL provides no conveniences at all for accessing individual bits.  Not so!  SHL operates only on FIXED values, and can only shift by up to 32 positions.  BIT variables are converted to FIXED if shift operations on them are needed.
SHR(NE1,NE2)
Logical shift right.  SHL and SHR are used in conjunction with &, |, and ¬ for masking and data packing.
But recall that it is preferable to use ~ rather than ¬ with XCOM-I.
STRING(IDENTIFIER)
STRING(INTEGER)

This function transforms the variable NE (which should be FIXED for proper usage) into a CHARACTER descriptor.  NE should have the form:

Length - 1
 Data Address
8 bits
24 bits

(XPL/I only.) I admit that the description at left is confusing.  The whole concept is confusing.  Here's my take on it:  I believe that STRING is used in one of two situations:
  • You have an IDENTIFIER for a CHARACTER variable, and you want the string descriptor for the string contained in that variable; or
  • By some kind of arithmetical process, a INTEGER value has been created that you want to treat as a string descriptor.
These are respectively equivalent to
  • COREWORD(ADDR(IDENTIFIER))
  • INTEGER

XCOM-I implements STRING using these equivalences, and there is no runtime library function as such that's called STRING.

To make things a bit more confusing, STRING is the one built-in I'm aware of that is also commonly used as a name for a variable. If names of built-ins are declared as variables (or procedures), the user's declaration overrides the built-in for the scope of the declaration.

STRING_GT(DE1,DE2)
This function returns a TRUE value if the contents of string DE1 is greater than the contents of string DE2, based on the collating sequence of the characters, irrespective of the lengths of DE1 and DE2. Otherwise, the value is FALSE. This is functionally equivalent to padding the shorter of DE1 or DE2 with blanks and then comparing the strings.
(XPL/I only.)  If this seems confusing, it may be helpful to recall that when the XPL relational operators <, >, <=, and >= compare two CHARACTER values, they look first at the lengths of the values, and only if the lengths are equal do they compare the actual character data.  For example,
'ZZZ' < 'AAAA'
because 'ZZZ' has only 3 characters, while 'AAAA' has 4.  Presumably STRING_GT was introduced because somebody at Intermetrics didn't think that kind of behavior was great, and thus STRING_GT('ZZZ', 'AAAA') will report instead that 'ZZZ' is greater than 'AAAA'.

The collating sequence in either case is EBCDIC rather than the more-usual ASCII.  The primary visually-obvious consequence of this is that digits come after letters rather than before them.
SUBSTR(DE,NE1,NE2)
A function with descriptor value specifying the substring of the string specified by DE, starting at position with NE1 with length NE2.
I.e., it allocates and returns a new string created by taking a substring of length NE2, starting at position NE1 in string DE.
SUBSTR(DE,NE)
Like the above except that all characters from NE to the end of the string are taken.

TIME
A function with numeric value given by the time-of-day coded as centiseconds since midnight.
McKeeman does not specify the time zone.  XCOM-I uses UTC.
TIME_OF_GENERATION
A word variable initialized with the value of TIME during compilation of the program.
TIME (see above) isn't literally available during compilation.  Moreover, XCOM-I implements this as a function rather than as a variable, which should be transparent to the programmer. 

Unfortunately, there's a slight bug in XCOM-I which I haven't found any satisfying way to resolve, which is that TIME_OF_GENERATION uses the local time-zone of the C compilation rather than UTC as TIME (see above) does.
TRACE
A procedure,
CALL TRACE;
which causes activation of the instruction-by-instruction trace at runtime.
This is described in quite a lot of detail in McKeeman's Appendix 2.  At present, it is accepted during compilation but does nothing at runtime in code generated by XCOM-I.
UNTRACE
A procedure,
CALL UNTRACE;
which turns off run-time trace.
See TRACE.

The MONITOR Built-In Runtime-Library Function

As mentioned before, an XPL/I program obtained various services outside what the XPL/I language proper or runtime library could provide, by instead making requests to the separate "submonitor" program. The mechanism was a call of the MONITOR procedure. For XCOM-I, on the other hand, there is no separate submonitor program, and we may as well think of MONITOR as being just another built-in runtime-library function. Well, not just any runtime-library function. A big difference is that it provides a very large number of functions, each one of which can require its own unique syntax, thus necessitating a somewhat more-flexible discussion of how to use it.

The only uniform feature among the many aspects of MONITOR usage is that each separate function it provides is identified by a number, and such a function number is passed to MONITOR as its first parameter. My explanations in the table below are mostly pulled from Chapter 13 of IR-182-1, and then altered according to my understanding (or lack thereof). Functions 24 through 32 are deduced, poorly, from the HAL/S-FC BAL source-code file for the submonitor program (which happens to be called "MONITOR").

A number of the MONITOR(...) functions work with what's called "IBM hexadecimal floating-point" format, and specifically to the 64-bit (double-precision) version of that format, as opposed to the 32-bit (single-precision) version of it. To make the discussion more concise, I'll just refer to it as "DP floating point".

To be perfectly clear, there is no floating-point datatype in XPL/I, there are no floating-point literal constants, and there is no provision whatever to make it convenient for you (the programmer!) to hard-code such constants into your XPL source code, nor to interpret any such hexadecimal constant you find within legacy source code. Rather, you must somehow obtain the hexadecimal equivalents for whatever floating-point constants you wish to use, and then hard-code those hexadecimals into your code. For your convenience — or more accurately, for mine — I've included a little utility called ibmHex.py that you can use to convert back-and-forth between human-readable floating-point numbers and DP floating point. Just run ibmHex.py --help for instructions. This little utility can either be run in a stand-alone fashion, or else imported as a Python module. But I digress!

To understand DP floating point, imagine 8 groups of 8 bits each:

SEEEEEEE FFFFFFFF FFFFFFFF ... FFFFFFFF

where S is the sign, E is the exponent, and F is the fraction. (SP floating point is the same, but with 3 FFFFFFFF-groups rather than 7 of them.) The exponent is a power of 16, biased by 64, and thus represents 16-64 through 1663. The fraction is an unsigned number, of which the leftmost bit represents 1/2, the next bit represents 1/4, and so on. As a special case, 0 is encoded as all zeroes.

For example, the 64-bit hexadecimal pair 0x42640000 0x00000000 parses as:

or in total, 1100100 (binary), or 100 decimal.

As in the preceding section, I want to make it clear that the descriptions given here are how the XCOM-I runtime library's MONITOR functions work, and not how the original MONITOR functions as confined to an IBM 360 runtime environment worked!

MONITOR Call
Description From Original Documentation
Additional Notes
CALL MONITOR(0,n);
Closes output file n.
I believe this refers to the sequential file or PDS attached for use with OUTPUT(n), and not the random-access file (if any) attached for use with FILE(n).
F=MONITOR(1,n,name);
Assumes that a PDS is attached to output device n.  Physically writes any data previously buffered in memory by OUTPUT(n) operations into the PDS's member named name.  The name parameter is a string 8 characters long, padded with blanks as necessary.  Returns 0 if the member hadn't existed previously in the PDS, or 1 if the contents of an existing member of that name was replaced.

F=MONITOR(2,n,name);
Assumes that a PDS is attached to input device n.  Sets future INPUT(n) operations to pull data from the PDS's member named name.  The name parameter is a string 8 characters long, padded with blanks as necessary.  Returns 0 if the member was found, or 1 if it was not found.
The end-of-data for the member is detected when an input string of 0 length is encountered.

IR-182-1 asserts that devices 4 and 7 have the following abnormal ad hoc behavior very specific to HAL/S-FC.  A PDS called "INCLUDE" is normally attached to input device 4, while either "INCLUDE" or "OUTPUT6" is normally attached to input device 7.  But member name  is sought in device 4 or 7, then name is first sought in "INCLUDE" but upon failure is then sought in "OUTPUT6".
CALL MONITOR(3,n);
Closes input file n. I believe this refers to the sequential file or PDS attached for use with INPUT(n), and not the random-access file (if any) attached for use with FILE(n).
CALL MONITOR(4,n,b);
Changes the record size of random-access file n to b.  Must precede the first use of FILE(n).

CALL MONITOR(5,ADDR(DW));
Sets the location of the double-word work area (DW) for subsequent use by MONITOR(9,...) and MONITOR(10,...).
DW is meant to be literal here, at least for HAL/S-FC, and should not be replaced by anything else.

DW refers to an array of FIXED variables used in pairs, to hold 64-bit values. In particular, these pairs are often used to hold double-precision floating-point numbers in IBM System/360 format.   MONITOR(9,op) (see below) is then capable of performing various arithmetical operations on those numbers.  Since XPL/I itself has no facilities for floating-point variables or operations on them, this workaround is the only available way to employ floating-point numbers in XPL/I programs.

In legacy HAL/S code, I've found that at least 14 words of memory are needed for this work area.  In the XCOM-I implementation, the
MONITOR(5) call is rejected within 14 words of the end of memory.
F=MONITOR(6,ADDR(basedVariable),n);
Allocates n bytes of storage in free memory, clearing that memory to 0, and assigns the pointer of the basedVariable to point to that newly-allocated space.  Returns 0 upon success, or 1 upon failure.
I.e., basedVariable is supposed to have been declared in XPL via
BASED basedVariable ...;
and n should be an integral multiple of the size of basedVariable's entries.

Very important:  While it is true that MONITOR(6) behaves in this fashion, do not interpret it to mean that this is the mechanism by which user code allocates memory for its own BASED variables!

Because of this, this is presently a no-op in XCOM-I.  It always return 1 for failure.
F=MONITOR(7,ADDR(basedVariable),n);
Frees the memory previously allocated via MONITOR(6,ADDR(basedVariable),n).  However, basedVariable's pointer is not changed, and thus will continue to point to the freed area until explicitly changed.
See the comments for MONITOR(6).  It is implemented in XCOM-I as a no-op that always returns 0 (for success).
CALL MONITOR(8, dev, filenum);
Set PDS DDNAME.
Apparently, the use of MONITOR(8) changed after the only documentation of it (in IR-182-1) was produced, leaving us to try to infer its usage from context in the HAL/S-FC and MONITOR's source code.  The purpose seems to be to change the association of files to device numbers, à la DD's in JCL, but to do so dynamically during execution rather than statically at program startup. 

The dev parameter, an integer, would appear to be the logical device number as used in an XPL INPUT(dev) or OUTPUT(dev) (or in a HAL/S READ(dev) or WRITE(dev)).

The filenum parameter is trickier for me to understand, because I'm unclear how DD in JCL associates a dataset name with a file number, so take what I say with a grain of salt.  My inference, subject to change, is that if you have a dataset identified with the DD name "INPUTn" (where n is a digit), then that is filenum=n; whereas a dataset named "OUTPUTn" is associated with filenum="80000000"|n.  Recall that "80000000" is XPL-speak for 0x80000000.  So the filenum is implicit in the dataset name, and the most-significant bit is used to indicate the direction of data flow.

For example, INPUT(4) invocations might normally input data from INPUT4, but you could associate it instead with INPUT2 via
CALL MONITOR(8, 4, "80000000" | 2);
and then reassociate it later with INPUT4 via
CALL MONITOR(8, 4, "80000000" | 4);
The description "Set PDS DDNAME" I've given as the description comes from the associated program comment in MONITOR.bal, the source-code for the MONITOR procedure.  It would seem to imply that it has something specifically do with Partitioned Data Sets (PDS).  It's difficult to see why that would be so; I think it merely reflects the specific usage in HAL/S-FC, which indeed relates to PDS.  The XCOM-I runtime library allows it to be used for and PDS or sequential dataset.  It is not applicable to random-access files.

Of course, there is no JCL, and consequently no DD statements, associated with an XPL or XPL/I file compiled with XCOM-I, nor with a HAL/S file compiled with HAL/S-FC.  The description above has to be applied instead to command-line switches in a hopefully-obvious way.
F=MONITOR(9,op);
Performs floating point evaluation as specified by value of op.  Operands are obtained from work area whose address was set up via a MONITOR(5) call. The first operand is taken from the first double word of the work area and the second operand from the second double word. The result is placed in the first double word of the work area. The return code is 0 if the operation succeeds, or 1 if the operation fails (under or overflow).

The values of op are:
OP
Function
1
arg1 + arg2
2
arg1 - arg2
3
arg1 * arg2
4
arg1 / arg2
5
arg1arg2
6
sin(arg1)
7
cos(arg1)
8
tan(arg1)
9
exp(arg1)
10
log(arg1)
11
sqrt(arg1)

"arg1" refers to the DP floating-point number stored in the pair of work-area valued DW(0),DW(1).

"arg2" refers to the DP floating-point number stored in the pair of work-area valued DW(2),DW(3).

The DP floating-point result of the operation is stored back into DW(0),DW(1), although not all of the operations can necessarily produce results of full DP accuracy from DP operands.

The angular unit for trigonometric operations is the radian.

Note that the values stored in these registers of the work-area are in the binary format of "IBM hexadecimal floating-point", which are not used anywhere else in XPL memory (unless copied from the working area).
F=MONITOR(10,string);
Performs character to DP floating-point conversion upon characters in string. The return code is 0 if the result is valid, or 1 if conversion was not possible. The result is placed in the first double word of the work area provided by the MONITOR(5) call.
In other words, string is interpreted as a DP floating-point number and stored in DW(0),DW(1).  Any of the usual representations for decimal numbers are accepted in the string, including the usual E notation for exponents.
CALL MONITOR(11);
No-op.

string=MONITOR(12,p);
Converts floating-point number in the first double word of the work area to standard HAL/S character form. Value of p indicates whether the operand is SP (p=0) or DP (p=8).
In other words, this is the inverse of MONITOR(10,string) (see above).

As far as the "standard HAL/S character form" is concerned, it's described in HAL/S documentation (Programming in HAL/S, p. 3) as follows:
  • 0.0: Printed as " 0.0" (notice the leading space).
  • Positive: Printed as " d.ddd...E±ee"
  • Negative: Printed as "-d.ddd...E±ee"
Except for the special case 0.0, the number of printed fractional digits is always the same, although we're not told exactly how many that it is:  merely that it is "implementation dependent".  I believe, without any basis — and therefore XCOM-I implements — that the number of fractional digits is chosen to provide the maximum accuracy, which is 6 for single precision and 15 for double precision.
address=MONITOR(13,name);
Performs DELETE of current option processor and then LOADs an option processor specified by name. The option processor loaded is called and passed a pointer to the PARM field in effect at the time of compiler invocation. The option processor passes the PARM field and establishes an options table (see Chapter 9 [of document IR-182-1]) whose address is passed back as a return value.  If name is a null string, the pointer to the existing options table is returned.
MONITOR(13,name) returns an address which is used by the XPL program, in some program-specific fashion, to find options settings for it that have been set up by the run-time library.  In C terms, it's the equivalent for argv[].

The remaining gobbledygook in the documented description relates to the fact that program options (such as those for HAL/S-FC compiler) would originally have been provided by the Job Control Language (JCL) which initiated execution of the program, by means a "PARM field" given on one of the JCL cards.  For example, in the JCL
//XPL EXEC PGM=MONITOR,
// PARM='SYTSIZE=1800,REFSIZE=20000,LISTING2,$I,$V,$U,$W'

the PARM field is the string 'SYTSIZE=1800,REFSIZE=20000,LISTING2,$I,$V,$U,$W', and the comma-delimited items are the individual options selected.  Presumably, the operating system wouldn't have been able to directly interpret the meaning of this PARM field on its own, since the types of options would have been specific to the program being run.  So an "option processor" specific to the program being run must be loaded to perform that interpretation.  In the case of Intermetrics's version of HAL/S-FC, the particular options processor for compiler pass 1 was name='COMPOPT ', whereas it was an empty string for passes 2 and 3, and 'LISTOPT ' for pass 4.  (Note the trailing spaces to make the non-empty strings come out to 8 characters.)  While these different options processors weren't identical, it appears to me that they were mutually consistent.

Again, just to be doubly clear, MONITOR(13) provides access to options settings not for XCOM or XCOM-I, but rather for the XPL program being compiled by XCOM-I, and specifically for HAL/S-FC.  So while I will try to fully document these options in write-ups about HAL/S-FC, I'll only give the briefest of summaries here in this discussion of XCOM-I.
Note: HAL/S-FC optional parameters are of two types: those of type 1 are the simple on/off parameters, such as the flags in OPTIONS_CODE as described above; those of type 2 instead have a numerical or string value.
The name parameter in MONITOR(13,name) is ignored entirely by XCOM-I.  That's because in the XCOM-I system, options are given by command-line switches that would have been evaluated long before MONITOR(13,name) is even executed.  The name of the options processor is instead given via the XPL program's command-line option --optproc.  For all of the HAL/S-FC-related options processors, the address returned by MONITOR(13) is a pointer in memory to the beginning of a block of 6 consecutive FIXED values:
  • The first is a FIXED value (called OPTIONS_CODE or OPTION_BITS) that collects 32 bit flags, each of them (in principle) itself representing a type 1 option.
Aside:  And what are those type 1 options? Document IR-182-1 (p. 3-18) lists interpretations of each of the bit flags by bit-number and compiler pass, but unfortunately with inadequate description of what the options do. Meanwhile, the HAL/S-FC User's Manual (p. 5-2) also lists what I presume are all of the bit flags, with good descriptions but not by bit-position. Unfortunately, the names are often somewhat or very different between the two documents, and for those discrepant cases it remains difficult to match bit-position in OPTIONS_CODE with the documented type 1 option. But most of them clearly match between the documents and are thus adequately documented. For example, the option LISTING2 shown in the sample JCL above can be obtained by masking, OPTIONS_CODE&2, and is described as providing a secondary, unformatted HAL/S program listing.
  • The remaining five values are the pointer addresses for BASED FIXED arrays: CON, PRO, TYPE2, VALS, NPVALS (or MONVALS), each of which the options processor has already allocated memory for and equipped with data. Obtaining information about these arrays of data from surviving documentation is a tad challenging, so what I have to say about it is partially based on inference:
    • CON: This is an array of string descriptors for the selected type 1 options. HAL/S-FC uses it just for printing a report in pass 1, while using OPTIONS_CODE in place of it for any other purpose. There are 22 options altogether in CON. Each option is literally as given in the HAL/S-FC User's Manual if selected, while it is prefixed by "NO" if not selected. So for example, the LISTING2 option appears in this array either as 'LISTING2' or as 'NOLISTING2'. The options appear in CON in alphabetical order according to their names as given in the HAL/S-FC User's Manual (i.e., sans any prefixes of "NO").
    • PRO: The interpretation is TBD, but the array is never used by HAL/S-FC, so MONITOR(13) does not provision it, but instead always returns a pointer equal to 0.
    • TYPE2: This array contains the names (as string descriptors) of options of type 2. There are 13 such type 2 options, always present in the same order: TITLE, LINECT, PAGES, SYMBOLS, MACROSIZE, LITSTRING, COMPUNIT, XREFSIZE, CARDTYPE, LABELSIZE, DSR, BLOCKSUM, and MFID (for primary flight software) or OLDTPL (for backup flight software).
    • VALS: This array contains the values of the options of type 2, in the same order as the names in the TYPE2 array. The values for TITLE, CARDTYPE, and MFID/OLDTPL are string descriptors, whereas all of the other values are integers. The HAL/S-FC User's Manual (p. 5-5) describes them all. For example, TYPE2(4) should be a string descriptor for 'MACROSIZE' and VALS(4) should be the value of the MACROSIZE option (or its default 500 if not specified).
    • NPVALS/MONVALS: The interpretation is TBD, but the array is never used by HAL/S-FC, so MONITOR(13) does not provision it, but instead always returns a pointer equal to 0.
F=MONITOR(14,n,a);
N/A
This is a no-op for XCOM-I but may be implemented in the future.  It is related to so-called Simulation Data Files (SDF), which are only relevant to HAL/S-FC compiler passes 3 and 4, which are not currently on Virtual AGC's roadmap for implementation since none of the support software for SDF has survived.
I=MONITOR(15);
Returns Revision Level and Catenation Number from last MONITOR(2) call.  Catenation number is obtained from PDS directory data and Revision Level from user data field as specified in the HAL/SDL ICD. The values are returned in the left and right halfwords of the result.
TBD
CALL MONITOR(16,n);
Sets flags in byte to be returned as high order byte of return code at end of compilation. Flags are passed as right most byte of fullword n. If high order bit of n is zero, flags are OR'ed into existing flags. If high order bit of n is one, flags replace existing flags.
TBD
CALL MONITOR(17,name);
Causes name to be copied to third parm field (if any) passed to MONITOR by the program that invoked the compiler. See HAL/SDL ICD.
TBD
T=MONITOR(18);
Returns elapsed CPU time since beginning of run in units of .01 seconds.

F=MONITOR(19,addressList,sizeList);
This is an extended form of MONITOR(6) (see above), accepting arrays of addresses of BASED variables and their block sizes.  Unlike MONITOR(6), the newly-allocated blocks of memory are not cleared to 0.

CALL MONITOR(20,addressList,sizeList);
This is an extended form of MONITOR(7) (see above), accepting arrays of addresses of BASED variables and their block sizes.

I=MONITOR(21);
Returns remaining amount of memory (in bytes) that's free for allocation via MONITOR(6).
For Intermetrics XCOM, I believe, MONITOR(6) could use any contiguous block of free memory, including those that were candidates for garbage collection.  Whereas XCOM-I only allocates memory within the contiguous block at the end of the free-memory space.  Consequently, this function would originally have found the largest free block wherever it was located, whereas XCOM-I merely reports the size of the final block of free memory.
F=MONITOR(22,n1);
F=MONITOR(22,0,n2);
Calls SDF access package.
I don't know what this means, nor what the parameters represent.
descriptor=MONITOR(23);
Returns the descriptor of the 10-character string obtained from the ID field of the File Control Block of the first phase of the compiler.  The ID field is maintained by the XPLZAP program and contains the identifying string printed on the header of each page of the HAL listing". I think that what the original documentation was trying to get at is that HAL/S-FC's version code, printed on each of the report pages printed by XCOM (or now, XCOM-I), is given by this ID string, and that the ID string is provided to each pass of HAL/S-FC via a call to MONITOR(23).  Of course, in the modern reimplementation, there is no XPLZAP program, and it wouldn't be compatible with XCOM-I if there were one.  In XCOM-I, the ID string itself is by default 'REL32V0   ', but it can be changed via the XCOM-I command-line parameter --identifier.

As for what a "descriptor" is, see the description of the CHARACTER datatype.   In brief, the return value is not itself the ID string, but an integer value (i.e., of the FIXED datatype) that provides an index for finding the ID string in memory.
MONITOR(24)
Read a block of a load module.
TBD
MONITOR(25)
Read a mass-memory load block.
TBD
MONITOR(26)
Read a MAF (memory analysis file) block
TBD
MONITOR(27)
Write a MAF block
TBD
MONITOR(28)
Link to dump analysis service routine
TBD
MONITOR(29)
Return current page number
TBD
MONITOR(30)
Return JFCB as string
TBD
CALL MONITOR(31,n,recnum)
Virtual-memory lookahead service.
I think this may be optional.  At any rate, it presently does nothing in XCOM-I.
F=MONITOR(32)
Find out subpool minimum size
The memory-allocation and -deallocation functions (MONITOR 6, 7, 19, and 20) are supposed to get their memory from something called "SUBPOOL 22".  SUBPOOLs apparently have block-sizes which are either 2048 or 4096 bytes, depending on the operating system.  MONITOR(32) returns the appropriate block size for the operating system.  Given that the block size is bigger in later versions of the operating system, I infer that bigger is better, within certain limits, and hence XCOM-I arbitrarily returns 4096.
MONITOR(33)
Find out FILE max REC# and BLKSIZ
TBD

Debugging XPL Programs

It is admittedly unlikely that many people will be writing new XPL or XPL/I programs nowadays, and will instead be compiling only legacy XPL or XPL/I programs ... assuming that I haven't already compiled all of them first! (Which is a distinct possibility.) Since such legacy programs will presumably all have been debugged decades before, there's not as much need for a debugger as there is for computer languages in which there are many active developers. Nevertheless, XPL or XPL/I programs can be run under a debugger to a certain extent.

The trick is to realize that while we have no debugger for the XPL language, we do have debuggers for C, and once XCOM-I has translated XPL or XPL-I software into C, such C debuggers can be used. It is merely necessary when compiling the C code to include the necessary command-line switches that cause the C compiler to incorporate debugging information into the executable. For the gcc C compiler, that command-line switch is -ggdb, and incorporating it can be done simply by adding it to the make command like via
make EXTRA=-ggdb ...
Having done this, any debugger which can accept gdb style debugging info, such as gdb itself or full-screen wrappers for gdb, can now be used for debugging.  For other C compilers or other debuggers, perhaps different switches would be needed.

But debugging XPL code via its C translation is not as convenient as a native XPL debugger would be.

The principal difficulty in debugging the code in C vs in XPL is that XCOM-I doesn't model XPL variables as C variables, but rather as numerical locations in an array called memory.  Moreover, the format of this data in "memory" is that of the IBM System/360 rather than the native format of the computer on which debugging is being performed.  While in my opinion, these design choices for XCOM-I were are all necessary and unavoidable, there's no denying that they make it trickier to use a debugger to see the how the values of variable change during execution. 

I have, however, provided a few C functions in the runtime library to make it a little less painful to examine XPL variables within a debugger.  You can also use the XPL CALL INLINE feature to directly embed calls to these functions within your program, without running a debugger at all, if it's more convenient for you to do so.

The debugging functions are:

In using these debugging functions, note that they all require mangled forms of variable names and parameters of PROCEDUREs. Mangled names consist of the names of the variables or parameters as DECLAREd in the XPL source code, but prefixed by the names of all of the parent PROCEDUREs. Perhaps an example would make this clearer. Suppose your XPL source code looked like the following:

DECLARE X FIXED, Y FIXED, Z FIXED;
PROC1:
PROCEDURE(X, Y);
DECLARE X FIXED, Y FIXED, Z FIXED;

PROC2:
PROCEDURE(X, Y);
DECLARE X FIXED, Y FIXED, Z FIXED;
...
END PROC2;

END PROC1;

Then the mangled variable and parameter names we'd find in our memory map (and just for reference, PROCEDURE names), as well as being used in our debugging functions, would be:

X
Y
Z
PROC1
PROC1xX
PROC1xY
PROC1xZ
PROC1xPROC2
PROC1xPROC2xX
PROC1xPROC2xY
PROC1xPROC2xZ

Thus while we have lots of global and local variables and function parameters named X, Y, and Z, their mangled names are all distinct.
Aside: By the way, the lower-case 'x' characters appearing in the mangled names have nothing to do with the fact that one of our identifiers is 'X'.  That's a coincidence.  Rather, they're just convenient separators XCOM-I conventionally uses between scope names and the variable names.  Recall that XPL identifiers are case-insensitive.  XCOM-I translates them all internally to upper case.  Hence, lower-case 'x' is not a character that can appear in unmangled identifiers or names of scopes.

Patches for Basic Assembly Language Code

A serious difficulty in working with XPL/I code is that the Space Shuttle PASS programs were not written entirely in XPL/I, but also inserted inline IBM System/360 basic assembly-language (BAL) code at various junctures.  However good XCOM-I may (or may not) be at translating XPL or XPL/I to C, it is not capable of inferring the intent of arbitrary BAL code, nor of translating such code into C.

Moreover, there is a second problem associated with BAL code that has been inlined into XPL/I code, which is that while I can imagine replacing such BAL code by C-language code that provides the same functionality, I do not want to do it in a way that necessitates altering the original XPL/I source code!  Why is that?  Well, the XPL/I source code for (say) HAL/S-FC is quite large, comprising tens of thousands of lines across hundreds of files, and I'd prefer to avoid storing two almost-identical copies of it in our Virtual AGC source-code tree, one of which is identical to the original and one of which has been altered. It's better from my perspective to have a single copy of it that's verifiably identical to the original.

Don't worry!  XCOM-I let's us satisfy our desires.  But to understand how, you need to know a little more about this BAL source code that's causing the problem.

BAL source code is embedded in XPL or XPL/I code via calls to the XPL built-in function INLINE that look roughly like this:

CALL INLINE("58", 3, 0, DW_AD); /* L 3,DW_AD */

/*LOAD DOUBLE FROM STACK SPACE 3 TO REGISTER 0*/
         CALL INLINE("68", 0, 0, 3, 0);            /* LD   0,0(0,3)           */
/*LOAD POSITIVE VALUE OF REGISTER 0 INTO REGISTER 0*/
         CALL INLINE("20", 0, 0);                  /* LPDR 0,0                */
/*LOAD ROUNDING VALUE INTO STACK 1 THEN ADD TO REGISTER 0*/
         CALL INLINE("58", 1, 0, ADDR_ROUNDER);    /* L    1,ADDR_ROUNDER     */
CALL INLINE("6A", 0, 0, 1, 0); /* AD 0,0(0,1) */
         CALL INLINE("58", 1, 0, ADDR_FIXED_LIMIT);/* L    1,ADDR_FIXED_LIMIT */
CALL INLINE("58", 2, 0, PTR); /* L 2,PTR */
/*COMPARE REGISTER 0 TO THE POSITIVE INTEGER LIMIT*/
CALL INLINE("69", 0, 0, 1, 0); /* CD 0,0(0,1) */
/*BRANCH TO 'LIMIT_OK' IF REGISTER 0 IS LESS THAN OR EQUAL TO THE LIMIT       */
CALL INLINE("07",12, 2); /* BNHR 2 */
There are several different variations to INLINE's syntax, but those differences aren't important to us.  What's important at the moment is rather that each CALL INLINE(...) inserts a single BAL instruction.

Another thing that's important is that when XCOM-I encounters a CALL INLINE(...), by default it translates it simply to an empty C statement; i.e., to a C statement that's just a semi-colon.  But it also inserts a numbered program comment.  The code above, for example, might be translated to C by XCOM-I as:
        // (459) CALL INLINE("58", 3, 0, DW_AD);
;
// (460) CALL INLINE("68", 0, 0, 3, 0);
;
// (461) CALL INLINE("20", 0, 0);
;
// (462) CALL INLINE("58", 1, 0, ADDR_ROUNDER);
;
// (463) CALL INLINE("6A", 0, 0, 1, 0);
;
// (464) CALL INLINE("58", 1, 0, ADDR_FIXED_LIMIT);
;
// (465) CALL INLINE("58", 2, 0, PTR);
;
// (466) CALL INLINE("69", 0, 0, 1, 0);
;

These parenthesized numbers which XCOM-I has inserted into the comments are not only all distinct from each other, but also are always the same every time the source code has been compiled, as long no INLINEs have been added or removed in the meantime ... an assumption which at this point seems pretty safe!  Let's call them patch numbers.

Reasonably enough, patch numbers can be used to define places at which XCOM-I makes a patch.  Suppose, for example, that we had a file called "patch459.c" containing C code that we wanted to use to mimic the functionality of the BAL code above.  Similarly, we could have a patch460.c, a patch461.c, and so on, but there's little reason to do so, since it's just easier to put all of our replacement code for this sequence of instructions into a single file.

Each time XCOM-I encounters a CALL INLINE(...), it looks for a patch file corresponding to the INLINE's patch number.  If it finds such a file, then it inserts it, and it's only if there's no such patch file exists that the default translation into an empty C statement occurs.

Of course, the glib explanation I've just given glosses over a lot of the difficulties, such as figuring out what C code is appropriate for any given BAL code.  I can't give you any amazingly-handy insights on that topic, I'm afraid.  In the case of this particular example, the code I've shown above is just the beginning portion of a longer sequence of BAL instructions, intermixed with XPL statements, which occurs in the ROUND_SCALAR PROCEDURE in the HAL/S-FC source-code file HALMATIN.xpl.  Collectively, I think the entire sequence may round a double-precision floating-point number previously loaded into a pair of the CPU's floating-point registers (referred to by the symbolic name DW[0] and DW[1]) to an integer, then compare that integer to the positive and negative limits of the 32-bit signed integer datatype (FIXED), and finally, cap the value at those limits, ultimately storing the rounded-and-capped value back into DW[0] and DW[1].  (Which would not be terribly surprising given that the name of the PROCEDURE is ROUND_SCALAR.) 
Aside: How did I arrive at these conclusions?  My best explanation is that you sit quietly, unfocus your eyes, clear your mind, attain some kind of a zenlike state, ignore the BAL instructions themselves, and think merely of the program comments and symbolic names.  On the other hand, I suspect that my explanation is perhaps not of universal applicability.  And it might be fair to mention that many of these sequences of BAL instructions use these same kinds of rounding and capping operations, and you do get a feel for them after looking at a few.
Aside: Realize too that the values stored in the CPU's floating-point registers will be in IBM 360 floating-point format, so to do anything useful with them, we may want to translate them back and forth into the native C floating-point format.
If all that's true, then our patch file (patch459.c) might look something like the following, where I've invented some plausible-sounding helper functions to implement it.  It's not important at the moment that you don't understand the patch code, or even if it's correct.  It probably isn't!  We're just exploring the principles involved in patching.  Note that the return at the end is not an essential part of the patching process; rather it just happens to be appropriate for this example, but wouldn't be present in most patches.
// Note: ADDR(NULL, 0, "DW", i) gives the address of DW[i] in the 
// simulated-memory array. For efficiency's sake, we'd probably
// want to determine these addresses just once, during program
// initialization But let's not worry about that at the moment.
double x = fromFloatIBM(ADDR(NULL, 0, "DW", 0), ADDR(NULL, 0, "DW", 1));
double r = round(x);
int32_t n;
if (r > 2147483647)
n = 2147483647;
else if (r < -2147483648)
n = -2147483648;
else
n = r;
toFloatIBM(ADDR(NULL, 0, "DW", 0), ADDR(NULL, 0, "DW", 1), n);
return 1;

Putting all this together, if XCOM-I is used to compile the program once again, it now finds patch-file patch459.c, but no patch460.c, patch461.c, etc., so it ends up compiling our mess of CALL INLINE(...) statements to:
        { // (459) CALL INLINE("58", 3, 0, DW_AD);
// Note: ADDR(NULL, 0, "DW", i) gives the address of DW[i] in the
// simulated-memory array. For efficiency's sake, we'd probably
// want to determine these addresses just once, during program
// initialization But let's not worry about that at the moment.
double x = fromFloatIBM(ADDR(NULL, 0, "DW", 0), ADDR(NULL, 0, "DW", 1));
double r = round(x);
int32_t n;
if (r > 2147483647)
n = 2147483647;
else if (r < -2147483648)
n = -2147483648;
else
n = r;
toFloatIBM(ADDR(NULL, 0, "DW", 0), ADDR(NULL, 0, "DW", 1), n);
return 1; }
// (460) CALL INLINE("68", 0, 0, 3, 0);
;
// (461) CALL INLINE("20", 0, 0);
;
// (462) CALL INLINE("58", 1, 0, ADDR_ROUNDER);
;
// (463) CALL INLINE("6A", 0, 0, 1, 0);
;
// (464) CALL INLINE("58", 1, 0, ADDR_FIXED_LIMIT);
;
// (465) CALL INLINE("58", 2, 0, PTR);
;
// (466) CALL INLINE("69", 0, 0, 1, 0);
;
Notice that the patch is automatically placed within a C {...} block, which is important if the CALL INLINE(...) happens to have been preceded by something like IF ... THEN or ELSE.  The return at the end of our patch causes all of the remainder of the ROUND_SCALAR PROCEDURE to be skipped past, probably generating a few warnings from the C compiler.  But in a more-usual example the return wouldn't have been in the patch, and so the compiler warnings wouldn't be generated either.

By default, XCOM-I will look for patch files in the same folder holding the XPL/I source-code file it's compiling, though it has a command-line option (--patch=PATHNAME) that allows a different folder to be specified, which in the case of a huge XPL/I program like HAL/S-FC is probably a better idea.

Incidentally, in XCOM-I, if CALL INLINE(...) is used with a single parameter that's a string, then no patch file is sought.  Rather, XCOM-I treats that single string parameter as a line of C code, and inserts the line directly into the output C code.  Thus if you were willing to modify the original XPL source code rather than leaving it unchanged and using the patch-file technique, you might be able to do so just by altering the parameters of the CALL INLINE(...) statements to contain C source code rather than BAL source code.



This page is available under the Creative Commons No Rights Reserved License
Last modified by Ronald Burkey on 2024-05-13

Virtual AGC is
              hosted by ibiblio.org