The Computer Journal, Issue 53

Z-System Corner

© Jay Sage

Reproduced with permission of author and publisher.

First Some Personal Comments

Until I was 27 years old and met my wife, I had essentially never been out of the United States, and the same is true of my parents to this day. My whole family had the common American myopia that pictures the whole world as thinking and acting pretty much the same way we do.

My wife, on the other hand, came from a very different background. She was born in one country and spent several years living in a second before she came to the United States. Her father's history was similar. When my wife was born, he was living in his fourth country, and today he lives in his sixth!

It was not until our honeymoon that I made the dramatic step of leaving North America and setting foot on another continent. My life and outlook were irrevocably altered. A few years later when my employer offered me the opportunity to go to Japan and spend a year working at the Toshiba Central Research Laboratory, I jumped at the opportunity. It was a splendid year, and I loved Japan. The very different culture, however, helped me see my own culture in a way that was probably not possible without that experience. I came back with an enormously greater appreciation for what my own country offers, an appreciation that continues unabated.

My wife, keenly aware of the importance of a world perspective, wanted to get our children started early. She had the idea that, rather than simply visiting with her parents in Switzerland, we should take the opportunity to expose our children deeply to European culture by enrolling them in the public school there. (The school calendar in Europe is different, and our children can get in about six weeks of school after their school here ends.)

Since we preferred that the children learn a language more widely spoken than a Swiss dialect of German, we decided to rent a vacation house in the mountains of the nearby Black Forest in Germany. This is a wonderful place to spend a vacation, and the children learn the language that my wife's family has used since coming to the United States. Last summer was the sixth year, and I am always thrilled to hear the children talking comfortably with their German friends. Last summer, rather than sleep late and have us drive her to school, my 11-year-old daughter preferred to get up early enough to catch the school bus at 6:30 am (!) so that she could spend the time with her friends on the bus.

Computer User Groups in Germany

Now we come to the first of the connections between this story and my TCJ column for this issue. In the first few years I was in Europe, I tried very hard to promote Z-System there, but it was a very frustrating experience. I knew that MS-DOS computers had not yet made the same inroads that they had here and that even businesses were still using CP/M computers. However, in Germany and Switzerland, the culture apparently did not lend itself to the formation of user groups as in the United States (and England and Holland). I was able to find a few individuals interested in 8-bit computing, but no user groups. Those individuals confirmed my impression; they, too, felt all alone.

One year I travelled to Munich to visit a fellow owner of a BigBoard I computer. I learned of him when he wrote a letter to MicroCornucopia magazine. While in Munich, I wandered into computer stores, each time asking if they knew of any CP/M clubs in Germany. The answer was always no. I did find one salesman, Zvonimir Racic, who was interested enough to start one, but it did not last even to the next summer. I have had no further contact with him.

Since I was having no luck searching for 8-bit enthusiasts in person, I decided to try another approach. With great effort (and help from my wife and a German colleague at work here), I composed a letter-to-the-editor in German to one of the major German hobbyist magazines. They never even acknowledged it. At that point I gave up trying.

ZedFest Germany!

Given those earlier experiences, you can imagine how excited I was this last summer to be at the first European Z-System Festival. In the small town of Brackenheim, Germany, near Stuttgart, a small but growing group of 8-bit activists got together to make plans, to share viewpoints, and, most importantly, to meet each other face to face.

It all started when Uwe Herczeg, at whose computer store in Brackenheim the meeting was held, decided to put a small ad in "Computer Flohmarkt" (Computer Fleamarket), a newsprint magazine filled almost entirely with classified ads. His ad sought contact with any other CP/M computerists who might still be left in Germany. Amazingly, he received more than one hundred responses! Among them were the people at the ZedFest, who form the core of the CP/M activist community in Germany today.

The man I was most eager to meet was Helmut Jungkunz. I no longer remember exactly how we first came into contact (maybe he will tell some stories some day here in TCJ), but we had been in very active communication for over a year. Helmut is the 'nuclear reactor' that powers the 8-bit community in Germany. He introduced Z-System to Germany, is sysop of Z-Node #51 in Munich, is the only Z-System dealer in Europe, and heads the Schneider/Amstrad CPC User Group. If only I had run into Helmut years before when I was in Munich!

