Trailing-Edge - PDP-10 Archives - decuslib20-01 - decus/20-0004/23lisp.tty
There are no other files named 23lisp.tty in the archive.

                               SECTION 23

                      CLISP - CONVERSATIONAL LISP

23.1 Introduction

The syntax of LISP is very simple, in the sense that it can be described
concisely, but not in the sense  that LISP programs are easy to  read or
write!  This simplicity of syntax is achieved by, and at the expense of,
extensive  use   of  explicit   structuring,  namely   grouping  through
parenthesesization.  Unlike many languages, there are no  reserved words
in  LISP such  as IF,  THEN, AND,  OR, FOR,  DO, BEGIN,  END,  etc., nor
reserved  characters  like +,  -,  *,  /, =,  _,  etc.   This eliminates
entirely  the  need  for  parsers  and  precedence  rules  in  the  LISP
interpreter and compiler, and thereby makes program manipulation of LISP
programs straightforward.   In other  words, a  program that  "looks at"
other LISP  programs does  not need  to incorporate  a lot  of syntactic
information.  For example, a LISP  interpreter can be written in  one or
two pages of LISP code ([McC1], pp. 70-71).   It is for this reason that
LISP  is by  far  the most  suitable, and  frequently  used, programming
language for  writing programs  that deal with  other programs  as data,
e.g., programs that analyze, modify, or construct other programs.

However, it is precisely this same simplicity of syntax that  makes LISP
programs  difficult  to  read  and  write  (especially  for  beginners).
'Pushing down' is something programs do very well, and people do poorly.
As an example, consider the following two "equivalent" sentences:

    "The rat that the  cat that the dog  that I owned chased  caught ate
    the cheese."
    "I own the dog that chased the cat that caught the rat that  ate the

    CLISP was designed and implemented by W. Teitelman.  It is discussed
    in [Tei5].

    except for parentheses (and  period), which are used  for indicating
    structure, and space and end-of-line, which are used  for delimiting


Natural  language  contains   many  linguistic  devices  such   as  that
illustrated  in  the  second sentence  above  for  minimizing embedding,
because embedded  sentences are more  difficult to grasp  and understand
than  equivalent non-embedded  ones (even  if the  latter  sentences are
somewhat  longer).   Similarly, most  high  level  programming languages
offer syntactic devices for reducing apparent depth and complexity  of a
program:  the  reserved words  and  infix operators  used  in ALGOL-like
languages  simultaneously  delimit  operands  and  operations,  and also
convey  meaning to  the programmer.   They are  far more  intuitive than
parentheses.  In  fact, since  LISP uses  parentheses (i.e.,  lists) for
almost all syntactic forms,  there is very little  information contained
in the  parentheses for the  person reading a  LISP program, and  so the
parentheses tend mostly to be ignored: the meaning of a  particular LISP
expression for people is found almost entirely in the words, not  in the
structure.  For example, the following expression
            (COND (EQ N 0) 1) (T TIMES N FACTORIAL ((SUB1 N)))
is recognizable  as FACTORIAL  even though there  are five  misplaced or
missing  parentheses.  Grouping  words together  in parentheses  is done
more for LISP's benefit, than for the programmer's.

CLISP is designed to make INTERLISP programs easier to read and write by
permitting  the user  to  employ various  infix  operators, IF-THEN-ELSE
statements,  FOR-DO-WHILE-UNLESS-FROM-TO-etc.  expressions,   which  are
automatically converted  to equivalent  INTERLISP expressions  when they
are  first  interpreted.  For  example,  FACTORIAL could  be  written in
                 (IF N=0 THEN 1 ELSE N*(FACTORIAL N-1))

Note that this expression would  become an equivalent COND after  it had
been interpreted once,  so that programs that  might have to  analyze or
otherwise process  this expression  could take  advantage of  the simple

There have been similar efforts in other LISP systems, most  notably the
MLISP language at Stanford [Smi1].  CLISP differs from these in  that it
does not attempt to  replace the LISP syntax  so much as to  augment it.
In fact, one of the principal  criteria in the design of CLISP  was that
users  be able  to  freely intermix  LISP  and CLISP  without  having to
identify  which  is  which.   Users  can  write  programs,  or  type  in
expressions for evaluation,  in LISP, CLISP, or  a mixture of  both.  In
this way, users do not have to learn a whole new language and  syntax in
order to be able to use selected facilities of CLISP when and where they
find them useful.

CLISP is  implemented via  the error  correction machinery  in INTERLISP
(see  Section  17).   Thus,  any  expression  that  is  well-formed from
INTERLISP's standpoint will  never be seen by  CLISP (i.e., if  the user
defined  a function  IF,  he would  effectively  turn off  that  part of
CLISP).   This means  that interpreted  programs that  do not  use CLISP
constructs do not pay for its availability by slower execution time.  In
fact, the INTERLISP interpreter does not "know" about CLISP at  all.  It
operates  as before,  and  when an  erroneous form  is  encountered, the
interpreter calls an error routine which in turn invokes  the Do-What-I-
Mean  (DWIM)  analyzer  which  contains  CLISP.   If  the  expression in
question turns  out to  be a CLISP  construct, the  equivalent INTERLISP
form is  returned to  the interpreter. In  addition, the  original CLISP
expression,  is modified  so that  it becomes  the  correctly translated
INTERLISP form.  In this way, the analysis and translation are done only


Integrating CLISP  into the INTERLISP  system (instead of,  for example,
implementing it  as a separate  preprocessor) makes  possible Do-What-I-
Mean features for CLISP constructs as well as for pure LISP expressions.
For example, if the user has defined a function named  GET-PARENT, CLISP
would  know not  to attempt  to interpret  the form  (GET-PARENT)  as an
arithmetic infix  operation.  (Actually,  CLISP would  never get  to see
this  form,  since  it  does  not  contain  any  errors.)   If  the user
mistakenly writes (GET-PRAENT), CLISP would know he  meant (GET-PARENT),
and not (DIFFERENCE GET PRAENT), by using the information that PRAENT is
not the name of  a variable, and that GET-PARENT  is the name of  a user
function  whose  spelling  is  "very  close"  to  that   of  GET-PRAENT.
Similarly,  by using  information  about the  program's  environment not
readily available to a preprocessor, CLISP can successfully  resolve the
following sorts of ambiguities:

1)  (LIST X*FACT N),  where  FACT  is  the  name  of  a  variable, means
    (LIST (X*FACT) N).

2)  (LIST X*FACT N),  where  FACT is  not  the name  of  a  variable but
    instead is the name of a function, means (LIST X*(FACT N)),  i.e., N
    is FACT's argument.

3)  (LIST X*FACT(N)), FACT the name of a function (and not the name of a
    variable), means (LIST X*(FACT N)).

4)  cases (1), (2) and (3) with FACT misspelled!

The first expression is correct both from the standpoint of CLISP syntax
and  semantics and  the  change would  be  made without  the  user being
notified.  In the other cases,  the user would be informed  or consulted
about what  was taking  place.  For  example, to  take an  extreme case,
suppose the expression (LIST X*FCCT N) were encountered, where there was
both a  function named FACT  and a variable  named FCT.  The  user would
first be asked if FCCT were  a misspelling of FCT.  If he said  YES, the
expression would be interpreted as (LIST (X*FCT) N).  If he said NO, the

    Through this discussion, we speak of CLISP or DWIM asking  the user.
    Actually, if the expression in question was typed in by the user for
    immediate   execution,  the   user   is  simply   informed   of  the
    transformation,  on  the  grounds  that  the  user  would  prefer an
    occasional   misinterpretation   rather   than   being  continuously
    bothered, especially since he can always retype what he  intended if
    a mistake  occurs, and  ask the programmer's  assistant to  UNDO the
    effects of the mistaken operations if necessary. For transformations
    on expressions in user  programs, the user can inform  CLISP whether
    he wishes  to operate in  CAUTIOUS or TRUSTING  mode. In  the former
    case   (most   typical)  the   user   will  be   asked   to  approve
    transformations, in  the latter,  CLISP will operate  as it  does on
    type-in, i.e., perform the transformation after informing the user.


user would  be asked if  FCCT were  a misspelling of  FACT, i.e.,  if he
intended X*FCCT N to mean X*(FACT N).  If he said YES to  this question,
the indicated  transformation would  be performed.  If  he said  NO, the
system would then ask if  X*FCCT should be treated as CLISP,  since FCCT
is not the name of a  (bound) variable.  If he said YES,  the expression
would  be  transformed,  if  NO,  it  would  be  left  alone,  i.e.,  as
(LIST X*FCCT N).  Note that we  have not even considered the  case where
X*FCCT is  itself a  misspelling of  a variable  name, e.g.,  a variable
named XFCT (as with  GET-PRAENT).  This sort of transformation  would be
considered after the user said NO to X*FCCT N -> X*(FACT N).   The graph
of the possible interpretations  for (LIST X*FCCT N) where FCT  and XFCT
are the names of variables, and FACT is the name of a function, is shown
in Figure 23-1 below.

    This question is important because many INTERLISP users already have
    programs that employ  identifiers containing CLISP  operators. Thus,
    if CLISP encounters the expression  A/B in a context where  either A
    or B are not the names of variables, it will ask the user if  A/B is
    intended  to be  CLISP, in  case the  user really  does have  a free
    variable named A/B.


                             Figure 23-1


The final states for the various terminal nodes shown in the graph are:

    1:   (LIST (TIMES X FCT) N)
    2:   (LIST (TIMES X (FACT N)))
    3:   (LIST XFCT N)
    4:   (LIST (TIMES X FCCT) N)
    5:   (LIST X*FCCT N)

CLISP can also handle parentheses errors caused by typing 8 or 9 for "("
or ")".  (On most terminals, 8  and 9 are the lower case  characters for
"(" and ")",  i.e., "(" and "8"  appear on the same  key, as do  ")" and
"9".)  For example, if the user writes N*8FACTORIAL N-1, the parentheses
error can be detected and fixed before the infix operator * is converted
to  the INTERLISP  function TIMES.   CLISP is  able to  distinguish this
situation from cases like N*8*X meaning (TIMES N 8 X), or N*8X, where 8X
is  the  name  of  a variable,  again  by  using  information  about the
programming environment.  In fact, by integrating CLISP with DWIM, CLISP
has been made sufficiently tolerant of errors that almost everything can
be  misspelled!   For  example,  CLISP  can  successfully  translate the
definition of FACTORIAL:

                (IFF N=0 THENN1 ESLE N*8FACTTORIALNN-1)

to  the  corresponding COND,  while  making 5  spelling  corrections and
fixing the parenthesis error.

This sort  of robustness  prevails throughout  CLISP.  For  example, the
iterative statement permits the user to say things like:


However,  the user  can  also write  OLD (X_M),  (OLD X_M), (OLD (X_M)),
permute      the      order      of      the       operators,      e.g.,
DO PRINT X TO N FOR OLD X_M WHILE PRIMEP X, omit either or both  sets of
parentheses, misspell any  or all of the  operators FOR, OLD,  FROM, TO,
DO, or WHILE, or leave out the word DO entirely!  And, of course, he can

    CLISP also contains a facility for converting from INTERLISP back to
    CLISP,  so  that after  running  the above  incorrect  definition of
    FACTORIAL, the user could "CLISPIFY" the now correct LISP version to
    obtain (IF N=0 THEN 1 ELSE N*(FACTORIAL N-1)).

    This expression should be self explanatory, except possibly  for the
    operator OLD, which says X is to be the variable of iteration, i.e.,
    the one to be stepped from N to M, but X is not to be  rebound. Thus
    when this loop finishes execution, X will be equal to N+1.


also misspell PRINT, PRIMEP, M or N!

CLISP is well  integrated into the  INTERLISP system.  For  example, the
above iterative statement  translates into an equivalent  INTERLISP form
using PROG, COND, GO, etc.  When the interpreter subsequently encounters
this  CLISP  expression,  it  automatically  obtains  and  evaluates the
translation.  Similarly, the compiler "knows" to compile  the translated
form.   However,   if  the  user   PRETTYPRINTs  his  program,   at  the
corresponding point  in his function,  PRETTYPRINT "knows" to  print the
original CLISP.  Similarly, when the user edits his program,  the editor
keeps the translation invisible to  the user.  If the user  modifies the
CLISP,  the translation  is automatically  discarded and  recomputed the
next time the expression is evaluated.

In short, CLISP is not a language at all, but rather a system.  It plays
a role  analagous to  that of the  programmer's assistant  (Section 22).
Whereas the  programmer's assistant is  an invisible  intermediary agent
between the user's console  requests and the INTERLISP  executive, CLISP
sits between the user's programs and the INTERLISP interpreter.

Only a  small effort  has been devoted  to defining  the core  syntax of
CLISP.  Instead, most of the effort has been concentrated on providing a
facility which "makes sense" out of the input expressions  using context
information as well as built-in and acquired information about  user and
system programs.  It  has been said that  communication is based  on the
intention of the speaker to  produce an effect in the  recipient.  CLISP
operates under the  assumption that what the  user said was  intended to
represent a meaningful operation, and therefore tries very hard  to make
sense out of it.  The motivation behind CLISP is not to provide the user
with many different ways of saying the same thing, but to enable  him to
worry less  about the  syntactic aspects of  his communication  with the
system.  In other words,  it gives the user  a new degree of  freedom by
permitting him to concentrate more  on the problem at hand,  rather than
on translation into a formal and unambiguous language.

    In this example, the only  thing the user could not misspell  is the
    first X, since it specifies  the name of the variable  of iteration.
    The other two instances of X could be misspelled.

 (SETQ X (ADD1 X))
 (GO $$LP))

    See page 23.26, for discussion of how translations are stored.


23.2 CLISP Syntax

Throughout  CLISP,  a  non-atomic  form, i.e.,  a  list,  can  always be
substituted  for  a  variable,  and  vice  versa,  without  changing the
interpretation.  For  example, if  the value  of (FOO X)  is A,  and the
value of (FIE Y) is B, then (LIST (FOO X)+(FIE Y)) has the same value as
(LIST A+B).  Note that the first  expression consists of a list  of four
elements: the  atom "LIST", the  list "(FOO X)", the  atom "+",  and the
list "(FIE X)", whereas the second expression, (LIST A+B), consists of a
list of only  two elements: the atom  "LIST" and the atom  "A+B".  Since
(LIST (FOO X)+(FIE Y))         is         indistinguishable         from
(LIST (FOO X) + (FIE Y)) because spaces before or after parentheses have
no  effect on  the  INTERLISP READ  program,   to  be  consistent, extra
spaces have no effect on atomic operands either.  In other  words, CLISP
will  treat  (LIST A+ B),  (LIST A +B),  and  (LIST A + B)  the  same as

23.3 Infix Operators

CLISP  recognizes the  arithmetic infix  operators +,  -, *,  /,  and ^.
These  are converted  to IPLUS,  IDIFFERENCE (or  in the  case  of unary
minus,  IMINUS), ITIMES,  IQUOTIENT,  and EXPT.    The  usual precedence
rules apply (although these can be easily changed by the  user),   i.e.,
* has higher precedence than + so that A+B*C is the same as A+(B*C), and
both *  and / are  lower than ^  so that 2*X^2  is the same  as 2*(X^2).
Operators of the same precedence  group from left to right,  e.g., A/B/C
is  equivalent to  (A/B)/C.  Minus  is binary  whenever  possible, i.e.,
except when it is the first operator  in a list, as in (-A) or  (-A), or

    CLISP does not use its  own special READ program because  this would
    require the user  to explicitly identify CLISP  expressions, instead
    of being able to intermix INTERLISP and CLISP.

    The I in IPLUS denotes integer arithmetic, i.e., IPLUS  converts its
    arguments to integers, and returns an integer value.  INTERLISP also
    contains  floating  point  arithmetic  functions  as  well  as mixed
    arithmetic  functions (see  Section 13).  Floating  point arithmetic
    functions are used in the translation if one or both of the operands
    are  themselves floating  point numbers,  e.g., X+1.5  translates as
    (FPLUS X 1.5). In addition, CLISP contains a facility  for declaring
    which type of  arithmetic is to be  used, either by making  a global
    declaration, or by separate declarations about  individual functions
    or variables. See section on declarations, page 23.29.

    The complete  order of  precedence for CLISP  operators is  given in
    Figure 23-2, page 23.12.


                                                         13 14
when it immediately follows another operator, as in A*-B.   

Note that grouping with parentheses  can always be used to  override the
normal  precedence  grouping,  or  when  the  user  is  not  sure  how a
particular expression will parse.

CLISP also  recognizes as infix  operators =, GT,  LT, GE, and  LE,   as
well as various predicates, e.g., MEMBER, AND, OR, EQUAL, etc.    AND is
higher than  OR, e.g., (X OR Y AND Z)  is the same  as (X OR (Y AND Z)),
and both  AND and  OR are lower  than the  other infix  operators, e.g.,
(X AND Y EQUAL Z) is the same as (X AND (Y EQUAL Z)).  All of  the infix
predicates   have   lower  precedence   than   INTERLISP   forms,  i.e.,
(FOO X GT FIE Y) is  the same as  ((FOO X) GT (FIE Y)), since it  is far
more common to apply a predicate to two forms, than to use a  Boolean as
an argument to a function, e.g., (FOO (X GT (FIE Y))).   However, again,
the user can easily change this.

