petera@utcsri.UUCP (Smith) (02/20/86)
Here is the manual for PC-LISP cut it at the line: ------------------------- CUT HERE --------------------------- A GUIDE TO THE PC-LISP INTERPRETER (V2.07) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By Peter Ashwood-Smith ~~~~~~~~~~~~~~~~~~~~~~ University of Toronto, ~~~~~~~~~~~~~~~~~~~~~ Ontario, Canada. ~~~~~~~~~~~~~~~ With thanks to Brian Robertson for the math functions. Feb 01/86. 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 reasonable subset of Franz LISP. It is not 100% compatible with Franz but the functions have the same names and do the same things as Franz when possible. This is version 2.07 and is the result of about 1.5 years of work. Version 2.07 has these features: - types float, alpha, list, and port, function bodies lambda, nlambda and macro. - full garbage collection of all types. - compacting, relocating heap space for all strings. - shallow binding techniques for O(1) symbol value lookup. (Dynamic scoping). - access to some MSDOS BIOS graphics routines. - sufficient built in functions to allow implementation in LISP of most Franz or other functions. Includes prog,eval,apply,defun and def. - 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. - Requires a bare minimum of 256K but the garbage collection frequency is most bearable with 512K+. This version is ShareWare. The idea is that you can make as many copies as you like and distribute them to anyone or any BBS you please, the more the better. My only requests are that you not use it for profit of any kind, or remove the name(s) of myself or any contributing authors from the program header or from this manual. If you decide that you like the program then send us 15$ to contribute to future development costs. Future development will consist of adding more types including strings, arrays and the functions that operate on them. If I have time and there is enough interest I may also add a compiler. The program is written 98% in C using the Lattice C 2.03 compiler with some extra assembly language routines to make up for the otherwise excellent Lattice compiler and libraries. The math library was written for me by Brian Robertson also of the University of Toronto. 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 so I am not endorsing it. It just happens to be the book that I use. EXAMPLE FUNCTIONS INCLUDED IN EXTFUNC.L ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For those of you that are like me and hate to read a manual you can take a look in the file EXTFUNC.L which should have come with the interpreter. This file contains lots of function definitions to help fill the gap between PC and Franz LISP. It also contains a small Turtle Graphics demonstration. If you want to start learning LISP or try out the demonstration you should start PC-LISP by typing "PC-LISP EXTFUNC.L" from MS-DOS. Then wait a bit until you see the "-->" prompt. Be patient if you have 512K or more memory because it takes a few seconds for PC-LISP to organize your memory. If you only have a 256K machine, now is the time to start thinking about an extra 256K. PC-LISP will be much faster not having to garbage collect all the time. Anyway you now have the prompt "-->" and EXTFUNC.L is loaded. To run the graphics demo you just type "(GraphicsDemo)" without the quotes, followed by ENTER. You must have a graphics adapter for this demo to run. Don't even try it if your machine has no graphics capability. (Graphics are slow but thats the price of portability across MS-DOS machines). Note that if you want to start learning LISP from a book such as LISPcraft, you should also load "EXTFUNC.L" as functions that are referenced in the first couple of chapters are defined in this file. The functions defined in EXTFUNC.L are not very effecient so you may want to replace them with your own definitions. 3 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. These will be allocated to the three basic data types in percentages that you can specify via 2 environment variables. The default is that 25% of the memory will be allocated for alpha atoms. 15% will be allocated for heap space, and the rest for cons,port, and float cells. If you set the environment variables LISP%HEAP and LISP%ALPH to an integer between 1 and 90 these will become the new percentages for the heap and alpha respectively, the rest going to cons, port, and float 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. PC-LISP will then print the banner message, the total memory available and the percentages that are allocated to each object. Next it will begin to read the parameters from the command line. The command usage is: * PC-LISP [=n..n] [ [+s][-s] 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. The Lattice C compiler does not generate code that will handle stacks larger than 64K. The +s and -s options turn on and off the printing of statistical information while the file 'file' is being read and evaluated. Statistical information is just a line telling what percentage of cell, alpha and heap space you have used. Garbage collection occurs at 100% use for a given object. The statistical info is useful to see just how efficient/inefficient your functions are. You can get the same information in list form by calling the PC-LISP function (memstat) where the three numbers returned correspond to the percentage of cell, alpha and heap space used so far. The files on the command line are processed one by one. This consists of reading each list in the file and evaluating it. The results are not printed on 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). 4 SYNTAX ~~~~~~ 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. Where an S-expression is an atom, or a list and: An atom may be one of three kinds. It may be an alpha atom , a real number atom, or a literal alpha atom. An alpha atom is just a letter followed by letters/digits and certain special symbols. There may be no more than 254 characters in the alpha atom. To allow you to enter any text as an atom you may delimit the atom with |'s. These will define a literal alpha atom in which you may place any character between the delimiters (except | itself). A real atom is just as you might think an optional plus or minus sign followed by a sequence of digits, followed optionally by a radix point and more digits. Sorry, exponential notation is not supported. It should get into the next version. A list is just a left ( followed by a of sequence of atoms or lists followed by a right ). A list may also be a sequence of atoms or lists followed by a '.' followed by an atom followed by a right parenthisis ). This is called a dotted pair and it means that the CAR and CDR of a list are both atoms. Note that a space on either side of the dot is essential syntactically. You may optionally place [ and ] in the list to represent meta- parenthesis. Basically the ] just closes all open lists up to the nearest [, or to the beginning of the list if no [ is present. Here are some example legal lists. (now is (the . time)) ; dotted pair (the . time) (1 now16 (is (the (time ] ; the ] closes all 4 ('s (car [quote(a b c d]) ; the ] closes 2 ('s to ] (ThisIsBiggerAtom012345678) ; Upper case is ok () ; empty list is equiv to 'nil (1 (-2.2 +3.333)) ; some numbers all floats! ((((((|all one atom|] ; "all one atom" spaces too! Note that you are allowed to mix any number of spaces, line- feeds, carriage returns, form feeds, tabs etc. into your input. Comments may start at any point in a line and will continue until the end of the line as shown in the above example lists. READ MACRO QUOTE ~~~~~~~~~~~~~~~~ PC-LISP supplies one read macro called 'quote' and written using the little ' symbol. (User read macros in later versions) This read macro is just a short hand way of writing the list (quote XX). Where XX is what follows the '. Here are some examples of what the read macro will do to your input before passing it to the evaluator. 'apples -- goes to --> (quote apples) '|too late| (quote |too late|) '(1 2 3) (quote (1 2 3)) 5 SYNTAX ERRORS ~~~~~~~~~~~~~ When you enter a list which is not correctly nested the interpreter will return the wonderfully informative 'syntax error' message. 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. EVALUATING S-EXPRESSIONS ~~~~~~~~~~~~~~~~~~~~~~~~ An S-expression may be an atom or a list. If it is an atom the evaluation of it is its current binding. Most atoms are not bound to begin with so just entering an atom will result in the error 'unbound atom'. Float atoms are bound to their actual values so when you enter 2 you will get 2 back again. Evaluating a list consists of calling the function named or given by the first element in the list with parameters given by the rest of the list. For example there is a function called '+' which takes any number of float values and returns their sum. So: -->(+ 2 4 6 8) Would return the result of 2+4+6+8 ie 20. We can also compose these function calls by using list nesting. For example we can subtract 2+4 from 6+8 as follows: -->(- (+ 6 8) (+ 2 4)) We can also perform operations on other types of atoms. Suppose that we wanted to reverse the list (now is the time). There is a built in function called 'reverse' that does just what we want. So we could try typing. -->(reverse (now is the time)) But the interpreter will be confused! It does not know that 'now' is data and not a function taking arguments 'is', 'the' and 'time'. We must use the function 'quote' which returns its arguments unevaluated, hence its name "quote". -->(reverse (quote (now is the time))) Will give us the desired result (time the is now). But we can do the same thing without using the (quote) function directly. Remember the read macro ' above? Well it will replace the entry '(now is the time) with (quote (now is the time)). Hence we type: -->(reverse '(now is the time)) 6 EVALUATING S-EXPRESSIONS CONT'D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This gives us the correct result without as much typing. You will note that the subtraction of 2+4 from 6+8 could also have been entered as: -->(- (+ '6 '8) (+ '2 '4)) However, the extra 's are redundant because a float 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. So a legal input to the interpreter could be: -->((lambda(x)(+ x 10)) 14) Which would be processed as follows. First the parameters to the lambda expression are evaluated. That 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. So I can evaluate the following expression. -->((lambda(f x)(f (car x))) '(lambda(l)(car l)) '((hi))) 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 lambda expressions with multiple bodies, nlambda expressions with multiple bodies and labeled lambda and nlambda expressions. See the Built In Functions Section which follows for more details on lambda and nlambda. A slightly restricted macro form is also permitted. For information on macros see the MACRO section of the manual. 7 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. It is worth noting that when an error occurs in 'eval' or 'apply' it usually means that a parameter is being passed incorrectly to a user defined function. Since the error message will refer to a built in function when it tells you about an error you will have to figure out which one of your functions made that call. 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. 8 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. - f1...fN are float atom parameters. - l1...lN are list expression parameters. - p1...pN are port atom parameters. - s1...sN are S-expressions (any atom type or list) - {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". - [a1], [f1], [l1], [p1], [s1], etc... are optional parameters of the indicated type. A default will be provided if they are not present in the parameter list for the indicated function. - *a1*, *f1*, *l1*, *p1* or *s1* this means that the arguments of the function are NOT evaluated prior to the function call hence it is not necessary to quote them. 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 which requires additional code to function. The functions are placed into 7 categories. These are as follows: The math functions, the boolean functions, list and atom creators and selectors, file I/O functions, functions with side effects or that are effected by state of system, list evaluation control functions and the MSDOS bios calls for graphics output. Note that the BIOS calls are still experimental. Specifically the line drawing algorithm does not draw lines as smooth or as fast as I would like. This should be fixed by the next release. 9 THE MATH FUNCTIONS ~~~~~~~~~~~~~~~~~~ The functions manipulate floats. They are mostly obvious. Note that these function work on floats while the Franz functions work on -numbers-. Anywhere in PC-LISP where the parameter is giving position or ordinal information the nearest integer to the float parameter is taken as the correct value. (abs f1) ----> absolute value of f1 is returned. (acos f1) ----> arc cosine of f1 is returned. (asin f1) ----> arc sine of f1 is returned. (atan f1 f2) ----> arc tangent of ( f1 divided by f2 ). (cos f1) ----> cosine of f1, f1 is radians (exp f1) ----> returns e to the power f1. (expt f1 f2) ----> returns f1**f2 using: exp(f2*log(f1)) (fact f1) ----> returns factorial of nearest int to f1. (log f1) ----> natural logarithm of f1. (log10 f1) ----> base 10 logarithm of f1. (max f1..fn) ----> largest of f1...fn. (min f1..fn) ----> smallest of f1...fn. (mod f1 f2) ----> r1 modulo r2. (random [f1])----> random float in 0..32K or 0..f1 (sin f1) ----> sine of f1, f1 is radians. (sqrt f1) ----> square root of f1. (* f1 .. fn) ----> f1*f2*f3*.....fn (or 1 if n = 0) (+ f1 .. fn) ----> f1+f2+f3+.....fn (or 0 if n = 0) (- f1 .. fn) ----> f1-f2-f3-.....fn (or 0 if n = 0) (/ f1 .. fn) ----> f1/f2/f3/.....fn (or 1 if n = 0) (< f1 f2) ----> if (f1 < f2) t else nil; (= f1 f2) ----> if (f1 = f2) t else nil; (> f1 f2) ----> if (f1 > f2) t else nil; One thing worth noting is that when you compare two floats you must use = or equal. You cannot use eq because the two float values will almost certainly be stored in different cells even if they have the same value. Hence eq will return nil because they are not the same object. Note that Franz will return 't when you compare two fixnums using eq because car and cdr pointers are used to store the actual fixnum itself hence they appear to be the same object. This is an interpreter dependent trick and PC- LISP does not do it. Consider this example which computes the sum of the cubes of a list of floats called X which is setq'ed to (1 2 3 4 5): -->(setq X '(1 2 3 4 5)) (1 2 3 4 5) -->(eval (cons '+ (mapcar '* X X X))) 225 It operates as follows: First mapcar computes the products 1*1*1, 2*2*2,... 5*5*5 and creates the list (1 8 27 64 125). Then cons splices a '+ onto the front creating (+ 1 8 27 64 125). Finally eval gets the list and computes the result 225. 10 THE BOOLEAN FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~ These functions all return boolean values. The values 't and 'nil represent true and false respectively. 't and 'nil are predefined atoms whose bindings are exactly themselves. Note that 'nil is equivalent to the empty list (). It is the only object that is both at atom and a list. (alphalessp a1 a2) ---> if (ASCII a1 < a2) t else nil; (atom s1) ---> if (s1 of type atom) 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 looks like s2) t else nil; (floatp s1) ---> if (s1 of type float) t else nil; (listp s1) ---> if (s1 of type list) t else nil; (not s1) ---> if (s1 != nil) nil else t; (null s1) ---> if (s1 != nil) nil else t; (or s1 s2 .. sN) ---> if (any si != nil) t else nil; (portp s1) ---> if (s1 of type port) t else nil; Where ASCII a1 < a2 means that string a1 comes before string a2 using the ASCII codes for the characters in both a1 and a2. And, a1 bound means that a1 has been assigned a value. And, s1 looks like s2 means that s1 and s2 have the same list structure and the leaves point to the same atoms. And, s1 and s2 the same object means that they are exactly the same list or the same atom. Note that (eq 'a 'a) will always return 't because the object 'a is unique to the LISP interpreter. On the other hand the call (eq '(a) '(a)) will always return 'nil because the two parameters '(a) and '(a) are distinct lists created by the read function. However (equal '(a) '(a)) will return 't because the two lists are of the same form and have the same leaves. 11 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. (append l1..ln) ---> list made by joining all of l1..ln. (ascii f1) ---> atom with name 'char' where 'char' has ordinal value f1:(0 < f1 < 255). (assoc a1 s2) ---> if s2 is a list of (atom.value) pairs then assoc --> (a1.value) from s2. (car l1) ---> first element in l1 (cdr l1) ---> l1 without first element of l1. (c{a|d}+r l1) ---> performs repeated car or cdr's on l1 as given by reverse of {a|d}+. (cons s1 s2) ---> list with s1 as 1st elem s2 is rest. (explode a1) ---> list of chars in name of a1. (exploden a1) ---> list of ascii values of chars in a1. (implode l1) ---> atom with name formed by compressing all atom elements of l1. (length l1) ---> float atom = # of elements of l1 (list s1 s2...sN) ---> a list with elements (s1 s2 ...sN) (nth f1 l1) ---> f1'th element of l1 (indexed from 0) (nthchar a1 f1) ---> f1'th char in name of a1 (from 0) (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,float,port,alpha or other as determined by type of parameter s1. 12 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% where 'file' is the name of associated port. (close p1) ---> closes the port p1 and returns t. (fileopen a1 a2) ---> port to read write to/from to file a1 using mode a2. Where modes are 'r 'w 'rw 'rwa ... (load a1) ---> The file a1 is read and all lists are evaluated. LISP will look first for file a1, then a1.l then for LIB/a1 and finally for LIB/a1.l. Where LIB is the value of the environment variable : LISP%LIB (patom a1 [p1]) ---> print atom without delimiters to port p1. Default to standard output if p1 not present. returns a1. (print s1 [p1]) ---> print list with delimiters on atoms to p1 or standard out. (read [p1 [s1]]) ---> returns S-expression read from p1 or standard input and returns it or nil on end of file. If s1 is present it returns s1 on EOF. (readc [p1 [s1]]) ---> returns character read from p1 or standard input and returns it or nil on end of file. If s1 is present it returns s1 on EOF. A word about delimiters. Unless you use the patom function all printing of atoms will be done in such a way that they can be read again and produce the same structure. This means that if the atom was originally entered with the | delimiters it will be printed with them. Or, if you created an atom via an implode or ascii function call that has a funny value in it, it will print with the delimiters. So if you want to print a prompt on the screen with spaces in it the best way is to enter (patom '|my prompt|). Note also that 'r 'w and 'a stand for read, write and append respectively. 13 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 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 second (lambda(x)(first(cdr x)))) -->(def sideff (lambda(x)(print x)(caddar x)))) -->(def plus (nlambda(l)(eval(cons '+ l)))) -->(def firstm (macro(l)(cons 'car (cdr l)))) (defun *a1* [*a2*] *l1* *s1* *s2* ....*sN*) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defun will do the same job as "def" except that it will build the lambda or nlambda expression for you. a1 is the name of the function. a2 if present must be one of expr or fexpr. If it is not present it defaults to expr. l1 is the list of formal parameters which for an fexpr (nlambda) should contain one atom formal parameter name. s1...sn are bodies for the lambda or nlambda expression. The following example produces the same effects as the above "def" calls. Defun returns the atom name of the function that it defines. See MACROS for (defun x macro...) -->(defun first(x)(car x)) -->(defun second(x)(first(cdr x))) -->(defun sideff(x)(print x)(caddar x))) -->(defun plus fexpr(l)(eval(cons '+ l))) -->(defun firstm macro(l)(cons 'car (cdr l))) (exit) ~~~~~~ The LISP interpreter will exit to MSDOS. Depending on how much memmory you have it may ask for a system disk to reload COMMAND.COM. Note that the video mode will not be reset if you call (exit). It will however be reset to 80x25B&W if you quit with a control-Z from the top level. (gc) ~~~~ Starts garbage collection of alpha and cell space. Returns t (gensym [a1]) ~~~~~~~~~~~~~ Will generate a new alph atom whose name is gnnnnn where nnnnn will be different each time. It is guaranteed to generate a new atom. If a1 is present then the atoms have the form (name of a1)nnnnn. 14 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 a1) ~~~~~~~~~~~ Will return an atom whose print name is the string set by environment variable a1. For example we can get the PATH variable setting by entering (getenv 'PATH). Note that these must be in upper case for MSDOS. (hashtabstat) ~~~~~~~~~~~~~ Will return a list containing 250 float atoms. Each float atoms value represents the number of elements in the bucket for that hash location in the atom hash table. 250 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 atoms. Large bucket values will not effect any execution times except those of explode, implode, ascii and gensym. In other words only functions that create new atoms will be slowed down by poor hashing. Reading will always suffer from large hash bucket values. (memstat) { not present in Franz } ~~~~~~~~~ returns three floats. 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. 15 FUNCTIONS WITH SIDE EFFECTS OR THAT ARE EFFECTED BY SYSTEM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CONT'D ~~~~~~ (oblist) ~~~~~~~~ Returns a list of every known object 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. (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. 16 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 ((= n 0) 1) (t (* n (factorial (- n 1)))))) 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 --> 17 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 infinite 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 floats 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. 18 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 type which is a lambda expression. Labeled lambda expressions are also allowed. (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 'plus that behaves the same way as '+ in all contexts as follows: -->(def plus (nlambda(l)(eval(cons '+ l)))) or -->(defun plus 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)) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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)))))) 19 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). Cond evaluates the s1's one by one until it finds one that does not eval to nil. It then returns the result of evaluating the corresponding s2. If all of the s1's (called guards) evaluate to nil, it returns 'nil. For example: -->(cond ((eq (type t) 'float) '|t is a float|) ((eq (type t) 'list) '|t is a list |) (t '|t not list or float|))) |t not list or float| (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) 20 LIST EVALUATION CONTROL FUNCTIONS CONT'D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (defun a1 macro l1 s1 s2 ... sn) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Macro is a special body, similar to nlambda except that it causes code replacement when it is evaluated. An example is the best explanation I can give you: (Read LISPcraft 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 (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). This is accomplished as follows: When a macro is encountered, eval passes the entire expression (first-element (quote a b c)) to the macro body. This body is (cons 'car (cdr l)) and is evaluated in the context where the entire expression is bound to the macro parameter l. This results in the code fragment (car (quote a b c)) which is substituted in the code for the original (first-element (quote (a b c))) expression and evaluated giving 'a. The above example demonstrates this by showing what happens to the value of a variable 'x before and after evaluation of the macro. Note the change in the value of x but that the result of (eval x) remains the same. That is the whole purpose of macros. PC-LISP macros have two limitations that Franz macros do not have. A PC-LISP macro MUST return a piece of code that is a list. YOU CANNOT RETURN AN ATOM FROM A MACRO. Secondly a PC-LISP macro must have been def'ined, defun'ed, or putd'ed, otherwise it will not function correctly. Ie YOU CANNOT USE IT LIKE A LAMBDA OR NLAMBDA BODY WITHOUT A NAME. (macroexpand s1) ~~~~~~~~~~~~~~~~ This function lets you see at what the macro expansion of s1 looks like prior to evaluation and substitution. For example: -->(macroexpand '(first-element '(a b c))) (car '(a b c)) 21 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 we ever evaluate a S- expression and it has the form (return Z) we unbind all the atoms in l1 and all labels, and return the result Z. If we ever encounter a list of the form (go loc) we reset our next list to be the location given by loc. 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. Entering and exiting progs is a little expensive time wise, it is probably better not to nest them if you are interested in performance. Note prog labels must be alpha or literal alpha atoms: 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 float 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 examples which follow: -->(eval (cons '+ (hashtabstat))) 306 -->(length(oblist)) 306 22 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 any MSDOS machine 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 not. (#scrline# f1 f2 f3 f4 f5) ~~~~~~~~~~~~~~~~~~~~~~~~~~ Draws a line on the screen connecting (f1,f2) with the point (f3,f4) using attribute f5. This function calls the BIOS set dot function for each point. Hence it is not very fast. (#scrmde# f1) ~~~~~~~~~~~~~ Sets the video mode to f1. Modes are values between 0 and 9. (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. This is as of DOS 2.11. (#scrsap# f1) ~~~~~~~~~~~~~ Sets the active video page to f1. f1 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. (#scrscp# f1 f2 f3) ~~~~~~~~~~~~~~~~~~~ Sets the cursor position to be in page f1 at row f2 and in column f3. Where 0 is the top row and 0 is leftmost col. (#scrsct# f1 f2) ~~~~~~~~~~~~~~~~ Sets the cursor type to agree with the following: f1 bit 5 (0 = blink 1 = steady), bit 6 (0 = visible, 1 = invisible), bits 4-0 = start line for cursor within character cell. f2 bits 4-0 = end line for cursor within character cell. (#scrwdot# f1 f2 f3) ~~~~~~~~~~~~~~~~~~~~ Write a dot (pixel). The pixel at row f1 and column f2 has its color value XORed with the color attribute f3. Since the color attributes vary from machine to machine you will have to look up the correct values for this parameter. 23 TECHNICAL INFORMATION ~~~~~~~~~~~~~~~~~~~~~ The interpreter is written using the Lattice C compiler version 2.03. It consists of 7 separate modules. A scanner, parser, memory manager, list evaluator and critical functions module, a built in functions module, 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. 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 may be variable sized blocks of up to 254 bytes long. These will be extended in future versions of PC-LISP to allow allocation of up to 64K blocks for use by arrays etc. The rest of the cells used by PC-LISP are all considered as one. This consists of the float, cons, 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. These are all kept track of in a large vector of pointers. 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. 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. 24 KNOWN BUGS OR LACKING FEATURES OF V2.07 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -It is possible to run out of stack space while garbage collecting. This occasionally happens with machines that have only 256K of RAM. I have never seen it happen with > 256K of RAM however it can probably still happen. This is because the garbage collector requires stack space to operate. A better version of the garbage collection that uses no stack space will be introduced in a later version. If the stack does overflow during garbage collection, an appropriate error message is printed. A link inverting marking traversal is slated for the next version of PC-LISP and will fix this problem. -You cannot input floats in exponential notation. This is because the LISP lexical analyzer does not yet recognize them. -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 printed from within a prog. These are $[|return|]$ and $[|go|]$. If you attempt to 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. This can occur if you try to use a prog to print the contents of the oblist. If you must print the oblist atom by atom to a file or the screen, make sure you 'absorb' the result of any print or patom calls. -You are not prevented from altering the bindings of t and nil. This means that if you use t or nil as the name of a parameter somewhere it will get temporarily bound to something else while that procedure is executing. You can also set or setq them to other values. Attempts to alter their bindings should obviously be trapped but are not in V2.07. -Macros must return a peice of code which is a list. Atoms cannot be returned. Franz allows either but to alter PC-LISP would require some medium scale surgery that I do not want to undertake unless the feature is really missed. 25 KNOWN BUGS OR LACKING FEATURES OF V2.07 (CONT'D) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The interpreter is pretty slow. This is caused by the continuous error checking, and the use of an extra stack for marking intermediate results. Speed-ups are possible but make the program less robust so I am hesitant to add them. The fact that there is no integer type also causes slow-downs when you are using a float as a loop counter. Floats however are more useful in general than ints so of the two I decided to omit integers for the first large scale release. It is interesting to note that some benchmarks that I ran on a not too busy Vax 11/750 were only about 2 times as fast (waiting time) as my Tandy 2000 (80186 @ 8Mhz). This is not really a fair comparison though because Franz is a much bigger and more complex program. I would be interested to know how PC-LISP performs on other MS-DOS machines. 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 (which will probably not be until September 86). 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. 26