The Computer Journal, Issue 46

Z-System Corner

© Jay Sage

Reproduced with permission of author and publisher.

Although I have not yet finished the treatment of MEX, I am going to start a new subject this time: the ZMATE macro text editor. During the past two months I have been working on a number of code patches to MEX-Plus to fix some problems and to add some new features that I wanted or needed. That work is not complete, so I have decided to hold off on a MEX update until next time. As usual, I do have a few miscellaneous items to bring to your attention.

Pieces of Eight

First, I would like to put in a plug for the "Pieces of Eight" magazine (POE) from the Connecticut CP/M Users' Group (CCP/M). CCP/M recently decided to begin addressing a national audience and not just their local members. Even if you cannot attend their meetings, the subscription to POE that your $15 annual dues brings you is alone worth the price.

POE is a very nice complement to TCJ. I don't think I will offend CCP/M by saying that their magazine is far less serious than this one. There is some solid technical content, but the emphasis is definitely on the human side of computing. It is really fun to read, and not just by us computer nuts but by our entire families as well.

The July, 1990, issue has a feature article on the Trenton Computer Festival held in April. On the cover is a picture taken there showing me, Bridger Mitchell, Al Hawley, and Cam Cotrill. (In case you might be questioning my motives, their flattering me by putting my picture on the cover provided only a fraction of the inspiration for this plug!)

Inside are more pictures: Rob Friefeld (LSH, SALIAS), Carson Wilson (ZDE, ZSDOS), Hal Bower (ZSDOS), Bruce Morgen (MEX+2Z and lots of program patches), Howard Goldstein (our alpha tester and bug catcher and fixer extraordinaire), and quite a few others. As you can see, Trenton drew Z-Team members and enthusiasts from all over the country! If you want to learn more about the festival, sign up for POE. Send dues to Tom Veile, 26 Slater Ave., Norwich, CT 06360.

A Patch for The Word Plus

Some time ago I published here a set of ARUNZ aliases for automating the use of The Word Plus spell checker. Well, Richard Swift liked them just fine, but it then annoyed him that he still had to hit a carriage return to get past TW's prompt about whether the configurati on was correct. He wanted TW to get right to work.

At first I didn't really see why he was making such a fuss about such a little thing. Then it began to eat at me, too. This one little thing was standing in the way of complete automation.

Well, it took a good bit of poking around in the TW.COM code, but in the end it was quite easy to patch around this annoying prompt. First I located where the code that put up the prompt began, and then I found where things picked up again after it. A simple jump instruction at the beginning to skip over it should do the trick, I thought.

Unfortunately, it was not quite that simple. As Bruce Morgen had described earlier in an issue of his NAOG newsletter, the programs in The Word Plus suite perform some simple internal checking to make sure the file is not corrupted and has loaded successfully. Nice of those folks, but after I put in my patch, the code looked corrupted. I could have figured out the new checksum value and stuck it into the testing code, but it was easier just to bypass the checking entirely.

At first I put the changes into a patch file that would be overlaid onto the original code. Then, however, I decided that there was no real need to make the change permanently. When running TW manually, one would probably want the prompt to appear so that one would have the option of changing the setup. So, my solution was the old GET/POKE/GO technique introduced by Bruce Morgen (boy does that name keep coming up!).

My original ARUNZ alias had a command of the form

tw:tw <file> <dictionary>

I just replaced that by

/TWPAT <file> <dictionary>

and wrote the new alias TWPAT with the command lines

get 100 tw:tw.com load TW.COM
poke 103 c3 3b 01 patch to jump over code test
poke 395 c3 2a 04 patch to jump over prompt
go $* run the patched code

Now I could invoke the patched TW whenever I wanted by using the command TWPAT instead.

The ZMATE Text Editor

Now for the main topic of this column, the first in a series of articles on ZMATE. This one will be just an introduction and will cover only its design philosophy and mode of operation. Next time I will start to describe its language in detail.

Interpreters and Compilers

A casual user would classify ZMATE as an application program, and more precisely as a text editor or wordprocessor. In its soul, however, it is really a high-level programming language. In some ways it is similar to the familiar BASIC interpreter.

Like almost all the programming languages most people work with, BASIC is oriented toward numerical computation. For example, at the system prompt one can enter a command such as

print ( n1 + n2 ) * n3

BASIC will then retrieve the values associated with the variables N1, N2, and N3, substitute them into the mathematical expression, evaluate the expression, and print the result to the screen.

