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:
( addr1 n1 addr2 n2 addr1+n1 addr1 -- )

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. 

;

 

¡@