What is this little example supposed to do? First, it declares 3 variables of thedeclare 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;
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.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 aPROCEDURE
in XCOM,ERROR(MSG,SEVERITY)
, whose purpose is to print error messages. InERROR
, the parameterSEVERITY
(a scalar) is used with a subscript,SEVERITY(-1)
, in what the authors' comments call "A PORNOGRAPHIC WAY OF OBTAINING THE RETURN ADDRESS [ofERROR
]". Well, the comment is certainly worth a laugh! And the idea behind it was to allowERROR
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."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.
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
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").
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: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.
PAGE 1
1 2 3
1 2 3
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.
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:
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.cd XCOM-I
XCOM-I.py Tests/Example-6.18.6.xpl
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. |
/* |
This produces an executable program within the Example-6.18.6/ folder that's also called Example-6.18.6.make -C Example-6.18.6
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:
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 CC=clang
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:Finally, you can now run the now-fully-compiled program:
make -C Example-6.18.6 CC=clang EXTRA="-Wdangling-else"
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.)Example-6.18.6/Example-6.18.6
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 "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).Example-6.18.6/Example-6.18.6 --help
".
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 XPLOne 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 formDECLARE
statements. TheseDECLARE
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.
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: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 iscd XCOM-I
XCOM-I.py --xpl Tests/SKELETON.xpl make -C SKELETON SKELETON/SKELETON
$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:Or since ANALYZER accepts the input grammar it analyzes on "device 0" (attached by default toANALYZER/ANALYZER --ddo=2,PUNCH.txt <Tests/XPL.bnf
stdin
, which is why we usually pipe in the input via <
), we could even run it instead as:For a still meatier sample program, we can turn to the source code of the original XPL compiler itself, XCOM. XCOM 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.ANALYZER/ANALYZER --ddi=0,Tests/XPL.bnf --ddo=2,PUNCH.txt <Tests/XPL.bnf
But then compiling an XPL program using XCOM involves a little JCLcd XCOM-I
XCOM-I.py --xpl Tests/XCOM.xpl make -C XCOM
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.)FILE(1)
— The compiled object code.FILE(2)
— A scratch file (i.e., temporary working space) for data.FILE(3)
— A scratch file for strings.DISKBYTES=3600
. Compiling an XPL program called (say) "Program.xpl" with XCOM could thus look something like this: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: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
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:
- It only accepts upper-case code for identifiers and keywords ... in spite of the fact that every speck of XPL source code in the book is printed in lower case!
- It requires an
EOF
token at the end of the source code.- It won't allow you to have two division operations (including
MOD
as a division) in the same statement, instead emitting an error message saying that it requires a "busy register".- I'm sure there are plenty more oddities, but I don't know that I'll be using McKeeman's XCOM enough in the future to discover any more of them!
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.
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.cd .../virtualagc/yaShuttle/"Source Code"/PASS.REL32V0/PASS1.PROCS
make
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.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 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.
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.
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 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.
There are only three basic datatypes:
FIXED
is a 32-bit signed integer. (Stored as 2's-complement, in big-endian byte order, vs the little-endian byte order used in most personal computers today.)CHARACTER
is a variable-width character string, with a string-length limited to 256 or less. Strings are stored as a 32-bit unsigned integer known as a descriptor, paired with a separate area from 1 to 256 bytes containing the individual characters of the string, encoded in EBCDIC. The descriptor has 8 bits specifying the string length (minus 1) and 24-bits providing the starting memory-address of the character data.BIT(n)
, where n
is from 1 to 2048, is an n
-bit object. The amount of storage varies by the precision:BIT(1)
through B(8)
are stored in memory as single bytes.BIT(9)
through BIT(16)
are stored as 2-byte "half-words".BIT(17)
through BIT(32)
are stored as 32-bit words.BIT(33)
through BIT(2048)
are stored similarly to CHARACTER
variables: There's a 32-bit "descriptor", of which 8 bits is the number of bytes needed to store all of the bits, minus 1, and 24 bits area pointer to elsewhere in memory, where the bytes themselves are stored. Thus, a long BIT(n)
like this uses up 4 bytes for the descriptor, plus ⌊(n+7)/8⌋ bytes (5 for n=33 through 256 for n=2048) for the data. 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.
A(3)
or B(N)
. Aside: It's easy to become confused and to imagine (incorrectly!) that you can treat aTheCHARACTER
variable (as opposed to an array ofCHARACTER
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 theBYTE
function, which can either retrieve the EBCDIC numerical encoding of an individual character in aCHARACTER
variable, or else to store a new EBCDIC numerical value at a given position in aCHARACTER
variable. Thus if we had aCHARACTER
variableC
which held the value 'HELLO!', thenBYTE(C, 3)
would return 211 (the EBCDIC encoding for the letter 'L'), while the assignment statement "BYTE(C, 3) = 198;
" would change the contents ofC
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-inSUBSTR
function to retrieve a specific character position as a newCHARACTER
object of length 1.
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!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.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:BIT(1)
through BIT(32)
: The rightmost bit corresponds to the least-significant bit in the byte at the highest address.BIT(32)
through BIT(2048)
: The leftmost bit corresponds to the most-significant bit in the byte at the lowest address.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'sBut enough of these measlyIF
andDO 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 theBIT(5)
variableA
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 ...;
".
BIT
-based frustrations!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 (DECLARE F FIXED, C CHARACTER, B BIT(5);
DECLARE FS(10) FIXED, CS(10) CHARACTER, BS(10) BIT(5);
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:
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.DECLARE BUFFER(3600-1) BIT(8);
INITIAL
. This attribute allows you to supply an initial value for the variable, such as: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 withinDECLARE 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);
PROCEDURE
s, they're not reinitialized each time the PROCEDURE
is executed.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:CHARACTER
literals — i.e., text strings — are enclosed in single-quote (') characters. If a single-quote itself must appear within the string, you use two single-quotes in succession. For example: 'I am the ''king'' of the world!'
.FIXED
or BIT(N)
have one of several forms:-10
is not a literal for the number -10, but is instead the minus operator followed by the literal for the number 10. In most cases this is a distinction without significance, because XCOM-I (or the original XCOM) automatically tries to perform all computations that are possible at compile time. Nevertheless, this distinction does cause some arithmetically-satisfactory expressions to be syntactically illegal in XPL. For example, the expression 5 + -5
isn't legal in XPL.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.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 wayMore significantly, XPL/I adds an additional kind of datatype that it calls aARRAY
variables were stored in memory vsDECLARE
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.
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. BASED
variables:AlthoughBASED FB FIXED;
BASED RB RECORD:
F FIXED,
C CHARACTER,
A(10) BIT(5),
END;
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.)User code that initially allocates free memory — let's say 25 records to start with — for a
When I said that aBASED
is a "pointer to an array", I was glossing over the fact that to be useful aBASED
must track a lot more information about theBASED
than just its data's location in memory. In fact, aBASED
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 aBASED
called (say)MYBASED
and you executed the built-in functionADDR(MYBASED)
, it would return the address of the dope vector forMYBASED
. 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:
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.
FIXED
pointer giving the address of the actual data.BIT(16)
giving the size in bytes of each array entry.BIT(16)
giving the number of "descriptors". I'm not clear on what that means, but perhaps is the number of non-constantCHARACTER
values appearing per record of theBASED
. I could see that as being useful if the implementation of (say) garbage collection involved searching the entire
BASED
looking forCHARACTER
data, since you could more-easily know to stop searching once they had all been found.FIXED
giving the total number of array entries for which space has been allocated.FIXED
giving the total number of array entries actually used so far.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.FIXED
. It appears to me that this field supplies some properties of theBASED
in the form of bit fields. It is laid out as follows:
- Bit 24 indicates the
BASED
is "constant", which appears to mean that you cannot incrementally grow it. (The macroNEXT_ELEMENT(based)
is used to increase the size of the specifiedbased
by 1 record, an operation which fails if thebased
is "constant".)- Bit 25 indicates the
BASED
is "unmoveable". If abased
is "unmovable", it means that an operation likeNEXT_ELEMENT(based)
(see above) will succeed only if there is enough free space immediately following the allocated memory that can be "stolen". Whereas if it's not unmovable, then thebased
may migrate in its entirety to a newly-allocated block elsewhere and the space it originally occupied may thus be freed.BIT(16)
of purpose TBD. It is referred to as "global factor".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.
With that discussion in mind, in understanding some of the things that need to happen withBASED
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:
RECORD_ALLOC(based)
, used in expressions, returns the number of records allocated inBASED
variablebased
.RECORD_USED(based)
, normally used on the left-hand-side in assignments, sets the number of records used so far in based. Its most-common usage isRECORD_USED(based)=RECORD_ALLOC(based)
, but it can also be used with something other than that on the right-hand side to truncate the array or to skip past the lowest indexes. And it can be used in expressions or conditionals, though that happens relatively seldom.RECORD_TOP(based)
, as you might expect, simply returnsRECORD_USED(based)-1
.ALLOCATE_SPACE(based, top)
allocates enough space forbased
to insure that it contains at leasttop+1
records in total. It will fail if we already haveRECORD_ALLOC(based)>0
.NEXT_ELEMENT(based)
incrementsRECORD_USED(based)
by 1, stealing the space from adjacent free memory or else reallocating and moving the entire array if necessary to do so.RECORD_FREE(based)
frees the data forbased
, returning the allocated space to the free pool.RECORD_SEAL(based)
,RECORD_UNSEAL(based)
: Enables or disables the "constant" attribute of thebased
.RECORD_CONSTANT(based, top, moveable)
LikeALLOCATE_SPACE(based, top)
, but additionally enables the "constant" property, and optionally enables the "unmovable" property.RECORD_WIDTH(based)
returns the record width of thebased
.RECORD_LINK()
prepares the data for transferringCOMMON
memory to the next program loaded.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.
BASED
variable is typically a two-step process that looks something like this:Or, if you knew that you were going to need more elements later, you might allocate a bit extra, for example:BASED MYVARIABLE FIXED;
...
RECORD_CONSTANT(MYVARIABLE, 25, MOVEABLE); /* OR UNMOVEABLE */
RECORD_USED(MYVARIABLE) = RECORD_ALLOC(MYVARIABLE);
Having allocated the space for it, you can now useBASED MYBASED FIXED;
...
ALLOCATE_SPACE(MYBASED, 30);
RECORD_USED(MYBASED) = 25;
MYBASED
just like any other array of FIXED
, such as in assignments like "MYBASED
(27) = 6;
" or "X =
MYBASED
(N) + 12;
". This will incrementNEXT_RECORD(MYBASED);
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.BASED RECORD
variable:AccessingBASED MYNEWBASED RECORD:
F FIXED,
C CHARACTER,
A(9) FIXED,
END;
...
RECORD_CONSTANT(MYNEWBASED, 30, MOVEABLE);
RECORD_USED(MYNEWBASED) = 25;
MYNEWBASE
requires the dotted style often used these days for
accessing fields of structures or classes. Some examples include:and so on.MYNEWBASED(6).F = 12;
MYNEWBASED(10).C = 'XPL is where it is at!';
MYNEWBASED(20).A(6) = 15;
X = MYNEWBASED(6).F;
DECLARE
statements is the LITERALLY
attribute. Here's an example:Notice thatDECLARE ARRAYTOP LITERALLY '255';
DECLARE MYARRAY(ARRAYTOP) FIXED;
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:This clarifies an example of a commonly-desirable declaration I gave in the preceding section, which in this section would be expressed as:DECLARE MYARRAY(255) FIXED;
As noted in the preceding section, standard XPL's grammar wouldn't allow an expression (likeDECLARE RECSIZE LITERALLY '3600';
DECLARE BUFFER(RECSIZE-1) BIT(8);
RECSIZE-1
) in that context, so this particular convenience in making declarations is only available in XPL/I.This expands toDECLARE DEVICE LITERALLY '6', DECLARE OUT LITERALLY 'OUTPUT(DEVICE)';
OUT = 'My message';
Macros can expand to portions of statements, as the ones above have, or to multiple statements, such asOUTPUT(6) = 'My message';
which expands to:DECLARE MYBLOCK LITERALLY 'DO; X=1; Y=X+3; END';
...
IF X=7 THEN;
MYBLOCK;
Macros can also have arguments. Consider the following:IF X=7 THEN;
DO; X=1; Y=X+3; END;
This declaration means thatDECLARE MYMAC(2) LITERALLY '%1% = %2%';
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.
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:
expression1&expression2
, and expression1
evaluates to 0, is expression2
even evaluated?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
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 theAside: 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!
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:
IF
:&
operator; i.e., I*I
.&
operator; i.e., 100-I**
. IF
:&
operator; i.e., I*I
.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, 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". 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.COMMON
data is declared in XPL/I by three methods:COMMON
in place of the keyword DECLARE
.COMMON ARRAY
in place of the keyword ARRAY
.COMMON BASED
in place of the keyword BASED
.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.
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: TheIn 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.)
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. |
|
An XPL program consists of any sequence of XPL statements, followed by the token EOF
. In particular:
PROCEDURE
s. 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 thePROCEDURE
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.
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.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?
/?c ... XPL/I source code ... ?/
#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.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.
/%INCLUDE module %/
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./* ...comment... $%module */
/%INCLUDE module ...comment... %/
directive, which acts the same way, and for which my comments are otherwise the same./**MERGE module procedure */
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.
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 otherPROCEDURE
definitions, and are accessible within thatPROCEDURE
or descendant scopes. Can aPROCEDURE
be defined within aDO...END
block? It can! But (trap!) it is accessible anywhere in the enclosingPROCEDURE
or thatPROCEDURE
's descendants, and is not limited to theDO...END
block in which it was defined. (For example, theCOMMENT_BRACKET
procedure within HAL/S-FC'sOUTPUTWR
.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 CALL
s 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
PROCEDURE
s cannot be recursive, either directly or indirectly.
Taking these facts altogether, XCOM-I implements both parameters and local variables of PROCEDURE
s 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:
PROCEDURE
s even if the PROCEDURE
has no RETURN
statement, or the RETURN
statement specifies no value. In this case, we are told, the return value is simply some unpredictable value from some unspecified System/360 register. Which is rotten, of course, but so what? This is never going to happen, right? Wrong! Actual XPL code does this from time to time. XCOM-I, on the other hand, always returns a value from a PROCEDURE
, whether or not there are any RETURN
statements specifying a return value; the returned value in this case is 0 if FIXED
, a BIT
value of the appropriate width evaluating to 0, or else the empty string for a CHARACTER
.RETURN
statements may exist at the global level, outside of the scope of any procedure, and may return a value when they do. But they can. XCOM-I treats these as exits from the program back to the operating system, with the returned value being the program's exit code. It thus expects the return value to be a program status code.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:
ESCAPE;
ESCAPE LABEL;
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:
EXIT
;
— Exits from the innermost enclosing DO ... END
block. I.e., it's essentially a GO TO
to just after the closest enclosing END
.EXIT LABEL;
— Exits from an enclosing DO ... END
that isn't necessarily the innermost one, but rather the one which instead has the specified LABEL
attached to it. By "attached to it", I mean that they're directly adjacent, as in "LABEL: DO ...
".
Until a more-plausible explanation comes along, my assumption is that
in XPL/I has the same behavior as HAL/S's ESCAPE
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:REPEAT;
REPEAT
LABEL;
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:REPEAT;
— "Repeats" the smallest enclosing DO ... END
. In case the smallest enclosing DO ... END
is a loop — DO WHILE
or DO UNTIL
or DO I = X TO Y
— saying that it "repeats" has a pretty clear meaning: XPL/I REPEAT
is like a Python or C continue
statement. On the other hand, if the immediately-enclosing DO ... END
is not a loop, the expected behavior is less clear; nor are there any instances of REPEAT
in non-loops in legacy XPL/I code from which we might get a clue. XCOM-I implements REPEAT
without a label simply as a jump to the beginning of the block, which means that you could form an infinite loop if there were no other code (like ESCAPE
or GO TO
) to exit the loop.REPEAT
LABEL;
— Breaks out of inner loops as needed, until reaching an enclosing DO ... END
loop that has the attached LABEL
. That's the block that it repeats.Aside: The way my XPL/I implementation is different from HAL/S is that in HAL/S,Note: NeitherREPEAT
(without a label) goes to the beginning of the innermost enclosing loop (DO WHILE
orDO UNTIL
orDO I = X TO Y
) rather than the innermost enclosingDO ... END
. Which makes sense, since that's what you'd normally want.
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.
For XPL code such asit appears to be undocumented what valueDO I = 1 to 100;
...
END;
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.
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 PROCEDURE
s. 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:
FIXED
— NE
x
CHARACTER
descriptor —
DEx
V
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 NE th 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 theWhether 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:
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 longXPL/I's BASED variables are stored in a separate memory region and do not participate in garbage collection by COMPACTIFY . See 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",I think that what's being implied by this cryptic comment is that McKeeman's COREWORD in XPL worked like this:
whereas Intermetrics's
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 XCOM-I conforms to the latter (Intermetrics) usage, with the exception that addresses like 1, 2, 3, 5, 6, 7, 9, ... are perfectly fine: Aside: In point of fact, |
||||
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 NE th 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 procedurewhich 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 areOne 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:
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.The parameters of the --raf switch are:
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 |
||||
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 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 INLINE s 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, reads a single line from sequential file number 5 into the variable C .Important: These "sequential files" and theBy 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 withThere'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 :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 (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 :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: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:
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 followingVery 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 bySimilarly to INPUT , you can attach additional sequential output files (F ) on device numbers (N ),Or for Partitioned Data Sets, For selecting partitions of a PDS, you don't use the same MONITOR call as for INPUT , but instead use:
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: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:
|
||||
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(
|
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)
|
This function transforms the variable NE (which should be FIXED for proper usage) into a CHARACTER descriptor. NE should have the form:
|
(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:
XCOM-I implements To make things a bit more confusing, |
||||
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,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,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,which turns off run-time trace. |
See TRACE . |
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 import
ed 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 andBASED basedVariable ...; 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 viaand then reassociate it later withCALL MONITOR(8, 4, "80000000" | 2); INPUT4 viaThe description "Set PDS DDNAME" I've given as the description comes from the associated program comment in MONITOR.bal, the source-code for theCALL MONITOR(8, 4, "80000000" | 4); 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:
|
"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:
|
||||||||||||||||||||||||
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 the PARM field is the string//XPL EXEC PGM=MONITOR, '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 inThe 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:
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
|
||||||||||||||||||||||||
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,
|
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);
|
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
|
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 |
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.
-ggdb
, and incorporating it can be done simply by adding it to the make command like viaHaving 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.make EXTRA=-ggdb ...
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. 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.void printMemoryMap(char *heading)
— This function prints out the entire current state of the memory map. I.e., the addresses of all variables, including dynamically-allocated ones, and the contents of all of those variables. Of course, for an XPL program any complexity, the printout is quite long, so this function is presumably used sparingly. The heading
parameter is simply a message printed at the top, which can be helpful if you call printMemoryMap
several times (perhaps via CALL INLINE
) in the same program run. In a gdb console, you could run it via the command call printMemoryMap("...")
, whereas you could instead embed it in your XPL source code via CALL INLINE('
printMemoryMap("...")
');
.char *getXPL(char *identifier)
— Returns (as a C string) the value of a single XPL variable whose name is given by the identifier
. The identifier
string can be any identifier expression that's syntactically correct in XPL/I, provided that subscripts consist entirely of decimal digits, possibly with a leading minus sign. This includes expressions like "V"
, "V(5)"
, "B(3).V"
, or "B(3).V(-5)"
. Recall that in XPL/I, subscripts can be applied to scalar variables. If you are querying a BASED RECORD
, then be sure to include the desired field, since while getXPL
knows how to print an individual field of a RECORD
, it does not know how to print a collection of fields such as a RECORD
. In a gdb console, you could use getXPL
with a command like print getXPL(...)
. For example, print getXPL("C1(4)")
. void printXPL(char *identifier)
— This provides the same functionality as getXPL
, except that it prints its output to stdout
rather than returning it as a string. In a gdb console, you could run it via the command call printXPL("...")
, or you could instead embed it in your XPL source code via CALL INLINE('
printXPL("...")
');
.int bitBits
— By default, getXPL
and printXPL
print the data of a BIT
variable in hexadecimal notation. In contrast, legacy XPL source code has often logically partitioned the data in BIT
strings into subgroupings of 1, 2, or 3 bits (rather than 4 as for hexadecimal), thus using literal constants (such as initializers in declarations) that are in binary, base-4, or octal notation. In those cases, it's difficult to relate hexadecimal strings returned by getXPL
with the literals shown in the XPL source code. The global variable bitBits
addresses this by allowing you to change the radix used for the BIT
data. By default bitBits
is 4 (hexadecimal), but can be changed to 1 (binary), 2 (base-4), or 3 (octal)
. In a gdb console, you could change via a command like set bitBits=2
.In using these debugging functions, note that they all require mangled forms of variable names and parameters of PROCEDURE
s. Mangled names consist of the names of the variables or parameters as DECLARE
d in the XPL source code, but prefixed by the names of all of the parent PROCEDURE
s. 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:
Thus while we have lots of global and local variables and function parameters namedX
Y
Z
PROC1
PROC1xX
PROC1xY
PROC1xZ
PROC1xPROC2
PROC1xPROC2xX
PROC1xPROC2xY
PROC1xPROC2xZ
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.
INLINE
that look roughly like this:
There are several different variations toCALL 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 */
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.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: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// (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);
;
INLINE
s have been added or removed in the meantime ... an assumption which at this point seems pretty safe! Let's call them patch numbers.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.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.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// 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;
CALL INLINE(...)
statements to:Notice that the patch is automatically placed within a C{ // (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);
;
{...}
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.--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.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.