BASIC also allows one to write programs comprising a series of numbered statements such as:

100 n1 = 10
110 n2 = 5
120 n3 = 3
130 print ( n1 + n2 ) * n3

When the immediate command "RUN" is entered, the entire sequence of commands is carried out, and the number 45 appears on the screen.

One could write a program to do the same thing using assembly language, the native language of a computer. However, a high-level language like BASIC makes it far easier to generate the required instructions. This is especially true when we are dealing with floating point numbers, or when we are using array variables or advanced mathematical (trig and log) functions.

When the BASIC interpreter we described above is told to "RUN", it processes the program statements one at a time. First it analyzes a statement to determine the procedures required to perform the specified function. Then it calls routines that execute those procedures. This means that when a BASIC statement appears in a loop, the analysis has to be repeated each time the statement is executed.

A compiler provides an alternative approach. The compiler can be thought of as an automatic assembly language program writer. You write your program using the commands of the high-level language, and then the compiler converts them into an assembly language program for you.

Some compilers generate actual assembly language source code that you then have to assemble. The PASCAL Z compiler, for example, worked this way. This approach makes program development slower but allows you to fine-tune the code if you so desire. Other compilers, such as Turbo Pascal, generate only the machine code (COM) files. Some compilers, such as BDS C, follow a two-step process, but the intermediate code is not standard assembly code.

A compiler, as you might guess, has the advantage of execution speed, since the high-level language statements have to be analyzed and converted into machine code only once, even when they are executed repeatedly in a loop. Also, more complex programs that need more working memory can be accommodated, since the code that figures out how to process the high-level language statements does not have to be in memory when the final program is run.

On the other hand, an interpreter offers many advantages that may make it well worth giving up some speed. Programs are much easier to develop with an interpreter for several reasons. First, you can execute them immediately, without having to go through the extra step of compilation (and possibly assembly and linkage) before execution. Second, the programs can be run line by line, and you can watch what is happening and catch errors more easily.

There are also some things that an interpreter can do that a compiler generally cannot. For example, suppose you are working with an array variable (a variable that holds a collection of values, not just a single value). With a compiler, you would have to specify the size - or at least a maximum size - of the array at the time the program is compiled so that the compiler can allocate enough memory for it. With an interpreter, this is not necessary. It does not have to allocate the memory until the variable is first referenced. As a result, it is quite acceptable for its size to be determined by computations performed earlier in the program.

ZMATE as Interpreter

ZMATE is, in a way, like the BASIC interpreter, except that its intrinsic high-level language functions (we will call these 'primitives') are aimed at text processing rather than number processing. Just as BASIC has some text-processing primitives (e.g., string variables and functions), so ZMATE has some numerical functions, but it is the text-manipulation primitives that are emphasized and richly developed.

If your past experience has been confined to the usual programming languages - BASIC, FORTRAN, PASCAL, C, etc. - you probably have trouble picturing what a text-processing language would look like. Here are some examples to help convey the concept.

While most variables in BASIC contain either single numbers or arrays of numbers, ZMATE has 'variables' called buffers that contain pieces of text. Primitives allow reading disk files into these buffers or writing text from the buffers out to files.

Each buffer has two pointers. One is called the cursor. It is where most ZMATE primitives perform their operation. The other pointer is called a tag, and together with the cursor it defines a block of text for some block-operation primitives.

A whole set of ZMATE primitives deals with cursor motion. The cursor can be moved forward and backward in the buffer by units of characters, words, paragraphs, or the whole buffer. For example, you can tell the cursor to backup by three words or go forward two paragraphs.

This highlights the difference between a number-processing and a text processing language. BASIC supports string variables that can contain a line of text, but it does not know about words and paragraphs. The user would have to write complex code to deal with these text concepts. As a text-processing language, ZMATE provides the code for this as part of the language primitives.

Other ZMATE primitives search for strings and compare strings or characters. Text can be inserted and deleted. Blocks of text can be moved between buffers for cutting and pasting operations. All the usual control primitives are provided to allow testing, conditional operations, and looping.

There are also special facilities for handling text formatting and text input from the keyboard. Soft carriage returns can be placed into text automatically, and various kinds of indentation and margin control are provided. These functions make it easy to write a wordprocessor in the ZMATE language.

How the ZMATE Language is Used

