|
CHAPTER 13. EDITOR
In a Forth computer, new definitions are stored in the dictionary in a compiled form. The source text is not saved. Although there are many different ways to recover textual information from the compiled definitions, to 'de-compile' a definition is not the best way to write and edit Forth definitions. As we have discussed in Chapter 10 on the virtual memory, Forth uses the disk to store source text which can be compiled very easily using the word LOAD . To enter source text into the disk memory and to modify them repeatedly during program development and testing, a text editor is indispensable. As in any other language processor, the editor is the principal interface between a programmer and the computer. A good editor makes the programming tasks easier, and in some rare cases enjoyable. As of now, figForth has yet to have a standardized text editor. In the figForth model, however, there was included a sample text editor by Bill Ragsdale. I will discuss this particular editor in this Chapter. A text editor provides important and extensive examples in using Forth language to handle texts and strings. It is worthwhile for a serious student of the Forth language to go through these examples carefully, to learn techniques in string manipulations. To facilitate text editing, texts on disk are organized in blocks of 1024 bytes (a unit of screen). Each screen is divided into 16 lines of 64 characters each. A screenful of text thus arranged fits comfortably on the screen of an ordinary CRT terminal, hence the name 'screen'. The text on a screen is most conveniently accessed by lines. A string within a line can be searched and its location indicated by a screen cursor for editing actions, like inserting or deleting characters. A text editor generally performs two quite distinguishable tasks--line editing and string editing. In this figForth sample editor, words are defined separately for these two tasks.
13.1. LINE EDITOR In the text editor, a screenful of text is maintained in the disk buffers, or the screen buffer. The screen number which denotes the physical location of this screen of text on disk is stored in a user variable SCR. The cursor location in this screen buffer is stored in another user variable R# . Text to be put into the screen buffer or deleted from the screen buffer is temporarily stored in the text buffer area pointed to by the word PAD, which returns the memory address 68 bytes above the dictionary pointer DP. PAD is used as a 'scratch pad' during editing processes, holding text for the screen buffer or strings to be matched with the text in the screen buffer. Most of the editor definitions have single character names to ease typing during editing. Some of these simple names cause conflects with the names of other definitions defined in the FORTH vocabulary. It is thus advantageous to group all the editing definitions into a separate vocabulary called EDITOR. The EDITOR vocabulary is defined as:
VOCABULARY EDITOR IMMEDIATE This phrase creates the EDITOR vocabulary which is linked to the trunk FORTH vocabulary. EDITOR when called will make EDITOR the CONTEXT vocabulary, so that definitions defined in EDITOR will be readily accessible in editing screens of text. The phrase
EDITOR DEFINITIONS makes EDITOR vocabulary also the CURRENT vocabulary. In this way new definitions will be added to the EDITOR vocabulary instead of being treated as regular definitions adding to the FORTH vocabulary. Two basic utility words are used by the editor to perform the line editing functions. TEXT moves a line of text from the input stream to the text buffer area of PAD. The word LINE computes the line address in the screen buffer. Text lines of 64 characters can then be transferred from PAD to screen buffer or vice versa. We shall first present these two words before getting into the line editing commands.
: TEXT c --
Move a text string delimited by character c from the dictionary buffer (word buffer) into PAD, blank- filling the remainder of PAD to 64 characters.
HERE Top of dictionary, beginning of word buffer. The text interpreter puts the text string here. C/L 1+ BLANKS Fill word buffer with 65 blanks. WORD Move the text, delimited by character c, from the input stream to the word buffer. PAD Address of the text buffer. C/L 1+ CMOVE Move the text, 64 bytes of text and 1 length byte, to PAD. ; ¡@ : LINE n -- addr
Leave address of the beginning of line n in the screen buffer. The screen number is in SCR. Read the disk block from disk if it is not already in the disk buffers.
DUP FFF0H AND Make sure n is between 0 and 15. 17 ?ERROR If not, issue an error message. SCR @ Get the screen number from SCR . (LINE) Read the screen into screen buffer which is composed of the disk buffers. Compute the address of the n'th line in the screen buffer and push it on stack. DROP Discard the character count left on stack by (LINE) . Only the line address is left on stack now. ; ¡@ : -MOVE addr n --
Copy a line of text from addr to n'th line in the current screen buffer.
LINE Get the line address in screen buffer. C/L CMOVE Move 64 characters from addr to line n in screen buffer. UPDATE Notify the disk handler this buffer has been modified. It will be written back to disk to update the disk storage. ; ¡@ : H n --
Copy n'th line to PAD. Hold the text there ready to be typed out.
LINE Get the line address. PAD 1+ Starting address of text in PAD . C/L DUP PAD C! Put 64 in the length byte of PAD . CMOVE Move one line. ; ¡@ : S n --
Spread n'th line with blanks. Down shift the original n'th and subsequent lines by one line. The last line in the screen is lost.
DUP 1- Lower limit of lines to be moved. 0EH 14, the last line to be shifted down. DO I LINE Get I'th line address I 1+ Next line -MOVE Downshift one line. 1 +LOOP Decrement loop count and repeat till done. E Erase the n'th line. ; ¡@ : D n --
Delete the n'th line. Move subsequent lines up one line. The delete line is held in PAD in case it is still needed.
DUP H Copy the n'th line to PAD. 0FH The last line. DUP ROT Get n to top of stack. DO I 1+ LINE Next line to be moved. I -MOVE Upshift by one line. LOOP E Erase the last line. ; ¡@ : E n -- ¡@ Erase the n'th line in the screen buffer by filling with 64 blanks.
LINE Line address. C/L BLANKS Fill with blanks. UPDATE ; ¡@ : R n -- ¡@ Replace the n'th line with text stored in PAD. ¡@ PAD 1+ Starting address of the text in PAD. SWAP -MOVE Move text from PAD to n'th line. ; ¡@ : P n --
Put following text on line n. Write over its contents.
1 TEXT Accept the following text of C/L characters or till CR to PAD. R Put the text into line n. ; ¡@ : I n -- Insert text from PAD to n'th line. Shift the original n'th and subsequent lines down by one line. The last line in the screen is lost.
DUP S Spread line n and pad with blanks. R Move PAD into line n. ; ¡@ : CLEAR n -- ¡@ Clear the n'th screen by padding with blanks. ¡@ SCR ! Store screen number n into SCR . 10H 0 DO Erase 16 lines FORTH I Get the loop count from return stack. I was redefined by the editor to insert line into a screen. To call the I which gets the loop count, FORTH must be called to make the trunk FORTH vocabulary the CONTEXT vocabulary, which is searched first to get the correct I. This demonstrates the use of vocabularies. EDITOR E Set the CONTEXT vocabulary back to EDITOR vocabulary to continue editing texts. E will erase the I'th line. LOOP ; ¡@ : COPY n1 n2 --
Copy screen n1 in drive 0 to screen n2 in drive 1. This is accomplished by reading blocks in screen n1 to disk buffers and changing block numbers to those associated with screen n2. The disk buffers are then flushed back to disk.
B/SCR * First block in screen n2. OFFSET @ + Add block offset for drive 1. SWAP B/SCR * First block in screen n1. B/SCR OVER + Last block number + 1. SWAP DO Go through all blocks in screen n1. DUP Copy block number in screen n2. FORTH I Current block number in screen n1 as the loop count. BLOCK Read the block from screen n1 to disk buffer. 2 - ! Store the block number in screen n2 into the first cell of the disk buffer, which contains the disk block number. This tricks the system to think the block is in the screen n2. 1+ T UPDATE Set update bit in disk buffer to be flushed back to disk. LOOP DROP Discard the block number on stack. FLUSH Write all disk buffers containing data from screen n1 back to screen n2, because the block numbers were switched. ;
13.2. STRING EDITOR The above words belong to what might be called a line editor, which handles the text by whole lines. The line editor is convenient in inputting lines of texts. However, if some mistakes are discovered or only a few characters in a line need to be changed, the line editor is not suitable because one would have to retype the whole line. Here, a string editor is more effective. The string editor uses a variable R# as a cursor pointing to a character in a string which can be accessed by the string editor most easily. The string editor must be able to search a line or the entire screen for a specified string and point the cursor to this string. It must have means to delete and modify characters neighboring the cursor. A colon definition MATCH is used to search a range of text for a specified string and move the cursor accordingly. MATCH and a few utility words are used here to build up the word set involved in the string editor.
: MATCH addr1 n1 addr2 n2 -- f n3
The text to be searched begins at addr1 and is n1 bytes long. The string to be matched begins at addr2 and is n2 bytes long. The boolean flag is true if a match is found. n3 is then the cursor advancement to the end of the found string. If no match is found, f will be false and n3 be 0.
>R >R 2DUP Duplicate addr1 and n1. R> R> 2SWAP Move the copied addr1 and n1 to the top of the stack.
OVER + SWAP Now the stack looks like: DO Scan the whole source text. 2DUP Duplicate addr2 and n2. FORTH I The loop index points to source text. -TEXT Is the source text here the same as the string at addr2 ? IF Yes, the string is found in the text. >R 2DROP R> Discard n1 and addr2 on the stack. - I SWAP - Offset to the end of the found string. 0 SWAP Put a boolean underneath. 0 0 LEAVE Put two dummy zeros on the stack and prepare to leave the loop. THEN LOOP No match this time. Loop back. 2DROP Discard garbage on the stack. SWAP 0= SWAP Correct the boolean flag upon exit. ; ¡@ : -TEXT addr1 n addr2 -- f
If the strings at addr1 and addr2 match to n characters, return a true flag. Otherwise, return a false flag.
SWAP -DUP IF If n1 is zero, bypass the tests. OVER + SWAP ( addr1 addr2+n1 addr2 -- ) DO Scan the string at addr2 . DUP C@ Fetch a character from the first string. FORTH I C@ - Equal to the corresponding character in the second string? IF 0= LEAVE Not the same. Leave the loop. ELSE 1+ THEN Continue on. LOOP ELSE DROP 0= n is zero . Leave a false flag. Neither address may be zero. THEN ;
The 32-bit double number instructions used in MATCH and -TEXT should be defined in the FORTH trunk vocabulary as following:
: 2DROP d -- ¡@ Discard two numbers from the stack. ¡@ DROP DROP ; ¡@ : 2DUP d -- d d ¡@ Duplicate a double number. ¡@ OVER OVER ; ¡@ : 2SWAP d1 d2 -- d2 d1 ¡@ Bring the second double number to the top of the stack. ¡@ ROT >R Save top half of the second number. ROT R> Move bottom half and restore top half. ; ¡@ : TOP -- ¡@ Move the cursor to home, top left of the screen.
0 R# ! Store 0 in R# , the cursor pointer. ; ¡@ : #LOCATE -- n1 n2 ¡@ From the cursor pointer R# compute the line number n2 and the character offset n1 in line number n2.
R# @ Get the cursor location. C/L /MOD Divide cursor location by C/L. Line number is the quotient and the offset is the remainder. ; ¡@ : #LEAD -- addr n ¡@ From R# compute the line address addr in the screen buffer and the offset from addr to the cursor location n.
#LOCATE Get offset and line number. LINE From line number compute the line address in screen buffer. SWAP ; ¡@ : #LAG -- addr n
From R# compute the line address addr in the screen buffer and the offset from cursor location to the end of line.
#LEAD Get the line address and the offset to cursor. DUP >R Save the offset. + The address of the cursor in screen buffer. C/L R> - The offset from cursor to end of line. ; ¡@ : M n --
Move cursor by n characters. Print the line containing the cursor for editing.
R# +! Move cursor by updating R#. CR SPACE Start a new printing line. #LEAD TYPE Type the text preceding the cursor. 5FH EMIT Print a caret (^) sign at the cursor location. #LAG TYPE Print the text after the cursor. #LOCATE . DROP Type the line number at the end of text. ;
: T n --
Type the n'th line in the current screen. Save the text also in PAD.
DUP C/L * Character offset of n'th line in the screen. R# ! Point the cursor to the beginning of n'th line. H Move n'th line to PAD. 0 M Print the n'th line on output device. ;
: L -- ¡@ Re-list the current screen under editing.
SCR @ LIST List the current screen. 0 M Print the line containing the cursor. ; ¡@ : 1LINE -- f
Scan a line of text beginning at the cursor location for a string matching with one stored in PAD. Return true flag if a matching string is found with cursor moved to the end of the found string. Return a false flag if no match.
#LAG PAD COUNT Prepare addresses and character counts to that as required by MATCH . MATCH Go matching. R# +! Move the cursor to the end of the matching string. ; ¡@ : FIND --
Search the entire screen for a string stored in PAD. If not found, issue an error message. If found, move cursor to the end of the found string.
BEGIN 3FFH R# @ < Is the cursor location > 1023? IF Yes, outside the screen. TOP Home the cursor. PAD HERE C/L 1+ CMOVE Move the string searched for to HERE to be typed out as part of an error message. 0 ERROR Issue an error message. ENDIF 1LINE Scan one line for a match. UNTIL ; ¡@ : DELETE n --
Delete n characters in front of the cursor. Move the text from the end of line to fill up the space. Blank fill at the end of line.
>R Save the character count. #LAG + End of line. FORTH R - Save blank fill location. #LAG R MINUS R# +! Back up cursor by n characters. #LEAD + New cursor location. SWAP MOVE Move the rest of line forward to fill the deleted string R> BLANKS Blank fill to the end. UPDATE ; ¡@ : N --
Find the next occurrence of the text already in PAD.
FIND Matching. 0 M If found, type out the whole line in which the string was found with the cursor properly displayed. ; ¡@ : F -- ¡@ Find the first occurrence of the following text string.
1 TEXT Put the following text string into PAD . N Find the string and type out the line. ; ¡@ : B -- ¡@ Back the cursor to the beginning of the string just matched.
PAD C@ Get the length byte of the text string in PAD . MINUS M Back up the cursor and type out the whole line. ;
: X --
Delete the following text from the current line.
1 TEXT Put the text in PAD . FIND Go find the string. PAD C@ Get the length byte of the string. DELETE Delete that many characters. 0 M Type the modified line. ;
: TILL --
Delete all characters from cursor location to the end of the following text string.
#LEAD + The current cursor address. 1 TEXT Put the following text in PAD . 1LINE Scan the line for a match. 0= 0 ?ERROR No match. Issue an error message. #LEAD + SWAP - The number of characters to be deleted. DELETE Delete that many characters and move the rest of line to fill up the space left. 0 M Type out the new line. ; ¡@ : C --
Spread the text at cursor to insert the following string. Character pushed off the end of line are lost.
1 TEXT PAD COUNT Accept text string and move to PAD . #LAG ROT OVER MIN >R Save the smaller of the character count in PAD and the number of characters after the cursor. FORTH R Get the smaller count R# +! Move the cursor by that many bytes R - >R Number of characters to be saved. DUP HERE R CMOVE Move the old text from cursor on to HERE for t emporary storage. HERE #LEAD + R> CMOVE Move the same text back. Put at new location to the right, leaving space to insert a string from PAD . R> CMOVE Move the new string in place. UPDATE 0 M Show the new line. ;
¡@ |