The Computer Journal, Issue 33
Z-System Corner
© Jay Sage
Reproduced with permission of author and publisher.
This text is a version of my column in TCJ issue 33 which has been edited to reflect the changes in ARUNZ from version 0.9J to 0.9N. Not all new features are reflected, but the example alias scripts should now work.
JPS 11/13/88
For my column this time I plan to cover two subjects, both of which I have dealt with somewhat at length in the past. Nevertheless, there just seems to be a lot more to say on these subjects. The first is ARUNZ; the second is shells in general, and the way WordStar 4 behaves (or rather misbehaves) in particular.
I was quite surprised and pleased by the enthusiastic response to my detailed treatment of ARUNZ in issue 31. Apparently, there were many, many people who were unaware of what ARUNZ was and who are now quite eager to put it to use. There are two specific reasons for taking up the subject of ARUNZ here again so soon.
First of all, I think that readers will benefit from a discussion of some additional concrete examples. Since my own uses are the ones I know best, I plan to take the ALIAS.CMD file from my own system as an example and discuss a number of interesting scripts. My first cut at doing that for this column came out much too long, so I will cover half of the file this time. The other half will be covered in the next column.
The second reason is that I have just gone through a major upgrade to ARUNZ. It is now at version 0.9J. Several aspects of its operation as described in my previous column have been changed, and quite a few new parameters have been added.
The changes in ARUNZ were stimulated by two factors. One is the two new dynamic Z Systems that will have been released by the time you read this: NZCOM for Z80 computers running CP/M 2.2 and Z3PLUS for Z80 computers running CP/M-Plus. These two products represent a tremendous advance in the concept of an operating system, and everyone interested in experimenting with or using Z System - even if he already has a manually installed ZCPR3 running now - should get the one that is appropriate to his computer.
With these new Z System implementations, if your level of computer skill is high enough to run a wordprocessor or menu program, then you can have a Z System designed to your specifications in a matter of minutes. You can change the design of your Z System at any time, even between applications. As described later, ARUNZ now has some parameters to return addresses of system components so that aliases can work properly even when those system components move around, as they may do under these dynamic systems.
My New Computer System
The second impetus came from my finally building for myself a state-of-the-art computer! For most of my work in the past I have used a BigBoard I with four 8" floppy disk drives and an SB180 with four 5" disk drives. Neither machine had a hard disk.
The SB180, my main system for the past year and a half, had been sitting on the floor in the study. The pc board was mounted in a makeshift chassis with two power supplies, just as I got it from someone who bought it at the Software Arts liquidation auction and after they had stripped out the disk drives (at $25 I could hardly complain!). I added my own drives, which sat in the open air (for cooling among other reasons) in two separate drive cabinets elsewhere on the floor. All in all not very pretty and not as functional as it could have been.
The sad part of it is that during all this time I had everything needed to turn the SB180 into an enjoyable and productive system. A high-speed 35 Mb hard disk was collecting dust on a shelf; an attractive surplus Televideo PC-clone chassis adorned the work bench in the basement; the XBIOS software disks sat ignored in one of my many diskette boxes.
Finally one weekend I decided that it would be more efficient in the long run to take some time off from my programming and writing work to reconstruct the system. Indeed, it has been! The SB180 is now attractively mounted in the Televideo chassis with one 96-tpi floppy and one 48-tpi floppy. The hard disk is configured as four 8 Mb partitions and runs very nicely with the fast Adaptec 4000 controller.
With the hardware upgraded, I then did the same to the software. Installing XBIOS on the SB180 took so little time that I really had to kick myself for not doing it sooner. Richard Jacobson was quite right in his description of it in issue 31. Thank you, Malcom Kemp, for a really nice product.
Once I was fixing things up, I decided I should really do it up right, so I also purchased the ETS180IO+ board from Ken Taschner of Electronic Technical Services - this despite the fact that a Micromint COMM180 board was also a part of my longstanding inventory of unused equipment. I cannot compare the ETS board to the COMM180, never having used the latter, but I certainly am highly pleased with it. XBIOS includes complete support for the ETS board, so configuring the system to make use of the extra ETS180IO+ features, like the additional parallel and serial ports and the battery-backed clock, was very easy.
I have been so pleased with the new system that I even went out and bought a real computer table for it to sit on. For the past years, the terminal's CRT unit had been sitting on one of those flimsy folding dining-room utility tables, with a yellow-pages phone book under it to jack it up to the right height. The keyboard sat on a second folding table, and the whole thing was always in imminent danger of toppling over. What a pleasure it is to sit at the new system.
While I'm waxing enthusiastic, let me mention one other thing I did to reduce the disarray in the study. I bought four Wilson-Jones media drawers to house my vast collection of floppies. These diskette cabinets resemble professional letter filing cabinets. A drawer, which can hold more than 100 floppies, pulls out on a full suspension track so that one can easily reach all the way to the back. Since there is no top to flip open, units can be stacked on top of each other to save a great deal of table space. Clips are provided to secure the units to their neighbors both horizontally and vertically.
The only drawback to these disk drawers has been their cost. Inmac and the other major commercial supply houses want more than $60 each! But Lyben, which sends its catalogs out to many computer hobbyists, offers them for only $35. Extra dividers, which I recommend, are just under $6 per package of five. Lyben can be reached at 313-589-3440 (Michigan). [Note added at last moment - I am sorry to say that I just received the new Lyben catalog, and the price has now gone up to $45. Although this is still a bargain when compared to other vendors' prices, I'm glad I put in my order when I did.]
ARUNZ VERSION 0.9J
(edited for version 0.9n)
Now that I have had my chance to show my excitement over the new state of my computer and computer room, let's get on with the discussion of ARUNZ. First we will discuss the changes introduced since version 0.9G, both the old features that have changed and the new features that have been introduced.
Changes in Old Features
Because, as noted in my last column, ZCPR34 can pass commands containing explicit file types or wildcard characters ('?' and '*'), the characters used to define special matching conditions in the alias names in ALIAS.CMD had to be changed. The period, which had been used to indicate the beginning of optional characters in the alias name, has been replaced by the comma. The question mark had been used to indicate a wild-character match in the alias name. Since it can now be an actual character to be matched, the underscore has replaced it.
Since the command verb can now include an explicit file type (not necessarily COM) and/or a directory prefix, several changes have been made to the parameters that parse the command verb. In general, all of the command line tokens are now treated in the same way; all four token parsing parameters ('D', 'U', ':', and '.') now work with digits from 0 to 9 and not just 1 to 9. Thus the command line
C3:TEST>arunz b12:test.z80 commandtail
or, with ARUNZ running as the extended command processor (ECP), the command
C3:TEST>b12:test.z80 commandtail
will have the following parameter values for token 0, the command verb:
$TD0 B (Token 0 Drive) $TU0 12 (Token 0 User) $TN0 TEST (Token 0 fileName) $TT0 Z80 (Token 0 fileType)
THIS IS A SIGNIFICANT CHANGE. PLEASE TAKE CAREFUL NOTE OF IT. The parameters $TD0 and $TU0 no longer necessarily return the logged-in drive and user. For the standard configuration of ZCPR33 (and 34) a verb of the form B12:TEST cannot be passed to the extended command processor; the presence of an explicit directory specification results in the immediate invocation of the error handler (skipping the ECP) if the file cannot be found in the specified directory. However, if a file type is included, the 'bad' command will be passed to the ECP.
New Features
There are now three new parameters that do return information about the directory that was current (logged in) when ARUNZ was invoked. These parameters are shown below with their meaning and the values they would have with the example command above:
parameter meaning value $HD Home Drive (D) C $HU Home User (U) 3 $HB Home Both (i.e., DU) C3
There is also a new parameter to denote the entire command line, including both the command verb and the command tail. Many people in the past confused "command line" with "command tail" and attempted to use the parameter $* for the former. The new parameter is '$!'. It is roughly equivalent to '$0 $*', but there is one important difference. The latter parameter expression always includes a space after the command verb, even if there was no tail ('$*' was null). This space caused problems with some commands. For example, when the SLR assembler SLR180 is invoked with nothing after it, it enters interactive mode and allows the user to enter a series of assembly requests. Unfortunately, the code is not smart enough to distinguish a completely nonexistent command tail from one with only spaces. When the command "SLR180_" is entered, where '_' represents a blank space, the assembler looks for a source file with a null name. Not finding it, it returns with an error message.
I used to deal with this problem by writing a complex alias of the form:
SLR180 if nu $*;asm:slr180;else;asm:slr180 $*;fi
With the new parameter, all this complication can be avoided. The script is simply:
SLR180 asm:$!
If you are wondering why one would want an alias like this, just wait a while. It will be explained later.
There is also a whole set of new parameters that generate the addresses of almost all of the Z System modules. This capability will become important with the dynamic Z Systems now being introduced (NZCOM and Z3PLUS). With those systems, the addresses of the RCP, FCP, CCP, and so on can all change during system operation. The new parameters permit one to make reference to the addresses of those modules even when they move around. My ALIAS.CMD file described below will have some examples of how these parameters are used.
These parameters begin with $A ('A' for address) and are followed by an additional letter as follows:
B BIOS L MCL (command Line) C CCP M MSG (message buffer) D DOS N NDR E ENV P PATH F FCP R RCP I IOP S STK (shell stack) X XFCB (external FCB)
Amazingly enough, these names are all mnemonic except for the conflict over 'M' between the multiple command line buffer (MCL) and message buffer (MSG). I resolved this by using 'L' (think of LINE) for the MCL.
Finally, there is a new symbol that can be used to make a special kind of alias name specification in ALIAS.CMD. If a name element begins with a '>', then only the file type of the command verb is used in the comparison. Without this feature one had to use very complex forms to recognize a file type. For example, suppose you want to be able to enter the name of a library file as LBRNAME.LBR as a command and have VLU invoked on it. The following script used to be required:
?.LBR=??.LBR=???.LBR=????.LBR=?????.LBR=
??????.LBR=???????.LBR=????????.LBR vlu $0
Every possible number of characters in the library name had to be dealt with explicitly. With the new symbol and the other ARUNZ09J features, one can define this script more simply as follows:
>LBR vlu $TN0
Example ALIAS.CMD File
Now that we have described the new resources available in ARUNZ09J, we will begin our look at part of the ALIAS.CMD file that I am using right now on the SB180. It will be the second half of the file, because that part contains some items of immediate relevance.
First some words of philosophy. There are many ways in which Z System can be used effectively, and I am always amazed and impressed at the different styles developed by different users. What I will now describe is my approach. As they say, yours may differ! In any case, I hope these comments will stimulate some good ideas, and, as always, I eagerly await your comments and suggestions.
I am a strong believer in short search paths. When I make a mistake in typing a command, I do not want to have to twiddle my thumbs while the command processor thrashes through a lot of directories searching for the nonexistent command. I want the error handler to take care of it as quickly as possible. As a result, the search path on my SB180 includes only one directory, A0, the RAM disk. (With XBIOS, the RAM disk can be mapped to the A drive.)
When I enter a command, it is searched for only in A0. If it is not found there, then ARUNZ (renamed to CMDRUN.COM) is loaded from A0, and it looks for a script in ALIAS.CMD, also in A0. If ARUNZ cannot resolve the command, then the error handler, EASE in my case, is invoked (you guessed it, also on A0). Thus no directory other than the RAM disk is accessed except by an explicit directory reference generated either by an alias script or by a manually entered command. Everything appears to operate instantaneously.
Aliases to Provide Explicit Directory Prefixes
Obviously, I cannot keep all the COM files that I use in directory A0. In fact, with the tiny RAM disk on the SB180 (and allowing about 100K for a BGii swap file), there is barely enough room for CMDRUN.COM (ARUNZ), ALIAS.CMD, EASE.COM, EASE.VAR, IF.COM, ZF.COM (ZFILER), ZFILER.CMD, SAVSTAMP.COM, ZEX.COM, ZEX.RSX, and a few directory programs. Fortunately, this is all that really needs to be there.
So what do I do about all the other COM files that I want to use? There are two possibilities. I could invoke them manually with explicit directory references, as in "B0:CRC FILESPEC", but this would clearly be a nuisance (and contrary to the spirit of Z System!). The other alternative is to provide alias definitions in ALIAS.CMD for all the commands in other directories that I want to use.
A second half of my ALIAS.CMD file is shown in Listing 1. The group of aliases at the very end comprises several sets of definitions that do just what I have described for several of the directories on the hard disk. As I use programs in other directories, I add them to the ALIAS.CMD file.
These aliases are included at the end, by the way, so that other definitions can preempt them as desired. If you look carefully, you will see some aliases defined here that are also defined earlier in the ALIAS.CMD file. The earliest definition always takes precedence, because ARUNZ scans ALIAS.CMD from the beginning and stops as soon as it encounters a matching name specification.
Directory B0, named SYS, contains most of my system utilities. Directory B1, named ASM, contains my assembly language utilities. A few commonly used files are in other directories. The aliases defined in these sections do nothing more than add an explicit directory prefix to the command entered. For example, the script definition
AFIND b0:$!
would take my command line "AFIND TAIL..." and turn it into "B0:AFIND TAIL...". Note how compact the definitions can be. You do not need a separate line for each command. Similar scripts could be constructed, by the way, for COM files kept in COMMAND.LBR and extracted and executed by LX. I do not use LX, so I have no examples to show.
There are several fairly easy ways to automate the construction of these entries in the ALIAS.CMD file. If you use PMATE or VEDIT as your text editor, you can write macros that will perform the entire process. That is how I generated the aliases you see. With the PMATE macro, I can easily repeat the process from time to time to make sure that all my COM files are represented by aliases. So far I have run my PMATE macro on user areas 0, 1, 2, 3, and 4 of hard disk partition B.
Lacking these tools, you can run "SD *.COM /FX" to get a file DISK.DIR containing a horizontally sorted listing of all the COM files in a directory (without going to a lot of trouble, I do not get a sorted listing from PMATE). Then use your favorite editor, whatever it is, to add carriage returns so that each file is on its own line and to delete all of the text after the file name (i.e., the dot, file type, and file size). If there are any commands for which you want to have special aliases (we'll see some examples shortly), you may delete their names from the list (or you can leave them - they do no harm). Then close up the list, inserting equal signs and, when the line is wide enough, add the command script. Finally, merge this with the rest of your ALIAS.CMD file.
Aliases for Special Command Redefinitions
Just before the simple redefinition aliases there are six commands that have been separated out for special treatment. Consider the first of them:
ZP,ATCH b0:zpatch $*
I find that my fingers have some difficulty typing the full ZPATCH correctly, and this alias permits me to enter simply ZP. Note that in this case we cannot use "b0:$!" for the script because the alias name allows for forms other than an exact ZPATCH. If the script used the $! parameter and the command was entered as ZP, then the expanded script would become "B0:ZP ...", which would not work.
The alias for crunching is similar in some respects but more elaborate. The letter combination CH must give me trouble, because I often type CRUNCH wrong, too, unless I work very carefully. This alias not only lets me use the short form CR; it also allows the command to work with named directories.
CR,UNCH b0:crunch $td1$tu1:$tf1 $td2$tu2:
By expanding the first and second parameters explicitly, named directory references can be converted to the DU: form that CRUNCH can deal with.
The alias for DATSWEEP goes a little further than the other two insofar as alternative forms are concerned.
DATSW,EEP=DS=SWEEP b0:datsweep $*
It allows abbreviated forms as short as DATSW, but it additionally allows alternative nicknames for the command, such as DS or the more familiar SWEEP, which it replaces on my system.
The next example in this section shows how a program that does not know about Z System file specifications at all can be made to work with them anyway.
LDIR $td1$tu1:;b0:ldir $tn1;$hb:
For LDIR I just started to use LDIR-B, which displays date stamp information about files in the library. Unfortunately, it does not know about named directories; in fact, it does not even know anything about user numbers. If he is true to form, Bruce Morgen, the Intrepid Patcher, will soon have a ZLDIR-B or an LDRZ-B that will accept full Z System file specs, and I will be able to retire this alias.
At present, however, LDIR-B accepts only the standard CP/M syntax for files. As a result, it is not enough simply to pick apart the token, as it would be if LDIR would accept the form DU:NAME.TYP. Instead, the directory specified for the library is logged into, then the LDIR command is run on the library name, and finally the original directory is relogged. This will work very nicely unless the user number specified is higher than 15 (and your Z33/Z34 is not configured for logging into high user numbers).
The last two examples in this series illustrate still another way to make aliases lighten the typing burden. With XBIOS, alternative versions of the operating system are described in model files. These typically have a file type of MDL, but that type is not required or the default. Consequently, the SYSBLD system-defining utility and the XBOOT system-loading utility must be given an explicit file type. Since I always use MDL for the type, I created these aliases to add the file type for me so that I can enter the commands simply as "SYSBLD TEST" or "XBOOT BIGSYS".
SYSBLD b0:;b0:$0 $1.mdl;$hb:
BOOT=XBOOT b0:;b0:xboot $1.mdl
The XBOOT alias lets me save a little typing by omitting the leading 'X' if I wish. The SYSBLD alias returns to the original directory when it is finished. Since XBOOT coldboots a new operating system, any trailing commands are lost anyway. The XBOOT command will soon support a warmboot mode, in which, like NZCOM and Z3PLUS, the new system is created without affecting the multiple command line, shell stack, or other loaded system modules that have not changed their address or size. I might then add an alias REBOOT or WBOOT (warmboot) that will load a new system and return to the original directory.
Memory Display Aliases
In my system development work I often have occasion to examine various parts of memory. I might want to look at the beginning of the BIOS to check the hooks into an RSX (resident system extension), or I might want to see the contents of the ZCPR3 message buffer to see how some flags are being used.
I used to have a set of aliases like these with explicit addresses in the script ("P FE00" to look at the ENV, for example). This relieved my mind of the task of remembering the addresses where these modules were located in memory. With the new dynamic systems, even a good memory will not suffice, since the modules can move around, and one can not easily be sure just where they are at any given time.
By using the new parameters that I described earlier, the scripts always have the correct addresses. [Actually, they can still be fooled if these parameters are used in multiple-command-line scripts that include the loading of a new dynamic system. As I warned in my earlier article on ARUNZ, all parameters are expanded at the time the alias is invoked. If the system is changed after that, the parameter values may no longer be correct when that part of the script actually runs.]
; Memory display aliases
PBIOS=BIOS p $ab
PCCP=CCP=CPR p $ac
PDOS=DOS p $ad
PENV=ENV p $ae
PFCP=FCP p $af
PIOP=IOP p $ai
PMCL=MCL p $al
PMSG=MSG p $am
PNDR=NDR p $an
PPATH p $ap
PRCP=RCP p $ar
PSHL=PSHELL=SHL=SHELL p $as
PXFCB=XFCB=PFCB=FCB p $ax
; Special equivalents
ZP,ATCH b0:zpatch $*
CR,UNCH b0:crunch $td1$tu1:$tf1 $td2$tu2:
DATSW,EEP=DS=SWEEP b0:datsweep $*
LDIR $td1$tu1:;b0:ldir $tn1;$hb:
SYSBLD b0:;b0:$0 $1.mdl;$hb:
XBOOT=BOOT b0:;b0:xboot $1.mdl
; Complete set of direct equivalents
CMDRUN=LPUT=EDIT0=ERA=IF=REN=SD=SDD=
XD=ZEX=ZF=VLU=W=ZPATCH=COPY=ECHO b0:$!
FF=GOTO=JF=JETLDR87=NULU=PWD=SAVE=
SP=UNCR=VTYPE=XDIR=AFIND=SALIAS=AREA b0:$!
BD=CRUNCHßA=DIFF=DISKRST=
DOSERR=DU=EDITNDR=ERRSET=HSH=ALIAS b0:$!
LLF=LGET=LOADNDR=LPUT14=LT23=LUSH=
LX=MOVE=MU3=PATH=PAUSE=PIP=PROT b0:$!
PROTCCP=PUBLIC=PUTDS=Q=SAP=SAVENDR=
SAVSTAMP=SFA=SHCTRL=SHOW=SQ=STAT b0:$!
SUB=SYSGEN=UF=UNERASE=XSUB=DATE=
DATSWEEP=MKDIR=SHSET=TPA=Z3INS=Z3LOC b0:$!
ZRIP=ASSGN=BSX=FVCD=HDINIT=HDUTIL=
MDINIT=MPTEST=SETDFLT=STARTHD4=SWX b0:$!
SYSBLD=XSYSGEN=TIME=XBOOT0=XVERS=
MCOPY=LZED=PUTBG=STARTHD=STARTHD1 b0:$!
STRTFULL=LDR=JETLDR=LHC=SSTAT=XBOOT=
MAP=STARTBIG=LT=LDIR=QL=SETD=CRC b0:$!
4ERA=4MU3=4REN=4SAVE=T4MAKE=DOSVER=
DRO=LOGGED=SRO=SRW=LBREXT b0:$!
SLR180+=SLRNK=SLRNK+=Z80ASM=DDT=
DSDZ=FORM7=MAKESYM=MLOAD=SLRMAC=XIZ b1:$!
ZAS=ZLINK=ZXLATE=SLR180=180FIG32=
180FIG+=LNKFIG+=SLRIB=ZLIB=ZREF b1:$!
ZZCNFG=LNKFIG=180FIG b1:$!
BGSERIAL=LOADBG=STARTBG=BGPRINT=
BGPRNCFG=DSCONFIG=Q=REMOVE=SECURE b2:$!
SETBG=SPOOLER=DATSWEEP=SDD=SETTERM b2:$!
INSTALL=MEX b3:$!
WSCHANGE=WS=WINSTALL=MOVEPRN b4:$!Listing 1.
The second half of the ALIAS.CMD file from my SB180 with XBIOS, slightly shortened and rearranged.
Shells and WordStar Release 4
As I noted in an earlier column, WordStar Release 4 was a very exciting event for the CP/M world in general and the Z-System world in particular. It was the first major commercial program to recognize Z System and to make use of its features. Unfortunately, the Z System code in WS4 was not adequately tested, and many errors, some quite serious, slipped through. Some of the most significant errors concern WS4's operation as a ZCPR3 shell.
Let's begin with a little background on the concept of a shell in ZCPR. Normally, during Z System operation the user is prompted for command line input. This input may consist of a string of commands separated by semicolons. When the entire sequence of commands has been completed and the command line buffer is again empty, the user would be prompted again for input.
This prompting is performed by the ZCPR command processor, which, because it is limited in size to 2K, is correspondingly limited in its power. Richard Conn, creator of ZCPR, had the brilliant idea of including a facility in ZCPR3 for, in effect, replacing - or, perhaps better said, augmenting - the command processor as a source of commands for the system. This is the shell facility.
Under ZCPR3, when the command processor finds that there are no more commands in the command line buffer for it to perform, before it prompts the user for input, it first checks a memory buffer called the shell stack. If it finds a command line there, it executes that command immediately, without prompting the user for input. The program run in that way is called a shell, because it is like a shell around the command processor kernel. The shell is what the user sees instead of the command processor, and the shell will normally get commands from the user and pass them to the command processor. In effect, the outward appearance of the operating system can be changed completely when a shell is selected.
A perfect example of a shell is the EASE history shell. To the user it looks rather like the command processor. But there are two very important differences. First of all, the command line editing facilities are greatly augmented. One can move the cursor left or right by characters, words, or commands; one can insert new characters or enter new characters on top of existing characters; characters or words can be deleted. One has, in a way, a wordprocessor at one's disposal in creating the command line.
The second feature is the ability to record and recall commands in a history file. Many users find that they execute the same or similar commands repeatedly. The history feature of EASE makes this very convenient. These two command generation features require far too much code to include in the command processor itself, so it is very convenient to have the shell capability.
Programs designed to run as shells have to include special code to distinguish when they have been invoked by the user and when they have been invoked by the command processor. ZCPR3 makes this information available to such programs. When invoked by the user, they simply write the appropriate command line into the shell stack so that the next time the command processor is ready for new input, the shell will be called on. After that, the user sees only the shell. Shells normally have a command that the user can enter to turn the shell off.
ZCPR3 goes beyond having just a single shell; it has a stack of shells. A typical configuration allows four shell commands in the stack. When the user invokes a command designed to run as a shell, it pushes its name onto the stack. When the user cancels that shell, any shell that had been running previously comes back into force. Only when the last shell command has been cancelled (popped from the shell stack) does the user see the command processor again.
Let's look at some of the shells that are available under Z System. We have already mentioned the EASE history shell. There is also the HSH history shell, which offers similar capabilities. It was written in C and cannot be updated to take advantage of innovations like type-3 and type-4 commands. I would say that EASE is the history shell of choice today. This is especially true because EASE can do double service as an error handler as well, with the identical command line editing interface.
Then there are the menu shells, programs that allow the user with just a few keystrokes to initiate desired command sequences. They come in several flavors. MENU stresses the on-screen menu of command choices associated with single keystrokes. VFILER and ZFILER stress the on-screen display of the files on which commands will operate; the commands associated with keys are not normally visible. Z/VFILER offer many internal file maintenance commands (copy, erase, rename, move, archive). VMENU and FMANAGER are inbetween. Both the files in the directory and the menu of possible commands are shown on the screen.
What Kind of Programs Should be Shells?
Not all programs should be shells. From a strict conceptual viewpoint, only programs that are intended to take over the command input function from the command processor on a semipermanent basis should be shells. The history shells and the MENU and VMENU type shells clearly qualify. One generally enters those environments for the long haul, not just for a quick command or two.
ZFILER and VFILER are marginal from this viewpoint. One generally enters them to perform some short-term file maintenance operations, after which one exits to resume normal operations. It is rare, I believe, to reside inside ZFILER or VFILER for extended periods of time, though I am sure there are some users who do so.
Many people (I believe mistakenly) try to set up as shells any program from which they would like to run other tasks and automatically return. This is the situation with WordStar. No one will claim that the main function of WordStar is to generate command lines! Clearly it is intended to be a file editor. Why, then, was it made into a ZCPR3 shell in the first place? I'm really not sure.
WordStar's 'R' command really does not offer very much. In neither the ZCPR nor the CP/M configuration does any information about the operating environment seem to be retained. For example, one might expect on return to WordStar that the control-r function would be able to recall the most recently specified file name. But this does not seem to be the case, although it could easily have been done. In the ZCPR version, the name could be assigned to one of the four system file names in the environment descriptor; in the CP/M version it could be kept in the RSX code at the top of the TPA that enables WordStar to be reinvoked after a command is executed.
The WordStar 'R' command does not save any time, either. Essentially no part of WordStar remains in memory. The user could just as well use the 'X' command to leave WordStar, run whatever other programs he wished, and then reinvoke WS. Nevertheless, I can understand why users would enjoy the convenience of a command like the 'R' command that automatically brings one back to WordStar. Shells, however, are not the way to do this, at least not shells in the ZCPR3 sense.
ZCPR2-Style Shells
In ZCPR2 Richard Conn had already implemented an earlier version of the shell concept which, interestingly enough, would be the appropriate way for WordStar and perhaps even ZFILER/VFILER to operate. He did not have a shell stack, but he did have programs like MENU that, when they generated commands, always appended their own invocation to the end of the command line. Thus if the menu command script associated with the 'W' key was "WS fn2", where fn2 represents system file name #2, then the actual command placed into the command line buffer would be "WS fn2;MENU". In this way, after the user's command ran, the MENU program would come back.
Let's compare how the two shell schemes would have worked with WordStar. Suppose we want to edit the file MYTEXT.DOC and then copy it to our archive disk with the command "PPIP ARCHIVE:=MYTEXT.DOC". We might have created the following alias script for such operations:
WSWORK ws $1;ppip archive:=$1
Then we just enter the command "WSWORK MYTEXT.DOC" when we want to work on the file and have it backed up automatically when we are done.
Here is what WS4 does as a ZCPR3-type shell. The command line starts out as:
WSWORK MYTEXT.DOC
When the alias WSWORK is expanded the command line becomes:
WS MYTEXT.DOC;PPIP ARCHIVE:=MYTEXT.DOC
When WordStar runs, it pushes its name onto the shell stack so that it will be invoked the next time the command line is empty. Noting that the command line is not empty, it returns control to the command processor. Then the PPIP command is executed, backing up our unmodified file (horrors!!!) Finally the command line is empty and WS, as the current shell, starts running. Since it was invoked as a shell, it prompts the user to press any key before it clears the screen to start editing. By this time it has forgotten all about the file we designated and it presents us with the main menu. All in all, a rather foolish and useless way to go about things.
You might think that the problem would be solved if WS did not check for pending commands but went ahead immediately with its work. Indeed, this would work fine until the 'R' command was used. Then either the pending PPIP command would be lost (replaced by the command generated by the 'R' operation) or executed (if the 'R' command appended it to the command it generated). In either case we have disaster!
Now suppose WS4 had used the ZCPR2-style shell concept. After the alias had been expanded, the "WS MYTEXT.DOC" command would run, and we would edit our file. While in WS4, suppose we want to find where on our disks we have files with names starting with OLDTEXT. We use the 'R' command to enter the command line "FF OLDTEXT". The 'R' command would append ";WS" to the end the command we entered and insert it into the command line buffer before the current pointer, leaving the following string in the buffer:
FF OLDTEXT;WS;PPIP ARCHIVE:=MYTEXT.DOC
After the FF command was finished, WordStar would be executed again. Just what we wanted.
In fact, under ZCPR3 WS could be much cleverer than this. First of all, it could determine from the external file control block the name (and under Z33 the directory) used to invoke WordStar in the first place. There would be no need, as there is now, to configure WS to know its own name and to make sure that the directory with WS is on the command search path. The 'R' command could have appended "B4:WSNEW" if WSNEW had been its name and it had been loaded from directory B4.
There is one problem, however. We would really like WS to wait before clearing the screen and obliterating the results of the FF command. With the ZCPR3-type shell, WS can determine from a flag in the ZCPR3 message buffer whether it was invoked as a shell. For the ZCPR2-style shell we would have to include an option on the command line. WS could, for example, recognize the command form "WS /S" as a signal that WS was running as a shell. It would then wait for a key to be pressed before resuming, just as under a ZCPR3-style shell. Of course, you would not be able to specify an edit file with the name "/S" from the command line in this case, but that is not much of a sacrifice or restriction.
We could continue to work this way as long as we liked. Only when we finally exited WS with the 'X' command would the PPIP command run. This, of course, is just the right way to operate!
ZCPR2 vs ZCPR3 Shell Tradeoffs
Once I started thinking about the old ZCPR2-type shells, I began to wonder why one would ever want a ZCPR3-type shell. At first I thought that Z2-style shells could not be nested, but that does not seem to be the case. Suppose we run MENU and select the 'V' option to run VFILER. The command line at that point would be
VFILER;MENU /S
where we have assumed that a "/S" option is used to indicate invocation as a shell. While in VFILER we might run a macro to crunch the file we are pointing to. The macro could spawn the command line "CRUNCH FN.FT". The command line buffer would then contain
CRUNCH FN.FT;VFILER /S;MENU /S
After the crunch is complete, VFILER would be reentered. On exit from VFILER with the 'X' command, MENU would start to run. Thus nesting is not only possible with Z2-type shelling, it is not limited by a fixed number of elements in the shell stack as in ZCPR3 (the standard limit is 4). Only the size of the command line buffer would set a limit.
What disadvantages are there to the Z2-style shell? Well, I'm afraid that I cannot come up with much in the way of substantial reasons. The shell stack provides a very convenient place to keep status information for a program. I do that in ZFILER so that it can remember option settings made with the 'O' command. On the other hand, this information could be kept as additional flags on the command line, as with the "/S" option flag. There is no reason why the information could not be stored even in binary format, except that the null byte (00 hex) would have to be avoided.
If the 128 bytes currently set aside for the shell stack were added to the multiple command line buffer, the use of memory would be more efficient than it is now with Z3-style shells. Z3 shells use shell stack memory in fixed blocks; with Z2 shells the space would be used only as needed. I rarely have more than one shell running, which means that most of the time 96 bytes of shell stack space are totally wasted. Of course, with the present setup of ZCPR3, the multiple command line buffer cannot be longer than 255 bytes, because the size value is stored in the environment descriptor as a byte rather than as a word. The command line pointer, however, is a full word, and so extension to longer command lines would be quite possible (I'll keep that in mind for Z35!).
Following this line of reasoning, I am coming to the conclusion that only programs like history shells and true menu shells should be implemented as ZCPR3-style shells. Other programs, like ZFILER and WordStar should use the ZCPR2 style. If I am missing some important point here, I hope that readers will write in to enlighten me.
Forming a Synthesis
So long as the command line buffer is fixed at its present length and so long as 128 bytes are set aside as a shell stack, one should make the best of the situation. Rob Wood has come up with a fascinating concept that does just that.
Rob was working on Steve Cohen's W (wildcard) shell. He recognized that on many occasions one wants to perform a wildcarded operation followed by some additional commands (just as with the WordStar example followed by PPIP). As a ZCPR3-type shell, W could not do this. It always executed what it was supposed to do after the wild operation before the wild operation!
Rob came up with a brilliant way to combine the ZCPR2 and ZCPR3 shell concepts. When his version of W is invoked manually by the user, it pushes its name, as a good ZCPR3 shell does, onto the shell stack. But it does not then return to the command processor to execute commands pending in the command line. It starts running immediately, doing the thing it was asked to do and using the shell stack entry to maintain needed data.
In the course of operation, however, it does one unusual thing. After each command that it generates and passes to the command line buffer, it appends its own name, as a good ZCPR2 shell does. This command serves as a separator between the shell-generated commands and those that were on the original command line after the W command. After the shell-generated commands have run, W starts to run. It checks the top of the shell stack, and if it finds its own name there, it says "Aha, I'm a shell," and proceeds to use the information in the shell stack to generate the next set of commands. This process continues until W has no more work to do. Then it pops its name off the shell stack and returns to the command processor. The commands originally included after the W command are still there and now execute exactly as intended. Beautiful!
WordStar Shell Bugs
It is bad enough that WordStar's conceptual implementation as a shell is flawed. On top of that, the shell code was not even written correctly. The person who wrote the code (not MicroPro's fault, I would like to add) tried to take a short cut and flubbed it. When a shell installs itself, it should always - I repeat, always - push itself onto the stack. WordStar tries to take the following shortcut. If it sees that the shell stack is currently empty, it just writes its name into the first entry, leaving the other entries as they were.
When WordStar terminates, however, it pops the stack. At this point whatever junk was in the second shell stack entry becomes the currently running shell. The coding shortcut (which I would think took extra code rather than less code, but that is beside the point) assumed that if the current shell stack entry was null, all the others would be, too. But this need not be the case at all. And in many cases it has not in fact been the case, and very strange behavior has been observed with WordStar. Some users have reported that WordStar works on their computers only if invoked from a shell! That is because WordStar properly pushes itself onto the stack in that case.
There are basically two strategies one can take for dealing with the shell problems in WordStar. One is to fix the above problem and live with the other anomalies (just don't ever put commands after WS in a multiple command line). The other is to disable the shell feature entirely.
To fix the bug described above, Rick Charnes wrote a program called SHELLINI to initialize the shell stack before using WordStar. On bulletin boards in the past both Rick and I presented aliases that one can use to disable the shell stack while WS is running and to reenable it after WS has finished. I will now describe patches that can be made directly to WordStar itself. First I will explain what the patches do; later I will discuss how to install them.
Listing 2 shows a patch I call WSSHLFIX that will fix the bug just described. The code assumes that you do not already have any initialization or termination patches installed. If you do, you will have to add the routines here to the ones you are already using.
The patch works as follows. When WS starts running, the initialization routine is called. It extracts the shell stack address from the ENV descriptor and goes there to see if a shell command is on the stack. If there is, no further action is required, since WS already works correctly in this case. If, on the other hand, the first shell entry is null, then the routine calculates the address of the beginning of the second shell entry and places a zero byte there. When this stack entry is popped later, it will be inactive.
Listing 3 shows a patch I call WSSHLOFF that will completely disable the shell feature of ZCPR3 while WS is running. It works as follows. When WS starts running, the initialization routine is called. It gets the number of shell stacks defined for the user's system in the ENV descriptor and saves it away in the termination code for later restoration. Then it sets the value to 0. WordStar later checks this value to see if the shell feature is enabled in ZCPR3. Since WordStar thinks that there is no shell facility, it operates the 'R' command as it would under CP/M. Later, on exit from WS, the termination routine restores the shell-stack-number so that normal shell operation will continue upon exit from WS.
The easiest way to install these patches is to assemble them to HEX files and use the following MLOAD command (MLOAD is a very useful program available from remote access systems such as Z Nodes):
MLOAD WS=WS.COM,WSSHLxxx
Substitute the name you use for your version of WordStar and the name of the patch you want to install. That's it; you're all done.
If you do not have MLOAD, you can install the patches using the patching feature in WSCHANGE. From the main menu select item C (Computer), and from that menu select item F (Computer Patches). From that menu, work through items C (initialization subroutine), D (un-initialization subroutine), and E (general patch area), installing the appropriate bytes listed in Table 1.
Summary
We have covered a lot of material this time. The issue of shells is a very tricky one, and I hope to hear from readers with their comments. I would also enjoy learning about interesting ARUNZ aliases that you have created.
; Program: WSSHLFIX
; Author: Jay Sage
; Date: March 26, 1988
; This code is a configuration overlay to correct a problem in the shell
; handling code in WordStar Release 4.
;
; Problem: WS takes a mistaken shortcut when installing its name on the shell
; stack. If the stack is currently empty, it does not bother to push the
; entries up. However, when it exits, it does pop the stack, at which point
; any garbage that had been in the stack becomes the active shell. This patch
; makes sure that the second stack entry is null in that case.
;---- Addresses
initsub equ 03bbh
exitsub equ 03b3h
morpat equ 045bh
;---- Patch code
org initsub ; Initialization subroutine patch
init: jp initpatch
;----
org morpat ; General patch area
initpatch: ; Initialization patch
ld hl,(109h) ; Get ENV address
ld de,1eh ; Offset to shell stack address
add hl,de ; Pointer th shell stack address in HL
ld e,(hl) ; Address to DE
inc hl
ld d,(hl)
ld a,(de) ; See if first entry is null
or a
ret nz ; If not, we have no problem
inc hl ; Advance to ENV pointer to
inc hl ; ..size of stack entry
ld l,(hl) ; Get size into HL
ld h,0
add hl,de ; Address of 2nd entry in HL
ld (hl),0 ; Make sure that entry is null
ret
endListing 2.
Source code for a patch to fix the bug in the coding of shell stack pushing and popping in WordStar Release 4.
; Program: WSSHLOFF
; Author: Jay Sage
; Date: March 26, 1988
; This code is a configuration overlay to correct a problem in the shell
; handling code in WordStar Release 4.
;
; Problem: Because WordStar runs as a ZCPR3 shell, it is impossible to use
; WS in a multiple command line with commands intended to execute after WS is
; finished. One can disable this by patching the ZCPR3 environment to show
; zero entries in the shell stack while WS is running. This effectively
; disables WS4's shell capability. Unfortunately, it means that the extended
; features of the 'R' command under ZCPR3 are also lost.
;---- Addresses
initsub equ 03bbh
exitsub equ 03beh
morpat equ 045bh
;---- Patch code
org initsub ; Initialization subroutine
init: jp initpatch
;----
org exitsub ; Un-initialization subroutine
exit: jp exitpatch
;----
org morpat ; General patch area
initpatch: ; Initialization patch
call getshls ; Get pointer to shell stack number
ld a,(hl) ; Get number
ld (shstks),a ; Save it for later restoration
ld (hl),0 ; Set it to zero to disable shells
ret
exitpatch: ; Termination patch
call getshls ; Get pointer to shell stack number
shstks equ $+1 ; Pointer for code modification
ld (hl),0 ; Value supplied by INITPATCH code
ret
getshls:
ld hl,(109h) ; Get ENV address
ld de,20h ; Offset to number of shell entries
add hl,de ; HL points to number of shell entries
ret
endListing 3.
Source code for a patch that disables the shell feature of ZCPR3 while WordStar 4 is running and reenables it on exit.
WSSHLFIX patch bytes:
initialization subroutine: C3 5B 04un-initialization subroutine:
(should be this way already) 00 00 C9general patch area: 2A 09 01 11 1E 00 19 5E 23 56 1A
B7 C0 23 23 6E 26 00 19 36 00 C9
WSSHLOFF patch bytes
initialization subroutine: C3 5B 04un-initialization subroutine: C3 65 04general patch area: CD 6B 04 7E 32 69 04 36 00 C9 CD 6B
04 36 00 C9 2A 09 01 11 20 00 19 C9Table 1.
List of HEX bytes for installing either of the patches into WordStar Release 4 to deal with the problems in the shell code.
[This article was originally published in issue 33 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 1988, 1991 Socrates Press and respective authors]