;;; -*- Mode:LISP; Package:USER; Base:10; Readtable:CL -*-

(define-standard-structure
  continuation
   previous-continuation
   fctn
   machine-state)
  
(define-standard-structure
  history-subproblem
   valid-flag
   previous-subproblem
   next-subproblem
   reductions)

(define-standard-structure
  history-reduction
   valid-flag
   previous-reduction
   next-reduction
   continuation)

(define-standard-structure
  history
   current-subproblem
   current-reduction)

(defun create-double-linked-structures (size maker linker)
  (let ((elements '()))
    (dotimes (count size)
      (push (funcall maker) elements))
    (do ((count 0 (1+ count))
	 (skeleton (apply #'circular-list elements) (rest skeleton)))
	((= count size) elements)
      (let ((this-element (first  skeleton))
	    (next-element (second skeleton)))
	(funcall linker this-element next-element)))))

(defun create-reduction-ring (size)
  (create-double-linked-structures
    size
    #'(lambda ()
	(make-history-reduction
	  :valid-flag nil
	  :previous-reduction   nil
	  :next-reduction       nil
	  :continuation         nil))
    #'(lambda (this-reduction next-reduction)
	(setf (history-reduction-next-reduction     this-reduction) next-reduction)
	      (history-reduction-previous-reduction next-reduction) this-reduction)))

(defun create-subproblem-ring (size reduction-ring-size)
  (create-double-linked-structures
    size
    #'(lambda ()
	(make-history-subproblem
	  :valid-flag nil
	  :previous-subproblem   nil
	  :next-subproblem       nil
	  :reductions            (first (create-reduction-ring reduction-ring-size))))
    #'(lambda (this-subproblem next-subproblem)
	(setf (history-subproblem-next-subproblem     this-subproblem) next-subproblem
	      (history-subproblem-previous-subproblem next-subproblem) this-subproblem))))

(defun create-history (subproblems reductions)
  (let ((s-ring (create-subproblem-ring subproblems reductions)))
    (make-history
      :current-subproblem (first s-ring)
      :current-reduction  (history-subproblem-reductions (first s-ring)))))

(defun spread-history (history receiver)
  (funcall receiver
	   (history-current-subproblem history)
	   (history-current-reduction history)))

(defun rotate-reduction-ring (direction history)
  (let ((next-reduction
	  (funcall direction
	    (history-current-reduction history))))
    (setf (history-current-reduction history) next-reduction)
    next-reduction))

(defun rotate-subproblem-ring (direction history)
  (spread-history history
    #'(lambda (current-subproblem current-reduction)
	(setf (history-subproblem-reductions current-subproblem) current-reduction)
	(let ((next-subproblem (funcall direction current-subproblem)))
	  (setf (history-current-subproblem history) next-subproblem
		(history-current-reduction  history) (history-subproblem-reductions next-subproblem))
	  next-subproblem))))

(defvar *maximum-subproblems-to-record* 10.)
(defvar *maximum-reductions-to-record*   5.)

(defvar *history*)

(defun print-cont-result (values)
  (inspect values)
  (format t "~%Returning to LISP~% ~S" values))

(defun startup (fctn arglist)
  (setq *history* (create-history *maximum-subproblems-to-record*
				  *maximum-reductions-to-record*))
  (top-level-continuation-driver
    fctn
    (make-continuation
      :previous-continuation ()
      :fctn                  #'print-cont-result
      :machine-state         ())
    arglist))

(defun top-level-continuation-driver (initial-fctn initial-continuation initial-state)
  (do ((fctn         initial-fctn)
       (continuation initial-continuation)
       (state        initial-state))
      ((null continuation) (apply fctn state))
    (let ((next-continuation
	    (catch 'continue
	      (apply fctn continuation state))))
; Debugging
;      (format t "~%Received ~S ~S ~S"
;	      (continuation-fctn                  next-continuation)
;	      (continuation-machine-state         next-continuation)
;	      (continuation-previous-continuation next-continuation))
      (setq fctn         (continuation-fctn                  next-continuation)
	    continuation (continuation-previous-continuation next-continuation)
            state        (continuation-machine-state         next-continuation)))))