Note that  only single  character operators,  e.g., +,  _, =,  etc., can
appear in the interior of an  atom. All other operators must be  set off
from identifiers with spaces.  For example, XLTY will not  be recognized

    There are some do-what-I-mean features associated with  Unary minus,
    as in (LIST -X Y). See section on operation, page 23.55.

    Note that + in front of  a number will disappear when the  number is
    read,  e.g., (FOO X +2)  is indistinguishable  from  (FOO X 2). This
    means  that  (FOO X +2) will  not  be interpreted  as  CLISP,  or be
    converted to  (FOO (IPLUS X 2)). Similarly,  (FOO X -2) will  not be
    interpreted the same as (FOO X-2). To circumvent this, always type a
    space  between the  + or  - and  a number  if an  infix  operator is
    intended, e.g., write (FOO X + 2).

    Greater Than, Less Than, Greater than or Equal to, and Less  than or
    Equal to, respectively.  GT, LT, GE, and LE are all affected  by the
    same  declarations as  +  and *,  with  the initial  default  to use

    Currently  the  complete  list  is  MEMBER,  MEMB,   FMEMB,  ILESSP,
    New infix operators can be easily added, as described in the section
    on CLISP  internal conventions, page  23.58. Spelling  correction on
    misspelled infix  operators is peformed  using clispinfixsplst  as a
    spelling list.



: is an infix operator  used in CLISP for extracting  substructures from
lists,   e.g., X:3 specifies the 3rd element of X,  (FOO Y)::2 specifies
the  second tail  of  (FOO Y), i.e.,  (CDDR (FOO Y)), and  Z:1:2  is the
second  element  of the  first  element of  Z,  or  (CADAR Z).  Negative
numbers may  be used  to indicate position  counting from  the end  of a
list, e.g., X:-1 is the  last element of X, or (CAR (LAST X)),  X::-1 is
the last tail, i.e., (LAST X).

_ is used to indicate assignment, e.g., X_Y translates  to (SETQ X Y).
   In conjunction with  : and ::, _ can  also be used to perform  a more
general type of assignment, namely one involving structure modification.
For example, X:2_Y means make the second element of X be Y, in INTERLISP

    In some  cases, DWIM will  be able to  diagnose this situation  as a
    run-on spelling error, in which case after the atom is  split apart,
    CLISP will be able to perform the indicated transformation.

    The record facility, page 23.39, provides another way  of extracting
    substructures by allowing  the user to  assign names to  the various
    parts of  the structure  and then  retrieve from  or store  into the
    corresponding structure  by name. The  pattern match  facility, page
    23.31, also can be used  to extract substructure. : is also  used to
    indicate both record and pattern match operations.

    The interpretation  of negative numbers  can be explained  neatly in
    terms  of  edit commands:  :-n  returns what  would  be  the current
    expression after  executing the  command -n,  and ::-n  returns what
    would be the current expression after executing -n followed by UP.

    If x does not have a value, and is not the name of one of  the bound
    variables of the function  in which it appears,  spelling correction
    is attempted. However, since this may simply be a case  of assigning
    an initial value  to a new free  variable, DWIM will always  ask for
    approval before making the correction.

    Note that an atom of the  form X_Y, appearing at the top level  of a
    PROG, will not be  recognized as an assignment statement  because it
    will be interpreted  as a PROG  label by the  INTERLISP interpreter,
    and therefore will not cause an error, so DWIM and CLISP  will never
    get to see it. Instead, one must write (X_Y).


                         22 23
terms (RPLACA (CDR X) Y).      Negative numbers can also be  used, e.g.,
X:-2_Y.   _ is  also used to  indicate assignment in  record operations,
page 23.39, and pattern match operations, page 23.31.

_ has different precedence on the left from on the right.  On  the left,
_ is  a "tight" operator,  i.e., high precedence,  so that A+B_C  is the
same as A+(B_C).  On the right, _ has broader scope so that A_B+C is the
same as A_(B+C).

On typein, $_form (alt-mode_form)  is equivalent to set the  "last thing
mentioned".    For example,  immediately  after examining  the  value of
LONGVARIABLENAME, the user could set it by typing $_ followed by a form.

23.4 Prefix Operators

CLISP recognizes ' and ~ as  prefix operators. ' means QUOTE when  it is
the first character in an identifier, and is ignored when it is  used in
the interior of an  identifier.  Thus, X='Y means  (EQ X (QUOTE Y)), but
X=CAN'T means (EQ X CAN'T), not (EQ X CAN) followed by  (QUOTE T).  This
enables users  to have variable  and function names  with ' in  them (so
long as the ' is not the first character).

Following ', all operators are  ignored for the rest of  the identifier,
e.g.,   '*A  means   (QUOTE *A),   and  'X=Y   means   (QUOTE X=Y),  not

On      typein,      '$ (i.e., 'alt-mode)      is      equivalent     to
(QUOTE value-of-lastword) (see Section 17).  For example,  after calling
prettyprint on LONGFUNCTION, the  user could move its definition  to FOO

    Note that the value of this operation is the value of  rplaca, which
    is the corresponding node.

    The user can  indicate he wants  /rplaca and /rplacd  used (undoable
    version  of  rplaca and  rplacd,  see Section  22),  or  frplaca and
    frplacd  (fast versions  of rplaca  and rplacd,  see Section  5), by
    means  of  declarations (page  23.29).  The initial  default  is for
    rplaca and rplacd.

    which translates to (RPLACA (NLEFT X 2) Y).

    i.e., is equivalent to (SETQ lastword form). See Section 17.

    To write (EQ (QUOTE X) Y),  one writes Y='X,  or 'X =Y. This  is one
    place where an extra space does make a difference.


by typing (MOVD '$ 'FOO).