Also at ZedFest Germany was Tilmann Reh, whose articles on the Z280-based CPU280 computer you have seen in this and the previous issue of TCJ. I had communicated with him several times over the Internet, and I was eager to meet him. He travelled to the meeting by motorcycle and was quite a sight in his sleek, black leather riding outfit and space-age helmet.

Tilmann brought along a CPU280 card to show me. It was hard to believe that such a powerful computer could fit on such a tiny card! Of greater interest to others at the meeting (they were almost all using the CPU280 already!) was the prototype he brought of the IDE disk controller for use with the CPU280. Uwe Herczeg was working on the software to integrate it into the system.

Others present were as follows: Fritz Chwolka, head of Club 80 in Aachen, on the western border of Germany near Belgium and Holland; Herbert Oppmann, member of the MTX User Group, which now supports ZCPR33 for the MTX computers; Andreas Kisslinger, also from Munich, well at home with several operating systems, and an expert Z80 programmer; and Guenther Schock, a hardware expert whose projects, such as an LCD terminal and a CP/M-Plus laptop computer, we may soon read about in TCJ.

Finally, I was especially flattered by two hobbyists who came from very far away to meet me. Juergen Peters, a mainframe hardware engineer by profession, drove down from Hamburg in the far north of Germany. Franz Moessl's trip seemed the most impressive, even though the actual distance was considerably less than from Hamburg. He came all the way from Italy, where he lives in 8-bit isolation in the Tirol, a part of northern Italy adjacent to Austria.

For two days, we all had a wonderful time talking computers and socializing (and, of course, drinking beer!). During one of the discussions I was told about an important issue that constitutes the second connection between computers and my personal comments earlier.

Programming for Compatibility - Again!

In my previous column I talked about making Z-System programs compatible - or at least tolerant - of vanilla CP/M. Ideally, Z-System programs would work to the extent possible in whatever environment they found themselves; at the least they would terminate in a graceful manner. It is too early to tell how responsive the community will be to my message, but the initial indications are very positive. Howard Goldstein, after proofreading the draft of my column, immediately fixed up LPUT and LBREXT. As soon as Rob Friefeld received and read my column, he started to look at his programs.

There is a second issue in programming for compatibility, and that is programming for compatibility in culture and language. For years, we in the United States have been writing our programs with only our own keyboards, our own screens, and our own language in mind. We did not do this out of disrespect or deliberate disregard; we did it because nothing made us think of a more cosmopolitan picture. In our experience, only Americans were using Z-System. That has now changed, and we need to change.

I won't be able to cover all the implications of this here. For one thing, I do not yet know myself what all the implications are. We will need to communicate with and learn from others in the world who are using and developing 8-bit computers.

There are two issues that I will mention here. The most important one is the use of special characters. In the United States, we are quite accustomed to using various special characters, often for pseudo-graphics. For example, we may use the verticule (vertical bar) character (ASCII 7C) as a separator. Most European languages (and probably non-European languages, too) have characters with accents, and these are represented by the ASCII codes for special characters. In Germany, for example, the following associations are apparently used:

[ upper case A with umlaut
\ upper case O with umlaut
] upper case U with umlaut
{ lower case a with umlaut
| lower case o with umlaut
} lower case u with umlaut

When we include those characters in our programs, the screen displays in Europe (and Canada, for that matter) are often not pleasant.

The second issue concerns language. For example, programmers in the United States generally assume that yes/no questions will be answered with 'Y' or 'N'. In Germany, however, it would be nicer if 'J' (for 'ja') and 'N' (for 'nein') could be used. In France it would be 'O' (for 'oui') and 'N' (for 'non'). We also, of course, display all screen information, including prompts, in English.

Is there anything we can do to be more accommodating in these matters? I'm not sure about the whole solution, but I have some ideas. For yes/no questions, we could allow for all common language possibilities: 'Y', 'J', 'O', and 'S' for the affirmative, for example (do we need anything other than 'N' for the negative?). Another possibility is that we include a language configuration screen with the ZCNFG CFG file provided with programs. Simple language changes, such as the letters used for yes and no and the characters used for special functions, could be changed using this screen. ZCNFG might be able to handle more significant text items for programs with little text output.

When a program provides a lot of text output to the screen, there may be too many changes to handle with ZCNFG, and we might have to go back to the old overlay method of configuration. Our programs could come with source code for language overlays. Authors with skills in other languages might even take a stab at providing some of these overlays themselves. Otherwise, native speakers in various countries could provide the overlays.

