The Revised Maclisp ManualThe PitmanualPage A-4
Published by HyperMeta Inc.
Prev | Index | Next
[Blue Marble]
Climate Change
Are you part of the problem
or part of the solution?

The Read-Eval-Print Loop

Read-Eval-Print LoopConceptInterpreter Interaction

Normally, when Maclisp is invoked as a job, it presents the user with what is called a read-eval-print loop. The purpose of this loop is to read expressions typed by the user on the terminal, evaluate them, and type the results back on the terminal. Such expressions may be entered to see what value they return or to produce a side-effect on the global environment.

A close approximation to the behavior of this toplevel loop is given by the following functional description:

  (DO NIL (NIL) ;Loop forever
    (PRINT (EVAL (READ)))))

The actual read-eval-print loop of Maclisp is somewhat more complex than the simple one defined here because it provides a few extra features (such as upkeep of the variables *, +, and -, described later in this chapter). The important point is that the primitives required for writing a read-eval-print loop are all available to the user. Syntactically correct lisp expressions have a well-defined mapping into normal lisp data structures (the READ function accomplishes this mapping). Data structures can be evaluated as program in a well-defined way (the EVAL function does this). Data structures can be displayed in a way which could later be typed back in to the lisp reader (the PRINT function does this). Giving the user the power to invoke all of these operations from his code is a very powerful feature of Lisp which sets it apart from most other languages.

Here is a sample console session (note that the semicolon character makes the rest of a line an arbitrary comment which is ignored by the Lisp reader):

   ;; Set the input/output radix to base-10 (default is base-8).
   ;;  An example of evaluation for side-effect on the global environment.

   ;; Define a function for later use.
   ;;  Another example of evaluation for side-effect.
   (DEFUN CUBE (X) (EXPT X 3))

   ;; Evaluate an expression.
   ;;  An example of evaluation just to see the value returned.
   (CUBE 3)

For Interactive Use


The value of this symbol is the result of the last evaluation performed at top level or in a break loop which was not deeper than the current break loop. It is bound locally in each break loop. If an error occurs before a value for the input form has been computed, this symbol retains its previous value.


