home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Phoenix CD 2.0
/
Phoenix_CD.cdr
/
01e
/
lisp211.zip
/
PC-LISP.DOC
< prev
next >
Wrap
Text File
|
1986-05-16
|
136KB
|
2,929 lines
A GUIDE TO THE PC-LISP INTERPRETER (V2.11)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By Peter Ashwood-Smith
~~~~~~~~~~~~~~~~~~~~~~
University of Toronto,
~~~~~~~~~~~~~~~~~~~~~
Ontario, Canada.
~~~~~~~~~~~~~~~
With thanks to Brian Robertson for the math functions
May 15 1986,
for Guylaine
email: petera!utcsri or br!utcsri
mail: Peter Ashwood-Smith
#811, 120 St. Patrick St.
Toronto, Ontario,
Canada,
M5T-2X7.
phone: (416) 593-7574.
1
INTRODUCTION
~~~~~~~~~~~~
PC-LISP is a small implementation of LISP for ANY MS-DOS
machine. While small, it is capable of running a pretty good
subset of Franz LISP. The functions are supposed to perform in
the same way as Franz with a few exceptions made for effeciencies
sake. Version 2.11 has the following features.
- Types fixnum, flonum, list, port, symbol, string and
hunk, lambda, nlambda, macro and lexpr.
- Read Macros including splicing read macros.
- Full garbage collection of ALL types.
- Compacting relocating heap management.
- Access to some MSDOS BIOS graphics routines.
- Over 150 built in functions, sufficient to allow you
to implement many other Franz functions in PC-LISP.
- Stack overflow detection & full error checking
on all calls, tracing of user defined functions,
and dumping of stack via (showstack).
- One level of break from which bindings at point
of error can be seen.
- Access to as much (non extended) memory as you've
got and control over how this memory is spread
among the various data types.
- Will run on 256K PC/AT(w/woEGA)/XT and just about any
other MS-DOS machine. It is not hardware dependent.
This program is Shareware. This means that it you are free
to distribute it or post it to any BBS that you want. The more
the better. The idea is that if you feel you like the program and
are pleased with it then send us $15 to help cover development
costs. Source code for this program is available upon request.
You must however send me 3 blank diskettes and about $1.50 to
cover first class postage. The program can be compiled with any
good C compiler that has a pretty complete libc. In particular
the program will compile with almost no changes on most Unix
systems. A source code guide will probably be included with the
source if it is finished at the time I receive your source
request. Please do not request source unless you plan to use it.
Thanks to Brian Robertson also of the University of Toronto
Department of Computer Science for the math functions that he
wrote for the otherwise excellent Lattice C V2.03 compiler which
did not originally come with a math library.
2
A WARNING
~~~~~~~~~
As I mentioned previously this program was compiled with the
Lattice C compiler, as such the program contains code to which
Lattice Inc. holds a copyright. If you sell it I can only get
angry but Lattice could take you to court. And, as with all
software, you use it at your own risk. I will not be held
responsible for loss of any kind as a result of the correct or
incorrect use of this program.
A NOTE
~~~~~~
The rest of this manual assumes some knowledge of LISP,
MSDOS and a little programming experience. If you are new to LISP
or programming in general you should work your way through a book
on LISP such as LISPcraft by Robert Wilensky. You can use the
interpreter to run almost all of the examples in the earlier
chapters. I obviously cannot attempt to teach you LISP here
because it would require many hundreds of pages and there are
much better books on the subject than I could write. Also, there
are other good books on Franz LISP besides LISPcraft. I recommend
LISPcraft because it is the book I happen to use.
IF YOU WANT TO TRY PC-LISP RIGHT NOW
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Make sure that PC-LISP.EXE and PC-LISP.L are in the same
directory. Then type PC-LISP from the DOS prompt. Wait until you
get the "-->" prompt. If your machine has some sort of graphics
capability you can try the graphics demo as follows. Type "(load
'turtle)" without the "'s. Wait until you see the "t" and the
prompt "-->" again, then type "(GraphicsDemo)". You should see
some Logo like squirals etc. If you do not have any graphics
capability try "(load 'queens)" or "(load 'hanoi)" and then
(queens 5) or (hanoi 5) respectively. For a more extensive
example turn to the last couple of chapters in LISPcraft and look
at the deductive data base retriever. Type (load 'match) and look
at the match.l documentation. You can then play with all the
funcions mentioned in LISPcraft.
3
EXAMPLE LOAD FILES AND THE PC-LISP.L FILE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Included with PC-LISP (V2.11) are a number of .L files.
These include: PC-LISP.L, MATCH.L, TURTLE.L, DRAGON.L and perhaps
a few others. These are as follows.
PC-LISP.L
~~~~~~~~~
A file of extra functions to help fill the gap between PC
and Franz LISP. This file defines the pretty print function and a
number of macros etc. It will be automatically loaded from the
current directory or from the directory whose path is set in
LISP%LIB when PC-LISP is executed. The functions in this file are
NOT documented in this manual, look instead at a Franz manual.
MATCH.L
~~~~~~~
A small programming example taken from the last 2 chapters
of LISPcraft. It is a deductive data base retriever. This is
along the lines of PROLOG. Very few changes were necessary to get
this to run under PC-LISP.
TURTLE.L
~~~~~~~~
Turtle Graphics primitives and a small demonstration
program. To run the demo you call the function "GraphicsDemo"
without any parameters. This should run albeit slowly on just
about every MS-DOS machine. Note that the video functions that
are still experimental so use them for fun but don't rely on
them. These primitives look at the global variable !Mode to
decide what resolution to use. If you have mode 8 (640X400) you
should use it as the lines are much sharper. Turtle graphic modes
can be set by typing (setq !Mode -number-).
DRAGON.L
~~~~~~~~
A very slow example of a dragon curve. This one was
translated from a FORTH example in the April/86 BYTE. It takes a
long time on my 8Mhz 80186 machine so it will probably run for a
few hours on a PC or AT. I usually let it run for about 1/2 hour
before getting tired of waiting. To run it you just type (load
'dragon) then type (DragonCurve 16). If you have a higher
resolution machine like a Tandy 2000 then type (setq !Mode 8)
before you run it and it will look sharper at this (640x400)
resolution.
4
USERS GUIDE
~~~~~~~~~~~
The PC-LISP program is self contained. To run it just type
the command PC-LISP or whatever you called it. When it starts it
will start grabbing memory in chunks of 16K each. By default PC-
LISP will grab as much memory as possible but by setting the
LISP%MEM environment variable to an integer >= 3, PC-LISP will
stop when this many 16K blocks have been allocated. These will be
distributed to the three basic data types in percentages that you
can specify via 2 environment variables. The default is that 5%
of the memory will be allocated for alpha atoms. 5% will be
allocated for heap space, and the rest for cons,port, fixnum,
string, flonum and hunk cell types.If you set the environment
variables LISP%HEAP and LISP%ALPH to an integer between 1 and 85
these will become the new percentages for the heap and alpha
respectively, the rest going to cons, port,flonum, fixnum, hunk
and string cells. Note that the percentages are only accurate to
the nearest 16K boundary. In other words the set of 16K blocks
are divided among the three types as closely to the percentages
that you specify as possible. If the percentages that you specify
are unreasonable PC-LISP will stop with an error message,
otherwise PC-LISP will continue by giving back a very small
amount of memory for use by the standard I/O routines. You can
alter the amount given back by setting the environment variable
LISP%KEEP to the amount you want to give back (See memory
management). PC-LISP will then print the banner message, the
total memory available and the actual percentages that are
allocated to each object. Before processing the command line PC-
LISP will look for a file called PC-LISP.L first in the current
directory, next in the library directories specified in the
LISP%LIB environment variable as per the (load) function. If it
finds PC-LISP.L it will be loaded. Next PC-LISP will read the
parameters on the command line. The usage is as follows.
*
PC-LISP [=nnnn] [file]
The optional parameter =nnnn is the Lattice set stack size
option. It is preset to 32K and cannot be set smaller. You may
set it larger up to 64K if you wish. A 32K stack gives you about
466 recursive calls, 50K = 731 calls, 60K = 878 calls, and 64K =
936 calls. 8086 machines do not allow effecient stacks > 64K.
The files on the command line are processed one by one. This
consists of loading each file as per the (load) function. This
means that PC-LISP will look in the current directory for file,
then in file.l, then in the directories given in the LISP%LIB
environment variable, when found the file is read and every list
is evaluated. The results are NOT echoed to the console. Finally
when all the files have been processed you will find yourself
with the LISP top level prompt '-->'. Typing control-Z and ENTER
(MS-DOS end of file) when you see the '-->' prompt will cause PC-
LISP to exit to whatever program called it. If an error occurs
you will see the prompt 'er>'. For more info see the 'TERMINATION
OF EVALUATION' section of this manual and the commands
(showstack), (trace), and (untrace).
5
SYNTAX OR WHAT IS A LIST ANYWAY?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You will now be in the LISP interpreter and can start to
play with it. Basically it is expecting you to type an S-
expression whose value it will evaluate and return. Formally an
S-expression can be defined with a B.N.F Grammar where + means at
least one occurence of and, * means any number of occurences of.
<S-expression> ::= <fixnum> | <flonum> | <string> | <symbol>
| '(' <elements> ')'
+
<elements> ::= (<S-expression>) '.' <S-expression>
*
| (<S-expression>)
Where characters whose ascii values are in 0..31 are ignored
and have no effect other than delimiting other input items. Also
characters between ; and the end of a line are ignored in the
same way as the white space characters just described, these are
used to introduce comments into your LISP programs.
The the basic list elements <fixnum>, <flonum>, <string> and
<symbol> are defined as follows.
A <fixnum> is a sign + , - or none followed by a sequence of
digits 0..9. If the sequence of digits represents a fixnum larger
than can be stored in a 32 bit integer it is taken to be the
nearest <flonum>. A <fixnum> can always be spotted when it is
printed by the lack of a radix point. Examples are: 2, +2, -2,
and -333333 .
A <flonum> is a sign + , - or none followed by digits 0..9
which may be followed by a radix point and more digits 0..9 this
may optionally be followed by an exponent specifier 'e' or 'E'
which may optionally be followed by a sign + , - or none,
optionally followed by the exponent digits 0..9. A <flonum> can
always be spotted when it is printed by the presence of either a
radix point, or the exponent specifier 'e'. Examples : 2.0,
-2.0, +2.0, -2e10, -2e+20, -4.0E-13, 2E, -2E
A <string> is a " followed by up to 254 characters followed
by a terminating " or |. If the character \ is present in the
string and the following character is one of t,b,n,r or f the two
characters are replaced by a tab, backspace, newline, carriage
return or form feed respectively. If the \ is not followed by one
of the previously mentioned special characters, the following
character is used to subtitute the \ and itself in the string.
The \ is called the escape character and allows you to put non
printing formatting characters into a string. It also allows you
to put a " or | into a string which you could not
otherwise do. Examples: "abcd", "a\tb", "a\"b", "a\|b"
6
SYNTAX OR WHAT IS A LIST ANYWAY? CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A <symbol> is either a string delimited with |'s instead of
the "'s, or a sequence of characters none of which are spaces or
non printing characters with ascii values < 32 or > 126. If the
symbol is delimited with |'s the escape character \ may be used
in exactly the same manner as with a string and all characters
may be placed between the | delimiters with the exception of " or
| which must be preceeded by the escape character if they are to
be literally included in the symbol. If the symbol is not
delimited by |'s then the characters must be in a sequence that
follows the following rules. The characters ( ) [ ] " | and ; are
reserved and will cause termination of the symbol. The set of
characters that are skipped as white space (those with ascii
values in the range 0..31) are termed white space characters. The
set of characters that have been defined as read macros are
termed macro trigger characters. Only the ' char is initially a
read macro trigger character. The special characters are all of
these above character classes. Using these definitions, a symbol
can either start with a character in 0..9 or a character not in
0..9. If the character is not in 0..9 then the the following
characters can be chosen from among all but the special
characters. If the first character in the symbol is in 0..9 then
the last character must be chosen from among the set of all
characters that are neither special nor in 0..9. A symbol may
be composed of up to 254 characters all of which are significant.
Here are a few examples: a a1 1a 1- 1234abc #hi# !hi% An_ATOM
|ab\nc| junk.l ThisIsOneRatherLargeAtomThatDemonstratesLength.
An atomic S-expression is just one of a fixnum, flonum,
string and symbol. The only other type of S-expression is a list
S-expression.
In order to describe what a list S-expression is you need to
know some lisp terminology for the parts of a list. First a list
consists of two parts, the first element of the list is called
the car of the list and the rest of the elements in the list is
called the cdr of the list. Don't blame me I did not pick these
names, they come from register names on an old IBM machine. For
example the list (a b c) has car a and cdr (b c). Now that we
know the two parts of a list, we need to know how to build a
list. A list is built with a cons or constructor cell. The
constructor cell has two parts to it, the first is the car of the
list and the second is the cdr of the list. Hence one cons cell
describes one list. Its car part describes the first element in
the list, and its cdr part describes the list of the rest of the
elements in the list. For the example list (a b c), the internal
structure may look something like this, where a [ | ] represents
a cons cell *--> is a pointer, / is a nil pointer.
[*|*] ---> [*|*] ---> [*|/]
| | |
v v v
a b c
7
SYNTAX OR WHAT IS A LIST ANYWAY? CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is an example of a simple nested list which can be
input as : (a (b c) nil d) and which results in a structure like
this:
[*|*] ---> [*|*] ---> [/|*] ---> [*|/]
| | |
v v v
a [*|*] ---> [*|*] d
| |
v v
b c
The dot '.' can be used to separate the last element in a
list from the others in the list. When this occurs the
constructed list will have a slightly different last cons cell
second field. Rather than pointing to another cons cell whose car
points to the last element, this field will point directly to the
last element. For example inputting (a . b) creates the following
list structure, which will also print as (a . b).
[*|*]
| |
v v
a b
However if the last element in the list is another list and
we preceed it by a dot, the list is spliced into the upper list
as if the last element were not really a list. For example if I
were to input (a . (b . (c))) the following structure which is
identical to that constructed by (a b c) would be built. It will
also print as (a b c).
[*|*] ---> [*|*] ---> [*|/]
| | |
v v v
a b c
The dotted pair is not normally used except when you wish to
save storage. An example might be when you create a list of
symbols and their associated values. In this case making the
symbol and its associated value a dotted pair will save 1 cons
cell or about 10 bytes per symbol value pair.
Finally, I have shown these structures with symbol elements.
You can have absolutly any type as an element of a list,
including of course a list as shown in the second example above.
This is a very quick look at list structure and you should look
at LISPcraft for more details.
8
META SYNTAX
~~~~~~~~~~~
Following are some syntactic properties that are really
above the level of the syntax of a simple S-expression. Thus they
are called meta syntax conventions. I consider Meta syntax as
anything that does not conform to the B.N.F grammar previously
given. These extensions to the syntax of S-expressions consist of
any extra syntax intdoduced by built in or user defined read
macros and the replacement of multiple parenthesis which occurs
when a single super parenthesis is used.
PC-LISP supplies one built in read macro called 'quote' and
written using the little ' symbol. This read macro is just a
short hand way of writing the list (quote S). Where S is the S-
expression that follows the ' in the input stream. Here are some
examples of the simple conversion that the read macro performs on
your input.
'apples -- goes to --> (quote apples)
'|too late| (quote |too late|)
'(1 2 3) (quote (1 2 3))
''a (quote (quote a))
'"hi" (quote "hi")
If you are new to LISP you will soon see just how useful
this little read macro is when you start typing expressions. It
reduces the amount of typing you must do, reduces the amount of
list nesting you have to look at and draws attention to data in
your expressions.
User defined read macros are also provided. See the
(setsyntax) function in the next section of the manual. The
backquote macro together with comma (,) and at (@) are
implemented in the PC-LISP.L load file, but are not documented
here. Again, see LISPcraft for a discussion of these read macros.
9
META SYNTAX CONT'D
~~~~~~~~~~~~~~~~~~
PC-LISP also provides the meta or super parenthesis [ ].
One of the problems with LISP is the often overwhelming number of
parenthesis. It is very common to not supply enough closing )'s
and therefore have syntactic/semantic errors in your program. The
[ and ] characters when properly used allow you to force certain
structures even if enough )'s have not been provided. They
operate as follows. When the [ is encountered in the input, it
acts like a ( except that a note is made of the number of
unclosed ('s so far. Now when a ] is encountered in the input,
all lists up to and including the matching [ are closed. If there
is no matching [, ie none has been entered or all have been
closed with a ] then all open lists are closed. These parenthesis
may be nested up to 16 levels deep. But, deep nesting reduces
their usefullness. NOTE: If you open a list with a [ you must
close it with a ]. If you close it with a ) you will cause the
next [ ] pair to function incorrectly. The super nesting
information is reset whenever a new file is processed, or
whenever the break level is entered. That is, meta parenthesis
cannot be used accross a load or read of another file. Finally,
here are a few example legal inputs which use the meta
parenthesis and the list that results from their input.
((("hello world\n"] -- goes to --> ((("hello world\n")))
(([(((8 9] 10 ] ((((((8 9)))) 10))
[[[[[a]]]]] (((((a)))))
I should just mention again the fact that meta parenthesis
will not operate accross multiple reads. For example suppose you
were using (read) to get sublists from lists in one file, and
then swithced to reading lists from another file, then returned
to the original file. If the original input file made use of the
super parenthesis and the particular sublist being read was
between a pair of superparenthesis, this information will be lost
when you resume reading the file. Hence the next ] you hit will
terminate all open lists rather than those opened after the lost
[. The moral of this example is not to use the super parenthesis
in a data file whose reading may be interrupted by other I/O.
This is not a particularly imposing limitation.
10
SYNTAX ERRORS
~~~~~~~~~~~~~
When you enter a list which is not correct syntactically
the interpreter will return the wonderfully informative 'syntax
error' message. This message will either contain a further
message describing the error, or contain the text of the nearest
token to the error. You will have to figure out where it is in
the input list. Note that if you do not finish entering a list,
ie you put one too few closing )'s on the end, the interpreter
will wait until you enter it before continuing. If you are not
sure what has happened just type "]]" and all lists will be
closed and the interpreter will try to do something with the
list. If you are running input from a file the interpreter will
detect the end of file and give you a 'syntax error' because the
list was unclosed. Try also (showstack), it can help pinpoint the
error in a large load file. V2.11's syntax error handling is
pretty poor. Hopefully I can improve it in later versions
(suggestions are welcome).
EVALUATING S-EXPRESSIONS
~~~~~~~~~~~~~~~~~~~~~~~~
The interpreter expects an S-expression to be typed at the
prompt '-->'. The interpreter will evaluate the expression and
print the resulting S-expression. If the expression is either a
fixnum or a flonum, the interpreter just returns it because a
number evaluates to itself. If the expression is a string, the
interpreter also returns it because a string evaluates to itself.
If however the expression is a symbol, the interpreter returns
the binding of the symbol. It is an error to try to evaluate a
symbol that has no binding. Certain predefined atoms are
prebound, while all other symbols are unbound until bound by a
function call or a set / setq. If the expression is a list, then
the first element in the list is taken to be a function name or
description, the rest of the elements are taken to be parameters
to the function. The interpreter will evaluate each of the
arguments and then pass them to the appropriate function whose
result is returned. For example: The list S-expression with a '+'
as the first element and fixnums as elements will evaluate as the
sum of the fixnums. Eg.
-->(+ 2 4 6 8)
20
We can also compose these function calls by using list
nesting. Sublists are evaluated prior to upper levels. Eg:
-->(- (+ 6 8) (+ 2 4))
8
We can also perform operations on other objects besides
numbers. Suppose that we wanted to reverse the list (time flies
like arrows). Trying the built in function reverse we get:
-->(reverse (time flies like arrows))
--- error in built in function [apply] ---
11
EVALUATING S-EXPRESSIONS CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
But the interpreter will be confused! It does not know that
'time' is data and not a function taking arguments 'flies',
'like' and 'arrows'. To indicate it is upset PC-LISP prints out
the error message above and alters the prompt. More on this
later. What can we do to fix this? We must use the function
'quote' which returns its arguments unevaluated, hence the name
"quote".
-->(reverse (quote (time flies like arrows)))
(arrows like flies time)
Will give us the desired result (arrows like flies time). We
can do the same thing without using the (quote) function
directly. Remember the read macro ' above? Well it will replace
the entry '(time flies like arrows) with (quote(time flies like
arrows)). So more concisely we can ask PC-LISP to evaluate:
-->(reverse '(time flies like arrows))
(arrows like flies time)
This gives us the correct result without as much typing. You
will now note that the subtraction of 2+4 from 6+8 could also
have been entered as:
-->(- (+ '6 '8) (+ '2 '4))
8
However, the extra 's are redundant because a fixnum
evaluates to itself. In general a LISP expression is evaluated by
first evaluating each of its arguments, and then applying the
function to the arguments, where the function is the first thing
in the list. Remember that evaluation of the function (quote s1)
returns s1 unevaluated. LISP will also allow the function name
to be replaced by a function body called a lambda expression.
Which is just a function body without a name. Example:
-->((lambda(x)(+ x 10)) 14)
24
Which would be processed as follows. First the parameters to
the lambda expression are evaluated. That's just 14. Next the
body of the lambda expression is evaluated but with the value 14
bound to the formal parameter given in the lambda expression. So
the body evaluated is (+ x 10) where x is bound to 14. The result
is just 24. Note that lambda expressions can be passed as
parameters as can built in functions or user defined functions.
Hence I can evaluate the following input. Note I use the ]
character to close the three open lists rather than typing ))) at
the end of the line.
-->((lambda(f x)(f (car x))) '(lambda(l)(car l)) '((hi]
hi
12
EVALUATING S-EXPRESSIONS CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Which evaluates as follows. The parameters to the call which
are the expressions '(lambda(l)(cdr l)) and '((hi)) are
evaluated. This results in the expressions being returned because
they are quoted. These are then bound to 'f and 'x respectively
and the body of the first lambda expression is evaluated. This
means that the expression ((lambda(l)(car l))(car ((hi)))) is
evaluated. So again the parameters to the function are evaluated.
Since the only parameter is (car ((hi))) it is evaluated
resulting in (hi). This is then bound to l and (car l) is
evaluated giving hi.
PC-LISP is also capable of handling all other function body
kinds. These are lambda, nlambda, lexpr, fexpr and macro kinds.
These expression kinds may all have multiple bodies which are
evaluated in order, the last one producing the value that is
returned. See the section on BUILT IN FUNCTIONS and MACROS for
more details on these kinds and how they operate. Better yet read
LISPcraft.
13
TERMINATION OF EXPRESSION EVALUATION
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are three distinct ways that evaluation can terminate.
First, evaluation can end naturally when there is no more work to
do. In this case the resulting S-expression is printed on the
console and you are presented with the prompt "-->". Second, you
can request premature termination by hitting the CONTROL-BREAK or
CONTROL-C keys simultaneously (hereafter referred to as CONTROL-
BREAK). Note that this will only interrupt list evaluation, it
will NOT interrupt garbage collection which continues to
completion. So, if you hit CONTROL-BREAK or CONTROL-C and you
don't get any response, wait a second or two because it will
respond after garbage collection ends. Finally, execution can
terminate when PC-LISP detects a bad parameter to a built in
function, a stack overflows, a division by zero is attempted, or
an atom is unbound etc. In all cases but a normal termination you
will be returned to a break error level. This is when the prompt
looks like 'er>'. This means that variable bindings are being
held for you to examine. So if the evaluation aborts with the
message "error in built in function [car]", you can examine the
atom bindings that were in effect when this error occurred by
typing the name of the atom desired. This causes its binding to
be displayed. When you are finished with the break level just hit
CONTROL-Z plus ENTER and you will be placed back in the normal
top level and all bindings that were non global will be gone.
Note you can do anything at the break level that you can do at
the top level. If further errors occur you will stay in the break
level and any bindings at the time of the second error will be in
effect as well as any bindings that were in effect at the
previous break level. If bindings effecting atoms whose values
are being held in the first break level are rebound at the second
break level these first bindings will be hidden by the secondary
bindings.
An error in built in functions 'eval' or 'apply' can mean
two things. First, your expression could contain a bad direct
call to eval or apply. Or, your code may be trying to apply a
function that does not exist to a list of parameters, or trying
to apply a bad lambda form. The interpreter does not distinguish
an error made in a direct call by you to eval/apply or an
indirect call to eval/apply, made by the interpreter on your
behalf to get the expression evaluated.
It is also useful to know what the circumstances of the
failure were. You can display the last 20 evaluations with the
command (showstack). This will print the stack from the top to
the 20th element of the stack. This gives you the path of
evaluation that lead to the error. For more information on the
(showstack) command look in the section FUNCTIONS WITH SIDE
EFFECTS OR THAT ARE EFFECTED BY SYSTEM.
It is possible but hopefully pretty unlikely that the
interpreter will stop on an internal error. If this happens try
to duplicate it and let me know so I can fix it.
14
DATA TYPES IN PC-LISP
~~~~~~~~~~~~~~~~~~~~~
PC-LISP has the following data types, 32 bit integers,
single precision floating point numbers, lists, ports for file
I/O, alpha atoms, strings and hunks (up to 126 in length just one
short of Franz!). The (type) function returns these atoms:
fixnum - a 32 bit integer.
flonum - a single precision floating point number.
list - a list of cons cells.
symbol - an alpha atom, with print name up to 254 chars
which may include spaces tabs etc, but which
should not include an (ascii 0) character.
Symbols may have property, bindings and functions
associated with them. Symbols with same print
name are the same object.
string - A string of characters up to 254 in length. It
has nothing else associated with it. Strings
with same print name are not necessarily the
same object.
port - A stream that is open for read or write. This
type can only be created by (fileopen).
hunk - An array of 1 to 126 elements. The elements may
be of any other type including hunks. Franz
allows 127, the missing element is due to a space
saving decision. This type can only be created
by a call to (hunk) or (makhunk).
Fixnums and flonums are together known as numbers. The read
function will always read a number as a flonum and then see if it
can represent it as a fixnum without loss of precision. Hence if
the number 50000000000 is entered it will be represented as a
flonum because it exceeds the precision of a fixnum. If a number
has a decimal point or exponent specifier 'e' or 'E' in it, it is
assumed to be a flonum even if there are no non zero digits
following the radix point.
Fixnums and flonums will not appear the same when printed.
The print function will output a flonum with a radix point and
perhaps an exponent specifier if it will make the output smaller.
Hunks when printed appear as { e0 e1 e2 .... eN }. They are
indexed from zero. They cannot be entered, ie there is no read
mechanism for creating them you must create them with a function
call. It is possible to implement arrays and vectors using hunks
although I have not done this yet. See HUNKS.
15
THE BUILT IN FUNCTIONS AND VARIABLES
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Following is a list of each built in function. I will denote
the allowed arguments as follows:
- a1...aN are alpha atom parameters, type symbol.
- h1...hN are string or alpha atoms, type string or symbol.
- x1...xN are integer atom parameters, type fixnum (32bits).
- f1...fN are float atom parameters, type flonun.
- n1...nN are number atom parameters, type flonum or fixnum.
- z1...zN are numbers but all are of the same type.
- l1...lN are lists, must be nil or of type list.
- p1...pN are port atom parameters, type port.
- s1...sN are S-expressions (any atom type or list)
- H is a hunk.
Additional Definitions:
~~~~~~~~~~~~~~~~~~~~~~~
"{a|d}+" means any sequence of characters of length greater
than 0 consisting of a's and d's in any combination. This
defines the car,cdr,cadr,caar,cadar... function class as
follows: "c{a|d}+r".
"[ -stuff- ]" indicates that -stuff- is/are optional and if
not provided a default will be provided for you.
"*-stuff-*" indicates that -stuff- is not evaluated. An
example of this is the function (quote *s1*) whose single S-
expression parameter s1 is enclosed in *'s to indicate that quote
is passed the argument s1 unevaluated.
For the simpler functions I will describe the functions
using a sort of "if (condition) result1 else result2" notation
which should be pretty obvious to most people. For functions that
are a little more complex I will give a short English description
and perhaps an example. If the example code shows the '-->'
prompt you should be able to type exactly what follows each
prompt and get the same responses from PC-LISP. If the example
does not show a '-->' prompt the example is a code fragment and
will not necessarily produce the same results shown.
16
PREDEFINED GLOBAL VARIABLES (ATOMS)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A number of atoms are globally prebound by PC-LISP. These
variables are testable and setable by you but in some cases
altering the bindings is highly inadvisable. Note that a binding
can be inadvertantly altered by defining one of these atoms as a
local or parameter atom to a function or a prog, or directly by
using 'set' or 'setq'.
"displace-macros" - This atom when non nil will cause macro
expansion to be follwed by code substitution if such substitution
is possible. The default value is nil meaning no substitution.
"t" - This atom means 'true', it is bound to itself.
Various predicates return this to indicate a true condition. You
should NOT change the binding of this atom, to do so will cause
PC-LISP to produce incorrect answers.
"nil" - This is not really an atom, it represents the empty
list (). It is not bound to () but rather equivalent to () in all
contexts. Any attempt to create a symbol with print name "nil"
will result in ().
"$ldprint" - Is initially bound to "t". When not bound to
"nil" this atom causes the printing of the -- [file loaded] --
message when the function (load file) is executed. When "nil"
this atom prevents the printing of the above message. This is
useful when you want to load files silently under program
control.
"$gcprint" - Is initially bound to "nil". When bound to
"nil" garbage collection proceeds silently. If bound non "nil"
then at the end of a garbage collection cycle 4 numbers are
printed. The first is the number of collection cycles that have
occured since PC-LISP was started, the second is the percentage
of cons cells that are in use, the third the percentage of alpha
cells, and the third the percentage of heap space that is in use.
These last three numbers are exactly what you get back with a
call to (memstat).
"$gccount$ - Is initially bound to 0. It increases by one
every time garbage collection occurs. This number is the same as
the first number printed when $gcprint is bound non "nil" and
garbage collection occurs. While you can set $gccount$ to any
value you want, its global binding will be reset to the correct
garbage collection cycle count whenever collection finishes.
"piport", "poport", "errport" - Are bound to the standard
input, standard output and standard error ports respectively. You
can use these to force patom, princ, print and pp-form to send
their output to the standard output or error. Or, to force read
and readc to get their input from the standard input. They are
initially bound to the keyboard and screen. You can alter their
bindings if you wish but this is not recommended.
17
THE MATH FUNCTIONS
~~~~~~~~~~~~~~~~~~
Functions that operate on numbers, fixnums or flonums. Note
that the arrow --X--> may indicate what type is returned. If X is
's' then the same type as the parameter(s) selected is returned.
If X is 'f' then a flonum type is returned. If X is 'x' then a
fixnum is returned. If X is 'b' then the best type is returned,
this means that a fixnum is returned if possible. Note that you
should use fixnums together with "1+, 1- zerop" when ever
possible because doing so gives nearly a 50% decrease in run time
for many expressions, especially counted loops or recursion.
TRIG AND MIXED FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~
(abs n1) --s-> absolute value of n1 is returned.
(acos n1) --f-> arc cosine of n1 is returned.
(asin n1) --f-> arc sine of n1 is returned.
(atan n1 n2) --f-> arc tangent of (quotient n1 n2).
(cos n1) --f-> cosine of n1, n1 is radians
(exp n1) --f-> returns e to the power n1.
(expt n1 n2) - b-> n1^n2 via exp&log if n1 or n2 flonum.
(fact x1) --x-> returns x1! ie x1*(x1-1)*(x1-2)*....1
(fix n1) --x-> returns nearest fixnum to number n1.
(float n1) --f-> returns nearest flonum to number n1.
(log n1) --f-> natural logarithm of n1 (ie base e).
(log10 n1) --f-> log base 10 of n1 {not present in Franz}
(lsh x1 x2) --x-> x1 left shifted x2 bits (x2 may be < 0).
(max n1..nN) --s-> largest of n1...nN or (0 if N = 0)
(min n1..nN) --s-> smallest of n1..nN or (0 if N = 0)
(mod x1 x2) --x-> remainder of x1 divided by x2.
(random [x1])--x-> random fixnum, or random in 0...x1-1.
(sin n1) --f-> sine of n1, n1 is radians.
(sqrt n1) --f-> square root of n1.
(1+ x1) --x-> x1+1.
(add1 n1) --b-> n1+1 (done with fixnums if n1 is fixnum).
(1- x1) --x-> x1-1.
(sub1 n1) --b-> n1-1 (done with fixnums if n1 is fixnum).
BASIC MATH FUNCTIONS
~~~~~~~~~~~~~~~~~~~~
(* x1 ...... ..xN) --x-> x1*x2*x3*.....nN (or 1 if N = 0)
(times n1 .. ..nN) --b-> n1*n2*n3......nN (or 1 if N = 0)
(product n1....nN) --b-> Ditto
(+ x1....... ..xN) --x-> x1+x2+x3+.....xN (or 0 if N = 0)
(add n1 .......nN) --b-> n1+n2+n3+.....nN (or 0 if N = 0)
(sum n1 .......nN) --b-> Ditto
(plus n1.......nN) --b-> Ditto
(- x1....... ..xN) --x-> x1-x2-x3-.....xN (or 0 if N = 0)
(diff n1.......nN) --b-> n1-n2-n3-.....nN (or 0 if N = 0)
(difference....nN) --b-> Ditto
(/ x1....... ..xN) --x-> x1/x2/x3/.....xN (or 1 if N = 0)
(quotient n1...nN) --b-> n1/n2/n3/.....xN (or 1 if N = 0)
Note that the Basic functions that operate on numbers will
return a fixnum if the result can be stored in one.
18
THE BOOLEAN FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~
These functions all return boolean values. The objects t and
nil represent true and false respectively. Note however that most
functions treat a non nil value as being t. t is a predefined
atom whose binding is t while nil is not a real atom but rather
a lexical item that is EQUIVALENT to () in all contexts. Hence
nil and () are legal as both an atom and a list in all functions.
Note when comparing flonums and fixnums you cannot use (eq)
because they are not identical objects. In Franz (eq 1 1) returns
t because of a space saving trick. You should not rely on this
working in other LISPS including PC-LISP.
(alphalessp h1 h2) ---> if (h1 ASCII before h2) t else nil;
(atom s1) ---> if (s1 not type list) t else nil;
(and s1 s2 .. sN) ---> if (a1...aN all != nil) t else nil;
(boundp a1) ---> if (a1 bound) (a1.eval(a1)) else nil;
(eq s1 s2) ---> if (s1 & s2 same object) t else nil;
(equal s1 s2) ---> if (s1 has s2's structure) t else nil;
(evenp n1) ---> if (n1 mod 2 is zero) t else nil;
(fixp s1) ---> if (s1 of type fixnum) t else nil;
(floatp s1) ---> if (s1 of type flonum) t else nil;
(greaterp n1...nN) ---> if (n1>n2>n3...>nN) t else nil;
(hunkp s1) ---> if (s1 of type hunk) t else nil;
(lessp n1...nN) ---> if (n1<n2<n3...<nN) t else nil;
(listp s1) ---> if (s1 of type list) t else nil;
(minusp n1) ---> if (n1 < 0 or 0.0) t else nil;
(not s1) ---> if (s1 != nil) nil else t;
(null s1) ---> Ditto
(numberp s1) ---> if (s1 is fix of float) t else nil;
(numbp s1) ---> Ditto.
(or s1 s2 .. sN) ---> if (any si != nil) t else nil;
(oddp n1) ---> if (n1 mod2 is non zero) t else nil;
(plusp n1) ---> if (n1 > 0 or 0.0) t else nil;
(portp s1) ---> if (s1 of type port) t else nil;
(zerop n1) ---> if (n1 = 0 or 0.0) t else nil;
(< z1 z2) ---> if (z1 < z2) t else nil;
(= z1 z2) ---> if (z1 = z2) t else nil;
(> z1 z2) ---> if (z1 > z2) t else nil;
Note carefully the difference between (eq) and (equal). One
checks for identical objects, ie the same object, while the other
checks for two objects that have the same "structure" and
identical leaves.
Note that the (and) and (or) functions evaluate their
arguments one by one until the result is known. Ie, short circuit
evaluation is performed.
Note that proper choice of fixnums over flonums and proper
choice of fixnum functions can yield large performance
improvements. For example (zerop n) is faster than (= 0 n)
because (zerop) like all functions that take number parameters is
biased towards fixnums.
19
LIST & ATOM CREATORS AND SELECTORS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These functions will take lists and atoms as parameters and
return larger or smaller lists or atoms. They have no side
effects on the LISP system nor are their results affected by
anything other than the values of the parameters given to them.
These functions are all nondestructive they do not alter their
parameters in any way.
(append l1..ln) ---> list made by joining all of l1..ln.
If any of l1..ln is nil they are
ignored.
(ascii n1) ---> atom with name 'char' where 'char'
has ordinal value n1:(0 < n1 < 256).
(assoc s1 s2) ---> if s2 is a list of (key.value) pairs
then assoc --> (key.value) from s2,
where (equal key a1) is t else nil.
(car l1) ---> first element in l1. If l1 is nil
car returns nil.
(cdr l1) ---> Everything but the car of l1. If
l1 is nil cdr returns nil.
(c{a|d}+r l1) ---> performs repeated car or cdr's on
l1 as given by reverse of {a|d}+.
Returns nil if it cars or cdrs off
the end of a list.
(character-index h1 h2) -x-> Returns the index (from 1) of first
char in h2 in h1. h2 can be a fixnum
ascii value. Returns nil if none.
(concat h1 .. hN) ---> Forms a new atom by concatenating
all the strings or atoms. Or nil if
if N = 0.
(cons s1 s2) ---> list with s1 as 1st elem s2 is rest.
If s2 is nil the list has one
element. If s2 is an atom the pair
print with a dot. (cons 'a 'b) will
print as (a . b).
(explode h1) ---> list of chars in print name of h1.
If h1 is nil returns (n i l)
(exploden h1) ---> list of ascii values of chars in h1.
If h1 is nil returns (110 105 108).
(get_pname h1) ---> String equal to print name of atom
h1 or same as string h1.
20
LIST & ATOM CREATORS AND SELECTORS (CONT'D)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(hunk-to-list H) ---> Returns a list whose elements are
(eq) to those of hunk H and in the
same order.
(implode l1) ---> atom with name formed by compressing
first char of each atoms print name
in l1. Imploding (n i l) returns
the empty list nil.
(last l1) ---> returns the last element in l1. If
l1 is nil it returns nil.
(length l1) -x-> fixnum = to length of list l1.
The length of nil is 0.
(list s1 s2...sN) ---> a list with elements (s1 s2 ...sN)
If N = 0 list returns nil.
(member s1 l1) ---> If (s1 (equal) to a top level sub
list of l1) this sublist, else nil.
(memq s1 l1) ---> If (s1 (eq) to a top level sub list
of l1) this sublist, else nil.
(nth n1 l1) ---> n1'th element of l1 (indexed from 0)
like (cad...dr l1) with n1 d's.
(nthcdr n1 l1) ---> returns result of cdr'ing down the
list n1 times. If n1 < 0 it returns
(nil l1).
(nthchar h1 n1) ---> n1'th char in the print name of h1
indexed from 1.
(pairlis l1 l2 l3) ---> l1 is list of atoms. l2 is a list
of S-expressions. l3 is a list of
((a1.s1)....) The result is the
pairing of atoms in l1 with values
in l2 with l3 appended (see assoc).
(quote *s1*) ---> exactly s1 unevaled without changes.
(reverse l1) ---> the list l1, reversed at top level.
(type s1) ---> list,flonum,port,symbol, fixnum or
hunk as determined by the type of
the parameter s1.
21
LIST & ATOM CREATORS AND SELECTORS (CONT'D)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(sizeof h1)
~~~~~~~~~~~
Will return the number of bytes necessary to store an object
of type h1. Legal values for h1 are 'list,'symbol,'flonum,
'fixnum, 'string , 'hunk and 'port. The size returned is the
amount of memory used to store the cell, incidental heap space,
property list space, binding stack space and function body space
is not counted for types 'symbol, 'string or 'hunk.
(stringp s1)
~~~~~~~~~~~~
Will return t if the S-expression s1 is of type string,
otherwise it returns nil.
(substring h1 n1 [n2])
~~~~~~~~~~~~~~~~~~~~~~
If n1 is positive substring will return the substring in
string h1 starting at position n1 (indexed from 1) for n2
characters or until the end of the string if n2 is not present.
If n1 is negative the substring starts at |n1| chars from the end
of the string and continues for n2 characters or to the end of
the string if n2 is not present. If the range specified is not
contained within the bounds of the string, nil is returned.
(memusage s1) { not in Franz }
~~~~~~~~~~~~~
Will return the approximate amount of storage that the S-
expression s1 is occupying in bytes. The printname heap space is
included in this computation as are file true name atoms. This
function is not smart, it will count an atom twice if it is
referenced more than once in the list. The space count does not
include storage needed for binding stacks, property lists, or
function bodies that are associated with a particular atom. Hunk
and string space include the heap space owned by the cell.
22
NONINTERNING/INTERNING FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unless otherwise stated in this manual, any function that
returns an atom will intern it (put it on the oblist). However
the following functions are not included in the above statement.
Note also that the list returned by (oblist) is a copy of the
real oblist. Note carefully that the atoms created by read are
interned. See a really good LISP manual on this stuff because it
can be really confusing.
(copysymbol a1 s1)
~~~~~~~~~~~~~~~~~~
Returns an UNINTERNED copy of atom a1. If the flag parameter
s1 is non nil then the returned atom has property, value, and
function definitions eq to a1 otherwise its property, value and
function definitions are nil,undefined, and undefined
respectively.
(gensym [a1])
~~~~~~~~~~~~~
Returns an UNINTERNED atom whose print name is of the form
Xnnnnn where X is either 'g' or the print name of a1 (if a1 is
provided) and nnnnnn is some number such that no interned or
uninterned atom in the system has the same print name. Note that
the the existence of a clashing interned or uninterned atom is
checked before selecting the value of nnnnn.
(intern a1)
~~~~~~~~~~~
Will INTERN a1 on the oblist. If an atom with the same print
name as a1 is already on the oblist THIS ATOM IS RETURNED,
otherwise a1 is physically added to the oblist and is returned.
(remob a1)
~~~~~~~~~~
Will return a1 after having physically removed a1 from the
oblist. Future calls to read will create a new atom with the same
print name as a1. This can be confusing if a1 had a function
definition, property, or value assocaited with it.
(maknam l1)
~~~~~~~~~~~
Takes a list of atoms as parameter and returns an UNINTERNED
atom whose print name is the concatenation of the first character
in the print names of every atom in the provided list parameter.
This is the same as (implode) except that implode interns its
result.
(uconcat a1 a2 ... aN)
~~~~~~~~~~~~~~~~~~~~~~
Returns an UNINTERNED atom whose print name is the
concatenation of each of the print names of a1...aN. If N=0, or
if N=1 and a1 is nil, then the empty list nil is returned. Note
that the empty list nil is neither interned or uninterned because
it is not really an atom.
23
FILE I/O FUNCTIONS
~~~~~~~~~~~~~~~~~~
These functions perform simple list/atom and character I/O
you must be careful when writing lists to files to terminate with
a new line before closing the file. Otherwise they may cause
problems for some MS-DOS editors etc. These functions operate on
type 'port' which is returned by 'fileopen' and which when
printed is just %file@nn% where 'file' is the name of the
associated port and nn is the file number 0..(20?). MS-DOS
imposes limits on the number of open files you can have. This is
usually more than 16 with no more than 5 to the same file.
(close p1)
~~~~~~~~~~
Closes the port p1 and returns t. Note that you must be
careful to write a line feed (ascii 10) to the file before
closing it in some cases. Certain MS-DOS text editors do not like
files with very large line lengths.
(fileopen h1 h2)
~~~~~~~~~~~~~~~~
Opens file whose name is h1 for mode h2 access. h1 should be
a file name optionally including a path. h2 should be one of 'r,
'w, or 'a meaning read, write or append respecively. The function
if successful returns a port atom which will print as %file@nn%.
If the function is not successful nil is returned. Fileopen does
not look in any but the current directory for a relative path or
file. Note devices like "con:" are allowed in place of file
names.
(filepos p1 [x1])
~~~~~~~~~~~~~~~~~
If fixnum parameter x1 is not provided filepos will return
the current file position where the next read/write operation
will take place for port p1. If x1 is provided it is interpreted
as a new position where the next read/write should take place.
The read/write pointer is seeked accordingly and the value x1 is
returned if the seek completes successfully. Otherwise nil.
(load h1)
~~~~~~~~~
Will try to find the file whose name is h1 and load it into
PC-LISP. Loading means reading every list, and evaluating it. The
results of the evaluation are NOT printed on the console. In
trying to find the file h1, load uses the following strategy.
First it looks for file h1 in the current directory, then it
looks for h1.l in the current directory. Then it gets the value
of the environment variable LISP%LIB which should be a comma
separated sequence of MS-DOS paths (exactly the same syntax as
for PATH). It then repeats the above searching strategy for every
directory in the path list. For example if I entered this from
the COMMAND shell:
24
FILE I/O FUNCTIONS (CONT'D)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
"set LISP%LIB= c:\usr\libs\lisp\bootup ; c:\lisp\work\;"
then ran PC-LISP, it would try to load the file PC-LISP.L first
from the current directory, then from the two directories on the
C drive that are specified in the above assignment. Future calls
to (load h1) will also look for files in the same way. When a
file has been successfully loaded PC-LISP examines the value of
atom $ldprint. If this value is non-nil (default is t) PC-LISP
will print a message saying that the file was loaded
successfully. If this value is nil then no message is printed. In
either case if the load is successful a value of t is returned
and if the load fails a value of nil is returned.
(patom s1 [p1]) & (princ s1 [p1])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Will cause the S-expression s1 to be printed without
delimiters or escapes on the output port p1, or on the standard
output if no p1 parameter is given. Without delimiters means that
if an atom has a print name that is not legal without the | |
delimiters or with an escape \, neither will be added when
printing the list with patom. patom returns s1 while princ
returns t. Strings will print without quotes or escapes.
(print s1 [p1])
~~~~~~~~~~~~~~~
Will cause the S-expression s1 to be printed with delimiters
and escapes if necessary on the output port p1, or on the
standard output if no p1 parameter is given. All atoms that would
require | | delimiting, strings that require " " delimiting and
characters that would have to be preceeded by the escape to be
input, will be printed with the delimiters and any necessary
escapes. If a character is one of the format characters tab, back
space, carriage return, line feed or form feed, it will print
preceeded by the escape as \t \b \r \n or \f respectively. If the
characters ordinal value is < 32 or > 126 and it is not a format
character, it will print as \?. Print returns the expression s1.
(read [p1 [s1]])
~~~~~~~~~~~~~~~~
Reads the next S-expression from p1 or from the standard
input if p1 is not given and returns it. If s1 is given and end
of file is read the read function will return s1. If s1 is not
given and end of file is read the read function will return nil.
(readc [p1 [s1]])
~~~~~~~~~~~~~~~~~
Reads the next character from p1 or from the standard input
if p1 is not given and returns it as an atom with a single
character name. If s1 is given and end of file is read the readc
function will return s1. If s1 is not given and end of file is
read the readc function will return nil.
25
FILE I/O FUNCTIONS (CONT'D)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
(sys:unlink h1)
~~~~~~~~~~~~~~~
Will erase the file whose name is the print name of atom h1.
If the erase is successful a value of 0 is returned. If the erase
is unsuccessful a value of -1 is returned.
(truename p1)
~~~~~~~~~~~~~
Will return an atom whose print name is the same as the name
of the file associated with port p1. This is just the same as the
value printed between the % and @ signs when a port is printed.
(flatsize s1 [x1])
~~~~~~~~~~~~~~~~~~
Returns the number of character positions necessary to print
s1 using the call (print s1). If x1 is present then flatsize will
stop computing the output size of s1 as soon as it determines
that the size is larger than x1. This feature is useful if you
want to see if something will fit in some small given amount of
space but not knowing if the list is very big or not.
(flatc s1 [x1])
~~~~~~~~~~~~~~~
Returns the number of character positions necessary to print
s1 using the call (patom s1). x1 is the same as in flatsize.
(pp-form s1 [ p1 [x1] ] )
~~~~~~~~~~~~~~~~~~~~~~~~~
Causes the expression s1 to be pretty-printed on port p1
indented by x1 spaces. If p1 is absent the standard output is
assumed. If x1 is absent an indent of 0 is assumed. If s1
contains a list such as (prog .... label1 ... label2...) the
normal indenting will be ignored for label1 & label2 etc. This
causes the labels to stand out. For example IF the following
function were present in PC-LISP then I could run pp-form:
-->(pp-form (getd 'character-index-written-in-lisp))
(lambda (a c)
(prog (n)
(setq n 1 a (explode a))
(cond ((fixp c) (setq c (ascii c))))
nxt:
(cond ((null a) (return nil)))
(cond ((eq (car a) c) (return n)))
(setq n (1+ n) a (cdr a))
(go nxt:)))
Note that the PC-LISP.L file contains a definition of pp,
the LISP general function pretty printer. It makes use of pp-
form to get its work done. I will not describe it here but it is
fully described in LISPcraft.
26
FUNCTIONS WITH SIDE EFFECTS OR THAT ARE EFFECTED BY SYSTEM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These functions will either have an effect on the way the
system behaves in the future or will give you a result about the
way the system has behaved in the past and future calls will not
necessarily give the same results.
(def *a1* *l1*)
~~~~~~~~~~~~~~~
a1 is a function name and l1 is a lambda,nlambda, lexpr or
macro body. The body is associated with the atom a1 from now on
and can be used as a user defined function. Def returns a1.
-->(def first (lambda(x)(car x)))
-->(def llast (lexpr(n)(last (arg n))))
-->(def myadd (nlambda(l)(eval(cons '+ l))))
-->(def firstm (macro(l)(cons 'car (cdr l))))
(defun *a1* [*a2*] *s0* *s1* *s2* ....*sN*)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Defun will do the same job as "def" except that it will
build the expression body for you. a1 is the name of the
expression that you are defining, a2 is an optional expression
kind indicator which may be either expr, fexpr or macro. The
default is expr. These kinds correspond directly to lambda,
nlambda and macro forms. s0 specifies the formal parameters to
the expression. Usually this is just a list of symbols. If it is
a single symbol it is assumed that the symbol is the single
parameter to an lexpr form and an lexpr form will be constructed
from the ensuing bodies s1....sn. If it is a list of symbols then
a lambda, nlambda or macro body will be constructed from the
bodies s1...sn according to the kind specified by parameter a2.
For example, these calls to defun do the same job as the above
calls to def.
-->(defun first(x)(car x))
-->(defun llast n (last (arg n)))
-->(defun myadd fexpr(l)(eval(cons '+ l)))
-->(defun firstm macro(l)(cons 'car (cdr l)))
(exit)
~~~~~~
The LISP interpreter will exit to MSDOS. Depending on how
big you set LISP%MEM MSDOS may ask for a system disk to reload
COMMAND.COM. Note that the video mode will be left alone if you
call exit. But if you leave via CONTROL-Z the video mode will be
set to 80x25B&W. (Only if you have made a call to (#scrmde#)).
(gc)
~~~~
Starts garbage collection of alpha and cell space. Returns t
27
FUNCTIONS WITH SIDE EFFECTS OR THAT ARE EFFECTED BY SYSTEM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CONT'D
~~~~~~
(get a1 a2)
~~~~~~~~~~~
Will return the value associated with property key a2 in
a1's property list. This value will have been set by a previous
call to (putprop a1 s1 a2). Example:
-->(get 'frank 'lastname)
(getd a1)
~~~~~~~~~
Will return the lambda, nlambda or macro expression that is
associated with a1 or nil if no such expression is associated
with a1.
(getenv h1)
~~~~~~~~~~~
Will return an atom whose print name is the string set by
environment variable h1. For example we can get the PATH variable
setting by evaluating (getenv 'PATH). Note that these must be in
upper case because MS-DOS converts the variable names to upper.
(hashtabstat)
~~~~~~~~~~~~~
Will return a list containing 503 fixnums. Each of these
represents the number of elements in the bucket for that hash
location in the heap hash table. 503 is the size of the hash
table. This is not especially useful for you but it gives me a
way of checking how the hashing function is distributing the
heap using cells. Heap using cells are symbol, string and hunk.
The cell itself is allocated from the alpha or other memory
blocks while its variable length space is allocated from the
heap. Hence this table contains the oblist plus strings and
hunks. Note however that unlike symbols, strings and hunks are
not unique objects.
(memstat) { not present in Franz }
~~~~~~~~~
Returns three fixnums. The first is the percentage of cell
space that is in use. The second is the percentage of alpha cell
space and the third is the percentage of heap space in use. When
any of these reach 100%, garbage collection will occur. Alpha and
cell space is collected together. Heap space is only collected
when you run out. After garbage collection you will see these
three percentages drop. The alpha and cell percentages should
drop to tell you how much memory is actually in use at that
moment. The heap space when compacted and gathered will not
necessarily drop to indicate how much you really have left. This
is because heap space is gathered in blocks of 16K, not all at
once as with atoms and cells. So, there will almost certainly be
more than 20% free heap space in other non compacted blocks even
if memstat reports 80% of the heap space is in use.
28
FUNCTIONS WITH SIDE EFFECTS OR THAT ARE EFFECTED BY SYSTEM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CONT'D
~~~~~~
(oblist)
~~~~~~~~
Returns a list of most known symbols in the system at the
current moment. Note that if you call oblist and assign the
result somewhere you will cause every one of those objects to be
kept by the system. If there are lots of large alpha atoms the
heap and alpha space will be tied up until you set the assigned
variable to some other value. Several special internal atoms are
not placed in the returned list to keep them out of user code.
(plist a1)
~~~~~~~~~~
Will return the property list for atom a1. The property list
is of the form ((ke1 . value1)(key2 . value2)...(keyn . valuen)).
Note that plist returns a top level copy of the property list
because remprop destroys this lists top level structure.
(putd a1 l1)
~~~~~~~~~~~~
Identical to "def" except that the parameters a1 and l1 are
evaluated. This allows you to write functions that create
functions and add them to the LISP interpreter.
(putprop a1 s1 a2)
~~~~~~~~~~~~~~~~~~
Adds to the property list of a1 the value s1 associated with
the property indicator a2. It returns the value of a1. For
example: (putprop 'Peter 'AshwoodSmith 'lastname)
(remprop a1 a2)
~~~~~~~~~~~~~~~
Removes the property associated with key a2 from the
property list of atom a1. The top level structure of the property
list is actually destroyed. It returns the old property list
starting at the point where the deletion was made.
(set a1 s1)
~~~~~~~~~~~
Will bind a1 to s1 at current scope level or globally if no
scope exists for a1 yet. Set returns s1.
(setplist a1 l1)
~~~~~~~~~~~~~~~~
Will set the property list of atom a1 to the list l1 where
the list must be ((keyn.valn)..). It returns this new list l1.
(setq *a1* s1 *a2* s2 ..... *an* sn)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows an infinite number of variable and value pairs and it
does not evaluate the variables a1...an. So (setq a 'val1 b
'val2) binds val1 to a and val2 to b. Setq will return sn.
29
FUNCTIONS WITH SIDE EFFECTS OR THAT ARE EFFECTED BY SYSTEM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CONT'D
~~~~~~
(setsyntax a1 a2 l1)
~~~~~~~~~~~~~~~~~~~~
Is a way of defining a read expression macro l1 to be
associated with chracter a1. And invoked in 'vmacro or
'vsplicing-macro mode depending on a2. This function allows you
to alter the way that (read) works. Basically after calling
setsyntax the expression l1 will be invoked whenever the
character a1 is found in the input stream and this character is
not escaped or hidden in a comment or delimiters of some kind.
For example a macro : that pretty prints the following function
name could be defined as follows:
-->(setsyntax '|:| 'vmacro '(lambda()(list 'pp (read))))
Then if I typed :pp at the input prompt the character :
would be read causing the expression (list 'pp (read)) to be
invoked. This would then read the pp atom and construct the list
(pp pp) which would then be passed back to the read function
which would pass it back to the eval loop which will evaluate it
and pretty print the function pp. Read macro expressions are
lambda expressions that take no parameters. Any calls to (read)
must not have any arguments, (read) will know where to read the
next expression from because of a global binding performed by the
read macro driver on behalf of the read function.
Splicing macros are also available. Just replace the 'vmacro
parameter with 'vsplicing-macro. What will happen is that the
returned list will be spliced into the input expression, rather
than forming a sublist expression in the current input. This is
useful if you want to define your own comment delimiters and
return nil. For example let's define a new comment delimiter say
the < and > characters.
-->(defun SkipToEnd()
(cond ((eq (readc) '|>|) nil)
(t (SkipToEnd))))
SkipToEnd
-->(setsyntax '|<| 'vsplicing-macro '(lambda()(SkipToEnd)))
t
-->(and t <junk junk junk> t)
t
What I have done is first write a comment skipping function
that just reads input character by character until the > is
found. I then associate the character '<' with a lambda
expression that calls this skipper. The macro is a splicing macro
as the last (and t <junk...junk> t) demonstrates. Think about
what would have happened if the macro were non splicing and I put
a comment in the (and ....) list. Try it and see, then you will
know why splicing macros are needed.
30
FUNCTIONS WITH SIDE EFFECTS OR THAT ARE EFFECTED BY SYSTEM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CONT'D
~~~~~~
(trace [*a1* *a2* *a3* ..... *an*])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Will turn on tracing of the user defined functions a1...an.
Note that you cannot trace built in functions. If you call trace
with no parameters it will return a list of all user defined
functions that have been set for tracing by a previous call to
trace, otherwise trace returns exactly the list (a1 a2...an)
after enabling tracing of each of these user defined functions.
If any of the atoms is not a user defined function trace stops
and returns an error. All atoms up to the point of error will be
traced.
(untrace [*a1* *a2* *a3* ..... *an*])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Will disable tracing of the listed functions which must all
be user defined. If no parameters are given it disables tracing
of all functions. Untrace returns a list of all functions whose
tracing has been disabled. Here is a demonstration of how you can
use them. The --> is the LISP prompt. This is the sort of
sequence that you should see on the console. The comments ;...
were added to tell you what is going on.
-->(defun factorial(n) ; define n! = n * (n-1)!
(cond ((zerop n) 1)
(t (* n (factorial (1- n]
factorial
-->(trace factorial) ; ask LISP to trace n!
(factorial)
-->(factorial 5) ; ask LISP for 5!
<enter> factorial( 5 ) ; entered with parm=5
<enter> factorial( 4 ) ; " " " 4
<enter> factorial( 3 ) ; " " " 3
<enter> factorial( 2 ) ; " " " 2
<enter> factorial( 1 ) ; " " " 1
<enter> factorial( 0 ) ; " " " 0
<EXIT> factorial 1 ; exit 0! = 1
<EXIT> factorial 1 ; exit 1! = 1
<EXIT> factorial 2 ; exit 2! = 1
<EXIT> factorial 6 ; exit 3! = 6
<EXIT> factorial 24 ; exit 4! = 24
<EXIT> factorial 120 ; exit 5! = 120
120
-->(untrace factorial) ; ask LISP to shut up
(factorial)
-->(factorial 5) ; now it is quiet again.
120
-->
31
FUNCTIONS WITH SIDE EFFECTS OR THAT ARE EFFECTED BY SYSTEM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CONT'D
~~~~~~
(showstack)
~~~~~~~~~~~
This function will display a copy of the last 20 eval
and apply evaluations from the internal stack. The top of the
internal stack is copied whenever LISP is about to enter the
break level (prompt 'er>'). This means that if you execute some
function and it aborts prematurely you can call showstack from
the break level and see exactly what lead to the error. Whenever
a new error occurs the old copy of the top 20 elements on the
internal stack is lost and a new trace is copied for you to
display via (showstack). This is unlike Franz which allows
lots of break levels. For example consider this example session
with PC-LISP which is similar to an example in LISPcraft.
-->(defun foobar(y)(prog(x)(setq x (cons (car 8) y]
foobar
-->(foobar '(a b c))
--- error evaluating built in function [car] ---
er>x
()
er>y
(a b c)
er>(showstack)
[] (car 8)
[] (car 8)
[] (cons <**> y)
[] (setq x <**>)
[] (prog(x) <**>)
[] (foobar '(a b c))
t
In this example I declared a function called 'foobar' which
runs a prog and does a single assignment to x. When I execute it
with parameter '(a b c). PC-LISP correctly tells me that there
was an error evaluating the built in function 'car'. I can
examine the values of x and y and see that x is still set to the
empty list () that the prog call set it to. y is bound to the
parameter passed to foobar as expected. Next I called (showstack)
to see the trace of execution. I see that the top evaluation (car
8) is the culprit. The evaluation previous to that is also (car
8) but this evaluation was before the arguments had been
evaluated. Remember that fixnums eval to themselves. The <**>
symbols in the show stack are just a short hand way of saying
look at the entry above to see what the <**> should be replaced
with. This greatly reduces the amount of information that you
have to look at when you read a stack dump. It also allows you to
follow the stream of partial evaluations by looking at each <**>
in turn. Note that infinite recursion leaves a stream of <**>'s.
32
LIST EVALUATION CONTROL FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These functions are the control flow functions for LISP they
effect which lists are evaluated and how. They operate on the
basic LISP function types, descriptions of which follow.
(lambda l1 s1....sn)
~~~~~~~~~~~~~~~~~~~~
This is not a function but it is a list construct which
can act as a function in any context where a function is legal. A
lambda expression is a function body. The S-expressions s1..sn
are expressions that are evaluated in the order s1...sn. The
result is the evaluation of sn. The atoms in the list l1 are
called bound variables. They will be bound to values that occur
on the right of the lambda expression before the S-expressions
s1..sn are evaluated and unbound after the value of sn is
returned.
(nlambda l1 s1....sn)
~~~~~~~~~~~~~~~~~~~~~
This is a function body construct similar to lambda but with
a few major differences. The first is that the list l1 must only
specify one formal parameter. This will be set to a list of the
UNEVALUATED parameters that fall on the right of the nlambda
expression when it is being evaluated. This function allows you
to write functions with a variable number of parameters and to
control the evaluation of these parameters. For example we can
write a function called 'ADDEM that behaves the same way as '+ in
nearly all contexts as follows:
-->(def ADDEM (nlambda(l)(eval(cons '+ l))))
or
-->(defun ADDEM fexpr(l)(eval(cons '+ l)))
Both of which create the same nlambda expression. This
function will behave as follows when spotted on the left of a
sequence of parameters 1 2 3 4. First it will not evaluate the
sequence of parameters 1 2 3 4. Second it makes these into a list
(1 2 3 4). It then binds 'l to this list and evaluates the
expression (eval(cons( '+ l))). This expression results in (eval
(+ 1 2 3 4)). Which is just the desired result 10.
(label a1 (lambda|nlambda l1 s1..sn)) {not in Franz}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This acts just like a lambda expression except that the body
is temporarily bound to the name a1 for evaluation of the body
s1. This allows recursive calls to the same body. The binding of
the body to the name a1 will be forgotten as soon as the
expression s1 terminates the recursion. For example:
(label LastElement (lambda(List)
(cond ((null (cdr List))(car List))
(t (LastElement (cdr List))))))
33
LIST EVALUATION CONTROL FUNCTIONS CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(lexpr (a1) s1 s2 .... sN)
~~~~~~~~~~~~~~~~~~~~~~~~~~
This function body form is similar to the nlambda form
except that all of its variable number of arguments are evaluated
and the args are accessed in a different manner. The second
element of the lexpr form must be a list of exactly one atom. The
remaining elements of the lexpr form represent bodies that are
evaluated one after the other. The result of evaluating a list
whose first element is an lexpr is just the value that results
from evaluating s1....sN in order in the context where a1 is
bound to the number of actual parameters, and the (arg), (setarg)
and (listify) functions behave as follows:
(arg [n1])
~~~~~~~~~~
When in the context of an lexprs' evaluation, will return
either the number of arguments provided to the nearest enclosing
lexpr, or the nth argument indexed from 1 passed to that lexpr
depending on whether n1 is provided or not as a parameter. An
error occurs if n1 is less than 1 or greater than (arg).
(setarg n1 s1)
~~~~~~~~~~~~~~
When in the context of an lexprs' evaluation, will return
exactly s1. It has the side effect that future calls to (arg n1)
will return the value s1 for the duration of the current
enclosing lexpr evaluation. An error occurs if n1 is less than 1
or greater than (arg).
(listify n1)
~~~~~~~~~~~~
When in the context of an lexprs' evaluation, will return a
tail of the list of arguments that were passed to the nearest
enclosing lexpr. The head of this tail is either (arg n1) if n1
is positive, or (arg (+ (arg) n1 1)) if n is negative. An error
occurs if the value of n1 does not correctly index a head within
the actual argument list of the nearest enclosing lexpr.
Here is a small lexpr example which just sets some global
variables to allow us to see what went on inside. Again see
LISPcraft for a much better description.
-->((lexpr(n)
(setq a0 n a1 (arg 1) an (arg(arg)))
(listify -3)
) 'A 'B 'C 'D 'E 'F 'G )
(E F G)
-->a0
7
-->a1
A
-->an
G
34
LIST EVALUATION CONTROL FUNCTIONS CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(apply s1 l1)
~~~~~~~~~~~~~
The function s1 is evaluated in the context resulting from
binding its formal parameters to the values in l1. The result of
this evaluation is returned. Example:
-->(apply '(lambda(x y z)(* (+ x y) z)) '(2 3 4))
20
(cond l1 l2 ... ln)
~~~~~~~~~~~~~~~~~~~
The lists l1 ... ln are checked on by one. They are of the
form (s1 s2 .. sn). Cond evaluates the s1's one by one until it
finds one that does not eval to nil. It then evaluates the s2..sn
expressions one by one and returns the result of evaluating sn.
If all of the s1's (called guards) evaluate to nil, it returns
'nil. For example:
-->(cond ((equal '(a b c) (cdr '(x a b c))) 'yes)
(t 'opps))
yes
(eval s1)
~~~~~~~~~
Runs the LISP interpreter on the S-expression s1. It just
removes a quote from the expression s1. For example:
-->(eval '(+ 2 4))
6
(mapcar s1 l1 l2 l3 .... ln)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This function will map the function s1 onto the parameter
list made by taking the car of each of l1...ln. It forms a list
of the results of the repeated application of s1 to the next
elements in the lists l1...ln. It stops when the list l1 runs out
of elements. Note that each of l1...ln should have the same
number of elements, although this condition is not checked for
and nil will be substituted if a list runs out of elements before
the others. Extra elements in any list are ignored. For example:
-->(mapcar '< '(10 20 30) '(11 19 30))
(t nil nil)
Which returns the results of (< 10 11) (< 20 19) and (< 30
30) as the list (t nil nil). Note that s1 could be any built in
function, user defined function or lambda expression. For
example:
-->(mapcar 'putprop '(John Fred Bill)
'(Mary Sue Linda)
'(mother sister daughter))
(Mary Sue Linda)
35
LIST EVALUATION CONTROL FUNCTIONS CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(defun a1 macro l1 s1 s2 ... sn)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Macro is a special body, similar to nlambda except that it
may causes code replacement when it is evaluated. When a macro is
encountered the list (name arg1 arg2...) is bound to the
macro parameter. Name is the name of the macro and arg1..argn are
the arguments that were provided to it. Then the bodies of the
macro are evaluated and the expression returned by the last body
is returned. Then depending on the value of displace-macros and
the type of the returned S-expression, the returned S-expression
may destructively replace the peice of code that called it. If
the value of displace-macros is nil (its default value) or the
type of the returned S-expression is not one that can be
replaced, no destructive substitution will occur. Next regardless
of whether the S-expression was substituted or not, the S-
expression is evaluated and the value returned. This all sounds
pretty compex, but in fact it is quite simple, here is an
example:
-->(defun first-elemet macro(l)(cons 'car (cdr l)))
first-element
-->(setq x '(first-element '(a b c)))
(first-element '(a b c))
-->(eval x)
a
-->x
(first-element '(a b c))
-->(setq displace-macros t)
t
-->(eval x)
a
-->x
(car '(a b c))
-->(eval x)
a
In the example above I have first declared a macro called
'first-element' which when run given a list parameter should
return the first element in the list. I could have done this
using a lambda expression but this would require parameter
binding etc every time I execute 'first-element'. Rather, what I
have chosen to do is to cause (first-element x) to be replaced by
the code (car x) everywhere it is encountered. Then future
execution of (first-element x) is just as costly as an execution
of (car x). Let's examine what I did above. First I declared a
macro which will take the parameter (first-element -stuff-) and
construct the code (car -stuff-). I then set x to be an
expression which when evaluated should give 'a. I then verify
this by evaluating x, sure enough it is 'a. I then look at the
code for x which has not changed. Now, I set the global variable
displace-macros to be non nil. What I should now expect is that
(eval x) will give the same answer, but with the side effect of
doing the code substitution so that future passes of the
36
LIST EVALUATION CONTROL FUNCTIONS CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
expression bound to x will run much faster. This is the whole
reason for macros, they are not much use if they are expanded
every time, it is more work than a simple user defined lambda
expression call. Anyway after running x and looking at its
definition we can see that the code has indeed been substituted.
It is worth noting that unless you set displace-macros to be non
nil all your macros will be expanded every time they are
encountered. This is probably not what you want. You should set
displace-macros to be t to cause macros to behave properly. The
only reason I did not set displace-macros to be t by default is
that Franz does not.
Note, macros may return any type expression however some
expressions may not result in code substitution because of
internal problems with doing the substitution. In particular a
macro that directly returns an atom, hunk or string will never
result in code replacement, while a macro that returns a list,
fixnum, flonum or port can result in code replacement. Since code
replacement is a physical copying of one cell over another heap
space owning functions cannot be physically substituted because
their cells are unique. You should note however that these
limitations do not occur much in practice since usually a macro
will return a number or a list. For exampe a quoted atom is ok
because it is really the list (quote x). In any case PC-LISP
macros will always return the correct values regardless of
these substitution limitations.
Macro bodies can function in all contexts that an nlambda
body can function, however expansion, if it is to occur will only
happen when a macro is referred to by its atom name which was
defined by a defun, def or putd call. Using macros in this manner
does not seem to have any real use though.
(macroexpand s1)
~~~~~~~~~~~~~~~~
This function lets you see what the macro expansion of s1
looks like prior to evaluation and substitution. This function is
necessary to help debug macro definitions because otherwise the
intermediate code is only visible on the showstack and the code
may not be on the showstack when the error occurs. For example:
-->(macroexpand '(first-element '(a b c)))
(car '(a b c))
37
LIST EVALUATION CONTROL FUNCTIONS CONT'D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(prog l1 s1.....sn)
~~~~~~~~~~~~~~~~~~~
Prog is a way of escaping the pure LISP applicative
programming environment. It allows you to evaluate a sequence of
S-expressions one after the other in true imperative style. It
allows you to use the functions (go..) and (return ..) to perform
the goto and return functions that imperative languages permit.
Prog operates as follows: The list l1 which is a list of atom
names is scanned and each atom is bound to nil at this scope
level. Next the S-expressions s1..sn are scanned once. If any of
s1..sn are atoms they are bound to the S-expression that follows
them. Next we start evaluating lists s1...sn ignoring the atoms
which are assumed to be labels. If after evaluation an S-
expression is of the form ($[|return|]$ Z) we unbind all the
atoms and labels and return the S-expression Z. If after
evaluation a list is of the form ($[|go|]$ Z) we alter our
evaluation to start next at Z. The functions (go) and (return)
will return the above mentioned special forms. If at any time we
reach sn, and it is not a go or a return, we simply unbind all of
l1 and the labels in s1...sn and return the result of evaluating
sn. Note that prog labels must be alpha or literal alpha atoms.
Also note that the (return) and (go) mechanisms are not the same
as Franz and will only operate if the special form works its way
back to the prog. Because of this you are advised to keep the
calls to go and return within the lexical scope of the prog body
and to insure that the special form returned is not absorbed by
some higher level function.
For example:
-->(prog (List SumOfAtoms)
(setq List (hashtabstat))
(setq SumOfAtoms 0)
LOOP (cond ((null List) (return SumOfAtoms)))
(setq SumOfAtoms (+ (car List) SumOfAtoms))
(setq List (cdr List))
(go LOOP)
)
306
This peice of code operates as follows. First it creates two
local variables. Next it binds the variable List to the list of
hash bucket totals from the alpha hash table. It then sets a sum
counter to 0. Next it checks the List variable to see if it is
nil. If so it returns the Sum Of all the Atoms. Otherwise it adds
the first fixnum in the list List to the running SumOfAtoms,
winds in the list List by one, and jumps to LOOP. Note also that
we can accomplish the same thing as the above prog with the much
simpler example which follows:
-->(eval (cons '+ (hashtabstat)))
306
38
HUNKS
~~~~~
A hunk is just an array of 1 to 126 elements. The elements
may be any other type including hunks. With hunks it is possible
to create self referencial structures (see DANGEROUS FUNCTIONS).
A Hunks element storage space comes from the heap. Hunks like
strings and alpha print names are subject to compaction
relocation and reclaimation.
(hunk s1 s2 .... sN)
~~~~~~~~~~~~~~~~~~~~
Returns a newly created hunk of size N whose elements are
s1, s2 ... sN in that order. N must be in the range 1 to 126
inclusive. Note that a hunk is printed like a list but
surrounded by { }. Ie {s1 s2 ...sN}.
(cxr n1 H)
~~~~~~~~~~
Returns the n1'th element of hunk H indexed from 0. Hence n1
must be in the range 0 .. (hunksize H)-1.
(hunkp s1)
~~~~~~~~~~
Returns true if s1 is of type hunk, otherwise it returns
nil. Note this function has also been mentioned with the other
predicates.
(hunksize H)
~~~~~~~~~~~~
Returns a fixnum whose value is the size of the hunk. This
value is one larger than the largest index allowed into the hunk
by both cxr and rplacx. The size of a hunk is fixed at the time
of its creation and can never change throughout its life.
(makhunk n1) or (makhunk (s1 s2 ...sN))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The first form returns a nil filled hunk of n1 elements.
Needless to say, n1 must be between 1 and 126 inclusive. The
second form is just identical to (hunk s1.....sN).
(rplacx n1 H s1)
~~~~~~~~~~~~~~~~
Returns the hunk H, however as a side effect element n1 of H
has been made (eq) to s1. In other words H[n1] = s1. Note that
this function like rplaca and rplacd allows you to create self
referencial structures.
39
DANGEROUS FUNCTIONS
~~~~~~~~~~~~~~~~~~~
The following two functions have potentially disasterous
results if used by unwary or inexperienced LISP programmers. The
third function is provided to make their use less dangerous.
(rplaca l1 s1)
~~~~~~~~~~~~~~
The cons cell l1 is physically altered so that its car is
(eq) to s1. That is the car pointer of l1 is set to point to s1.
The list l1 is returned. (l1 must not be nil).
(rplacd l1 s1)
~~~~~~~~~~~~~~
The cons cell l1 is physically altered so that its cdr is
(eq) to s1. That is the cdr pointer of l1 is set to point to s1.
The list l1 is returned. (l1 must not be nil).
(copy s1)
~~~~~~~~~
Returns a structure (equal) to s1 but made with new cons
cells. Note that only cons cells are copied, strings, atoms,
hunks etc are not copied.
Warning #1 - altering a cons cell allows you to create
structures that point (refer) to themselves. While this does not
cause a problem for the LISP interpreter or garbage collector it
does mean that many built in functions will either loop around
the structure infinitely or recurse until a stack overflows.
-->(setq x '(a b c d))
(a b c d)
-->(rplaca x x)
((((((((((((((((((((((((((((((((((((((((...............
-- stack overflow --
er>
Warning #2 - altering a cons cell can cause a million little
side effects that you did not count on. Consider carefully the
following example.
-->(defun FooBar(x) (cons x '(b c)))
FooBar
-->(setq z (FooBar 'a))
(a b c)
-->(rplaca (cdr z) 'GOTCHA!)
(GOTCAH! c)
-->(FooBar 'a)
(a GOTCHA! c)
What happened? The rplaca has modified the list that is a
constant in FooBar. Lists are not copied unless necessary and
building the list (a b c) did not require a copy of the constant
list (b c) to be made. Ie (cdr z) is eq to (b c) in FooBar.
40
MSDOS BIOS CALLS FOR GRAPHICS OUTPUT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These functions are still experimental. They do however
allow you to play with drawing recursive curves etc. They
all result in an INT 10H. This means that the graphics
should be portable to most MSDOS machines and should run under
any windowing environment like Topview or MSwindows. This is why
they are so slow. Note that they all return 't. They do not check
to see if the INT call was successful or if you have a graphics
capability. You can crash your system if you abuse these
functions.
(#scrline# n1 n2 n3 n4 n5)
~~~~~~~~~~~~~~~~~~~~~~~~~~
Draws a line on the screen connecting (n1,n2) with the point
(n3,n4) using attribute n5. This function calls the BIOS set dot
function for each point. Hence it is not very fast.
(#scrmde# n1) {ah=0, al=n1, INT 10H}
~~~~~~~~~~~~~
Sets the video mode to n1. Modes are positive numbers 0.....
Where (8 and 9) are high resolution for the Tandy2000 and I
suppose are high resolution modes on other machines that support
the (640 x 400) or greater graphics resolutions. These are all
listed in your hardware reference manual but basically they are:
0 = 40x25B&W, 1=40x25COL, 2=80x25B&W 3=80x25COL, 4 =320x200COL,
5=320x200B&W, 6=640x200B&W, 7=reserved, 8=640x400COL,
9=640x400B&W etc...? This is as of DOS 2.11. Also note that the
AT EGA Graphics Modes should also work with no problem however,
resolutions greater than 1024x1024 will cause the (#scrline#)
function to cease to work correctly.
(#scrsap# n1) {ah=5, al=n1, INT 10H}
~~~~~~~~~~~~~
Sets the active video page to n1. n1 should be between 0 and
8. This is valid for text modes only. Versions of MSDOS other
than 2.11 may not support this call.
(#scrspt# bh bl al) {ah=11,bh=bh,bl=bl,al=al,INT 10H}
~~~~~~~~~~~~~~~~~~~
Sets the color palette according to the value in bh. For
most BIOS compatable machines these are: If bh=0 it sets
background color bl. If bh=1 it sets the default palette to the
number 0 or 1 in BL. If bh=2 it sets a single palette entry where
bl is the palette entry number and al is the color value. See
your BIOS reference for the color values and additional info.
41
MSDOS BIOS CALLS FOR GRAPHICS OUTPUT (CONT'D)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(#scrscp# n1 n2 n3) {ah=2,bh=n1,dh=n2,dl=n3,INT 10H}
~~~~~~~~~~~~~~~~~~~
Sets the cursor position to be in page n1 at row n2 and in
column n3. Where 0 is the top row and 0 is leftmost col.
(#scrsct# n1 n2) {ah=1,ch=n1,cl=n2,INT 10H}
~~~~~~~~~~~~~~~~
Sets the cursor type to agree with the following: n1 bit
5 (0 = blink 1 = steady), bit 6 (0 = visible, 1 = invisible),
bits 4-0 = start line for cursor within character cell. n2 bits
4-0 = end line for cursor within character cell.
(#scrwdot# n1 n2 n3) {ah=12,cx=n1,dx=n2,al=n3,INT 10H}
~~~~~~~~~~~~~~~~~~~~
Write a dot (pixel). The pixel at row n1 and column n2 has
its color value XORed with the color attribute n3. Since the
color attributes vary from machine to machine you will have to
look up the correct values for this parameter in your BIOS guide.
42
MSDOS BIOS CALLS FOR DATE AND TIME
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rather than try to implement the (sys:time) function or the
(status localtime) call in PC-LISP I have provided access to the
MS-DOS get date and get time BIOS calls. These are INT 21H
function numbers 2A and 2C hex respectively. Here is how you get
at them from PC-LISP.
(#date#)
~~~~~~~~
Returns a list of four fixnums. The first element in this
list represents the year 1980 means 1980 etc. The second element
is the month of the year where 1 means January etc. The next
element in the list represents the day of the month where 1 means
the first day, etc. The last element in the list represents the
day of the week where 0 means Sunday etc.
(#time#)
~~~~~~~~
Returns a list of four fixnums. The first element in this
list represents the hour of the day where 1 means 1AM and 24
means 12 PM. The next element represents the minutes these are 0
through 59. The next element represents the seconds, these
represent hundredths of a second, 0-99.
For example:
-->(append (#date#) (#time#))
(1986 4 6 0 20 23 22 15)
Means that the date is Sunday April 6th, 1986 and the local
time is 8:23:22 and 15/100 of a second.
43
MEMORY EXHAUSTION
~~~~~~~~~~~~~~~~~
The memory is all used up when you get a message such as
"LISP cons cells exhausted". Usually when this happens it is
because you are tying up memory somewhere but do not realize it.
The most common way to tie up memory is to execute an infinite
recursion such as (defun looper(n)(looper (+ n 1))). The stack
will of course overflow and YOUR BINDINGS WILL BE HELD FOR YOU!!
This means that ALL bindings are held. If you execute the above
program several times from the break level, 'er>', you will
eventually run out of CONS cells. They are all in use to hold the
values n, n+1, n+2,...... to the point of the first stack
overflow. Then n, n+1,.... to the point of the second overflow
and so on and so on. Eventually there is no more space left to
evaluate the function (looper). The solution is simple: If you
run an infinite recursion by mistake and are placed in the break
level, use the showstack to figure out where you are. Then use
the break level to examine variables etc. But before retrying
anything return to the top level. This will cause the held
bindings to be dropped and the cells will become reclaimable
garbage (ie free). Consider the following session with PC-LISP
V2.11:
-->(defun looper(n)(looper (+ n 1))) ; infinite function
looper
-->(looper 0) ; run it from 0
-- Stack Overflow -- ; all n's saved!
er>n ; last value of n
588
er>(looper 0) ; another run will
-- Stack Overflow -- ; save more n's
er>(looper 0)
-- Stack Overflow --
er>(looper 0) ; another run will
LISP out of cons cells! ; save more n's
B>
Note that in last (looper 0) call we made from the break
level was unable to complete because we ran out of memory. When
this happens PC-LISP gives up and returns to DOS, hence the B>
prompt. We could have avoided this problem if we had entered a
CONTROL-Z ENTER sequence at the 'er>' prompt before any further
calls to (looper 0) were made. This would have freed up all the
held intermediate bindings of n.
If you find that you are running out of heap space it may be
because you are keeping too many unused strings,symbols or hunks.
The easiest way to do this by mistake is the following: (setq x
(oblist)). The variable x is globally set to the oblist contents.
Now, all objects that were in the oblist at the time of the call
will never be freed. The heap space associated with their print
names will also be unreclaimable. The solution is to be careful
what you do with copies of the oblist if heap space is in demand.
Usually heap space is not critical and you need not worry.
44
TECHNICAL INFORMATION
~~~~~~~~~~~~~~~~~~~~~
The interpreter is written using the Lattice C compiler
version 2.03. It consists of 8 separate modules totaling nearly
10,000 lines of C. The modules are: A scanner, parser, memory
manager, list evaluator and critical functions module, two built
in functions modules, a library of extra Unix libc functions not
provided by Lattice C consisting of assembly language routines
for setjmp(), longjmp() and getenv(), and finally a modified C
start up assembly language module to provide signal trapping for
stack overflow and control-break. The program is designed to
compile without changing more than 2 #defines under UNIX 4.2 and
sys V. This has been done as has porting to a SUN.
Memory is organized as follows. Alpha cells have fields for
a shallow stack of bindings, a pointer to heap space for the
print names, a pointer to any built in or user defined functions,
and a pointer to any property lists. Alpha cells are the largest
of all the cells and have their own fixed storage area. Heap
space which is just the space used for the print names of the
alpha cells and strings, and the element array for hunks may be
variable sized blocks of up to 254 bytes long. This is why a hunk
can have only 126 elements in PC-LISP. The rest of the cells used
by PC-LISP are all considered as one. This consists of the
flonum, fixnum, list, string, hunk and port cells. They have
their own contiguous slice of memory. This means that three
different contiguous types of memory are required. It is managed
in the following way. At start up time the percentages of memory
are read from the default settings or the environment variables
LISP%HEAP and LISP%ALPH. Next memory is allocated in 16K chunks
these are the largest contiguous pieces handled by the memory
manager. If the environment variable LISP%MEM has an integer
value, this is used as the upper limit on the number of 16K
chunks to allocate. These are all kept track of in a large vector
of pointers. After all chunks have been allocated 8K are given
back for use by the I/O functions. If the environment variable
LISP%KEEP is set to an integer value that many bytes are given
back instead of 8K. If file I/O seems to stop working it is
probably because the standard I/O functions have run out of
memory, in this case either set LISP%KEEP a bit bigger, or set
LISP%MEM to a value that does not cause all free memory to be
allocated. Next groups of these blocks are primed for use by
alpha,cell, or heap managers. These managers handle the
distribution and reclamation of memory in their block. The heap
manager will perform compaction and relocation to get free space.
The alpha and cell managers will perform mark and gather garbage
collection to get space. The heap manager may request mark and
gather collection if there is a real shortage of heap space.
Stack overflow detection is done by intercepting the call to
the Lattice C stack overflow routine, temporarily resetting the
stack, and them making a call to my own C stack overflow routine.
This then longjmps out of the error condition. The Unix version
handles the error in the same way except that the overflow
results in a SIGSEGV which then calls the same routine.
45
TECHNICAL INFORMATION (CONT'D)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Control-BREAK detection is done via periodic testing of the
status in the evaluator main loop, and the read main loop. When a
break is detected control is transferred to the break handler
which prints a message and longjmps back to the mainline code.
The Unix version will have made a signal call asking that the
break handler be executed when a user break key is hit. Hence the
results are the same. CONTROL-C checking is done in the same way
except that a CONTROL-C will only be spotted on I/O so a looping
non printing function can only be stopped with CONTROL-BREAK.
Note that CONTROL-BREAK is INT 1BH and CONTROL-C is INT 23H.
If your machine does not support int 1BH, you can easily
patch PC-LISP to trap whatever vector you want. To do this just
start disassembling PC-LISP with DEBUG. The procedures that set
and reset the int 1BH vector are pretty near the start of the
program and are very easy to spot. Note that there are a couple
of other set/reset interrupt vector routines here so do not get
the wrong one. Look for calls to the MS-DOS set interrupt vector
routine. If you have trouble doing this drop me a line and I will
try to help you get it done. There should not be many machines
for which this patch is necessary because most MS-DOS machines,
even partially PC compatible, seem to generate an interrupt 1BH
when CONTROL BREAK is hit.
46
KNOWN BUGS OR LACKING FEATURES OF V2.11
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-It is possible to run out of stack space while garbage
collecting. When this happens the garbage collection is retried
once but the error is unrecoverable. You should treat this as a
stack overflow caused by your program. This can be fixed with a
link inversion marking phase in the next release of PC-LISP. See
also the section MEMORY EXHAUSTION for more details on this
problem. Note that if the stack overflows on the second garbage
collection retry it gives up and advises you of a probable memory
corruption.
-Line drawing is not too quick, or too clean. The lines take
time to draw because they go through the BIOS, they are not very
clean at certain slopes due to some bugs. But the video graphics
routines are still experimental so do not rely on them too much.
You will also note that several other video INT calls are
missing.
-If too many (load 'file) calls fail you will run out of
available ports. This is because they are left open. PC-LISP does
not close open load ports if an error occurs while reading from
them.
-Two special atoms with rather obscure names should never be
directly returned manipulated in a prog. These are $[|return|]$
and $[|go|]$. If you attempt say print these from within a prog,
the print function will return them and this will confuse the
heck out of prog which uses them for internal purposes. Because
of this the (oblist) call does not return them. Thus the only way
they can get into your code is for you to enter them directly.
Since this is unlikely and I have warned you the problem should
not occur.
-You are not prevented from altering the binding of t. This
means that if you use t as a parameter or set/setq it to
something other than t you may cause some strange behaviour,
especially if you bind t to nil by accident.
-Macros are slightly restricted in that only lists, fixnums
, flonums or ports can be substituted. This is a small difference
from Franz but one that would require significant performance
penalties to implement. Since not substituting these types is
less expensive than implementing substitution would be, I will
not implement this feature of Franz in a PC environment.
-Explode and Exploden only work on atoms or strings. In Franz
you can explode anything. For PC-LISP I decided to leave out this
feature because it complicates the print functions which are
already pretty messy.
47
KNOWN BUGS OR LACKING FEATURES OF V2.11 (CONT'D)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-It is possible for the I/O functions to stop working if they
run out of memory. Since they get their memory separately from
the other functions in PC-LISP the only solution is to run PC-
LISP with a little less memory either by setting the environment
variable LISP%MEM to a value that leaves one or more 16K blocks
free, or to set LISP%KEEP a little larger than 8K so that more
memory is free for use by the I/O functions.
-The interpreter is slow. I am planning on introducing a
compiler which should speed things up significantly. I will also
rewrite a very small portion of the interpreter in assember which
should double or tripple the speed.
-Car and cdr will not access the first and second element of
a hunk as they do in Franz.
-Showstack does not print lists in compressed form
horizontally. The vertical compression <**> is however done. It
also occasionally gets confused and does not print the last
evaluation this sometimes happens on macro expansion. Showstack
may also get confused and print a list one element at a time
rather than as a complete list. This is because showstack is
trying to trace backwards through an internal stack which has a
lot of intermediate stuff on it and can get confused by the
extra stacked info.
RE BUGS OR DESIRED ENHANCMENTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I have tried to think of everything that a user could do to
crash the system and protect him/her from it but I'm sure my
imagination has only covered half of the possibilities. If you
find any other bugs or if you think some features would be nice
to add to PC-LISP, I will consider them for the next major
release. Please don't hesitate to let me know what you think,
good or bad. I'd appreciate the feed back as I have put a lot of
work into this program and want to know what you people out there
think of it.
Regards,
Peter Ashwood-Smith.
48