;;; -*- Mode: Lisp; Package: Prolog; Base: 10. ; -*-
;;; (C) Copyright 1983,1984,1985, Uppsala University

(defun remove-recursions-from-file (from-file-name to-file-name)
  (with-open-file (from from-file-name ':in)
    (with-open-file (to to-file-name ':out)
      (let ((eof (cons nil nil))
            (standard-output to))
        (do ((form (read from eof) (read from eof)))
            ((eq form eof))
          (cond ((and (consp form)
                      (eq (first form) 'defun)
                      (print (remove-recursions-from-defun form))))
                (t (print form))))))))

(defconst *remove-recursions* t
  "If non-nil tail-recursions are turned into iteration")

(defmacro deffun arguments
  "Just like Defun except that tail-recursion optimization is done if *remove-recursions*"
 (cond (*remove-recursions*
        (remove-recursions-from-defun (cons 'defun arguments)))
       (t (cons 'defun arguments))))

(defmacro deffsubst arguments
  "Just like Defsubst except that tail-recursion optimization is done if *remove-recursions*"
  ;;if there are any recursions left this becomes a defun
 (cond (*remove-recursions*
        (remove-recursions-from-defun (cons 'defsubst arguments)))
       (t (cons 'defun arguments))))

(defun contains (form x)
  (cond ((eq x form))
        ((consp form)
         (or (contains (first form) x) (contains (rest1 form) x)))))

(defvar *keep-original-definitions* nil
  "If non-nil then DEFFUN will put the original definition on the property list")

(defun keep-if-need-be (function-name form)
  (cond (*keep-original-definitions*
         `((defprop ,function-name ,form original-definition )))))

(defvar *new-names* () "The variable names to PSETQ in a tail recursive goto.")

;;; Begin changes for version 8.0.

(defun parse-defun (defun)
  (let* ((function-name (cadr defun))
	 (argument-list (ldiff (caddr defun) (memq '&aux (caddr defun))))
	 (body (cdddr defun))
	 declaration-part 
	 (pattern (cons function-name (default-pattern argument-list))))
    (let-if (or (stringp (car body))
		(eq 'declare (careful-first (car body))))
	    ((declaration-part (list (car body))) (body (cdr body)))
      (let-if (eq 'comment (caar body))
	      ((pattern (cons (get (car body) ':predicator) (get (car body) ':argument-list)))
	       (body (cdr body)))
	(values function-name argument-list (memq '&aux (caddr defun))
		declaration-part body pattern
		(mapcan #'(lambda (x)
			    (cond ((not (memq x lambda-list-keywords))
				   (list (careful-first x)))))
			argument-list))))))

(defun default-pattern (args)
  (cond ((eq '&rest (car args)) '(&rest))
	((eq '() (car args)) ())
	((memq (car args) lambda-list-keywords)
	 (default-pattern (cdr args)))
	(t (cons '* (default-pattern (cdr args))))))

(defvar *call-pattern* () "Predicator and argument list declaration of current frob.")

(defun remove-recursions-from-defun (form)
 ;;works by rebinding the lambda-variables and defining a macro which expands
 ;;to a SETQ and GO
  (multiple-value-bind (function-name argument-list AUX-PART
			declaration-part body *CALL-PATTERN* iterative-names)
      (parse-defun form)
    (LET* ((label-name (intern (string-append function-name "-Label")))
	   (defsubst-arguments
	     ;;The idea being to make deffsubst be safe w.r.t. choice of arg. names.
	     (and (eq (first form) 'defsubst)
		  (EQ (FIRST *CALL-PATTERN*) FUNCTION-NAME)	;don't do it for compiled guys
		  (mapcar #'careful-first argument-list)))	;predicates
	   (new-names
	     (new-names-for-arguments
	       iterative-names
	       (and (EQ (FIRST *CALL-PATTERN*) FUNCTION-NAME)	;don't do it for compiled guys
		    (local-variables-in-forms body))))
	   (new-body
	     (body-of-iterative-form function-name label-name new-names iterative-names body
				     defsubst-arguments AUX-PART)))
      (cond ((eq new-body body) ;;nothing could be done
	     `(progn 'compile
		     ,@(keep-if-need-be function-name form)
		     (,(first form)
			,function-name
			,ARGUMENT-LIST
			,@declaration-part
			,@(COND ((CDR AUX-PART) `((LET ,(CDR AUX-PART) ,@BODY)))
				(T BODY)))))
	    ((and (eq (first form) 'defsubst)
		  (contains-recursive-call-p function-name new-body))
	     (remove-recursions-from-defun (cons 'defun (rest1 form))))
	    (t `(progn 'compile
		       ,@(keep-if-need-be function-name form)
		       (,(first form)
			,function-name
			,(defsubst-arglist defsubst-arguments
					   (mapcar #'cons iterative-names new-names)
					   argument-list)
			,@declaration-part
			,@NEW-BODY)))))))

(defun body-of-iterative-form (function-name label-name *new-names* old-names
                               body defsubst-p AUX-PART)
 (let ((bindings
	 (mapcan
	     #'(lambda (new-name OLD-name)
		 (cond ((eq new-name OLD-name) nil)
		       ((not (atom new-name))
			(list (list (first OLD-name)
				    (first new-name))))
		       (t (list (list OLD-name new-name)))))
	     *new-names* OLD-names))
       (new-body (replace-iterative-calls-in-last function-name label-name body)))
   (cond ((eq (last-element new-body) (last-element body))
          body)  ;;no optimizations possible
         (t `((prog ,(and defsubst-p
			  (mapcan
			    #'(lambda (name old)
				(cond ((not (memq name lambda-list-keywords))
				       (list (list name (defsubstify old () defsubst-p))))))
			    *new-names* old-names))
		    ,label-name
		    (return (LET ,(APPEND BINDINGS (CDR AUX-PART)) ,@NEW-BODY))))))))

(DEFSUBST -P (D) (MEMQ D '(- *)))

(DEFSUBST +P (D) (NOT (-P D)))

(defun arg-matcher (list-or-list* CALL-PATTERN formals actuals)
  (cond ((EQ T (CAR CALL-PATTERN))
	 ;;NO CONTINUATION IN A TAIL-RECURSIVE :DETERMINISTIC :ALWAYS DEFFSUBST.
	 (arg-matcher list-or-list* (cdr CALL-PATTERN) FORMALS (cdr actuals)))
	((null formals) ()) ;;should really check actuals too...
        ((and (MEMQ '&REST *CALL-PATTERN*) (null (cdr formals)))
         (list*-if-neq
           (car formals)
           `(,(selectq list-or-list*
                (list 'prolog-list)
                (list* 'prolog-list*))
             ,@actuals)
	   ()
	   T))
        ((and (eq 'list* list-or-list*) (null (cdr actuals)))
         (list* (car formals)
                `(car ,(car actuals))
                (arg-matcher
                  list-or-list*
		  (cdr CALL-PATTERN)
                  (cdr formals)
                  `((cdr ,(car actuals))))))
        (t (list*-if-neq
             (car formals)
             (car actuals)
             (arg-matcher
	       list-or-list* (cdr CALL-PATTERN) (cdr formals) (cdr actuals))
	     (+P (car CALL-PATTERN))))))

(defun list*-if-neq (x y z &optional must-dereference)
  (let ((y (macroexpand
             (cond ((and (consp y) (eq 'list*-in-area (first y)) (null (rest3 y)))
                    (third y))
		   ((and (consp y) (eq 'prolog-list* (first y)) (null (rest2 y)))
                    (second y))
                   (t y)))))
    (cond ((eq x y) z)
	  ((AND MUST-DEREFERENCE
		(not (memq (careful-first y)
			   '(car cdr prolog-cons prolog-list prolog-list*
				 cons-in-area list-in-area list*-in-area))))
	   `(,X (%DEREFERENCE ,Y) ,@Z))
	  (t `(,x ,y ,@z)))))

(defun make-macro-application (list-or-list* label-name arguments)	;8
  (let* ((bindings (arg-matcher list-or-list* (cdr *CALL-PATTERN*) *new-names* arguments)))
    (cond ((null bindings) `(go ,label-name))
          (t `(progn (psetq ,@bindings)
                     (go ,label-name))))))

(defvar *special-forms-that-return-last* '(and or progn let let*))  

(defun replace-iterative-calls-with-macro-2 (function-name label-name form)
 ;;this replaces all tail recursive calls to function-name with label-name
  (cond ((atom form) form)
	(t (let ((function (car form)) (arguments (cdr form)))
             (cond ((eq function function-name)
                    (make-macro-application 'list label-name arguments))
                   ((eq function 'quote) form)
                   ((eq function 'cond)
                    (cons 'cond
                          (mapcar
                            #'(lambda (clause)
                                (cond ((eq clause (car (last arguments)))
                                       (replace-iterative-calls-in-last
                                         function-name label-name clause))
                                      (t (cons (first clause)
                                               (replace-iterative-calls-in-last
                                                 function-name label-name (rest1 clause))))))
                                arguments)))
                   ((memq function *special-forms-that-return-last*)
                    (cons function
                          (replace-iterative-calls-in-last function-name
                                                           label-name
                                                           arguments)))
                   ((eq function 'funcall)
                    (cond ((RECURSIVE-CALL-P arguments)
                           (make-macro-application 'list
                                                   label-name
                                                   (rest1 arguments)))
                          (t form)))
                   ((EQ FUNCTION 'LEXPR-FUNCALL)
                    (cond ((RECURSIVE-CALL-P arguments)
                           (make-macro-application 'list*
                                                   label-name
                                                   (rest1 arguments)))
                          (t form)))
                   ((eq function 'prog)
                    (list* function (first arguments)
                           (mapcar
                             #'(lambda (form)
                                 (replace-iterative-calls-with-macro
                                   (list 'inside-prog function-name)
                                   label-name
                                   form))
                             (rest1 arguments))))
                   ((and (eq function 'return)
                         (consp function-name)
                         (eq (first function-name) 'inside-prog))
                    ;;if its a recursive call in the return then flush the return
                    (cond ((and (consp (first arguments)) (null (rest1 arguments)))
                           (list 'return
                                 (replace-iterative-calls-with-macro
                                   (second function-name)
                                   label-name
                                   (first arguments))))
                          (t form)))
                   ((symbolp function)
                    (let ((expansion (macroexpand form)))
                      (cond ((eq expansion form) ;;didnt expand
                             form)
                            (t (let ((new-form
                                       (replace-iterative-calls-with-macro
                                         function-name
                                         label-name
                                         expansion)))
                                 (cond ((eq new-form expansion)
                                        ;;didnt help to expand it
                                        form)
                                       (t new-form)))))))
                   ((eq (first function) 'lambda)
                    (cons
                      (list* 'lambda
                             (second function)
                             (replace-iterative-calls-in-last
                               function-name
                               label-name
                               (rest2 function)))
                      arguments))
                   (t form))))))

(DEFUN RECURSIVE-CALL-P (FORM)
  (COND ((CONSP (CAR FORM))
	 (SELECTQ (CAAR FORM)
	   ((QUOTE FUNCTION) (EQ (CADAR FORM) (CAR *CALL-PATTERN*)))
	   (CURRENT-ENTRYPOINT
	    (AND (EQUAL (CADAR FORM) `',(CAR *CALL-PATTERN*))
		 #+LEXICAL (TRIVIAL-CONTINUATION-P (CADR FORM))
		 (OR (NEQ T (CADR *CALL-PATTERN*))
		     (EQUAL #-LEXICAL ''(TRUE) #+LEXICAL #'TRUE (MACROEXPAND (CADR FORM))))))))))

;;; End changes for version 8.0.

(defun defsubst-arglist (defsubst-arguments mapping argument-list)
  (cond ((null argument-list) ())
	((memq (car argument-list) lambda-list-keywords)
	 (cons (car argument-list)
	       (defsubst-arglist defsubst-arguments mapping (cdr argument-list))))
	(t (cons (defsubstify (car argument-list) mapping defsubst-arguments)
		 (defsubst-arglist defsubst-arguments mapping (cdr argument-list))))))

(defun defsubstify (x mapping defsubst-arguments)
  (cond ((consp x)
	 (cons (defsubstify (car x) mapping defsubst-arguments)
	       (defsubstify (cdr x) mapping defsubst-arguments)))
	((memq x defsubst-arguments)
	 (intern (string-append x "-defsubst")))
	((assq x mapping) (cdr (assq x mapping)))
	(x)))

(defun new-names-for-arguments (iterative-names local-variables)
 (cond ((null iterative-names) nil)
       (t (let ((name (first iterative-names)))
	    (cons (iterative-variable-name name local-variables)
		  (new-names-for-arguments (rest1 iterative-names)
					   local-variables))))))

(defun local-variables-in-forms (forms)
  (cond ((null forms) nil)
        (t (append (local-variables-in-form (first forms))
                   (local-variables-in-forms (rest1 forms))))))

(defun local-variables-in-form (form)
 ;;this returns all LAMBDA, DO, and PROG variables in form
  (cond ((atom form) nil)
	(t (let ((first (car form)) (rest (cdr form)))
	     (selectq first
	       ((LET LET* DO DO* PROG PROG*)
		;;if there are local variables in the SECOND of the do
		;;then this will not see them
		(cond ((and (eq first 'prog) (first rest) (atom (first rest)))
		       ;;named prog, system-93
		       (append (mapcar #'careful-first (second rest))
			       (local-variables-in-forms (rest2 rest))))
		      (t
		       (append (mapcar #'careful-first (first rest))
			       (local-variables-in-forms (rest1 rest))))))
	       ((DO-NAMED DO*-NAMED)
		(append (mapcar #'careful-first (second rest))
			(local-variables-in-forms (rest2 rest))))
	       ((LAMBDA)
		(append (first rest) ;;this will copy the orginal
			(local-variables-in-forms (rest1 rest))))
	       ((QUOTE COMMENT) nil)
	       (t (let ((new-form (macroexpand form)))
		    (cond ((neq form new-form)
			   (local-variables-in-form new-form))))))))))
  
(defun iterative-variable-name (name local-variables)
  (cond ((not (atom name))
         (let ((first-name
                 (iterative-variable-name (first name) local-variables))
               (second-name
                 (iterative-variable-name (second name) local-variables)))
           (cond ((and (eq (first name) first-name)
                       (eq (second name) second-name))
                  name)
                 (t (list* first-name second-name (rest2 name))))))
        ((memq name local-variables)
        ;;to avoid possible name conflicts must rename the variable 
         (intern (string-append name "-external")))
        (t name)))
   
(defun replace-iterative-calls-with-macro (function-name label-name form)
 ;;this replaces all tail recursive calls to function-name with label-name
 (let ((new-form
         (replace-iterative-calls-with-macro-2 function-name label-name form)))
   (cond ((equal form new-form) form)
         (t new-form))))

(defun replace-iterative-calls-in-last (function-name label-name clauses)
 (cond ((null clauses) clauses)
       ((null (rest1 clauses)) ;;its the last one
        (list
          (replace-iterative-calls-with-macro function-name
                                              label-name
                                              (first clauses))))
       (t (cons (first clauses)
                (replace-iterative-calls-in-last function-name
                                                 label-name
                                                 (rest1 clauses))))))

(defun contains-recursive-call-p (function body)
  ;;this should be a good enough approximation though
  ;;special forms can make this return T when its not recursive
  (contains body function))
