The Computer Journal, Issue 41

Z-System Corner

© Jay Sage

Reproduced with permission of author and publisher.

By the time you read this, summer vacation will probably be just a fond memory for you, but it is August as I write this, and I have just returned from three weeks in Israel. This was a total vacation. I didn't touch or even think about computers the whole time I was away, except at the very end when my mind started to refocus on the responsibilities that awaited me at home, including this TCJ column. It was so nice to get "computer compulsion" out of my system that I have not been all that eager to get back immediately to my old routine... but I know it will happen soon enough.

Having not thought about computing for a month, I had to work to recall the things I was excited about before I left and planned to discuss in this issue. Fortunately, I left myself some notes. One item was the continuation of the BYE discussion, this time covering the extended DOS functions implemented in BYE. The truth is I have neither the energy nor the time to take up that subject now. Instead, I am going to come back once again to my favorite subject: aliases and ARUNZ.

I think ARUNZ is the one thing that keeps me hooked on Z-System and not eager to follow after the MS-DOS crowd. Although I have looked hard, I have not found anything with the combined simplicity and power of ARUNZ for MS-DOS. The mainframe batch processing language REXX, which has been ported by Mansfield Software to DOS machines, is far more powerful, and some day I hope to port a greatly simplified version to Z-System. The DOS version of REXX, you see, takes over 200K of memory while it is running! That often does not leave enough memory, even on a 640K machine, for application programs to run. I think I actually have more problems running out of TPA on my Compaq 386 than I do on my SB180!

Anyway, for this column I am going to begin with a very brief report on a rather dramatic change in the works for ARUNZ and the Z-System as a whole. Then I am going to describe two ARUNZ applications that I recently developed for my own use. I think they illustrate some interesting general principles, and you may even find them useful as they are.

The Extended Multiple Command Line

Most people who use ARUNZ aliases - or even standard aliases - sooner or later run into a situation where the command line overflows and the whole process comes to a crashing halt (well, not really a crash, but a sudden stoppage). The standard Z-System configuration supports a multiple command line buffer (MCL) that can accommodate 203 characters. The largest size possible is 255 characters. Either way, there comes a time when aliases are invoked from command lines that already contain additional commands, and the combined command line is too long to fit in the MCL. People like Rick Charnes would have this happen constantly if they did not adopt a strategy to avoid the problem (more about that in one of our examples later).

I have long been intrigued by the possibility of having a much longer command line. The command processor (CPR) has always used a word-size (16-bit) pointer into the command line, and so, without any change in the ZCPR34 code, the CPR could handle a command line as big as the address space of the Z80.

To verify this, I performed a simple experiment. I configured a Z-System with free memory after the MCL, and then, using the memory utility program MU3, I manually filled in the command line with a very long multiple command line sequence terminated, as required, by a null character (binary zero). Sure enough, after exiting from MU3, the huge command line ran without a hitch.

The next step was to write a special new version of ARUNZ that could be configured to recognize an oversized MCL. Richard Conn set up the environment descriptor (ENV) with a one-byte value for the length of the command line that the MCL buffer could contain. Thus there is presently no way to support an extended MCL (XMCL) in a system-invariant way, that is, in a way that allows programs to determine at run time how big the MCL is. We are working on that problem right now, and, by the time you are reading this, there will almost certainly be a new ENV type defined (81H) that uses one of the remaining spare bytes in the type-80H ENV to report the size of XMCL to programs smart enough to check for it.

The original, single-byte MCL size value in the ENV has to remain as is and contain a value no larger (by definition) than 255 (0FFH). That value is used by the command processor when new user input is being requested. There is no way for the CPR to allow users to type in command lines longer than 255 characters without adding a vast amount of code to perform the line-input function now so conveniently and efficiently provided by the DOS. A shell could be written that included such code, but I really can't imagine anyone typing in such long command lines. If they do, it probably shows that they are not making proper use of aliases.

I have decided to use only one of the spare ENV bytes for the XMCL size and to let that value represent the size - in paragraphs - of the total MCL memory buffer allocated, including the five bytes used by the address pointer, size bytes, and terminating null. The term 'paragraph' as a unit of memory is not often used in the Z80 world. I believe it was introduced with the 8086 processor, where the segment registers represent addresses that are shifted four bits right. Each unit in the segment register is, therefore, 16 bytes and is called a paragraph. With this system, the XMCL buffer can be as large as 255 * 16 = 4080, which allows command lines with up to 4075 characters. Rich Charnes, do you think you can live with that without cramping your style too much?!

