Z-System Corneò (c) by Jay Sage The Computer Journal, Issue 41 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 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. [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] Š