To make it easier to use such overlays, one would want to collect all the messages that a program uses in one area rather than scattering them throughout the program as we generally do today. Rather than using SYSLIB routines like PRINT that display characters provided in-line in the code, we should use routines like PSTR that display characters pointed to by a register pair. Such source code is not as convenient to read, but it would make patching much easier (and it makes debugging easier, too). Another small detail would be to provide some extra spacing between message strings in case a message in another language is longer. Except in very rare instances, the slightly longer code that results from this approach will not be an undue burden.

New Z-Nodes

Before turning to the main technical subject, I would like to announce four new Z-Nodes that joined in the past two months. Dave Chapman in Victoria, British Columbia (604-380-0007), and Terry Bendell in Collingwood, Ontario (705-444-9234) are our two new nodes in Canada. Ewen McNeill has established the first Z-Node in New Zealand. His node is running, I believe, on a mainframe computer. Those who call in at 64-4-389-5478 in Wellington will learn about other ways to connect to the system. Finally, we also have a new node in the United States. The Kaypro Club of St. Louis has turned its system, run by sysop Bob Rosenfeld, into a Z-Node. It can be reached at 314-821-0638 on the MOSLO/24 outdial of PC-Pursuit. Give these new nodes a call and welcome them to our ranks.

An NZCOM Virtual BIOS Keyboard Buffer

Z-System's extended batch processor, ZEX, is a very powerful tool with many fascinating and effective uses. Like SUBMIT or the MS-DOS batch facility, it can carry out a sequence of commands. However, if this is all one needs, then alias scripts should almost always be used instead, since they impose no additional system overhead.

ZEX, because of all the powerful features it provides, takes up quite a lot of memory. I just ran the utility TPA.COM on my system. Invoked from the command line, it reported a TPA of 51.5K; running it under ZEX gave a figure of 47.5K. Thus ZEX cost 4K of memory: 2K for its own code and 2K because its RSX (resident system extension) locked in the 2K command processor.

The situation where ZEX has been indispensible is when one wants a script not only to invoke commands but also to provide 'interactive' input to a program. If programs can accept input on the command line, then an alias script can handle the situation, but many programs take their input only interactively.

Once ZEX is loaded, it remains in memory until all commands in its script have been totally completed. There have been numerous occasions when I have wanted to feed just a short string of characters to a program before I proceed manually. In such a case, I really hated to suffer the memory penalty of ZEX to accomplish this. In some cases, such as with my database manager, the program would not be able to run with ZEX in place.

The solution I present here provides yet another example of the power of the NZCOM virtual BIOS. I simply added a little code to my normal virtual BIOS to implement a keyboard buffer, and then I wrote a utility to fill that buffer. The BIOS I called KEYBIOS; the utility I called KEYIN. A typical command line (probably in an alias) would look like:

KEYIN string for program;PROGRAM

KEYIN fills the keyboard buffer. Then, when PROGRAM runs and requests user input, the KEYBIOS sees characters in the buffer and returns them to the program. How this works will be clearer after you see the code for KEYBIOS.

KEYBIOS

The new code contained in the virtual BIOS to support the keyboard buffer is shown in Listing 1. Here is what the code has to accomplish. When a program calls the BIOS to get a character, the virtual BIOS must first look in the keyboard buffer. If it finds a character there, then it returns that character and sets its pointer to the next character. If the keyboard buffer is empty, then the code simply passes the job on to the real BIOS, which will return a character actually typed at the keyboard.

One complication is that the BIOS also supports a function (called console status) that asks if there is a character ready without actually fetching it. We have to fake out that call, too. We follow a similar strategy. We first look in the keyboard buffer. If there is a character there, then we report back that a character is ready. If there is no character in the buffer, then we pass the job on to the console status routine in the real BIOS. It is amazingly simple!

How do we do all this? Well, I think the code in Listing 1, with all its comments, is fairly clear. There are just a couple of things I would like to elaborate on.

The code includes two items that are not actually needed by KEYBIOS for its functioning. First, the code includes a signature string, 'KEYIN', at an established location. This allows a utility, such as the KEYIN.COM program that we will discuss in more detail shortly, to determine that the appropriate VBIOS is present. Following the signature string is a byte containing the length of the buffer. KEYIN.COM needs this to know how much space is available in the buffer. Without this information, it might overfill the buffer and clobber code.

