Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.2 9/18/84; site utcsri.UUCP Path: utzoo!utcsri!petera From: petera@utcsri.UUCP (Smith) Newsgroups: net.micro Subject: PC-LISP PACKAGE (article 7 of 13) Message-ID: <2654@utcsri.UUCP> Date: Sun, 27-Apr-86 14:43:36 EDT Article-I.D.: utcsri.2654 Posted: Sun Apr 27 14:43:36 1986 Date-Received: Sun, 27-Apr-86 15:51:15 EDT Distribution: net Organization: CSRI, University of Toronto Lines: 1187 [ line eater ] [ PC-LISP.DOC (part 2 of 2) ] ---------------------------- CUT HERE --------------------------- 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. (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. 20 FILE I/O FUNCTIONS (CONT'D) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ (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. 21 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 ADDEM (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 ADDEM 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 (gensym [h1]) ~~~~~~~~~~~~~ Returns and interns a guaranteed new alpha atom whose print name is Xnnnn where nnnn is some base 10 integer and X is: 'g' if h1 is not present, the print name of h1 if h1 is a symbol, or the string h1 if h1 is a string. 22 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. 23 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. 24 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! factorial( 5 ) ; entered with parm=5 factorial( 4 ) ; " " " 4 factorial( 3 ) ; " " " 3 factorial( 2 ) ; " " " 2 factorial( 1 ) ; " " " 1 factorial( 0 ) ; " " " 0 factorial 1 ; exit 0! = 1 factorial 1 ; exit 1! = 1 factorial 2 ; exit 2! = 1 factorial 6 ; exit 3! = 6 factorial 24 ; exit 4! = 24 factorial 120 ; exit 5! = 120 120 -->(untrace factorial) ; ask LISP to shut up (factorial) -->(factorial 5) ; now it is quiet again. 120 --> 25 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. 26 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 '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)))))) 27 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) 28 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-element 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)) 29 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. The mechanism is usually invisible. 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 30 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 referential 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 reclamation. (hunk s1 s2 .... sN) ~~~~~~~~~~~~~~~~~~~~ Returns a newly created hunk of size N whose elements are s1, s2 ... sN in that order. The hunk will print as {s1 s2 (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 referential structures. 31 DANGEROUS FUNCTIONS ~~~~~~~~~~~~~~~~~~~ The following two functions have potentially disastrous 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 tha 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 when they encounter the abnormal structure. For example: -->(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) (append x '(temp1 temp2))) FooBar -->(setq z (FooBar nil)) (temp1 temp2) -->(rplaca z 'GOTCHA!) (GOTCAH! temp2) -->(FooBar '(a b c)) (a b c GOTCHA! temp2) What happened? Well the list (temp1 temp2) is only stored once and when FooBar is computed the returned list is actually the list (temp1 temp2), hence when we alter it's car, FooBar now appends the list (GOTCHA! temp2) instead of (temp1 temp2). 32 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. n5 is not very useful, colors are not allowed yet so make n5 odd. (#scrmde# n1) ~~~~~~~~~~~~~ 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. (#scrsap# n1) ~~~~~~~~~~~~~ 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. (#scrscp# n1 n2 n3) ~~~~~~~~~~~~~~~~~~~ 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) ~~~~~~~~~~~~~~~~ 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) ~~~~~~~~~~~~~~~~~~~~ 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. 33 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. 34 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.10: -->(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. 35 TECHNICAL INFORMATION ~~~~~~~~~~~~~~~~~~~~~ The interpreter is written using the Lattice C compiler version 2.03. It consists of 7 separate modules totaling nearly 11,000 lines of C. The modules are: 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 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. 36 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. 37 KNOWN BUGS OR LACKING FEATURES OF V2.10 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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. -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 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 behavior , especially if you bind t to nil by accident. -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. -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. 38 KNOWN BUGS OR LACKING FEATURES OF V2.10 (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. -Car and cdr will not access the first and second element of a hunk as they do in Franz. -Read does not recognize the escape '\x' notation. -Character-index will not take a fixnum second parameter as per Franz. Sorry I spotted this too late to fix it in V2.10. -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 ENHANCEMENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. Note - I am planning on releasing the source code some time in the future but not until the program reaches a reasonably mature level. I also want to write a programmers manual so that you can add functions easily and fix bugs without too much trouble. Please be patient for the source. Regards Peter Ashwood-Smith. 39