(LIST * *)			=> ((FOO BAR) (FOO BAR))
(EQ (CAR *) FOOBAR)		=> T
(TRACE #.*)			=> (FOO-BAR)


The value of this symbol is the previous form read at Lisp top level or in a break loop which was not deeper than the current break loop. It is bound locally in each break loop.




The value of this symbol is the current form being evaluated at Lisp top level or in a break loop. It is of somewhat limited usefulness.

;; A form that prints itself...
=> T

;; A form that explains about itself...
(FORMAT T "~&Form being evaluated: ~S" -)
Form being evaluated: (FORMAT T "~&Form being evaluated: ~S" -)
=> NIL		

;; An easy way to get infinite recursion...
(EVAL -)


*BREAKFunction(*BREAK flag msg)

[PDP-10 Only] If the first argument, flag, is not NIL, a Lisp breakpoint will occur and the message “;BKPT msg” will be displayed. The msg will be PRINC'd for better readability.

A breakpoint or break-loop is a full recursive invocation of the read-eval-print loop. S-expressions typed in will be evaluated and the results of evaluation will be printed just as at Lisp top level. A breakpoint is run within an ERRSET so that errors occurring within the breakpoint do not cause an unintended return from the break. Also, the state of the I/O system is saved prior to invocation of a breakpoint and restored upon return.

If the symbol $P is typed in at break level, *BREAK returns NIL. e.g.,

(progn (prin1 (*break t "Print what?")) 'done)
;BKPT Print what?

If (RETURN form) is typed in, the break loop will evaluate form and return the resulting value. Here's an example:

(progn (prin1 (*break t "Print what?")) 'done)
;BKPT Print what?
(return 'foo)

Note that RETURN is not executed as a function in this case; rather, a form whose car is RETURN is special-cased in this way by the break loop handler just as is the token $P. Here's an example to illustrate that:

(progn (prin1 (*break t "Print what?")) 'done)
;BKPT Print what?
(progn (return 'foo))

Another way to return from a break is to throw to the tag BREAK. e.g.,

(progn (prin1 (*break t "Print what?")) 'done)
;BKPT Print what?
(*throw 'break 'this)

*BREAK can be used to allow user intervention in a program when something unexpected happens. It is used in this way by the LISP error system.

BREAKSpecial Form(BREAK msg [flag])

Applies *BREAK to the result of evaluating flag and to msg (which is not evaluated). If flag is not given, it defaults to T.

(defun fact (x)
  (cond ((minusp x) (break "Factorial of a negative number?"))
        ((zerop x) 1)
        (t (times x (fact (sub1 x))))))

`(fact of 3 is ,(fact 3))
=> (FACT OF 3 IS 6)

`(fact of -3 is ,(fact -3))
;BKPT Factorial of a negative number?

=> -3

(return 'an-error)


[PDP-10 Only] The value of the variable $P is a symbol which if typed at the toplevel of a breakpoint, terminates the breakpoint and returns NIL to the caller of the break. On Multics, this variable doesn't exist; the symbol $P is looked for explicitly by the breakpoint handler.

The obscure name was taken from the DDT debugging program for the PDP-10, which uses the command $P to mean “proceed the current job.”

Note: In PDP-10 implementations, the “$” in $P is an altmode. On Multics, it is a dollarsign.

Web-Only Note:

The PDP-10 operating system on which MACLISP was developed was ITS, which used a user command shell called DDT that was also a debugger (hence the name). It was possible, by typing Control-Z, to temporarily stop a running program and return to DDT; if one wanted to proceed again with running that program, the DDT command was $P. This name choice must surely seem obscure by modern standards, but it was easy to type and easy to remember for the community that developed it.

(progn (print 'foo) (break pause) 'done)


(let (($p 'continue)) ;Use CONTINUE here instead of $P
  (print 'foo) (break "Type CONTINUE to proceed") 'done)
;BKPT Type CONTINUE to proceed


Read-Eval-Print Status Options


Returns the evaluable expression which is taking the place of the normal Lisp top-level Read-Eval-Print operation, or NIL if the normal Lisp Read-Eval-Print loop is in effect.


Evaluates and returns exp and sets the top level form to this value. For example, one could replace the Read-Eval-Print loop with a Read-Factorial-Print loop by defining an appropriate definition of FACTORIAL and then doing:



[PDP-10 Only] Like (STATUS TOPLEVEL) but for BREAK loops.


[PDP-10 Only] Like (SSTATUS TOPLEVEL form) but for BREAK loops.

Read-Eval-Print Control Variables


[PDP-10 Only] If the value of READ is not NIL, it should be a function of 0 to 2 args which has the same calling conventions as READ and which is to be called in place of READ in the Lisp Read-Eval-Print loop.

When READ is NIL, ordinary READ is called. Since this variable doesn't exist on Multics, ordinary READ is always called by the Read-Eval-Print loop.


The value of the symbol PRIN1 is the name of a function to be used instead of PRIN1 in the toplevel read-eval-print loop and in error messages only. PRINT still calls the standard PRIN1 lsubr, not the value of this variable.

The internal functions used by the system's read-eval-print loop have names which are accessible to the user. They are functions of 0 or 1 arg named *-READ-EVAL-PRINT, READ-*-EVAL-PRINT, READ-EVAL-*-PRINT, and READ-EVAL-PRINT-*, and are described elsewhere in this manual. To allow the user some control over the communication between these various functions, a series of hook variables are provided. Each hook variable has the same name as the function which it precedes. A description of these hook variables follows.


[PDP-10 Only] If not NIL, this variable holds the name of a function of no arguments which is to be run prior to the READ part of each cycle through the read-eval-print loop. The function's return value is ignored.

(setq *-read-eval-print #'(lambda nil (princ "> ")))
=> (LAMBDA NIL (PRINC "> "))
> (comment Note how we now get prompted for input)
> (setq *-read-eval-print nil)
=> NIL
(comment The prompting has gone away)


[PDP-10 Only] If not NIL, this variable holds the name of a function of one argument which is to be run prior to the EVAL part of each cycle through the read-eval-print loop. Its argument is the form read by the *-READ-EVAL-PRINT part of the loop, and its return value becomes the argument to the READ-*-EVAL-PRINT function.

;; In an embedded language, one might want to call a different
;; evaluator.
(defun dummy-eval (exp) (list 'dummy-result exp))
;; Test our `evaluator'...
(dummy-eval '(f x 3))
;; Now set things up so that user gets prompted for input nicely and
;; everything he types is DUMMY-EVAL'd instead of Lisp EVAL'd.
(setq read-*-eval-print #'(lambda (exp) `(dummy-eval ',exp))
      *-read-eval-print #'(lambda nil (princ "> ")))
=> (LAMBDA NIL (PRINC "> "))
> (foo 3)


[PDP-10 Only] If not NIL, this variable holds the name of a function of one argument which is to be run prior to the PRINT part of each cycle through the read-eval-print loop. Its argument is the evaluted form returned by the *-READ-EVAL-PRINT part of the loop, and its return value becomes the argument to the READ-EVAL-*-PRINT function.

;; Saving output lines
(let ((temp (make-list 5))) ; We want to remember the last 5 forms
  (setq the-outputs (nconc temp temp) ; Make it a circular list
        read-eval-*-print #'(lambda (x)
                              (setq the-outputs (nthcdr 4. the-outputs))
                              (rplaca the-outputs x)
  (defun last-output (&optional (n 0)) (nth n the-outputs))

(+ 3 4)
=> 7

(list (last-output) (last-output 1))
=> (7 ALL-SET)


[PDP-10 Only] If not NIL, this variable holds the name of a function of no arguments and whose return value is ignored. It is to be run prior to a call to the READ-EVAL-PRINT-* subr, which performs the trailing part of each cycle through the read-eval-print loop.

(setq read-eval-print-* #'(lambda nil (print '*********)))
=> (LAMBDA NIL (PRINT '*********))

(comment Note the dividers between I/O pairs)

(setq read-eval-print-* nil)
=> NIL

(comment No more dividers!)

[Blue Marble]
Climate Change
Is it really safe to play “wait and see?”

The Revised Maclisp Manual (Sunday Morning Edition)
Published Sunday, December 16, 2007 06:17am EST, and updated Sunday, July 6, 2008.
Prev | Index | Next