The implementation of the buffer itself is much like the multiple command line in ZCPR3. At the beginning of the buffer there is a word that contains the address of the next character available from the buffer. A null character (ASCII value zero) is used to indicate the end of the buffer.

The KEYIN Utility

Listing 2 shows a very rudimentary version of the KEYIN utility that is used to add characters to the keyboard buffer in KEYBIOS. Again, the listing with its comments is largely self-explanatory, and I will elaborate on only a few issues.

First, in view of my earlier discussion, I am embarrassed that this code does not have the facilities for language invariance that I recommended. I was tempted to put them in for the listing, but I decided that there would be too much risk of introducing an error. Before releasing the full version of the utility, I certainly will follow my own advice.

The code does try to be quite rigorous. Once the program has displayed it signon message, it checks to see if any data has been passed on the command line. If there is none, a syntax message is displayed and the program terminates. In the final version, the code should check for the standard Z-System help request "//" in the command tail.

Next, the code looks for the signature string at the proper offset in the BIOS. If it does not find it, then an appropriate message is displayed and execution terminates. Otherwise, various information from the buffer header is fetched and stored for later use.

There may be characters already in the buffer, and KEYIN is designed to retain them and to append any new input. In the final version, one might want an option switch to flush any characters that remain in the buffer. To make the work easier, a temporary buffer in KEYIN is used to form the new contents for the keyboard buffer. Therefore, we start out by copying anything in the key buffer to the working buffer.

Next we append characters from the command tail. In the final version of KEYIN, the code should include all the special string interpretation techniques used in ECHO.COM so that control characters and other special characters that cannot be entered directly in the command tail (such as semicolons) can be included.

Since the structure of KEYBIOS is such that the buffer can never be longer than 255 characters, we monitor the number of characters in the working buffer and abort if the count exceeds that value. Once the working buffer is completely filled, then we check the actual character account against the actual size of the keyboard buffer. If all the characters will fit, we move them into the buffer and set the pointer to the beginning of the buffer. KEYIN can then return control to the command processor.

If the characters will not all fit in the buffer, then we have to do something else. In the simple version in Listing 2, the program simply gives an error message and aborts. This leaves the key buffer unchanged. In a fully developed version of KEYIN, the error handler should be called so that the user can decide how to deal with the problem. If no error handler is available, then we have a more difficult problem. One course of action would be to clear the key buffer and flush the entire command line buffer (and terminate ZEX if it is running). Another possibility might be to clear the key buffer but allow the subsequent commands in the command line buffer (or ZEX) to run. The user would then have to do manually what KEYIN was trying to care of for the user.

Very Important Caveats

As it turns out, dealing with characters at the BIOS level, as we do with KEYBIOS, involves some troublesome issues. When I first got KEYBIOS running, I was surprised that I always lost the first character that I put into the buffer. That missing character then appeared the next time the command processor prompt appeared. Many of you have probably seen a mysterious character like this, and you may have recognized it as something you typed earlier that was ignored.

It is beyond the scope of this article to deal with this issue fully. Basically, it derives from a fundamental flaw in the design of CP/M. As we have seen, CP/M has a function to get a character from the BIOS and to ask the BIOS if a character is ready. But there is no way to ask what the character is without actually fetching it.

Why is that a problem? Well, the disk operating system (BDOS or equivalent) often provides special processing when the characters control-C, control-S, or control-P are pressed. Unfortunately, as we just noted, it has no way of finding out if one of those characters has been pressed without reading the next character. If the character turns out to be one of the three special ones, all is well; it processes the character.

But what if it is some other character. The BDOS would really like to say, "so sorry, not for me" and put the character back for the application program to read it. But it can't do that. So it does the next best thing. It puts it into a special buffer in the BDOS, and the BDOS plays the same kind of trick that we do in KEYBIOS. When a program asks for a character, BDOS first checks its special one-character buffer.

When programs perform all their character I/O using the BDOS or all of their I/O using the BIOS, then things will work reasonably well. However, if calls to the BDOS and BIOS are mixed, then characters can get lost in the BDOS buffer only to appear later when not wanted.