Most people will not want to allocate that much memory to the operating system, and I would never have considered this step before the new dynamic versions of Z-System were available. While I might be willing to allocate 1K to the XMCL most of the time, I certainly would want to be able to reclaim that memory when I need it. I'm not sure whether NZCOM or Z3PLUS can be cajoled into handling this kind of flexibility yet; new versions may be needed at some time in the future.

I put the new version of ARUNZ out for beta test, and it worked just fine, and one could write very long alias scripts. Rick Charnes, however, quickly identified a problem. Suppose a conventional alias appeared in the command sequence. After expanding itself and constructing the new command line, the alias would find that, as far as it knew, there was not enough room for it in the MCL. In a nutshell, the hard part with going to the XMCL is that it is not enough to have an advanced ARUNZ; all programs that operate on the MCL must be upgraded. We hope to have new versions of the library routines in Z3LIB that perform these functions. Then, if we are lucky, most of the utility programs can be upgraded simply by relinking. I'm sure it won't be quite that easy, of course!

A MEX Alias

For those who are not familiar with it, MEX (Modem EXecutive) is an advanced telecommunications program written by Ron Fowler of NightOwl Software. Early versions were released for free to the public (up to version 1.14), while the most advanced versions (called MEX-Plus) are commercial products. I use version 1.65, and some of the specifics in my example apply to that version. I am pretty sure that the technique I describe can be applied to the free version as well.

Rather than being a telecommunications program, MEX should probably be considered a telecommunications programming language. It supports a very wide range of internal commands for managing telecommunications tasks, and it even has a script language for automating complex sequences of operations.

The MEX command line allows multiple commands to be entered just as in Z-System, and a MEX command allows the user to define the command separator. Although I depend on aliases to generate complex Z-System commands and MEX script files to automate complex MEX command sequences, I still frequently make use of simple, manually entered multiple commands.

Being accustomed as I am to entering Z-System commands separated by semicolons, I naturally set up my version of MEX to use the semicolon as its separator, too. Now I can comfortably work in both environments. However, I also frequently like to invoke MEX with some initial commands, which MEX allows one to include in the command tail. Here's a simple example.

B11:TEMP>mex read mnp on

This command invokes MEX and tells it to run the script file MNP.MEX with the parameter "ON". This script causes a string to be sent to my modem which engages the MNP error correcting mode (yes, when I purchased my most recent modem - replacing a USR Password - I decided to spend the extra money for MNP, although at the time there weren't many systems that supported it; now I'm glad I did).

That command line works fine. But often I want to do more, and so I always wanted to enter something like:

B11:TEMP>mex read mnp on;call zitel

