Trailing-Edge
-
PDP-10 Archives
-
decus_20tap1_198111
-
decus/20-0004/23lisp.doc
There are no other files named 23lisp.doc in the archive.
SECTION 23
1
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
2
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."
versus
"I own the dog that chased the cat that caught the rat that ate the
cheese."
------------------------------------------------------------------------
1
CLISP was designed and implemented by W. Teitelman. It is discussed
in [Tei5].
2
except for parentheses (and period), which are used for indicating
structure, and space and end-of-line, which are used for delimiting
identifiers.
23.1
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
CLISP:
(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
syntax.
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
once.
23.2
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
3
expression would be interpreted as (LIST (X*FCT) N). If he said NO, the
------------------------------------------------------------------------
3
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.
23.3
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
4
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.
------------------------------------------------------------------------
4
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.
23.4
Figure 23-1
23.5
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
5
fixing the parenthesis error.
This sort of robustness prevails throughout CLISP. For example, the
6
iterative statement permits the user to say things like:
FOR OLD X FROM M TO N DO (PRINT X) WHILE (PRIMEP X)
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
------------------------------------------------------------------------
5
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)).
6
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.
23.6
7
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
8
using PROG, COND, GO, etc. When the interpreter subsequently encounters
this CLISP expression, it automatically obtains and evaluates the
9
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.
------------------------------------------------------------------------
7
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.
8
(PROG NIL
(SETQ X M)
$$LP(COND
((OR (IGREATERP X N)
(NOT (PRIMEP X)))
(RETURN)))
(PRINT X)
(SETQ X (ADD1 X))
(GO $$LP))
9
See page 23.26, for discussion of how translations are stored.
23.7
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
10
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
(LIST A+B).
23.3 Infix Operators
CLISP recognizes the arithmetic infix operators +, -, *, /, and ^.
These are converted to IPLUS, IDIFFERENCE (or in the case of unary
11
minus, IMINUS), ITIMES, IQUOTIENT, and EXPT. The usual precedence
12
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
------------------------------------------------------------------------
10
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.
11
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.
12
The complete order of precedence for CLISP operators is given in
Figure 23-2, page 23.12.
23.8
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.
15
CLISP also recognizes as infix operators =, GT, LT, GE, and LE, as
16
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
------------------------------------------------------------------------
13
There are some do-what-I-mean features associated with Unary minus,
as in (LIST -X Y). See section on operation, page 23.55.
14
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).
15
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
IGREATERP and ILESSP.
16
Currently the complete list is MEMBER, MEMB, FMEMB, ILESSP,
IGREATERP, LESSP, GREATERP, FGTP, EQ, NEQ, EQP, EQUAL, OR, and AND.
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.
23.9
17
as CLISP.
: is an infix operator used in CLISP for extracting substructures from
18
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
19
the last tail, i.e., (LAST X).
20
_ is used to indicate assignment, e.g., X_Y translates to (SETQ X Y).
21
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
------------------------------------------------------------------------
17
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.
18
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.
19
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.
20
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.
21
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).
23.10
22 23
terms (RPLACA (CDR X) Y). Negative numbers can also be used, e.g.,
24
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
25
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
26
(EQ (QUOTE X) Y).
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
------------------------------------------------------------------------
22
Note that the value of this operation is the value of rplaca, which
is the corresponding node.
23
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.
24
which translates to (RPLACA (NLEFT X 2) Y).
25
i.e., is equivalent to (SETQ lastword form). See Section 17.
26
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.
23.11
27
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
'
:
28
_ (left precedence)
29
- (unary), ~
^
*, /
+, - (binary)
_ (right precedence)
=
INTERLISP forms
LT, GT, EQUAL, MEMBER, etc.
AND
OR
IF, THEN, ELSEIF, ELSE
iterative statement operators
Figure 23-2
------------------------------------------------------------------------
27
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.
28
_ 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.
29
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.12
30
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
33
for the rest of the identifier. Note however that <A B ' C> D> is
equivalent to (LIST A B (QUOTE C>) D).
23.6 IF, THEN, ELSE
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.
------------------------------------------------------------------------
30
The <,> operator was written by P.C. Jackson.
31
Not (NCONC (APPEND A B) C), which would have the same value, but
would attach C to B, and not attach either to A.
32
The user can indicate /nconc or /nconc1 be used instead of nconc and
nconc1 by declarations.
33
Only if a previous unmatched < has been seen, e.g., (PRINT 'A>B)
will print the atom A>B.
23.13
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
34
omitted between IF|ELSEIF, and THEN. For example, (IF FOO X Y THEN --)
35
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
36
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
37
(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
------------------------------------------------------------------------
34
IF, THEN, ELSE, and ELSEIF can also be misspelled. Spelling
correction is performed using clispifwordsplst as a spelling list.
35
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
IF -- THEN FOO ELSEIF --.
36
occasionally interacting with the user to resolve ambiguous cases.
37
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.
23.14
38
iterate."
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,
IN, ON, FROM, TO, BY, WHEN, WHILE, UNTIL, REPEATWHILE, REPEATUNTIL,
UNLESS, COLLECT, JOIN, DO, SUM, COUNT, ALWAYS, NEVER, THEREIS, AS,
FIRST, FINALLY, EACHTIME. Their function is explained below. New
operators can be defined as described on page 23.24. Misspellings of
39
operators are recognized and corrected. The order of appearance of
40
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
------------------------------------------------------------------------
38
The statement translates to:
(PROG ($$VAL) $$LP(COND ((EQ (SETQ X (READ))(QUOTE STOP)) (RETURN
$$VAL)))
(PRINT (EVAL X)) $$ITERATE (GO $$LP))
39
using the spelling list clispforwordsplst.
40
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
printed, e.g., (WHILE DEFINED, THEREFORE DISABLED IN CLISP).
23.15
41
possible.
JOIN form like DO, except that the values are NCONCed.
42
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)
43
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
~form.
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
------------------------------------------------------------------------
41
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
mapcar.
42
/NCONC, /MAPCONC, and /MAPCON are used when the declaration UNDOABLE
is in effect.
43
iplus, fplus, or plus will be used for the translation depending on
the declarations in effect.
23.16
44
(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
------------------------------------------------------------------------
44
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
(FOR X ON Y THEREIS (NUMBERP (CAR X))).
23.17
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
car[var].
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
23.18
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
45
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
iteration.
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
------------------------------------------------------------------------
45
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.
23.19
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.
46
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
47
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,
e.g.,
(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
example,
(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
------------------------------------------------------------------------
46
form itself, not its value, which in general CLISP would have no way
of knowing in advance.
47
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.
23.20
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
I).
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.
Miscellaneous
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
supplied.
5. Each operand can be the name of a function, in which case it is
23.21
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
work.
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
{initialize}
$$LP {eachtime}
{test}
{body}
$$ITERATE
{aftertest}
{update}
(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.,
------------------------------------------------------------------------
48
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.
49
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.
50
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.
23.22
51
(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
i.s.
Similarly, if no terminating condition is detected, i.e., no IN, ON,
52
WHILE, UNTIL, TO, or a RETURN or GO, a warning message is printed :
POSSIBLE NON-TERMINATING ITERATIVE STATEMENT: followed by the i.s.
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
translated.
------------------------------------------------------------------------
51
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)).
52
unless the value of clispi.s.gag is T. clispi.s.gag is initially
NIL.
23.23
Defining New Iterative Statement Operators
The following function is available for defining new iterative statement
operators:
i.s.opr[name;form;others;evalflg]
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
53
its body.
For example, for COLLECT, form would be (SETQ $$VAL (NCONC1 $$VAL BODY))
54
For SUM, form would be ($$VAL_$$VAL+BODY), others would be (FIRST
$$VAL_0).
55
For NEVER: (IF BODY THEN $$VAL_NIL (GO $$OUT))), and for
THEREIS: (IF BODY THEN $$VAL_I.V. (GO $$OUT)).
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
56
reference name's operand.
If evalflg is T, form and others are evaluated
------------------------------------------------------------------------
53
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.
54
$$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.
55
(IF BODY THEN RETURN NIL) would exit from the i.s. immediately and
therefore not execute the operations specified via a FINALLY (if
any).
56
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.
23.24
at translation time, and their values used as
described above.
Examples:
(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
57
write (FIND X IN Y SUCHTHAT X MEMBER Z).
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
AN ARRAY OR Z IS A LIST, and A AND B ARE NUMBERS AND LESS THAN 5 AND GT
0 is equivalent to the conjunction of the indicated six predicates.
------------------------------------------------------------------------
57
In the current system, WHERE is synonymous with WHEN, SUCHTHAT and
ISTHERE with THEREIS, and FIND with FOR.
23.25
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,
LIST, LITATOM, LITERAL ATOM, NEGATIVE, NIL (i.e., X IS NIL), NULL,
NUMBER, SMALL INTEGER, SMALL NUMBER, STRING; and the following binary
relations in their singular and plural forms: EQ TO, EQUAL TO, GEQ,
GREATER THAN, GT, LESS THAN, LT, MEMB OF, MEMBER OF, TAIL OF. All
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.
newisword[sing;plu;form;vars]
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
A SMALL INTEGER); (ARE SMALL INTEGERS); (SMALLP X); (X)] and "TAIL OF"
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,
58
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,
------------------------------------------------------------------------
58
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.
23.26
59
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
62
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
63
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
------------------------------------------------------------------------
59
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.
60
The actual storing of the translation is performed by the function
clisptran, page 23.61.
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
such.
62
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.
63
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.
23.27
64
CLISP expression.
65
If clisparray is NIL, translations are implemented instead by
replacing the CLISP expression by an expression of the form
66
(CLISP% translation . CLISP-expression), e.g.,
(FOR X IN Y COLLECT (CAR X)) would be replaced by
(CLISP% (MAPCAR Y (FUNCTION CAR)) FOR X IN Y COLLECT (CAR X)). Both
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
------------------------------------------------------------------------
64
Note that the user can always examine the translation himself by
performing (GETHASH expression CLISPARRAY).
65
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.
66
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
above.
23.28
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
clisparray.
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
67
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%
expressions.
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.
------------------------------------------------------------------------
67
Note that makefile will reset prettytranflg to T, using resetvar,
when called with the option NOCLISP.
23.29
The initial global declaration is INTEGER and STANDARD.
Table of Declarations
Declaration INTERLISP functions to be used
INTEGER or FIXED IPLUS, IMINUS, IDIFFERENCE, ITIMES, IQUOTIENT,
ILESSP, IGREATERP
FLOATING FPLUS, FMINUS, FDIFFERENCE, FTIMES, FQUOTIENT,
LESSP, FGTP
MIXED PLUS, MINUS, DIFFERENCE, TIMES, QUOTIENT, LESSP,
GREATERP
FAST FRPLACA, FRPLACD, FMEMB, FLAST, FASSOC
UNDOABLE /RPLACA, /RPLACD, /NCONC, /NCONC1, /MAPCONC,
/MAPCON
STANDARD RPLACA, RPLACD, MEMB, LAST, ASSOC, NCONC,
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
68
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
------------------------------------------------------------------------
68
'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
used.
23.30
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.,
(PATVARDEFAULT = QUOTE).
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.,
(CLISP: FAST RPLACA RPLACD) specifies that FMEMB, FLAST, RPLACA, and
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
(CLISP: (X INTEGER) FLOATING).
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).
69
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
------------------------------------------------------------------------
69
The pattern match compiler was written by L. M. Masinter.
23.31
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
70
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 --)
is:
[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
71
(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
(CDR (LISTP (CAR X))).
------------------------------------------------------------------------
70
Wherever possible, already existing INTERLISP functions are used in
the translation, e.g., the translation of ($ 'A $) uses MEMB,
($ ('A $) $) uses ASSOC, etc.
71
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.
23.32
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:
PATTERN MEANING
$1, or & matches an arbitrary element of a list
'expression matches only an element which is equal to the given
72
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_&.
73
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
------------------------------------------------------------------------
72
eq, memb, and assoc are automatically used in the translation when
the quoted expression is atomic, otherwise equal, member, and
sassoc.
73
patvardefault can be changed within a particular function by using a
local declaration, as described on page 23.30.
23.33
matches a list which matches the given patterns,
e.g., (& &), (-- 'A).
element-pattern@function-object
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,
74
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
length).
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 $)
will.
------------------------------------------------------------------------
74
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.
23.34
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
atom_$.
75
. 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.
------------------------------------------------------------------------
75
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))).
23.35
Segment-pattern@function-object
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
matched).
Assignments
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]
------------------------------------------------------------------------
76
The translation of this pattern is:
(COND ((AND (CDR X) (EQUAL (CADR X) Y))
(SETQ Y (CAR X))
T)).
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
NIL.
77
unless, of course, the value of Y was A before the match started.
23.36
Place-markers
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
78
as (AND (CDR X) (EQUAL (CAR X) (CADR X)).
Replacements
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
79
(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
successful.
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 =.
------------------------------------------------------------------------
78
Just (EQUAL (CAR X) (CADR X)) would incorrectly match with (NIL).
79
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.
23.37
Reconstruction
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
80
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
(AND (EQ (CADR X) (QUOTE A)) (RPLACD X (CDDR X))).
Examples
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))))
------------------------------------------------------------------------
80
The original CLISP is replaced by an expression of the form
(MATCH form1 WITH pattern => form2). CLISP also recognizes
expressions input in this form.
23.38
X:(('A 'B) 'C Y_$1 $) Since ('A 'B) does not end in $ or --, (CDDAR X)
must be NIL.
(COND
((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))
T))
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
element.
[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.
81
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,
------------------------------------------------------------------------
81
The record package was written by L. M. Masinter.
23.39
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.,
(RECORD NODE (POSITION . LABEL) (RECORD POSITION (XLOC . YLOC)))
would permit the user to refer to POSITION, or to its subfields XLOC and
YLOC.
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
------------------------------------------------------------------------
82
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.
83
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.
23.40
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
84
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
(CONS (CONS 10 (CDAR FOO)) (QUOTE L1)).
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})
85
This expression is evaluated to effect the corresponding declaration.
------------------------------------------------------------------------
84
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.
85
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.
23.41
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,
ATOMRECORD, ASSOCRECORD, PROPRECORD, DATATYPE, HASHLINK or ACCESSFNS.
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
86
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
87
functions will take this extra field into account. For
------------------------------------------------------------------------
86
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
macro.
87
This type-field is used by the record package in the translation of
TYPE? expressions.
23.42
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) ...).
88
Accessing is performed with assoc, storing with
putassoc.
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
89
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
------------------------------------------------------------------------
88
or fassoc, depending on current CLISP declarations.
89
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.
23.43
90
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
91
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
integer.
SIGNEDBITS n field contains an n-bit signed
integer.
INTEGER or FIXP field contains a full word signed
integer.
FLOATING or FLOATP field contains a full word floating
point number.
For example, the declaration
(DATATYPE MSG ((FLG BITS 12) TEXT (CNT BITS 4) HEADER
(DATE SIGNEDBITS 18) (PRIO FLOATP)))
would define a data type MSG which occupies (in
INTERLISP-10) three words of storage with two pointer
92
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
------------------------------------------------------------------------
90
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.
91
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.
92
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
PRIO).
23.44
argument, the datum, and will be used for accessing.
93
setdef should be a function of two arguments, the
94
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
95
(GETHASH X).
ATOMRECORD fields is a list of property names, e.g.,
(ATOMRECORD (EXPR CODE MACRO BLKLIBRARYDEF)). Accessing
96
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
------------------------------------------------------------------------
93
setdef may be omitted, in which case, no store operations are
allowed.
94
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
[ACCESSFNS ((FIRSTCHAR (NTHCHAR DATUM 1)
(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.
95
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
CREATE NOT DEFINED FOR THIS RECORD.
96
As with ACCESSFNS, CREATE is not defined for ATOMRECORD records.
23.45
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
example,
(RECORD C(A . D)) and
(ACCESSFNS C((A CAR RPLACA)
(D CDR RPLACD)) C_(CONS A D))
97
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
98
nested to an arbitrary depth.
------------------------------------------------------------------------
97
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
declaration:
[ACCESSFNS LINK
((FROM (ELT FROMARRAY DATUM) (SETA FROMARRAY DATUM NEWVALUE))
(TO (ELT TOARRAY DATUM) (SETA TOARRAY DATUM NEWVALUE)))
LINK _ (PROG1 (SETQ LINKCNT (ADD1 LINKCNT))
(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).
98
Note that, in a few cases, it makes sense for a given field to have
more than one subdeclaration. For example, in
(RECORD (A . B) (PROPRECORD B (FOO FIE FUM)) (HASHLINK B C))
B is elaborated by both a PROPRECORD and a HASHLINK. Similarly,
(RECORD (A B) (RECORD A (C D)) (RECORD A (FOO FIE)))
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.
23.46
(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.
CREATE
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
99
fields initialized to the appropriate values. {assignments} is
optional and may contain expressions of the following form:
100
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
------------------------------------------------------------------------
99
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.
100
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.
23.47
101
used.
If the value of a field is neither explicitly specified, nor implicitly
specified via USING, REUSING or COPYING, the default value in the
102
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)),
(CREATE A B_T COPYING X)) as [LIST T(COPYALL(CADR X)) (COPYALL(CADDR X],
(CREATE A B_T REUSING X) as (CONS T (CDR X)), and
(CREATE A B_T) as (LIST T NIL 3).
TYPE?
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
103
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
------------------------------------------------------------------------
101
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.)
102
For non-pointer fields in DATATYPE records, zero is used.
103
The record package uses the pattern match compiler for this purpose.
23.48
(RECORD MSG (ID (FROM TO) . TEXT)
MSG @ (FMEMB (CAR DATUM) '(STATUS REPLY]
the expression (TYPE? MSG X) would translate to
104
(FMEMB (CAR X) (QUOTE (STATUS REPLY))).
Data-paths
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
(RECORD MSG (ID (FROM TO) . TEXT)) and (RECORD TEXT (HEADER . TXT))
also subdivide TEXT into two subfields. The user may then specify
X:MSG.HEADER to achieve the interpretation "X is a MSG, retrieve its
105
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.,
106
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
------------------------------------------------------------------------
104
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.
105
X:HEADER by itself is interpreted to mean that X is an instance of
TEXT, and translates as (CAR X).
106
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.
23.49
107
unambiguous (it must go through TEXT); however, X:TEXT is not, as
108
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
function
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
edited.
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
109
deleted, and thus will be recomputed using the new information. If
------------------------------------------------------------------------
107
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
ambiguous.
108
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.
109
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.)
23.50
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.
23.13 CLISPIFY
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
110
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))
111
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))) ...
becomes
112
... label (WHILE pred DO ... forms ...) ...
Clispify is not destructive to the original INTERLISP expression, i.e.,
113
clispify produces a new expression without changing the original.
Clispify will not convert expressions appearing as arguments to NLAMBDA
------------------------------------------------------------------------
110
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.
111
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.
112
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.
113
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.
23.51
114
functions.
The value of various global parameters affect the operation of clispify:
cl:flg
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.
clremparsflg
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
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.
clispifyenglshflg
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.
funnyatomlst
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
------------------------------------------------------------------------
114
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.
23.52
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.
clispifyprettyflg
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
115
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
116
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.
------------------------------------------------------------------------
115
Thus dwimify performs all DWIM transformations, not just CLISP
transformations, i.e., it does spelling correction, fixes 8-9
errors, handles F/L, etc.
116
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.
23.53
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
119
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.
------------------------------------------------------------------------
117
Note that the user could achieve the same effect by simply setting
the corresponding variables, and giving the functions dummy
definitions.
118
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."
119
Dwimify and dwimifyfns also "notice" free variables that are set in
the expression being processed.
23.54
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
compiling.
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
120
"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
------------------------------------------------------------------------
120
CLISP transformations, like all DWIM corrections, are undoable.
23.55
121
printed.
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
122
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,
123
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
found.
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
------------------------------------------------------------------------
121
This entire discussion also applies to CLISP transformation
initiated by calls to DWIM from dwimify.
122
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.
123
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.
23.56
124
LAST-ELL TREAT AS CLISP ?
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
125
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.
------------------------------------------------------------------------
124
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.
125
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.57
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
126
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
127
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
128
or changing selected property values.
------------------------------------------------------------------------
126
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.
127
The waiting time on such interactions is three times as long as for
simple corrections, i.e., 3*dwimwait.
128
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.
23.58
CLISPTYPE The property value of the property CLISPTYPE is
129
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
operators.
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.
------------------------------------------------------------------------
129
Unless otherwise specified, the property is stored on the property
list of the operator.
23.59
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
(SETQ CLISPCHARRAY (MAKEBITTABLE CLISPCHARS)).
clispinfixsplst is a list of infix operators used for spelling
correction.
As an example, suppose the user wants to make | be an infix character
operator meaning OR. He performs:
_(PUT (QUOTE |) (QUOTE CLISPTYPE)
(GETP (QUOTE OR) (QUOTE CLISPTYPE)))
_PUT(| LISPFN OR)
_PUT(| BROADSCOPE T)
_PUT(OR CLISPINFIX |)
_SETQ(CLISPCHARS (CONS (QUOTE |) CLISPCHARS))
_SETQ(CLISPCHARRAY (MAKEBITTABLE CLISPCHARS))
23.60
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
statements).
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
core.
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
23.61
130
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
undoable.
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
generated.
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
details.
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.
------------------------------------------------------------------------
130
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.
23.62
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
NIL.
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
131
of clispifyprettyflg:
ALL all functions
T,EXPRS functions currently defined
as exprs
------------------------------------------------------------------------
131
Another way to inform prettyprint to clispify functions is for the
function to have a CLISP declaration containing the word CLISPIFY.
23.63
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
132
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.
------------------------------------------------------------------------
132
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.
23.64
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
lowercase.
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.
23.65