Every time I tried using KEYIN, I found that the command processor swallowed a character when it took control after KEYIN was finished. The pragmatic solution was to include a backspace character as the first one in the keyboard buffer, since the command processor would ignore it.

There are some other issues that KEYBIOS does not address. For example, some programs begin by flushing characters from the BIOS. Here is how they do it. They call CONST, and if there is a character they read it. This is repeated until CONST reports that no characters remain. Such a program will completely defeat KEYIN/KEYBIOS. Joe Wright has addressed these issues very carefully and cleverly in his IOPs (such as NuKey). KEYBIOS, however, is meant to be a very simple, minimal-memory solution, and it seems to do the job in my applications.


 

Listing 1

The code needed to implement the keyboard buffer in the NZCOM virtual BIOS.

; Everything is standard in the KEYBIOS, even the opening
; jump table

START:  JP      BOOT            ; Cold boot
WBOOTE: JP      WBOOT
        JP      CONST
        JP      CONIN
        JP      CONOUT
        JP      LIST
        JP      PUNCH
        JP      READER

; ... standard VBIOS code omitted here

; We make a small change in the auxillary IOP jumps. The
; console status (CONST) and console input (CONIN) entries
; jump to our new code.

AUXJMP:
CONST:  JP      KCONST          ; Jump to extended routine
CONIN:  JP      KCONIN          ; Jump to extended routine
CONOUT: JP      ICONOT
LIST:   JP      ILIST
PUNCH:  JP      IPUNCH
READER: JP      IREADR
LISTST: JP      ILSTST

; End of Header....  The following code is free-form and
; may be moved around if necessary.

; ---------------------------------------------------------

; The new code for the keyboard buffer is inserted right
; after the auxilliary jump table. It starts with an
; identifying signature.  Then comes the actual keyin
; buffer followed by the new code. The buffer starts with a
; word that points to the next character to fetch, if any.
; The pointer is initialized to the start of the buffer,
; which is initialized to a null to indicate the end of the
; buffer's contents.

; The following equate defines the number of actual
; characters that the keyboard input buffer can hold,
; exclusive of the terminating null and the header
; information.

kbufsize        equ     60

        db      'KEYIN'         ; Signature
        db      kbufsize        ; Length of buffer
keyptr: dw      keybuf          ; Pointer to next char
keybuf: ds      kbufsize,0      ; Fill with nulls
        db      0               ; Terminating null

; Subroutine to fetch the next key from the keyboard buffer
; and set zero flag if no character.  HL is left pointing
; to the pointer to the next character.

ktest:  ld      hl,(keyptr)     ; Get ptr to next char
        ld      a,(hl)          ; Get the character
        or      a               ; Test for null
        ret

; Extended console status code.  It checks the keyboard
; buffer and returns true if there is a character in the
; buffer.  Otherwise it passes the call on to the CBIOS.

kconst: call    ktest           ; Test for key in buffer
        jp      z,iconst        ; If none, call BIOS
        ld      a,0ffh          ; Otherwise return FF
        ret

; Extended console input code.  If there is a character in
; the keyboard buffer, then it is returned and the pointer
; is advanced.  Otherwise the CBIOS is called.

kconin: call    ktest           ; Test for key in buffer
        jp      z,iconin        ; If none, call BIOS
        inc     hl              ; Increment pointer
        ld      (keyptr),hl     ; Save it
        ret                     ; Return with key in A

; ... the rest of the stardard VBIOS code follows


 

Listing 2

Rudimentary version of the KEYIN utility for filling the keyboard buffer in KEYBIOS.

; Program:              KEYIN
; Author:               Jay Sage
; Date:                 October 1, 1991

; This program manages the keyboard inbut buffer in the
; special version of the NZCOM virtual BIOS called KEYBIOS.
; It interprets the command tail and adds the indicated
; input to the keyboard buffer (if it fits).

sigoff  equ     97H             ; Offset to signature

cr      equ     0dh
lf      equ     0ah
bell    equ     07h
tab     equ     09h

; Library routines

        extrn   z3init, eprint

keyin:
        jp      start
        db      'Z3ENV'
        db      1
env:    dw      0               ; Filled in by ZCPR33+
        dw      keyin           ; For type-4 version

; Target signature that identifies KEYBIOS.

sign:   db      'KEYIN'         ; KEYBIOS signature string
nsign   equ     $ - sign        ; Length of signature

