{++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
{+		PASCAL/Z COMPILER OPTIONS		+}
{++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
{$C- <<< CONTROL-C KEYPRESS CHECKING OFF >>>		 }	
{$F- <<< FLOATING POINT ERROR CHECKING OFF >>>		 }
{$M- <<< INTEGER MULT & DIVD ERROR CHECKING OFF		 }
{++++++++++++++++++++++++++++++++++++++++++++++++++++++++}


(* LAST EDITED: 11/29/81 rep *)

PROGRAM LISP {INPUT,OUTPUT};
{
+  PROGRAM TITLE:	THE ESSENCE OF A LISP INTERPRETER.
+  WRITTEN BY:		W. TAYLOR AND L. COX
+
+  WRITTEN FOR:		US DEPT OF ENERGY
+			CONTRACT # W-7405-ENG-48
+
+	FIRST DATA STARTED : 10/29/76
+	LAST DATE MODIFIED : 12/10/76
+
+ ENTERED BY RAY PENLEY 8 DEC 80.
+ -SOME IDENTIFIERS HAVE BEEN SLIGHTLY MODIFIED BECAUSE OF THE
+  LIMITATION ON IDENTIFIER LENGTH OF 8 CHARACTERS.
}
	{++++++++++++++++++++++++++++++++++++++++++++++++}
	{+	RESERVED WORDS TABLE LISP		+}
	{++++++++++++++++++++++++++++++++++++++++++++++++}
{
	'APPEND    '	<
	'ATOM      '	<  A VARIABLE OR LITERAL USED IN A LIST.
	'REPLACEH  '	<
	'REPLACET  '	<
	'CAR       '	<  THE FIRST ELEMENT OF A LIST.
	'COND      '	<
	'COPY      '	<
	'CONC      '	<
	'CONS      '	<
	'EQ        '	<
	'QUOTE     '	<
	'LABEL     '	<
	'LAMBDA    '	<  FIRST ELEMENT OF A USER DEFINED FUNCTION.
	'CDR       '	<  ALL ELEMENTS OF A LIST EXCEPT THE FIRST ELEMENT.
	'FIN       '	<  FINISHED.

}

LABEL
  1,	{ USED TO RECOVER AFTER AN ERROR BY THE USER }
  2;	{ IN CASE THE END OF FILE IS REACHED BEFORE A FIN CARD }

CONST
  MAXNODE = 600;
{}INPUT = 0;	{ Pascal/Z = console as input }
{}IDLENGTH = 10;

TYPE
{}ALFA = ARRAY [1..10] OF CHAR;
  INPUTSYMBOL = (ATOM, PERIOD, LPAREN, RPAREN);
  RESERVEWORDS = (RELACEHSYM, RELACETSYM, HEADSYM, TAILSYM, EQSYM, QUOTESYM,
		  ATOMSYM, CONDSYM, LABELSYM, LAMBDASYM, COPYSYM, APPENDSYM,
		  CONCSYM, CONSSYM);
  STATUSTYPE = (UNMARKED, LEFT, RIGHT, MARKED);
  SYMBEXPPTR = ^SYMBOLICEXPRESSION;
  SYMBOLICEXPRESSION = RECORD
			 STATUS : STATUSTYPE;
			 NEXT   : SYMBEXPPTR;
			 CASE ANATOM: BOOLEAN OF
			   TRUE: (NAME: ALFA;
				  CASE ISARESERVEDWORD: BOOLEAN OF
				    TRUE: (RESSYM: RESERVEWORDS));
			   FALSE: (HEAD, TAIL: SYMBEXPPTR)
			END;

{
	Symbolicexpression is the record structure used	to implement
	a LISP list.  This record has a tag field 'ANATOM' which tells
	which kind of node a particular node represents (i.e. an atom
	or a pair of pointers 'HEAD' and 'TAIL'), 'ANATOM' is always
	checked before accessing either the name field or the head and
	tail fields of a node.  Two pages ahead there are three diagrams
	which should clarify the data structure.
}

{	THE GLOBAL VARIABLES	}

VAR
{}DUMMY		: CHAR;		{ required in the Pascal/Z version }

{ VARIABLES WHICH PASS INFORMATION FROM THE SCANNER TO THE READ ROUTINE }

  LOOKAHEADSYM,			{ USED TO SAVE A SYMBOL WHEN WE BACK UP }
  SYM		: INPUTSYMBOL;	{ THE SYMBOL THAT WAS LAST SCANNED }
  ID		: ALFA;		{ NAME OF THE ATOM THAT WAS LAST READ }
  ALREADYPEEKED	: BOOLEAN;	{ TELLS 'NEXTSYM' WHETHER WE HAVE PEEKED }
  CH		: CHAR;		{ THE LAST CHAR READ FROM INPUT }
  PTR		: SYMBEXPPTR;	{ POINTER TO THE EXPRESSION BEING EVALUATED }

	{ THE GLOBAL LISTS OF LISP NODES }

  FREELIST,	{ POINTER TO THE LINEAR LIST OF FREE NODES }
  NODELIST,	{ POINTER USED TO MAKE A LINEAS SCAN OF ALL}
		{ THE NODES DURING GARBAGE COLLECTION.	   }
  ALIST	: SYMBEXPPTR;{ POINTER TO THE ASSOCIATION LIST }

	{ TWO NODES WHICH HAVE CONSTANT VALUES }

  NILNODE,
  TNODE	: SYMBOLICEXPRESSION;

	{ VARIABLES USED TO IDENTIFY ATOMS WITH PRE-DEFINED MEANINGS }

  RESWORD	: RESERVEWORDS;
  RESERVED	: BOOLEAN;
  RESWORDS	: ARRAY [RESERVEWORDS] OF ALFA;
  FREENODES	: INTEGER; { NUMBER OF CURRENTLY FREE NODES KNOWN }
  NUMBEROFGCS	: INTEGER; { # OF GARBAGE COLLECTIONS MADE }
{

					 \
					  \
	THE ATOM 'A' IS			---\---
	REPRESENTED BY --->             I     I
					I  A  I
					I     I
					-------


				   \
				    \
				-----\-----
	THE DOTTED PAIR		I    I    I
	'(A.B)' IS		I  / I \  I
	REPESENTED BY --->	I /  I  \ I
				-/-------\-
				/         \
			   ----/----   ----\----
			   I       I   I       I
			   I   A   I   I   B   I
			   I       I   I       I
			   ---------   ---------


				   \
				    \
				-----\-----
	THE LIST '(AB)'		I    I    I
	IS REPRESENTED 		I  / I \  I
	BY --->			I /  I  \ I
				-/-------\-
				/         \
			   ----/----       \
			   I       I        \
			   I   A   I    -----\-----
			   I       I    I    I    I
			   ---------    I   /I\   I
					I  / I \  I
					--/-----\--
					 /       \
				    ----/---- ----\----
				    I       I I       I
				    I   B   I I  NIL  I
				    I       I I       I
				    --------- ---------
}
(*	*	THE GARBAGE COLLECTOR        *          *)
{
 In  general  there are two approaches to maintaining lists of available space
in list processing systems... The  reference counter technique and the garbage
collector technique.

 The reference counter technique requires that for  each  node  or  record  we
maintain  a  count  of  the number of nodes which reference or point to it and
update this count continuously. ie.  with  every  manipulation  In general, if
circular or ring structures are permitted to develope this technique will  not
be  able  to  reclaim  rings which are no longer in use and have been isolared
from the active structure.

 The alternative method, garbage  collection,  does not function continuously,
but is activated only when further storage is required and none is  available.
The complete process consists of two stages.  A marking stage which identifies
nodes  still  reachable (in use) and a collection stage where all nodes in the
system are examined and those not in  use  are merged into a list of available
space.  This is the technique we have chosen to implement here for reasons  of
simplicity and to enhance the interactive nature of out system.

 The  marking  stage  is  theoretically simple, especially in LISP programming
systems where all records are essentially the same size.  All that is required
is a traversal of the active  list  structure, each time marking nodes 1 level
deeper into the tree on each pass.  This is both crude and inefficient.

 Another alternative procedure which could be used would use a recursive  walk
of  the  tree  structure to mark the nodes in use.  This requires the use of a
stack to store  back  pointers  to  branches  not  taken.    This algorithm is
efficient, but tend to  be  self  defeating  in  the  folowing  manner.    The
requisite  stack  could  become  quite large (requiring significant amounts of
storage).  However, the  reason  we  are  performing garbage collection in the
first place is due to  an  insufficiency  of  storage  space.    Therefore  an
usdesirable  situation  is likely to arise where the garbage collector's stack
cannot expand to perform the marking  pass.  Even though there are significant
amounts of free space waiting to be reclaimed.

 A solution to this dilema came when it was realized that space in  the  nodes
themselves  (i.e.  the  left  and right pointers) could be used in lieu of the
explicit stack.  In this way  the  stack  information can be embedded into the
list itself as it is traversed.  This algorithm has been  discussed  in  Knuth
and  in  Berztiss:  Data  Structures,  Theory  and  Practice (2nd ed.), and is
implemented below.

 Since Pascal does not allow structures to be addressed both with pointers and
as indexed arrays, an additional field has been added to sequentially link the
nodes.  This pointer field is  set  on initial creation, and remains invarient
throughout the run.  Using this field, we can simulate a linear  pass  through
the  nodes  for  the  collection  stage.    Of  course, a marker field is also
required.
}
(*	*	*	*	*	*	*	*)

PROCEDURE GARBAGEMAN;

  PROCEDURE MARK(LIST: SYMBEXPPTR);
  VAR
    FATHER, SON, CURRENT: SYMBEXPPTR;
  BEGIN
    FATHER := NIL;
    CURRENT := LIST;
    SON := CURRENT;
    WHILE ( CURRENT<>NIL ) DO
      WITH CURRENT^ DO
	CASE STATUS OF
	  UNMARKED:
	    IF ( ANATOM ) THEN
	      STATUS := MARKED
	    ELSE
	      IF (HEAD^.STATUS <> UNMARKED) OR (HEAD = CURRENT) THEN
		IF (TAIL^.STATUS <> UNMARKED) OR (TAIL = CURRENT) THEN
		   STATUS := MARKED
		ELSE BEGIN
		  STATUS := RIGHT; SON := TAIL; TAIL := FATHER;
		  FATHER := CURRENT; CURRENT := SON
		END
	      ELSE BEGIN
		STATUS := LEFT; SON := HEAD; HEAD := FATHER;
		FATHER := CURRENT; CURRENT := SON
	      END;
	  LEFT:
	    IF ( TAIL^.STATUS <> UNMARKED ) THEN BEGIN
	      STATUS := MARKED; FATHER := HEAD; HEAD := SON;
	      SON := CURRENT
	    END
	    ELSE BEGIN
	      STATUS := RIGHT; CURRENT := TAIL; TAIL := HEAD;
	      HEAD := SON; SON := CURRENT
	    END;
	  RIGHT:
	    BEGIN
		STATUS := MARKED; FATHER := TAIL; TAIL := SON;
		SON := CURRENT
	    END;
	  MARKED: CURRENT := FATHER
	END { OF CASE }
  END { OF MARK };

  PROCEDURE COLLECTFREENODES;
  VAR
    TEMP: SYMBEXPPTR;
  BEGIN
    WRITELN(' NUMBER OF FREE NODES BEFORE COLLECTION = ', FREENODES:1, '.');
    FREELIST := NIL; FREENODES := 0; TEMP := NODELIST;
    WHILE ( TEMP <> NIL ) DO BEGIN
	IF ( TEMP^.STATUS <> UNMARKED ) THEN
	  TEMP^.STATUS := UNMARKED
	ELSE BEGIN
	  FREENODES := FREENODES + 1; TEMP^.HEAD := FREELIST;
	  FREELIST := TEMP
	END;
	TEMP := TEMP^.NEXT;
    END {WHILE};
    WRITELN(' NUMBER OF FREE NODES AFTER COLLECTION = ', FREENODES:1,'.');
  END { OF COLLECTFREENODES };

BEGIN{ GARBAGEMAN }
  NUMBEROFGCS := NUMBEROFGCS + 1; WRITELN;
  WRITELN(' GARBAGE COLLECTION. '); WRITELN; MARK(ALIST);
  IF ( PTR <> NIL ) THEN MARK(PTR);
  COLLECTFREENODES
END{ OF GARBAGEMAN };

PROCEDURE POP(VAR SPTR: SYMBEXPPTR);
BEGIN
  IF ( FREELIST = NIL ) THEN BEGIN
    WRITELN(' NOT ENOUGH SPACE TO EVALUATE THE EXPRESSION.');
{}  GOTO 2;
  END;
  FREENODES := FREENODES - 1;
  SPTR := FREELIST;
  FREELIST := FREELIST^.HEAD;
END{ OF POP };


{	INPUT / OUTPUT UTILITY ROUTINES		 }

PROCEDURE ERROR(NUMBER: INTEGER);
BEGIN
  WRITELN; WRITE('  ERROR   ', NUMBER:1, ', ');
  CASE NUMBER OF
    1: WRITELN('ATOM OR LPAREN EXPECTED IN THE S-EXPR.');
    2: WRITELN('ATOM, LPAREN, OR RPAREN EXPECTED IN THE S-EXPR.');
    3: WRITELN('LABEL AND LAMBDA ARE NOT NAMES OF FUNCTIONS.');
    4: WRITELN('RPAREN EXPECTED IN THE S-EXPR.');
    5: WRITELN('1ST ARGUMENT OF REPLACEH IS AN ATOM.');
    6: WRITELN('1ST ARGUMENT OF REPLACET IS AN ATOM.');
    7: WRITELN('ARGUMENT HEAD IS AN ATOM.');
    8: WRITELN('ARGUMENT TAIL IS AN ATOM.');
    9: WRITELN('1ST ARGUMENT OF APPEND IS NOT A LIST.');
   10: WRITELN('COMMA OR RPAREN EXPECTED IN CONCATENATE.');
   11: WRITELN('END OF FILE ENCOUNTERED BEFORE A "FIN" CARD.');
   12: WRITELN('LAMBDA OR LABEL EXPECTED.');
  END{CASE};
{}IF NUMBER IN [11] THEN
    GOTO 2
  ELSE
    GOTO 1
END { OF ERROR };

PROCEDURE BACKUPINPUT;
{	PUTS A LEFT PARENTHESIS INTO THE STREAM OF INPUT
	SYMBOLS.  THIS MAKES PROCEDURE READEXPR EASIER
	THAN IT OTHERWISE WOULD BE.
}
BEGIN
  ALREADYPEEKED := TRUE; LOOKAHEADSYM := SYM; SYM := LPAREN
END{ OF BACKUPINPUT };

PROCEDURE NEXTSYM;
{	READS THE NEXT SYMBOL FROM THE INPUT FILE.  A SYMBOL IS	DEFINED
	BY THE GOLBAL TYPE "INPUTSYMBOL".  THE GLOBAL VARIABLE 'SYM'
	RETURNS THE TYPE OF THE NEXT SYMBOL READ.  THE GLOBAL VARIABLE
	'ID' RETURNS THE NAME OF AN ATOM IF THE SYMBOL IS AN ATOM.  IF
	THE SYMBOL IS A RESERVED WORD THE GLOBAL VARIABLE 'RESERVED' IS
	SET TO TRUE AND THE GLOBAL VARIABLE 'RESWORD' TELLS WHICH RESERVED
	WORD WAS READ.
}
VAR	I: INTEGER;
BEGIN
  IF ( ALREADYPEEKED ) THEN BEGIN
      SYM := LOOKAHEADSYM; ALREADYPEEKED := FALSE
  END
  ELSE
    BEGIN
      WHILE ( CH=' ' ) DO BEGIN
	IF ( EOLN(INPUT) ) THEN WRITELN;
	READ(CH);
      END{WHILE};
      IF ( CH IN ['(','.',')'] ) THEN BEGIN
	CASE CH OF
	  '(': SYM := LPAREN;
	  '.': SYM := PERIOD;
	  ')': SYM := RPAREN
	END{CASE};
	IF ( EOLN(INPUT) ) THEN WRITELN;
	READ(CH);
      END
      ELSE BEGIN
	SYM := ATOM; ID := '          ';
	I := 0;
	REPEAT
	  I := I + 1;
	  IF ( I < (IDLENGTH+1) ) THEN ID[I] := CH;
	  IF ( EOLN(INPUT) ) THEN WRITELN;
	  READ(CH);
	UNTIL ( CH IN [' ','(','.',')'] );
	RESWORD := RELACEHSYM;
	WHILE (ID <> RESWORDS[RESWORD]) AND (RESWORD <> CONSSYM) DO
	  RESWORD := SUCC(RESWORD);
	RESERVED := ( ID=RESWORDS[RESWORD] )
      END
    END  
END{ OF NEXTSYM };

PROCEDURE READEXPR(VAR SPTR: SYMBEXPPTR);
{
	THIS PROCEDURE RECURSIVELY READS IN THE NEXT SYMBOLIC EXPRESSION
	FROM THE INPUT FILE.  WHEN CALLED THE GLOBAL VARIABLE 'SYM' MUST
	BE THE FIRST SYMBOL IN THE SYMBOLIC EXPRESSION TO BE READ.  A
	POINTER TO THE SYMBOLIC EXPRESSION READ IS RETURNED VIA THE
	VARIABLE PARAMETER SPTR.
	EXPRESSIONS ARE READ AND STORED IN THE APPROPRIATE STRUCTURE
	USING THE FOLLOWING GRAMMAR FOR SYMBOLIC EXPRESSIONS:

	<s-expr> ::= <atom>
		 or ( <s-expr> . <s-expr> )
		 or ( <s-expr> <s-expr> ... <s-expr> )

	WHERE ... MEANS AN ARBITRARY NUMBER OF. (I.E. ZERO OR MORE.)
	TO PARSE USING THE THIRD RULE, THE IDENTITY
		(ABC ... Z) = (A . (BC ... Z))
	IS UTILIZED.  AN EXTRA LEFT PARENTHESIS IS INSERTED INTO THE
	INPUT STREAM AS IF IT OCCURED AFTER THE IMAGINARY DOT.  WHEN
	IT COMES TIME TO READ THE IMAGINARY MATCHING RIGHT PARENTHESIS 
	IT IS JUST NOT READ (BECAUSE IT IS NOT THERE).
}
VAR	NXT: SYMBEXPPTR;
BEGIN
  POP(SPTR);
  NXT := SPTR^.NEXT;
  CASE SYM OF
    RPAREN, PERIOD: ERROR(1);
    ATOM:
	WITH SPTR^ DO BEGIN {  <ATOM>  }
	  ANATOM := TRUE; NAME := ID; ISARESERVEDWORD := RESERVED;
	  IF ( RESERVED ) THEN RESSYM := RESWORD
	END;
    LPAREN:
	WITH SPTR^ DO BEGIN
	  NEXTSYM;
	  IF ( SYM=PERIOD ) THEN ERROR(2)
	  ELSE
	    IF ( SYM=RPAREN ) THEN SPTR^ := NILNODE {   () = NIL   }
	    ELSE BEGIN
		ANATOM := FALSE; READEXPR(HEAD); NEXTSYM;
		IF ( SYM=PERIOD ) THEN BEGIN {   ( <S-EXPR> . <S-EXPR> )   }
		   NEXTSYM;  READEXPR(TAIL); NEXTSYM;
		   IF ( SYM<>RPAREN ) THEN ERROR(4)
		END
		ELSE BEGIN {   ( <S-EXPR> <S-EXPR> ... <S-EXPR> )   }
		  BACKUPINPUT; READEXPR(TAIL)
		END
	    END
	END{WITH}
  END{CASE};
  SPTR^.NEXT := NXT;
END{ OF READEXPR };

PROCEDURE PRINTNAME(NAME: ALFA);
{
	PRINTS THE NAME OF AN ATOM WITH ONE TRAILING BLANK.
}
VAR	I: INTEGER;
BEGIN
  I := 1;
  REPEAT
    WRITE(NAME[I]);
    I := I + 1
  UNTIL (NAME[I]=' ') OR ( I=11 );
  WRITE(' ');
END{ OF PRINTNAME };

PROCEDURE PRINTEXPR(SPTR: SYMBEXPPTR);
{
	THE ALGORITHM FOR THIS PROCEDURE WAS PROVIDED BY WEISSMAN'S LISP
	1.5 PRIMER, PG 125.  THIS PROCEDURE PRINTS THE SYMBOLIC
	EXPRESSION POINTED TO BY THE ARGUMENT 'SPTR' IN THE LIST LIST
	NOTATION. (THE SAME NOTATION IN WHICH EXPRESSIONS ARE READ.)
}
LABEL 1;
BEGIN
  IF ( SPTR^.ANATOM ) THEN
    PRINTNAME(SPTR^.NAME)
  ELSE BEGIN
    WRITE('(');
 1: WITH SPTR^ DO BEGIN
	PRINTEXPR(HEAD);
	IF ( TAIL^.ANATOM ) AND (TAIL^.NAME='NIL       ') THEN
	  WRITE(')')
	ELSE IF ( TAIL^.ANATOM ) THEN BEGIN
	  WRITE('.'); PRINTEXPR(TAIL); WRITE(')')
	END
	ELSE BEGIN
	  SPTR := TAIL;
	  GOTO 1
	END
    END{WITH}
  END
END{ OF PRINTEXPR };

{	END OF I/O UTILITY ROUTINES	}


{	THE EXPRESSION EVALUATOR EVAL	   }

FUNCTION EVAL( E, ALIST: SYMBEXPPTR ): SYMBEXPPTR;
{
 Function eval evaluates the LISP expression 'e' using the association
 list 'alist'. This function uses the following several local functions
 to do so. The algorithm is a Pascal version of the classical LISP
 problem of writing the LISP eval routine in pure LISP. The LISP version
 of the code is as follows:

 (lambda (e alist)
   cond
     ((atom a) (lookup e alist))
     ((atom (car e))
       (cond ((eq (car e) (quote quote))
           (cadr e))
         ((eq (car e) (quote atom))
           (atom (eval (card e) alist)
         ((eq (car e) (quote eq))
           (eq (eval (cadr e) alist)))
         ((eq (car e) (quote car))
           (car (eval (cadr e) alist)))
         ((eq (car e) (quote cdr))
           (cdr (eval (cadr e) alist)))
         ((eq (car e) (quote cons)
           (cons (eval (cadr e) alist)
             (eval (caddr e) alist)
         ((eq (car e) (quote cond)
           (evcon (cdr e))
         (t (eval (cons (lookup (car e) alist)
           (cdr e)) alist )))
     ((eq (caar e) (quote label))
       (eval (cons (caddr e)
         (cdr e)
       (cons (cons (cadar e) (car e))
         alist) ))
   ((eq (caar e) (quote lambda))
     (eval (caddar e)
       (bindargs (cadar e) (cdr e) )))))


	The resulting Pascal code follows:
}
VAR	TEMP, CAROFE, CAAROFE: SYMBEXPPTR;
{
	The first ten of the following local functions implement
	ten of the primitives which operate on the LISP data
	structure. The last three ; 'lookup', 'bindargs', and 'evcon'
	are used by 'eval' to interpret a LISP expresson.
}
  FUNCTION REPLACEH(SPTR1, SPTR2: SYMBEXPPTR): SYMBEXPPTR;
  BEGIN
    IF ( SPTR1^.ANATOM ) THEN ERROR(5)
    ELSE SPTR1^.HEAD := SPTR2;
    REPLACEH := SPTR1;
  END{ OF REPLACEH };

  FUNCTION REPLACET(SPTR1, SPTR2: SYMBEXPPTR): SYMBEXPPTR;
  BEGIN
    IF ( SPTR1^.ANATOM ) THEN ERROR(6)
    ELSE SPTR1^.TAIL := SPTR2;
    REPLACET := SPTR1;
  END{ OF REPLACET };

  FUNCTION HEAD(SPTR: SYMBEXPPTR): SYMBEXPPTR;
  BEGIN
    IF ( SPTR^.ANATOM ) THEN ERROR(7)
    ELSE HEAD := SPTR^.HEAD;
  END{ OF HEAD };

  FUNCTION TAIL(SPTR: SYMBEXPPTR): SYMBEXPPTR;
  BEGIN
    IF ( SPTR^.ANATOM ) THEN ERROR(8)
    ELSE TAIL := SPTR^.TAIL;
  END{ OF TAIL };

  FUNCTION CONS(SPTR1, SPTR2: SYMBEXPPTR): SYMBEXPPTR;
  VAR	TEMP: SYMBEXPPTR;
  BEGIN
    POP(TEMP);
    TEMP^.ANATOM := FALSE; TEMP^.HEAD := SPTR1;
    TEMP^.TAIL := SPTR2; CONS := TEMP;
  END{ OF CONS };

  FUNCTION COPY(SPTR: SYMBEXPPTR): SYMBEXPPTR;
  {
	THIS FUNCTION CREATES A COPY OF THE STRUCTURE
	POINTED TO BY THE PARAMETER 'SPTR'
  }
  VAR	TEMP, NXT: SYMBEXPPTR;
  BEGIN
    IF ( SPTR^.ANATOM ) THEN BEGIN
	POP(TEMP);
	NXT := TEMP^.NEXT; TEMP^ := SPTR^;
 	TEMP^.NEXT := NXT; COPY := TEMP
    END
    ELSE
	COPY := CONS(COPY(SPTR^.HEAD), COPY(SPTR^.TAIL));
  END{ OF COPY };

  FUNCTION APPEND(SPTR1, SPTR2: SYMBEXPPTR): SYMBEXPPTR;
  {
	THE RECURSIVE ALGORITHM IS FROM WEISSMAN, PG 97.
  }
  BEGIN
    IF ( SPTR1^.ANATOM ) THEN
      IF ( SPTR1^.NAME<>'NIL       ' ) THEN ERROR(9)
      ELSE APPEND := SPTR2
    ELSE
      APPEND := CONS(COPY(SPTR1^.HEAD), APPEND(SPTR1^.TAIL,SPTR2));
  END{ OF APPEND };

  FUNCTION CONC(SPTR1: SYMBEXPPTR): SYMBEXPPTR;
  {
	This function serves as the basic concatenation mechanism
	for variable numbers of list expressions in the input stream.
	The concatenation is handled recursively, using the identity:
	   conc(a,b,c,d) = conc(a,cons(b,cons(c,(cons(d,nil))))

	The routine is called when a conc(..... command has been
	recognized on input, and its single argument is the first
	expression in the chain.  It has the side effect of reading
	all following input up to the parenthesis closing the
	conc command.

	The procedure consists of the following steps-
	  1. call with 1st expression as argument.
	  2. read the next expression.
	  3. if the expression just read was not the last, recurse.
	  4. otherwise... unwind.
}
  VAR
    SPTR2, NILPTR: SYMBEXPPTR;
  BEGIN
    IF ( SYM<>RPAREN ) THEN BEGIN
	NEXTSYM; READEXPR(SPTR2); NEXTSYM;
	CONC := CONS(SPTR1, CONC(SPTR2));
    END
    ELSE
      IF ( SYM=RPAREN ) THEN BEGIN
	NEW(NILPTR);
	WITH NILPTR^ DO BEGIN
	  ANATOM := TRUE; NAME := 'NIL       ';
	END{WITH};
	CONC := CONS(SPTR1, NILPTR);
      END
      ELSE
	ERROR(10);
  END{ OF CONC };

  FUNCTION EQQ(SPTR1, SPTR2: SYMBEXPPTR): SYMBEXPPTR;
  VAR	TEMP, NXT: SYMBEXPPTR;
  BEGIN
    POP(TEMP);
    NXT := TEMP^.NEXT;
    IF ( SPTR1^.ANATOM ) AND ( SPTR2^.ANATOM ) THEN
      IF ( SPTR1^.NAME=SPTR2^.NAME ) THEN
	TEMP^ := TNODE
      ELSE if ( sptr1=sptr2 ) then
	temp^ := tnode
      else
	temp^ := nilnode;
    TEMP^.NEXT := NXT; EQQ := TEMP;
  END{ OF EQQ };

  FUNCTION ATOM(SPTR: SYMBEXPPTR): SYMBEXPPTR;
  VAR	TEMP, NXT: SYMBEXPPTR;
  BEGIN
    POP(TEMP);
    NXT := TEMP^.NEXT;
    IF ( SPTR^.ANATOM ) THEN
      TEMP^ := TNODE
    ELSE
      TEMP^ := NILNODE;
    TEMP^.NEXT := NXT; ATOM := TEMP;
  END{ OF ATOM };

  FUNCTION LOOKUP(KEY, ALIST: SYMBEXPPTR): SYMBEXPPTR;
  VAR
    TEMP: SYMBEXPPTR;
  BEGIN
    TEMP := EQQ( HEAD( HEAD(ALIST)), KEY);
    IF ( TEMP^.NAME='T         ' ) THEN
      LOOKUP := TAIL(HEAD(ALIST))
    ELSE
      LOOKUP := LOOKUP(KEY, TAIL(ALIST))
  END{ OF LOOKUP };

  FUNCTION BINDARGS(NAMES, VALUES: SYMBEXPPTR): SYMBEXPPTR;
  VAR
    TEMP, TEMP2: SYMBEXPPTR;
  BEGIN
    IF ( NAMES^.ANATOM ) AND (NAMES^.NAME='NIL       ') THEN
      BINDARGS := ALIST
    ELSE BEGIN
	TEMP := CONS( HEAD(NAMES), EVAL(HEAD(VALUES), ALIST) );
	TEMP2 := BINDARGS(TAIL(NAMES), TAIL(VALUES));
	BINDARGS := CONS(TEMP, TEMP2)
    END
  END{ OF BINDARGS };

  FUNCTION EVCON(CONDPAIRS: SYMBEXPPTR): SYMBEXPPTR;
  VAR
    TEMP: SYMBEXPPTR;
  BEGIN
    TEMP := EVAL( HEAD(HEAD(CONDPAIRS)),ALIST );
    IF ( TEMP^.ANATOM ) AND (TEMP^.NAME='NIL       ') THEN
      EVCON := EVCON( TAIL(CONDPAIRS) )
    ELSE
      EVCON := EVAL( HEAD(TAIL(HEAD(CONDPAIRS))),ALIST )
  END{ OF EVCON };


  BEGIN	{   * E V A L *   }
    IF ( E^.ANATOM ) THEN EVAL := LOOKUP(E, ALIST)
    ELSE
      BEGIN
	CAROFE := HEAD(E);
	IF ( CAROFE^.ANATOM ) THEN
	   IF NOT ( CAROFE^.ISARESERVEDWORD ) THEN
	     EVAL := EVAL( CONS(LOOKUP(CAROFE,ALIST),TAIL(E)), ALIST )
	   ELSE
	     CASE CAROFE^.RESSYM OF

	       LABELSYM, LAMBDASYM: ERROR(3);

	       QUOTESYM	: EVAL := HEAD(TAIL(E));

	       ATOMSYM	: EVAL := ATOM(EVAL(HEAD(TAIL(E)),ALIST));

	       EQSYM	: EVAL := EQQ(EVAL(HEAD(TAIL(E)),ALIST),
				      EVAL(HEAD(TAIL(TAIL(E))), ALIST));

	       HEADSYM	: EVAL := HEAD(EVAL(HEAD(TAIL(E)),ALIST));

	       TAILSYM	: EVAL := TAIL(EVAL(HEAD(TAIL(E)),ALIST));

	       CONSSYM	: EVAL := CONS(EVAL(HEAD(TAIL(E)),ALIST),
				       EVAL(HEAD(TAIL(TAIL(E))), ALIST));

	       CONDSYM	: EVAL := EVCON(TAIL(E));

	       CONCSYM	: {};

	       APPENDSYM : EVAL := APPEND(EVAL(HEAD(TAIL(E)),ALIST),
					  EVAL(HEAD(TAIL(TAIL(E))), ALIST));

	       RELACEHSYM : EVAL := REPLACEH(EVAL(HEAD(TAIL(E)),ALIST),
					   EVAL(HEAD(TAIL(TAIL(E))), ALIST));

	       RELACETSYM : EVAL := REPLACET(EVAL(HEAD(TAIL(E)),ALIST),
					   EVAL(HEAD(TAIL(TAIL(E))), ALIST));
	     END{CASE}
         ELSE
	   BEGIN
	     CAAROFE := HEAD(CAROFE);
	     IF ( CAAROFE^.ANATOM ) AND ( CAAROFE^.ISARESERVEDWORD ) THEN
	       IF NOT ( CAAROFE^.RESSYM IN [LABELSYM, LAMBDASYM] ) THEN
		 ERROR(12)
	       ELSE
		 CASE CAAROFE^.RESSYM OF
		   LABELSYM:
		      BEGIN
			TEMP := CONS( CONS(HEAD(TAIL(CAROFE)),
			   	      HEAD(TAIL(TAIL(CAROFE)))), ALIST);
			EVAL := EVAL(CONS(HEAD(TAIL(TAIL(CAROFE))),
			   		TAIL(E)),TEMP)
		      END;
		   LAMBDASYM:
		      BEGIN
			TEMP := BINDARGS(HEAD(TAIL(CAROFE)), TAIL(E));
			EVAL := EVAL( HEAD( TAIL( TAIL(CAROFE))), TEMP)
		      END
		 END{ CASE }
	     ELSE
	       EVAL := EVAL(CONS(EVAL(CAROFE, ALIST), TAIL(E)), ALIST)
          END   
      END
END{ OF EVAL };

PROCEDURE INITIALIZE;
VAR	I: INTEGER;
	TEMP, NXT: SYMBEXPPTR;
BEGIN
  ALREADYPEEKED := FALSE;
  READ(CH);
  NUMBEROFGCS := 0;
  FREENODES := MAXNODE;
  WITH NILNODE DO BEGIN
    ANATOM := TRUE; NEXT := NIL; NAME := 'NIL       ';
    STATUS := UNMARKED; ISARESERVEDWORD := FALSE
  END;

  WITH TNODE DO BEGIN
    ANATOM := TRUE; NEXT := NIL; NAME := 'T         ';
    STATUS := UNMARKED; ISARESERVEDWORD := FALSE
  END;
{
	ALLOCATE STORAGE AND MARK IT FREE
}
  FREELIST := NIL;
  FOR I:=1 TO MAXNODE DO BEGIN
    NEW(NODELIST); NODELIST^.NEXT := FREELIST;
    NODELIST^.HEAD := FREELIST; NODELIST^.STATUS := UNMARKED;
    FREELIST := NODELIST
  END;
{
	INITIALIZE RESERVED WORD TABLE
}
  RESWORDS[ APPENDSYM   ] := 'APPEND    ';
  RESWORDS[ ATOMSYM     ] := 'ATOM      ';
  RESWORDS[ HEADSYM     ] := 'CAR       ';
  RESWORDS[ TAILSYM     ] := 'CDR       ';
  RESWORDS[ CONDSYM     ] := 'COND      ';
  RESWORDS[ COPYSYM     ] := 'COPY      ';
  RESWORDS[ CONCSYM     ] := 'CONC      ';
  RESWORDS[ CONSSYM     ] := 'CONS      ';
  RESWORDS[ EQSYM       ] := 'EQ        ';
  RESWORDS[ LABELSYM    ] := 'LABEL     ';
  RESWORDS[ LAMBDASYM   ] := 'LAMBDA    ';
  RESWORDS[ QUOTESYM    ] := 'QUOTE     ';
  RESWORDS[ RELACEHSYM  ] := 'REPLACEH  ';
  RESWORDS[ RELACETSYM  ] := 'REPLACET  ';
{
	INITIALIZE THE A-LIST WITH  T  AND  NIL
}
  POP(ALIST);
  ALIST^.ANATOM := FALSE;
  ALIST^.STATUS := UNMARKED;
  POP(ALIST^.TAIL);
  NXT := ALIST^.TAIL^.NEXT;
  ALIST^.TAIL^ := NILNODE;
  ALIST^.TAIL^.NEXT := NXT;
  POP(ALIST^.HEAD);
{
	BIND NIL TO THE ATOM NIL
}
  WITH ALIST^.HEAD^ DO BEGIN
    ANATOM := FALSE; STATUS := UNMARKED; POP(HEAD);
    NXT := HEAD^.NEXT; HEAD^ := NILNODE; HEAD^.NEXT := NXT;
    POP(TAIL); NXT := TAIL^.NEXT; TAIL^ := NILNODE;
    TAIL^.NEXT := NXT
  END;
  POP(TEMP);
  TEMP^.ANATOM := FALSE;
  TEMP^.STATUS := UNMARKED;
  TEMP^.TAIL := ALIST;
  ALIST := TEMP;
  POP(ALIST^.HEAD);
{
	BIND  T  TO THE ATOM  T
}
  WITH ALIST^.HEAD^ DO BEGIN
    ANATOM := FALSE;  STATUS := UNMARKED; POP(HEAD);
    NXT := HEAD^.NEXT; HEAD^ := TNODE; HEAD^.NEXT := NXT;
    POP(TAIL); NXT := TAIL^.NEXT; TAIL^ := TNODE;
    TAIL^.NEXT := NXT
  END
END{ OF INITIALIZE };



BEGIN{+		LISP MAIN PROGRAM		+}
  WRITELN(' * EVAL *');
  INITIALIZE;
  NEXTSYM;
  READEXPR(PTR);
{}READLN(DUMMY);
  WRITELN;
  WHILE NOT ( PTR^.ANATOM ) OR ( PTR^.NAME<>'FIN       ' ) DO BEGIN
    WRITELN;
    WRITELN(' * VALUE *');
    PRINTEXPR( EVAL(PTR, ALIST) );
1:  WRITELN;
    WRITELN;
    IF ( EOF(INPUT) ) THEN ERROR(11);
    PTR := NIL;
    { CALL THE } GARBAGEMAN;
    WRITELN; WRITELN;
    WRITELN(' * EVAL *');
    NEXTSYM;
    READEXPR(PTR);
{}  READLN(DUMMY);
    WRITELN;
  END;
2:WRITELN; WRITELN;
  WRITELN(' TOTAL NUMBER OF GARBAGE COLLECTIONS = ', NUMBEROFGCS:1,'.');
  WRITELN;
  WRITELN(' FREE NODES LEFT UPON EXIT = ', FREENODES:1, '.');
  WRITELN
END { OF LISP }.