In our examples above, we saw that a BASIC statement can be entered for immediate execution. ZMATE, too, allows this. We also saw that BASIC programs containing a sequence of statements can be prepared for later execution. The same is true of ZMATE. In fact, ZMATE can have a number of programs loaded and ready for execution at the same time, and one program can call another as a subroutine.

ZMATE allows its language to be used in one other very special way. Programs that are permanently stored in the ZMATE COM file can be bound to a key or sequence of keys. Then when that key sequence is typed at the keyboard, the program is automatically executed. ZMATE commands executed this way are called "instant commands."

As an example, suppose we write this little ZMATE program:

100 put the tag where the cursor is now
110 move the cursor forward one word
120 delete the block (tag-to-cursor)
130 stop

[I am using a BASIC-like pseudo-language for this example. The actual ZMATE language, which we will get into next time, is not at all like this.] If we now bind this program to the '^T' (control-T) key, we will have implemented the WordStar delete-word function.

This should give you a sense now of how ZMATE can be used to implement a text editor or wordprocessor. Although ZMATE comes with some standard programs and key bindings, you can change the standard programs, can attach your own new programs, and can change the key bindings. Thus you have extensive control over the way ZMATE works and can add any functions you like to it.

The ZMATE Screen

The normal appearance of the screen while ZMATE is running is shown in Fig. 1. In fact, I captured this screen using the BGii 'screen' command while writing this article. I have made a few changes to adapt it to the TCJ format. The real screen is the full width of the terminal, usually 80 characters, and the full length, usually 24 lines. I have reduced both of these sizes.

/------------------------------------------------------------------\
| TCJ: TCJ:TCJ46.WS,TCJ:TCJ46.$$$ buf=T arg=0 |col = 18 |
| INSERT MODE |line= 204 |
| ----------------------------------------------------|free= 13454 |
| 100 put the tag where the cursor is now< |
| 110 move the cursor to the next word< |
| 120 delete the block (tag-to-cursor)< |
| 130 stop< |
| < |
| [I am using a BASIC-like pseudo-language for this example. The |
| actual ZMATE language, which we will get to next time, is not at |
| all like this.] If we now bind this program to the ^T key, we |
| will have implemented the WordStar delete-word function.< |
| < |
| < |
| The ZMATE Screen< |
| < |
| The normal appearance of the screen while ZMATE is running is |
| shown in Fig. 1. In fact, I captured this screen using the BGii |
| 'screen' command while writing this article. I have made a few |
| changes to adapt it to the TCJ format. The real screen is the |
\------------------------------------------------------------------/

Fig. 1.
This is a snapshot of the ZMATE screen approximately as it appeared while I was writing this column.

All but the top three lines are used for the display of text. In the original PMATE, only one buffer could be viewed. With ZMATE, Bridger Mitchell made it possible to look at two buffers or at two sections of one buffer at the same time. By the way, the '<' characters at the ends of some lines in Fig. 1 indicate hard carriage returns. The other lines end with soft returns. If one changes the margins, the text instantly readjusts.

At the left of the top line, ZMATE shows the currently logged directory, the file that is open for input, and the file that is open for output. In this case, the output file is a temporary file, TCJ:TCJ46.$$$. When one closes the edit file, the input file will be given a file type of BAK, while the temporary output file name will be changed to the original input file name.

In the center of the top line, two status variables are displayed. The first tells us which buffer is currently being edited (there are 12 of them); the second is a numerical value returned by the last ZMATE command that was performed. That value can convey information to the user or can be used for testing in a program.

At the right edge of the screen, three other status variables are displayed. The position of the cursor is given as a column and line number. The third value tells how much free memory is available for additional text.

The second line in Fig. 1 shows the mode status "INSERT MODE". ZMATE can run in three modes: insert, overtype, and command. In command mode, the second line is where the user enters ZMATE program statements for immediate execution. After a command is entered, it is executed by pressing the escape key (ESC).

The most recently entered command remains on the command line and can be executed again by pressing ESC again. Other instant command functions can be executed in between. This gives ZMATE wonderful power. It is one of the things that the author of Vedit - which began, I believe, as a PMATE clone - never understood and is one of the reasons why I have always found Vedit unacceptable as an editor.

Here is an example of how this facility can be used. Suppose we want to change a number of words to upper case. Assuming this is not already defined as a built-in editor function, we write a command line with code that changes all letters of the word containing the cursor to upper case. Then we press ESC, and the current word is converted. Suppose the next word we want to convert is down two lines and over three words from where we are now. Assuming WordStar-like bindings, we could press "^X^X^F^F^F". Then we can press ESC again to convert that word. In a sense, ZMATE commands typed on the command line become bound temporarily as an instant command on the ESC key.