; Signon message

signon:
        call    eprint
        db      'KEYIN, version 1.1 (10/01/91)'
        db      cr,lf
        db      0
        ret

; Show help message.

help:
        call    eprint
        db      '  Syntax: KEYIN string',cr,lf
        db      0
        ret

; We get here if the signature is not right.  Report problem
; to user and terminate.

badbios:
        call    eprint
        db      '  KEYBIOS not present!',cr,lf
        db      0
        ret

start:

; Initialize environment

        ld      hl,(env)
        call    Z3init

; Display signon message

        call    signon

; See if there is a command tail.

        ld      hl,80h          ; Point to tail buffer
        ld      a,(hl)          ; Get count
        or      a               ; Test for zero
        jr      z,help          ; If zero, show help screen

; Make sure that the KEYBIOS is running by checking for the
; signature.

        ld      hl,(1)          ; Get warmboot address
        ld      de,sigoff-3     ; Offset to signature from
                                ; ..warmboot entry
        add     hl,de
        ld      de,sign         ; Point to signature
        ld      b,nsign         ; Length of signature
sigtest:
        ld      a,(de)
        cp      (hl)
        jr      nz,badbios      ; Jump if no match
        inc     hl              ; Bump pointers
        inc     de
        djnz    sigtest         ; Test whole signature

; Now we are ready for the real task

        ld      a,(hl)          ; Save the size of
        ld      (kbufsize),a    ; .. the keyboard buffer
        inc     hl              ; Point to pointer
        ld      (kptradr),hl    ; Save its address
        ld      e,(hl)          ; Get its value into DE
        inc     hl
        ld      d,(hl)
        inc     hl              ; Point to start of buffer
        ld      (keybuf),hl     ; Save the address

        ld      hl,buffer       ; Point to working buffer
        ld      bc,0            ; Initialize count

; Copy any remaining contents of keyboard buffer to working
; buffer

copy1:
        ld      a,(de)          ; Get next character from
                                ; ..key buffer
        ld      (hl),a          ; Write to working buffer
        or      a               ; Set flag
        jr      z,copy2         ; If null, break out of loop
        inc     de              ; Otherwise bump pointers
        inc     hl
        inc     c               ; Increment count
        jr      copy1           ; Loop back for next char

; Now we have to add the new characters from the command
; line.  HL still points to working buffer. This code needs
; to be extended with the full functionality of ECHO.COM so
; that control characters and other special characters (such
; as semicolons) can be entered.

copy2:
        ld      de,82h          ; Point to second char (if
                                ; ..any) in command tail
copy2a:
        ld      a,(de)          ; Get the character
        ld      (hl),a          ; Store it in working buffer
        or      a               ; Set flag
        jr      z,fillkey       ; If null, we're done
        inc     de              ; Otherwise, bump pointers
        inc     hl
        inc     c               ; Increment the count
        jr      z,overflow      ; If we reach zero,
                                ; ..definitely too many
        jr      copy2a          ; Else loop back for more

; Now we have to copy the contents of the work buffer back
; into the keyboard input buffer.

fillkey:
        ld      a,(kbufsize)    ; Make sure there is room
        cp      c
        jr      c,overflow      ; Too many characters

        ld      de,(keybuf)     ; Get start of buffer
        ld      hl,(kptradr)    ; Address of pointer
        ld      (hl),e          ; Set pointer to beginning
        inc     hl
        ld      (hl),d
        ld      hl,buffer       ; Point to working buffer
        inc     bc              ; Adjust counter to include
                                ; ..trailing null
        ldir                    ; Copy it all
        ret                     ; We're done

; We get here if the keyboard buffer is too small to hold
; all the characters. For the moment we just give a message,
; but the code should call the error handler if possible. If
; there is no error handler, then the MCL should probably be
; cleared.

overflow:
        call    eprint
        db      'Too many characters for '
        db      'keyboard input buffer.'
        db      cr,lf
        db      0
        ret

; Data items

        dseg

kbufsize:       ds      1       ; Size of keyboard buffer
kptradr:        ds      2       ; Address of next-char ptr
keybuf:         ds      2       ; Address of beg of buffer
buffer:         ds      257     ; Working buffer

        end

 


[This article was originally published in issue 53 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 1991 Socrates Press and respective authors]