~ means NOT.  ~ can negate a form, as in ~(ASSOC X Y), or ~X,  or negate
an infix operator, e.g., (A ~GT B) is the same as (A LEQ B).   Note that
~A=B means (EQ (NOT A) B).

                 Order of Precedence of CLISP operators

                        _ (left precedence)
                        - (unary), ~
                        *, /
                        +, - (binary)
                        _ (right precedence)
                        INTERLISP forms
                        LT, GT, EQUAL, MEMBER, etc.
                        IF, THEN, ELSEIF, ELSE
                        iterative statement operators

                              Figure 23-2

    Not     (MOVD $ 'FOO),    which     would    be     equivalent    to
    (MOVD LONGFUNCTION 'FOO),  and  would  (probably)  cause   a  U.B.A.
    LONGFUNCTION error, nor  MOVD($ FOO), which would actually  move the
    definition of $ to FOO, since DWIM and the spelling  corrector would
    never be invoked.

    _ has a  different left and right  precedence, e.g., A+B_C+D  is the
    same as A+(B_(C+D)). In other words, _ has minimal scope on the left
    and maximal scope on the right.

    When ~  negates an operator,  e.g., ~=, ~LT,  the two  operators are
    treated as a single operator whose precedence is that of  the second
    operator. When  ~ negates a  function, e.g., (~FOO X Y),  it negates
    the whole form, i.e., (~(FOO X Y)).


23.5 Constructing Lists - the <,> operators  

Angle brackets  are used  in CLISP to  indicate list  construction.  The
appearance of a "<" corresponds to a "(" and indicates that a list is to
be constructed containing all the elements up to the  corresponding '>'.
For example, <A B <C>> translates to (LIST A B (LIST C)).  ! can be used
to indicate that the next expression is to be inserted in the list  as a
segment,   e.g.,   <A B ! C>  translates   to   (CONS A (CONS B C))  and
<! A ! B C> to  (APPEND A B (LIST C)). !! is  used to indicate  that the
next expression  is to be  inserted as a  segment, and  furthermore, all
list structure to  its right in the  angle brackets is to  be physically
attached  to  it,   e.g.,  <!! A B>  translates  to   (NCONC1 A B),  and
                                      31  32
<!!A !B !C> to (NCONC A (APPEND B C)).       Note that  <, !, !!,  and >
need  not  be separate  atoms,  for example,  <A B ! C>  may  be written
equally well  as < A B !C >.  Also,  arbitrary INTERLISP or  CLISP forms
may  be  used  within  angle  brackets.   For  example,  one  can  write
<FOO_(FIE X) ! Y>   which  translates   to  (CONS (SETQ FOO (FIE X)) Y).
CLISPIFY  converts expressions  in  cons, list,  append,  nconc, nconc1,
/nconc, and /nconc1 into equivalent CLISP expressions using <, >, !, and

Note: angle brackets differ from other CLISP operators in that  they act
more like brackets than  operators. For example, <A B 'C>  translates to
(LIST A B (QUOTE C)) even though following ', all operators  are ignored
for  the rest  of the  identifier.   Note  however that  <A B ' C> D> is
equivalent to (LIST A B (QUOTE C>) D).


CLISP   translates   expressions   employing   IF|THEN|ELSEIF|ELSE  into
equivalent conditional expressions.   The segment between  IF|ELSEIF and
the next  THEN corresponds to  the predicate of  a COND clause,  and the
segment  between THEN  and the  next ELSE|ELSEIF  as  the consequent(s).
ELSE is the same as ELSEIF T THEN.

    The <,> operator was written by P.C. Jackson.

    Not  (NCONC (APPEND A B) C), which  would have  the same  value, but
    would attach C to B, and not attach either to A.

    The user can indicate /nconc or /nconc1 be used instead of nconc and
    nconc1 by declarations.

    Only if  a previous  unmatched < has  been seen,  e.g., (PRINT 'A>B)
    will print the atom A>B.


IF, THEN, ELSE,  and ELSEIF are of  lower precedence than all  infix and
prefix operators, as well as INTERLISP forms, so that parentheses can be
omitted between IF|ELSEIF, and THEN.   For example, (IF FOO X Y THEN --)
is equivalent to (IF (FOO X Y) THEN --).   Similarly, CLISP treats (IF X
THEN FOO  X Y  ELSE --)  as equivalent  to (IF X THEN (FOO X Y) ELSE --)
because it does not "make sense" to evaluate a variable for  effect.  In
other  words,  even   if  FOO  were  also   the  name  of   a  variable,
(COND (X FOO X Y))  doesn't make  sense.  Essentially,  CLISP determines
whether the segment between THEN and the next ELSE|ELSEIF corresponds to
one    form    or     several    and    acts     accordingly.      Thus,
(IF -- THEN (FOO X) Y ELSE --)   corresponds  to   a  clause   with  two
consequents.  Similarly,  (IF -- THEN FOO_X Y ELSE --) corresponds  to a
clause    with    two     consequents,    and    is     equivalent    to
(IF -- THEN (FOO_X) Y ELSE --).

23.7 Iterative Statements

The following is an example of a CLISP iterative statement:

              (WHILE X_(READ)~='STOP DO (PRINT (EVAL X)))

This statement says "READ  an expression and set X  to it.  If X  is not
equal  to  the  atom  STOP,  then  evaluate  X,  print  the  result, and

    IF,  THEN,  ELSE,  and  ELSEIF  can  also  be  misspelled.  Spelling
    correction is performed using clispifwordsplst as a spelling list.

    If FOO is  the name of a  variable, IF FOO THEN -- is  translated as
    (COND (FOO --)) even if FOO is  also the name of a function.  If the
    functional  interpretation is  intended, FOO  should be  enclosed in
    parentheses,      e.g.,      IF (FOO) THEN --.      Similary     for

    occasionally interacting with the user to resolve ambiguous cases.

    To write the equivalent of  a singleton cond clause, i.e.,  a clause
    with a predicate but  no consequent, write either  nothing following
    the THEN, or omit the THEN entirely, e.g., (IF (FOO X) THEN ELSEIF -
    -) or (IF (FOO X) ELSEIF --), meaning  if (FOO X) is not NIL,  it is
    the value of the cond.



The i.s. (iterative statement) in its various forms permits the  user to
specify  complicated  iterative  statements  in  a  straightforward  and
visible  manner.  Rather  than  the user  having to  perform  the mental
transformations  to  an  equivalent  INTERLISP  form  using  PROG, MAPC,
MAPCAR, etc., the  system does it  for him.  The  goal was to  provide a
robust and  tolerant facility  which could  "make sense"  out of  a wide
class of  iterative statements.  Accordingly,  the user should  not feel
obliged  to  read  and  understand in  detail  the  description  of each
operator given below in order to use iterative statements.

Currently, the following i.s. operators are implemented: FOR, BIND, OLD,
FIRST,  FINALLY,  EACHTIME.   Their function  is  explained  below.  New
operators can be  defined as described  on page 23.24.   Misspellings of
operators are  recognized and  corrected.   The  order of  appearance of
operators is never important;   CLISP scans the entire  statement before
it begins to construct the equivalent INTERLISP form.

DO form            specifies what is to  be done at each  iteration.  DO
                   with no  other operator  specifies an  infinite loop.
                   If some explicit or implicit terminating condition is
                   specified, the value  of the i.s. is  NIL.  Translate
                   to MAPC or MAP whenever possible.

COLLECT form       like DO, except specifies  that the value of  form at
                   each iteration is to be collected in a list, which is
                   returned as the value of the i.s. when it terminates.
                   Translates  to  MAPCAR,  MAPLIST  or  SUBSET whenever

    The statement translates to:

    using the spelling list clispforwordsplst.

    DWIM and CLISP  are invoked on  iterative statements because  car of
    the  i.s. is  not the  name of  a function,  and hence  generates an
    error. If the user  defines a function by  the same name as  an i.s.
    operator, e.g., WHILE,  TO, etc., the  operator will no  longer have
    the CLISP interpretation when it appears as car of a  form, although
    it will continue to be treated as an i.s. operator if it  appears in
    the interior  of an  i.s. To alert  the user,  a warning  message is



JOIN form          like  DO,  except   that  the  values   are  NCONCed.
                   Translates to MAPCONC or MAPCON whenever possible.

SUM form           like DO, except specifies that the values of  form at
                   each iteration be added together and returned  as the
                   value of the i.s.,  e.g., (FOR I FROM 1 TO 5 SUM I^2)
                   is equal to 1+4+9+16+25.

COUNT form         like DO, except counts  number of times that  form is
                   true, and returns that count as its value.

ALWAYS form        like DO,  except returns  T if the  value of  form is
                   non-NIL for  all iterations (returns  NIL as  soon as
                   the     value    of     form    is     NIL),    e.g.,
                   (FOR X IN Y ALWAYS (ATOM X))    is   the    same   as
                   (EVERY Y (FUNCTION ATOM)).

NEVER form         like ALWAYS, except returns T if the value of form is
                   never true,  i.e., NEVER form  is the same  as ALWAYS

THEREIS form       returns the first value of the i.v. for which form is
                   non-NIL,  e.g.,  (FOR X IN Y THEREIS NUMBERP) returns
                   the  first  number   in  Y,  and  is   equivalent  to

    when COLLECT translates to a PROG, e.g., a WHILE operator appears in
    the iterative statement, the translation employs an open tconc using
    two  pointers similar  to that  used by  the compiler  for compiling

    /NCONC, /MAPCONC, and /MAPCON are used when the declaration UNDOABLE
    is in effect.

    iplus, fplus, or plus will be used for the translation  depending on
    the declarations in effect.


                   (CAR (SOME Y (FUNCTION NUMBERP))).

DO, COLLECT,  JOIN, SUM, ALWAYS,  NEVER, and THEREIS  are examples  of a
certain  kind  of  i.s.  operator  called  an  i.s.type.   The  i.s.type
specifies what is to be  done at each iteration.  Its operand  is called
the body of the iterative  statement.  Each i.s. must have one  and only
one i.s.type.

FOR var            specifies the variable  of iteration, or  i.v., which
                   is used in conjunction with IN, ON, FROM, TO, and BY.
                   The variable  is rebound for  the scope of  the i.s.,
                   except when modified by OLD as described below.

FOR vars           vars  a list  of variables,  e.g., FOR (X Y Z) IN --.
                   The first  variable is the  i.v., the rest  are dummy
                   variables.  See BIND below.

OLD var            indicates   var   is  not   to   be   rebound,  e.g.,
                   (FOR OLD X FROM 1 TO N DO -- UNTIL --),

BIND var, vars     used    to    specify    dummy    variables,    e.g.,
                   FOR (X Y Z) IN --       is        equivalent       to
                   FOR X BIND (Y Z) IN --.   BIND  can  be  used without
                   FOR.  For example, in the i.s. shown on page 23.14, X
                   could      be     made      local      by     writing
                   (BIND X WHILE X_(READ)~='STOP...).

Note:    FOR, OLD,  and BIND  variables can be  initialized by  using _,
e.g., (FOR OLD (X_form) BIND (Y_form)...).

IN form            specifies that  the i.s.  is to  iterate down  a list
                   with  the  i.v.  being  reset  to  the  corresponding
                   element    at   each    iteration.     For   example,
                   FOR X IN Y DO --            corresponds            to
                   (MAPC Y (FUNCTION (LAMBDA (X) --))).  If no  i.v. has
                   been   specified,   a   dummy   is   supplied,  e.g.,
                   IN Y COLLECT CADR       is        equivalent       to
                   (MAPCAR Y (FUNCTION CADR)).

ON form            same  as IN  except  that the  i.v. is  reset  to the

    THEREIS returns the i.v. instead  of the tail (as does  the function
    some)  in  order  to  provide  an  interpretation   consistent  with
    statements such  as (FOR I FROM 1 TO 10 THEREIS --), where  there is
    no tail. Note that (SOME Y (FUNCTION NUMBERP)) is equivalent to


                   corresponding  tail  at  each  iteration.    Thus  IN
                   corresponds to  MAPC, MAPCAR,  and MAPCONC,  while ON
                   corresponds to MAP, MAPLIST, and MAPCON.

IN OLD var         specifies that the i.s. is to iterate down  var, with
                   var itself being  reset to the corresponding  tail at
                   each          iteration,          e.g.,         after
                   (FOR X IN OLD L DO -- UNTIL --)  finishes, L  will be
                   some tail of its original value.

IN OLD (var_form)  same as IN OLD var, except var is first set  to value
                   of form.

ON OLD var         same as IN  OLD var except the  i.v. is reset  to the
                   current value of var at each iteration, instead of to

ON OLD (var_form)  same as ON OLD var, except var is first set  to value
                   of form.

WHEN form          provides a way of excepting certain  iterations.  For
                   example,        (FOR X IN Y COLLECT X WHEN NUMBERP X)
                   collects only the elements of Y that are numbers.

UNLESS form        same as WHEN except for the difference in sign, i.e.,
                   WHEN Z is the same as UNLESS ~Z.

WHILE form         provides a  way of terminating  the i.s.   WHILE form
                   evaluates  form  before each  iteration,  and  if the
                   value is NIL, exits.

UNTIL form         Same as  WHILE except for  difference in  sign, i.e.,
                   WHILE form is equivalent to UNTIL ~form.

UNTIL n            n a number, equivalent to UNTIL (i.v. GT n).

REPEATWHILE form   same as WHILE except the test is performed  after the
                   evalution of the body,  but before the i.v.  is reset
                   for the next iteration.

REPEATUNTIL form/n same as UNTIL, except the test is performed after the
                   evaluation of the body.

FROM form          is used to specify  an initial value for  a numerical
                   i.v.   The  i.v. is  automatically  incremented  by 1
                   after each iteration (unless BY is specified).  If no


                   i.v. has been specified, a dummy i.v. is supplied and
                   initialized, e.g., (COLLECT SQRT FROM 2 TO 5) returns
                   (1.414 1.732 2.0 2.236).

TO form            is used  to specify the  final value for  a numerical
                   i.v.   If  FROM   is  not  specified,  the   i.v.  is
                   initialized to 1.  If  no i.v. has been  specified, a
                   dummy i.v. is supplied and initialized.  If BY is not
                   specified, the i.v. is automatically incremented by 1
                   after each iteration.    When the i.v.  is definitely
                   being incremented, i.e., either BY is  not specified,
                   or  its  operand  is  a  positive  number,  the  i.s.
                   terminates when  the i.v. exceeds  the value  of form
                   (which   is   reevaluated   each   iteration)   e.g.,
                   (FOR X FROM 1 TO 10 --),     is     equivalent     to
                   (FOR X FROM 1 UNTIL (X GT 10) --).

                   Similarly,   when  the   i.v.  is   definitely  being
                   decremented the i.s. terminates when the i.v. becomes
                   less than the value of form (see description of BY).

BY form (with IN/ON)
                   If IN or  ON have been  specified, the value  of form
                   determines the tail for the next iteration,  which in
                   turn determines the  value for the i.v.  as described
                   earlier, i.e., the  new i.v. is  car of the  tail for
                   IN, the tail itself for ON.  In conjunction  with IN,
                   the user can refer to the current tail within form by
                   using  the  i.v.  or  the  operand  for  IN/ON, e.g.,
                   (FOR Z IN L BY (CDDR Z) ...)                       or
                   (FOR Z IN L BY (CDDR L) ...).   At  translation time,
                   the  name of  the internal  variable which  holds the
                   value of the current tail is substituted for the i.v.
                   throughout         form.          For        example,
                   (FOR X IN Y BY (CDR (MEMB 'FOO (CDR X))) COLLECT X)
                   specifies  that  after  each  iteration,  cdr  of the
                   current tail is to be searched for the atom  FOO, and
                   (cdr of)  this latter  tail to be  used for  the next

BY form (without IN/ON)
                   If IN or ON have not been used, BY specifies  how the
                   i.v. itself is reset  at each iteration.  If  FROM or
                   TO  have  been specified,  the  i.v. is  known  to be
                   numerical, so the new i.v. is computed by  adding the

    except when both the operands  to TO and FROM are numbers,  and TO's
    operand is  less than FROM's  operand, e.g., FROM 10 TO 1,  in which
    case the  i.v. is  decremented by  1 after  each iteration.  In this
    case, the i.s. terminates when the i.v. becomes less than  the value
    of form.


                   value  of  form (which is reevaluated each iteration)
                   to   the   current   value   of   the   i.v.,   e.g.,
                   (FOR N FROM 1 TO 10 BY 2 COLLECT N)  makes a  list of
                   the first five odd numbers.

                   If form is  a positive number,   the  i.s. terminates
                   when the value of the i.v. exceeds the value  of TO's
                   operand.   If form  is  a negative  number,  the i.s.
                   terminates when  the value of  the i.v.  becomes less
                   than           TO's           operand,          e.g.,
                   (FOR I FROM N TO M BY -2 UNTIL (I LT M) ...).
                   Otherwise,   the  terminating   condition   for  each
                   iteration  depends  on  the value  of  form  for that
                   iteration: if form < 0, the test is whether  the i.v.
                   is less  than TO's operand,  if form > 0 the  test is
                   whether the i.v.  exceeds TO's operand,  otherwise if
                   form=0, the i.s. terminates unconditionally.

                   If FROM or  TO have not  been specified, the  i.v. is
                   simply  reset  to  the  value  of  form   after  each
                   iteration,    e.g.,     (FOR I FROM N BY 2 ...)    is
                   equivalent to (FOR I_N BY (IPLUS I 2) ...).

FIRST form         form is  evaluated once  before the  first iteration,
                   (FOR X Y Z IN L -- FIRST (FOO Y Z)), and FOO could be
                   used to initialize Y and Z.

FINALLY form       form  is evaluated  after the  i.s.  terminates.  For
                   (FOR X IN L BIND Y_0 DO (IF ATOM X THEN Y_Y+1)
                   FINALLY (RETURN Y)) will  return the number  of atoms
                   in L.

EACHTIME form      form is evaluated at the beginning of  each iteration
                   before, and regardless of, any testing.  For example,
                   consider (FOR  I FROM  1 TO  N DO  (... (FOO  I) ...)
                   UNLESS (...  (FOO I) ...)  UNTIL (... (FOO  I) ...)).
                   The user  might want to  set a temporary  variable to
                   the value of (FOO I)  in order to avoid  computing it
                   three times each iteration. However,  without knowing

    form itself, not its value, which in general CLISP would have no way
    of knowing in advance.

    A  temporary variable  is used  so that  x is  only  evaluated once.
    However, code  for TO's  operand appears  twice in  the translation,
    even though it is evaluated only once.


                   the translation, he would not know whether to put the
                   assignment in  the operand to  DO, UNLESS,  or UNTIL,
                   i.e.,  which one  would  be executed  first.   He can
                   avoid this problem by simply writing  EACHTIME J_(FOO

AS var             is used to  specify an iterative  statement involving
                   more    than    one    iterative    variable,   e.g.,
                   (FOR X IN Y AS U IN V DO --)  corresponds  to  map2c.
                   The  i.s.  terminates  when  any  of  the terminating
                   conditions          are           met,          e.g.,
                   (FOR X IN Y AS I FROM I TO 10 COLLECT X) makes a list
                   of  the  first ten  elements  of Y,  or  however many
                   elements there are on Y if less than 10.

                   The operand to AS,  var, specifies the new  i.v.  For
                   the remainder  of the  i.s., or  until another  AS is
                   encountered, all operators refer to the new i.v.  For
                   example, (FOR I FROM I TO N1 AS J FROM 1 TO N2 BY 2
                   AS K FROM N3 TO 1 BY -1 --) terminates when I exceeds
                   N1, or J exceeds N2, or K becomes less than 1.  After
                   each iteration, I is incremented by 1, J by 2,  and K
                   by -1.


1.  Lowercase  versions  of all  i.s.  operators are  equivalent  to the
    uppercase, e.g., (for X in Y ...).

2.  Each i.s. operator is of lower precedence than all  INTERLISP forms,
    so  parentheses around  the  operands can  be omitted,  and  will be
    supplied  where  necessary,   e.g.,  BIND (X Y Z)  can   be  written
    BIND X Y Z, OLD (X_form)   as   OLD X_form,    WHEN (NUMBERP X)   as
    WHEN NUMBERP X, etc.

3.  RETURN  or  GO may  be  used in  any  operand.  (In  this  case, the
    translation of the iterative statement will always be in the form of
    a PROG, never a mapping function.) RETURN means return from the i.s.
    (with the indicated value), not  from the function in which  the i.s
    appears.  GO refers  to a label elsewhere  in the function  in which
    the i.s.  appears, except for  the labels $$LP,$$ITERATE,  and $$OUT
    which are reserved, as described in 6 below.

4.  In the  case of FIRST,  FINALLY, EACHTIME, or  one of  the i.s.oprs,
    e.g., DO, COLLECT, SUM, etc.,  the operand can consist of  more than
    one form,  e.g., COLLECT (PRINT X:1) X:2, in  which case a  PROGN is

5.  Each operand  can be the  name of  a function, in  which case  it is


                                                     48 49 50
    applied      to       the      (last)       i.v.,              e.g.,
    FOR X IN Y DO PRINT WHEN NUMBERP,      is      the      same      as
    FOR X IN Y DO (PRINT X) WHEN (NUMBERP X).  Note  that the  i.v. need
    not be  explicitly specified, e.g.,  IN Y DO PRINT WHEN NUMBERP will

6.  While the exact  form of the  translation of an  iterative statement
    depends on which operators are  present, a PROG will always  be used
    whenever  the  i.s.  specifies  dummy  variables,  i.e.,  if  a BIND
    operator appears, or there is more than one variable specified  by a
    FOR operator, or a GO, RETURN, or a reference to the  variable $$VAL
    appears in any of  the operands.  When a  PROG is used, the  form of
    the translation is:

                             (PROG variables
                             $$LP  {eachtime}
                                   (GO $$LP)
                             $$OUT {finalize}
                                   (RETURN $$VAL))

    where {test} corresponds to that portion of the loop that  tests for
    termination and also  for those iterations  for which {body}  is not
    going to  be executed, (as  indicated by a  WHEN or  UNLESS); {body}
    corresponds to the operand of the i.s.opr, e.g., DO,  COLLECT, etc.;
    {aftertest} corresponds to those tests for termination  specified by
    REPEATWHILE or  REPEATUNTIL; and {update}  corresponds to  that part
    that resets  the tail, increments  the counter, etc.  in preparation
    for the  next iteration.   {initialize}, {finalize},  and {eachtime}
    correspond to the operands of FIRST, FINALLY, and EACHTIME, if any.

    Note that since {body} always appears at the top level of  the PROG,
    the user  can insert labels  in {body}, and  go to them  from within
    {body}      or     from      other     i.s.      operands,     e.g.,

    For  i.s.oprs,  e.g.,  DO, COLLECT,  JOIN,  the  function  is always
    applied to the  first i.v. in the  i.s., whether explicity  named or
    not. For example, (IN Y AS I FROM 1 TO 10 DO PRINT)  prints elements
    on Y, not integers between 1 and 10.

    Note that this feature does not make much sense for FOR,  OLD, BIND,
    IN, or  ON, since they  "operate" before the  loop starts,  when the
    i.v. may not even be bound.

    In the case of BY in conjunction with IN, the function is applied to
    the current tail e.g., FOR X IN Y BY CDDR ..., is the same as  FOR X
    IN Y BY (CDDR X)... See page 23.19.


    (FOR X IN Y FIRST (GO A) DO (FOO) A (FIE)).   The  user can  also go
    to $$LP, $$ITERATE, or $$OUT, or explicitly set $$VAL.

Errors in Iterative Statements

An error will be generated and an appropriate diagnostic printed  if any
of the following conditions hold:

l.  Operator with null operand, i.e., two adjacent operators, as  in FOR
    X IN Y UNTIL DO --

2.  Operand  consisting of  more  than one  form (except  as  operand to
    FIRST, FINALLY, or one of the i.s.oprs), e.g., FOR X IN Y  (PRINT X)
    COLLECT --.

3.  IN, ON, FROM, TO, or BY appear twice in same i.s.

4.  Both IN and ON used on same i.v.

5.  FROM or TO used with IN or ON on same i.v.

6.  More than one i.s.type, e.g., a DO and a SUM.

In 3, 4, or 5, an error is not generated if an intervening AS occurs.

If an error occurs, the i.s. is left unchanged.

If no  DO, COLLECT,  JOIN or any  of the  other i.s.oprs  are specified,
CLISP will first attempt to find an operand consisting of more  than one
form,  e.g.,  FOR X IN Y (PRINT X) WHEN ATOM X, and  in  this  case will
insert a DO  after the first  form.  (In this  case, condition 2  is not
considered to be  met, and an error  is not generated.) If  CLISP cannot
find such  an operand,  and no  WHILE or  UNTIL appears  in the  i.s., a
warning message  is printed: NO  DO, COLLECT, OR  JOIN: followed  by the

Similarly, if  no terminating  condition is detected,  i.e., no  IN, ON,
WHILE, UNTIL,  TO, or a  RETURN or GO,  a warning message  is printed  :
However, since  the user may  be planning to  terminate the i.s.  via an
error, control-E, or a retfrom from a lower function, the i.s.  is still

    However, since {body} is dwimified as a list of forms,  the label(s)
    should be added to  the dummy variables for the  iterative statement
    in order to prevent their being dwimified and  possibly "corrected",
    e.g., (FOR X IN Y BIND A FIRST (GO A) DO (FOO) A (FIE)).

    unless the  value of clispi.s.gag  is T.  clispi.s.gag  is initially


Defining New Iterative Statement Operators

The following function is available for defining new iterative statement

                        name is the name of the new i.s.opr.  If form is
                        not NIL, name will  be a new i.s.type,  and form
                        its body.

For example, for COLLECT, form would be (SETQ $$VAL (NCONC1 $$VAL BODY))
For  SUM, form  would be  ($$VAL_$$VAL+BODY),   others  would  be (FIRST
For NEVER: (IF BODY THEN $$VAL_NIL (GO $$OUT))),   and for

                        others is an (optional) list of  additional i.s.
                        operators and  operands which  will be  added to
                        the i.s.  at the place  where name  appears.  If
                        form  is  NIL,  name is  a  new  i.s.opr defined
                        entirely by others.

                        In both  form and others,  $$VAL can be  used to
                        reference the value to be returned by  the i.s.,
                        I.V. to reference the current i.v., and  BODY to
                        reference name's operand.

                        If evalflg is  T, form and others  are evaluated

    The i.s.type  is the i.s.opr  that specifies what  is to be  done at
    each  iteration,  e.g.,  performing  an  operation  (DO), collecting
    values on a  list (COLLECT), adding  numbers (SUM), searching  for a
    particular condition  (THEREIS), etc.   Each i.s.  can have  one and
    only one i.s. type.

    $$VAL+BODY is used instead of (IPLUS $$VAL BODY) so that  the choice
    of function used  in the translation,  i.e., iplus, fplus,  or plus,
    will be determined by the declarations then in effect.

    (IF BODY THEN RETURN NIL)  would exit from the i.s.  immediately and
    therefore not  execute the  operations specified  via a  FINALLY (if

    In  other  words,  the  current i.v.  will  be  substituted  for all
    instances of  I.V. and  name's operand will  be substituted  for all
    instances of BODY throughout form and others.


                        at translation  time, and  their values  used as
                        described above.


(1) To define RCOLLECT, a version of COLLECT which uses cons  instead of
    nconc1 and then reverses the list of values:
    i.s.opr[RCOLLECT;($$VAL_(CONS BODY $$VAL));
                 (FINALLY (RETURN (DREVERSE $$VAL)))]

(2) To define TCOLLECT, a version of COLLECT which uses tconc:
    i.s.opr[TCOLLECT;(TCONC $$VAL BODY);
                 (FIRST $$VAL_(CONS) FINALLY (RETURN (CAR $$VAL)))]

(3) To                          define                          PRODUCT:
      i.s.opr[PRODUCT;($$VAL_$$VAL*BODY);(FIRST $$VAL_1)]

(4) To define  UPTO, a  version of  TO whose  operand is  evaluated only
    once: i.s.opr[UPTO;NIL;(BIND $$FOO_BODY TO $$FOO)].

i.s.opr can  also be used  to define synonyms  for already  defined i.s.
operators   by    calling   i.s.opr   with    form   an    atom,   e.g.,
i.s.opr[WHERE;WHEN]  makes  WHERE  be  the  same  as  WHEN.   Similarly,
following i.s.opr[ISTHERE;THEREIS],  one can  write (ISTHERE ATOM IN Y),
and following  i.s.opr[FIND;FOR] and i.s.opr[SUCHTHAT;THEREIS],  one can

For  convenience,  there  is  a  prettydefmacro,  I.S.OPRS,  which dumps
i.s.oprs,  e.g.,  (I.S.OPRS  PRODUCT UPTO)  as  a  prettycom  will print
suitable expressions so that these iterative statement operators will be
(re)defined when the file is loaded.

This completes the description of iterative statements.

23.8 English Phrases

CLISP  also  recognizes a  limited  but expandable  set  of english-like
constructions of the form  "A is B", e.g., FOO  IS A NUMBER, Z IS  NOT A
STRING, (CDDR X) ISN'T  A TAIL OF Y.   Both subject and relation  can be
"distributed", e.g., X AND Y ARE ATOMIC is equivalent to X IS ATOMIC AND
Y IS ATOMIC.  Similarly, Z IS AN  ARRAY OR A LIST is equivalent to  Z IS
0  is equivalent  to the  conjunction of  the indicated  six predicates.

    In the current system,  WHERE is synonymous with WHEN,  SUCHTHAT and
    ISTHERE with THEREIS, and FIND with FOR.


These constructions are translated to the corresponding LISP expressions
when they are run or dwimified.  In addition, clispify will convert LISP
forms into "english" when clispifyenglshflg is T.

Clisp  currently  knows about  the  following unary  relations  in their
singular and plural forms:  ARRAY, ATOM, ATOMIC, FLOATING  POINT NUMBER,
NUMBER, SMALL  INTEGER, SMALL NUMBER,  STRING; and the  following binary
relations in  their singular  and plural  forms: EQ  TO, EQUAL  TO, GEQ,
relationships can  be negated  with either NOT,  N, or  N'T, e.g.,  X IS
~LESS THEN Y, A AND B AREN'T ATOMIC.  New relations can be  defined with
the function newisword.

                        sing  is the  singular form  of the  new english
                        construct, plu  the plural without  the subject.
                        form   is  the   form  the   singular  construct
                        translates to, and vars the parameters.

For example, "SMALL INTEGER" could have been defined by  newisword[(X IS
by newisword[(X IS A TAIL OF Y); (ARE TAILS OF Y); (TAILP X Y); (X Y)].

23.9 CLISP Translations

The  translation  of  infix operators  and  IF|THEN|ELSE  statements are
handled  in   CLISP  by   replacing  the   CLISP  expression   with  the
corresponding INTERLISP expression,  and discarding the  original CLISP,
because (1) the CLISP expression is easily recomputable (by clispify),
and (2)  the INTERLISP expressions  are simple and  straightforward.  In
addition to saving the space  required to retain both the CLISP  and the
INTERLISP, another reason for  discarding the original CLISP is  that it
may contain  errors that  were corrected in  the course  of translation,
e.g., the user writes FOO_FOOO:1, N*8FOO X), etc.  If the original CLISP
were  retained, either  the user  would have  to go  back and  fix these
errors by hand,  thereby negating the  advantage of having  DWIM perform
these  corrections, or  else DWIM  would have  to keep  correcting these
errors over and over.

Where (1)  or (2)  are not  the case,  e.g., with  iterative statements,

    Note that clispify is sufficiently fast that it is practical for the
    user to configure his  INTERLISP system so that all  expressions are
    automatically clispifyed  immediately before  they are  presented to
    him. For example, he can define  an edit macro to use in place  of P
    which calls clispify on  the current expression before  printing it.
    Similarly,  he  can  inform prettyprint  to  call  clispify  on each
    expression before printing it, etc.


pattern  matches,  record  expressions,  etc.    the  original  CLISP is
retained (or a slightly  modified version thereof), and  the translation
                                                              60  61
is stored  elsewhere, usually  in clisparray,   a  hash array.       The
interpreter automatically checks this  array using gethash when  given a
form car of which is not a function.   Similarly, the  compiler performs
a gethash when  given a form it  does not recognize to  see if it  has a
translation, which is then  compiled instead of the form.   Whenever the
user changes a CLISP  expresson by editing it, the  editor automatically
deletes its  translation (if one  exists), so that  the next time  it is
evaluated  or  dwimified,  the expression  will  be  retranslated.   The
function ppt  and the  edit commands  PPT and  CLISP: are  available for
examining translations, see page 23.64.  Similarly, if  prettytranflg is
T, prettyprint will print the translations instead of  the corresponding

    The  handling  of   translations  for  IF|THEN|ELSE   statements  is
    determined by  the value of  clispiftranflg. If T,  the translations
    are stored elsewhere, and the (modified) CLISP retained as described
    below.  If NIL,  the  corresponding COND  replaces  the IF|THEN|ELSE
    expression. The initial value of clispiftranflg is NIL.

    The actual storing of  the translation is performed by  the function
    clisptran, page 23.61.

    The user can also indicate that he wants the original clisp retained
    by  embedding  it  in  an  expression  of  the  form (CLISP . clisp-
    expression),  e.g.,  (CLISP X:5:3) or  (CLISP <A B C ! D>).  In such
    cases, the translation will  be stored remotely as described  in the
    text.  Furthermore, such expressions  will be treated as  clisp even
    if infix  and prefix transformations  have been disabled  by setting
    clispflg to  NIL, as described  on page 23.61.  In other  words, the
    user can instruct the system  to interpret as clisp infix  or prefix
    constructs only those  expressions that are specifically  flagged as

    CLISP translations can also be used to supply an  interpretation for
    funtion objects, as well as forms, either for function  objects that
    are used openly,  i.e., appearing as  car of form,  function objects
    that are explicitly applyed, as with arguments to mapping functions,
    or function objects contained in function definition cells.   In all
    cases,  if  car  of  the  object  is  not  LAMBDA  or  NLAMBDA,  the
    interpreter and compiler will check clisparray.

    If the value of clispretranflg is T, dwimify will also (re)translate
    any expressions which have translations stored remotely. The initial
    value of clispretranflg is NIL.


CLISP expression.

If  clisparray  is  NIL,    translations  are  implemented   instead  by
replacing  the   CLISP  expression   by  an   expression  of   the  form
(CLISP%  translation . CLISP-expression),                          e.g.,
(FOR X IN Y COLLECT (CAR X)) would be replaced by
the editor and prettyprint know about CLISP%  expressions and treat them
specially by suppressing  the translations: Prettyprint prints  just the
CLISP  (unless prettytranflg=T, as  described below),  while  the editor
makes  the  translation  completely  invisible,  e.g.,  if  the  current
expression  were the  above CLISP%  expression,  F MAPCAR would  fail to
find the MAPCAR, and (3 ON)  would replace IN with ON, i.e.,  the editor
operates as though both the  CLISP%  and the MAPCAR were not  there.  As
with translations implemented via clisparray, if the CLISP expression is
changed by editing it, the translation is automatically deleted.

CLISP%   expressions will  interpret and  compile correctly:  CLISP%  is
defined as  an nlambda  nospread function  with an  appropriate compiler
macro. Note that if the user sets clisparray to NIL, he can  then break,
trace,  or  advise  CLISP%   to  monitor  the  evaluation  of  iterative
statements, pattern matches, and record operations. This  technique will
work even  if clisparray was  not NIL at  the time the  expressions were
originally translated, since setting clisparray to NIL  will effectively
delete the  translations, thereby  causing the  CLISP expressions  to be
retranslated when they are first encountered. Note that if the user only
wishes to  monitor the CLISP  in a certain  function, he  can accomplish
this by embedding its definition in (RESETVAR CLISPARRAY NIL *).

If a CLISP%  expression  is encountered and  clisparray is not  NIL, the
translation is transferred to the hash array, and the CLISP%  expression
replaced by  just the  CLISP.  Setting  prettytranflg to  CLISP%  causes
prettyprint to print CLISP expressions that have been translated  in the

    Note that  the user  can always examine  the translation  himself by
    performing (GETHASH expression CLISPARRAY).

    clisparray is initially NIL, and #clisparray is its size.  The first
    time  a translation  is  performed, a  hash  array of  this  size is
    created. Therefore  to disable clisparray,  both it  and #clisparray
    should be set to NIL.

    CLISP%  is an atom consisting of  the six characters C, L, I,  S, P,
    and space, which must be preceded by the escape character % in order
    for it to be included as a part of an identifier. The intent  was to
    deliberately make this atom hard  to type so as to make  it unlikely
    that it would  otherwise appear in a  user's program or  data, since
    the editor  and prettyprint  treat it  very specially,  as described


form   of  (CLISP%  translation   .  CLISP-expression),   even   if  the
translation  is  currently  stored  in  clisparray.  These  two features
together  provide  the user  with  a way  of  dumping  CLISP expressions
together  with their  translations  so that  when reloaded  (and  run or
dwimified),  the  translations  will  automatically  be  transferred  to

In summary, if  prettytranflg=NIL, only the  CLISP is printed  (used for
producing  listings).   If  prettytranflg=T,  only  the  translation  is
printed  (used for  exporting programs  to systems  that do  not provide
CLISP,  and  to  examine  translations  for  debugging  purposes).    If
prettytranflg=CLISP% ,      an      expression      of      the     form
(CLISP%  translation . CLISP) is printed,  (used for dumping  both CLISP
and translations).  The preferred  method of storing translations  is in
clisparray,  so  that if  any  CLISP%  expressions  are  converted while
clisparray is not NIL, they will automatically be converted so as to use
clisparray.    If  clisparray=NIL,   they  will   be  left   alone,  and
furthermore,  new  translations   will  be  implemented   using  CLISP% 

23.10 Declarations

Declarations are used to affect the choice of INTERLISP function used as
the  translation of  a  particular operator.   For example,  A+B  can be
translated as either (IPLUS A B), (FPLUS A B), or  (PLUS A B), depending
on the  declaration in effect.   Similarly X:1_Y can  mean (RPLACA X Y),
(FRPLACA X Y),  or  (/RPLACA X Y), and  <!!A B>  either  (NCONC1 A B) or
(/NCONC1 A B).   The table  below  gives the  declarations  available in
CLISP,  and  the  INTERLISP  functions  they  indicate.   The  choice of
function   on  all   CLISP   transformations  are   affected   by  these
declarations,  i.e.,  iterative  statements,  pattern   matches,  record
operations, as well as infix and prefix operators.

The user can make (change) a global declaration by calling  the function
CLISPDEC and  giving it as  its argument a  list of  declarations, e.g.,
(CLISPDEC (QUOTE (FLOATING UNDOABLE))).   Changing a  global declaration
does not affect the speed of subsequent CLISP transformations, since all
CLISP transformation are table driven (i.e., property list),  and global
declarations are accomplished by making the appropriate internal changes
to CLISP at  the time of the  declaration.  If a function  employs local
declarations  (described  below),  there  will  be  a  slight   loss  in
efficiency owing  to the  fact that for  each CLISP  transformation, the
declaration list must be searched for possibly relevant declarations.

Declarations are implemented in the  order that they are given,  so that
later declarations override earlier ones.  For example,  the declaration
FAST specifies that FRPLACA, FRPLACD, FMEMB, and FLAST be used  in place
of RPLACA, RPLACD, MEMB, and LAST; the declaration RPLACA specifies that
RPLACA be  used. Therefore, the  declarations (FAST RPLACA  RPLACD) will
cause FMEMB, FLAST, RPLACA, and RPLACD to be used.

    Note that  makefile will reset  prettytranflg to T,  using resetvar,
    when called with the option NOCLISP.


The initial global declaration is INTEGER and STANDARD.

Table of Declarations

Declaration             INTERLISP functions to be used

                        ILESSP, IGREATERP

                        LESSP, FGTP




                        NCONC1, MAPCONC, MAPCON

RPLACA, RPLACD,         corresponding function
/RPLACA, ...

Local Declarations

The user  can also  make declarations affecting  a selected  function or
functions by inserting an expression of the form (CLISP: . declarations)
immediately  following  the  argument  list,  i.e.,  as  CADDR   of  the
definition.   Such  local  declarations  take  precedence   over  global
declarations.    Declarations  affecting   selected  variables   can  be
indicated by lists, where the  first element is the name of  a variable,
and  the rest  of  the list  the  declarations for  that  variable.  For
example, (CLISP: FLOATING (X INTEGER))  specifies that in  this function
integer arithmetic  be used for  computations involving X,  and floating
arithmetic for all  other computations.   The  user can also  make local
record   declarations   by  inserting   a   record   declaration,  e.g.,
(RECORD --),  (ARRAYRECORD --),  etc., in  the  local  declaration list.
Local record  declarations override global  record declarations  for the
function in which they appear.   Local declarations can also be  used to

    'involving'  means  where the  variable  itself is  an  operand. For
    example,  with  the  declaration  (FLOATING (X INTEGER))  in effect,
    (FOO X)+(FIE X)  would  translate  to  FPLUS,  i.e.,   use  floating
    arithmetic, even though X appears somewhere inside of  the operands,
    whereas   X+(FIE X)  would   translate  to   IPLUS.  If   there  are
    declarations involving both  operands, e.g., X+Y, with  (X FLOATING)
    (Y INTEGER), whichever appears first in the declaration list will be


override the global  setting of certain DWIM/CLISP  parameters effective
only for transformations within that function, by including in the local
declaration  an  expression   of  the  form   (variable = value),  e.g.,

The  CLISP: expression  is  converted to  a  comment of  a  special form
recognized by CLISP.  Whenever  a CLISP transformation that  is affected
by declarations  is about to  be performed in  a function,  this comment
will be searched  for a relevant declaration,  and if one is  found, the
corresponding function will be used.  Otherwise, if none are  found, the
global declaration(s) currently in effect will be used.

Local declarations are  effective in the order  that they are  given, so
that  later declarations  can be  used to  override earlier  ones, e.g.,
RPLACD be used.  An exception to this is that declarations  for specific
variables  take  precedence  of  general,   function-wide  declarations,
regardless     of     the     order     of     appearance,     as     in

Clispify  also checks  the declarations  in effect  before  selecting an
infix operator to ensure that the corresponding CLISP construct would in
fact  translate  back  to  this  form.   For  example,  if   a  FLOATING
declaration is in effect, clispify will convert (FPLUS X Y) to  X+Y, but
leave (IPLUS X Y) as is.  Note that if (FPLUS X Y) is CLISPIFYed while a
FLOATING  declaration  is  under effect,  and  then  the  declaration is
changed to INTEGER,  when X+Y is translated  back to INTERLISP,  it will
become (IPLUS X Y).

23.11 The Pattern Match Compiler

CLISP contains a fairly general pattern match facility.  The  purpose of
this pattern match facility is to make more convenient the specifying of
certain  tests that  would  otherwise be  clumsy  to write  (and  not as
intelligible), by allowing the user to give instead a pattern  which the
datum  is supposed  to match.   Essentially, the  user writes  "Does the
(expression) X look  like (the pattern) P?" For  example, X:(& 'A -- 'B)
asks whether the second element of X is an A, and the last element  a B.
The implementation of the matching is performed by computing  (once) the
equivalent  INTERLISP  expression  which  will  perform   the  indicated
operation, and substituting  this for the  pattern, and not  by invoking
each time  a general purpose  capability such as  that found in  FLIP or
PLANNER.    For   example,  the   translation   of   X:(& 'A -- 'B)  is:
(AND  (EQ (CADR X) (QUOTE A))  (EQ (CAR (LAST X)) (QUOTE B))).  Thus the
CLISP  pattern match  facility  is really  a Pattern  Compiler,  and the
emphasis  in  its  design  and  implementation  has  been  more  on  the
efficiency of object code  than on generality and sophistication  of its
matching capabilities.  The  goal was to  provide a facility  that could

    The pattern match compiler was written by L. M. Masinter.


and would be  used even where efficiency  was paramount, e.g.,  in inner
loops.  As a result, the  CLISP pattern match facility does  not contain
(yet)  some  of  the  more  esoteric  features  of  other  pattern match
languages,  such  as  repeated  patterns,  disjunctive  and  conjunctive
patterns, recursion, etc.  However, the user can be confident  that what
facilities  it  does  provide  will  result  in   INTERLISP  expressions
comparable to those he would generate by hand.

The syntax for pattern match expressions is form:pattern,  where pattern
is  a  list  as  described below.   As  with  iterative  statements, the
translation of patterns, i.e., the corresponding  INTERLISP expressions,
are stored in clisparray, a hash array, as described on page 23.26.  The
original expression, form:pattern, is  replaced by an expression  of the
form (MATCH form WITH pattern).  CLISP also recognizes expressions input
in this form.

If form appears more than once in the translation, and it is  not either
a  variable, or  an  expression that  is  easy to  (re)compute,  such as
(CAR Y), (CDDR Z), etc., a dummy variable will be generated and bound to
the value of  form so that  form is not  evaluated a multiple  number of
times.   For  example,  the translation  of  (FOO X):($ 'A $)  is simply
(MEMB (QUOTE A) (FOO X)),  while the  translation  of (FOO X):('A 'B --)

                   [PROG ($$2) (RETURN
                         (AND (EQ (CAR (SETQ $$2 (FOO X)))
                                  (QUOTE A))
                              (EQ (CADR $$2) (QUOTE B].

In the interests of efficiency, the pattern match compiler  assumes that
all lists end in  NIL, i.e., there are  no LISTP checks inserted  in the
translation to check tails.  For example, the translation of X:('A & --)
is (AND (EQ (CAR X) (QUOTE A)) (CDR X)), which will match with  (A B) as
well as (A . B).  Similarly, the pattern match compiler does  not insert
LISTP  checks on  elements,  e.g., X:(('A --) --)  translates  simply as
(EQ (CAAR X) (QUOTE A)), and X:(($1 $1 --) --) as (CDAR X).    Note that
the  user can  explicitly insert  LISTP checks  himself by  using  @, as
described  on page  23.34, e.g.,  X:(($1 $1 --)@LISTP --)  translates as

    Wherever possible, already existing INTERLISP functions are  used in
    the  translation,  e.g., the  translation  of ($  'A  $)  uses MEMB,
    ($ ('A $) $) uses ASSOC, etc.

    The  insertion of  LISTP checks  for elements  is controlled  by the
    variable patlistpcheck.  When patlistpcheck is  T, LISTP  checks are
    inserted, e.g., X:(('A --) --) translates as:
                   (EQ (CAR (LISTP (CAR (LISTP X)))) (QUOTE A))
    patlistpcheck is initially  NIL. Its value  can be changed  within a
    particular function  by using a  local declaration, as  described on
    page 23.30.


Pattern Elements

A pattern consists of a list of pattern elements.  Each  pattern element
is said to  match either an  element of a  data structure or  a segment.
(cf. the editor's pattern matcher, "--" matches any arbitrary segment of
a list, while & or a subpattern match only one element of a list.) Those
patterns  which  may  match  a segment  of  a  list  are  called SEGMENT
patterns; those that match a single element are called ELEMENT patterns.

Element Patterns

There are several types of element patterns, best given by their syntax:


$1, or &           matches an arbitrary element of a list

'expression        matches only an element  which is equal to  the given
                   expression e.g., 'A,   '(A B).

=form              matches only an element  which is equal to  the value
                   of form, e.g., =X, =(REVERSE Y).

==form             same as =, but uses an eq check instead of equal.

atom               treatment depends on setting of patvardefault.
                   If patvardefault is ' or QUOTE, same as 'atom.
                   If patvardefault is = or EQUAL, same as =atom.
                   If patvardefault is == or EQ, same as ==atom.
                   If patvardefault is _ or SETQ, same as atom_&.
                   patvardefault is initially =.

Note: numbers and strings are always interpreted as though patvardefault
were =,  regardless of its  setting.  Eq, memb,  and assoc are  used for
comparisons involving small integers.

(pattern1 ... patternn) n > 1

    eq, memb, and assoc  are automatically used in the  translation when
    the  quoted  expression  is  atomic,  otherwise  equal,  member, and

    patvardefault can be changed within a particular function by using a
    local declaration, as described on page 23.30.


                   matches  a  list which  matches  the  given patterns,
                   e.g., (& &), (-- 'A).

                   matches an element if the element-pattern matches it,
                   and  the function-object  (name  of a  function  or a
                   LAMBDA  expression) applied  to that  element returns
                   non-NIL,   e.g.,   &@NUMBERP   matches    a   number,
                   ('A --)@FOO matches a list whose first element  is A,
                   and for which FOO applied to that list is non-NIL.

*                  matches any arbitrary  element.  If the  entire match
                   succeeds,  the element  which matched  the *  will be
                   returned as the value of the match.

Note:  normally, the  pattern  match compiler  constructs  an expression
whose value is guaranteed to be non-NIL if the match succeeds and NIL if
it  fails.  However,  if  a *  appears  in the  pattern,  the expression
generated will either return NIL if the match fails, or whatever matched
the * even though that may be NIL.  For example,  X:('A * --) translates
as (AND  (EQ (CAR X) (QUOTE A))  (CADR X)).

~element-pattern   matches an element if  the element is not  matched by
                   element-pattern, e.g.,  ~'A,  ~=X,  ~(-- 'A --).

Segment Patterns

$, or --           matches any segment of a list (including one  of zero

The difference between $ and -- is in the type of search  they generate.
For         example,         X:($ 'A 'B $)         translates         as
(EQ (CADR (MEMB (QUOTE A) X)) (QUOTE B)),     whereas     X:(-- 'A 'B $)
translates            as:            [SOME X (FUNCTION (LAMBDA ($$2 $$1)
(AND (EQ $$2 (QUOTE A)) (EQ (CADR $$1) (QUOTE B].  Thus, a paraphrase of
($ 'A 'B $)  would be  "Is  the element  following  the first  A  a B?",
whereas  a  paraphrase  of  (-- 'A 'B $)  would  be  "Is  there   any  A
immediately followed  by a B?"  Note that the  pattern employing  $ will
result  in a  more efficient  search than  that employing  --.  However,
($ 'A 'B $) will not match with (X Y Z A M N O A B C),  but (-- 'A 'B $)

    For "simple" tests, the function-object is applied before a match is
    attempted with  the pattern, e.g.,  ((-- 'A --)@LISTP --) translates
    as  (AND  (LISTP (CAR X))  (MEMB (QUOTE A) (CAR X))), not  the other
    way around.


Essentially, once a pattern following  a $ matches, the $  never resumes
searching, whereas -- produces  a translation that will  always continue
searching until  there is  no possibility of  success.  However,  if the
pattern match  compiler can  deduce from the  pattern that  continuing a
search  after a  particular failure  cannot possibly  succeed,  then the
translations  for both  -- and  $ will  be the  same. For  example, both
X:($ 'A $3 $) and (-- 'A $3 --) translate as (CDDDR (MEMB (QUOTE A) X)),
because if  there are not  three elements following  the first  A, there
certainly will not be three elements following subsequent A's,  so there
is  no   reason  to  continue   searching,  even  for   --.   Similarly,
($ 'A $ 'B $) and (-- 'A -- 'B --) are equivalent.

$2, $3, etc.       matches a segment of the given length.  Note  that $1
                   is not a segment pattern.

!element-pattern   matches any segment  which the given  element pattern
                   would match as a list.  For example, if the  value of
                   FOO  is   (A B C)  !=FOO   will  match   the  segment
                   ... A B C ...  etc.  Note that !* is  permissible and
                   means Value-of-match_$, e.g.,  X:($ 'A !*) translates
                   to (CDR (MEMB (QUOTE A) X)).

Note: since ! appearing in  front of the last pattern specifies  a match
with some tail of the given expression, it also makes sense in this case
for a  ! to appear  in front of  a pattern that  can only match  with an
atom, e.g., ($2 !'A) means match  if cddr of the expression is  the atom
A.  Similarly, X:($ ! 'A) translates to (EQ (CDR (LAST X)) (QUOTE A)).

!atom              treatment  depends on  setting of  patvardefault.  If
                   patvardefault  is '  or  QUOTE, same  as  !'atom (see
                   above discussion).  If  patvardefault is =  or EQUAL,
                   same as !=atom.   If patvardefault is == or  EQ, same
                   as !==atom.  If patvardefault  is _ or SETQ,  same as

.                  The  atom  "."  is  treated  exactly  like   !.    In
                   addition, if a  pattern ends in  an atom, the  "." is
                   first changed to  !, e.g., ($1 . A) and  ($1 ! A) are
                   equivalent,  even  though  the  atom  "."   does  not
                   explicitly appear in the pattern.

    With one exception, namely '.' preceding an assignment does not have
    the  special  interpretation  that  !  has  preceding  an assignment
    (see page  23.36).  For  example,  X:('A . FOO_'B)   translates  as:
    (AND  (EQ (CAR X) (QUOTE A))                  (EQ (CDR X) (QUOTE B))
    (SETQ FOO (CDR X))), but X:('A ! FOO_'B) translates as:
                   (AND (EQ (CAR X) (QUOTE A))
                         (NULL (CDDR X))
                         (EQ (CADR X) (QUOTE B))
                         (SETQ FOO (CDR X))).


                   matches a segment if the segment-pattern  matches it,
                   and the function object applied to  the corresponding
                   segment   (as   a   list)   returns   non-NIL,  e.g.,
                   ($@CDDR 'D $) matches (A B C D E) but  not (A B D E),
                   since CDDR of (A B) is NIL.

Note:  an @  pattern applied  to a  segment will  require  computing the
corresponding structure (with ldiff) each time the predicate  is applied
(except  when  the segment  in  question is  a  tail of  the  list being


Any pattern element may be preceded by a variable and a '_',  meaning if
the match succeeds (i.e., everything matches), the variable given  is to
be  set  to  what   matches  that  pattern  element.  For   example,  if
X = (A B C D E), X:($2 Y_$3) will set Y to (C D E).  Assignments are not
performed  until  the  entire match  has  succeeded.   Thus, assignments
cannot be used to specify a  search for an element found earlier  in the
                           76                                    77
match, e.g., X:(Y_$1 =Y --)   will not match with  (A A B C ...).   This
type of match is achieved by using place-markers, described below.

If the variable is preceded by a !, the assignment is to the tail of the
list as of  that point in  the pattern, i.e.,  that portion of  the list
matched  by  the  remainder  of  the  pattern.  For  example,  if  X  is
(A B C D E), X:($ !Y_'C 'D $) sets  Y to (C D E),  i.e., cddr of  X.  In
other words, when ! precedes an assignment, it acts as a modifier to the
_, and has no effect  whatsoever on the pattern itself,  e.g., X:('A 'B)
and X:('A !FOO_'B) match identically,  and in the latter case,  FOO will
be set to CDR of X.

Note:  *_pattern-element  and !*_pattern-element  are  acceptable, e.g.,
X:($ 'A *_('B --) --) translates as:

              [PROG ($$2) (RETURN
                    (AND (EQ (CAADR (SETQ $$2 (MEMB (QUOTE A) X)))
                             (QUOTE B))
                         (CADR $$2]

    The translation of this pattern is:
    The  AND is  because if  Y  is NIL,  the pattern  should  match with
    (A NIL), but not  with just (A). The  T is because (CAR X)  might be

     unless, of course, the value of Y was A before the match started.



Variables of the form #n, n a number, are called place-markers,  and are
interpreted specially by the pattern match compiler.   Place-markers are
used in  a pattern  to mark or  refer to  a particular  pattern element.
Functionally, they are used  like ordinary variables, i.e., they  can be
assigned values, or used freely in forms appearing in the pattern, e.g.,
X:(#1_$1 =(ADD1 #1)) will match the  list (2 3).  However, they  are not
really  variables  in the  sense  that they  are  not bound,  nor  can a
function called  from within  the pattern  expect to  be able  to obtain
their   values.   For   convenience,  regardless   of  the   setting  of
patvardefault,  the  first  appearance of  a  defaulted  place-marker is
interpreted as though patvardefault were _. Thus the above pattern could
have  been written  as X:(#1 =(ADD1 #1)).   Subsequent appearances  of a
place-marker  are  interpreted  as  though  patvardefault  were  =.  For
example, X:(#1 #1 --) is equivalent to X:(#1_$1 =#1 --),  and translates


Any pattern element may be followed by a "_" and a form, meaning  if the
match succeeds,  the part  of the data  that matched  is to  be replaced
(e.g., with RPLACA or RPLACD)   with the value of <form>.   For example,
if X =(A B C D E), X:($ 'C $1_Y $1) will replace the fourth element of X
with  the  value  of  Y.   As  with  assignments,  replacements  are not
performed until  after it is  determined that the  entire match  will be

Replacements involving segments splice the corresponding  structure into
the list being matched, e.g., if X is (A B C D E F) and FOO  is (1 2 3),
after  the  pattern  ('A $_FOO 'D $)  is  matched  with  X,  X  will  be
(A 1 2 3 D E F), and FOO will be eq to CDR of x, i.e., (1 2 3 D E F).

Note that ($ FOO_FIE $) is ambiguous, since it is not clear  whether FOO
or FIE is the pattern  element, i.e., whether _ specifies  assignment or
replacement.  For example,  if patvardefault is  =, this pattern  can be
interpreted as ($ FOO_=FIE $), meaning search for the value of  FIE, and
if found set FOO to  it, or ($ =FOO_FIE $) meaning search for  the value
of FOO,  and if  found, store the  value of  FIE into  the corresponding
position.  In such cases, the user should disambiguate by not  using the
patvardefault option, i.e., by specifying ' or =.

    Just (EQUAL (CAR X) (CADR X)) would incorrectly match with (NIL).

    The user can indicate he wants /rplaca and /rplacd used,  or frplaca
    and frplacd, by  means of declarations.  The initial default  is for
    rplaca and rplacd.



The user can  specify a value for  a pattern match operation  other than
what is returned by the  match by writing after the pattern  => followed
by   another  form,   e.g.,   X:(FOO_$ 'A --) => (REVERSE FOO),    which
translates as:

                   [PROG ($$2) (RETURN
                         (COND ((SETQ $$2 (MEMB (QUOTE A) X))
                                 (SETQ FOO (LDIFF X $2))
                                 (REVERSE FOO].

Place-markers in the pattern can be referred to from within  form, e.g.,
the above could  also have been written  as X:(!#1 'A --)=>(REVERSE #1).
If ->  is used  in place  of =>,  the expression  being matched  is also
physically   changed   to    the   value   of   form.     For   example,
X:(#1 'A !#2) -> (CONS #1 #2) would remove the second element from X, if
it were equal to A.

In general, form1:pattern->form2 is translated so as to compute form2 if
the match is successful, and then smash its value into the first node of
form1.  However,  whenever possible, the  translation does  not actually
require form2 to  be computed in its  entirety, but instead  the pattern
match compiler  uses form2 as  an indication of  what should be  done to
form1.   For   example,   X:(#1 'A !#2) -> (CONS #1 #2)   translates  as


X:(-- 'A --)            --  matches any  arbitrary segment.   'A matches
                        only  an  A, and  the  2nd --  again  matches an
                        arbitrary  segment;  thus  this   translates  to
                        (MEMB (QUOTE A) X).

X:(-- 'A)               Again, -- matches an arbitrary segment; however,
                        since there is no -- after the 'A, A must be the
                        last  element of  X.  Thus  this  translates to:
                        (EQ (CAR (LAST X)) (QUOTE A)).

X:('A 'B -- 'C $3 --)   CAR of  X must  be A,  and CADR  must be  B, and
                        there must be at least three elements  after the
                        first C, so the translation is:
                        (AND (EQ (CAR X) (QUOTE A))
                             (EQ (CADR X) (QUOTE B))
                             (CDDDR (MEMB (QUOTE C) (CDDR X))))

    The  original  CLISP  is  replaced  by  an  expression  of  the form
    (MATCH form1 WITH pattern => form2).    CLISP     also    recognizes
    expressions input in this form.


X:(('A 'B) 'C Y_$1 $)   Since ('A 'B) does not end in $ or --, (CDDAR X)
                        must be NIL.
                ((AND (EQ (CAAR X) (QUOTE A))
                      (EQ (CADAR X) (QUOTE B))
                      (NULL (CDDAR X))
                      (EQ (CADR X) (QUOTE C))
                      (CDDR X))
                  (SETQ Y (CADDR X))

X:(#1 'A $ 'B 'C #1 $)  #1 is implicitly  assigned to the  first element
                        in  the list.  The $  searches for  the  first B
                        following A. This B must be followed by a C, and
                        the  C  by  an  expression  equal  to  the first

              [PROG ($$2) (RETURN
                    (AND (EQ (CADR X) (QUOTE A))
                         (EQ [CADR (SETQ $$2 (MEMB (QUOTE B) (CDDR X]
                             (QUOTE C))
                         (CDDR $$2)
                         (EQUAL (CADDR $$2) (CAR X]

X:(#1 'A -- 'B 'C #1 $) Similar  to the  pattern above,  except  that --
                        specifies a  search for  any B  followed by  a C
                        followed   by   the   first   element,   so  the
                        translation is:

              [AND (EQ (CADR X) (QUOTE A))
                   (SOME (CDDR X) (FUNCTION (LAMBDA ($$2 $$1)
                             (AND (EQ $$2 (QUOTE B))
                                  (EQ (CADR $$1) (QUOTE C))
                                  (CDDR $$1)
                                  (EQUAL (CADDR $$1) (CAR X]

This concludes the description of the pattern match compiler.

23.11  The Record Package

The advantages of "data-less" or  data-structure-independent programming
have long  been known: more  readable code, fewer  bugs, the  ability to
change the data structure without having to make major  modifications to
the  program, etc.   The  record package  in CLISP  both  encourages and
facilitates this good programming practice by providing a uniform syntax
for creating, accessing  and storing data  into many different  types of
data  structures,   e.g.  those   employing  arrays,   list  structures,

    The record package was written by L. M. Masinter.


association lists, hash links,  etc., and combinations thereof,  as well
as  removing from  the user  the task  of writing  the  various routines
themselves.  The user declares (once) the data structure(s) used  by his
programs, and thereafter  indicates the manipulations  of the data  in a
data-structure-independent  manner.   The  record  package automatically
computes from the declaration(s) the corresponding INTERLISP expressions
necessary to  accomplish the  indicated access/storage  operations.  The
user can change his data structure simply by changing  the corresponding
declaration(s), and his program automatically (re)adjusts itself  to the
new conventions.

The  user  informs the  record  package  about the  format  of  his data
structures by making record declaration.  A record declaration defines a
record, i.e., a data structure.  The record declaration is a description
of the record, associating names with its various parts, or fields.  For
example,   the  record   declaration  (RECORD MSG (ID (FROM TO) . TEXT))
describes a data structure  called MSG, which contains four  fields: ID,
FROM, TO, and TEXT.  The  user can then reference these fields  by name,
either to  retrieve their contents  or to store  new data into  them, by
using the : operator followed  by the field name.  For example,  for the
above record declaration, X:FROM would be equivalent (and  translate) to
                                                    82 83
(CAADR X), and Y:TO_Z to (CAR (RPLACA (CDADR Y) Z)).   

The fields  of a record  can be further  broken down into  sub-fields by
subdeclarations within the record, e.g.,
would permit the user to refer to POSITION, or to its subfields XLOC and

Note that what the record declaration is really doing is  specifying the
data-paths   of  the   structure,   and  thereby   specifying   how  the
corresponding  access/storage operations  are  to be  carried  out.  For
example, the above declaration of NODE says the XLOC of a NODE is  to be
found as the CAR of its  POSITION, which is the CAR of the  NODE itself.
Hence, N:XLOC_30 is achieved by performing (CAR (RPLACA (CAR N) 30)).

Note also that when the user writes N:XLOC, he is implicitly  saying the
N is an  instance of the record  NODE, or at least  is to be  treated as

    or /RPLACA or FRPLACA, depending on the CLISP declaration in effect.
    Note that  the value  of X:TO_Z  is Z.  In general,  the value  of a
    replacement record operation  is the same  as the value  stored into
    the field.  In this case,  the INTERLISP-10 compiler  will eliminate
    the CAR if  the value of  X:TO_Z is not  actually used, e.g.  if the
    replacement is a statement in a PROG.

    Record operations  are implemented by  replacing expressions  of the
    form    X:FOO   by       (fetch FOO of X),    and   X:FOO_Y    by   
    (replace FOO of X with Y)   and   then   storing   the   translation
    elsewhere,  usually in  a hash  array, as  described on  page 23.26.
    CLISP also recognizes expressions input in this form; both lower and
    upper case are acceptable.


such for this particular  operation. In other words,  the interpretation
of N:field never depends on the value of N.  The record package does not
provide any facility which uses run-time checks to determine data paths,
nor is there  any error checking other  than that provided  by INTERLISP
itself.  For example, if N  happened to be an array, N:YLOC  would still
compute (CDAR N).

The user can also create new data structures using a  record declaration
as a guide or template.   Initial values for the contents of  each field
can be specified in the CREATE expression, defaulted to values specified
in  the  record declaration,  or  CREATE  can be  instructed  to  use an
existing datum as a model, i.e.  to obtain the field values for  the new
datum from the corresponding  fields of an existing datum.  For example,
with        the         above        declaration         of        NODE,
(CREATE NODE USING FOO XLOC_10 LABEL_'L1)          translates         to

The record  package also provides  a facility for  allowing the  user to
test if a datum is an instance of a given record via a TYPE? expression,
as explained below.

RECORD (used to specify elements and tails of a list structure)  is just
one of several record-types currently implemented. For example, the user
can specify a property list format by using the record  type PROPRECORD,
or that fields are to be associated with parts of the data structure via
hash links by  using the record-type HASHLINK,  or that an  entirely new
data  type  be  allocated  (as described  in  section  3)  by  using the
record-type DATATYPE. These are described in detail below.

As  with all  DWIM/CLISP facilities,  the record  package  contains many
do-what-I-mean  features,  spelling correction  on  field  names, record
types, etc. In addition, the record package includes a RECORDS prettydef
command  for dumping  record declarations,  as well  as  the appropriate
modifications  to the  file  package (Section  14), so  that  files? and
cleanup will inform the user about records that need to be dumped.

Record Declarations

A record declaration is an expression of the form
  (record-type record-name fields . {defaults and/or subdeclarations})
This expression is evaluated to effect the corresponding declaration.

    However, it is possible to make the interpretation of  N:YLOC differ
    from that of M:YLOC (regardless of the values of N and M),  by using
    local record  declarations, as  described on  page 23.30.  Note that
    this distinction depends on a translation-time check, not run-time.

    Local record declarations  are performed by including  an expression
    of  this  form in  the  CLISP declaration  for  that  function (page
    23.30), rather than evaluating the expression itself.


1. record-type  specifies  the "type"  of  data being  described  by the
   record declaration, and thereby implicitly specifies the  data paths,
   i.e., how the corresponding access/storage operations  are performed.
   record-type  currently  is  either  RECORD,  TYPERECORD, ARRAYRECORD,
   RECORD and TYPERECORD are used to describe list  structures, DATATYPE
   to  describe  user   data-types,  ARRAYRECORD  to   describe  arrays,
   ATOMRECORD to  describe (the property  list of) atoms,  PROPRECORD to
   describe lists in property  list format, and ASSOCRECORD  to describe
   association list format. HASHLINK can be used with any type  of data:
   it simply  specifies the data  path to be  a hash-link.  ACCESSFNS is
   also  type-less;  the user  specifies  the data-paths  in  the record
   declaration itself, as described below.

2. record-name is a literal atom used to identify the record declaration
   for dumping to files  via the RECORDS prettydef command  and creating
   instances  of  the   record  via  CREATE.  DATATYPE   and  TYPERECORD
   declarations also use record-name to identify the data  structure (as
   described below).

   For subdeclarations, record-name  specifies the parent field  that is
   being elaborated.

3. fields   describes   the   structure  of   the   record.   Its  exact
   interpretation varies with the record-type:

   RECORD       fields is a  list structure whose non-NIL  literal atoms
                are  taken  as  field-names to  be  associated  with the
                corresponding elements  and tails  of a  list structure.
                NIL can  be used as  a place marker  to fill  an unnamed
                field, e.g., (A NIL B)  describes a three  element list,
                with B corresponding to the third element. A  number may
                be used to indicate a sequence of NILs, e.g.  (A 4 B) is
                interpreted as (A NIL NIL NIL NIL B).

   TYPERECORD   Similar to RECORD  except that record-name is  also used
                as  an indicator  in CAR  of the  datum to  signify what
                "type" of  record it  is.  CREATE  will insert  an extra
                field  containing record-name  at the  beginning  of the
                structure, and the translation of the access and storage
                functions will take this extra field into account.   For

    For  some  top-level declarations,  record-name  is  optional, e.g.,
    (RECORD (ID (FROM TO) . TEXT))    is    acceptable.    However,   if
    record-name is omitted, the user cannot specify the record  by name,
    e.g., in  CREATE expressions,  or when  using the  RECORDS prettydef

    This type-field is used by the record package in the  translation of
    TYPE? expressions.


                example,   for   (TYPERECORD MSG (ID (FROM TO) . TEXT)),
                X:FROM translates as (CAADDR X), not (CAADR X).

   ASSOCRECORD  fields is a list of literal atoms. The fields are stored
                in            a-list            format;            i.e.,
                ((fieldname . value) (fieldname . value) ...).
                Accessing  is  performed  with  assoc,     storing  with

   PROPRECORD   fields  is  a list  of  property names.  The  fields are
                stored    in     "property    list"     format;    i.e.,
                (fieldname value fieldname value ...).    Accessing   is
                performed  with  listget,  storing  with  listput.  Both
                ASSOCRECORD and PROPRECORD are useful for  defining data
                structures in which  it is often  the case that  many of
                the fields are NIL. A CREATE for these record types only
                store those fields which are non-NIL.

   ARRAYRECORD  fields is a list of field-names that are associated with
                the corresponding elements of the array. NIL can be used
                as  a  place  marker  for  an  unnamed  field (element).
                Positive integers  can be used  as abbreviation  for the
                corresponding    number    of    NILs.    For   example,
                (ARRAYRECORD (ORG   DEST NIL ID 3 TEXT))   describes  an
                eight element array, with ORG corresponding to the first
                element, ID to the fourth, and TEXT to the eighth.

   HASHLINK     fields is  either just  field-name, i.e.  an atom,  or a
                list  interpreted as  (field-name arrayname arraysize).
                arrayname indicates  the hash-array to  be used;  if not
                given,    SYSHASHARRAY    is    used.     For   example,
                (HASHLINK (CLISP CLISPARRAY)) would  permit the  user to
                obtain  the CLISP  translation  of X  by  simply writing
                X:CLISP.  arraysize  is used  for initializing  the hash
                array: if arrayname has not been initialized at the time
                of    the    declaration,   it    will    be    set   to
                (LIST (HARRAY (OR arraysize 100))). HASHLINKs are useful
                as subdeclarations  to other  records to  add additional
                fields to already existing data-structures.

   DATATYPE     specifies  that  a new  user  data type  with  type name
                record-name  be   allocated  via   declaredatatype  (see

    or fassoc, depending on current CLISP declarations.

    However,   with   the   declaration   (PROPRECORD FIE (H I J))   the
    expression (CREATE FIE) would still construct (H NIL), since a later
    operation of  X:J_T could  not possibly change  the instance  of the
    record if it were NIL.


                Section 3).   When  a DATATYPE declaration is  given for
                the first time, the system allocates storage space and a
                type  number  for  that data  type.  Thus,  unlike other
                record-types, the records of a DATATYPE  declaration are
                represented with  a completely  new INTERLISP  type, and
                not in terms of other existing types.   fields is a list
                of  field  specifications, where  each  specification is
                either fieldname or (fieldname fieldtype).  If fieldtype
                is  omitted (or  fieldtype=POINTER) then  the  field can
                contain  a  pointer to  any  arbitrary  INTERLISP datum.
                Other options for fieldtype are:

                BITS n               field  contains  an  n-bit unsigned

                SIGNEDBITS n         field  contains  an   n-bit  signed

                INTEGER or FIXP      field contains  a full  word signed

                FLOATING or FLOATP   field contains a full word floating
                                     point number.

                For example, the declaration
                                (DATE SIGNEDBITS 18) (PRIO FLOATP)))
                would  define  a  data  type  MSG  which   occupies  (in
                INTERLISP-10) three  words of  storage with  two pointer
                fields, with 2 bits left over.

   ACCESSFNS    fields      is      a     list      of      the     form
                (field-name accessdef setdef), or  a list  with elements
                of  this  form.  accessdef  should  a  function  of  one

    Since  the  data  type  must be  set  up  at  run-time,  the RECORDS
    prettydef command will dump a declaredatatype expression as  well as
    the DATATYPE declaration itself.

    For this reason, DATATYPE  declarations should be used  with caution
    within local declarations,  since a new  and different data  type is
    allocated for each one with a different name.

    Fields are allocated in such  a way as to optimize the  storage used
    and  not  necessarily  in   the  order  specified.  To   store  this
    information  in   a  conventional   RECORD  list   structure,  e.g.,
    (RECORD MSG (FLG TEXT CNT DATE PRIO . HEADER)),  would take  5 words
    of  list space  and up  to three  number boxes  (for FLG,  DATE, and


                argument,  the datum,  and will  be used  for accessing.
                setdef    should be  a  function of  two  arguments, the
                datum and the new value, and is used for  storing.   For
                example,               (HASHLINK X FOO)              and
                (ACCESSFNS X (FOO GETHASH PUTHASH))  generate equivalent
                code  for  X:FOO;  in  both  cases  the  translation  is
                (GETHASH X).

   ATOMRECORD   fields   is   a   list   of   property    names,   e.g.,
                is performed with getp, storing with put.  '

4. {defaults and/or subdeclarations}   is  optional.   It   may  contain
   expressions of the form:

   (1) field-name _ form   allows the user to specify within  the record
                           declaration the default value to be stored in
                           field-name by a CREATE (if no value  is given
                           within the  CREATE expression  itself).  Note
                           that form  is evaluated  at CREATE  time, not
                           when the declaration is made.

   (2) record-name _ form  (re)defines  the  manner in  which  CREATE of
                           this  record   should  be   performed.   This
                           provides  a way  of specifying  how ACCESSFNS

    setdef  may  be omitted,  in  which case,  no  store  operations are

    Both accessdef and setdef may also be a LAMBDA expression or  a form
    written in  terms of  variables DATUM  and (in  the case  of setdef)
    NEWVALUE. For example, given the declaration
                             (RPLSTRING DATUM 1 NEWVALUE))
                  (RESTCHARS (SUBSTRING DATUM 2]
     X:FIRSTCHAR_Y would translate to (RPLSTRING X 1 Y). Since no setdef
    is   given  for   the   RESTCHARS  field,   attempting   to  perform
    X:RESTCHARS_Y would generate an error, REPLACE UNDEFINED FOR FIELD.

    Note that ACCESSFNS  do not have a  CREATE definition; the  user may
    supply   one   in  the   {defaults and/or subdeclarations}   of  the
    declaration, as described  below. Attempting to CREATE  an ACCESSFNS
    record without  specifying a create  definition will cause  an error

    As with ACCESSFNS, CREATE is not defined for ATOMRECORD records.


                           should  be  created or  overriding  the usual
                           definition  of  CREATE.  form  should  be  an
                           expression  using  the  field-names   of  the
                           declaration as variables; the forms  given in
                           the  CREATE  will  be  substituted   in.  For
                        (RECORD C(A . D)) and
                        (ACCESSFNS C((A CAR RPLACA)
                        (D CDR RPLACD)) C_(CONS A D))
                        are completely equivalent.

   (3) a subdeclaration    i.e.,  a  record declaration  of  any  of the
                           above   types.    The   record-name    of   a
                           subdeclaration must be either the record-name
                           of  its immediately  superior  declaration or
                           one of  the superior's  field-names.  Instead
                           of  identifying the  declaration as  with top
                           level  declarations,  the  record-name  of  a
                           subdeclaration identifies the parent field or
                           record  that   is  being  described   by  the
                           subdeclaration.    Subdeclarations   can   be
                           nested to an arbitrary depth.

    This facility allows the use of data-structures not specified by one
    of  the  9  built-in   record  types.  For  example,   one  possible
    representation  of  a  data-structure  is  to  store  the  fields in
    parallel arrays, especially if  the number of instances  required is
    known,  and they  do  not need  to  be garbage  collected.  Thus, to
    implement a data structure called LINK with two fields FROM  and TO,
    one would have two arrays FROMARRAY and TOARRAY.  The representation
    of an "instance" of the record would be an integer which is  used to
    index  into   the  arrays.  This   can  be  accomplished   with  the
                      (SETA FROMARRAY LINKCNT FROM)
                      (SETA TOARRAY LINKCNT TO]
    To CREATE a new LINK, a counter is incremented and the  new elements
    stored  (although  the  create  form  given  the  declaration should
    actually include a test for overflow).

    Note that, in a few cases, it makes sense for a given field  to have
    more than one subdeclaration. For example, in
    B is elaborated by both a PROPRECORD and a HASHLINK. Similarly,
    is also acceptable, and essentially "overlays" (FOO FIE) and  (C D),
    i.e. X:FOO  and X:C would  be equivalent. In  such cases,  the first
    subdeclaration is the one used by CREATE.


   (4) record-name @ fn    (Re)defines   the  manner   in   which  TYPE?
                           expressions are to be translated as described
                           below in the discussion of TYPE? expressions.


Record  operations  can  be  applied  to  arbitrary   structures,  i.e.,
structures created  directly by  user programs can  be manipulated  in a
data-independent  manner  using  record  declarations.  However,  to  be
completely data-independent, new data  should be created using  the same
declarations that define its data paths. This can be done by means of an
expression of  the form (CREATE record-name . {assignments}).   A CREATE
expression  translates into  an appropriate  INTERLISP form  using cons,
list, puthash, array, etc., that creates the new datum with  the various
fields  initialized  to  the  appropriate  values.     {assignments}  is
optional and may contain expressions of the following form:

     field-name _ form     specifies initial value for field-name.

     USING form            specifies  that for  all fields  not  given a
                           value by (1), the value of  the corresponding
                           field in form is to be used.

     COPYING form          like  USING except  the  corresponding values
                           are copied (with copyall).

     REUSING form          like  USING, except  that  wherever possible,
                           the  corresponding   structure  in   form  is

    CREATE  is  not defined  as  a function.   Instead,  DWIM  calls the
    appropriate  function in  the record  package giving  it  the entire
    CREATE  expression as  an argument.  The translation  of  the CREATE
    expression, i.e., the INTERLISP form which is evaluated to construct
    the datum,  is then stored  elsewhere, as with  iterative statements
    and pattern matches.

    The record package  goes to great pain  to insure that the  order of
    evaluation  in the  translation is  the same  as that  given  in the
    original create  expression if  the side  effects of  one expression
    might  affect  the evaluation  of  another. For  example,  given the
    declaration      (RECORD CONS (CAR . CDR)),      the      expression
    (CREATE CONS CDR_X CAR_Y)   will   translate   to   (CONS Y X),  but
    (CREATE CONS CDR_(FOO) CAR_(FIE)) will translate to
     (PROG ($$1) (RETURN (CONS (PROGN (SETQ $$1 (FOO)) (FIE)) $$1)))
    because, for example, FOO might set some variables used by FIE.



If the value of a field is neither explicitly specified,  nor implicitly
specified  via  USING, REUSING  or  COPYING, the  default  value  in the
declaration is  used, if any,  otherwise NIL.    For  example, following
(RECORD A (B C D) D _ 3),
(CREATE A B_T USING X) translates as (LIST T (CADR X) (CADDR X)),


The record package allows the user to test if a given datum "looks like"
an instance of a record. This can be done via an expression of  the form
(TYPE? record-name form).  TYPE?  is  mainly  intended  for declarations
involving record-type DATATYPE  or TYPERECORD. For DATATYPEs,  the TYPE?
check is exact;  i.e. the TYPE? expression  will return non-NIL  only if
the value of form is an instance of the record named by record-name. For
RECORDs, the TYPE? expression will test that the list structure  has the
right number  of list  cells.    For  TYPERECORDs, the  TYPE? expression
will check that the value of form is a list beginning  with record-name.
For ARRAYRECORDs, it  checks that the value  is an array of  the correct
size. For  PROPRECORDs and  ASSOCRECORDs, a  TYPE? expression  will make
sure that the value of form is a property/association list with property
names among the field-names of the declaration.

The user may  (re)define the interpretation  of TYPE? expressions  for a
record by including  an expression of the  form ... record-name @ fn ...
in the  tail of the  declaration. fn  may be either  a function  name, a
LAMBDA  expression,  or a  form  in  terms of  the  variable  DATUM. For
example, with the declaration

    Note that  (CREATE record REUSING form ...) does  not itself  do any
    destructive operations on the value of form. The distinction between
    USING  and  REUSING is  that  (CREATE record REUSING form  ...) will
    incorporate as much as possible  of the old data structure  into the
    new  one being  created, while  (CREATE record USING form  ...) will
    create a completely  new data structure,  with only the  contents of
    the fields  re-used. For example,  CREATE REUSING a  PROPRECORD just
    conses the new property names and values onto the list, while CREATE
    USING copies  the top  level of  the list.  Another example  of this
    distinction occurs when a  field is elaborated by  a subdeclaration:
    USING will create  a new instance  of the sub-record,  while REUSING
    will use  the old contents  of the field  (unless some field  of the
    subdeclaration is assigned in the CREATE expression.)

    For non-pointer fields in DATATYPE records, zero is used.

    The record package uses the pattern match compiler for this purpose.


         (RECORD MSG (ID (FROM TO) . TEXT)
              MSG @ (FMEMB (CAR DATUM) '(STATUS REPLY]
the      expression      (TYPE? MSG X)      would      translate      to


The user may also  elaborate a field by  declaring that field name  in a
separate record declaration (as opposed to an  embedded subdeclaration).
For example, the two declarations
also  subdivide  TEXT into  two  subfields. The  user  may  then specify
X:MSG.HEADER to  achieve the  interpretation "X is  a MSG,  retrieve its
HEADER".     The  central point  of  separate declarations  is  that the
(sub)record  is   not  tied   to  another   record  (as   with  embedded
declarations), and therefore can be used in many different contexts. For
example, one might additionally have a declaration
                  (RECORD REPLY (TEXT TO . RESPONSE)).
In  this case,  one could  specify X:REPLY.HEADER  to mean  that X  is a
REPLY, and to retrieve (CAAR X).  In general, the user may specify  as a
data     path     a    chain     of     record/field     names,    e.g.,
X:MSG.TEXT.HEADER.SUBHEAD... etc.,    where there is some path from each
record  to the  next in  the  chain.  Only  as much  of the  path  as is
necessary to disambiguate it  needs to be specified.  For  example, with
the above declarations of MSG, TEXT and REPLY, the path  X:MSG.HEADER is

    Attempting  to  execute a  TYPE?  expression for  a  record  of type
    ACCESSFNS,  HASHLINK,  RECORD,  will  cause  an  error,   TYPE?  NOT
    IMPLEMENTED  FOR THIS  RECORD. Of  course, the  user can  always re-
    define  the interpretation  of  TYPE? expressions  for  a particular
    declaration   by   inclusion   of   an   expression   of   the  form
    record-name @ fn in the declaration, as described above.

    X:HEADER by itself is interpreted  to mean that X is an  instance of
    TEXT, and translates as (CAR X).

    Translation  of  expressions  involving data  paths  are  handled by
    replacing the expression  by a fetch  or replace statement  with the
    fields  given in  a list;  e.g., X:FOO.FIE.A  and  X:FOO.FIE.A_Y are
    replaced    by    the    expression     (fetch (FOO FIE A) X)    and
    (replace (FOO FIE A) of X with Y) respectively, with the translation
    stored elsewhere. Input of this form is also acceptable.


unambiguous (it  must go  through TEXT); however,  X:TEXT is  not,    as
this could mean that X is either a MSG or a REPLY.    The record package
interprets a  data path by  performing a tree  search among  all current
declarations for a  path from each name  to the next,  considering first
local declarations (if any) and then global ones.

Changing Record Declarations

The  user  may edit  (or  delete) global  record  declarations  with the

editrec[editrecx]        nlambda, nospread function, similar to editf or
                         editv. editrec  calls the editor  on a  copy of
                         all declarations in which car[editrecx]] is the
                         record-name  or  a  field  name.  On  exit,  it
                         redeclares   those   that   have   changed  and
                         undeclares  any  that  have  been  deleted.  If
                         car[editrecx]]  is  NIL,  all  declarations are

Records can also be declared  local to a particular function by  using a
CLISP  declaration,  as  described  on  page  23.30;  all  local  record
declarations override global ones.

For both global and local records, the translation is computed using all
CLISP declarations in  effect as described on  page 23.29, e.g.,  if the
declaration UNDOABLE is in effect, /RPLACA, /RPLACD, /PUTHASH, etc. will
be used.

When  the  user redeclares  a  global record,  the  translations  of all
expressions involving that record or any of its fields are automatically
deleted,    and thus will  be recomputed using the new  information.  If

    Note  that  if  a  field  has  an  identical  interpretation  in two
    declarations, e.g. if the  field TEXT occurred in the  same location
    within the declarations of MSG and REPLY, it would not be considered

    In this case, the message AMBIGUOUS RECORD FIELD  is printed  and an
    error is  generated. If a  data-path rather than  a single  field is
    ambiguous,   (e.g.,   if   there   were   yet   another  declaration
    (RECORD TO (NAME . HEADER))  and the  user  specified X:MSG.HEADER),
    the error AMBIGUOUS DATA PATH  is generated.

    from clisparray. If  the user is not  using this method  for storing
    translations,  i.e.,  is  instead  using  the  CLISP%   method (page
    23.28),  those expressions  already translated  will remain  as they
    are. (There is no practical way to locate them.)


the user changes a local record declaration, or changes some other CLISP
declaration, e.g., STANDARD to  FAST, and wishes the new  information to
affect  record expressions  already translated,  he must  make  sure the
corresponding translations are removed, usually either by CLISPIFYING or
applying the !DW edit macro.


Clispify  converts  INTERLISP  expressions  to  CLISP.   Note  that  the
expression  given to  clispify need  not have  originally been  input as
CLISP, i.e., clispify can be used on functions that were  written before
CLISP was even implemented.  Clispify is cognizant of  declaration rules
as well as  all of the precedence  rules.    For example,  clispify will
convert  (IPLUS A (ITIMES B C)) into  A+B*C,  but (ITIMES A (IPLUS B C))
into  A*(B+C).     Clispify  converts calls  to  the  six  basic mapping
functions,  MAP,  MAPC,  MAPCAR,  MAPLIST,  MAPCONC,  and  MAPCON,  into
equivalent  iterative  statements.   It  also  converts  certain  easily
recognizable internal PROG loops to the corresponding i.s.  For example,

         ... label (COND (pred ... forms ... (GO label))) ...
         ... label (WHILE pred DO ... forms ...) ...

Clispify is not destructive to the original INTERLISP  expression, i.e.,
clispify  produces a  new expression  without changing  the original.
Clispify will not convert expressions appearing as arguments  to NLAMBDA

    clispify is table driven exactly  the same as CLISP, so that  if the
    user  changes any  precedence,  or defines  new  operators, clispify
    "automatically" knows about it.

    clispify  also  knows  how to  handle  expressions  consisting  of a
    mixture of INTERLISP and CLISP, e.g., (IPLUS A B*C) is  converted to
    A+B*C, but (ITIMES A B+C) to (A*(B+C)). clispify handles  such cases
    by first dwimifying the expression.

    clispify can convert all iterative statements input in CLISP back to
    CLISP, regardless  of how complicated  the translation  was, because
    the original CLISP is saved.

    The  new  expression  may  however  contain  some  "pieces"  of  the
    original, since clispify attempts  to minimize the number  of CONSes
    by not copying structure whenever possible.



The value of various global parameters affect the operation of clispify:


The user can disable the : transformation by setting the variable cl:flg
to NIL.   This will  prevent clispify  from constructing  any expression
employing a : infix operator, e.g., (CADR X) will not be  transformed to
X:2.  When cl:flg  is T, clispify will  convert to : notation  only when
the argument is atomic or a simple list (a function name and  one atomic
argument).  If  cl:flg is  ALL, clispify will  convert to  : expressions
whenever possible.  The initial value of cl:flg is T.


Clispify will  remove parentheses  in certain  cases from  simple forms,
where "simple" means  a function name and  one or two  atomic arguments.
For example, (COND ((ATOM X) --)) will CLISPIFY  to (IF ATOM X THEN --).
However,  if  clremparsflg  is   set  to  NIL,  clispify   will  produce
(IF (ATOM X) THEN --).   Note that  regardless  of the  setting  of this
flag, the expression can be input in either form.  The initial  value of
clremparsflg is T.


clispifypackflg  affects the  treatment of  infix operators  with atomic
operands.  If clispifypackflg is T, clispify will pack these into single
atoms, e.g.,  (IPLUS A (ITIMES B C)) becomes A+B*C.   If clispifypackflg
is NIL,  no packing  is done,  e.g., the  above becomes  A + B * C.  The
initial value of clispifypackflg is T.


If T,  causes clispify  to convert  LISP forms  to english  phrases when
possible, e.g., (MEMBER X Y) -> X IS A MEMBER OF Y..  See page 23.25.


Suppose the user has variables named A, B, and A*B.  If clispify were to
convert (ITIMES A B) to A*B,  A*B would not translate back  correctly to
(ITIMES A B), since it  would be the name  of a variable,  and therefore
would not cause an error.   The user can prevent this from  happening by
adding A*B to the list funnyatomlst.  Then, (ITIMES A B)  would clispify
to A * B.

Note that A*B's appearance  on funnyatomlst would not  enable DWIM/CLISP
to decode A*B+C as (IPLUS A*B C); funnyatomlst is used only by clispify.
Thus, if an identifier contains  a CLISP character, it should  always be
separated (with spaces) from other  operators.  For example, if X*  is a

    Except for those  functions with property  INFO, value EVAL  such as
    nlsetq, resetlst, etc.  clispify also contains built  in information
    enabling it to process special forms such as prog, selectq, etc.


variable, the user should write (SETQ X* form) in CLISP as X* _form, not
X*_form.  However, in  general, it is best  to avoid use  of identifiers
containing CLISP character operators as much as possible.


If T,  causes prettyprint  to clispify  all expressions  before printing
them  (but  not  to  redefine  any  functions).    clispifyprettyflg  is
temporarily reset to T, using resetvar, when makefile is called with the
option CLISPIFY, or when the file in question has property FILETYPE with
value CLISP on its property list.  clispifyprettyflg is initially NIL.

In  addition to  the  above controls,  disabling a  CLISP  operator (see
cldisable,  page 23.63)  will  also disable  the  corresponding CLISPIFY
transformation.  Thus, if _ is  "turned off", A_B will not  transform to
(SETQ A B), nor vice versa.

23.14 Dwimify

Dwimify is  effectively a preprocessor  for CLISP.  Dwimify  operates by
scanning an expression as though it were being interpreted, and for each
form that would generate an error, calling DWIM to "fix" it.    Thus the
user will see the same messages,  and be asked for approval in  the same
situations, as he would if the expression were actually run.  If DWIM is
unable to make a correction, no message is printed, the form is  left as
it was, and the analysis proceeds.

Dwimify knows exactly how the interpreter works.  It knows the syntax of
progs, selectqs, lambda  expressions, setqs, et  al.  It knows  that the
argument of nlambdas are  not evaluated.    It also knows  how variables
are bound.  In  the course of its  analysis of a  particular expression,
dwimify builds a list of the bound variables from the LAMBDA expressions
and  PROGs  that  it  encounters.   It  uses  this  list   for  spelling
corrections.  Dwimify also knows not to try to "correct"  variables that
are  on this  list since  they  would be  bound if  the  expression were
actually being run.  However,  note that dwimify cannot, a  priori, know
about variables  that are  used freely but  would be  bound in  a higher
function  if  the  expression  were  evaluated  in  its  normal context.
Therefore, dwimify  will try to  "correct" these  variables.  Similarly,
dwimify will attempt to correct  forms for which car is  undefined, even
when  the form  is not  in  error from  the user's  standpoint,  but the
corresponding function has simply not yet been defined.

    Thus  dwimify  performs  all DWIM  transformations,  not  just CLISP
    transformations,  i.e.,  it  does  spelling  correction,  fixes  8-9
    errors, handles F/L, etc.

    The user can inform  dwimify that an NLAMBDA function  does evaluate
    its arguments (presumably by direct calls to eval), by  including on
    its property list the property INFO with value EVAL.


In most cases,  an attempt to  transform a form  that is already  as the
user intended  will have  no effect  (because there  will be  nothing to
which that form could reasonably be transformed).  However, in  order to
avoid needless calls  to DWIM or to  avoid possible confusion,  the user
can  inform dwimify  not to  attempt corrections  or  transformations on
certain functions or variables by adding them to the list nofixfnslst or
                          117 118
nofixvarslst respectively.    

Dwimify and dwimifyfns (used to dwimify several functions)  maintain two
internal lists  of those functions  and variables for  which corrections
were  unsuccessfully   attempted.   These   lists  are   initialized  to
nofixfnslst  and  nofixvarslst.   Once  an  attempt  is  made  to  fix a
particular function or variable, and the attempt fails, the  function or
variable  is added  to  the corresponding  list, so  that  on subsequent
occurrences (within this call  to dwimify or dwimifyfns), no  attempt at
correction is made.   For example, if FOO  calls FIE several  times, and
FIE is undefined at the  time FOO is dwimified, dwimify will  not bother
with  FIE after  the  first occurrence.   In other  words,  once dwimify
"notices"  a function  or  variable, it  no longer  attempts  to correct
it.    Moreover, once dwimify "notices" such functions or  variables, it
subsequently treats them the  same as though they were  actually defined
or set.

Note that  these internal lists  are local to  each call to  dwimify and
dwimifyfns, so that if a function containing FOOO, a misspelled  call to
FOO, is dwimified before FOO is defined or mentioned, if the function is
dwimified again after FOO has been defined, the correction will be made.

Note  that  the  user can  undo  selected  transformations  performed by
dwimify, as described in section 22.

    Note that the user could  achieve the same effect by  simply setting
    the  corresponding  variables,   and  giving  the   functions  dummy

    Dwimify will  never attempt corrections  on global  variables, i.e.,
    variables that  are a  member of  the list  globalvars, or  have the
    property GLOBALVAR with value T, on their property list.  Similarly,
    variables  declared  to  be  LOCALFREEVARS  or  SPECVARS   in  block
    declarations  are  automatically added  to  nofixvarslst  at compile
    time, so that they will not be "corrected."

    Dwimify and dwimifyfns also "notice" free variables that are  set in
    the expression being processed.


Compiling CLISP

Since  the compiler  does  not know  about  CLISP, in  order  to compile
functions  containing CLISP  constructs, the  definitions must  first be
dwimified.  The user can automate this process in several ways:

1)  If  the  variable  dwimifycompflg is  T,  the  compiler  will always
    dwimify  expressions  before  compiling  them.    dwimifycompflg  is
    initially NIL.

2)  If a file has the property FILETYPE with value CLISP on its property
    list,  tcompl, bcompl,  recompile,  and brecompile  will  operate as
    though  dwimifycompflg  is  T  and  dwimify  all  expressions before

3)  If the function definition has a CLISP declaration (see page 23.29),
    including a  null declaration, i.e.,  just (CLISP:),  the definition
    will be automatically dwimified before compiling.

Note: compileuserfn (Section 18) is defined to call dwimify on iterative
statements,   IF-THEN  statements,   and  fetch,   replace,   and  match
expressions, i.e., any  CLISP construct which  can be recognized  by its
car of form.   Thus, if the only  CLISP constructs in a  function appear
inside of iterative statements  or IF statements, the function  does not
have to be dwimified before compiling.

Note: tcompl, bcompl, recompile, and brecompile all scan the entire file
before doing any compiling, and take note of the names of  all functions
that are defined in the file as well as the names of all  variables that
are set  by adding them  to nofixfnslst and  nofixvarslst, respectively.
Thus, if a function is not currently defined, but is defined in the file
being compiled,  when dwimify  is called before  compiling, it  will not
attempt to interpret the function  name as CLISP when it appears  as car
of a form.  In addition, nospellflg (see page 23.61) is reset to  T when
compiling functions from a file so as to suppress spelling correction.

23.15 Operation

CLISP is  a part  of the  basic INTERLISP  system.  Without  any special
preparations, the user can include CLISP constructs in programs, or type
them in directly for evaluation (in eval or apply format), and  when the
"error" occurrs, and DWIM is called, it will  destructively    transform
the  CLISP  to  the equivalent  INTERLISP  expression  and  evaluate the
INTERLISP expression.  User approval is not requested, and no message is

    CLISP transformations, like all DWIM corrections, are undoable.



However,  if  a  CLISP  construct  contains  an  error,  an  appropriate
diagnostic is generated, and  the form is left unchanged.   For example,
if    the   user    writes   (LIST    X+Y*),   the    error   diagnostic
MISSING OPERAND AT X+Y* IN (LIST X+Y*)  would be  generated.  Similarly,
if the user writes (LAST+EL X), CLISP knows that  ((IPLUS LAST EL) X) is
not  a  valid  INTERLISP expression,  so  the  error  diagnostic MISSING
OPERATOR IN (LAST+EL X) is generated.  (For example, the user might have
meant  to say  (LAST+EL*X).) Note  that if  LAST+EL were  the name  of a
defined function, CLISP would never see this form.

Since  the bad  CLISP  transformation might  not  be CLISP  at  all, for
example, it might be a misspelling of a user function or  variable, DWIM
holds all CLISP error messages until after trying other corrections.  If
one of these  succeeds, the CLISP  message is discarded.   Otherwise, if
all  fail,  the message  is  printed  (but no  change  is  made).    For
example,  suppose  the  user  types  (R/PLACA X Y).   CLISP  generates a
diagnostic,  since  ((IQUOTIENT R PLACA) X Y)  is  obviously  not right.
However, since R/PLACA spelling corrects to /RPLACA, this  diagnostic is
never printed.

If a CLISP infix construct  is well formed from a  syntactic standpoint,
but  one or  both of  its operands  are atomic  and not  bound,    it is
possible that  either the  operand is misspelled,  e.g., the  user wrote
X+YY for X+Y, or that a CLISP transformation operation was  not intended
at all, but that the  entire expression is a misspelling.   For example,
if the user  has a variable  named LAST-EL, and  writes (LIST LAST-ELL).
Therefore, CLISP computes, but does not actually perform,  the indicated
infix transformation.  DWIM  then continues, and if  it is able  to make
another correction, does so, and ignores the CLISP  interpretation.  For
example, with LAST-ELL, the transformation LAST-ELL -> LAST-EL  would be

If no other  transformation is found, and  DWIM is about to  interpret a
construct as CLISP for which one of the operands is not bound, DWIM will
ask  the user  whether  CLISP was  intended,  in this  case  by printing

    This  entire  discussion   also  applies  to   CLISP  transformation
    initiated by calls to DWIM from dwimify.

    Except that  CLISP error  messages are not  printed on  type-in. For
    example, typing X+*Y will just produce a U.B.A. X+*Y message.

    For the purpose of dwimifying, "not bound" means no top level value,
    not  on list  of  bound variables  built  up by  dwimify  during its
    analysis  of  the expression,  and  not on  nofixvarslst,  i.e., not
    previously seen.



The  same  sort of  procedure  is followed  with  8 and  9  errors.  For
example, suppose the  user writes FOO8*X where  FOO8 is not  bound.  The
CLISP transformation is noted, and DWIM proceeds.  It next asks the user
to approve FOO8*X -> FOO ( *X.  (For  example, this would make  sense if
the user has (or plans to define) a function named *X.)  If  he refuses,
the user is asked whether FOO8*X is to be treated as  CLISP.  Similarly,
if FOO8 were  the name of  a variable, and  the user writes  FOOO8*X, he
will  first  be  asked to  approve  FOOO8*X -> FOOO ( XX,     and  if he
refuses, then be offered the FOOO8 -> FOO8 correction.

CLISP  also  contains  provision for  correcting  misspellings  of infix
operators (other than single characters), IF words, and  i.s. operators.
This is implemented in  such a way that  the user who does  not misspell
them   is   not   penalized.    For   example,   if   the   user  writes
IF N=0 THEN 1 ELSSE N*(FACT N-1) CLISP does not operate by checking each
word to see if it is  a misspelling of IF, THEN, ELSE, or  ELSEIF, since
this would seriously degrade  CLISP's performance on all  IF statements.
Instead, CLISP assumes that all  of the IF words are  spelled correctly,
and           transforms           the           expression           to
(COND ((ZEROP N) 1 ELSSE N*(FACT N-1))).  Later, after DWIM  cannot find
any other interpretation  for ELSSE, and using  the fact that  this atom
originally  appeared  in   an  IF  statement,  DWIM   attempts  spelling
correction, using (IF THEN ELSE ELSEIF) for a spelling list.   When this
is  successful,  DWIM  "fails"  all the  way  back  to  the  original IF
statement, changes ELSSE to ELSE, and starts over.  Misspellings of AND,
OR, LT, GT, etc. are handled similarly.

CLISP  also  contains  many  Do-What-I-Mean  features  besides  spelling
corrections.  For example, the form (LIST +X Y) would generate a MISSING
OPERATOR  error.   However, (LIST -X Y)  makes  sense, if  the  minus is
unary, so DWIM offers  this interpretation to the user.   Another common
error,  especially  for  new  users,  is  to  write  (LIST X*FOO(Y))  or
(LIST X*FOO Y),  where  FOO  is  the  name  of  a  function,  instead of
(LIST X*(FOO Y)).  Therefore, whenever an  operand that is not  bound is
also  the  name  of  a   function  (or  corrects  to  one),   the  above
interpretations are offered.

    If more than one infix operator was involved in the CLISP construct,
    e.g.,  X+Y+Z,  or the  operation  was an  assignment  to  a variable
    already noticed, or treatasclispflg  is T (initially NIL),  the user
    will simply be  informed of the  correction, e.g., X+Y+Z  TREATED AS
    CLISP. Otherwise,  even if  DWIM was enabled  in TRUSTING  mode, the
    user will be asked to approve the correction.

    The 8-9 transformation is tried before spelling correction  since it
    is  empirically  more  likely  that  an  unbound  atom  or undefined
    function containing an 8 or a 9 is a parenthesis error,  rather than
    a spelling error.


23.16 CLISP Interaction with User

Syntactically  and semantically  well formed  CLISP  transformations are
always   performed   without   informing   the   user.     Other   CLISP
transformations described in the previous section, e.g., misspellings of
operands,  infix operators,  parentheses  errors, unary  minus  - binary
minus errors, all follow the same protocol as other DWIM transformations
(Section 17).  That  is, if DWIM has  been enabled in TRUSTING  mode, or
the  transformation  is  in  an expression  typed  in  by  the  user for
immediate execution,  user approval  is not requested,  but the  user is
informed.    However, if the transformation involves a user program, and
DWIM was enabled  in CAUTIOUS mode, the  user will be asked  to approve.
If  he says  NO,  the transformation  is  not performed.   Thus,  in the
previous  section,  phrases  such  as  "one  of  these (transformations)
succeeds" and  "the transformation LAST-ELL ->  LAST-EL would  be found"
etc., all mean  if the user is  in CAUTIOUS mode and  the error is  in a
program, the corresponding transformation will be performed only  if the
user approves (or defaults by not responding).  If the user says NO, the
procedure followed is the same as though the transformation had not been
found.  For example, if  A*B appears in the  function FOO, and B  is not
bound (and no other transformations are found) the user would be asked
                    A*B [IN FOO]  TREAT AS CLISP ?
If the user  approved, A*B would  be transformed to  (ITIMES A B), which
would then  cause a U.B.A.  B error  in the event  that the  program was
being run (remember the  entire discussion also applies  to DWIMIFYing).
If the user said NO, A*B would be left alone.

23.17 CLISP Internal Conventions

Note: the  reader can  skip this  section and  proceed to  "Function and
Variables"  (page  23.61), unless  he  wants to  add  new  operators, or
modify the action of existing ones (other than by making declarations).

CLISP  is  almost  entirely  table  driven  by  property  lists  for the
corresponding infix or prefix operators.  Thus it is relatively  easy to
add new infix or prefix  operators or change old ones, simply  by adding
or changing selected property values.

    However, in certain situations,  DWIM will ask for approval  even if
    DWIM is enabled in TRUSTING mode. For example, the user  will always
    be  asked  to  approve  a spelling  correction  that  might  also be
    interpreted as a CLISP transformation, as in LAST-ELL -> LAST-EL.

    The waiting time on such interactions is three times as long  as for
    simple corrections, i.e., 3*dwimwait.

    There is some built in  information for handling minus, :, ',  <, >,
    and  ~,  i.e.,  the  user  could  not  himself  add  such  "special"
    operators, although he can disable them.


CLISPTYPE               The property value of the property  CLISPTYPE is
                        the precedence number of the operator:    higher
                        values   have  higher   precedence,   i.e.,  are
                        tighter.    Note  that   the  actual   value  is
                        unimportant,  only the  value relative  to other
                        operators.  For example, CLISPTYPE for :, ^, and
                        * are 14, 6, and 4 respectively.  Operators with
                        the same precedence group left to right, e.g., /
                        also has precedence 4, so A/B*C is (A/B)*C.

                        An operator can have a different left  and right
                        precedence by making the value of CLISPTYPE be a
                        dotted pair of two numbers, e.g., CLISPTYPE of _
                        is  (8 . -12).  In  this case,  car is  the left
                        precedence, and cdr the right, i.e., car is used
                        when comparing with  operators on the  left, and
                        cdr with operators  on the right.   For example,
                        A*B_C+D  is  parsed as  A*(B_(C+D))  because the
                        left precedence of _ is 8, which is  higher than
                        that of *, which is 4.  The right  precedence of
                        _ is -12, which  is lower than that of  +, which
                        is 2.

                        If the CLISPTYPE property for any infix operator
                        is    removed,    the     corresponding    CLISP
                        transformation  is  disabled,  as  well  as  the
                        inverse CLISPIFY transformation.

UNARYOP                 The  value of  property  UNARYOP must  be  T for
                        unary operators.  The  operand is always  on the
                        right, i.e.,  unary operators are  always prefix

BROADSCOPE              The  value of  property BROADSCOPE  is T  if the
                        operator  has  lower  precedence  than INTERLISP
                        forms, e.g., LT, EQUAL, AND, etc.   For example,
                        (FOO X AND Y) parses as ((FOO X) AND Y).  If the
                        BROADSCOPE  property   were  removed   from  the
                        property list of AND, (FOO X AND Y)  would parse
                        as (FOO (X AND Y)).

LISPFN                  The value of the property LISPFN is the  name of
                        the  function   to  which  the   infix  operator
                        translates.   For example,  the value  of LISPFN
                        for ^ is EXPT,  for ' QUOTE, etc.  If  the value
                        of  the  property  LISPFN  is  NIL,   the  infix
                        operator itself is also the function  e.g., AND,
                        OR, EQUAL.

    Unless otherwise specified, the  property is stored on  the property
    list of the operator.


SETFN                   If FOO has a SETFN property FIE, then (FOO --)_X
                        translates to  (FIE -- X).  For example,  if the
                        user makes ELT be an infix operator, e.g.  #, by
                        putting   appropriate   CLISPTYPE   and   LISPFN
                        properties on the property list of # then he can
                        also  make #  followed by  _ translate  to SETA,
                        e.g., X#N_Y to (SETA X N Y), by putting  SETA on
                        the  property  list of  ELT  under  the property
                        SETFN.  Putting (ELT) (i.e., list[ELT]))  on the
                        property list of SETA under property  SETFN will
                        enable SETA forms to CLISPIFY back to ELT's.

CLISPINFIX              The value of this property is the CLISP infix to
                        be used in CLISPIFYing.  This property is stored
                        on  the  property  list  of   the  corresponding
                        INTERLISP function, e.g., the value  of property
                        CLISPINFIX for EXPT is ^, for QUOTE is ' etc.

Global  declarations operate  by changing  the corresponding  LISPFN and
CLISPINFIX properties.

clispchars              is a list of single character operators that can
                        appear in  the interior  of an  atom.  Currently
                        these are: +, -, *, /, ^, ~, ', =, _, :,  <, and

clispcharray            is a bit  table of the characters  on clispchars
                        used  for  calls to  strposl  (see  Section 10).
                        clispcharray is initialized by performing

clispinfixsplst         is a list  of infix operators used  for spelling

As an example, suppose  the user wants to  make | be an  infix character
operator meaning OR.  He performs:



23.18 CLISP Functions and Variables

clispflg                if  set  to  NIL, disables  all  CLISP  infix or
                        prefix  transformations  (but  does  not  affect
                        IF/THEN/ELSE     statements,     or    iterative

                        If  clispflg=TYPE-IN, CLISP  transformations are
                        performed only on expressions that are  typed in
                        for evaluation, i.e., not on user programs.

                        If   clispflg=T,   CLISP   transformations   are
                        performed on all expressions.

                        The   initial   value   for   clispflg   is   T.
                        clispifying anything  will cause clispflg  to be
                        set to T.

clisparray              hash  array   used  for   storing  translations.
                        clisparray   is   checked   by   faulteval   and
                        faultapply  on  erroneous  forms  before calling
                        DWIM, and by the compiler.

clisptran[x;tran]       gives x the translation tran.  If  clisparray is
                        not  NIL,  uses hashing  scheme,  otherwise uses
                        CLISP% scheme. See page 23.26 - page 23.29.

nofixfnslst             list of functions  that dwimify will not  try to
                        correct.  See page 23.54.

nofixvarslst            list of variables  that dwimify will not  try to
                        correct.  See page 23.54.

nospellflg              If nospellflg is T, dwimify will not perform any
                        spelling  corrections.   The  initial  value  of
                        nospellflg  is NIL.   nospellflg is  reset  to T
                        when compiling  functions whose  definitions are
                        obtained  from a  file, as  opposed to  being in

dwimify[x;l]            dwimifies x, i.e., performs all  corrections and
                        transformations  that  would be  performed  if x
                        were run.  If  x is an atom  and l is NIL,  x is
                        treated  as  the  name of  a  function,  and its
                        entire definition is dwimified.

                        Otherwise, if x is a list or l is not NIL,  x is
                        the  expression to  be dwimified.   If l  is not
                        NIL, it is the edit push-down list leading to x,
                        and is used for determining context,  i.e., what
                        bound variables  would be in  effect when  x was
                        evaluated, whether  x is a  form or  sequence of


                        forms, e.g., a cond clause, etc.

dwimifyfns[fns]         nlambda,  nospread. Dwimifies  each  function on
                        fns.  If fns  consists of only one  element, the
                        value    of    car[fns]    is     used,    e.g.,
                        dwimifyfns[FOOFNS].     Every     30    seconds,
                        dwimifyfns prints the name of the function it is
                        processing, a la prettyprint.

dwimifycompflg          if  T,  dwimify is  called  before  compiling an
                        expression.  See page 23.55.

clispdec[declst]        puts  into  effect the  declarations  in declst.
                        clispdec performs spelling corrections  on words
                        not  recognized  as  declarations.   clispdec is

clispify[x;l]           clispifies x.  If x is  an atom and l is  NIL, x
                        is treated  as the name  of a function,  and its
                        definition  (or  EXPR  property)  is clispified.
                        After  clispify  has  finished,  x  is redefined
                        (using  /PUTD)  with its  new  CLISP definition.
                        The value of clispify is x.  If x is  atomic and
                        not the name of a function,  spelling correction
                        is  attempted.   If  this  fails,  an  error  is

                        If x is a list, or l is not NIL, x itself is the
                        expression to be  clispified.  If l is  not NIL,
                        it is the edit  push-down list leading to  x and
                        is used to determine context as with dwimify, as
                        well  as to  obtain the  local  declarations, if
                        any.  The  value of  clispify is  the clispified
                        version of x.

                        See  earlier   section  on  CLISPIFY   for  more

clispifyfns[fns]        nlambda,  nospread.   Calls  clispify   on  each
                        member of fns under errorset protection.  If fns
                        consists  of  only  one  element,  the  value of
                        car[fns]  is  used,  e.g.,  clispifyfns[FOOFNS].
                        Every 30 seconds, clispifyfns prints the name of
                        the function  it is  working, a  la prettyprint.
                        Value is list of functions clispifyed.

    If x is an iterative statement and l is NIL, dwimify will also print
    the translation, i.e., what is stored in the hash array.


cldisable[op]           disables op, e.g., cldisable[-] makes -  be just
                        another character.  cldisable can be used on all
                        CLISP operators,  e.g., infix  operators, prefix
                        operators,  iterative statement  operators, etc.
                        cldisable is undoable.

clispiftranflg          affects handling of translations of IF|THEN|ELSE
                        statements.  If  T, the translations  are stored
                        elsewhere,  and the  (modified)  CLISP retained.
                        If  NIL,  the  corresponding   COND  expression,
                        replaces the CLISP. clispiftranflg  is initially
                        NIL. See page 23.26.

clispretranflg          If  T,  informs  dwimify  to  (re)translate  all
                        expression   which  have   remote  translations,
                        either in hash array or using CLISP%.  Initially

cl:flg                  affects clispify's  handling of  forms beginning
                        with car,  cdr, ... cddddr,  as well  as pattern
                        match and record expressions.  See page 23.52.

clremparsflg            affects clispify's  removal of  parentheses from
                        "small" forms.  See page 23.52.

clispifypackflg         if  T,  informs clispify  to  pack  operator and
                        atomic operands  into single  atoms; if  NIL, no
                        packing is done.  See page 23.52.

clispifyenglshflg       if   T,   informs  clispify   to   convert  LISP
                        expressions  to english  phrases  when possible.
                        See page 23.25.

clispifyprettyflg       if  non-NIL,  causes  prettyprint   to  CLISPIFY
                        selected  function  definitions  before printing
                        them according to the  following interpretations
                        of clispifyprettyflg:

                             ALL            all functions

                             T,EXPRS        functions  currently defined
                                            as exprs

    Another way to inform  prettyprint to clispify functions is  for the
    function to have a CLISP declaration containing the word CLISPIFY.


                             CHANGES        functions  marked  as having
                                            been changed

                             a list         a member of that list

                        clispifyprettyflg  is (temporarily)  reset  to T
                        when   makefile  is   called  with   the  option
                        CLISPIFY,  and reset  to CHANGES  when  the file
                        being  dumped  has the  property  FILETYPE value
                        CLISP.   clispifyprettyflg is initially NIL.

prettytranflg           If T,  causes prettyprint to  print translations
                        instead  of CLISP  expressions.  This  is useful
                        for  creating  a file  for  compilation,  or for
                        exporting to  a LISP system  that does  not have
                        CLISP.  prettytranflg is (temporarily)  reset to
                        T  when  makefile  is  called  with  the  option
                        NOCLISP. If  prettytranflg is CLISP% ,  both the
                        CLISP   and   translations   are    printed   in
                        appropriate  form.  For  more details,  see page
                        23.28.  prettytranflg is initially NIL.

PPT                     is  both  a  function  and  an  edit  macro  for
                        prettyprinting  translations. It  performs  a PP
                        after  first   resetting  prettytranflg   to  T,
                        thereby causing  any translations to  be printed
                        instead of the corresponding CLISP.

CLISP:                  edit macro that  obtains the translation  of the
                        correct expression, if any, from clisparray, and
                        calls edite on it.

funnyatomlst            list of identifiers containing  CLISP operators.
                        Used   by   clispify   to   avoid   accidentally
                        constructing    a    user    identifier,   e.g.,
                        (ITIMES A B) should not become A*B if A*B is the
                        name of a PROG variable.  See page 23.52.

CL                      edit  macro.  Replaces  current  expression with
                        CLISPIFYed    current    expression.     Current
                        expression can be an element or tail.

    If  clispifyprettyflg  is  non-NIL,  and  the   only  transformation
    performed by  DWIM are well  formed CLISP transformations,  i.e., no
    spelling corrections,  the function will  not be marked  as changed,
    since it  would only have  to be re-clispified  and re-prettyprinted
    when the file was written out.


DW                      edit macro.  DWIMIFYs current  expression, which
                        can be an element (atom or list) or tail.

Both CL and DW  can be called when  the current expression is  either an
element or a tail and will work properly.  Both consult the declarations
in the function being edited, if any, and both are undoable.

lowercase[flg]          If flg=T, lowercase makes the necessary internal
                        modifications  so that  clispify will  use lower
                        case  versions  of  AND,  OR,  IF,  THEN,  ELSE,
                        ELSEIF, and  all i.s. operators.   This produces
                        more readable  output.  Note  that the  user can
                        always type in either upper or lower case  (or a
                        combination),  regardless   of  the   action  of

                        If flg=NIL, clispify will use uppercase versions
                        of AND,  OR, et al.   The value of  lowercase is
                        its previous "setting".  Lowercase  is undoable.
                        The initial setting for lowercase is T.