R65 TCJ46.WS The ZMATE Text Editor Jay Sage 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 configuration 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 I just replaced that by /TWPAT 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 back up 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. ================================================================= R70 /------------------------------------------------------------------\ | 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 | \------------------------------------------------------------------/ R65 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. commands. The Word ======== Patch developed for Swift to bypass the prompt. See TWPAT.Z80 in¨ TW: directory. Plug for POE magazine =====================