This would start out by doing what the first example did but would then continue by placing a call to the ZITEL BBS. [If you can keep a secret, I'll tell you that the ZITEL BBS is the MS-DOS system that I run for the ZI/TEL Group of the Boston Computer Society. ZI/TEL comes from the letters in Zilog and Intel, and it symbolizes the fact that we support the two main operating systems run on chips from those companies: CP/M and MS-DOS. The BBS machine, a Kaypro 286/16, is sitting in the other room (you don't think I'd allow it in the same room with the Z-Node, do you?), and it has an HST 9600 bps modem with MNP error correction. If you want to contact me there, by the way, the number is 617-965-7046.]

An on-the-ball reader already realized that the above command will not work, because the semicolon separator before the CALL command, which I intended as the MEX separator, will be interpreted by the CPR as its separator, and it will terminate the MEX command. What can we do about this?

Some compromise here is inescapable, and I was willing to accept - from the CPR command line only - a MEX separator other than semicolon. Thus the following form would be acceptable

B11:TEMP>mex read mnp on!call zitel

with an exclamation point as the separator as in CP/M-Plus. But for years I could not figure out how to accomplish this.

At first I thought there was a very simple solution. When MEX starts up, it can be set up to automatically run an initialization script file INI.MEX. So, I created a version of MEX (the MEX "CLONE" command makes it easy to create new versions) that used "!" as the separator, and I created an INI.MEX file with the command


Thus, as soon as MEX was running, the separator would be set back to a semicolon. Unfortunately, to my chagrin, I learned that MEX invokes the INI.MEX script only when no commands are included on the command line. With the ZITEL command line shown earlier, MEX would be left with the exclamation point as the separator.

Here is what I thought of next (at least momentarily). Rename MEX.COM to MEX!.COM and set up a MEX alias in ALIAS.CMD with the definition

MEX mex:mex! $*!stat sep ";"

The idea here is that the user's MEX commands from the command line (separated by "!") will be passed in by the $* parameter and will have the STAT command added. Thus our earlier example will turn into the command line

B11:TEMP>mex:mex! read mnp on!call zitel!stat sep ";"

This, of course, fails for the same reason that I could not just enter commands with semicolons in the first place. The trick to get around this is to use a command that for some reason Ron Fowler does not document in the MEX manual: POKE. It works like the Z-System command of the same name and places a byte of data into a specified memory address.

I knew the value I wanted to poke: 3BH, the hex value for the semicolon character. The question was, where should it go? To find out, I took a version of MEX set up with semicolon as the separator and the version with exclamation point as the separator and ran the utility DIFF on them (in verbose mode to show all the differences). Then I looked for the place where the former has a semicolon and the latter an exclamation point. For MEX-Plus this turned out to be 0D18H so that the MEX poke command would be

POKE $0D18 $3B

Note that MEX uses a dollar sign to designate hex numbers. The alias now read

MEX mex:mex! $*!poke $$0d18 $$3b

Observe that a double dollar sign is needed to get a single dollar sign character into a command. A lot of people forget this and end up with scripts that don't do what they're supposed to.

I tested this, and it works splendidly - but with one possible 'gotcha'. The commands passed to MEX must not invoke any script files that depend on the command separator being a semicolon (because it will be exclamation point until the final poke command runs); nor may the read files change the command separator (because the rest of the command sequence still assumes it is the exclamation point). For this reason, it is prudent to write all script files with only one command per line so that no separator is needed. I haven't been doing this, but I will from now on!

One final word on the script. I actually did not do this exactly as I have described. Instead, I left my MEX.COM set up with the semicolon separator, and I created a distinct ARUNZ alias called MEX! so that I would be reminded of the separator. This alias script reads

MEX! get 100;poke d18 "!;go $*!poke $$0d18 $$3b

This uses the famous poke&go technique originated by Bruce Morgen. MEX.COM is loaded into memory by the GET command, and then the Z-System POKE command sets "!" as the command separator. Then the modified loaded code is run by the GO command. The rest is as described previously.

A Spell-Check Alias

I try to remember to put all my writing through The Word Plus spelling checker that came with WordStar Release 4 so that as many typos as possible will be caught. The procedure for doing that on a Z-System is a bit complicated because the text file is generally not in the same user area as the spelling check program. While writing my last TCJ column, I finally got fed up with the complexity and automated the whole process using a set of aliases.

I wanted to support the following syntax:

C1:TCJ>spell filename.typ dictname

If just the file name was given, the alias would prompt for the name of the special dictionary to use, and if not even a file name was given, then the alias would prompt for both names. A special version of the command, ZSPELL, would take only the file name and would automatically use ZSYSTEM as the name of the special dictionary (it knows about mnemonics like ZCPR, MCL, and RCP, and about all those special words like debugger, relocatable, and modem). We'll describe the general alias set first. In listing the aliases, we will write them in multiline format for easy reading;in the ALIAS.CMD file the scripts have to be on a single line (though I hope that will change soon).

The user-interface alias, SPELL, deals only with the matter of how many parameters the user has provided. It reads as follows:

if nu $1;
if nu $2;
/TW1 $1;
/TW2 $1 $2;

If no parameters at all are provided (IF NULL $1), then the secondary script TW0 is run. The leading slash signals ZCPR34 that the command should be directed immediately to the extended command processor. If a first parameter but no second parameter is present (IF NULL $2), then the secondary script TW1 is run. Finally, if both parameter are provided, then script TW2 is run.

The script TW1 includes a prompt only for the name of the special dictionary file:

$"Name of special dictionary: "
/TW2 $1 $'e1

The first token in any user response to the first prompt ($'E1 - when working with ARUNZ you should have a printout of the parameter DOC file) is used along with the file name that was already given, and both are passed to TW2.

The script TW0 includes prompts for both the file name and the special dictionary:

$"Name of file to check: "
$"Name of special dictionary: "
if ~nu $'e1
/TW2 $'e1 $'e2

The first tokens in the responses to the prompts are passed to script TW2. If no file is specified for checking, the alias simply terminates.

Before we look at TW2, which does the real work, let me ask a rhetorical question: why do we break this process up into so many separate aliases. There are two main reasons. The first is that the command line buffer would overflow if all these smaller scripts were merged into a single big script. The extended MCL we discussed earlier could overcome this problem, but for another reason we would still have to use separate aliases.

As I have discussed in past columns, ARUNZ cannot know at the time it expands a script what the results of conditional tests will be later when the IF commands are run. Thus ARUNZ must process all user input prompts that appear in the script. This would mean asking for a file name and special dictionary even when the names were given on the command line. The solution to this problem is to put the prompts in separate scripts that get invoked only when the information requested in those prompts is actually needed.

Now let's look at the script TW2.

path /d=tw:;
tw:tw $tf1 $tn2.cmp;
path /d=;
/twend $tn2;

This is simpler than what you expected, no? Well, there is still a lot of work imbedded in the subroutine script TWEND, which we will cover later. Here we broke up the script solely to prevent MCL overflow.

The first command makes use of the ZSDOS file search path (see the articles by Hal Bower and Cam Cotrill on ZSDOS in TCJ issues 37 and 38). Although there was an attempt to update WordStar Release 4 to include some Z-System support, no such attempt was made with The Word Plus spell checker. In general, the file to be spell-checked will be in one directory and The Word files in another directory. The main program TW.COM could be located by the command processor using its search path, but TW.COM needs a number of auxiliary files, such as the dictionary files. How can the system be made to find all of these files at the same time. ZSDOS provides the answer.

I have replaced the standard Z-System PATH command with the ZSDOS utility ZPATH (renamed to PATH). The first command in TW2 defines the DOS search path to include the directory TW:, which is where I keep all the files that are part of The Word Plus spell-checking package. Once that directory is on the DOS path, all files in it will more-or-less appear to be in the current directory. Very handy! If you use ZDDOS, the search path is not available. I will not show it here, but you can accomplish the same thing using only public files. It's just not quite as neat and straightforward. I am willing to pay the small memory penalty to get the nice extra features of ZSDOS over ZDDOS.

The second command logs us into the directory where the file to be checked resides. If we did not include a DIR: prefix, we were already there, but the extra command does not hurt, and it is nice to know that a directory can be specified explicitly (in either DU: or DIR: form) for the file to be checked. There could be a problem if the file is in a user area above 15, since you may not be able to log into that area. My configuration of Z34 allows this, but when I run BGii I lose this feature (and I sure miss it). If you can't log into those areas, then you should not keep files there that you want to spell-check.

The third line actually runs the spell checker (you knew that had to happen some time!). Notice that even if the user specified a file type for the special dictionary, type CMP is used. Only the name ($TN2) without the type is taken from the user. As the master program TW.COM is run, it will find its component program files (e.g., SPELL.COM, LOOKUP.COM, MARKFIX.COM) and the various dictionaries in the TW: directory thanks to ZSDOS, and it will find the text file in the current directory. As it works through the text, if there are any questionable words, it will write out a file ERRWORDS.TXT to the current directory. If any words are added to the special or UPDATE dictionaries, then the modified dictionaries will be read from TW: but written out to the current directory. You must understand these facts in order to understand the rest of the script.

Once the spell-checking is complete, the ZSDOS path is set back to null (unless I have a special need to have the DOS perform a search, I leave it this way to avoid surprises). Then the ending script TWEND is run, and finally the original directory ($HB:) is restored as the current directory.

Now let's look at TWEND. As it is invoked, the name of the special dictionary is passed to it. TWEND's job is to clean up scratch files and to take care of any updated dictionaries. It reads

if ex errwords.txt;
era errwords.txt;
/dupd $1;
/dupd updict

For efficiency and to prevent MCL overflow, the dictionary updating is performed by yet another subroutine script, DUPD. It gets called twice, once with the special dictionary (if any) and once with the update dictionary. It reads as follows:

if ex $tn1.cmp;
mcopy tw:=$tn1.cmp /ex;

If an updated version of the specified dictionary exists in the current directory, then it is copied to the TW: directory, overwriting any existing file of that name (MCOPY option E). The source file is then erased (MCOPY option X). Oh yes, I almost forgot; the MCOPY here is my renamed version of the COPY program supplied with ZSDOS.

That is it except for showing you the special ZSPELL version of the alias. Notice that I make the "ELL" part of the command optional by inserting the comma in front of that part of the alias name. I also allow the script to be invoked under the name ZTW. The main SPELL script actually has the name "TW=SP,ELL" on my system. Since TW: is not on my command search path, the command "TW" will invoke the ARUNZ script unless I am in the TW: directory at the time.

if nu $1;
/TW2 $1 zsystem.cmp;
$"Name fo file to check: "
if ~nu $'e1
/TW2 $'e1 zsystem.cmp

I hope you find these alias examples useful and instructive. That's all for this time. See you again in two months.

[This article was originally published in issue 41 of The Computer Journal, P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the permission of the author and the publisher. Further reproduction for non-commercial purposes is authorized. This copyright notice must be retained. (c) Copyright 1989, 1991 Socrates Press and respective authors]