The Interlisp Editor

[This page is adapted from Interlisp Reference Manual*.]

Suppose we are editing the following incorrect definition of append (user input shown in WHITE):

[LAMBDA (X)
  Y
  (COND
    ((NUL X)
     Z)
    (T (CONS (CAR)
             (APPEND (CDR X Y]

We call the editor via the function editf:

_EDITF(APPEND)
edit
*

The editor responds by typing edit followed by *, which is the editor's prompt character, i.e., it signifies that the editor is ready to accept commands.

At any given moment, the editor's attention is centered on some substructure of the expression being edited. This substructure is called the current expression, and it is what te user sees when he gives the editor the command P, for print. Initially, the current expression is the top level one, i.e., the entire expression being edited. Thus:

*P
(LAMBDA (X) Y (COND & &))
*

Note that the editor prints the current expression as though printlevel were set to (2 . 20), i.e., sublists of sublists are printed as &, tails of long lists preinted as --. The command ? wil print the current expression as though printlevel were 1000.

*?
(LAMBDA (X) Y (COND ((NUL X) Z) (T (CONS (CAR) (APPEND (CDR X Y))))))
*

and the command PP will prettyprint the current expression.

A positive integer is interpreted by the editor as a command to descend into the correpondingly numbered element of the current expression. Thus:

*2
*P
(X)
*

A negative integer has a similar effect, but counting begins from the end of the current expression and proceeds backward, i.e., -1 refers to the last element in the current expression, -2 the next to the last, etc. For either positive or negative integer, if there is no such element, an error occurs, the editor types the faulty command followed by a ?, and then another *. The current expression is never changed when a command causes an error. Thus:

*P
(X)
*2

2 ?
*1
*P
X
*

A phrase of the form "the current expresseion is changed" or "the current expression becomes" refers to a shift in the editor's attention, not to a modification of the structure being edited.

When the user changes the current expression by descending into it, the old current expression is not lost. Instead, the editor actually operates by maintaining a chain of expressions leading to the current one. The current expression is simply the last link in the chain. Descending adds the indicated subexpression onto the end of the chain, thereby making it be the current expression. The command 0 is used to ascend the chain; it removes the last link of the chain, thereby making the previous link be the current expression. Thus:

*P
X
*0 P
(X)
*0 -1 P
(COND (& Z) (T &))
*

Note the use of several commands on a single line in the previous output. The editor operates in a line buffered mode, the same as evalqt. Thus no command is actually seen by the editor, or executed, until the line is terminated, either by a carriage return, or a matching right parenthesis. The user can thus use control-A and control-Q for line-editing edit commands, the same as he does for inputs the evalqt.

In our editing session, we will make the following corrections to append: delete Y from where it appears, add Y to the end of the argument list, change NUL to NULL, change Z to Y, add Z after CAR, and insert a right parenthesis following CDR X.

First we will delete Y. By now we have forgotten where we are in the function definition, but we want to be at the "top" so we use the command ^, which ascends through the entire chain of expressions to the top level expression, which then becomes the current expression, i.e., ^ removes all links except the first one.

*^ P
(LAMBDA (X) Y (COND & &))
*

Note that if we are already at the top, ^ has no effect, i.e., it is a no-op. However, 0 would generate an error. In other words, ^ means "go to the top," while 0 means "ascend one link."

The basic structure modification commands in the editor are:

(n)n ≥ 1 deletes the corresponding element from the current expression.
(n e1 … em)n,m ≥ 1 replaces the nth element in the current expression with e1 … em.
(-n e1 … em)n,m ≥ 1 inserts e1 … em before the nth element in the current expression.

Thus:

*P
(LAMBDA (X) Y (COND & &))
*(3)
*(2 (X Y))
*P
(LAMBDA (X Y) (COND & &))
*

All structure modification done by the editor is destructive, i.e., the edotor uses rplaca and rplacd to physically change the structure it was given.

Note that all three of the above commands perform their operation with respect to the nth element from the front of the current expression; the sign Of n is used to specify whether the operation is replacement or insertion. Thus, there is no way to specify deletion or replacement of the nth element from the end of the current expression, or insertion before the nth element from the end without counting out that element's position from the front of the list. Similarly, because we cannot specify insertion after a particular clement, we cannot attach something at the end of the current expression using the above commands. Instead, we use the command N (for nconc). Thus we could have performed the above changes instead by:

*P
(LAMBDA (X) Y (COND & &))
*(3)
*2 (N Y)
*P
(X Y)
*^ P
*(LAMBDA (X Y) (COND & &))
*

Now we are ready to change NUL to NULL. Rather than specify the sequence of descent commands necessary to reach NUL, and then replace it with NULL, e.g., 3 2 1 (1 NULL), we will use F, the find command, to find NUL:

*P
(LAMBDA (X Y) (COND & &))
*F NUL
*P   
(NUL X)
*(1 NULL)
*0 P
((NULL X) Z)
*

Note that F is special in that it corresponds to two inputs. In other words, F says to the editor, "treat your next command as an expression to be searched for." The search is carried out in printout order in the current expression. If the target expression is not found there, F automatically ascends and searches those portions of the higher expressions that would appear after (in a printout) the current expression. If the search is successful, the new current expression will be the structure where the expression was found, and the chain will be the same as one resulting from the appropriate sequence of ascent and descent commands. If the search is not successful, an error occurs, and neither the current expression nor the chain is changed:

*P
((NULL X) Z)
*F COND P
    
COND ?
*P
((NULL X) Z)
*

Here the search failed to find a cond following the current expression, although of course a cond does appear earlier in the structure. This last example illustrates another facet of the error recovery mechanism: to avoid further confusion when an error occurs, all commands on the line beyond the one which caused the error (and all commands that may have been typed ahead while the editor was computing) are forgotten.

We could also have used the R command (for replace) to change NUL to NULL. A command of the form (R e1 e2) will replace all occurrences of e1 in the current expression by e2. There must be at least one such occurrence or the R command will generate an error. Let us use the R command to change all Z's (even though there is only one) in append to Y:

*^ (R Z Y)
*F Z
      
Z ?
*PP
  [LAMBDA (X Y)
    (COND
      ((NULL X)
        Y)
      (T (CONS (CAR)
          (APPEND (CDR X Y]
*

The next task is to change (CAR) to (CAR X). We could do this by (R (CAR) (CAR X)), or by:

*F CAR
*(N X)
*P
(CAR X)
*

The expression we now want to change is the next expression after the current expression, i.e., we are currently looking at (CAR X) in (CONS (CAR X) (APPEND (CDR X Y))). We could get to the append expression by typing 0 and then 3 or -1, or we can use the command NX. which does both operations:

*P
(CAR X)
*NX P
(APPEND (CDR X Y))
*

Finally, to change (APPEND (CDR X Y)) to (APPEND (CDR X) Y), we could perform (2 (CDR X) Y), or (2 (CDR X)) and (N Y), or 2 and (3). deleting the Y, and then 0 (N Y). However, if Y were a complex expression, we would not want to have to retype it. Instead, we could use a command which effectively inserts and/or removes left and right parentheses. There are six of these commands: BI, BO, LI, LO, RI, and RO. for both in, both out, left in, left out, right in, and right out. Of course, we will always have the same number of left parentheses as right parentheses, because the parentheses are just a notational guide to structure that is provided by our print program. Thus, left in, left out, right in, and right out actually do not insert or remove just one parenthesis, but this is very suggestive of what actually happens.

In this case, we would like a right parenthesis to appear following X in (CDR X Y). Therefore, we use the command (RI 2 2), which means insert a right parentheses after the second element in the second clement (of the current expression):

*P
(APPEND (CDR X Y))
*(RI 2 2)
*P
(APPEND (CDR X) Y)
*

We have now finished our editing, and can exit from the editor, to test append, or we could test it while still inside of the editor, by using the E command:

*E APPEND((A B) (C D E))
(A B C D E)
*

The E command causes the next input to be given to evalqt. If there is another input following it, as in the above example, the first will be applied (apply) to the second. Otherwise, the input is evaluated (eval).

We prettyprint append, and leave the editor.

*PP
  [LAMBDA (X Y)
    (COND
      ((NULL X)
        Y)
      (T (CONS (CAR X)
          (APPEND (CDR X) Y]
*OK
APPEND
_

* Warren Teitelman. "Section 9 The Interlisp Editor," in Interlisp Reference Manual. (1978). pp. 9.1-9.7. [Online]. Available: https://www.softwarepreservation.org/projects/LISP/interlisp/Interlisp-Oct_1978.pdf. Accessed: October 6, 2024.