The Z-System Corner Jay Sage 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 STAT SEP ";" 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 mex:mex.com;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: SPELL if nu $1; /TW0; else; if nu $2; /TW1 $1; else; /TW2 $1 $2; fi; fi 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: TW1 $"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: TW0 $"Name of file to check: " $"Name of special dictionary: " if ~nu $'e1 /TW2 $'e1 $'e2 fi 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. TW2 path /d=tw:; $td1$tu1:; tw:tw $tf1 $tn2.cmp; path /d=; /twend $tn2; $hb: 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 TWEND if ex errwords.txt; era errwords.txt; fi; /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: DUPD if ex $tn1.cmp; mcopy tw:=$tn1.cmp /ex; fi 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. ZTW=ZSP,ELL if nu $1; /ZTW1; else; /TW2 $1 zsystem.cmp; fi ZTW1 $"Name fo file to check: " if ~nu $'e1 /TW2 $'e1 zsystem.cmp fi I hope you find these alias examples useful and instructive. That's all for¨ this time. See you again in two months.