In insert mode, we are effectively running a ZMATE program that asks the user to press keys, which are then inserted into the text. Overtype mode is the same except that the new characters replace the ones previously under the cursor. In both insert and overtype mode, instant commands operate just as in command mode. That is, key sequence binding are still fully in effect.

Key Bindings

This is a good time to make the role of key bindings more explicit. With ZMATE, one should think of no keys as producing direct input to the editor. All keys have to be bound to some function if they are to have any effect at all.

ZMATE has three sources for the functions that are bound to the keys. One of these comprises functions that produce ASCII characters. Most people would take it for granted that pressing the 'A' key would produce an 'A', but this is not necessarily so in ZMATE. This makes it quite easy to implement a non-standard keyboard layout, such as the Dvorak layout.

The bindings, moreover, are not one-to-one. You can have a number of different key sequences bound to the same function. So, if you want to have two ESC keys, you can bind a second keyboard key to the "produce-an-ESC-character" function as well. And I want to emphasize that these bindings are of sequences of one or more keys (up to some configurable maximum number) to any single function.

The key bindings are defined in a table with the following structure. Each entry, except the last, comprises a byte with a function number followed by the sequence of ASCII key codes bound to that function. The sequences are all exactly the maximum length specified in the configuration. If the defined sequence is shorter than this, null bytes (value 0) are used as filler. The end of the table is indicated by a value of FF hex in the function-number position.

The character-producing functions have numbers from from 1 to 127 inclusive. I am not sure about function 0. Putting a null into text is generally not allowed, as null is used to separate the buffers. If no explicit binding is specified for a single ASCII character in the range 1 to 127, it is by default bound to the function that produces that character. Thus the key sequence 'A' (a single press of the 'A' key) is bound to the "produce-an-A" function if it does not appear in the key binding table.

This direct mapping of ASCII characters is not, as I said above, required. For example, I use the tilde and back-apostrophe as lead-in keys to other sequences (some people would call these keys 'meta' keys). In order to be able to enter these two characters easily into text, I bind the sequence "~~" (two tildes in a row) to the "produce-a-tilde" function and "``" to the "produce-a-back-apostrophe" function.

The second set of functions, numbered from 128 to 191, is implemented in ZMATE's internal code. However, all but a few of them are in fact performed by macro statements in the standard ZMATE language. In PMATE there was no way to modify these; in ZMATE, they have been placed at the end of the code and referenced in a way that allows the overlay configuration patch to redefine these functions freely.

By my count, of the 64 functions of this type, all but 12 are defined by macro program statements. In some cases it is obvious why some are not. For example, there is a function for setting a repeat count that applies to the next command entered. There is also a function that aborts the execution of any macro. These functions would not make sense in the macro language itself.

For some functions it is not so clear why they are not implemented as macros. For example, there is a function to pop from the "garbage stack" the most recently deleted block of text. This is something that cannot presently be done in the command language, but I don't see why it couldn't or shouldn't be.

Then there are several functions for which there exist macro commands that perform the function. Switching to insert, overtype, or command mode are examples. I don't know why they are implemented directly in code rather than in the macro language.

The final set of functions is numbered from 192 to 254. A hexadecimal FF (255 decimal) is used to mark the end of the binding table, so this function number is not allowed. These functions are associated with what is called the "permanent macro area" or PMA in ZMATE.

The PMA is a text block that is permanently stored along with the ZMATE code and can be moved to and from editing buffers. It contains a series of macro definitions, each one introduced by a control-X character followed by the one-character name for the macro and then the program. Functions 192 to 254 correspond to macros whose one-character name is 160 less than the function number, i.e., from space (32) to caret (94). Because the PMA can be edited from within ZMATE, these instant-command functions can be modified quite easily. It might even be possible for one of these macros to be modified by another macro!

Permanent macros are not limited to the names that can be bound to key sequences. The maximum number of permanent macros would be 256 (0 to 255). However, (1) the value 0 is not allowed, (2) upper-case and lower-case letters are equivalent, and (3) not all characters with the high bit set are distinct from the same character without the high bit set (though some are different). In all, by my count there are 160 possible permanent macro names, of which 63, as mentioned earlier, can be bound to keys. The others can be invoked from the command line or from other macros.

Well, this completes the discussion of ZMATE for this time. Next time I will present its command language in detail.


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