(defun do-reduction (fctn continuation arglist)
  (let ((reduction-continuation
	  (make-continuation
	    :previous-continuation continuation
	    :fctn                  fctn
	    :machine-state         arglist)))
    (let ((reduction (rotate-reduction-ring #'history-reduction-next-reduction *history*)))
      (setf (history-reduction-continuation reduction) reduction-continuation
	    (history-reduction-valid-flag   reduction) t))
    (throw 'continue reduction-continuation)))

(defun do-subproblem (subproblem-fctn subproblem-args
		      current-continuation
		      return-fctn return-args)
  (let ((return-continuation (make-continuation
			       :previous-continuation current-continuation
			       :fctn                  return-fctn
			       :machine-state         return-args))
	(next-subproblem (rotate-subproblem-ring #'history-subproblem-next-subproblem *history*)))
    (setf (history-subproblem-valid-flag next-subproblem) t
	  (history-reduction-valid-flag (history-subproblem-reductions next-subproblem)) nil)
    (do-reduction subproblem-fctn return-continuation subproblem-args)))

(defun do-return (continuation return-value)
  (setf (continuation-machine-state continuation)
	   (cons return-value (continuation-machine-state continuation))
	(history-subproblem-valid-flag (history-current-subproblem *history*)) nil)
  (rotate-subproblem-ring #'history-subproblem-previous-subproblem *history*)
  (throw 'continue continuation))

(defun scode-eval (cont object environment)
  (send object :scode-eval cont environment))

(defun scode-apply (cont object arglist)
  (send object :scode-apply cont arglist))

(defflavor s-object () ()
  (:required-methods :scode-eval :scode-apply))

;;; Self evaluating

(defflavor self-eval-mixin () ())

(defmethod (self-eval-mixin :scode-eval) (cont environment)
  (declare (ignore environment))
  (do-return cont self))

(defflavor apply-error-mixin () ())

(defmethod (apply-error-mixin :scode-apply) (cont arglist)
  (declare (ignore cont arglist))
  (ferror 'nil "Application of non-procedure-object ~S." self))

(defflavor external-lisp-object (external-object) ()
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)
  
;;; Numbers

(defflavor number () (external-lisp-object self-eval-mixin apply-error-mixin))

;;; Symbols

(defflavor scheme-symbol (lisp-symbol) (apply-error-mixin self-eval-mixin s-object))

;;; External list structure (used to make read easier)

(defflavor external-list-structure () (external-lisp-object self-eval-mixin apply-error-mixin s-object))

(defmethod (external-list-structure spread) (if-null if-symbol if-number if-list)
  (cond ((null?   external-object)
	      (funcall if-null))
	((number? external-object)
	      (funcall if-number (make-instance 'number :external-object external-object)))
	((symbol? external-object)
	      (funcall if-symbol (make-instance 'scheme-symbol :external-object external-object)))
	((pair?   external-object)
	      (funcall if-list
		       (make-instance 'external-list-structure :external-object (car external-object))
		       (make-instance 'external-list-structure :external-object (cdr external-object))))))

;;; Quoted

(defflavor quoted (object) (apply-error-mixin s-object)
  :gettable-instance-variables
  :initable-instance-variables)

(defmethod (quoted :scode-eval) (cont environment)
  (declare (ignore environment))
  (do-return cont object))

;;; Procedure

(defflavor procedure (environment lambda) (self-eval-mixin s-object)
  :gettable-instance-variables
  :initable-instance-variables)
  
(defmethod (procedure :scode-apply) (cont arglist)
  (do-reduction cont
    #'scode-eval (list (send lambda :body) (make-environment lambda arglist))))

;;; Lambda

(defflavor lambda (bound-variables body) (apply-error-mixin s-object)
  :gettable-instance-variables
  :initable-instance-variables)

(defmethod (lambda :scode-eval) (cont environment)
  (do-return cont (make-instance 'procedure
				 :environment environment
				 :lambda      self)))

;;; Primitive

(defflavor primitive-procedure () (external-lisp-object self-eval-mixin s-object)
  :gettable-instance-variables
  :initable-instance-variables)

(defmethod (primitive-procedure :scode-apply) (cont arglist)
  (do-return cont (apply external-object arglist)))

;;; Delay and delayed

(defflavor delayed (environment-or-flag code-or-value)
	   (apply-error-mixin self-eval-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

(defflavor delay (form) (apply-error-mixin s-object)
  :gettable-instance-variables
  :initable-instance-variables)

(defmethod (delay :scode-eval) (cont environment)
  (do-return cont (make-instance 'delayed
				 :environment-or-flag environment
				 :code-or-value       self)))

;;; Continuations

(defflavor scode-continuation () (external-lisp-object self-eval-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

(defmethod (scode-continuation :scode-apply) (cont arglist)
  (declare (ignore cont))
  (do-return external-object arglist))

;;; The environment

(defflavor the-environment () (apply-error-mixin s-object))

(defmethod (the-environment :scode-eval) (cont environment)
  (do-return cont environment))
 
;;; Sequence

(defflavor sequence (first-form second-form) (apply-error-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

(defmethod (sequence :scode-eval) (cont environment)
  (do-subproblem #'scode-eval (list first-form environment)
		 cont
		 #'scode-eval (list second-form environment)))

;;; Bindings and Environments

(defflavor binding (name shadowed? value) (apply-error-mixin self-eval-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

(defflavor environment (internal-environment) (apply-error-mixin self-eval-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)
  
(defun make-environment (procedure &rest args)
  (make-instance 'environment
		 :internal-environment
		 (apply #'vector
			'()
			'()
			(map 'list #'(lambda (name value)
				       (make-instance 'binding
						      :name      name
						      :shadowed? nil
						      :value     value))
			     (send procedure :bound-variables)
			     (cons procedure args)))))

(defmacro environment-incrementals (internal-environment)
  `(elt ,internal-environment 0))

(defmacro environment-potentially-shadowed (internal-environment)
  `(elt ,internal-environment 1))

(defmethod (environment :environment-parent) ()
  (send (elt internal-environment 2) :environment))
	
(defmethod (environment :locate-in-frame) (symbol if-found if-not-found)
  (block quit
    (do ((index 0 (1+ index)))
	((= index (length internal-environment)) (return-from quit (funcall if-not-found)))
      (let ((binding (elt internal-environment index)))
	(when (eq? (send binding :name) symbol)
	  (return-from quit (funcall if-found index binding)))))
    (dolist (inc (environment-incrementals internal-environment))
      (when (eq? (send inc :name) symbol)
	(return-from quit (funcall if-found 0 inc))))))

(defmethod (environment :cached-lookup) (c-var depth offset)
  (let ((symbol (send c-var :symbol)))
    (labels ((deep-search (e)
	       (send e :locate-in-environment symbol
		 #'(lambda (depth offset binding)
		     (send c-var :set-depth depth)
		     (send c-var :set-offset offset)
		     binding)
		 #'(lambda () (ferror 'nil "Broken C-Variable")))))
      (do ((d depth (1- depth))
	   (e self (send self :environment-parent)))
	  ((zero? d)
	   (let ((candidate
		   (if (zero? offset)
		       (assoc symbol (environment-incrementals (send e :internal-environment)))
		       (elt (send e :internal-environment) offset))))
	     (if (or (null? candidate)
		     (send candidate :shadowed?))
		 (deep-search self)
		 candidate)))))))

(defmethod (environment :incremental-bind) (symbol value)
  (let ((lself self)
	(linternal-environment internal-environment));; lexical self
    (send self :locate-in-frame
	  #'(lambda (offset binding)
	      (declare (ignore offset))
	      (send binding :set-value value))
	  #'(lambda ()
	      (let ((binding
		      (make-instance 'binding
			:name symbol
			:shadowed?
			      (if (member symbol
					  (environment-potentially-shadowed
					    linternal-environment))
				  t
				  nil))))
		(setf (environment-incrementals linternal-environment)
		      (cons binding (environment-incrementals linternal-environment)))
		(do ((e (send lself :environment-parent) (send e :environment-parent)))
		    ((null? e) nil)
		  (pushnew symbol
			   (environment-incrementals (send e :internal-environment)))))))))

(defmethod (environment :locate-in-enviroment) (symbol if-found if-not-found)
  (let ((depth 0)
	(lself self))
    (block quit
      (tagbody search
	  (if (null? internal-environment)
	      (funcall if-not-found)
	      (send lself :locate-in-frame symbol
		    #'(lambda (offset-in-frame binding)
			(return-from quit (funcall if-found depth offset-in-frame binding)))
		    #'(lambda ()
			(setq lself (send lself :environment-parent))
			(setq depth (1+ depth))
			(go search))))))))

;;; variables

;all the work is done by the environment.

(defflavor c-variable (symbol depth offset) (apply-error-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

(defmethod (c-variable :lookup) (environment if-found if-not-found)
  (let ((lself self))
    (if (null? depth)
	(send environment :locate-in-environment symbol
	      #'(lambda (new-depth new-offset binding)
		  (send lself :set-depth new-depth)
		  (send lself :set-offset new-offset)
		  (funcall if-found binding))
	      #'(lambda ()
		  (funcall if-not-found)))
	(send environment :cached-lookup self))))

(defmethod (c-variable :scode-eval) (cont environment)
  (let ((binding (send self :lookup environment
		       #'values
		       #'(lambda () (ferror 'nil "Unbound variable")))))
    (do-return cont (send binding :value))))

;;; Set!

(defflavor assignment (identifier value) (apply-error-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

(defmethod (assignment :scode-eval) (cont environment)
  (labels ((assign (cont computed-value id)
	     (let ((binding (send id :lookup
			      #'values
			      #'(lambda () (ferror 'nil "Unbound variable")))))
	       (let ((old-value (send binding :value)))
		 (send binding :set-value computed-value)
		 (do-return cont old-value)))))
    (do-subproblem #'scode-eval (list value environment)
		   cont
		   #'assign (list identifier))))

;;; Define

(defflavor definition (identifier value) (apply-error-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

(defmethod (definition :scode-eval) (cont environment)
  (labels ((define (cont computed-value id)
	     (let ((symbol id))
	       (send environment :incremental-bind symbol computed-value)
	       (do-return cont symbol))))
    (do-subproblem #'scode-eval (list value environment)
		   cont
		   #'define (list identifier))))
  
;;; Combination

(defflavor combination (guts) (apply-error-mixin s-object)
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)

;(defmethod (combination :scode-eval) (cont environment)
;  (labels ((accumulate-args (cont args)
;	     (format t "Accumulating ~S" args)
;	     (if (null? args)
;		 (do-return cont '())
;		 (do-subproblem
;		   #'accumulate-args (list (rest args))
;		   cont
;		   #'(lambda (cont evaled-args)
;		       (do-subproblem
;			 #'scode-eval (cons (first args) environment)
;			 cont
;			 #'(lambda (cont evaled)
;			     (do-return cont (cons evaled evaled-args))) '())) '()))))
;    (accumulate-args
;      #'(lambda (vals)
;	  (do-reduction cont #'scode-apply vals))
;      guts)))


(defmethod (combination :scode-eval) (cont environment)
  (labels (
    (accumulate-args (cont evaled unevaled)
      (if (null? unevaled)
	  (let ((revaled (reverse evaled)))
	    (let ((operator (first revaled))
		  (operands (rest  revaled)))
	      (do-reduction #'scode-apply cont (list operator operands))))
	  (do-subproblem #'scode-eval (list (first unevaled) environment)
			 cont
			 #'(lambda (cont evaled-arg)
			     (do-reduction #'accumulate-args cont
					   (list (cons evaled-arg evaled) (rest unevaled)))) '()))))
    (accumulate-args cont '() guts)))

;;; Simple syntaxer written in Common Lisp.

(defvar *external-syntax-table* '())

(defun external-syntax (expression)
  (if (symbol? expression)
      (syntax-variable expression)
      (let ((syntaxer-pair (assq (first expression) *external-syntax-table*)))
	(if (null? syntaxer-pair)
	    (syntax-combination expression)
	    (apply (cadr syntaxer-pair) (rest expression))))))

(defun syntax-variable (expression)
  (make-instance 'c-variable
		 :symbol expression
		 :depth  nil
		 :offset nil))

(defun syntax-combination (expression)
  (make-instance 'combination
		 :guts (mapcar #'external-syntax expression)))


(defun syntax-define (name value)
  (make-instance 'definition
		 :identifier name
		 :value (external-syntax value)))

(push (list 'define #'syntax-define) *external-syntax-table*)

(defun syntax-set (name value)
  (make-instance 'assignment
		 :identifier name
		 :value (external-syntax value)))

(push (list 'set! #'syntax-set) *external-syntax-table*)

(defun syntax-named-lambda (bound-variables body)
  (make-instance 'lambda
		 :bound-variables bound-variables
		 :body (external-syntax body)))

(push (list 'named-lambda #'syntax-named-lambda) *external-syntax-table*)

(defun syntax-sequence (&rest expressions)
  (labels
    ((syntax-sequence-internal (elist)
       (let ((exp1 (external-syntax (first elist)))
	     (others (rest  elist)))
       (if (null? others)
	   exp1
	   (make-instance 'sequence
			  :first-form exp1
			  :second-form (syntax-sequence-internal others))))))
    (syntax-sequence-internal expressions)))

(push (list 'sequence #'syntax-sequence) *external-syntax-table*)

(defun syntax-quote (object)
  (make-instance 'external-list-structure
		 :external-object object))

(push (list 'quote #'syntax-quote) *external-syntax-table*)

(defun syntax-the-environment ()
  (make-instance 'the-environment))

(push (list 'the-environment #'syntax-the-environment) *external-syntax-table*)

