home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
aijournl
/
1986_10
/
asse.lsp
< prev
next >
Wrap
Text File
|
1986-09-20
|
31KB
|
707 lines
; A Simple Structure Editor (ASSE), Version 1.01
; by
; Jeffrey M. Jacobs
; Copyright (c) 1986, CONSART Systems Inc.
; All Rights Reserved.
; Permission granted for non-commercial distribution.
; CONSART Systems, Inc.
; P.O. Box 3016, Manhattan Beach, CA 90266
; (213)376-3802
; CompuServe: 75076,2603
; BIX: jeffjacobs
; USENET: jjacobs@well.UUCP
;
; This is the code for the ASSE editor. In many respects, it is a cram course
; in LISP programming. Unlike some courses, this one starts out hard and gets
; easier. The code is laid out in roughly the order it was developed. The normal
; development cycle tends to be "middle out". Most of the early EDIT functions
; consist primarily of LISP primitives. As development progresses, common
; sequences of calls are made into functions. Normally we would go back and
; "fix" earlier functions to use the newer functions. This WILL be done in the
; electronically distributed version, but for "educational" (and schedule)
; reasons, it was left as is. So the "early" stuff is more difficult to
; read and understand.
;
; If you are new to LISP, it might be worthwhile to examine the code; there is
; a wide variety of LISP techniques used. The techniques, functions, forms
; etc. are pre-COMMON LISP (CL) and should work in any LISP, including .
; SCHEME. The only exception to this is DEFMACRO, which is NOT the same as the
; pre-CL MACRO (so much for the compatibility of CL). These are written to
; be easy to change to FEXPR's; just delete the &REST from the variable list.
;
; ASSE was developed using XLISP 1.6 on a Mac. Areas where you might need to
; change things are indicated by comments beginning with ";*". There aren't.
; many... - Jeff Jacobs, CONSART Systems Inc.
; The following functions are for convenience and readability
(DEFUN SUB1 (X) (1- X)) ;* I like SUB1.
(DEFUN NEQ (X Y) (NOT (EQ X Y))) ;* "NOT EQ"...
;* SPECIAL is not defined or available in XLISP.
(DEFMACRO SPECIAL (&REST X) (PUTPROP (CAR X) T 'SPECIAL) NIL) ;* XLISP.
;* Returns the function definition of an atom; NIL if undefined.
(DEFUN SYMBOL-FUNCTION (X) (SYMBOL-VALUE X)) ;* XLISP 1.6.
; MEMQ searches the list Y for an element EQ to X and returns the sublist
; starting with X. For XLISP and Common LISP, MEMBER uses EQL, which is close
; enough. Older LISP's MEMBER use EQUAL as the test, which is slower and
; could produce incorrect results.
(DEFUN MEMQ (X Y) (MEMBER X Y)) ;* XLISP and Common LISP
; Declare the global variables to be SPECIAL and initialize them.
(SPECIAL *UNDO_LIST*) ; Contains all the information from destructive
(SETQ *UNDO_LIST* NIL) ; operations, i.e. smash_lists, edit_indicators, etc.
(SPECIAL *EDIT_CHAIN*) ; Contains the "navigation" chain, i.e. the previous
(SETQ *EDIT_CHAIN* NIL) ; POSitions visited.
(SPECIAL *EDIT_PRINT_LEVEL*) ; The level to which the P command prints.
(SETQ *EDIT_PRINT_LEVEL* (QUOTE 2)) ; I like 2...
(SPECIAL *LAST_TAIL*) ; Used to keep for results of the UP command for
(SETQ *LAST_TAIL* NIL) ; the "...".
(SPECIAL *EDIT_LAST*) ; The SAVED_STATE of the last edit, if necessary.
(SETQ *EDIT_LAST* NIL)
; This is the entry into the guts of ASSE "User interface" functions call
; EDIT_LIST, which initializes and saves globals, faking dynamic binding,
; which is missing in some LISPs, such as XLISP and SCHEME. The caller
; is responsible for passing the correct initial states for the globals.
; Note that "break on error" should be disabled. You will have to figure
; out how this is done in your LISP.
(DEFUN EDIT_LIST
(EDIT_LIST UNDO_LIST)
(PROG (SAVE_STATE RETURN_STATE SAVE_BRK)
(SETQ SAVE_BRK *BREAKENABLE*)
(SETQ *BREAKENABLE* NIL) ;* Turns off "break on error" in XLISP.
(SETQ SAVE_STATE ; Save "current" values
(CONS *EDIT_CHAIN* *UNDO_LIST*) ) ; of globals.
(SETQ *EDIT_CHAIN* EDIT_LIST) ; Init
(SETQ *UNDO_LIST* UNDO_LIST) ; globals.
(SETQ RETURN_STATE ; Do the actual editing.
(LIST (EDIT_LIST1) *EDIT_CHAIN* *UNDO_LIST*) )
(SETQ *EDIT_CHAIN* (CAR SAVE_STATE)) ; Restore previous values
(SETQ *UNDO_LIST* (CDR SAVE_STATE)) ; of globals.
(SETQ *BREAKENABLE* SAVE_BRK) ; Restore state of"break on error".
(RETURN RETURN_STATE) ) )
; All EDIT_LIST1 does is READ the user command and perform the appropriate
; action,usually dispatching to a function. The ERRSET catches any errors
; generated by the user, preventing the user from inadvertently being thrown
; back to the top level of LISP. If you don't understand it, don't worry
; about it.
(DEFUN EDIT_LIST1
()
(PROG (COMMAND)
LOOP
(PRINC "*")
(SETQ COMMAND (EDIT_GET_CMD))
(ERRSET (COND ((NUMBERP COMMAND) (EDIT_SETTO_NTH COMMAND))
((MEMQ COMMAND '(D DEL DELETE))
(EDIT_DEL) )
((MEMQ COMMAND '(INSERT INS I))
(EDIT_INS_NTH) )
((EQ COMMAND 'OK)
(SETQ *EDIT_EDIT_CHAIN* (LAST *EDIT_CHAIN*))
(RETURN 'OK) )
((EQ COMMAND 'SAVE)
(SETQ *EDIT_CHAIN* (LAST *EDIT_CHAIN*))
(RETURN 'SAVE) )
((EQ COMMAND 'P)
(EDIT_PRINT (CAR *EDIT_CHAIN*)
*EDIT_PRINT_LEVEL* )
(TERPRI) )
((EQ COMMAND 'PL)
(EDIT_PRINT (EDIT_CUR_EXP) (EDIT_GET_ARG)) )
((MEMQ COMMAND '(EMB EMBED))
(EDIT_EMBED) )
((EQ COMMAND 'PP)
(PP (EDIT_CUR_EXP)) )
((MEMQ COMMAND '(R REP REPLACE))
(EDIT_REPLACE) )
((MEMQ COMMAND '(EXTRACT EXT XTR))
(EDIT_EXTRACT) )
((EQ COMMAND '??) (EDIT??))
((EQ COMMAND '?) (EDIT?))
((EQ COMMAND 'UP) (EDIT_UP))
((EQ COMMAND 'UNDO) (EDIT_UNDO))
((EQ COMMAND 'UNUNDO) (EDIT_UNUNDO))
((EQ COMMAND 'REVERT) (EDIT_REVERT))
((EQ COMMAND 'UNBLOCK) (EDIT_UNBLOCK))
((EQ COMMAND 'TOP)
(SETQ *EDIT_CHAIN* (LAST *EDIT_CHAIN*)) )
((MEMQ COMMAND '(NEXT NX N NEX))
(EDIT_NEXT) )
((EQ COMMAND 'TEST)
(SETQ *UNDO_LIST*
(CONS 'BLOCK *UNDO_LIST*) ) )
(T (EDIT_ERROR "?" COMMAND)) )
T )
(GO LOOP) ) )
; EDIT_SETTO_NTH should be worked through and understood. This function consists
; entirely of LISP primitives and is good exercise. Parts of this function
; are an obvious candidate for a separately defined function (see below).
; Note that there is only one form within the PROG.
(DEFUN EDIT_SETTO_NTH (N)
(PROG (CUR_EXP_LEN)
(COND ((ZEROP N)
(COND ((NULL (CDR *EDIT_CHAIN*))
(EDIT_ERROR "At TOP" 0) )
(T (SETQ *EDIT_CHAIN* (CDR *EDIT_CHAIN*))) ) )
((>= (SETQ CUR_EXP_LEN ; Save length of "current expression".
(LENGTH (CAR *EDIT_CHAIN*)) ) ;
(ABS N) )
(SETQ *EDIT_CHAIN*
(CONS (NTH (COND ((MINUSP N) (+ CUR_EXP_LEN N))
(T (SUB1 N)) )
(CAR *EDIT_CHAIN*) )
*EDIT_CHAIN* ) ) )
(T (EDIT_ERROR "?" N)) ) ) )
; The following function simply returns the "current expression". Otherwise
; we would have to look at miles and miles of (CAR *EDIT_CHAIN*). Makes
; the rest of the code easier to read.
(DEFUN EDIT_CUR_EXP NIL (CAR *EDIT_CHAIN*))
; EDIT_DEL simply dispatches to the appropriate function based on the
; value of the argument. Most of the functions called by EDIT_LIST1 dispatch
; this way; future versions will also dispatch on the type as well.
; WARNING: EDIT_DEL_CURRENT is not defined, but, if it were, would delete the
; current expression. Something for you to try; see UP and NEXT for hints.
(DEFUN EDIT_DEL ()
(PROG (N)
(COND ((NUMBERP (SETQ N (EDIT_GET_ARG))) ; User arg must be numeric.
(COND ((ZEROP N) (EDIT_DEL_CURRENT))
((EQ N 1) (EDIT_DEL_FIRST)) ; DEL 1 is a special case.
(T (EDIT_DEL_NTH N)) ) )
(T (EDIT_ERROR "DEL-bad arg" N)) ) ) )
; You should also work your way through EDIT_DEL_NTH. It deletes the nth
; element if it exists. Take careful note of the calculation for NTHCDR; it's
; tricky. Remember that the user specifies POSition "base 1", but NTHCDR is
; zero-based.
(DEFUN EDIT_DEL_NTH (N)
(PROG (CUR_EXP_LEN POS)
(SETQ CUR_EXP_LEN (LENGTH (EDIT_CUR_EXP)))
(COND ((MINUSP (SETQ POS N)) ; Convert user arg to "absolute" POSition.
(SETQ POS (+ CUR_EXP_LEN (+ 1 N))) ))
(COND ((= POS 1) (EDIT_DEL_FIRST)) ; DEL 1 is a special case.
((>= CUR_EXP_LEN POS)
(EDIT_SMASH (SETQ POS (NTHCDR (- POS 2) (EDIT_CUR_EXP)))
(CAR POS)
(CDDR POS) )
(EDIT_CMD_DONE (LIST 'DEL N)))
(T (EDIT_ERROR "DEL-bad arg" N)) ) ) )
; Even though this is a "special case", it's surprisingly simple.
(DEFUN EDIT_DEL_FIRST
NIL
(EDIT_SMASH (EDIT_CUR_EXP)
(CADR (EDIT_CUR_EXP))
(CDR (EDIT_CUR_EXP)) )
(EDIT_SMASH (EDIT_CUR_EXP)
(CAR (EDIT_CUR_EXP))
(CDDR (EDIT_CUR_EXP)) )
(EDIT_CMD_DONE (LIST 'DEL 1)) )
; Again, a simple dispatching function...
(DEFUN EDIT_INS_NTH
NIL
(PROG (WHAT ARG1 POS)
(SETQ WHAT (EDIT_GET_ARG))
(SETQ ARG1 (EDIT_GET_ARG))
(SETQ POS (EDIT_GET_ARG))
(COND ((NUMBERP POS)
(COND ((MEMQ ARG1 '(BEFORE BEF BF B))
(EDIT_INS_BEFORE WHAT POS) )
((MEMQ ARG1 '(AFTER AFT AF A))
(EDIT_INS_AFTER WHAT POS) )
(T (EDIT_ERROR "Arg to INSERT must be BEFORE or AFTER"
ARG1 )) ) )
(T (EDIT_ERROR "INSERT requires numeric arg"
POS )) ) ) )
; EDIT_INS_BEFORE illustrates insertion. Note the calculation of the POSition
; is now peformed by EDIT_VAL_NTH. Compare this with EDIT_DEL_NTH.
(DEFUN EDIT_INS_BEFORE (WHAT WHERE)
(PROG (POS Z)
(COND ((SETQ POS (EDIT_VAL_NTH WHERE))(SETQ POS (SUB1 POS))) ; Before...
(T(EDIT_ERROR "EDIT_INS_BEFORE-Illegal Position Specified" WHERE)))
(COND ((ZEROP POS)
(EDIT_SMASH (SETQ Z (EDIT_CUR_EXP)) WHAT (CONS (CAR Z) (CDR Z)))
(EDIT_CMD_DONE (LIST 'INSERT WHAT 'BEFORE WHERE)))
(T(EDIT_SMASH (SETQ Z (NTHCDR (SUB1 POS) (EDIT_CUR_EXP)))
(CAR Z)
(CONS WHAT (CDR Z)))
(EDIT_CMD_DONE (LIST 'INSERT WHAT 'BEFORE WHERE))))))
; EDIT_INS_AFTER...
(DEFUN EDIT_INS_AFTER (WHAT WHERE)
(PROG (POS Z)
(COND ((SETQ POS (EDIT_VAL_NTH WHERE)))
(T(EDIT_ERROR "EDIT_INS_AFTER-Illegal Position Specified" WHERE)))
(COND ((ZEROP POS)
(EDIT_SMASH (SETQ Z (EDIT_CUR_EXP)) WHAT (CONS (CAR Z) (CDR Z)))
(EDIT_CMD_DONE (LIST 'INSERT WHAT 'BEFORE WHERE)))
(T(EDIT_SMASH (SETQ Z (NTHCDR (SUB1 POS) (EDIT_CUR_EXP)))
(CAR Z)
(CONS WHAT (CDR Z)))
(EDIT_CMD_DONE (LIST 'INSERT WHAT 'AFTER WHERE))))))
; This is the workhorse of the editor. It takes the CONS cell to be modfied
; and the new CAR and CDR as it's arguments. It saves the information on
; *UNDO_LIST* and then "smashes" the cell using RPLACA and RPLACD.
(DEFUN EDIT_SMASH
(CELL NEW_CAR NEW_CDR)
(COND ((ATOM CELL)
(EDIT_ERROR "EDIT_SMASH-internal error, CELL must be CONS"
CELL ) )
(T (SETQ *UNDO_LIST*
(CONS (LIST CELL (CAR CELL) (CDR CELL))
*UNDO_LIST* ) )
(RPLACA CELL NEW_CAR)
(RPLACD CELL NEW_CDR) ) ) )
; This saves the current state of the edit chain and the command issued by the
; user on *UNDO_LIST*.
(DEFUN EDIT_CMD_DONE
(CMD) ; CMD is the "user's" command.
(SETQ *UNDO_LIST*
(CONS (CONS '*EDIT_CHAIN* *EDIT_CHAIN*)
*UNDO_LIST* ) )
(SETQ *UNDO_LIST* (CONS CMD *UNDO_LIST*)) )
; The following function would have been nice to have earlier. It takes
; a user supplied numerical argument and converts it to a positive, VALidated
; NTH position number for the current expression. It is a predicate (test)
; that returns a number if the argument was VALid, or NIL if something was
; wrong.
(DEFUN EDIT_VAL_NTH
(N)
(COND ((NUMBERP N) (EDIT_NTH N)) (T NIL)) )
; Convert a POSition number and verify that it's within the current expression's
; length. Called by EDIT_VAL_NTH.
(DEFUN EDIT_NTH
(N)
(PROG (POS CUR_EXP_LEN)
(SETQ CUR_EXP_LEN (LENGTH (EDIT_CUR_EXP)))
(COND ((MINUSP (SETQ POS N))
(SETQ POS (+ CUR_EXP_LEN (+ 1 N))) ))
(RETURN (COND ((>= CUR_EXP_LEN POS) POS) (T NIL))) ) )
; Embed things in parentheses. Calls a separate function to handle an arg of
; the type (n m).
(DEFUN EDIT_EMBED ()
(PROG (ARG POS TMP)
(COND ((AND (NUMBERP (SETQ ARG (EDIT_GET_ARG)))
(SETQ POS (EDIT_NTH ARG)) )
(EDIT_SMASH (SETQ TMP
(NTHCDR (SUB1 POS) (EDIT_CUR_EXP)) )
(CONS (CAR TMP) NIL)
(CDR TMP) ) )
((CONSP ARG) (EDIT_EMBD_RANGE ARG))
(T (EDIT_ERROR "EMBED-bad arg" ARG)) ) ) )
; Embed a range of elements in parentheses. Note the use of TMP1 and TMP2 to
; save values. Called by EDIT_EMBED.
(DEFUN EDIT_EMBD_RANGE (ARG)
(PROG (POS1 POS2 TMP1 TMP2)
(COND ((AND (SETQ POS1 (EDIT_VAL_NTH (CAR ARG)))
(SETQ POS2 (EDIT_VAL_NTH (CADR ARG)))
(> POS2 POS1) )
(EDIT_SMASH (SETQ TMP1
(NTHCDR (SUB1 POS1) (EDIT_CUR_EXP)) )
(CONS (CAR TMP1) (CDR TMP1))
(CDR (SETQ TMP2
(NTHCDR (SUB1 POS2) (EDIT_CUR_EXP)) )) )
(EDIT_SMASH TMP2 (CAR TMP2) NIL)
(EDIT_CMD_DONE (LIST 'EMBED ARG)) )
(T (EDIT_ERROR "EDIT_EMBD_RANGE-bad arg" ARG)) ) ) )
; Remove a set of parentheses from the element specified by ARG. This should
; hopefully be fairly straightforward by now. See the text and diagrams
; while examining the code...
(DEFUN EDIT_EXTRACT
NIL
(PROG (ARG POS CELL EXTRACTEE SAVE TMP)
(COND ((SETQ POS
(EDIT_VAL_NTH (SETQ ARG (EDIT_GET_ARG))) )
(SETQ CELL
(NTHCDR (SUB1 POS) (EDIT_CUR_EXP)) )
(COND ((CONSP (SETQ EXTRACTEE (CAR CELL)))
(EDIT_SMASH EXTRACTEE
(CAR EXTRACTEE)
(CDR EXTRACTEE) )
(COND ((NULL (CDR EXTRACTEE))
(EDIT_SMASH CELL (CAR EXTRACTEE) (CDR CELL)) )
(T (SETQ SAVE (CDR CELL))
(EDIT_SMASH CELL
(CAR EXTRACTEE)
(CDR EXTRACTEE) )
(EDIT_SMASH (SETQ TMP (LAST EXTRACTEE))
(CAR TMP)
SAVE ) ) )
(EDIT_CMD_DONE (LIST 'EXTRACT ARG)) )
(T (EDIT_ERROR "EDIT_EXTRACT-bad arg" ARG)) ) )) ) )
; This is about as simple as things get. It would be a lot messier if
; we used LISP primitives in place of EDIT_VAL_NTH...
(DEFUN EDIT_REPLACE
NIL
(PROG (WHERE WHAT POS TMP)
(SETQ WHERE (EDIT_GET_ARG))
(SETQ WHAT (EDIT_GET_ARG))
(COND ((SETQ POS (EDIT_VAL_NTH WHERE))
(EDIT_SMASH (SETQ TMP
(NTHCDR (SUB1 POS) (EDIT_CUR_EXP)) )
WHAT
(CDR TMP) )
(EDIT_CMD_DONE (LIST 'REPLACE WHERE WHAT)) )
(T (EDIT_ERROR "EDIT_REPLACE-bad arg" WHERE)) ) ) )
; Process the whole undo list and print out the user's commands. This
; demonstrates "classic" LISP MAP function useage. It applies (FUNCTION...
; to all of the CARs of *UNDO_LIST*. Very elegant. Note that the predicates
; EDIT_CMDP, EDIT_SMASHP and EDIT_CHAINP are defined below.
(DEFUN EDIT??
NIL
(MAPC (FUNCTION (LAMBDA (X)
(COND ((OR (EQ X 'BLOCK) (EDIT_CMDP X))
(PRINT X) )) ))
*UNDO_LIST* ) )
; EDIT? prints out a user specified number of commands. Just a simple loop...
(DEFUN EDIT?
NIL
(PROG (COUNT LIST)
(COND ((NOT (NUMBERP (SETQ COUNT (EDIT_GET_CMD))))
(EDIT_ERROR "EDIT?-bad arg" COUNT)
(RETURN NIL) ))
(SETQ LIST *UNDO_LIST*)
LOOP
(COND ((OR (NULL LIST) (ZEROP COUNT))
(RETURN NIL) )
((OR (EDIT_CMDP (CAR LIST))
(EQ (CAR LIST) 'BLOCK) )
(PRINT (CAR LIST))
(SETQ COUNT (SUB1 COUNT)) ) )
(SETQ LIST (CDR LIST))
(GO LOOP) ) )
; UP is explained in the text. See the diagram. Note that if the result is
; a "tail", or sublist, *LAST_TAIL* is set.
(DEFUN EDIT_UP
NIL
(PROG (CUR_EXP HGHR_EXP)
(COND ((NULL (SETQ HGHR_EXP (CADR *EDIT_CHAIN*)))
(EDIT_ERROR "Already at TOP" NIL) )
((EQ (SETQ CUR_EXP (EDIT_CUR_EXP))
(CAR HGHR_EXP) )
(SETQ *LAST_TAIL* NIL)
(SETQ *EDIT_CHAIN* (CDR *EDIT_CHAIN*)) )
((SETQ *LAST_TAIL* (MEMQ CUR_EXP HGHR_EXP))
(SETQ *EDIT_CHAIN*
(CONS *LAST_TAIL* (CDR *EDIT_CHAIN*)) ) )
((SETQ *EDIT_CHAIN* (CDR *EDIT_CHAIN*))) ) ) )
(DEFUN EDIT_NEXT
NIL
(PROG (HGHR_EXP TMP)
(COND ((NULL (SETQ HGHR_EXP (CADR *EDIT_CHAIN*)))
(EDIT_ERROR "No NEXT" NIL) )
((AND (SETQ TMP (MEMQ (EDIT_CUR_EXP) HGHR_EXP))
(CONSP (CDR TMP)) )
(SETQ *EDIT_CHAIN*
(CONS (CADR TMP) (CDR *EDIT_CHAIN*)) ) )
(T (EDIT_ERROR "No NEXT" NIL)) ) ) )
; The following 3 predicates are used when searching *UNDO_LIST*. EDIT_UNDOP,
; EDIT_SMASHP, and EDIT_CHAINP return "useful" values, i.e. either the
; undo list element if the predicate is true, or NIL if false.
; If X is an UNDO or UNUNDO command, return X, else NIL.
(DEFUN EDIT_UNDOP
(X)
(COND ((AND (CONSP X)
(MEMQ (CAR X) '(UNDO UNUNDO)) )
X )) )
; If X is a smash_list, return it, otherwise NIL.
(DEFUN EDIT_SMASHP
(X)
(COND ((AND (CONSP X) (CONSP (CAR X))) X)
(T NIL) ) )
; If X is a edit_chain indicator, return it, else NIL.
(DEFUN EDIT_CHAINP
(X)
(COND ((AND (CONSP X)
(EQ (CAR X) '*EDIT_CHAIN*) )
X )) )
; If X is a command, return T, not X, else NIL.
(DEFUN EDIT_CMDP
(X)
(AND (CONSP X)
(ATOM (CAR X))
(NEQ (CAR X) '*EDIT_CHAIN*) ) )
; EDIT_UNDO undoes the last destructive command that was not an "undo" command,
; unless a BLOCK intervenes (or there is nothing to undo). Note that UNDO
; is undoable by UNUNDO.
(DEFUN EDIT_UNDO ()
(PROG (TMPLIST TMPCAR)
(SETQ TMPLIST *UNDO_LIST*)
LOOP
(COND ((OR (NULL TMPLIST)
(EQ (SETQ TMPCAR (CAR TMPLIST))
'BLOCK ) )
(EDIT_ERROR "Nothing to UNDO" NIL) )
((AND (EDIT_CMDP TMPCAR) ; Must be a command
(NOT (EDIT_UNDOP TMPCAR)) ) ; but skip "undo" commands.
(COND ((EDIT_CHAINP (CADR TMPLIST)) ; Verify that *UNDO_LIST* is ok.
(EDIT_UNDO1 (CDDR TMPLIST)) ; Undo the previous command.
(EDIT_CMD_DONE (LIST 'UNDO)) ; Save the UNDO command.
(SETQ *EDIT_CHAIN* (CDADR TMPLIST)) ; Reset *EDIT_CHAIN*.
(PRINT (LIST TMPCAR 'UNDONE)) )
(T (EDIT_ERROR "EDIT_UNDO-Garbaged *UNDO_LIST*, BIG trouble"
NIL )) ) )
(T (SETQ TMPLIST (CDR TMPLIST)) (GO LOOP)) ) ) )
; EDIT_UNDO1 actually performs the "undoing", using EDIT_SMASH to allow UNUNDO.
(DEFUN EDIT_UNDO1
(LIST)
(PROG (TMP)
LOOP
(COND ((SETQ TMP (EDIT_SMASHP (CAR LIST)))
(EDIT_SMASH (CAR TMP)
(CADR TMP)
(CADDR TMP) )
(SETQ LIST (CDR LIST))
(GO LOOP) )) ) )
; UNUNDO undoes the last command if and only if it was an UNDO command.
(DEFUN EDIT_UNUNDO ()
(PROG (TMPLIST TMPCAR)
(COND ((OR (NULL (SETQ TMPLIST *UNDO_LIST*))
(EQ (SETQ TMPCAR (CAR TMPLIST))
'BLOCK ) )
(EDIT_ERROR "No UNDO to UNDO" NIL) )
((EDIT_UNDOP TMPCAR) ; Is it an "undo" command?
(COND ((EDIT_CHAINP (CADR TMPLIST)) ; Verify that *UNDO_LIST* is ok.
(EDIT_UNDO1 (CDDR TMPLIST)) ; Undo the UNDO.
(EDIT_CMD_DONE (LIST 'UNUNDO)) ; Save the UNUNDO command.
(SETQ *EDIT_CHAIN* (CDADR TMPLIST)) ; Restore *EDIT_CHAIN*.
(PRINT (LIST TMPCAR 'UNDONE)) )
(T (EDIT_ERROR "EDIT_UNDO-Garbaged *UNDO_LIST*, BIG trouble"
NIL )) ) )
(T (EDIT_ERROR "Last command was not UNDO" NIL)) ) ) )
; EDIT_REVERT restores everything that was done since the last TEST/BLOCK was
; issued. REVERT is NOT undoable...
(DEFUN EDIT_REVERT
NIL
(PRINC "Are you SURE?")
(COND ((MEMQ (READ) '(Y YE YES))
(PROG (TMPLIST TMPCAR)
(SETQ TMPLIST *UNDO_LIST*)
LOOP
(COND ((OR (NULL TMPLIST)
(EQ (SETQ TMPCAR (CAR TMPLIST))
'BLOCK ) )
(SETQ *UNDO_LIST* TMPLIST)
(RETURN NIL) )
((EDIT_SMASHP TMPCAR)
(RPLACA (CAR TMPCAR) (CADR TMPCAR))
(RPLACD (CAR TMPCAR) (CADDR TMPCAR)) )
((EDIT_CHAINP TMPCAR)
(SETQ *EDIT_CHAIN* (CDR TMPCAR)) ) )
(SETQ TMPLIST (CDR TMPLIST))
(GO LOOP) ) )) )
; Destructively removes a BLOCK from *UNDO_LIST*
(DEFUN EDIT_UNBLOCK
NIL
(PROG (TMPLIST)
(COND ((NULL (SETQ TMPLIST *UNDO_LIST*)) ; Check for nothing on
(RETURN NIL) ) ; the list,
((EQ (CAR *UNDO_LIST*) (QUOTE BLOCK)) ; or for BLOCK at the
(SETQ *UNDO_LIST* (CDR *UNDO_LIST*)) ) ) ; begining.
LOOP
(COND ((EQ (CADR TMPLIST) (QUOTE BLOCK)) ; If BLOCK found,
(RPLACD TMPLIST (CDDR TMPLIST)) ; remove it from the list.
(RETURN NIL) )
((SETQ TMPLIST (CDR TMPLIST)) (GO LOOP)) ; Keep searching.
(T (EDIT_ERROR "No BLOCK found" NIL)) ) ) ) ; Report error.
; The User Interface functions follow. They are adequately described in the
; text. If your LISP is "old" and doesn't support DEFMACRO, just change it
; to a FEXPR and remove the &REST.
; NOTE that getting the function definition varies ridiculously from dialect
; to dialect. In NIL, it's
;
; (SI:INTERPRETER-CLOSURE-LAMBDA (SYMBOL-FUNCTION FUN))
;
; You will have to change this for your LISP. It shouldn't be as bad as NIL.
; If there is both a function definition and a saved state, EDITF assumes
; the saved state is the function. (It probably should verify this...).
(DEFMACRO EDITF
(&REST FUN)
(SETQ FUN (CAR FUN)) ; Get the function name.
(PROG (FUNDEF RESULT SAVED_STATE)
(COND ((NULL (SETQ FUNDEF (SYMBOL-FUNCTION FUN))) ;*Change this?
(PRINT (LIST "No function definition for" FUN))
(RETURN NIL) )
((SETQ SAVED_STATE
(GET FUN 'EDIT_SAVE) )
(SETQ RESULT
(EDIT_LIST (CAR SAVED_STATE)
(CADR SAVED_STATE) ) ) )
(T (SETQ RESULT
(EDIT_LIST (CONS FUNDEF NIL) NIL) )) )
(COND ((EQ (CAR RESULT) 'OK)
(REMPROP FUN 'EDIT_SAVE)
(PUTPROP '*EDIT_LAST* (CDR RESULT) 'EDIT_SAVE)
(RETURN (LIST 'QUOTE FUN)) )
((EQ (CAR RESULT) 'SAVE)
(PUTPROP FUN (CDR RESULT) 'EDIT_SAVE)
(RETURN (LIST 'QUOTE FUN)) )
(T (PRINT "Unknown return from EDIT_LIST")
NIL ) ) ) )
; EDITV is roughly the same as EDITF. Note that in XLISP 1.6 they are the
; same. As before, if you have "old" LISP, change to a FEXPR...
(DEFMACRO EDITV
(&REST VAR)
(SETQ VAR (CAR VAR))
(PROG (VALUE RESULT SAVED_STATE)
(COND ((ATOM (SETQ VALUE (SYMBOL-VALUE VAR)))
(PRINT (LIST "Value cannot be edited for" VAR))
(RETURN NIL) )
((SETQ SAVED_STATE
(GET VAR 'EDIT_SAVE) )
(SETQ RESULT
(EDIT_LIST (CAR SAVED_STATE)
(CADR SAVED_STATE) ) ) )
(T (SETQ RESULT
(EDIT_LIST (CONS VALUE NIL) NIL) )) )
(COND ((EQ (CAR RESULT) 'OK)
(REMPROP VAR 'EDIT_SAVE)
(PUTPROP '*EDIT_LAST* (CDR RESULT) 'EDIT_SAVE)
(RETURN (LIST 'QUOTE VAR)) )
((EQ (CAR RESULT) 'SAVE)
(PUTPROP VAR (CDR RESULT) 'EDIT_SAVE)
(RETURN (LIST 'QUOTE VAR)) )
(T (PRINT "Unknown return from EDIT_LIST")
NIL ) ) ) )
(DEFUN EDIT
(EXPR)
(PROG (RESULT SAVED_STATE)
(COND ((NULL EXPR)
(COND ((SETQ SAVED_STATE
(GET '*EDIT_LAST* 'EDIT_SAVE) )
(SETQ RESULT
(EDIT_LIST (CAR SAVED_STATE)
(CADR SAVED_STATE) ) ) )) )
((CONSP EXPR)
(SETQ RESULT
(EDIT_LIST (CONS EXPR NIL) NIL) ) )
(T (PRINT "Nothing to EDIT") (RETURN NIL)) )
(PUTPROP '*EDIT_LAST* (CDR RESULT) 'EDIT_SAVE)
(RETURN (CAR (LAST (CADR RESULT)))) ) )
; This causes everything between the invocation of EDIT_ERROR and the
; ERRSET in EDIT_LIST1 to be thrown away.
(DEFUN EDIT_ERROR
(STRING ARG)
(ERROR STRING ARG)
NIL )
; The I/O functions follow:
(DEFUN EDIT_PRINT
(EXPR DEPTH)
(COND ((EQ EXPR *LAST_TAIL*) (PRINC "...")))
(PRINT_LEV EXPR DEPTH) )
(DEFUN PRINT_LEV
(EXPR DEPTH)
(COND ((ATOM EXPR) (PRIN1 EXPR))
((CONSP EXPR)
(COND ((ZEROP DEPTH) (PRIN1 '&))
(T (PRINC "(")
(PRINT_LEV1 EXPR DEPTH)
(PRINC ")") ) ) ) ) )
(DEFUN PRINT_LEV1
(EXPR DEPTH)
(PROG (X)
(SETQ X EXPR)
LOOP
(COND ((ATOM (CAR X)) (PRIN1 (CAR X)))
(T (PRINT_LEV (CAR X) (- DEPTH 1))) )
(COND ((NULL (SETQ X (CDR X))) (RETURN))
(T (PRINC " ") (GO LOOP)) ) ) )
; Just a simple READ protected by an ERRSET to prevent accidental exits
; from the editor or a user induced break.
(DEFUN EDIT_GET_CMD
NIL
(PROG (X)
LOOP
(COND ((SETQ X (ERRSET (READ) NIL))
(RETURN (CAR X)) )
(T (PRINC "*") (GO LOOP)) ) ) )
(DEFUN EDIT_GET_ARG NIL (READ))